Full Code of naihe2010/apvlv for AI

master 621b81ea3844 cached
106 files
488.9 KB
134.8k tokens
221 symbols
1 requests
Download .txt
Showing preview only (519K chars total). Download the full file or copy to clipboard to get everything.
Repository: naihe2010/apvlv
Branch: master
Commit: 621b81ea3844
Files: 106
Total size: 488.9 KB

Directory structure:
gitextract_tk5z3yo1/

├── .clang-format
├── .dir-locals.el
├── .github/
│   └── workflows/
│       └── cmake-multi-platform.yml
├── .gitignore
├── AUTHORS
├── CMakeLists.txt
├── COPYING
├── NEWS
├── README.md
├── THANKS
├── TODO
├── apvlv.1
├── apvlvrc.example
├── cmake/
│   ├── CompileFlags.cmake
│   ├── Dependencies.cmake
│   └── Options.cmake
├── scripts/
│   ├── build.ps1
│   └── build.sh
├── share/
│   ├── applications/
│   │   └── apvlv.desktop
│   └── scripts/
│       └── internal.js
├── src/
│   ├── ApvlvCmds.cc
│   ├── ApvlvCmds.h
│   ├── ApvlvCompletion.cc
│   ├── ApvlvCompletion.h
│   ├── ApvlvDirectory.cc
│   ├── ApvlvDirectory.h
│   ├── ApvlvDired.cc
│   ├── ApvlvDired.h
│   ├── ApvlvEditor.cc
│   ├── ApvlvEditor.h
│   ├── ApvlvFile.cc
│   ├── ApvlvFile.h
│   ├── ApvlvFileIndex.cc
│   ├── ApvlvFileIndex.h
│   ├── ApvlvFileWidget.cc
│   ├── ApvlvFileWidget.h
│   ├── ApvlvFrame.cc
│   ├── ApvlvFrame.h
│   ├── ApvlvImageWidget.cc
│   ├── ApvlvImageWidget.h
│   ├── ApvlvInfo.cc
│   ├── ApvlvInfo.h
│   ├── ApvlvLab.cc
│   ├── ApvlvLab.h
│   ├── ApvlvLog.cc
│   ├── ApvlvLog.h
│   ├── ApvlvMarkdown.cc
│   ├── ApvlvMarkdown.h
│   ├── ApvlvNote.cc
│   ├── ApvlvNote.h
│   ├── ApvlvNoteWidget.cc
│   ├── ApvlvNoteWidget.h
│   ├── ApvlvOCR.cc
│   ├── ApvlvOCR.h
│   ├── ApvlvParams.cc
│   ├── ApvlvParams.h
│   ├── ApvlvQueue.cc
│   ├── ApvlvQueue.h
│   ├── ApvlvSearch.cc
│   ├── ApvlvSearch.h
│   ├── ApvlvSearchDialog.cc
│   ├── ApvlvSearchDialog.h
│   ├── ApvlvUtil.cc
│   ├── ApvlvUtil.h
│   ├── ApvlvView.cc
│   ├── ApvlvView.h
│   ├── ApvlvWeb.cc
│   ├── ApvlvWeb.h
│   ├── ApvlvWebViewWidget.cc
│   ├── ApvlvWebViewWidget.h
│   ├── ApvlvWidget.cc
│   ├── ApvlvWidget.h
│   ├── ApvlvWindow.cc
│   ├── ApvlvWindow.h
│   ├── CMakeLists.txt
│   ├── Engines.cmake
│   ├── Sources.cmake
│   ├── WindowsEngines.cmake
│   ├── config.h.in
│   ├── file/
│   │   ├── ApvlvAxOffice.cc
│   │   ├── ApvlvAxOffice.h
│   │   ├── ApvlvDjvu.cc
│   │   ├── ApvlvDjvu.h
│   │   ├── ApvlvEpub.cc
│   │   ├── ApvlvEpub.h
│   │   ├── ApvlvFb2.cc
│   │   ├── ApvlvFb2.h
│   │   ├── ApvlvHtm.cc
│   │   ├── ApvlvHtm.h
│   │   ├── ApvlvImage.cc
│   │   ├── ApvlvImage.h
│   │   ├── ApvlvLibreOffice.cc
│   │   ├── ApvlvLibreOffice.h
│   │   ├── ApvlvMuPdf.cc
│   │   ├── ApvlvMuPdf.h
│   │   ├── ApvlvPopplerPdf.cc
│   │   ├── ApvlvPopplerPdf.h
│   │   ├── ApvlvQtPdf.cc
│   │   ├── ApvlvQtPdf.h
│   │   ├── ApvlvTxt.cc
│   │   └── ApvlvTxt.h
│   ├── main.cc
│   └── testNote.cc
├── vcpkg-manifests/
│   └── system-qt/
│       └── vcpkg.json
├── vcpkg.json
└── zh_CN.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .clang-format
================================================
BasedOnStyle: GNU
ColumnLimit: 78
AllowShortFunctionsOnASingleLine: Empty
AllowShortLambdasOnASingleLine: Empty


================================================
FILE: .dir-locals.el
================================================
((nil (eval . (setq-local flycheck-clang-include-path
                          '("/usr/include/poppler/glib"
                            "/usr/include/glib-2.0"
                            "/usr/lib64/glib-2.0/include"
                            "/usr/include/cairo"
                            "/usr/include/pixman-1"
                            "/usr/include/freetype2"
                            "/usr/include/libxml2"
                            "/usr/include/poppler"
                            "/usr/include/pango-1.0"
                            "/usr/include/glib-2.0"
                            "/usr/include/gtk-3.0"
                            "/usr/include/gdk-pixbuf-2.0"
                            "/usr/include/gio-unix-2.0"
                            "/usr/include/atk-1.0")))))


================================================
FILE: .github/workflows/cmake-multi-platform.yml
================================================
# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform.
# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml
name: CMake on multiple platforms

on:
  push:
    branches: ["**"]
  pull_request:
    branches: ["master"]

jobs:
  build-linux:
    runs-on: ${{ matrix.os }}
    container: ${{ matrix.container }}

    strategy:
      # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable.
      fail-fast: false

      matrix:
        include:
          - os: ubuntu-latest
            container: fedora:43
          - os: ubuntu-latest
            container: debian:13
          - os: ubuntu-latest
            container: rockylinux:9
          - os: ubuntu-latest
            container: archlinux:latest

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Prepare (Fedora 43)
        if: matrix.container == 'fedora:43'
        run: |
          dnf install -y cmake cmark gcc-c++ \
            qt6-qtbase-devel qt6-qtwebengine-devel \
            qt6-qtpdf-devel ps2pdf libreofficekit-devel \
            quazip-qt6-devel poppler-qt6-devel \
            cmark-devel djvulibre-devel \
            mupdf-devel tesseract-devel \
            man ghostscript qt6-linguist \
            rpmbuild
          mkdir -p $GITHUB_WORKSPACE/share/doc/apvlv/translations

      - name: Prepare (Debian 13)
        if: matrix.container == 'debian:13'
        run: |
          apt-get update
          apt-get install -y cmake cmark g++ \
            qt6-base-dev qt6-webengine-dev \
            qt6-pdf-dev libreofficekit-dev \
            libquazip1-qt6-dev libpoppler-qt6-dev \
            libcmark-dev libdjvulibre-dev \
            libharfbuzz-dev \
            libmupdf-dev libtesseract-dev \
            man groff ghostscript qt6-tools-dev qt6-l10n-tools \
            pkg-config
          export PATH=/usr/lib/qt6/bin:$PATH
          mkdir -p $GITHUB_WORKSPACE/share/doc/apvlv/translations

      - name: Prepare (RockyLinux 9)
        if: matrix.container == 'rockylinux:9'
        run: |
          dnf install -y epel-release
          dnf config-manager --enable crb devel
          dnf install -y --nogpgcheck https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-$(rpm -E %rhel).noarch.rpm
          dnf install -y --nogpgcheck https://mirrors.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-$(rpm -E %rhel).noarch.rpm
          dnf install -y cmake cmark gcc-c++ \
            qt6-qtbase-devel qt6-qtwebengine-devel \
            qt6-qtpdf-devel libreofficekit-devel \
            cmark-devel djvulibre-devel \
            mupdf-devel tesseract-devel \
            man ghostscript qt6-linguist
            # quazip-qt6-devel poppler-qt6-devel
          mkdir -p $GITHUB_WORKSPACE/share/doc/apvlv/translations

      - name: Prepare (Arch Linux)
        if: matrix.container == 'archlinux:latest'
        run: |
          pacman -Syu --noconfirm
          pacman -S --noconfirm base-devel cmake make gcc \
            qt6-base qt6-webengine \
            libreoffice-fresh-sdk jdk-openjdk \
            quazip-qt6 poppler-qt6 \
            pkg-config \
            cmark djvulibre \
            mupdf mujs tesseract \
            man-db groff ghostscript qt6-tools \
            gtk3
          mkdir -p $GITHUB_WORKSPACE/share/doc/apvlv/translations

      - name: Build (Linux)
        run: |
          test -d /usr/lib/qt6/bin && export PATH=/usr/lib/qt6/bin:$PATH
          cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
          cmake --build build

  build-windows:
    name: Build (Windows)
    runs-on: windows-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Build with script
        run: ./scripts/build.ps1



================================================
FILE: .gitignore
================================================
/build
*~
cscope.files
cscope.out
/.idea
/.vscode
/.cache
share/doc/apvlv/translations/*
share/doc/apvlv/Startup.pdf


================================================
FILE: AUTHORS
================================================
Alf <naihe2010@126.com>


================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.16)
project(apvlv VERSION 0.7.0)

# vcpkg integration
if(DEFINED CMAKE_TOOLCHAIN_FILE)
    message(STATUS "Using vcpkg toolchain: ${CMAKE_TOOLCHAIN_FILE}")
endif()

# Include cmake modules
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(Options)
include(CompileFlags)
include(Dependencies)

# Version info
find_package(Git)
if(Git_FOUND)
    execute_process(
        COMMAND ${GIT_EXECUTABLE} log -1 --format=%h
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
        OUTPUT_VARIABLE VERSION_HASH
        OUTPUT_STRIP_TRAILING_WHITESPACE
    )
    if(VERSION_HASH)
        message(STATUS "Git hash: ${VERSION_HASH}")
        set(PROJECT_PATCH ${VERSION_HASH})
    endif()
endif()

# Package definitions
if(WIN32)
    # On Windows, use AppData\Local or install directory
    set(SYSCONFDIR "$ENV{LOCALAPPDATA}/apvlv" CACHE PATH "System config directory")
else()
    set(SYSCONFDIR "/etc" CACHE PATH "System config directory")
endif()
add_definitions(-DSYSCONFDIR="${SYSCONFDIR}")

add_definitions(-DPACKAGE_NAME="apvlv"
    -DPACKAGE_VERSION="${PROJECT_VERSION}"
    -DPACKAGE_BUGREPORT="Alf <naihe2010@126.com>"
    -DRELEASE="rel"
)

ADD_SUBDIRECTORY(src)

IF (NOT WIN32)
    ADD_CUSTOM_TARGET(CopyCompileCommands ALL
            COMMAND ${CMAKE_COMMAND} -E copy_if_different
            "${CMAKE_BINARY_DIR}/compile_commands.json"
            "${CMAKE_SOURCE_DIR}/compile_commands.json"
            COMMENT "Copying compile_commands.json to ${CMAKE_SOURCE_DIR}"
            DEPENDS ${CMAKE_BINARY_DIR}/compile_commands.json
    )
ENDIF ()

INSTALL(DIRECTORY share DESTINATION ".")
IF (WIN32)
    INSTALL(FILES apvlvrc.example DESTINATION ".")
ELSE (WIN32)
    ADD_CUSTOM_TARGET(Startup.pdf
            ALL
            COMMAND "man" "-t" "${CMAKE_SOURCE_DIR}/apvlv.1" "|" "ps2pdf" "-" "${CMAKE_SOURCE_DIR}/share/doc/apvlv/Startup.pdf"
            DEPENDS apvlv.1)
    INSTALL(FILES apvlvrc.example DESTINATION ${SYSCONFDIR})
    INSTALL(FILES apvlv.1 TYPE MAN)
ENDIF (WIN32)

SET(CPACK_PACKAGE_VENDOR "Alf")
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "apvlv - Alf's PDF/DJVU/EPUB Viewer like Vim")
SET(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_MAJOR})
SET(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_MINOR})
SET(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_PATCH})

IF (UNIX)
    SET(CPACK_SET_DESTDIR ON)
    SET(CPACK_PACKAGE_CONTACT "Alf <naihe2010@126.com>")
    SET(CPACK_GENERATOR DEB)
    SET(CPACK_DEBIAN_PACKAGE_DEPENDS
            "qt6 quazip cmark")
    IF (EXISTS "/etc/redhat-release")
        SET(CPACK_GENERATOR RPM)
        SET(CPACK_RPM_PACKAGE_REQUIRES "qt6-qtwebengine quazip-qt6 cmark")
    ENDIF ()
    SET(CPACK_SOURCE_GENERATOR TGZ)
    SET(CPACK_SOURCE_IGNORE_FILES
            ${CMAKE_BINARY_DIR}
            ".git"
            ".gitignore"
            "win32"
            "~$"
    )
ELSE (UNIX)
    SET(CPACK_GENERATOR NSIS)
    SET(CPACK_NSIS_CONTACT "Alf <naihe2010@126.com>")
    SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "CreateShortCut '\$DESKTOP\\\\apvlv.lnk' '\$INSTDIR\\\\bin\\\\apvlv.exe'")
    SET(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "Delete '\$DESKTOP\\\\apvlv.lnk'")
ENDIF (UNIX)

INCLUDE(CPack)


================================================
FILE: COPYING
================================================
		    GNU GENERAL PUBLIC LICENSE
		       Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

			    Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

		    GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) You must cause the modified files to carry prominent notices
    stating that you changed the files and the date of any change.

    b) You must cause any work that you distribute or publish, that in
    whole or in part contains or is derived from the Program or any
    part thereof, to be licensed as a whole at no charge to all third
    parties under the terms of this License.

    c) If the modified program normally reads commands interactively
    when run, you must cause it, when started running for such
    interactive use in the most ordinary way, to print or display an
    announcement including an appropriate copyright notice and a
    notice that there is no warranty (or else, saying that you provide
    a warranty) and that users may redistribute the program under
    these conditions, and telling the user how to view a copy of this
    License.  (Exception: if the Program itself is interactive but
    does not normally print such an announcement, your work based on
    the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

    a) Accompany it with the complete corresponding machine-readable
    source code, which must be distributed under the terms of Sections
    1 and 2 above on a medium customarily used for software interchange; or,

    b) Accompany it with a written offer, valid for at least three
    years, to give any third party, for a charge no more than your
    cost of physically performing source distribution, a complete
    machine-readable copy of the corresponding source code, to be
    distributed under the terms of Sections 1 and 2 above on a medium
    customarily used for software interchange; or,

    c) Accompany it with the information you received as to the offer
    to distribute corresponding source code.  (This alternative is
    allowed only for noncommercial distribution and only if you
    received the program in object code or executable form with such
    an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

			    NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

		     END OF TERMS AND CONDITIONS

	    How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

    Gnomovision version 69, Copyright (C) year name of author
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.


================================================
FILE: NEWS
================================================
apvlv 0.5.0
==========

apvlv 0.4.0
==========
Add a 'F' command for forwarding to a word in page when enable visual mode.
use mouse motion to select text, which support Ctrl mask.
Use mouse select and popup menu to annotate text, underline or comment.
When using shortcut 'v' or 'C-v' to selected content, shortcut 'A' to annotate text, shortcut 'U' to underline and shortcut 'C' to comment.

apvlv 0.3.0
===========
inner content toc display
shortcut 'c' or command ':content' to toggle content
shortcut '.' to repeat last action
add background option
auto set background=black when set inverted=yes
add 'content_follow_mode' option for control follow mode of content view

apvlv 0.2.0
===========
Add EPUB format support

apvlv 0.1.4
===========

Add TXT format support

apvlv 0.1.3
===========

Add HTML format support

apvlv 0.1.2
===========

Just swapped source from svn to git

apvlv 0.1.1
===========

News features

   * add '[int]s' to support skip some pages for some document
   * sorted in dir mode by filename
   * using 't' to open file in new tab window
   * using 'T' to open directory in new tab window

apvlv 0.1.0
===========

News features

   * UMD file support
   * using CMake for auto build tool
   * add rpm and deb installation package

===========

apvlv 0.0.9
News features

   * More like vim's info file scheme

   * Add `guioptions = "mT"` to support menu and tool bar

   * Add command bar response when command is failed or command is not valid

   * Add double click action on pdf pages for select a word, a line or a page
     of text

   * Mouse Copy Support: add left drag to select area, and right click to popup menu to copy to clipboard

   * Key Copy Support: add select area by pressing 'v' or <C-v> and copy area by press 'y'

   * add 'wrapscan' options in $HOME/.apvlvrc

   * add search support of dir and content view

   * add Startup.tex to source tar

   * use \ to support space in file name when :o[pen] or :doc

   * use 'G' to go to the last page

   * keep search direction when search with 'n' or 'N' after / or ?

   * add autoreload parameter to support auto reload document or directory view

   * add reverted pdf page feature

Bug Fix

   * display '*' when input password to open pdf docs

   * return false when search next but no first string was be searched.

   * return true when sub child dir has pdf files

   * fix a segfault when yank text

   * fix a memory leak when destroy a apvlv doc

   * fix the correct select mark when scroll up or down the page

   * fix the k or j 's length in continuous mode
  

apvlv 0.0.8
===========

News features

   * add DJVU format document viewing support 

   * add poppler-data support in Windows version

   * add :w[rite] filename to save document

   * add zw to fitwidth and zh to fitheight

   * made the scrollbar as a option in $HOME/.apvlvrc

Bug Fix

   * If a pdf file is not encrypted, not ask password when can't open it

   * highlight correctly after zoom in or zoom out

   * :z[oom] fitwidth or :z[oom] fitheight works


apvlv 0.0.7
===========

Changes for WARNING

   * apvlv don't support negative integer now

     Because the '-' cause many bugs in in <C-w>- to smaller a window, apvlv don't support negative integer from 0.0.7.4.

News features

   * add shell command line options, -h, -v and -c

   * add options to disable ~/.apvlvinfo

   * add a option "set autoscrolldoc=yes/no" to toggle auto scroll a doc from end to 1st

   * add a option "set pdfcache=[number]" to set the cache size of pdf object

   * set pdfcache >= 2 limit for apvlv works correctly

   * set no autoscroll and not continuous when pdf is a single page

   * add global /etc/apvlvrc

   * don't create ~/.apvlvrc automatically

   * made fullscreen really works under some WM

   * add key 'n' and 'N' to search and back search

Bug Fix

   * fix file is not exist segment fault

   * fix the command history segment fault

   * add document of / and ? key which appear from 0.0.3

   * fix lots of spelling errors in Startup.pdf


apvlv 0.0.6
============

News features

   * Warning !!! change 'goto' command's 'g' to 'G'

   * add continuous view of pdf page
     set in .apvlvrc "set continuous=yes/no"

   * add argument to support weather auto scroll page when k,j to page's end or head
     set in .apvlvrc "set autoscrollpage=yes/no"

   * add Up and Down key to get previous and next command in Command Prompt

   * add tab view
     add :tabnew and 'gt' 'gg' 'gT' 'g[n]' command to support tab switch, change goto page command from 'g' to 'G'

   * add content view
         * k, j to select up, down
         * h, l to expand, collapse
         * <CR> open selected page
         * 'o' open selected page in split window
         * 't' open selected page in new tab

   * add directory view by command 'O'
         * k, j to select up, down
         * h, l to expand, collapse
         * <CR> open selected file
         * 'o' open selected file in split window
         * 't' open selected file in new tab

   * add <C-]> goto a hyperlink and <C-t> to come back

   * add open encrypted pdf with password support

   * add open last directory

   * add mouse wheel scrollup and down support

   * add a padding argument to continuous page

   * add :number to goto page

 Bug fix

   * fix the windows open bug

   * fix the keys can't work on 64bit platform

   * fix some segment fault

   * change the width, height of apvlv can resize


apvlv 0.0.5
============

News features

    * add cache module to make the display faster.

    * add ':set [no]cache' to tell apvlv if use cache module.


apvlv 0.0.4
=============

News features

    * remove the status bar from the bottom, and add it to every window.

    * make the active window highlight the status bar.

    * add 'r' command to rotate page.

    * add ':TOtext' to translate pdf doc page to a text file.

    * add ':pr[int]' to print the document.

    * add ':open file' to open a PDF document.

    * add ':doc file' to set the doc to current window.

    * support open multiple PDF files once, and load them in cache. can be set by ':doc file'.

Bug Fix
   
    * fix the absolutepath () bug.


apvlv 0.0.3
==============

News features

    * Replace word which like 'C-a' to like '<C-a>' at every necessary place.

    * Support 'H', 'M' and 'L' to scroll one PDF page.

    * Support multiple window view and support below command to control them.
            0. ':sp', ':vsp', 'q', 'quit'
            1. '<C-w> <C-Q>', '<C-w> q
            2. '<C-w> k'
            3. '<C-w> j'
            4. '<C-w> h'
            5. '<C-w> l'
            6. '<C-w> <C-w>'
            7. '<C-w> -'
            8. '<C-w> +'

    * Support multiple PDF Doc to be opened at the memory.

    * Support map command mode
            like 'map a :vsp'


================================================
FILE: README.md
================================================
# apvlv

apvlv is a PDF/EPUB/TXT/FB2/MOBI/CBZ/HTML ... Viewer Under Linux/WIN32 and its behaviour like Vim.

Apvlv is an open source software, was created by Alf and is hosted on [github](https://github.com/naihe2010/apvlv).

Now, it is still growing.

Like Vim, Apvlv makes people reading their PDF/EPUB files just like using Vim.

So, Apvlv bindings lots of Vim command and its behaviour is like Vim. For example, < Ctrl-f > to forward page, < Ctrl-b > to previous page, 'k','j','h','l' to scrolling a page up, down, left or right, and so on.

And, Apvlv can understand that how many times you want to run the command.

The only thing you need to do is typing the number before the command. For example, typing '50' and < Ctrl-f > will go forward 50 pages, typing '30' and < Ctrl-b > will go previous 30 pages.

What's more import is apvlv can support view a directory as content of a pdf/epub document. Pressing 'k' or 'j' to move selected up or down, 'h' or 'l' to collapse or expand a dir, and press 't' will open the selected document in a new tab.

# Dependencies

+ Qt6 ( http://www.qt.org/ )
+ Quazip ( https://github.com/stachenov/quazip )

# Optional Dependencies

+ Poppler-qt6 ( https://www.qt.io/ )
+ MuPDF ( https://www.mupdf.com/ )
+ Teeseract ( https://github.com/tesseract-ocr/tessdoc )

# Download

+ Releases (https://github.com/naihe2010/apvlv/releases)

# Build

1. Using cmake to generate Makefile.
   
   ```
   cmake .
   ```
2. Execute make.
   
   ```
   make
   ```

# Install

+ Make a package and install it.
  
  ```
  make package
  ```
+ Or install it directly.
  
  ```
  sudo make install
  ```

# License

apvlv is licensed under the GNU General Public License (GPL).

# Contact

+ Email: Alf [naihe2010@126.com](mailto:naihe2010@126.com)

# Develop Tools

+ Vim (https://www.vim.org/)

+ Emacs (https://www.gnu.org/software/emacs/)

+ CLion (https://www.jetbrains.com/clion/)
  
  Thanks these great tools.

# Github Actions
![CI Status](https://github.com/naihe2010/apvlv/actions/workflows/cmake-multi-platform.yml/badge.svg)


================================================
FILE: THANKS
================================================
I would like to give thanks to the following for their support and
contributions:

        - Ian Munsie <darkstarsword@gmail.com>, for he fixing some memory leaks in ApvlvPDF::pagesearch 
        - Nico <zon3r@yahoo.com>, for his inverted feature code contribution.
          And, the code is copy from evince, and the author is Juanjo Marín.
          Thanks !!!

        - Daniel Friesel <foobar@derf.homelinux.org>, for his more beautiful man page
        - Andrew Kudryashov <andrewinsilenthill@gmail.com>, for his help about Startup.pdf
        - Adam <jiang.adam@gmail.com>, for his test and bug fix.
        - Stefan Ritter <xeno@thehappy.de>, for his man page of apvlv, his careful test and pack on Debian

        - Robert Smolinski <scottymcribs@gmail.com>, for his vimlike tabs code contribution. 
          And, he is a developer of apvlv then.

        - Robby Workman <rworkman@slackbuilds.org>, for his bug fix about doc directory
        - grandpa <lxdjob.gmail.com>, for his test on windows
        - Ali Gholami Rudi <aligrudi@gmail.com>, for his bug fixes
        - tocer <tocer@gmail.com>, for his test on linux and pack on ArchLinux.
        - pk <pk0206@gmail.com>, for his advices


================================================
FILE: TODO
================================================
.      Bug fix.


================================================
FILE: apvlv.1
================================================
.Dd December 24, 2009
.Dt apvlv 1
.Os
.Sh NAME
.Nm apvlv
.Nd PDF/DJVU/EPUB/HTML/TXT/FB2/CZW viewer with vim-like behaviour
.Sh SYNOPSIS
.Nm
.Op options
.Op file
.Sh DESCRIPTION
apvlv is a PDF/DJVU/EPUB/HTML/TXT/FB2/CZW viewer, which behaves like vim.
.Sh OPTIONS
.Bl -tag -width "v"
.It Fl c Ar file
Load configuration from
.Ar file
instead of the default
.Pa ~/.apvlvrc
.It Fl h
Show help message and exit
.It Fl v
Show version and exit
.El
.Sh COMMANDS
The following command keys can be used inside apvlv.
Some of them may be prefixed by a number (as in pressing "13G"),
this is indicated by a
.Ar count
in their description.  Unless noted otherwise, the default value for the
number is 1.
.Bl -tag -width "indent"
.It o
Display file chooser to open a PDF/DJVU/EPUB/HTML/TXT file
.It O
Select a directory to display
.It t
Display file chooser to open a PDF/DJVU/EPUB/HTML/TXT file in a new tab
.It T
Select a directory to display in a new tab
.It R
Reload the current file
.It r
Rotate the document clockwise by 90 degrees
.It G
Show page number
.Ar count
.It gt
Show next tab
.It gT
Show previous tab
.It PageDown, C-f
Go forward
.Ar count
pages
.It PageUp, C-b
Go backward
.Ar count
pages
.It C-d
Go forward
.Ar count
half pages
.It C-u
Go backward
.Ar count
half pages
.It H
Scroll to page head
.It M
Scroll to page middle
.It L
Scroll to page bottom
.It s
skip some pages
.Ar count
.It C-p, Up, k
Scroll up
.Ar count
units
.It C-n, Down, j
Scroll down
.Ar count
units
.It Backspace, Left, h
Scroll left
.Ar count
units
.It Space, Right, l
Scroll right
.Ar count
units
.It / Ar string
Search forwards for
.Ar string
.It ? Ar string
Search backwards for
.Ar string
.It f
Toggle between fullscreen and window mode
.It zi
Zoom in
.It zo
Zoom out
.It zw
Zoom to fit window width
.It zh
Zoom to fit window height
.It m Ar char
Mark the current position to
.Ar char ,
so that it can be recalled by pressing
.Ar char
.It ' Ar char
Return to the mark position
.Ar char
.It ''
Return to the last position
.It q
Close the current window
.It ZZ
quit the viewer with ZZ, like Vim
.It c
toggle directory display
.El
.Sh SETTINGS
These can be set in ~/.apvlvrc with
.Qq set Ar setting Op = Ar value .
.Bl -tag -width "indent"
.It fullscreen = yes/no
Enable/Disable fullscreen
.It width = Ar int
Default window width
.It height = Ar int
Default window height
.It fix_width = Ar int
fixed width
.It fix_height = Ar int
fixed height
.It defaultdir = Ar path
Default directory for the open dialogue
.It zoom = Ar mode
Set default zoom level
.Bl -tag -width "indent"
.It normal
The application sets the default zoom value
.It fitwidth
Fit pages to window width
.It fitheight
Fit pages to window height
.It Ar float
1.0 for 100%, 2.0 for 200%, etc.
.El
.It continuous = yes/no
Show PDF/DJVU/EPUB/HTML/TXT pages continuously or not.
.It autoscrollpage = yes/no
Enable/Disable scrolling the pages when hitting a page tail/head
.It noinfo = yes/no
Disable/Enable the usage of ~/.apvlvinfo
.It max_info = Ar int
Max file information will be saved, default is 100
.It scrollbar = yes/no
Set show scrollbar or not
.It wrapscan = yes/no
Set wrapscan to search text or not
.It doubleclick = Ar action
Set default double click action
.Bl -tag -width "indent"
.It none
Selection nothing
.It word
Selection a word under the cursor to clipboard
.It line
Selection a line under the cursor to clipboard
.It page
Selection a page under the cursor to clipboard
.El
.It guioptions = mTsS
Weather display menu, toolbar, status and status tool.
.It .pdf:engine = MuPDF/Poppler/QtPdf
Which engine to render .pdf file
.It .epub:engine = MuPDF/Web
Which engine to render .epub file
.It .fb2:engine = MuPDF/Web
Which engine to render .fb2 file
.It ocr:lang = eng+chi_sim
Pretrained languages which tesseract load to process
.It notes:dir = Ar dir
Directory to save ebook notes
.It autoreload = Ar int
If auto reload document after some seconds
.El
.Bl -tag -width "indent"
.It inverted = yes/no
If use inverted mode for pdf page
.It background = Ar color
Set background color
.El
.Sh PROMPT
Like the COMMANDS, but prefixed with a colon:
.Bl -tag -width "indent"
.It :h[elp]
Display the help document
.It :q[uit]
Close the current window
.It :o[pen] Ar file
Open
.Ar file
.It :doc Ar file
Load
.Ar file
into the current window
.It :TOtext Op Ar file
Translate
.Ar file
(or the current page) to a text file
.It :pr[int]
Print the current document
.It :tabnew
Create a new tab
.It :sp
Horizontally split the current window
.It :vsp
Vertically split the current window
.It :fp, :forwardpage Op Ar int
Go forward
.Ar int
pages (1 by default)
.It :bp, :prewardpage Op Ar int
Go backward
.Ar int
pages (1 by default)
.It :g, :goto Ar int
Go to page
.Ar int
.It :z[oom] Ar mode
Set zoom to
.Ar mode
(see "set zoom" in SETTINGS)
.It : Ns Ar int
Go to page
.Ar int
.It :directory
toggle directory display
.El
.Sh AUTHORS
apvlv was written by Alf <naihe2010@126.com>.
.Pp
This 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>.


================================================
FILE: apvlvrc.example
================================================
" some map

" map n to <C-f> to goto next page
"map n <C-f>

" and p to prepage
"map p <C-b>

" map I to zi, and O to zo
"map I zi
"map O zo

" if start apvlv as fullscreen mode, default is no
"set fullscreen=no

" zoom value, default is fitwidth
" zoom has 4 styles
" a float type number
" fitwidth
" fitheight
" normal
"set zoom=fitwidth

" set window size
"set width=800
"set height=600

" set command timeout between two key press 
"set commandtimeout=2000

" set default dir
"set defaultdir=C:\

" set weather use continuous view
" make sure the autoscrollpage is set to "yes" if you want to set this to yes
"set continuous=yes

" set a pad to continuous page
"set continuouspad=2

" set if auto scroll page when at the end or begin of one pdf page
"set autoscrollpage=yes

" set if auto scroll doc from 1st page when goto the last page
"set autoscrolldoc=yes

" set if disable ~/.apvlvinfo, default is no
"set noinfo=no

" set if wrapscan text
"set wrapscan = yes

" set double click action
" option value is 'word', 'line' or 'page'
"set doubleclick = page

" set GUI options
" m means menu, T means toolbar
"set guioptions = mT

" set if reverted pdf page
"set reverted = no


================================================
FILE: cmake/CompileFlags.cmake
================================================
# Compiler flags configuration

# C++ standard
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Common flags
if(NOT WIN32)
    set(CMAKE_CXX_FLAGS "-Wall -fno-strict-aliasing")

    if(CMAKE_BUILD_TYPE STREQUAL "Debug")
        set(CMAKE_CXX_FLAGS "-D_DEBUG -g ${CMAKE_CXX_FLAGS}")
    else()
        set(CMAKE_CXX_FLAGS "-O2 ${CMAKE_CXX_FLAGS}")
    endif()
else()
    add_definitions(-D_CRT_SECURE_NO_WARNINGS)
endif()

# Export compile commands
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Qt definitions
add_definitions(-DQT_MESSAGELOGCONTEXT)


================================================
FILE: cmake/Dependencies.cmake
================================================
# Dependency management for apvlv

# Platform-specific dependency handling
if(WIN32)
    # Windows - use vcpkg via find_package (vcpkg provides CMake config files)
    # Required dependencies
    find_package(cmark REQUIRED)
    find_package(quazip REQUIRED)
    find_package(djvulibre REQUIRED)
    
    # Setup variables for compatibility with Unix code
    if(TARGET cmark::cmark)
        set(CMARK_FOUND TRUE)
        get_target_property(CMARK_INCLUDE_DIRS cmark::cmark INTERFACE_INCLUDE_DIRECTORIES)
        if(NOT CMARK_INCLUDE_DIRS)
            get_target_property(CMARK_INCLUDE_DIRS cmark::cmark INCLUDE_DIRECTORIES)
        endif()
        set(CMARK_LIBRARIES cmark::cmark)
    elseif(cmark_FOUND)
        set(CMARK_FOUND TRUE)
        if(cmark_INCLUDE_DIR)
            set(CMARK_INCLUDE_DIRS ${cmark_INCLUDE_DIR})
        endif()
        if(cmark_LIBRARY)
            set(CMARK_LIBRARIES ${cmark_LIBRARY})
        endif()
    endif()
    
    if(TARGET quazip::quazip)
        set(QUAZIP_FOUND TRUE)
        get_target_property(QUAZIP_INCLUDE_DIRS quazip::quazip INTERFACE_INCLUDE_DIRECTORIES)
        if(NOT QUAZIP_INCLUDE_DIRS)
            get_target_property(QUAZIP_INCLUDE_DIRS quazip::quazip INCLUDE_DIRECTORIES)
        endif()
        set(QUAZIP_LIBRARIES quazip::quazip)
    elseif(quazip_FOUND)
        set(QUAZIP_FOUND TRUE)
        if(quazip_INCLUDE_DIR)
            set(QUAZIP_INCLUDE_DIRS ${quazip_INCLUDE_DIR})
        endif()
        if(quazip_LIBRARY)
            set(QUAZIP_LIBRARIES ${quazip_LIBRARY})
        endif()
    endif()
    
    # DjVu setup for Windows
    if(TARGET djvulibre::djvulibre)
        set(DJVULIBRE_FOUND TRUE)
        get_target_property(DJVULIBRE_INCLUDE_DIR djvulibre::djvulibre INTERFACE_INCLUDE_DIRECTORIES)
        if(DJVULIBRE_INCLUDE_DIR)
            get_filename_component(DJVULIBRE_DIR ${DJVULIBRE_INCLUDE_DIR} DIRECTORY)
        endif()
    elseif(djvulibre_FOUND)
        set(DJVULIBRE_FOUND TRUE)
        if(djvulibre_INCLUDE_DIR)
            set(DJVULIBRE_INCLUDE_DIR ${djvulibre_INCLUDE_DIR})
            get_filename_component(DJVULIBRE_DIR ${djvulibre_INCLUDE_DIR} DIRECTORY)
        endif()
        if(djvulibre_LIBRARY)
            get_filename_component(DJVULIBRE_LIB_DIR ${djvulibre_LIBRARY} DIRECTORY)
            if(NOT DJVULIBRE_DIR)
                set(DJVULIBRE_DIR ${DJVULIBRE_LIB_DIR})
            endif()
        endif()
    endif()
    
    # Optional dependencies for Windows
    if(APVLV_WITH_POPPLER)
        find_package(Poppler QUIET)
        if(Poppler_FOUND)
            set(POPPLER_FOUND TRUE)
            if(TARGET Poppler::poppler)
                set(POPPLER_LIBRARIES Poppler::poppler)
            elseif(poppler_LIBRARIES)
                set(POPPLER_LIBRARIES ${poppler_LIBRARIES})
            endif()
        endif()
    endif()
    
    if(APVLV_WITH_MUPDF)
        find_package(mupdf QUIET)
        if(mupdf_FOUND)
            set(MUPDF_FOUND TRUE)
            if(TARGET mupdf::mupdf)
                set(MUPDF_STATIC_LIBRARIES mupdf::mupdf)
            elseif(mupdf_LIBRARIES)
                set(MUPDF_STATIC_LIBRARIES ${mupdf_LIBRARIES})
            endif()
        endif()
    endif()
    
    if(APVLV_WITH_OCR)
        find_package(tesseract QUIET)
        if(tesseract_FOUND)
            set(TESSERACT_FOUND TRUE)
            if(TARGET tesseract::tesseract)
                set(TESSERACT_LIBRARIES tesseract::tesseract)
            elseif(tesseract_LIBRARIES)
                set(TESSERACT_LIBRARIES ${tesseract_LIBRARIES})
            endif()
        endif()
    endif()
else()
    # Unix/Linux - use pkg-config
    find_package(PkgConfig REQUIRED)
    pkg_check_modules(CMARK libcmark REQUIRED)
    pkg_check_modules(QUAZIP quazip1-qt6 REQUIRED)
    
    # Optional dependencies for Unix
    if(APVLV_WITH_POPPLER)
        pkg_check_modules(POPPLER poppler-qt6)
    endif()

    if(APVLV_WITH_MUPDF)
        pkg_check_modules(MUPDF mupdf)
    endif()

    if(APVLV_WITH_OCR)
        pkg_check_modules(TESSERACT tesseract)
    endif()
endif()

# Find Qt6 components
find_package(Qt6 NAMES Qt6 COMPONENTS
    Core Gui Widgets WebEngineWidgets Pdf PdfWidgets Xml PrintSupport
    REQUIRED
)

# Setup Qt variables
set(Qt_INCLUDE_DIRS
    ${Qt6Core_INCLUDE_DIRS}
    ${Qt6Gui_INCLUDE_DIRS}
    ${Qt6Widgets_INCLUDE_DIRS}
    ${Qt6WebEngineWidgets_INCLUDE_DIRS}
    ${Qt6Pdf_INCLUDE_DIRS}
    ${Qt6PdfWidgets_INCLUDE_DIRS}
    ${Qt6Xml_INCLUDE_DIRS}
    ${Qt6PrintSupport_INCLUDE_DIRS}
)

set(Qt_LIBRARIES
    Qt6::Core Qt6::Gui Qt6::Widgets
    Qt6::WebEngineWidgets Qt6::Pdf Qt6::PdfWidgets
    Qt6::Xml Qt6::PrintSupport
)


================================================
FILE: cmake/Options.cmake
================================================
# Build options for apvlv
option(APVLV_WITH_MUPDF "Enable MuPDF PDF engine" ON)
option(APVLV_WITH_POPPLER "Enable Poppler PDF engine" OFF)
option(APVLV_WITH_DJVU "Enable DjVu support" ON)
option(APVLV_WITH_OFFICE "Enable Office document support" ON)
option(APVLV_WITH_OCR "Enable OCR support" ON)

# Platform-specific defaults
if(WIN32)
    set(APVLV_WITH_POPPLER ON CACHE BOOL "Enable Poppler PDF engine" FORCE)
endif()


================================================
FILE: scripts/build.ps1
================================================
param(
    [string]$BuildDir = "",
    [string]$BuildType = "Release",
    [string]$VisualStudioVersion = "",
    [string]$VcpkgDir = "",
    [string]$VcpkgBuildTrees = ""
)

$ErrorActionPreference = "Stop"

function Write-Error-Exit {
    param([string]$Message)
    Write-Host "ERROR: $Message" -ForegroundColor Red
    exit 1
}

function Test-Command {
    param([string]$Command)
    return [bool](Get-Command $Command -ErrorAction SilentlyContinue)
}

function Get-VisualStudio {
    $vsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
    $instances = @()

    if (Test-Path $vsWhere) {
        try {
            $oldEncoding = [Console]::OutputEncoding
            [Console]::OutputEncoding = [System.Text.Encoding]::UTF8

            $json = & $vsWhere -all -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -format json -utf8

            [Console]::OutputEncoding = $oldEncoding

            if ($json) {
                $rawInstances = $json | ConvertFrom-Json
                if ($rawInstances -is [System.Management.Automation.PSCustomObject]) {
                    $rawInstances = @($rawInstances)
                }

                foreach ($item in $rawInstances) {
                    $majorVersion = $item.installationVersion.Split('.')[0]
                    $generator = ""
                    switch ($majorVersion) {
                        "18" { $generator = "Visual Studio 18 2026" }
                        "17" { $generator = "Visual Studio 17 2022" }
                        "16" { $generator = "Visual Studio 16 2019" }
                        "15" { $generator = "Visual Studio 15 2017" }
                        Default { $generator = "Visual Studio $majorVersion" }
                    }

                    $instances += @{
                        Path = $item.installationPath
                        Version = $item.installationVersion
                        Generator = $generator
                    }
                }
            }
        } catch {
            [Console]::OutputEncoding = $oldEncoding
        }
    }

    if ($instances.Count -eq 0) {
        $fallback = @(
            @{Path = "${env:ProgramFiles}\Microsoft Visual Studio\2022\Community"; Version = "17.0"; Generator = "Visual Studio 17 2022"},
            @{Path = "${env:ProgramFiles}\Microsoft Visual Studio\2019\Community"; Version = "16.0"; Generator = "Visual Studio 16 2019"}
        )

        foreach ($item in $fallback) {
            if (Test-Path $item.Path) {
                $instances += $item
            }
        }
    }

    if ($instances.Count -eq 0) { return $null }

    if ($VisualStudioVersion) {
        $selected = $instances | Where-Object { $_.Version.StartsWith($VisualStudioVersion) } | Sort-Object Version -Descending | Select-Object -First 1
        if ($selected) { return $selected }
        Write-Host "Warning: VS version '$VisualStudioVersion' not found, using latest" -ForegroundColor Yellow
    }

    return $instances | Sort-Object Version -Descending | Select-Object -First 1
}

# Initialize paths
$SrcDir = Split-Path -Parent $PSScriptRoot
if (-not $BuildDir) { $BuildDir = Join-Path $env:USERPROFILE "build\apvlv" }
$BuildDir = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($BuildDir)

if ($VcpkgDir) {
    $VcpkgRoot = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($VcpkgDir)
} else {
    $VcpkgRoot = Join-Path $BuildDir "vcpkg"
}
$VcpkgInstalledDir = Join-Path $BuildDir "vcpkg_installed"
$BuildDirPath = Join-Path $BuildDir "build"
if ($VcpkgBuildTrees) {
    $VcpkgBuildTreesPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($VcpkgBuildTrees)
} else {
    $VcpkgBuildTreesPath = ""
}

Write-Host "Build Configuration:" -ForegroundColor Cyan
Write-Host "  Build Directory: $BuildDir"
Write-Host "  Build Type: $BuildType"
Write-Host "  Source Directory: $SrcDir"

# Check dependencies
Write-Host "Checking dependencies..." -ForegroundColor Green
if (-not (Test-Command "git")) { Write-Error-Exit "Git not found" }
if (-not (Test-Command "cmake")) { Write-Error-Exit "CMake not found" }

$vs = Get-VisualStudio
if (-not $vs) { Write-Error-Exit "Visual Studio with C++ tools not found" }
Write-Host "Using: $($vs.Generator)" -ForegroundColor Green

# Create build directory
if (!(Test-Path $BuildDir)) {
    Write-Host "Creating build directory..." -ForegroundColor Green
    New-Item -ItemType Directory -Path $BuildDir | Out-Null
}

# Setup vcpkg
if (!(Test-Path $VcpkgRoot) -or !(Test-Path "$VcpkgRoot\.git")) {
    Write-Host "Cloning vcpkg..." -ForegroundColor Green
    if (Test-Path $VcpkgRoot) { Remove-Item -Recurse -Force $VcpkgRoot }
    Push-Location $BuildDir
    try {
        git clone https://github.com/microsoft/vcpkg vcpkg
        if ($LASTEXITCODE -ne 0) { Write-Error-Exit "Failed to clone vcpkg" }
    } finally {
        Pop-Location
    }
} else {
    Write-Host "Updating vcpkg..." -ForegroundColor Green
    Push-Location "$VcpkgRoot"
    try {
        $isShallow = (& git rev-parse --is-shallow-repository).Trim()
        if ($isShallow -eq "true") {
            git fetch --unshallow
            if ($LASTEXITCODE -ne 0) { Write-Error-Exit "Failed to unshallow vcpkg repository" }
        }
        git pull --ff-only
        if ($LASTEXITCODE -ne 0) { Write-Error-Exit "Failed to update vcpkg" }
    } finally {
        Pop-Location
    }
}

if (!(Test-Path "$VcpkgRoot\vcpkg.exe")) {
    Write-Host "Bootstrapping vcpkg..." -ForegroundColor Green
    Push-Location "$VcpkgRoot"
    try {
        .\bootstrap-vcpkg.bat
        if ($LASTEXITCODE -ne 0) { Write-Error-Exit "Failed to bootstrap vcpkg" }
    } finally {
        Pop-Location
    }
}

Write-Host "Installing vcpkg dependencies..." -ForegroundColor Green
$env:VCPKG_INSTALLED_DIR = $VcpkgInstalledDir
Push-Location "$SrcDir"
try {
    $installArgs = @("install", "--triplet=x64-windows", "--clean-after-build", "--x-install-root=$VcpkgInstalledDir")
    if ($VcpkgBuildTreesPath) { $installArgs += "--x-buildtrees-root=$VcpkgBuildTreesPath" }
    & "$VcpkgRoot\vcpkg.exe" $installArgs
    if ($LASTEXITCODE -ne 0) { Write-Error-Exit "Failed to install dependencies" }
} finally {
    Pop-Location
}
Remove-Item Env:VCPKG_INSTALLED_DIR -ErrorAction SilentlyContinue

# Configure and build
if (Test-Path $BuildDirPath) {
    Write-Host "Cleaning build directory..." -ForegroundColor Green
    Remove-Item -Recurse -Force $BuildDirPath
}

New-Item -ItemType Directory -Path $BuildDirPath | Out-Null

Write-Host "Configuring with CMake..." -ForegroundColor Green
Push-Location "$BuildDirPath"
try {
    cmake "$SrcDir" `
      -DCMAKE_TOOLCHAIN_FILE="$VcpkgRoot\scripts\buildsystems\vcpkg.cmake" `
      -DVCPKG_TARGET_TRIPLET=x64-windows `
      -DCMAKE_BUILD_TYPE=$BuildType `
      -G "$($vs.Generator)" `
      -A x64
    if ($LASTEXITCODE -ne 0) { Write-Error-Exit "CMake configuration failed" }
    Write-Host "Building project..." -ForegroundColor Green
    $ParallelJobs = [Math]::Min([Environment]::ProcessorCount, 8)
    cmake --build . --config $BuildType --parallel $ParallelJobs
    if ($LASTEXITCODE -ne 0) { Write-Error-Exit "Build failed" }
} finally {
    Pop-Location
}

Write-Host "Build completed successfully!" -ForegroundColor Green
Write-Host "Output: $BuildDirPath\$BuildType" -ForegroundColor Cyan


================================================
FILE: scripts/build.sh
================================================
#!/bin/bash
# Build script for apvlv using bash
# Usage: ./build.sh [debug clean test package deps help] [build_path]

set -e

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

print_status() {
    echo -e "${GREEN}[INFO]${NC} $1"
}

print_warning() {
    echo -e "${YELLOW}[WARN]${NC} $1"
}

print_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SRC_DIR="$(dirname "$SCRIPT_DIR")"

cleanup_vcpkg_locks() {
    if [ -n "$VCPKG_ROOT" ] && [ -d "$VCPKG_ROOT" ]; then
        print_status "Cleaning up vcpkg lock files..."
        rm -f "$VCPKG_ROOT/.vcpkg-root.lock" 2>/dev/null || true
        rm -f "$VCPKG_ROOT/vcpkg.lock" 2>/dev/null || true
        if [ -n "$VCPKG_DOWNLOADS" ] && [ -d "$VCPKG_DOWNLOADS" ]; then
            rm -f "$VCPKG_DOWNLOADS/.lock" 2>/dev/null || true
        fi
    fi
}

detect_system_qt() {
    SYSTEM_QT_FOUND=0
    SYSTEM_QT_PREFIX=""

    if [ "${USE_VCPKG_QT:-0}" = "1" ]; then
        print_status "USE_VCPKG_QT=1 set, skipping system Qt detection"
        return
    fi

    print_status "Checking for system Qt6 installation..."

    if command -v qmake6 &>/dev/null; then
        SYSTEM_QT_PREFIX=$(qmake6 -query QT_INSTALL_PREFIX 2>/dev/null)
        if [ -n "$SYSTEM_QT_PREFIX" ] && [ -d "$SYSTEM_QT_PREFIX" ]; then
            print_status "Found system Qt6 via qmake6: $SYSTEM_QT_PREFIX"
            SYSTEM_QT_FOUND=1
            return
        fi
    fi

    if command -v qmake &>/dev/null; then
        local qt_version=$(qmake -query QT_VERSION 2>/dev/null)
        if [[ "$qt_version" == 6.* ]]; then
            SYSTEM_QT_PREFIX=$(qmake -query QT_INSTALL_PREFIX 2>/dev/null)
            if [ -n "$SYSTEM_QT_PREFIX" ] && [ -d "$SYSTEM_QT_PREFIX" ]; then
                print_status "Found system Qt6 via qmake: $SYSTEM_QT_PREFIX"
                SYSTEM_QT_FOUND=1
                return
            fi
        fi
    fi

    if command -v pkg-config &>/dev/null; then
        if pkg-config --exists Qt6Core 2>/dev/null; then
            local qt6_libdir=$(pkg-config --variable=libdir Qt6Core 2>/dev/null)
            if [ -n "$qt6_libdir" ]; then
                SYSTEM_QT_PREFIX=$(dirname "$qt6_libdir")
                print_status "Found system Qt6 via pkg-config: $SYSTEM_QT_PREFIX"
                SYSTEM_QT_FOUND=1
                return
            fi
        fi
    fi

    local common_qt_paths=(
        "/usr/lib/qt6"
        "/usr/lib64/qt6"
        "/usr/share/qt6"
        "/usr"
        "/usr/local"
    )

    for qt_path in "${common_qt_paths[@]}"; do
        if [ -f "$qt_path/lib/cmake/Qt6/Qt6Config.cmake" ] ||
            [ -f "$qt_path/lib64/cmake/Qt6/Qt6Config.cmake" ] ||
            [ -f "/usr/lib/cmake/Qt6/Qt6Config.cmake" ]; then
            SYSTEM_QT_PREFIX="$qt_path"
            print_status "Found system Qt6 in: $SYSTEM_QT_PREFIX"
            SYSTEM_QT_FOUND=1
            return
        fi
    done

    if [ -f "/usr/lib/cmake/Qt6/Qt6Config.cmake" ] ||
        [ -f "/usr/lib64/cmake/Qt6/Qt6Config.cmake" ]; then
        SYSTEM_QT_PREFIX="/usr"
        print_status "Found system Qt6 CMake config in /usr"
        SYSTEM_QT_FOUND=1
        return
    fi

    print_warning "System Qt6 not found, will use vcpkg Qt"
}

check_system_qt_deps() {
    if ! command -v pkg-config &>/dev/null; then
        print_warning "pkg-config not found; cannot validate system Qt dependencies."
        return
    fi

    local missing=0

    if ! pkg-config --exists Qt6Core 2>/dev/null; then
        print_warning "Missing system Qt6 pkg-config entry (Qt6Core)."
        missing=1
    fi

    if ! pkg-config --exists quazip1-qt6 2>/dev/null; then
        print_warning "Missing system QuaZIP for Qt6 (pkg-config: quazip1-qt6)."
        missing=1
    fi

    if ! pkg-config --exists libcmark 2>/dev/null; then
        print_warning "Missing system cmark (pkg-config: libcmark)."
        missing=1
    fi

    if [ "$missing" = "1" ]; then
        print_warning "System Qt mode selected, but some required system packages are missing."
        print_warning "Please install distro packages providing: Qt6, quazip-qt6, cmark."
        print_warning "Or force vcpkg Qt by setting USE_VCPKG_QT=1."
    fi
}

execute_in_build_dir() {
    local cmd="$1"
    cd "$BUILD_DIR"
    if ! eval "$cmd"; then
        print_error "Command failed: $cmd"
        exit 1
    fi
    cd "$SRC_DIR"
}

execute_cmake_command() {
    local cmake_args=("$@")
    if [ "$SINGLE_CONFIG" = "false" ]; then
        cmake_args+=("--config" "$BUILD_TYPE")
    fi

    print_status "Running CMake command"
    if [ ! -d "$BUILD_DIR" ]; then
        print_error "Build directory not found: $BUILD_DIR"
        exit 1
    fi

    if ! (cd "$BUILD_DIR" && cmake "${cmake_args[@]}"); then
        print_error "CMake command failed"
        exit 1
    fi
}

find_and_run_test() {
    find_file_in_configs "testNote" "$BUILD_DIR"
    if [ "$FILE_FOUND" = "1" ]; then
        for config in "$BUILD_TYPE" "Release" "Debug"; do
            if [ -f "$BUILD_DIR/$config/testNote" ]; then
                "$BUILD_DIR/$config/testNote"
                if [ $? -ne 0 ]; then
                    print_error "Test execution failed."
                    exit 1
                fi
                return
            fi
        done
        if [ -f "$BUILD_DIR/testNote" ]; then
            "$BUILD_DIR/testNote"
            if [ $? -ne 0 ]; then
                print_error "Test execution failed."
                exit 1
            fi
        fi
    fi
}

check_tools() {
    if ! command -v git &>/dev/null; then
        print_error "Git not found."
        exit 1
    fi

    if ! command -v cmake &>/dev/null; then
        print_error "CMake not found."
        exit 1
    fi
}

setup_vcpkg() {
    git config --global --add safe.directory "$VCPKG_ROOT" 2>/dev/null || true

    local VCPKG_CLONE_URL="https://github.com/microsoft/vcpkg"

    if [ ! -d "$VCPKG_ROOT/.git" ]; then
        print_status "Cloning vcpkg..."
        echo
        if ! git clone --depth 1 '$VCPKG_CLONE_URL' '$VCPKG_ROOT'; then
            print_error "Failed to clone vcpkg after multiple attempts."
            exit 1
        fi
    else
        print_status "Updating vcpkg..."
        echo
        if ! git -C "$VCPKG_ROOT" pull --ff-only; then
            print_error "Failed to update vcpkg after multiple attempts."
            echo
            exit 1
        fi
    fi

    if [ ! -f "$VCPKG_ROOT/vcpkg" ]; then
        print_status "Bootstrapping vcpkg..."
        echo

        cleanup_vcpkg_locks

        if ! cd "$VCPKG_ROOT" && ./bootstrap-vcpkg.sh; then
            print_error "Failed to bootstrap vcpkg after multiple attempts."
            exit 1
        fi
    fi

    if [ ! -f "$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" ]; then
        print_error "vcpkg toolchain file not found."
        exit 1
    fi
}

install_dependencies() {
    print_status "Installing vcpkg dependencies..."

    cleanup_vcpkg_locks

    detect_system_qt

    local vcpkg_args=(
        "install"
        "--triplet=$VCPKG_TRIPLET"
        "--clean-after-build"
        "--x-install-root=$VCPKG_INSTALLED_DIR"
    )

    if [ "$APVLV_WITH_OCR" = "1" ]; then
        vcpkg_args+=("--x-feature=ocr")
    fi
    if [ "$APVLV_WITH_MUPDF" = "1" ]; then
        vcpkg_args+=("--x-feature=mupdf")
    fi

    local manifest_root="$SRC_DIR"
    if [ "$SYSTEM_QT_FOUND" = "1" ]; then
        manifest_root="$SRC_DIR/vcpkg-manifests/system-qt"
        check_system_qt_deps
        print_status "Using system Qt. vcpkg manifest root: $manifest_root"
    else
        print_status "Using vcpkg Qt. vcpkg manifest root: $manifest_root"
    fi

    export VCPKG_INSTALLED_DIR

    if ! cd "$SRC_DIR" && "$VCPKG_ROOT/vcpkg" ${vcpkg_args[*]} --x-manifest-root='$manifest_root'; then
        print_error "Failed to install vcpkg dependencies after multiple attempts."
        exit 1
    fi
}

ensure_vcpkg_and_deps() {
    setup_vcpkg
    install_dependencies
}

clean_build() {
    print_status "Cleaning build directory: $BUILD_DIR"
    if [ -d "$BUILD_DIR" ]; then
        rm -rf "$BUILD_DIR"
        if [ $? -ne 0 ]; then
            print_error "Failed to remove build directory."
            exit 1
        fi
    fi
    mkdir -p "$BUILD_DIR" || {
        print_error "Failed to create build directory."
        exit 1
    }
}

configure() {
    local cmake_cmd="cmake \"$SRC_DIR\""

    cmake_cmd+=" -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
    cmake_cmd+=" -DVCPKG_TARGET_TRIPLET=$VCPKG_TRIPLET"

    if [ "$SYSTEM_QT_FOUND" = "1" ]; then
        print_status "Configuring CMake to use system Qt from: $SYSTEM_QT_PREFIX"
        cmake_cmd+=" -DCMAKE_PREFIX_PATH=\"$SYSTEM_QT_PREFIX;$VCPKG_INSTALLED_DIR/$VCPKG_TRIPLET\""
        cmake_cmd+=" -DVCPKG_MANIFEST_MODE=OFF"
    fi

    local vcpkg_manifest_features=()
    if [ "$APVLV_WITH_MUPDF" = "1" ]; then
        cmake_cmd+=" -DAPVLV_WITH_MUPDF=ON"
        vcpkg_manifest_features+=("mupdf")
    fi
    if [ "$APVLV_WITH_OCR" = "1" ]; then
        cmake_cmd+=" -DAPVLV_WITH_OCR=ON"
        vcpkg_manifest_features+=("ocr")
    fi

    if [ ${#vcpkg_manifest_features[@]} -gt 0 ] && [ "$SYSTEM_QT_FOUND" != "1" ]; then
        cmake_cmd+=" -DVCPKG_MANIFEST_FEATURES=$(
            IFS=';'
            echo "${vcpkg_manifest_features[*]}"
        )"
    fi

    if command -v ninja &>/dev/null; then
        cmake_cmd+=' -G "Ninja"'
    else
        cmake_cmd+=' -G "Unix Makefiles"'
    fi

    execute_in_build_dir "$cmake_cmd"
}

build() {
    execute_cmake_command "--build" "." "--parallel"
}

create_package() {
    execute_cmake_command "--build" "." "--target" "package" "--parallel"
}

run_tests() {
    find_and_run_test
}

find_executable() {
    find_file_in_configs "apvlv" "$BUILD_DIR"
}

find_file_in_configs() {
    local filename="$1"
    local search_base="$2"

    FILE_FOUND=0
    for config in "$BUILD_TYPE" "Release" "Debug"; do
        if [ -f "$search_base/$config/$filename" ]; then
            print_status "Found: $search_base/$config/$filename"
            FILE_FOUND=1
            return
        fi
    done

    if [ -f "$search_base/$filename" ]; then
        print_status "Found: $search_base/$filename"
        FILE_FOUND=1
    fi

    if [ "$FILE_FOUND" = "0" ]; then
        print_warning "Could not locate $filename."
    fi
}

execute_build() {
    case "${ACTION,,}" in
        "clean")
            print_status "Cleaning build directory..."
            clean_build
            exit 0
            ;;
        "test")
            print_status "Running tests..."
            run_tests
            exit 0
            ;;
        "deps")
            print_status "Setting up vcpkg and installing dependencies..."
            ensure_vcpkg_and_deps
            print_status "Dependencies installed successfully."
            exit 0
            ;;
    esac

    check_tools

    print_status "Ensuring vcpkg and dependencies are installed..."
    ensure_vcpkg_and_deps

    print_status "Creating build directory if it doesn't exist: $BUILD_DIR"
    mkdir -p "$BUILD_DIR" || print_warning "Failed to create build directory, continuing..."

    clean_build
    configure

    print_status "Building $BUILD_TYPE version..."
    build

    if [ "${ACTION,,}" = "package" ]; then
        print_status "Creating package..."
        create_package
    fi

    find_executable

    print_status "Build completed successfully!"
}

show_help() {
    echo "Usage: build.sh [debug clean test package deps help] [build_path]"
    echo

    echo "Options:"
    echo "    debug    - Build in debug mode"
    echo "    clean    - Clean build directory"
    echo "    test     - Run tests"
    echo "    package  - Create installer package"
    echo "    deps     - Install vcpkg dependencies only"
    echo "    help     - Show this help message"
    echo "    build_path - Optional path for global build directory (default: ~/build/apvlv)"
    echo

    echo "Environment Variables:"
    echo "    GLOBAL_BUILD_DIR  - Path to global build directory"
    echo "    VCPKG_ROOT        - Path to vcpkg installation (default: \$GLOBAL_BUILD_DIR/vcpkg)"
    echo "    VCPKG_TARGET_TRIPLET - Target triplet for vcpkg"
    echo "    APVLV_WITH_MUPDF  - Set to 1 to enable MuPDF support"
    echo "    APVLV_WITH_OCR    - Set to 1 to enable OCR support"
    echo "    USE_VCPKG_QT      - Set to 1 to force using vcpkg Qt instead of system Qt"
    echo "    "
    echo "vcpkg Manifests:"
    echo "    Default: uses $SRC_DIR/vcpkg.json (includes qtbase)"
    echo "    System Qt mode: uses $SRC_DIR/vcpkg-manifests/system-qt/vcpkg.json (no qtbase)"
    echo

    echo "Qt Detection:"
    echo "    The script will automatically detect system Qt6 installation."
    echo "    If found, system Qt will be used instead of vcpkg Qt."
    echo "    Set USE_VCPKG_QT=1 to force using vcpkg Qt."
    echo

    echo "Examples:"
    echo "    build.sh                      - Build release version in default directory"
    echo "    build.sh debug                - Build debug version in default directory"
    echo "    build.sh ~/apvlv_build        - Build release version in specified directory"
    echo "    build.sh debug ~/apvlv_build  - Build debug version in specified directory"
    echo "    build.sh deps                 - Install dependencies only"
    echo "    build.sh package              - Create installer"
    echo "    USE_VCPKG_QT=1 build.sh       - Force using vcpkg Qt"
}

#-------------------# MAIN SCRIPT# -------------------------------

# Set default values
BUILD_TYPE="${BUILD_TYPE:-Release}"
ACTION="${ACTION:-build}"
GLOBAL_BUILD_DIR="${GLOBAL_BUILD_DIR:-$HOME/build/apvlv}"
VCPKG_ROOT="${VCPKG_ROOT:-$GLOBAL_BUILD_DIR/vcpkg}"
BUILD_DIR="${BUILD_DIR:-$GLOBAL_BUILD_DIR/build}"
VCPKG_INSTALLED_DIR="${VCPKG_INSTALLED_DIR:-$GLOBAL_BUILD_DIR/vcpkg_installed}"
VCPKG_TRIPLET="${VCPKG_TARGET_TRIPLET:-x64-linux}"

# Environment variables for features
USE_VCPKG_QT="${USE_VCPKG_QT:-0}"

# System Qt detection results (will be set by detect_system_qt)
SYSTEM_QT_FOUND=0
SYSTEM_QT_PREFIX=""

# Determine if we're using a single-config generator
SINGLE_CONFIG="true"
if command -v ninja &>/dev/null; then
    SINGLE_CONFIG="false"
fi

# Parse arguments
while [ $# -gt 0 ]; do
    case "${1,,}" in
        "debug")
            BUILD_TYPE="Debug"
            ;;
        "clean")
            ACTION="clean"
            ;;
        "test")
            ACTION="test"
            ;;
        "package")
            ACTION="package"
            ;;
        "deps")
            ACTION="deps"
            ;;
        "help")
            show_help
            exit 0
            ;;

        *)
            # Assume it's a build path
            GLOBAL_BUILD_DIR="$1"
            VCPKG_ROOT="$GLOBAL_BUILD_DIR/vcpkg"
            BUILD_DIR="$GLOBAL_BUILD_DIR/build"
            VCPKG_INSTALLED_DIR="$GLOBAL_BUILD_DIR/vcpkg_installed"
            ;;
    esac
    shift
done

# Adjust triplet based on build type
if [ "$BUILD_TYPE" = "Debug" ]; then
    if [[ ! "$VCPKG_TRIPLET" =~ -debug$ ]]; then
        VCPKG_TRIPLET="${VCPKG_TRIPLET}-debug"
    fi
else
    if [[ "$VCPKG_TRIPLET" =~ -debug$ ]]; then
        VCPKG_TRIPLET="${VCPKG_TRIPLET%-debug}"
    fi
fi

print_status "Build type: $BUILD_TYPE"
print_status "VCPKG triplet: $VCPKG_TRIPLET"
print_status "Using build directory: $GLOBAL_BUILD_DIR"

execute_build

exit 0


================================================
FILE: share/applications/apvlv.desktop
================================================
[Desktop Entry]
Version=1.0
Type=Application
Name=apvlv
Comment=A minimalistic document viewer
Comment[de]=Ein minimalistischer Dokumenten-Betrachter
Comment[fr]=Un visionneur de document minimaliste
Comment[ru]=Минималистичный просмотрщик документов
Comment[tr]=Minimalist bir belge görüntüleyicisi
Comment[es_CL]=Un visor de documentos minimalista
Comment[uk_UA]=Легкий переглядач документів
Comment[it]=Un visualizzatore di documenti minimalista
Comment[pl]=Minimalistyczna przeglądarka dokumentów
Exec=apvlv %f
Terminal=false
Categories=Office;Viewer;
MimeType=application/pdf;application/epub+zip;image/vnd.djvu;
Keywords=vim;pdf;
Icon=x-office-document


================================================
FILE: share/scripts/internal.js
================================================
// internal.js
function getSelectionOffset(index) {
    const selection = window.getSelection();

    if (!selection.rangeCount || index >= selection.rangeCount) {
        return [null, null];
    }

    const createOffsetRange = (container, offset) => {
        const range = document.createRange();
        range.setStart(document.documentElement, 0);
        range.setEnd(container, offset);
        return range.toString().length;
    };

    try {
        const range = selection.getRangeAt(index);
        return [createOffsetRange(range.startContainer, range.startOffset), createOffsetRange(range.endContainer, range.endOffset)];
    } catch (error) {
        console.error('Error accessing selection range:', error);
        return [null, null];
    }
}

function underlineTextNode(textNode, startOffset, endOffset, tooltipText) {
    if (!(textNode instanceof Text)) {
        throw new Error('Invalid text node provided');
    }

    const textContent = textNode.nodeValue;
    const validEndOffset = endOffset === -1 ? textContent.length : endOffset;

    if (startOffset < 0 || validEndOffset > textContent.length || startOffset > validEndOffset) {
        throw new Error('Invalid offset values');
    }

    const parent = textNode.parentNode;
    if (!parent) {
        throw new Error('Text node has no parent element');
    }

    const beforeText = textContent.slice(0, startOffset);
    const underlinedText = textContent.slice(startOffset, validEndOffset);
    const afterText = textContent.slice(validEndOffset);

    const underlineElement = document.createElement('u');
    underlineElement.textContent = underlinedText;
    if (tooltipText.length !== 0) {
        underlineElement.setAttribute('title', tooltipText);
    }

    const fragment = document.createDocumentFragment();
    if (beforeText) fragment.appendChild(document.createTextNode(beforeText));
    fragment.appendChild(underlineElement);
    if (afterText) fragment.appendChild(document.createTextNode(afterText));

    parent.replaceChild(fragment, textNode);
}

function traverseTextNodes(root, callback) {
    const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null,);

    let node;
    while ((node = walker.nextNode())) {
        if (callback(node) === false) break;
    }
}

function underlineByOffset(startOffset, endOffset, tooltip_msg) {
    if (startOffset >= endOffset || startOffset < 0) {
        throw new Error('Invalid offset range');
    }

    let currentOffset = 0;
    const nodesInfo = {
        start: {node: null, offset: 0}, end: {node: null, offset: 0}, between: []
    };

    traverseTextNodes(document.documentElement, (textNode) => {
        const nodeLength = textNode.nodeValue.length;
        const nodeEnd = currentOffset + nodeLength;

        if (!nodesInfo.start.node && currentOffset <= startOffset && nodeEnd > startOffset) {
            nodesInfo.start.node = textNode;
            nodesInfo.start.offset = startOffset - currentOffset;
        }

        if (!nodesInfo.end.node && currentOffset <= endOffset && nodeEnd > endOffset) {
            nodesInfo.end.node = textNode;
            nodesInfo.end.offset = endOffset - currentOffset;
            return false;
        }

        if (nodesInfo.start.node && !nodesInfo.end.node && textNode !== nodesInfo.start.node) {
            nodesInfo.between.push(textNode);
        }

        currentOffset = nodeEnd;
        return true;
    });

    if (nodesInfo.start.node && nodesInfo.end.node) {
        underlineTextNode(nodesInfo.start.node, nodesInfo.start.offset, nodesInfo.start.node === nodesInfo.end.node ? nodesInfo.end.offset : -1, tooltip_msg);

        nodesInfo.between.forEach(node => {
            underlineTextNode(node, 0, -1, tooltip_msg);
        });

        if (nodesInfo.start.node !== nodesInfo.end.node) {
            underlineTextNode(nodesInfo.end.node, 0, nodesInfo.end.offset, tooltip_msg);
        }
    }
}

function scrollByTimes(times, h, v) {
    window.scrollBy(times * h, times * v);
}

function scrollToAnchor(anchor) {
    if (typeof anchor === 'string' && anchor.startsWith('#')) {
        const element = document.getElementById(anchor.substr(1));
        if (element) {
            element.scrollIntoView();
        }
    }
}

function scrollToPosition(xrate, yrate) {
    const x = window.screenX * xrate;
    const y = (document.body.offsetHeight - window.innerHeight) * yrate;
    window.scroll(x, y);
}

function dispatchKeydownEvent(keyCode) {
    const event = new KeyboardEvent('keydown', {
        keyCode: keyCode, bubbles: true, cancelable: true
    });
    document.dispatchEvent(event);
}

================================================
FILE: src/ApvlvCmds.cc
================================================
/*
 * This file is part of the apvlv package
 *
 * Copyright (C) 2008 Alf.
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2.0 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
/* @CPPFILE ApvlvCmds.cc
 *
 *  Author: Alf <naihe2010@126.com>
 */

#include <Qt>
#include <cctype>
#include <cstring>

#include "ApvlvCmds.h"
#include "ApvlvView.h"

namespace apvlv
{
using namespace std;
using namespace Qt;

StringKeyMap Command::mKeyMap = {
  { "<BS>", Key_Backspace },      { "<Tab>", Key_Tab },
  { "<CR>", Key_Return },         { "<Esc>", Key_Escape },
  { "<Space>", Key_Space },       { "<lt>", Key_Less },
  { "<Bslash>", Key_Backslash },  { "<Bar>", Key_Bar },
  { "<Del>", Key_Delete },        { "<Up>", Key_Up },
  { "<Down>", Key_Down },         { "<Left>", Key_Left },
  { "<Right>", Key_Right },       { "<Help>", Key_Help },
  { "<Insert>", Key_Insert },     { "<Home>", Key_Home },
  { "<End>", Key_End },           { "<PageUp>", Key_PageUp },
  { "<PageDown>", Key_PageDown }, { "<KP_Up>", Key_Up },
  { "<KP_Down>", Key_Down },      { "<KP_Left>", Key_Left },
  { "<KP_Right>", Key_Right },    { "<KP_Prior>", Key_MediaPrevious },
  { "<KP_Next>", Key_MediaNext }, { "<KP_Home>", Key_Home },
  { "<KP_End>", Key_End },
};

CommandMap ApvlvCmds::mMaps;

static int
keyToControlChar (QKeyEvent *key)
{
  int char_key = key->key ();
  if (key->modifiers () & Qt::ShiftModifier)
    {
      char_key = toupper (char_key);
    }
  else
    {
      char_key = tolower (char_key);
    }
  if (key->modifiers () & ControlModifier)
    char_key = ctrlValue (char_key);

  return char_key;
}

constexpr static bool
isModifierKey (uint k)
{
  switch (k)
    {
    case Key_Shift:
    case Key_CapsLock:
    case Key_Meta:
    case Key_Alt:
    case Key_Super_L:
    case Key_Super_R:
    case Key_Hyper_L:
    case Key_Hyper_R:
    case Key_Control:
      return true;
    default:
      return false;
    }
}

Command::Command ()
    : mType (CmdType::CT_CMD), mHasPreCount (false), mPreCount (1),
      mOrigin (nullptr)
{
}

void
Command::type (CmdType type)
{
  mType = type;
}

CmdType
Command::type ()
{
  return mType;
}

void
Command::push (string_view sv, CmdType type)
{
  mType = type;

  mHasPreCount = false;
  mPreCount = 1;

  auto s = sv.data ();
  if (isdigit (*s))
    {
      mHasPreCount = true;
      mPreCount = (signed int)strtol (s, nullptr, 10);
      do
        {
          s++;
        }
      while (isdigit (*s));
    }

  if (*s == ':' || *s == '/' || *s == '?')
    {
      mStrCommand = s;
      mType = CmdType::CT_STRING;

      size_t off = mStrCommand.find ("<CR>");
      if (off != string::npos)
        {
          mStrCommand.erase (off, mStrCommand.length () - off);
          mType = CmdType::CT_STRING_RETURN;
          mNext = make_unique<Command> ();
          mNext->push (s + off + 4);
        }
      qDebug () << "set string type command: [" << mStrCommand << "]";
      return;
    }

  while (*s != '\0')
    {
      s = append (s);
    }
}

void
Command::process (ApvlvView *view)
{
  if (type () == CmdType::CT_STRING)
    {
      view->promptCommand (mStrCommand.c_str ());
    }
  else if (type () == CmdType::CT_STRING_RETURN)
    {
      view->run (mStrCommand.c_str ());
    }
  else
    {
      for (uint k = 0; k < keyVals ()->size (); ++k)
        {
          int key = keyval (k);
          if (key > 0)
            view->process (mHasPreCount, preCount (), keyval (k));
        }
    }

  if (next () != nullptr)
    {
      next ()->process (view);
    }
}

bool
Command::append (QKeyEvent *key)
{
  if (isModifierKey (key->key ()))
    return false;

  auto char_key = keyToControlChar (key);
  mKeyVals.push_back (char_key);
  return true;
}

const char *
Command::append (const char *s)
{
  size_t len;
  const char *e = strchr (s, '>');

  len = strlen (s);

  if (len >= 4 && *s == '<' && (*e != '\0' && *(s + 2) != '-'))
    {
      e++;
      for (const auto &it : mKeyMap)
        {
          if (it.first.compare (0, e - s, s) == 0)
            {
              mKeyVals.push_back (it.second);
              return e;
            }
        }
    }

  if (len >= 5 && s[0] == '<' && s[2] == '-' && s[4] == '>')
    {
      if (s[1] == 'C')
        {
          mKeyVals.push_back (ctrlValue (s[3]));
        }
      else
        {
          qWarning () << "Can't recognize the symbol: " << s;
        }
      return s + 5;
    }
  else
    {
      mKeyVals.push_back (s[0]);
      return s + 1;
    }
}

void
Command::setPreCount (int precount)
{
  mPreCount = precount;
  mHasPreCount = true;
}

int
Command::preCount () const
{
  return mPreCount;
}

void
Command::origin (Command *ori)
{
  mOrigin = ori;
}

Command *
Command::origin ()
{
  return mOrigin;
}

CommandKeyList *
Command::keyVals ()
{
  return &mKeyVals;
}

CommandKeyList
Command::keyvalv ()
{
  return mKeyVals;
}

Command *
Command::next ()
{
  return mNext.get ();
}

int
Command::keyval (uint id)
{
  return id >= mKeyVals.size () ? -1 : mKeyVals[id];
}

void
ApvlvCmds::buildCommandMap (string_view os, string_view ms)
{
  Command fir;
  fir.push (os);

  auto *secp = new Command ();
  secp->push (ms);

  for (auto &mMap : mMaps)
    {
      if (mMap.first == fir.keyvalv ())
        {
          delete mMap.second;
          mMap.second = secp;
          return;
        }
    }

  mMaps[fir.keyvalv ()] = secp;
}

ApvlvCmds::ApvlvCmds (ApvlvView *view)
{
  mView = view;

  mState = CmdState::CMD_OK;

  mTimeoutTimer = make_unique<QTimer> (this);
  QObject::connect (mTimeoutTimer.get (), SIGNAL (timeout ()), this,
                    SLOT (timeoutCallback ()));
}

ApvlvCmds::~ApvlvCmds ()
{
  if (mTimeoutTimer->isActive ())
    {
      mTimeoutTimer->stop ();
    }
}

void
ApvlvCmds::append (QKeyEvent *gev)
{
  if (mTimeoutTimer->isActive ())
    {
      mTimeoutTimer->stop ();
    }

  if (mState == CmdState::GETTING_CMD)
    {
      CommandKeyList v = mCmdHead->keyvalv ();
      v.push_back (keyToControlChar (gev));
      CmdReturn r = isMapCommand (&v);
      if (r == CmdReturn::NO_MATCH)
        {
          process (mCmdHead.get ());
          mCmdHead.release ();
          mState = CmdState::CMD_OK;
        }
    }

  if (mCmdHead == nullptr)
    mCmdHead = make_unique<Command> ();

  if (mState == CmdState::CMD_OK)
    {
      if (isdigit (int (gev->key ())) && gev->key () != '0')
        {
          auto c = char (gev->key ());
          mCountString += c;
          mState = CmdState::GETTING_COUNT;
          mTimeoutTimer->start (3000);
          return;
        }
    }

  else if (mState == CmdState::GETTING_COUNT)
    {
      if (isdigit (int (gev->key ())))
        {
          auto c = char (gev->key ());
          mCountString += c;
          mTimeoutTimer->start (3000);
          return;
        }
      else
        {
          if (!mCountString.empty ())
            {
              mCmdHead->setPreCount (
                  int (strtol (mCountString.c_str (), nullptr, 10)));
              mCountString = "";
            }
        }
    }

  bool valid = mCmdHead->append (gev);
  if (!valid)
    {
      mTimeoutTimer->start (3000);
      return;
    }

  mState = CmdState::GETTING_CMD;
  CmdReturn ret = isMapCommand (mCmdHead->keyVals ());
  if (ret == CmdReturn::NEED_MORE)
    {
      mTimeoutTimer->start (3000);
      return;
    }

  Command *pcmd;
  if (ret == CmdReturn::MATCH)
    {
      pcmd = getMapCommand (mCmdHead.get ());
      pcmd->origin (mCmdHead.get ());
      process (pcmd);
      pcmd->origin (nullptr);
      pcmd = nullptr;
    }
  else
    {
      pcmd = process (mCmdHead.get ());
    }

  mCmdHead.reset (pcmd);
  mState = CmdState::CMD_OK;
}

Command *
ApvlvCmds::process (Command *cmd)
{
  uint times = 1;
  Command *orig = cmd->origin ();
  if (orig != nullptr)
    {
      times = orig->preCount ();
    }

  for (uint i = 0; i < times; ++i)
    {
      cmd->process (mView);
    }
  return orig;
}

CmdReturn
ApvlvCmds::isMapCommand (CommandKeyList *ack)
{
  for (auto &mMap : mMaps)
    {
      if (*ack == mMap.first)
        {
          return CmdReturn::MATCH;
        }
      else
        {
          uint i;
          for (i = 0; i < ack->size (); ++i)
            {
              if ((*ack)[i] != mMap.first[i])
                break;
            }

          if (i == ack->size ())
            {
              return CmdReturn::NEED_MORE;
            }
        }
    }

  return CmdReturn::NO_MATCH;
}

Command *
ApvlvCmds::getMapCommand (Command *cmd)
{
  auto it = mMaps.find (*cmd->keyVals ());
  return it != mMaps.end () ? it->second : nullptr;
}

void
ApvlvCmds::timeoutCallback ()
{
  if (mCmdHead != nullptr)
    {
      process (mCmdHead.get ());
      mCmdHead.release ();
    }
  mState = CmdState::CMD_OK;
}
}

// Local Variables:
// mode: c++
// End:


================================================
FILE: src/ApvlvCmds.h
================================================
/*
 * This file is part of the apvlv package
 *
 * Copyright (C) 2008 Alf.
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2.0 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
/* @CPPFILE ApvlvCmds.h
 *
 *  Author: Alf <naihe2010@126.com>
 */

#ifndef _APVLV_CMDS_H_
#define _APVLV_CMDS_H_

#include <QKeyEvent>
#include <QTimer>
#include <map>
#include <vector>

namespace apvlv
{
enum class CmdType
{
  CT_CMD,
  CT_STRING,
  CT_STRING_RETURN
};

enum class CmdState
{
  GETTING_COUNT,
  GETTING_CMD,
  CMD_OK,
};

// command type
enum class CmdStatusType
{
  CMD_NONE,
  CMD_MESSAGE,
  CMD_CMD
};

// function return type
enum class CmdReturn
{
  MATCH,
  NEED_MORE,
  NO_MATCH,
};

// because every unsigned char is < 256, so use this marco to stand for
// Ctrl+char, Shift+char
constexpr int
ctrlValue (int c)
{
  return c + 256;
}

using StringKeyMap = std::map<std::string, int>;

class Command;
using CommandKeyList = std::vector<int>;
using CommandMap = std::map<CommandKeyList, Command *>;

class ApvlvView;
class Command final
{
public:
  Command ();

  ~Command () = default;

  void process (ApvlvView *view);

  void push (std::string_view s, CmdType type = CmdType::CT_CMD);

  bool append (QKeyEvent *key);

  const char *append (const char *s);

  void type (CmdType type);

  CmdType type ();

  CommandKeyList *keyVals ();

  CommandKeyList keyvalv ();

  void setPreCount (int precount);

  [[nodiscard]] int preCount () const;

  int keyval (uint id);

  Command *next ();

  void origin (Command *cmd);

  Command *origin ();

private:
  static StringKeyMap mKeyMap;

  // command type
  CmdType mType;

  // if it has count
  bool mHasPreCount;

  // how to describe this command in .apvlvrc
  // like <C-d><C-w>, <S-b>s, or :run, :vsp, ...
  std::string mStrCommand;

  // key's value list
  CommandKeyList mKeyVals;

  // cmd's pre count
  int mPreCount;

  // next command
  std::unique_ptr<Command> mNext;

  // when a key is map to other, this is the origin cmd.
  // after a mapped key was processed, return to this cmds
  Command *mOrigin;
};

class ApvlvCmds : public QObject
{
  Q_OBJECT
public:
  explicit ApvlvCmds (ApvlvView *view);

  ~ApvlvCmds () override;

  void append (QKeyEvent *gev);

  static void buildCommandMap (std::string_view os, std::string_view ms);

private:
  Command *process (Command *cmd);

  static CmdReturn isMapCommand (CommandKeyList *ack);

  static Command *getMapCommand (Command *cmd);

  static CommandMap mMaps;

  std::unique_ptr<Command> mCmdHead;

  // command view
  ApvlvView *mView;

  CmdState mState;

  std::unique_ptr<QTimer> mTimeoutTimer;

  std::string mCountString;

private slots:
  void timeoutCallback ();
};
}

#endif

// Local Variables:
// mode: c++
// End:


================================================
FILE: src/ApvlvCompletion.cc
================================================
/*
 * This file is part of the apvlv package
 * Copyright (C) <2008> Alf
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */
/* @CPPFILE ApvlvCompletion.cc
 *
 *  Author: Alf <naihe2010@126.com>
 */

#include <QDebug>
#include <filesystem>
#include <string>

#include "ApvlvCompletion.h"
#include "ApvlvUtil.h"

namespace apvlv
{
using namespace std;

string
ApvlvCompletion::complete (const string &prefix)
{
  auto iter = std::ranges::find_if (mItems,
                                    [prefix] (const string &item)
                                      {
                                        return item.find (prefix) == 0;
                                      });
  if (iter != mItems.cend ())
    return *iter;
  else
    return "";
}

void
ApvlvCompletion::addItems (const vector<string> &items)
{
  mItems.insert (mItems.end (), items.begin (), items.end ());
}

void
ApvlvCompletion::addPath (const string &path)
{
  vector<string> items;

  auto fspath = filesystem::path{ path };
  auto filename = fspath.filename ();
  auto dirname = fspath.parent_path ();
  for (auto &entry : filesystem::directory_iterator (dirname))
    {
      auto entry_filename = entry.path ().filename ().string ();
      if (filename.empty () || entry_filename.find (filename.string ()) == 0)
        {
          auto item = entry.path ().string ()
                      + (entry.is_directory () ? PATH_SEP_S : "");
          qDebug () << "add a item: " << item;
          items.emplace_back (item);
        }
    }

  addItems (items);
}

}

// Local Variables:
// mode: c++
// End:


================================================
FILE: src/ApvlvCompletion.h
================================================
/*
 * This file is part of the apvlv package
 * Copyright (C) <2008> Alf
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */
/* @CPPFILE ApvlvCompletion.h
 *
 *  Author: Alf <naihe2010@126.com>
 */
#ifndef _APVLV_COMPLETION_H_
#define _APVLV_COMPLETION_H_

#include <string>
#include <vector>

namespace apvlv
{

class ApvlvCompletion final
{
public:
  explicit ApvlvCompletion (const std::vector<std::string> &items)
      : mItems (items)
  {
  }
  ApvlvCompletion () = default;
  ~ApvlvCompletion () = default;

  void addItems (const std::vector<std::string> &items);
  void addPath (const std::string &path);

  std::string complete (const std::string &np);

private:
  std::vector<std::string> mItems;
};

};

#endif

/* Local Variables: */
/* mode: c++ */
/* End: */


================================================
FILE: src/ApvlvDirectory.cc
================================================
/*
 * This file is part of the apvlv package
 *
 * Copyright (C) 2008 Alf.
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2.0 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
/* @CPPFILE ApvlvFrame.cc
 *
 *  Author: Alf <naihe2010@126.com>
 */

#include <QApplication>
#include <QFile>
#include <QHeaderView>
#include <QInputDialog>
#include <QLocale>
#include <QMessageBox>
#include <QTimeZone>
#include <stack>

#include "ApvlvDirectory.h"
#include "ApvlvFrame.h"
#include "ApvlvNote.h"
#include "ApvlvNoteWidget.h"
#include "ApvlvParams.h"
#include "ApvlvUtil.h"

namespace apvlv
{
using namespace std;

std::vector<const char *> Directory::ColumnString = {
  QT_TR_NOOP ("Title"),     QT_TR_NOOP ("Modified Time"),
  QT_TR_NOOP ("File Size"), QT_TR_NOOP ("Tags"),
  QT_TR_NOOP ("Score"),
};
std::vector<const char *> Directory::SortByColumnString = {
  QT_TR_NOOP ("Sort By Title"),     QT_TR_NOOP ("Sort By Modified Time"),
  QT_TR_NOOP ("Sort By File Size"), QT_TR_NOOP ("Sort By Tags"),
  QT_TR_NOOP ("Sort By Score"),
};
std::vector<const char *> Directory::FilterTypeString = {
  QT_TR_NOOP ("Filter Title"),
  QT_TR_NOOP ("Filter File Name"),
  QT_TR_NOOP ("Filter Modified Time >="),
  QT_TR_NOOP ("Filter Modified Time <="),
  QT_TR_NOOP ("Filter File Size >="),
  QT_TR_NOOP ("Filter File Size <="),
  QT_TR_NOOP ("Filter Tag"),
  QT_TR_NOOP ("Filter Score >="),
  QT_TR_NOOP ("Filter Score <="),
};

void
ContentTree::keyPressEvent (QKeyEvent *event)
{
  event->ignore ();
}

Directory::Directory ()
{
  setLayout (&mLayout);
  mLayout.addWidget (&mToolBar, 0);
  mLayout.addWidget (&mTreeWidget);
  setupToolBar ();
  setupTree ();
  auto guioptions
      = ApvlvParams::instance ()->getStringOrDefault ("guioptions");
  if (guioptions.find ('S') == string::npos)
    {
      mToolBar.hide ();
    }
  QTimer::singleShot (50, this, SLOT (selectFirstItem ()));
}

void
Directory::setupToolBar ()
{
  mToolBar.addWidget (&mFilterText);
  QObject::connect (&mFilterText, SIGNAL (textEdited (const QString &)), this,
                    SLOT (onFilter ()));
  mToolBar.addSeparator ();
  mToolBar.addWidget (&mFilterType);
  for (auto const &str : FilterTypeString)
    {
      mFilterType.addItem (tr (str));
    }
  mToolBar.addSeparator ();
  QObject::connect (&mFilterType, SIGNAL (activated (int)), this,
                    SLOT (onFilter ()));

  auto refresh = mToolBar.addAction (tr ("Refresh"));
  refresh->setIcon (QIcon::fromTheme (QIcon::ThemeIcon::ViewRefresh));
  QObject::connect (refresh, SIGNAL (triggered (bool)), this,
                    SLOT (onRefresh ()));
  mToolBar.addSeparator ();

  auto expand_all = mToolBar.addAction (tr ("Expand All"));
  expand_all->setIcon (QIcon::fromTheme (QIcon::ThemeIcon::ListAdd));
  QObject::connect (expand_all, SIGNAL (triggered (bool)), &mTreeWidget,
                    SLOT (expandAll ()));
  auto collapse_all = mToolBar.addAction (tr ("Collapse All"));
  collapse_all->setIcon (QIcon::fromTheme (QIcon::ThemeIcon::ListRemove));
  QObject::connect (collapse_all, SIGNAL (triggered (bool)), &mTreeWidget,
                    SLOT (collapseAll ()));
  mToolBar.addSeparator ();

  mToolBar.addWidget (&mSortType);
  for (auto const &str : SortByColumnString)
    {
      mSortType.addItem (tr (str));
    }
  QObject::connect (&mSortType, SIGNAL (activated (int)), this,
                    SLOT (sortBy (int)));
}

void
Directory::setupTree ()
{
  mTreeWidget.setColumnCount (3);
  mTreeWidget.setColumnWidth (static_cast<int> (Column::Title), 300);
  mTreeWidget.setColumnWidth (static_cast<int> (Column::MTime), 150);
  mTreeWidget.setColumnWidth (static_cast<int> (Column::FileSize), 50);
  mTreeWidget.setColumnWidth (static_cast<int> (Column::Tags), 100);
  mTreeWidget.setColumnWidth (static_cast<int> (Column::Score), 50);
  mTreeWidget.setSortingEnabled (false);
  mTreeWidget.setHeaderHidden (false);
  QStringList labels;
  for (auto const &str : ColumnString)
    {
      labels.append (tr (str));
    }
  mTreeWidget.setHeaderLabels (labels);

  auto headerview = mTreeWidget.header ();
  headerview->setSectionsClickable (true);
  QObject::connect (headerview, SIGNAL (sectionClicked (int)), this,
                    SLOT (sortBy (int)));

  mTreeWidget.setVerticalScrollMode (
      QAbstractItemView::ScrollMode::ScrollPerItem);
  mTreeWidget.setSelectionBehavior (
      QAbstractItemView::SelectionBehavior::SelectRows);
  mTreeWidget.setSelectionMode (
      QAbstractItemView::SelectionMode::ExtendedSelection);

  mTypeIcons[FileIndexType::DIR] = QIcon (IconDir.c_str ());
  mTypeIcons[FileIndexType::FILE] = QIcon (IconFile.c_str ());
  mTypeIcons[FileIndexType::PAGE] = QIcon (IconPage.c_str ());

  QObject::connect (&mTreeWidget,
                    SIGNAL (itemActivated (QTreeWidgetItem *, int)), this,
                    SLOT (onRowActivated (QTreeWidgetItem *, int)));
  QObject::connect (&mTreeWidget,
                    SIGNAL (itemDoubleClicked (QTreeWidgetItem *, int)), this,
                    SLOT (onRowDoubleClicked ()));
  mTreeWidget.setContextMenuPolicy (Qt::ContextMenuPolicy::CustomContextMenu);
  QObject::connect (&mTreeWidget,
                    SIGNAL (customContextMenuRequested (const QPoint &)),
                    this, SLOT (onContextMenuRequest (const QPoint &)));
}

bool
Directory::isReady ()
{
  return (mTreeWidget.topLevelItemCount () > 0);
}

void
Directory::setIndex (const FileIndex &index)
{
  using enum FileIndexType;
  if (mTreeWidget.topLevelItemCount () == 0 || index.type == DIR)
    {
      refreshIndex (index);
      return;
    }

  auto cur_index = currentItemFileIndex ();
  if (mIndex.type == DIR && cur_index->type == FILE && index.type == FILE)
    {
      if (cur_index->mChildrenIndex.empty ())
        {
          auto cur_item = selectedTreeItem ();
          cur_index->moveChildChildren (index);
          for (auto &child : cur_index->mChildrenIndex)
            {
              setIndex (child, cur_item);
            }
        }
    }
}

void
Directory::preloadTags (const std::string &path)
{
  if (path.empty ())
    {
      return;
    }

  const auto &exts = FileFactory::supportFileExts ();
  for (const auto &entry :
       filesystem::recursive_directory_iterator (filesystem::path (path)))
    {
      if (!entry.is_regular_file ())
        continue;

      const auto &p = entry.path ();
      if (std::find (exts.begin (), exts.end (), p.extension ())
          == exts.end ())
        continue;

      const auto &np = Note::notePathOfPath (p.string ());
      const auto &n = make_unique<Note> ();
      if (n->load (np))
        {
          const auto &tags = n->tag ();
          for (const auto &tag : tags)
            {
              mTags.append (QString::fromLocal8Bit (tag));
            }
        }
    }
}

void
Directory::setItemSelected (QTreeWidgetItem *item)
{
  auto selitems = mTreeWidget.selectedItems ();
  for (auto selitem : selitems)
    {
      selitem->setSelected (false);
    }

  auto parent = item->parent ();
  while (parent)
    {
      mTreeWidget.expandItem (parent);
      parent = parent->parent ();
    }

  item->setSelected (true);
  if (item->isExpanded ())
    mTreeWidget.collapseItem (item);
  mTreeWidget.scrollToItem (item);
}

void
Directory::setIndex (FileIndex &index, QTreeWidgetItem *root_itr)
{
  auto itr = new QTreeWidgetItem ();
  setFileIndexToTreeItem (itr, &index);

  root_itr->addChild (itr);

  for (auto &child : index.mChildrenIndex)
    {
      setIndex (child, itr);
    }
}

void
Directory::refreshIndex (const FileIndex &index)
{
  mTreeWidget.clear ();

  mIndex = index;

  for (auto &child : mIndex.mChildrenIndex)
    {
      setIndex (child, mTreeWidget.invisibleRootItem ());
    }

  sortItems (mTreeWidget.invisibleRootItem ());

  preloadTags (mIndex.path);

  QTimer::singleShot (50, this, SLOT (selectFirstItem ()));
}

void
Directory::setFileIndexToTreeItem (QTreeWidgetItem *item, FileIndex *index)
{
  using enum Column;
  auto variant = QVariant::fromValue<FileIndex *> (index);
  item->setData (static_cast<int> (Title), Qt::UserRole, variant);
  item->setText (static_cast<int> (Title),
                 QString::fromLocal8Bit (index->title.c_str ()));
  item->setIcon (static_cast<int> (Title), mTypeIcons[index->type]);
  item->setToolTip (static_cast<int> (Title),
                    QString::fromLocal8Bit (index->path));

  using enum FileIndexType;
  if (index->type == FILE)
    {
      const auto date = QDateTime::fromSecsSinceEpoch (
          index->mtime, QTimeZone::systemTimeZone ());
      item->setText (static_cast<int> (MTime),
                     date.toString ("yyyy-MM-dd HH:mm:ss"));
      auto size = QLocale ().formattedDataSize (index->size);
      item->setText (static_cast<int> (FileSize), size);
      item->setText (static_cast<int> (Tags),
                     QString::fromLocal8Bit (index->tags));
      item->setToolTip (static_cast<int> (Tags),
                        QString::fromLocal8Bit (index->tags));
      const auto score
          = index->score > 0 ? QString::number (index->score) : "";
      item->setText (static_cast<int> (Score), score);
    }
}

FileIndex *
Directory::getFileIndexFromTreeItem (QTreeWidgetItem *item)
{
  if (item == nullptr)
    return nullptr;

  auto varaint = item->data (static_cast<int> (Column::Title), Qt::UserRole);
  auto index = varaint.value<FileIndex *> ();
  return index;
}

FileIndex *
Directory::treeItemToFileIndex (QTreeWidgetItem *item)
{
  while (item != nullptr)
    {
      auto index = getFileIndexFromTreeItem (item);
      if (index->type == FileIndexType::FILE)
        return index;
      else
        item = item->parent ();
    }

  return nullptr;
}

void
Directory::filterItemBy (QTreeWidgetItem *root, const filterFunc &filter_func)
{
  std::stack<QTreeWidgetItem *> item_stack;
  for (auto i = 0; i < root->childCount (); ++i)
    {
      auto item = root->child (i);
      item_stack.push (item);
    }

  while (!item_stack.empty ())
    {
      auto item = item_stack.top ();
      item_stack.pop ();

      auto index = getFileIndexFromTreeItem (item);
      auto [is_filter, same_as_file] = filter_func (index);
      if (is_filter)
        {
          item->setHidden (false);

          auto parent = item->parent ();
          while (parent)
            {
              parent->setHidden (false);
              parent = parent->parent ();
            }
        }
      else
        {
          item->setHidden (true);
        }

      if (index->type == FileIndexType::FILE && same_as_file)
        {
          setItemChildrenFilter (item, is_filter);
        }
      else
        {
          for (int i = 0; i < item->childCount (); ++i)
            {
              auto child_item = item->child (i);
              item_stack.push (child_item);
            }
        }
    }
}

void
Directory::setItemChildrenFilter (QTreeWidgetItem *root, bool is_filter)
{
  filterItemBy (root,
                [is_filter] (const FileIndex *a) -> filterFuncReturn
                  {
                    return { is_filter, false };
                  });
}

QTreeWidgetItem *
Directory::findTreeWidgetItem (QTreeWidgetItem *itr, FileIndexType type,
                               const string &path, int pn,
                               const string &anchor)
{
  if (itr == nullptr)
    return nullptr;

  auto index = getFileIndexFromTreeItem (itr);
  if (index == nullptr || index->type != type)
    {
      for (auto ind = 0; ind < itr->childCount (); ++ind)
        {
          auto child_itr = itr->child (ind);
          auto citr = findTreeWidgetItem (child_itr, type, path, pn, anchor);
          if (citr)
            return citr;
        }

      return nullptr;
    }

  auto file_index = treeItemToFileIndex (itr);
  if (file_index == nullptr)
    {
      if (index->page == pn && (anchor.empty () || index->anchor == anchor))
        {
          return itr;
        }

      return nullptr;
    }

  if (file_index->path == path && index->page == pn
      && (anchor.empty () || index->anchor == anchor))
    {
      return itr;
    }

  return nullptr;
}

bool
Directory::setCurrentIndex (const string &path, int pn, const string &anchor)
{
  auto itr = selectedTreeItem ();

  auto fitr = findTreeWidgetItem (itr, FileIndexType::PAGE, path, pn, anchor);

  if (fitr == nullptr)
    fitr = findTreeWidgetItem (mTreeWidget.invisibleRootItem (),
                               FileIndexType::PAGE, path, pn, anchor);

  if (fitr == nullptr)
    fitr = findTreeWidgetItem (mTreeWidget.invisibleRootItem (),
                               FileIndexType::FILE, path, pn, anchor);

  if (fitr)
    {
      setItemSelected (fitr);
      return true;
    }

  return false;
}

void
Directory::scrollUp (int times)
{
  auto item = selectedTreeItem ();
  if (item == nullptr)
    return;

  auto parent = item->parent ();
  if (parent == nullptr)
    {
      parent = mTreeWidget.invisibleRootItem ();
    }

  auto index = parent->indexOfChild (item);
  if (index > 0)
    {
      auto new_index = index > times ? index - times : 0;
      auto itr = parent->child (new_index);
      setItemSelected (itr);
    }
}

void
Directory::scrollDown (int times)
{
  auto item = selectedTreeItem ();
  if (item == nullptr)
    return;

  auto parent = item->parent ();
  if (parent == nullptr)
    {
      parent = mTreeWidget.invisibleRootItem ();
    }

  auto index = parent->indexOfChild (item);
  auto count = parent->childCount ();
  auto new_index = index + times < count ? index + times : count - 1;
  auto itr = parent->child (new_index);
  setItemSelected (itr);
}

void
Directory::scrollLeft (int times)
{
  auto item = selectedTreeItem ();
  if (item == nullptr)
    return;

  if (item->parent () == nullptr)
    return;

  auto parent = item->parent ();

  while (--times > 0 && parent->parent ())
    {
      parent = parent->parent ();
    }

  setItemSelected (parent);
}

void
Directory::scrollRight (int times)
{
  auto item = selectedTreeItem ();
  if (item == nullptr)
    return;

  if (item->childCount () == 0)
    return;

  item = item->child (0);

  while (--times > 0 && item->childCount () > 0)
    {
      item = item->child (0);
    }
  setItemSelected (item);
}

void
Directory::tag ()
{
  auto item = selectedTreeItem ();
  auto cur = getFileIndexFromTreeItem (item);
  if (cur == nullptr)
    return;
  if (cur->type != FileIndexType::FILE)
    return;
  auto path = Note::notePathOfPath (cur->path);
  auto note = make_unique<Note> ();
  filesystem::path note_path (path);
  if (exists (note_path))
    {
      if (note->load (path) == false)
        {
          auto msg = QString (tr ("load note of %s error")).arg (cur->path.c_str());
          QMessageBox::warning (nullptr, tr ("error"), msg);
          return;
        }
    }

  filesystem::path file_path{ cur->path };
  string filename = file_path.filename ();
  auto ans = NoteDialog::getTag (filename, note->tag (), mTags);
  if (ans.isEmpty ())
    {
      return;
    }

  note->addTag (ans.toStdString ());
  if (!exists (note_path))
    {
      note->dump (path);
    }

  auto tags = note->tagString ();
  if (cur->tags != tags)
    {
      cur->tags = tags;
      item->setText (static_cast<int> (Column::Tags),
                     QString::fromLocal8Bit (cur->tags));
    }
}

void
Directory::onFileRename ()
{
  auto items = mTreeWidget.selectedItems ();
  if (items.isEmpty ())
    return;

  auto item = items[0];
  auto index = getFileIndexFromTreeItem (item);
  if (index->type != FileIndexType::FILE)
    return;

  auto qpath = QString::fromLocal8Bit (index->path);
  auto text = QString (tr ("Input new name of %1")).arg (qpath);
  auto user_text = QInputDialog::getText (this, tr ("Rename"), text,
                                          QLineEdit::Normal, qpath);
  auto nname = user_text.trimmed ();
  if (nname.isEmpty ())
    return;

  if (QFile (qpath).rename (nname))
    {
      index->path = nname.toStdString ();
      index->title = index->path.substr (index->path.rfind ('/') + 1);
      setFileIndexToTreeItem (item, index);
    }
  else
    {
      text = QString (tr ("Rename %1 to %2 failed")).arg (qpath).arg (nname);
      QMessageBox::warning (this, tr ("Warning"), text);
    }
}

void
Directory::onFileDelete ()
{
  auto items = mTreeWidget.selectedItems ();
  if (items.isEmpty ())
    return;

  auto msg_res = QMessageBox::No;
  for (auto item : items)
    {
      auto index = getFileIndexFromTreeItem (item);
      if (index->type != FileIndexType::FILE)
        continue;

      auto qpath = QString::fromLocal8Bit (index->path);
      auto text
          = QString (tr ("Will delete the \n%1, confirm ?")).arg (qpath);
      if (msg_res == QMessageBox::No || msg_res == QMessageBox::Yes)
        {
          auto buttons = QMessageBox::Yes | QMessageBox::No;
          if (items.size () > 1)
            buttons = QMessageBox::Yes | QMessageBox::YesToAll
                      | QMessageBox::No | QMessageBox::NoToAll;
          msg_res = QMessageBox::question (this, tr ("Confirm"), text,
                                           buttons, QMessageBox::No);
        }

      if (msg_res == QMessageBox::NoToAll)
        return;

      else if (msg_res == QMessageBox::No)
        continue;

      auto parent = item->parent ();
      if (parent == nullptr)
        {
          auto offset = mTreeWidget.indexOfTopLevelItem (item);
          mIndex.removeChild (*index);
          item = mTreeWidget.takeTopLevelItem (offset);
          delete item;
        }
      else
        {
          auto pindex = getFileIndexFromTreeItem (parent);
          pindex->removeChild (*index);
          parent->removeChild (item);
        }

      qDebug () << "delete " << qpath;
      QFile::remove (qpath);
    }
}

void
Directory::onRefresh ()
{
  refreshIndex (mIndex);
}

void
Directory::onFilter ()
{
  auto root = mTreeWidget.invisibleRootItem ();
  auto cur = mFilterType.currentIndex ();
  auto type = static_cast<FilterType> (cur);
  auto text = mFilterText.text ().trimmed ();
  if (text.isEmpty ())
    {
      setItemChildrenFilter (root, true);
      return;
    }

  QDateTime datetime;
  qint64 qsize;
  decltype (FileIndex::size) size;
  float score;

  switch (type)
    {
    case FilterType::Title:
      filterItemBy (root,
                    [text] (const FileIndex *a) -> filterFuncReturn
                      {
                        return { a->title.find (text.toStdString ())
                                     != string::npos,
                                 false };
                      });
      break;

    case FilterType::FileName:
      filterItemBy (root,
                    [text] (const FileIndex *a) -> filterFuncReturn
                      {
                        return { a->type == FileIndexType::FILE
                                     && a->title.find (text.toStdString ())
                                            != string::npos,
                                 true };
                      });
      break;

    case FilterType::MTimeBe:
      datetime = QDateTime::fromString (text);
      filterItemBy (root,
                    [datetime] (const FileIndex *a) -> filterFuncReturn
                      {
                        return { a->type == FileIndexType::FILE
                                     && a->mtime
                                            >= datetime.toMSecsSinceEpoch (),
                                 true };
                      });
      break;

    case FilterType::MTimeLe:
      datetime = QDateTime::fromString (text);
      filterItemBy (root,
                    [datetime] (const FileIndex *a) -> filterFuncReturn
                      {
                        return { a->type == FileIndexType::FILE
                                     && a->mtime
                                            <= datetime.toMSecsSinceEpoch (),
                                 true };
                      });
      break;

    case FilterType::FileSizeBe:
      qsize = parseFormattedDataSize (text);
      size = static_cast<decltype (size)> (qsize);
      filterItemBy (root,
                    [size] (const FileIndex *a) -> filterFuncReturn
                      {
                        return { a->type == FileIndexType::FILE
                                     && a->size >= size,
                                 true };
                      });
      break;

    case FilterType::FileSizeLe:
      qsize = parseFormattedDataSize (text);
      size = static_cast<decltype (size)> (qsize);
      filterItemBy (root,
                    [size] (const FileIndex *a) -> filterFuncReturn
                      {
                        return { a->type == FileIndexType::FILE
                                     && a->size <= size,
                                 true };
                      });
      break;

    case FilterType::Tags:
      filterItemBy (root,
                    [text] (const FileIndex *a) -> filterFuncReturn
                      {
                        return { a->type == FileIndexType::FILE
                                     && a->tags.find (text.toStdString ())
                                            != string::npos,
                                 true };
                      });
      break;

    case FilterType::ScoreBe:
      score = QString (text).toFloat ();
      filterItemBy (root,
                    [score] (const FileIndex *a) -> filterFuncReturn
                      {
                        return { a->type == FileIndexType::FILE
                                     && a->score >= score,
                                 true };
                      });
      break;

    case FilterType::ScoreLe:
      score = QString (text).toFloat ();
      filterItemBy (root,
                    [score] (const FileIndex *a) -> filterFuncReturn
                      {
                        return { a->type == FileIndexType::FILE
                                     && a->score <= score,
                                 true };
                      });
      break;

    default:
      qWarning () << tr ("Filter Type is invalid");
      break;
    }

  mTreeWidget.expandAll ();
}

void
Directory::sortItems (QTreeWidgetItem *tree_iter)
{
  stack<QTreeWidgetItem *> needSort;
  needSort.push (tree_iter);

  using itemState = pair<QTreeWidgetItem *, bool>;
  while (!needSort.empty ())
    {
      auto root = needSort.top ();
      needSort.pop ();

      vector<itemState> item_list;
      for (auto i = 0; i < root->childCount (); ++i)
        {
          auto item = root->child (i);
          auto index = getFileIndexFromTreeItem (item);
          if (index->type != FileIndexType::PAGE)
            item_list.emplace_back (item, item->isExpanded ());
        }

      std::ranges::for_each (
          item_list,
          [this, &needSort] (itemState &p)
            {
              if (p.first->childCount () > 1)
                {
                  auto index = getFileIndexFromTreeItem (p.first);
                  if (index && index->type == FileIndexType::DIR)
                    {
                      needSort.push (p.first);
                    }
                }
            });

      if (mSortAscending)
        {
          switch (mSortColumn)
            {
              using enum Column;
            case Title:
              std::ranges::sort (item_list, std::ranges::greater (),
                                 [this] (itemState &p)
                                   {
                                     auto index
                                         = getFileIndexFromTreeItem (p.first);
                                     return index ? index->title : "";
                                   });
              break;
            case MTime:
              std::ranges::sort (item_list, std::ranges::greater (),
                                 [this] (itemState &p)
                                   {
                                     auto index
                                         = getFileIndexFromTreeItem (p.first);
                                     return index ? index->mtime : 0;
                                   });
              break;
            case FileSize:
              std::ranges::sort (item_list, std::ranges::greater (),
                                 [this] (itemState &p)
                                   {
                                     auto index
                                         = getFileIndexFromTreeItem (p.first);
                                     return index ? index->size : 0;
                                   });
              break;
            case Tags:
              std::ranges::sort (item_list, std::ranges::greater (),
                                 [this] (itemState &p)
                                   {
                                     auto index
                                         = getFileIndexFromTreeItem (p.first);
                                     return index ? index->tags : "";
                                   });
              break;
            case Score:
              std::ranges::sort (item_list, std::ranges::greater (),
                                 [this] (itemState &p)
                                   {
                                     auto index
                                         = getFileIndexFromTreeItem (p.first);
                                     return index ? index->score : 0.0f;
                                   });
              break;
            }
        }
      else
        {
          switch (mSortColumn)
            {
              using enum Column;
            case Title:
              std::ranges::sort (item_list, std::ranges::less (),
                                 [this] (itemState &p)
                                   {
                                     auto index
                                         = getFileIndexFromTreeItem (p.first);
                                     return index ? index->title : "";
                                   });
              break;
            case MTime:
              std::ranges::sort (item_list, std::ranges::less (),
                                 [this] (itemState &p)
                                   {
                                     auto index
                                         = getFileIndexFromTreeItem (p.first);
                                     return index ? index->mtime : 0;
                                   });
              break;
            case FileSize:
              std::ranges::sort (item_list, std::ranges::less (),
                                 [this] (itemState &p)
                                   {
                                     auto index
                                         = getFileIndexFromTreeItem (p.first);
                                     return index ? index->size : 0;
                                   });
              break;
            case Tags:
              std::ranges::sort (item_list, std::ranges::less (),
                                 [this] (itemState &p)
                                   {
                                     auto index
                                         = getFileIndexFromTreeItem (p.first);
                                     return index ? index->tags : "";
                                   });
              break;
            case Score:
              std::ranges::sort (item_list, std::ranges::less (),
                                 [this] (itemState &p)
                                   {
                                     auto index
                                         = getFileIndexFromTreeItem (p.first);
                                     return index ? index->score : 0.0f;
                                   });
              break;
            }
        }

      mTreeWidget.setUpdatesEnabled (false);
      for (auto i = 0;
           static_cast<decltype (item_list.size ())> (i) < item_list.size ();
           ++i)
        {
          auto p = item_list[i];
          auto oi = root->indexOfChild (p.first);
          if (oi != i)
            {
              root->takeChild (oi);
              root->insertChild (i, p.first);
              p.first->setExpanded (p.second);
            }
        }
      mTreeWidget.setUpdatesEnabled (true);
    }

  mTreeWidget.update ();
}

void
Directory::onRowActivated ([[maybe_unused]] QTreeWidgetItem *item,
                           [[maybe_unused]] int column)
{
  mFrame->directoryShowPage (currentItemFileIndex (), true);
  mFrame->toggledControlDirectory (true);
}

void
Directory::onRowDoubleClicked ()
{
  parentWidget ()->setFocus ();
}

void
Directory::onContextMenuRequest (const QPoint &point)
{
  auto items = mTreeWidget.selectedItems ();
  if (items.isEmpty ())
    return;

  auto item = items[0];
  auto index = getFileIndexFromTreeItem (item);
  if (index->type == FileIndexType::FILE)
    {
      mItemMenu.clear ();
      if (items.size () == 1)
        {
          auto rename_action = mItemMenu.addAction (tr ("Rename File"));
          QObject::connect (rename_action, SIGNAL (triggered (bool)), this,
                            SLOT (onFileRename ()));
        }
      if (std::all_of (items.begin (), items.end (),
                       [this] (QTreeWidgetItem *a)
                         {
                           auto i = getFileIndexFromTreeItem (a);
                           return i && i->type == FileIndexType::FILE;
                         }))
        {
          auto del_action = mItemMenu.addAction (tr ("Delete File"));
          del_action->setIcon (
              QIcon::fromTheme (QIcon::ThemeIcon::EditDelete));
          QObject::connect (del_action, SIGNAL (triggered (bool)), this,
                            SLOT (onFileDelete ()));
        }
      mItemMenu.popup (mTreeWidget.mapToGlobal (point));
    }
}

void
Directory::selectFirstItem ()
{
  if (setCurrentIndex (mFrame->filename (), mFrame->pageNumber (), ""))
    return;

  if (mTreeWidget.topLevelItemCount () > 0)
    {
      auto itr = mTreeWidget.topLevelItem (0);
      setItemSelected (itr);
    }
}

FileIndex *
Directory::currentItemFileIndex ()
{
  auto item = selectedTreeItem ();
  auto index = getFileIndexFromTreeItem (item);
  return index;
}

FileIndex *
Directory::currentFileFileIndex ()
{
  auto item = selectedTreeItem ();
  auto index = treeItemToFileIndex (item);
  return index;
}

}

// Local Variables:
// mode: c++
// End:


================================================
FILE: src/ApvlvDirectory.h
================================================
/*
 * This file is part of the apvlv package
 *
 * Copyright (C) 2008 Alf.
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2.0 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
/* @CPPFILE ApvlvFrame.h
 *
 *  Author: Alf <naihe2010@126.com>
 */

#ifndef _APVLV_CONTENT_H_
#define _APVLV_CONTENT_H_

#include <QComboBox>
#include <QMenu>
#include <QTimer>
#include <QToolBar>
#include <QTreeWidgetItem>
#include <QVBoxLayout>
#include <iostream>
#include <map>
#include <string>

#include "ApvlvFile.h"
#include "ApvlvUtil.h"
#include "ApvlvWidget.h"

namespace apvlv
{

class ContentTree : public QTreeWidget
{
protected:
  void keyPressEvent (QKeyEvent *event) override;
};

class ApvlvFrame;
class Directory final : public QFrame
{
  Q_OBJECT
public:
  Directory ();

  ~Directory () override = default;

  bool isReady ();

  enum class Column : int
  {
    Title = 0,
    MTime,
    FileSize,
    Tags,
    Score
  };
  static std::vector<const char *> ColumnString;
  static std::vector<const char *> SortByColumnString;

  enum class FilterType : int
  {
    Title = 0,
    FileName,
    MTimeBe,
    MTimeLe,
    FileSizeBe,
    FileSizeLe,
    Tags,
    ScoreBe,
    ScoreLe
  };
  static std::vector<const char *> FilterTypeString;

  FileIndex *currentItemFileIndex ();

  FileIndex *currentFileFileIndex ();

  QTreeWidgetItem *findTreeWidgetItem (QTreeWidgetItem *itr,
                                       FileIndexType type,
                                       const std::string &path, int pn,
                                       const std::string &anchor);

  bool setCurrentIndex (const std::string &path, int pn,
                        const std::string &anchor);

  void
  setFrame (ApvlvFrame *frame)
  {
    mFrame = frame;
  }

  void
  focusFilter ()
  {
    QTimer::singleShot (50, &mFilterText, SLOT (setFocus ()));
  }

  void scrollUp (int times);

  void scrollDown (int times);

  void scrollLeft (int times);

  void scrollRight (int times);

  void tag ();

  void
  setActive (bool active)
  {
    if (active)
      {
        QTimer::singleShot (50, &mTreeWidget, SLOT (setFocus ()));
      }
    else
      {
        mTreeWidget.clearFocus ();
      }
  }

  bool
  isActive ()
  {
    return mTreeWidget.hasFocus ();
  }

private:
  QVBoxLayout mLayout;
  QToolBar mToolBar;
  ApvlvLineEdit mFilterText;
  QComboBox mFilterType;
  QComboBox mSortType;
  ContentTree mTreeWidget;

  QMenu mItemMenu;

  std::map<FileIndexType, QIcon> mTypeIcons;

  FileIndex mIndex;
  QStringList mTags;
  Column mSortColumn{ Column::Title };

  ApvlvFrame *mFrame{ nullptr };

  bool mSortAscending{ true };

  void setupToolBar ();
  void setupTree ();

  QTreeWidgetItem *
  selectedTreeItem ()
  {
    auto selitems = mTreeWidget.selectedItems ();
    return selitems.isEmpty () ? nullptr : selitems[0];
  }
  void setItemSelected (QTreeWidgetItem *item);

  void setIndex (FileIndex &index, QTreeWidgetItem *root_itr);
  void refreshIndex (const FileIndex &index);

  void setFileIndexToTreeItem (QTreeWidgetItem *item, FileIndex *index);
  FileIndex *getFileIndexFromTreeItem (QTreeWidgetItem *item);

  FileIndex *treeItemToFileIndex (QTreeWidgetItem *item);

  using filterFuncReturn = std::tuple<bool, bool>;
  using filterFunc = std::function<filterFuncReturn (const FileIndex *)>;
  void filterItemBy (QTreeWidgetItem *root, const filterFunc &filter_func);
  void setItemChildrenFilter (QTreeWidgetItem *root, bool is_filter);

private slots:
  void onFileRename ();
  void onFileDelete ();
  void onRefresh ();
  void onFilter ();
  void
  sortBy (int method)
  {
    mSortAscending = !mSortAscending;
    mSortColumn = static_cast<Column> (method);
    sortItems (mTreeWidget.invisibleRootItem ());
  }

  void sortItems (QTreeWidgetItem *root);

  void onRowActivated (QTreeWidgetItem *item, int column);
  void onRowDoubleClicked ();
  void onContextMenuRequest (const QPoint &point);
  void selectFirstItem ();
  void setIndex (const FileIndex &index);
  void preloadTags (const std::string &path);
};
}

#endif

/* Local Variables: */
/* mode: c++ */
/* End: */


================================================
FILE: src/ApvlvDired.cc
================================================
/*
 * This file is part of the apvlv package
 *
 * Copyright (C) 2008 Alf.
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2.0 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
/* @CPPFILE ApvlvDired.cc
 *
 *  Author: Alf <naihe2010@126.com>
 */

#include <QFileDialog>
#include <QPushButton>
#include <regex>

#include "ApvlvDired.h"

namespace apvlv
{
using namespace std;

DiredDialog::DiredDialog (QWidget *parent) : QDialog (parent)
{
  setLayout (&mVboxLayout);
}

void
DiredDialog::activateItem (QListWidgetItem *item)
{
  auto words = item->data (Qt::UserRole).toStringList ();
  auto path = words[0];
  auto pn = words[1].toInt ();
  emit loadFile (path.toStdString (), pn);
  accept ();
}

}


================================================
FILE: src/ApvlvDired.h
================================================
/*
 * This file is part of the apvlv package
 *
 * Copyright (C) 2008 Alf.
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2.0 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
/* @CPPFILE ApvlvDired.h
 *
 *  Author: Alf <naihe2010@126.com>
 */

#ifndef _APVLV_DIRED_H_
#define _APVLV_DIRED_H_

#include <QDialog>
#include <QLineEdit>
#include <QListWidget>
#include <QTimer>
#include <QVBoxLayout>
#include <string>
#include <thread>

namespace apvlv
{

class DiredDialog : public QDialog
{
  Q_OBJECT
public:
  explicit DiredDialog (QWidget *parent = nullptr);
  ~DiredDialog () override = default;

signals:
  void loadFile (const std::string &path, int pn);

private:
  QVBoxLayout mVboxLayout;

private slots:
  void activateItem (QListWidgetItem *item);
};

}

#endif


================================================
FILE: src/ApvlvEditor.cc
================================================
/*
 * This file is part of the apvlv package
 *
 * Copyright (C) 2008 Alf.
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2.0 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
/* @CPPFILE ApvlvEditor.cc
 *
 *  Author: Alf <naihe2010@126.com>
 */

#include "ApvlvEditor.h"

namespace apvlv
{

Editor::Editor (QWidget *parent) : QTextEdit (parent)
{
  auto font = currentFont ();
  mFontFamily = font.family ();
  mFontPointSize = font.pointSizeF ();
  mFontWeight = font.weight ();
  mFontPixelSize = font.pixelSize ();
}

void
Editor::setZoomrate (double zm)
{
  auto font = currentFont ();
  auto updated = false;
  if (mFontPointSize != -1)
    {
      auto pointsize = mFontPointSize * zm;
      font.setPointSizeF (pointsize);
      updated = true;
    }
  else if (mFontPixelSize != -1)
    {
      auto pixelsize = mFontPixelSize * zm;
      font.setPixelSize (static_cast<int> (pixelsize));
      updated = true;
    }
  if (updated)
    {
      setFont (font);
      update ();
    }
}

}


================================================
FILE: src/ApvlvEditor.h
================================================
/*
 * This file is part of the apvlv package
 *
 * Copyright (C) 2008 Alf.
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2.0 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
/* @CPPFILE ApvlvEditor.h
 *
 *  Author: Alf <naihe2010@126.com>
 */

#ifndef _APVLV_EDITOR_H_
#define _APVLV_EDITOR_H_

#include <QTextEdit>

namespace apvlv
{

class Editor : public QTextEdit
{
  Q_OBJECT
public:
  explicit Editor (QWidget *parent = nullptr);
  ~Editor () override = default;

  void setZoomrate (double zm);

private:
  QString mFontFamily;
  QFont::Weight mFontWeight;
  qreal mFontPointSize;
  int mFontPixelSize;
};

}

#endif


================================================
FILE: src/ApvlvFile.cc
================================================
/*
 * This file is part of the apvlv package
 * Copyright (C) <2008>  <Alf>
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */
/* @CPPFILE File.cc
 *
 *  Author: Alf <naihe2010@126.com>
 */

#include <QBuffer>
#include <QPainter>
#include <QPrintDialog>
#include <QPrinter>
#include <algorithm>
#include <filesystem>
#include <functional>
#include <iostream>
#include <optional>
#include <sstream>
#include <utility>

#include "ApvlvFile.h"
#include "ApvlvUtil.h"
#include "ApvlvWebViewWidget.h"

namespace apvlv
{

using namespace std;

const string html_template = "<?xml version='1.0' encoding='UTF-8'?>\n"
                             "<html xmlns=\"http://www.w3.org/1999/xhtml\" "
                             "lang=\"en\" xml:lang=\"en\">\n"
                             "  <head>\n"
                             "    <title></title>\n"
                             "    <meta http-equiv=\"Content-Type\" "
                             "content=\"text/html; charset=utf-8\"/>\n"
                             "  </head>\n"
                             "  <body>\n"
                             "    <div content>"
                             "      <image src=%s />"
                             "    </div>"
                             "  </body>\n"
                             "</html>\n";

map<string, vector<string>> FileFactory::mSupportMimeTypes;
map<string, FileFactory::ExtClassList> FileFactory::mSupportClass;

const map<string, vector<string>> &
FileFactory::supportMimeTypes ()
{
  return mSupportMimeTypes;
}

vector<string>
FileFactory::supportFileExts ()
{
  unordered_set<string> extSet;
  for (const auto &pair : mSupportMimeTypes)
    {
      extSet.insert (pair.second.begin (), pair.second.end ());
    }
  vector<string> exts (extSet.begin (), extSet.end ());
  return exts;
}

ostream &
FileFactory::typeEngineDescription (ostream &os)
{
  os << "Engines: " << endl;
  for (auto &pair : mSupportClass)
    {
      string ext = pair.first;
      string engines;
      std::ranges::for_each (pair.second,
                             [&engines] (ExtClass &cls)
                               {
                                 engines.append (" ");
                                 engines.append (cls.first);
                               });
      os << "\t" << pair.first << ":\t\t" << engines << endl;
    }
  os << endl;
  return os;
}

int
FileFactory::registerClass (const string &name, const function<File *()> &fun,
                            initializer_list<string> exts)
{
  mSupportMimeTypes.insert ({ name, exts });
  std::ranges::for_each (exts,
                         [=] (const string &t)
                           {
                             auto iter = mSupportClass.find (t);
                             if (iter != mSupportClass.end ())
                               {
                                 auto &cls_list = iter->second;
                                 cls_list.emplace_back (name, fun);
                               }
                             else
                               {
                                 auto cls_list
                                     = ExtClassList{ { name, fun } };
                                 mSupportClass.insert ({ t, cls_list });
                               }
                           });
  return static_cast<int> (mSupportMimeTypes.size ());
}

optional<FileFactory::ExtClass>
FileFactory::findMatchClass (const std::string &filename)
{
  auto ext = filenameExtension (filename);
  if (ext.empty ())
    return nullopt;

  if (mSupportClass.find (ext) == mSupportClass.end ())
    return nullopt;

  auto cls_list = mSupportClass[ext];
  if (cls_list.size () == 1)
    return cls_list[0];

  auto cls_name
      = ApvlvParams::instance ()->getGroupStringOrDefault (ext, "engine", "");
  if (cls_name.empty ())
    return cls_list[0];

  for (auto const &cls : cls_list)
    {
      if (strcasecmp (cls.first.c_str (), cls_name.c_str ()) == 0)
        return cls;
    }

  return cls_list[0];
}

unique_ptr<File>
FileFactory::loadFile (const string &filename)
{
  auto cls = findMatchClass (filename);
  if (!cls.has_value ())
    {
      qWarning () << "no engine to open " << filename;
      return nullptr;
    }

  auto file = unique_ptr<File> (cls->second ());
  if (file->setFilename (filename))
    return file;
  else
    {
      qWarning () << "open " << QString::fromLocal8Bit (filename) << " error";
      return nullptr;
    }
}

File::~File ()
{
  mPages.clear ();
  srcPages.clear ();
  srcMimeTypes.clear ();
}

unique_ptr<SearchFileMatch>
File::grepFile (const string &seq, bool is_case, bool is_regex,
                atomic<bool> &is_abort)
{
  vector<SearchPageMatch> page_matches;
  auto pageSum = sum ();
  for (auto pn = 0; pn < pageSum; ++pn)
    {
      if (is_abort.load () == true)
        {
          qDebug () << "grep " << QString::fromLocal8Bit (mFilename)
                    << " abort before page " << pn;
          return nullptr;
        }

      auto size = pageSizeF (pn, 0);
      string content;
      if (pageText (pn, { 0, 0, size.width, size.height }, content) == false)
        continue;

      istringstream iss{ content };
      string line;
      SearchMatchList matches;
      while (getline (iss, line))
        {
          if (is_abort.load () == true)
            {
              qDebug () << "grep " << QString::fromLocal8Bit (mFilename)
                        << " abort at page " << pn;
              return nullptr;
            }

          auto founds = apvlv::grep (line, seq, is_case, is_regex);
          for (auto const &found : founds)
            {
              SearchMatch match{ line.substr (found.first, found.second),
                                 line, found.first, found.second };
              matches.push_back (match);
            }
        }
      if (!matches.empty ())
        {
          page_matches.push_back ({ pn, matches });
        }
    }

  if (page_matches.empty ())
    return nullptr;

  auto file_match = make_unique<SearchFileMatch> ();
  file_match->filename = getFilename ();
  file_match->page_matches = std::move (page_matches);
  return file_match;
}

bool
File::pageRenderToImage (int pn, double zm, int rot, QImage *img)
{
  return false;
}

bool
File::pageRenderToWebView (int pn, double zm, int rot, WebView *webview)
{
  webview->setZoomFactor (zm);
  QUrl pdfuri = QString ("apvlv:///%1-%2-%3-%4.html")
                    .arg (pn)
                    .arg (zm)
                    .arg (rot)
                    .arg (rand ());
  webview->load (pdfuri);
  return true;
}

string
File::pathMimeType (const string &path)
{
  if (srcMimeTypes.find (path) != srcMimeTypes.end ())
    return srcMimeTypes[path];
  else if (QString::fromLocal8Bit (path).endsWith (".png"))
    return "image/png";
  else
    return "text/html";
}

int
File::pathPageNumber (const string &path)
{
  if (srcPages.find (path) != srcPages.end ())
    return srcPages[path];
  return -1;
}

optional<QByteArray>
File::pathContent (const string &path)
{
  auto words = QString::fromLocal8Bit (path).split ("-");
  int pn = words[0].toInt ();
  double zm = words[1].toDouble ();
  int rot = words[2].toInt ();

  if (QString::fromLocal8Bit (path).endsWith (".html"))
    return pathContentHtml (pn, zm, rot);
  else
    return pathContentPng (pn, zm, rot);
}

optional<QByteArray>
File::pathContentHtml (int pn, double zm, int rot)
{
  auto src = QString ("apvlv:///%1-%2-%3-%4.png")
                 .arg (pn)
                 .arg (zm)
                 .arg (rot)
                 .arg (rand ());
  auto html = QString::asprintf (html_template.c_str (),
                                 src.toStdString ().c_str ());
  return QByteArray::fromStdString (html.toStdString ());
}

optional<QByteArray>
File::pathContentPng (int pn, double zm, int rot)
{
  QImage image;
  if (pageRenderToImage (pn, zm, rot, &image) == false)
    return nullopt;

  QByteArray array;
  QBuffer buffer (&array);
  buffer.open (QIODevice::WriteOnly);
  image.save (&buffer, "PNG");
  buffer.close ();
  return array;
}

bool
File::print (int page)
{
  QPrinter printer;
  QPrintDialog dialog (&printer);
  if (dialog.exec () != QDialog::Accepted)
    return false;

  QPainter painter (&printer);
  int startPage = (page >= 0) ? page : 0;
  int endPage = (page >= 0) ? page : sum () - 1;

  for (int pn = startPage; pn <= endPage; ++pn)
    {
      if (pn > startPage)
        printer.newPage ();

      QImage image;
      if (pageRenderToImage (pn, 1.0, 0, &image))
        {
          QRect rect = painter.viewport ();
          QSize size = image.size ();
          size.scale (rect.size (), Qt::KeepAspectRatio);
          painter.drawImage (0, 0, image.scaled (size));
        }
    }
  return true;
}

}

// Local Variables:
// mode: c++
// End:


================================================
FILE: src/ApvlvFile.h
================================================
/*
 * This file is part of the apvlv package
 * Copyright (C) <2008>  <Alf>
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */
/* @CPPFILE File.h
 *
 *  Author: Alf <naihe2010@126.com>
 */

#ifndef _APVLV_FILE_H_
#define _APVLV_FILE_H_

#include <map>
#include <memory>
#include <string>
#include <vector>

#include "ApvlvFileIndex.h"
#include "ApvlvNote.h"
#include "ApvlvParams.h"
#include "ApvlvSearch.h"

namespace apvlv
{

enum class DISPLAY_TYPE
{
  IMAGE,
  HTML,
  CUSTOM,
};

//
// link to an url, or a page num
//
struct ApvlvLink
{
  int mPage;
};

struct ApvlvCover
{
  std::string content;
  std::string mime_type;
};

using ApvlvLinks = std::vector<ApvlvLink>;

struct ApvlvPoint
{
  double x;
  double y;
};

struct Size
{
  int width;
  int height;
};

struct SizeF
{
  double width;
  double height;
};

/*
 * position of a search result, or just an area
 */
struct Rectangle
{
  double p1x;
  double p1y;
  double p2x;
  double p2y;
};

using CharRectangle = Rectangle;

struct WordRectangle
{
  std::string word;
  std::vector<CharRectangle> rect_list;
};

using WordListRectangle = std::vector<WordRectangle>;

class WebView;
class FileWidget;
class File
{
public:
  virtual ~File ();

  virtual bool load (const std::string &filename) = 0;

  /* File methods */
  [[nodiscard]] virtual DISPLAY_TYPE
  getDisplayType () const
  {
    return DISPLAY_TYPE::HTML;
  }

  virtual FileWidget *
  getWidget ()
  {
    return nullptr;
  }

  const std::string &
  getFilename ()
  {
    return mFilename;
  }

  bool
  setFilename (const std::string &filename)
  {
    if (load (filename))
      {
        mFilename = filename;

        auto note_path = Note::notePathOfFile (this);
        mNote.load (note_path);

        return true;
      }

    return false;
  }

  const ApvlvCover &
  getCover ()
  {
    return mCover;
  }

  Note *
  getNote ()
  {
    return &mNote;
  }

  const FileIndex &
  getIndex ()
  {
    return mIndex;
  }

  virtual std::unique_ptr<SearchFileMatch>
  grepFile (const std::string &seq, bool is_case, bool is_regex,
            std::atomic<bool> &is_abort);

  virtual int
  sum ()
  {
    return 1;
  };

  // Page methods
  Size
  pageSize (int page, int rot)
  {
    auto sizef = pageSizeF (page, rot);
    return { static_cast<int> (sizef.width),
             static_cast<int> (sizef.height) };
  }

  virtual SizeF
  pageSizeF (int page, int rot)
  {
    return { 0, 0 };
  }

  virtual int
  pageNumberWrap (int page)
  {
    auto scrdoc
        = ApvlvParams::instance ()->getBoolOrDefault ("autoscrolldoc");
    int c = sum ();

    if (page >= 0 && page < c)
      {
        return page;
      }
    else if (page >= c && scrdoc)
      {
        return page % c;
      }
    else if (page < 0 && scrdoc)
      {
        while (page < 0)
          page += c;
        return page;
      }
    else
      {
        return -1;
      }
  }

  virtual bool pageRenderToImage (int pn, double zm, int rot, QImage *img);

  virtual bool pageRenderToWebView (int pn, double zm, int rot,
                                    WebView *webview);

  // some ebooks only have image as page
  virtual bool
  pageIsOnlyImage (int pn)
  {
    return false;
  }

  virtual std::unique_ptr<ApvlvLinks>
  pageLinks (int pn)
  {
    return nullptr;
  }

  virtual bool
  pageText (int pn, const Rectangle &rect, std::string &text)
  {
    return false;
  }

  virtual std::unique_ptr<WordListRectangle>
  pageSearch (int pn, const char *str)
  {
    return nullptr;
  }

  virtual std::optional<std::vector<Rectangle>>
  pageHighlight (int pn, const ApvlvPoint &pa, const ApvlvPoint &pb)
  {
    return std::nullopt;
  }

  // path methods
  virtual std::optional<QByteArray> pathContent (const std::string &path);

  virtual std::string pathMimeType (const std::string &path);

  virtual int pathPageNumber (const std::string &path);

  virtual bool print (int page = -1);

protected:
  File () {}

  std::string mFilename;
  FileIndex mIndex;
  std::vector<std::string> mPages;
  std::map<std::string, int> srcPages;
  std::map<std::string, std::string> srcMimeTypes;
  ApvlvCover mCover;
  Note mNote;

private:
  std::optional<QByteArray> pathContentHtml (int, double, int);
  std::optional<QByteArray> pathContentPng (int, double, int);
};

class FileFactory
{
public:
  static int registerClass (const std::string &name,
                            const std::function<File *()> &fun,
                            std::initializer_list<std::string> exts);

  static const std::map<std::string, std::vector<std::string>> &
  supportMimeTypes ();

  static std::vector<std::string> supportFileExts ();

  static std::ostream &typeEngineDescription (std::ostream &os);

  using ExtClass = std::pair<std::string, std::function<File *()>>;
  using ExtClassList = std::vector<ExtClass>;

  static std::optional<ExtClass> findMatchClass (const std::string &filename);
  static std::unique_ptr<File> loadFile (const std::string &filename);

private:
  static std::map<std::string, std::vector<std::string>> mSupportMimeTypes;
  static std::map<std::string, ExtClassList> mSupportClass;
};

#define FILE_TYPE_DECLARATION(cls)                                           \
private:                                                                     \
  static int class_id_of_##cls
#define FILE_TYPE_DEFINITION(name, cls, ...)                                 \
  int cls::class_id_of_##cls = FileFactory::registerClass (                  \
      name,                                                                  \
      [] () -> File *                                                        \
        {                                                                    \
          return new cls ();                                                 \
        },                                                                   \
      __VA_ARGS__)

};

#endif

/* Local Variables: */
/* mode: c++ */
/* End: */


================================================
FILE: src/ApvlvFileIndex.cc
================================================
/*
 * This file is part of the apvlv package
 * Copyright (C) <2008>  <Alf>
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */
/* @CPPFILE File.cc
 *
 *  Author: Alf <naihe2010@126.com>
 */

#include <QBuffer>
#include <QDebug>
#include <algorithm>
#include <filesystem>
#include <iostream>

#include "ApvlvFile.h"
#include "ApvlvFileIndex.h"
#include "ApvlvUtil.h"

namespace apvlv
{

using namespace std;

void
FileIndex::loadDirectory (const string &path1)
{
  auto exts = FileFactory::supportFileExts ();

  try
    {
      for (auto &entry : filesystem::directory_iterator (
               path1,
               filesystem::directory_options::follow_directory_symlink))
        {
          if (entry.is_directory ())
            {
              auto index
                  = FileIndex (entry.path ().filename ().string (), 0,
                               entry.path ().string (), FileIndexType::DIR);
              index.loadDirectory (entry.path ().string ());
              auto last = entry.last_write_time ();
              index.mtime = filesystemTimeToMSeconds (last);
              if (!index.mChildrenIndex.empty ())
                {
                  mChildrenIndex.emplace_back (index);
                }
            }
          else if (entry.file_size () > 0)
            {
              auto file_ext = filenameExtension (entry.path ().string ());
              if (find (exts.cbegin (), exts.cend (), file_ext)
                  != exts.cend ())
                {
                  auto index = FileIndex (entry.path ().filename ().string (),
                                          0, entry.path ().string (),
                                          FileIndexType::FILE);
                  index.size = static_cast<int64_t> (entry.file_size ());
                  auto last = entry.last_write_time ();
                  index.mtime = filesystemTimeToMSeconds (last);
                  mChildrenIndex.emplace_back (index);
                }
            }
        }
    }
  catch (filesystem::filesystem_error &err)
    {
      qWarning () << "file system error: " << err.what ();
    }
}

void
FileIndex::moveChildChildren (const FileIndex &other_index)
{
  Q_ASSERT (type == FileIndexType::FILE);
  Q_ASSERT (other_index.type == FileIndexType::FILE);
  mChildrenIndex = other_index.mChildrenIndex;
}

void
FileIndex::removeChild (const FileIndex &child)
{
  mChildrenIndex.remove (child);
}

FileIndex::FileIndex (const string &title, int page, const string &path,
                      FileIndexType type)
{
  this->title = title;
  this->page = page;
  this->path = path;
  this->type = type;
  if (const filesystem::path p (path); exists (p))
    {
      auto note_path = Note::notePathOfPath (path);
      if (const filesystem::path np (note_path); exists (np))
        {
          const auto note = make_unique<Note> ();
          note->load (note_path);
          this->score = note->score ();
          this->tags = note->tagString ();
        }
    }
}

FileIndex::~FileIndex () = default;
}

// Local Variables:
// mode: c++
// End:


================================================
FILE: src/ApvlvFileIndex.h
================================================
/*
 * This file is part of the apvlv package
 * Copyright (C) <2008>  <Alf>
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */
/* @CPPFILE File.h
 *
 *  Author: Alf <naihe2010@126.com>
 */

#ifndef _APVLV_FILE_INDEX_H_
#define _APVLV_FILE_INDEX_H_

#include <QImage>
#include <list>
#include <string>

#include "ApvlvSearch.h"

namespace apvlv
{

enum class FileIndexType
{
  PAGE,
  FILE,
  DIR
};

class FileIndex
{
public:
  FileIndex () = default;
  FileIndex (const std::string &title, int page, const std::string &path,
             FileIndexType type);
  ~FileIndex ();

  friend bool
  operator== (const FileIndex &a, const FileIndex &b)
  {
    return a.title == b.title && a.page == b.page && a.path == b.path
           && a.anchor == b.anchor;
  }

  void loadDirectory (const std::string &path1);
  void moveChildChildren (const FileIndex &other_index);
  void removeChild (const FileIndex &child);

  /* public variables */
  FileIndexType type{ FileIndexType::PAGE };
  std::string title;
  int page{ 0 };
  std::string path;
  std::string anchor;
  std::list<FileIndex> mChildrenIndex;

  /* public file variables */
  std::int64_t size{ 0 };
  std::int64_t mtime{ 0 };
  std::string tags;
  float score{ 0.0f };
};

}

#endif

/* Local Variables: */
/* mode: c++ */
/* End: */


================================================
FILE: src/ApvlvFileWidget.cc
================================================
/*
 * This file is part of the apvlv package
 *
 * Copyright (C) 2008 Alf.
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2.0 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
/* @CPPFILE ApvlvFileWidget.cc
 *
 *  Author: Alf <naihe2010@126.com>
 */

#include "ApvlvFileWidget.h"
#include "ApvlvParams.h"

namespace apvlv
{

using namespace std;

void
FileWidget::scroll (int times, int w, int h)
{
  // need impl in child
}

double
FileWidget::scrollRate ()
{
  if (mValScrollBar == nullptr)
    return mScrollValue;

  double maxv = mValScrollBar->maximum () - mValScrollBar->minimum ();
  double val = mValScrollBar->value () / maxv;
  if (val > 1.0)
    {
      return 1.00;
    }
  else if (val > 0.0)
    {
      return val;
    }
  else
    {
      return 0.00;
    }
}

void
FileWidget::scrollTo (double s, double y)
{
  if (!mValScrollBar)
    return;

  auto maxv = mValScrollBar->maximum () - mValScrollBar->minimum ();
  auto val = static_cast<int> (y * maxv);
  mValScrollBar->setValue (val);
}

void
FileWidget::scrollUp (int times)
{
  if (mValScrollBar == nullptr)
    return;

  auto const kj_pixels = ApvlvParams::instance ()->getIntOrDefault (
      "kj_pixels", APVLV_KJ_PIXELS_DEFAULT);
  auto rate = kj_pixels * times;
  if (mValScrollBar->value () - rate >= mValScrollBar->minimum ())
    {
      mValScrollBar->setValue (mValScrollBar->value () - rate);
    }
  else if (mValScrollBar->value () > mValScrollBar->minimum ())
    {
      mValScrollBar->setValue (mValScrollBar->minimum ());
    }
  else
    {
      auto params = ApvlvParams::instance ();
      if (params->getBoolOrDefault ("autoscrollpage"))
        {
          if (mPageNumber == 0)
            {
              if (params->getBoolOrDefault ("autoscrolldoc"))
                {
                  showPage (mFile->sum () - 1, 1.0);
                }
            }
          else
            {
              showPage (mPageNumber - 1, 1.0);
            }
        }
    }
}

void
FileWidget::scrollDown (int times)
{
  if (!mValScrollBar)
    return;

  auto const kj_pixels = ApvlvParams::instance ()->getIntOrDefault (
      "kj_pixels", APVLV_KJ_PIXELS_DEFAULT);
  auto rate = kj_pixels * times;
  if (mValScrollBar->value () + rate <= mValScrollBar->maximum ())
    {
      mValScrollBar->setValue (mValScrollBar->value () + rate);
    }
  else if (mValScrollBar->value () < mValScrollBar->maximum ())
    {
      mValScrollBar->setValue (mValScrollBar->maximum ());
    }
  else
    {
      auto params = ApvlvParams::instance ();
      if (params->getBoolOrDefault ("autoscrollpage"))
        {
          if (mPageNumber == mFile->sum () - 1)
            {
              if (params->getBoolOrDefault ("autoscrolldoc"))
                {
                  showPage (0, 0.0);
                }
            }
          else
            {
              showPage (mPageNumber + 1, 0.0);
            }
        }
    }
}

void
FileWidget::scrollLeft (int times)
{
  if (!mHalScrollBar)
    return;

  auto const hl_pixels = ApvlvParams::instance ()->getIntOrDefault (
      "hl_pixels", APVLV_HL_PIXELS_DEFAULT);
  auto val = mHalScrollBar->value () - hl_pixels * times;
  if (val > mHalScrollBar->minimumWidth ())
    {
      mHalScrollBar->setValue (val);
    }
  else
    {
      mHalScrollBar->setValue (mHalScrollBar->minimumWidth ());
    }
}

void
FileWidget::scrollRight (int times)
{
  if (!mHalScrollBar)
    return;

  auto const hl_pixels = ApvlvParams::instance ()->getIntOrDefault (
      "hl_pixels", APVLV_HL_PIXELS_DEFAULT);
  auto val = mHalScrollBar->value () + hl_pixels * times;
  if (val + mHalScrollBar->width () < mHalScrollBar->maximumWidth ())
    {
      mHalScrollBar->setValue (val);
    }
  else
    {
      mHalScrollBar->setValue (mHalScrollBar->maximumWidth ()
                               - mHalScrollBar->width ());
    }
}

}

/* Local Variables: */
/* mode: c++ */
/* End: */


================================================
FILE: src/ApvlvFileWidget.h
================================================
/*
 * This file is part of the apvlv package
 *
 * Copyright (C) 2008 Alf.
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2.0 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
/* @CPPFILE ApvlvFileWidget.h
 *
 *  Author: Alf <naihe2010@126.com>
 */

#ifndef _APVLV_FILE_WIDGET_H_
#define _APVLV_FILE_WIDGET_H_

#include <QScrollBar>
#include <string>

#include "ApvlvFile.h"

namespace apvlv
{

const int APVLV_HL_PIXELS_DEFAULT = 40;
const int APVLV_KJ_PIXELS_DEFAULT = 15;

const double APVLV_ZOOMRATE_DEFAULT = 1.3f;
const int INVALID_PAGENUM = -1;

class File;
class FileWidget : public QObject
{
  Q_OBJECT
public:
  FileWidget () = default;

  ~FileWidget () override = default;

  [[nodiscard]] virtual QWidget *widget () = 0;

  [[nodiscard]] virtual File *
  file () const
  {
    return mFile;
  }

  virtual void
  setFile (File *file)
  {
    mFile = file;
  }

  virtual int
  pageNumber ()
  {
    return mPageNumber;
  }

  virtual std::string
  anchor ()
  {
    return mAnchor;
  }

  virtual double
  zoomrate ()
  {
    return mZoomrate;
  }

  virtual void
  showPage (int pn, double rate)
  {
    mPageNumber = pn;
    mScrollValue = rate;
  }

  virtual void
  showPage (int pn, const std::string &anchor)
  {
    mPageNumber = pn;
    mAnchor = anchor;
  }

  virtual void scroll (int times, int w, int h);

  virtual double scrollRate ();

  virtual void scrollTo (double s, double y);

  virtual void scrollUp (int times);

  virtual void scrollDown (int times);

  virtual void scrollLeft (int times);

  virtual void scrollRight (int times);

  virtual void
  setZoomrate (double zm)
  {
    mZoomrate = zm;
  }

  virtual void
  setRotate (int rot)
  {
    mRotate = rot;
  }

  virtual int
  rotate ()
  {
    return mRotate;
  }

  void
  setAnchor (const std::string &anchor)
  {
    mAnchor = anchor;
  }

  virtual void
  setSearchSelect (int select)
  {
    mSearchSelect = select;
  }

  [[nodiscard]] virtual int
  searchSelect () const
  {
    return mSearchSelect;
  }

  virtual void
  setSearchStr (const std::string &str)
  {
    mSearchStr = str;
  }

  [[nodiscard]] virtual std::string
  searchStr () const
  {
    return mSearchStr;
  }

  virtual void
  setSearchResults (const WordListRectangle &wlr)
  {
    mSearchResults = wlr;
  }

  virtual const WordListRectangle &
  searchResults ()
  {
    return mSearchResults;
  }

  virtual void
  setSelects (const std::vector<Rectangle> &rect_list)
  {
    mSelects = rect_list;
  }

  virtual const std::vector<Rectangle> &
  selects ()
  {
    return mSelects;
  }

protected:
  QScrollBar *mValScrollBar{ nullptr };
  QScrollBar *mHalScrollBar{ nullptr };

  File *mFile{ nullptr };

  int mPageNumber{ INVALID_PAGENUM };
  double mScrollValue{ 0.0f };
  std::string mAnchor;
  double mZoomrate{ APVLV_ZOOMRATE_DEFAULT };
  int mRotate{ 0 };

  std::string mSearchStr;
  WordListRectangle mSearchResults;
  int mSearchSelect{ 0 };

  std::vector<Rectangle> mSelects;
};
}

#endif

/* Local Variables: */
/* mode: c++ */
/* End: */


================================================
FILE: src/ApvlvFrame.cc
================================================
/*
 * This file is part of the apvlv package
 *
 * Copyright (C) 2008 Alf.
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2.0 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
/* @CPPFILE ApvlvFrame.cc
 *
 *  Author: Alf <naihe2010@126.com>
 */

#include <QBuffer>
#include <QClipboard>
#include <QMessageBox>
#include <filesystem>
#include <fstream>
#include <memory>

#include "ApvlvFileWidget.h"
#include "ApvlvFrame.h"
#include "ApvlvImageWidget.h"
#include "ApvlvInfo.h"
#include "ApvlvParams.h"
#include "ApvlvView.h"
#include "ApvlvWeb.h"
#include "ApvlvWebViewWidget.h"

namespace apvlv
{

using namespace std;
using namespace Qt;
using namespace CommandModeType;

std::vector<const char *> ApvlvFrame::ZoomLabel = {
  QT_TR_NOOP ("Default"),
  QT_TR_NOOP ("Fit Width"),
  QT_TR_NOOP ("Fit Height"),
  QT_TR_NOOP ("Custom"),
};

ApvlvFrame::ApvlvFrame (ApvlvView *view) : mToolStatus (this)
{
  mInuse = true;

  mView = view;

  mProCmd = 0;

  mZoomMode = ZoomMode::NORMAL;

  mSearchResults = nullptr;
  mSearchStr = "";

  setLayout (&mVbox);

  mPaned.setHandleWidth (4);
  mDirectoryWidth = DEFAULT_CONTENT_WIDTH;

  auto f_width = ApvlvParams::instance ()->getIntOrDefault ("fix_width");
  auto f_height = ApvlvParams::instance ()->getIntOrDefault ("fix_height");

  if (f_width > 0 && f_height > 0)
    {
      mPaned.setFixedSize (f_width, f_height);

      mVbox.addLayout (&mHBoxLayout, 1);
      mHBoxLayout.addWidget (&mPaned, 0);
    }
  else
    {
      mVbox.addWidget (&mPaned, 1);
    }

  mDirectory.setFrame (this);
  QObject::connect (this, SIGNAL (indexGenerited (const FileIndex &)),
                    &mDirectory, SLOT (setIndex (const FileIndex &)));

  // left is Directory
  mPaned.addWidget (&mDirectory);

  // Right is Text
  mPaned.addWidget (&mTextFrame);
  mTextFrame.setLayout (&mTextLayout);
  mTextLayout.addWidget (&mToolStatus, 0);
  auto guiopt = ApvlvParams::instance ()->getStringOrDefault ("guioptions");
  if (guiopt.find ('S') == string::npos)
    {
      mToolStatus.hide ();
    }

  mVbox.addWidget (&mStatus, 0);
  if (guiopt.find ('s') == string::npos)
    {
      mStatus.hide ();
    }
  qDebug () << "ApvlvFrame: " << this << " be created";
}

ApvlvFrame::~ApvlvFrame ()
{
  qDebug () << "ApvlvFrame: " << this << " be freed";
  saveLastPosition (mFilestr);
}

void
ApvlvFrame::inuse (bool use)
{
  mInuse = use;
}

bool
ApvlvFrame::inuse ()
{
  return mInuse;
}

bool
ApvlvFrame::loadUri (const string &uri)
{
  mFile = make_unique<ApvlvWEB> ();
  mFile->load (uri);
  setWidget (mFile->getDisplayType ());
  refresh (0, 0.0);
  return true;
}

const char *
ApvlvFrame::filename ()
{
  return mFilestr.empty () ? nullptr : mFilestr.c_str ();
}

bool
ApvlvFrame::print (int ct)
{
  if (!mFile)
    return false;
  return mFile->print (ct);
}

int
ApvlvFrame::getSkip ()
{
  return mSkip;
}

void
ApvlvFrame::setSkip (int ct)
{
  mSkip = ct;
}

void
ApvlvFrame::toggleDirectory ()
{
  auto show = !isShowDirectory ();
  toggleDirectory (show);
}

void
ApvlvFrame::toggleDirectory (bool show)
{
  if (show)
    {
      if (!mDirectory.isReady ())
        {
          qWarning () << "file " << mFilestr << " has no directory";
          show = false;
        }
    }

  auto sizes = mPaned.sizes ();
  if (show)
    {
      mDirIndex = {};
      if (sizes[0] == 0)
        {
          auto psize = mPaned.size ();
          sizes = { mDirectoryWidth, psize.width () - mPaned.handleWidth ()
                                         - DEFAULT_CONTENT_WIDTH };
          mPaned.setSizes (sizes);
        }
    }
  else
    {
      if (sizes[0] != 0)
        mDirectoryWidth = sizes[0];

      auto psize = mPaned.size ();
      sizes = { 0, psize.width () - mPaned.handleWidth () };
      mPaned.setSizes (sizes);
    }
}

void
ApvlvFrame::setActive (bool act)
{
  mActive = act;

  auto wid = mWidget->widget ();
  if (act)
    {
      QTimer::singleShot (50, wid, SLOT (setFocus ()));
    }
  else
    {
      wid->clearFocus ();
      clearFocus ();
    }

  if (mActive && filename ())
    {
      auto path = filesystem::path (filename ());
      auto base = path.filename ();
      mView->setTitle (base.string ());
    }

  mStatus.setActive (act);
}

void
ApvlvFrame::setDirIndex (const string &path)
{
  mDirIndex = { "", 0, path, FileIndexType::DIR };
  mDirIndex.loadDirectory (path);
  emit indexGenerited (mDirIndex);
  toggleDirectory (true);
}

bool
ApvlvFrame::toggledControlDirectory (bool is_right)
{
  if (!isShowDirectory ())
    {
      return false;
    }

  if (auto controlled = isControlledDirectory (); !controlled && !is_right)
    {
      mDirectory.setActive (true);
      return true;
    }
  else if (controlled && is_right)
    {
      mDirectory.setActive (false);
      return true;
    }

  return false;
}

bool
ApvlvFrame::isShowDirectory ()
{
  auto sizes = mPaned.sizes ();
  return sizes[0] > 1;
}

bool
ApvlvFrame::isControlledDirectory ()
{
  if (!isShowDirectory ())
    return false;

  return mDirectory.isActive ();
}

ApvlvFrame *
ApvlvFrame::findByWidget (QWidget *widget)
{
  for (auto doc = widget; doc != nullptr; doc = doc->parentWidget ())
    {
      if (doc->inherits ("apvlv::ApvlvFrame"))
        return dynamic_cast<ApvlvFrame *> (doc);
    }

  return nullptr;
}

ApvlvStatus::ApvlvStatus ()
{
  setFrameShape (QFrame::NoFrame);
  setLayout (&mLayout);
}

void
ApvlvStatus::setActive (bool act)
{
  auto children = findChildren<QLabel *> ();
  for (auto child : children)
    {
      if (child)
        {
          child->setEnabled (act);
        }
    }
}

void
ApvlvStatus::showMessages (const vector<string> &msgs)
{
  auto children = findChildren<QLabel *> ();
  vector<QWidget *> newlabels;
  for (std::size_t ind = 0; ind < msgs.size (); ++ind)
    {
      if (children.size () > (qsizetype)ind)
        {
          auto label = children[ind];
          label->setText (QString::fromLocal8Bit (msgs[ind]));
        }
      else
        {
          auto label = new QLabel ();
          label->setText (QString::fromLocal8Bit (msgs[ind]));
          newlabels.push_back (label);
        }
    }

  auto hbox = layout ();
  for (auto label : newlabels)
    {
      hbox->addWidget (label);
    }
}

ApvlvToolStatus::ApvlvToolStatus (ApvlvFrame *frame) : mFrame (frame)
{
  auto paction = addAction (QIcon::fromTheme (QIcon::ThemeIcon::GoPrevious),
                            tr ("Previous Page"));
  QObject::connect (paction, SIGNAL (triggered (bool)), mFrame,
                    SLOT (previousPage ()));
  auto naction = addAction (QIcon::fromTheme (QIcon::ThemeIcon::GoNext),
                            tr ("Next Page"));
  QObject::connect (naction, SIGNAL (triggered (bool)), mFrame,
                    SLOT (nextPage ()));

  mPageValue.setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed);
  QObject::connect (&mPageValue, SIGNAL (editingFinished ()), this,
                    SLOT (gotoPage ()));
  addWidget (&mPageValue);
  addWidget (&mPageSum);
  addSeparator ();

  addWidget (&mScrollRate);
  addSeparator ();

  auto iaction = addAction (QIcon::fromTheme (QIcon::ThemeIcon::ZoomIn),
                            tr ("Zoom In"));
  QObject::connect (iaction, SIGNAL (triggered (bool)), mFrame,
                    SLOT (zoomIn ()));
  auto oaction = addAction (QIcon::fromTheme (QIcon::ThemeIcon::ZoomOut),
                            tr ("Zoom Out"));
  QObject::connect (oaction, SIGNAL (triggered (bool)), mFrame,
                    SLOT (zoomOut ()));

  addWidget (&mZoomType);
  for (auto const &str : ApvlvFrame::ZoomLabel)
    {
      mZoomType.addItem (ApvlvFrame::tr (str));
    }
  QObject::connect (&mZoomType, SIGNAL (currentIndexChanged (int)), mFrame,
                    SLOT (setZoomMode (int)));
  mZoomType.setLineEdit (&mZoomValue);
  addSeparator ();

#ifdef APVLV_WITH_OCR
  addWidget (&mOcrParse);
  mOcrParse.setText (tr ("OCR Parse"));
  QObject::connect (&mOcrParse, SIGNAL (checkStateChanged (Qt::CheckState)),
                    mFrame, SLOT (ocrParse ()));

  addAction (&mOcrCopy);
  mOcrCopy.setIcon (QIcon::fromTheme (QIcon::ThemeIcon::Scanner));
  mOcrCopy.setText (tr ("OCR Copy"));
  QObject::connect (&mOcrCopy, SIGNAL (triggered (bool)), mFrame,
                    SLOT (ocrCopy ()));
#endif
}

void
ApvlvToolStatus::updateValue (int pn, int totpn, double zm, double sr)
{
  mPageValue.setText (QString::number (pn));
  mPageSum.setText (QString::fromLocal8Bit ("/%1").arg (totpn));
  if (mZoomType.currentIndex () == 3)
    {
      mZoomValue.setText (
          QString::fromLocal8Bit ("%1%").arg (static_cast<int> (zm * 100)));
    }
  mScrollRate.setText (
      QString::fromLocal8Bit ("%1%").arg (static_cast<int> (sr * 100)));

#ifdef APVLV_WITH_OCR
  auto need_ocr = mFrame->mFile->pageIsOnlyImage (mFrame->pageNumber ());
  mOcrParse.setEnabled (need_ocr);
  mOcrCopy.setEnabled (need_ocr);
#endif
}

void
ApvlvToolStatus::gotoPage ()
{
  auto text = mPageValue.text ().trimmed ();
  auto pn = text.toInt ();
  if (pn != mFrame->pageNumber ())
    {
      mFrame->showPage (pn, 0.f);
    }
}

CmdReturn
ApvlvFrame::subProcess ([[maybe_unused]] int ct, uint key)
{
  uint procmd = mProCmd;
  mProCmd = 0;
  switch (procmd)
    {
    case 'm':
      markposition (char (key));
      break;

    case '\'':
      jump (char (key));
      break;

    case 'z':
      if (key == 'i')
        {
          zoomIn ();
        }
      else if (key == 'o')
        {
          zoomOut ();
        }
      else if (key == 'h')
        {
          setZoomMode (static_cast<int> (ZoomMode::FITHEIGHT));
        }
      else if (key == 'w')
        {
          setZoomMode (static_cast<int> (ZoomMode::FITWIDTH));
        }
      break;

    default:
      return CmdReturn::NO_MATCH;
      break;
    }

  return CmdReturn::MATCH;
}

void
ApvlvFrame::previousPage ()
{
  previousPage (1);
}

void
ApvlvFrame::nextPage ()
{
  nextPage (1);
}

void
ApvlvFrame::setZoomMode (int mode)
{
  if (mode < static_cast<int> (ZoomMode::CUSTOM))
    {
      switch (static_cast<ZoomMode> (mode))
        {
          using enum ZoomMode;
        case NORMAL:
          setZoomString ("normal");
          break;
        case FITWIDTH:
          setZoomString ("fitwidth");
          break;
        case FITHEIGHT:
          setZoomString ("fitheight");
          break;
        case CUSTOM:
          break;
        }
    }

  updateStatus ();
}

void
ApvlvFrame::zoomIn ()
{
  auto zoomrate = mWidget->zoomrate ();
  setZoomrate (zoomrate * 1.1);
  updateStatus ();
}

void
ApvlvFrame::zoomOut ()
{
  auto zoomrate = mWidget->zoomrate ();
  setZoomrate (zoomrate / 1.1);
  updateStatus ();
}

#ifdef APVLV_WITH_OCR
void
ApvlvFrame::ocrParse ()
{
  auto meta = mWidget->widget ()->metaObject ();
  qDebug () << "widget is " << meta->className ();
  if (!mWidget->widget ()->inherits ("apvlv::ApvlvImage"))
    return;

  auto image = dynamic_cast<ApvlvImage *> (mWidget->widget ());
  auto state = mToolStatus.mOcrParse.checkState ();
  image->ocrDisplay (state == Qt::Checked);
}

void
ApvlvFrame::ocrCopy ()
{
  auto meta = mWidget->widget ()->metaObject ();
  qDebug () << "widget is " << meta->className ();
  if (!mWidget->widget ()->inherits ("apvlv::ApvlvImage"))
    return;

  auto image = dynamic_cast<ApvlvImage *> (mWidget->widget ());
  auto text = image->ocrGetText ();
#ifdef QT_DEBUG
  QMessageBox::information (this, tr ("text in clipboard"),
                            QString::fromLocal8Bit (text.get ()));
#endif
  auto clipboard = QGuiApplication::clipboard ();
  clipboard->setText (text.get ());
}
#endif

void
ApvlvFrame::wheelEvent (QWheelEvent *event)
{
  auto angel = event->angleDelta ();
  if (angel.y () > 0)
    {
      mWidget->scrollUp (1);
      updateStatus ();
    }
  else
    {
      mWidget->scrollDown (1);
      updateStatus ();
    }
}

CmdReturn
ApvlvFrame::process (int has, int ct, uint key)
{
  emit focusIn ();

  if (mProCmd != 0)
    {
      return subProcess (ct, key);
    }

  if (!has)
    {
      ct = 1;
    }

  switch (key)
    {
    case Key_PageDown:
    case ctrlValue ('f'):
      nextPage (ct);
      break;
    case Key_PageUp:
    case ctrlValue ('b'):
      previousPage (ct);
      break;
    case ctrlValue ('d'):
      halfNextPage (ct);
      break;
    case ctrlValue ('u'):
      halfPreviousPage (ct);
      break;
    case ':':
    case '/':
    case '?':
    case 'F':
      if (isControlledDirectory ())
        {
          mDirectory.focusFilter ();
        }
      else
        {
          mView->promptCommand (char (key));
          return CmdReturn::NEED_MORE;
        }
    case 'H':
      mWidget->scrollTo (0.0, 0.0);
      break;
    case 'M':
      mWidget->scrollTo (0.0, 0.5);
      break;
    case 'L':
      mWidget->scrollTo (0.0, 1.0);
      break;
    case '0':
      mWidget->scrollLeft (INT_MAX);
      break;
    case '$':
      mWidget->scrollRight (INT_MAX);
      break;
    case ctrlValue ('p'):
    case Key_Up:
    case 'k':
      if (isControlledDirectory ())
        {
          mDirectory.scrollUp (ct);
        }
      else
        {
          mWidget->scrollUp (ct);
          updateStatus ();
        }
      break;
    case ctrlValue ('n'):
    case ctrlValue ('j'):
    case Key_Down:
    case 'j':
      if (isControlledDirectory ())
        {
          mDirectory.scrollDown (ct);
        }
      else
        {
          mWidget->scrollDown (ct);
          updateStatus ();
        }
      break;
    case Key_Backspace:
    case Key_Left:
    case ctrlValue ('h'):
    case 'h':
      if (isControlledDirectory ())
        {
          mDirectory.scrollLeft (ct);
        }
      else
        {
          mWidget->scrollLeft (ct);
          updateStatus ();
        }
      break;
    case Key_Space:
    case Key_Right:
    case ctrlValue ('l'):
    case 'l':
      if (isControlledDirectory ())
        {
          mDirectory.scrollRight (ct);
        }
      else
        {
          mWidget->scrollRight (ct);
          updateStatus ();
        }
      break;
    case Key_Return:
      directoryShowPage (mDirectory.currentItemFileIndex (), false);
      break;
    case 'R':
      reload ();
      break;
    case ctrlValue (']'):
      gotoLink (ct);
      break;
    case ctrlValue ('t'):
      returnLink (ct);
      break;
    case 't':
      if (isControlledDirectory ())
        {
          mDirectory.tag ();
        }
      else
        {
          mView->newTab (HelpPdf);
          mView->open ();
        }
      break;
    case 'T':
      mView->newTab (HelpPdf);
      mView->openDir ();
      break;
    case 'o':
      mView->open ();
      break;
    case 'O':
      mView->openDir ();
      break;
    case 'r':
      rotate (ct);
      break;
    case 'G':
      markposition ('\'');
      if (!has)
        {
          showPage (mFile->sum () - 1, 0.0);
        }
      else
        {
          showPage (ct - 1, 0.0);
        }
      break;
    case 'm':
    case '\'':
    case 'z':
      mProCmd = key;
      return CmdReturn::NEED_MORE;
      break;
    case 'n':
      if (mSearchCmd == SEARCH)
        {
          markposition ('\'');
          search ("", false);
        }
      else if (mSearchCmd == BACKSEARCH)
        {
          markposition ('\'');
          search ("", true);
        }
      break;
    case 'N':
      if (mSearchCmd == SEARCH)
        {
          markposition ('\'');
          search ("", true);
        }
      else if (mSearchCmd == BACKSEARCH)
        {
          markposition ('\'');
          search ("", false);
        }
      break;
    case 's':
      setSkip (ct);
      break;
    case 'c':
      toggleDirectory ();
      break;
    default:
      return CmdReturn::NO_MATCH;
      break;
    }

  return CmdReturn::MATCH;
}

ApvlvFrame *
ApvlvFrame::clone ()
{
  auto *ndoc = new ApvlvFrame (mView);
  ndoc->loadFile (mFilestr, false, false);
  ndoc->showPage (mWidget->pageNumber (), mWidget->scrollRate ());
  return ndoc;
}

void
ApvlvFrame::setZoomrate (double zm)
{
  mZoomMode = ZoomMode::CUSTOM;
  mWidget->setZoomrate (zm);
}

void
ApvlvFrame::setZoomString (const char *z)
{
  auto zoomrate = mWidget->zoomrate ();
  if (z != nullptr)
    {
      string_view sv (z);
      if (sv == "normal")
        {
          mZoomMode = ZoomMode::NORMAL;
          zoomrate = 1.2;
        }
      else if (sv == "fitwidth")
        {
          mZoomMode = ZoomMode::FITWIDTH;
        }
      else if (sv == "fitheight")
        {
          mZoomMode = ZoomMode::FITHEIGHT;
        }
      else
        {
          double d = strtod (z, nullptr);
          if (d > 0)
            {
              mZoomMode = ZoomMode::CUSTOM;
              zoomrate = d;
            }
        }
    }

  if (mFile)
    {
      int pn = std::max (0, pageNumber () - 1);
      auto size = mFile->pageSizeF (pn, 0);

      if (size.width > 0 && size.height > 0)
        {
          auto wid = mWidget->widget ();
          if (mZoomMode == ZoomMode::FITWIDTH)
            {
              auto x_root = wid->width ();
              zoomrate = x_root / size.width;
            }
          else if (mZoomMode == ZoomMode::FITHEIGHT)
            {
              auto y_root = wid->height ();
              zoomrate = y_root / size.height;
            }
        }
      mWidget->setZoomrate (zoomrate);
    }
}

bool
ApvlvFrame::saveLastPosition (const string &filename)
{
  if (filename.empty () || HelpPdf == filename
      || ApvlvParams::instance ()->getBoolOrDefault ("noinfo", false))
    {
      return false;
    }

  bool ret = ApvlvInfo::instance ()->updateFile (
      mWidget->pageNumber (), mSkip, mWidget->scrollRate (), filename);
  return ret;
}

bool
ApvlvFrame::loadLastPosition (const string &filename)
{
  if (filename.empty () || HelpPdf == filename
      || ApvlvParams::instance ()->getBoolOrDefault ("noinfo"))
    {
      showPage (0, 0.0);
      return false;
    }

  bool ret = false;

  auto optfp = ApvlvInfo::instance ()->file (filename);
  if (optfp)
    {
      // correctly check
      showPage (optfp.value ()->page, 0.0);
      setSkip (optfp.value ()->skip);
    }
  else
    {
      showPage (0, 0.0);
      ApvlvInfo::instance ()->updateFile (0, 0.0, mWidget->zoomrate (),
                                          filename);
    }

  return ret;
}

bool
ApvlvFrame::reload ()
{
  return loadFile (mFilestr, false, isShowDirectory ());
}

int
ApvlvFrame::pageNumber ()
{
  return mWidget ? mWidget->pageNumber () : 0;
}

bool
ApvlvFrame::loadFile (const std::string &file, bool check,
                      bool show_directory)
{
  if (check && file == mFilestr)
    {
      return false;
    }

  saveLastPosition (mFilestr);

  mFile = FileFactory::loadFile (file);

  if (mFile)
    {
      emit indexGenerited (mFile->getIndex ());

      mFilestr = file;

      if (mFile->sum () <= 1)
        {
          qDebug () << "sum () = " << mFile->sum ();
        }

      setWidget (mFile->getDisplayType ());

      loadLastPosition (file);

      setActive (true);

      mSearchStr = "";
      mSearchResults = nullptr;

      if (ApvlvParams::instance ()->getIntOrDefault ("autoreload") > 0)
        {
          mWatcher = make_unique<QFileSystemWatcher> ();
          QObject::connect (mWatcher.get (), SIGNAL (fileChanged ()), this,
                            SLOT (changed_cb ()));

          auto systempath = filesystem::path (file);
          if (filesystem::is_symlink (systempath))
            {
              auto realname = filesystem::read_symlink (systempath).string ();
              if (filesystem::is_regular_file (realname))
                {
                  mWatcher->addPath (QString::fromLocal8Bit (realname));
                }
            }
          else
            {
              mWatcher->addPath (QString::fromLocal8Bit (file));
            }
        }
    }

  if (show_directory && mFile != nullptr)
    {
      toggleDirectory (true);
    }
  else
    {
      toggleDirectory (false);
    }

  return mFile != nullptr;
}

void
ApvlvFrame::markposition (const char s)
{
  ApvlvDocPosition adp = { mWidget->pageNumber (), mWidget->scrollRate () };
  mPositions[s] = adp;
}

void
ApvlvFrame::jump (const char s)
{
  auto it = mPositions.find (s);
  if (it != mPositions.end ())
    {
      ApvlvDocPosition adp = it->second;
      markposition ('\'');
      showPage (adp.pagenum, adp.scrollrate);
    }
}

void
ApvlvFrame::showPage (int pn, double s)
{
  auto rp = mFile->pageNumberWrap (pn);
  if (rp < 0)
    return;

  mWidget->setAnchor ("");

  if (!mZoominit)
    {
      mZoominit = true;
      setZoomString (nullptr);
    }

  refresh (rp, s);
}

void
ApvlvFrame::showPage (int pn, const std::string &anchor)
{
  auto rp = mFile->pageNumberWrap (pn);
  if (rp < 0)
    return;
  if (!mZoominit)
    {
      mZoominit = true;
      setZoomString (nullptr);
    }

  mWidget->showPage (rp, anchor);
  updateStatus ();
}

void
ApvlvFrame::nextPage (int times)
{
  showPage (mWidget->pageNumber () + times, 0.0f);
}

void
ApvlvFrame::previousPage (int times)
{
  showPage (mWidget->pageNumber () - times, 0.0f);
}

void
ApvlvFrame::refresh (int pn, double s)
{
  if (mFile == nullptr)
    return;

  mWidget->showPage (pn, s);
  updateStatus ();
}

void
ApvlvFrame::halfNextPage (int times)
{
  double sr = mWidget->scrollRate ();
  int rtimes = times / 2;

  if (times % 2 != 0)
    {
      if (sr > 0.5)
        {
          sr = 0;
          rtimes += 1;
        }
      else
        {
          sr = 1;
        }
    }

  showPage (mWidget->pageNumber () + rtimes, sr);
}

void
ApvlvFrame::halfPreviousPage (int times)
{
  double sr = mWidget->scrollRate ();
  int rtimes = times / 2;

  if (times % 2 != 0)
    {
      if (sr < 0.5)
        {
          sr = 1;
          rtimes += 1;
        }
      else
        {
          sr = 0;
        }
    }

  showPage (mWidget->pageNumber () - rtimes, sr);
}

bool
ApvlvFrame::needSearch (const std::string &str, bool reverse)
{
  if (mFile == nullptr)
    return false;

  // search a different string
  if (!str.empty () && str != mSearchStr)
    {
      qDebug () << "different string.";
      mSearchStr = str;
      return true;
    }

  else if (mSearchResults == nullptr)
    {
      qDebug () << "no result.";
      return true;
    }

  // same string, but need to search next page
  else if ((!reverse
            && mWidget->searchSelect () == (int)mSearchResults->size () - 1)
           || (reverse && mWidget->searchSelect () == 0))
    {
      qDebug () << "same, but need next string: s: " << reverse
                << ", sel: " << mWidget->searchSelect ()
                << ", max: " << mSearchResults->size ();
      return true;
    }

  // same string, not need search, but has zoomed
  else
    {
      qDebug () << "same, not need next string. sel: "
                << mWidget->searchSelect ()
                << ", max: " << mSearchResults->size ();
      if (!reverse)
        {
          setHighlightAndIndex (*mSearchResults,
                                mWidget->searchSelect () + 1);
        }
      else
        {
          setHighlightAndIndex (*mSearchResults,
                                mWidget->searchSelect () - 1);
        }

      return false;
    }

  return false;
}

bool
ApvlvFrame::search (const char *str, bool reverse)
{
  if (*str == '\0' && mSearchStr.empty ())
    {
      return false;
    }

  if (*str)
    {
      mSearchCmd
          = (reverse ? CommandModeType::BACKSEARCH : CommandModeType::SEARCH);
    }

  if (!needSearch (str, reverse))
    {
      return true;
    }

  mSearchResults = nullptr;
  unsetHighlight ();

  auto wrap = ApvlvParams::instance ()->getBoolOrDefault ("wrapscan");

  auto i = mWidget->pageNumber ();
  auto sum = mFile->sum ();
  auto from = i;
  bool search = false;
  while (true)
    {
      if (*str != 0 || search)
        {
          mSearchResults
              = mFile->pageSearch ((i + sum) % sum, mSearchStr.c_str ());
          if (mSearchResults != nullptr && !mSearchResults->empty ())
            {
              if (i != mWidget->pageNumber ())
                showPage (i, 0.0);
              auto results = *mSearchResults;
              auto sel = 0;
              if (reverse)
                sel = static_cast<int> (results.size () - 1);
              setHighlightAndIndex (results, sel);
              return true;
            }
        }

      search = true;

      if (!reverse && i < (wrap ? (from + sum) : (sum - 1)))
        {
          i++;
        }
      else if (reverse && i > (wrap ? (from - sum) : 0))
        {
          i--;
        }
      else
        {
          mView->errorMessage (string ("can't find word: "), mSearchStr);
          return false;
        }
    }
}

bool
ApvlvFrame::totext (const char *file)
{
  if (mFile == nullptr)
    return false;

  auto pn = mWidget->pageNumber ();
  string txt;
  auto size = mFile->pageSizeF (pn, 0);
  bool ret = mFile->pageText (pn, { 0, 0, size.width, size.height }, txt);
  if (ret)
    {
      fstream fs{ filename (), ios::out };
      if (fs.is_open ())
        {
          fs.write (txt.c_str (), txt.length ());
          fs.close ();
          return true;
        }
    }
  return false;
}

bool
ApvlvFrame::rotate (int ct)
{
  // just hack
  if (ct == 1)
    ct = 90;

  if (ct % 90 != 0)
    {
      mView->errorMessage ("Not a 90 times value: ", ct);
      return false;
    }

  auto rotate = mWidget->rotate ();
  rotate += ct;
  while (rotate < 0)
    {
      rotate += 360;
    }
  while (rotate > 360)
    {
      rotate -= 360;
    }
  mWidget->setRotate (rotate);
  refresh (mWidget->pageNumber (), 0.0);
  return true;
}

void
ApvlvFrame::gotoLink ([[maybe_unused]] int ct)
{
  // need impl
}

void
ApvlvFrame::returnLink ([[maybe_unused]] int ct)
{
  // need impl
}

void
ApvlvFrame::directoryShowPage (const FileIndex *index, bool force)
{
  if (index == nullptr)
    return;

  if (index->type == FileIndexType::FILE)
    {
      loadFile (index->path, true, true);
      return;
    }

  auto file = mDirectory.currentFileFileIndex ();
  if (file && file->path != mFilestr)
    loadFile (file->path, true, true);

  if (index->type == FileIndexType::PAGE)
    {
      if (index->page != mWidget->pageNumber ()
          || index->anchor != mWidget->anchor ())
        showPage (index->page, index->anchor);
    }
}

void
ApvlvFrame::setWidget (DISPLAY_TYPE type)
{
  auto sizes = mPaned.sizes ();

  if (type == DISPLAY_TYPE::IMAGE)
    {
      mWidget = make_unique<ImageWidget> ();
      mWidget->setFile (mFile.get ());
#ifdef APVLV_WITH_OCR
      ocrParse ();
#endif
    }
  else if (type == DISPLAY_TYPE::HTML)
    {
      mWidget = make_unique<WebViewWidget> ();
      mWidget->setFile (mFile.get ());
    }
  else
    {
      mWidget.reset (mFile->getWidget ());
    }
  mTextLayout.addWidget (mWidget->widget (), 1);

  mPaned.setSizes (sizes);
}

void
ApvlvFrame::unsetHighlight ()
{
  mWidget->setSearchStr ("");
  mWidget->setSearchSelect (0);
  mWidget->setSearchResults ({});
}

void
ApvlvFrame::setHighlightAndIndex (const WordListRectangle &poses, int sel)
{
  if (!poses[sel].word.empty ())
    {
      mWidget->setSearchStr (poses[sel].word);
    }
  mWidget->setSearchSelect (sel);
  mWidget->setSearchResults (poses);

  auto sr = mWidget->scrollRate ();
  if (!poses[sel].rect_list.empty ())
    {
      auto rect = poses[sel].rect_list[0];
      auto size
          = mFile->pageSizeF (mWidget->pageNumber (), mWidget->rotate ());
      if (size.height > 0)
        {
          sr = rect.p2y / size.height;
        }
    }

  refresh (mWidget->pageNumber (), sr);
}

void
ApvlvFrame::updateStatus ()
{
  if (filename ())
    {
      vector<string> labels;

      int pn = pageNumber () + 1;
      int totpn = mFile->sum ();
      auto zm = mWidget->zoomrate ();
      auto sr = mWidget->scrollRate ();
      string anchor = mWidget->anchor ();

      auto systempath = filesystem::path (filename ());
      auto bn = systempath.filename ();
      labels.emplace_back (bn.string ());

      auto ss = QString ("%1/%2").arg (pn).arg (totpn);
      labels.emplace_back (ss.toStdString ());

      ss = QString ("%1%").arg (static_cast<int> (zm * 100));
      labels.emplace_back (ss.toStdString ());

      ss = QString ("%1%").arg (static_cast<int> (sr * 100));
      labels.emplace_back (ss.toStdString ());

      mStatus.showMessages (labels);

      mToolStatus.updateValue (pn, totpn, zm, sr);

      mDirectory.setCurrentIndex (mFilestr, mWidget->pageNumber (),
                                  mWidget->anchor ());
    }
}

bool
ApvlvFrame::isStatusHidden ()
{
  return mStatus.isHidden ();
}

void
ApvlvFrame::statusShow ()
{
  mStatus.show ();
}

void
ApvlvFrame::statusHide ()
{
  mStatus.hide ();
}

}

// Local Variables:
// mode: c++
// End:


================================================
FILE: src/ApvlvFrame.h
================================================
/*
 * This file is part of the apvlv package
 *
 * Copyright (C) 2008 Alf.
 *
 * Contact: Alf <naihe2010@126.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2.0 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to t
Download .txt
gitextract_tk5z3yo1/

├── .clang-format
├── .dir-locals.el
├── .github/
│   └── workflows/
│       └── cmake-multi-platform.yml
├── .gitignore
├── AUTHORS
├── CMakeLists.txt
├── COPYING
├── NEWS
├── README.md
├── THANKS
├── TODO
├── apvlv.1
├── apvlvrc.example
├── cmake/
│   ├── CompileFlags.cmake
│   ├── Dependencies.cmake
│   └── Options.cmake
├── scripts/
│   ├── build.ps1
│   └── build.sh
├── share/
│   ├── applications/
│   │   └── apvlv.desktop
│   └── scripts/
│       └── internal.js
├── src/
│   ├── ApvlvCmds.cc
│   ├── ApvlvCmds.h
│   ├── ApvlvCompletion.cc
│   ├── ApvlvCompletion.h
│   ├── ApvlvDirectory.cc
│   ├── ApvlvDirectory.h
│   ├── ApvlvDired.cc
│   ├── ApvlvDired.h
│   ├── ApvlvEditor.cc
│   ├── ApvlvEditor.h
│   ├── ApvlvFile.cc
│   ├── ApvlvFile.h
│   ├── ApvlvFileIndex.cc
│   ├── ApvlvFileIndex.h
│   ├── ApvlvFileWidget.cc
│   ├── ApvlvFileWidget.h
│   ├── ApvlvFrame.cc
│   ├── ApvlvFrame.h
│   ├── ApvlvImageWidget.cc
│   ├── ApvlvImageWidget.h
│   ├── ApvlvInfo.cc
│   ├── ApvlvInfo.h
│   ├── ApvlvLab.cc
│   ├── ApvlvLab.h
│   ├── ApvlvLog.cc
│   ├── ApvlvLog.h
│   ├── ApvlvMarkdown.cc
│   ├── ApvlvMarkdown.h
│   ├── ApvlvNote.cc
│   ├── ApvlvNote.h
│   ├── ApvlvNoteWidget.cc
│   ├── ApvlvNoteWidget.h
│   ├── ApvlvOCR.cc
│   ├── ApvlvOCR.h
│   ├── ApvlvParams.cc
│   ├── ApvlvParams.h
│   ├── ApvlvQueue.cc
│   ├── ApvlvQueue.h
│   ├── ApvlvSearch.cc
│   ├── ApvlvSearch.h
│   ├── ApvlvSearchDialog.cc
│   ├── ApvlvSearchDialog.h
│   ├── ApvlvUtil.cc
│   ├── ApvlvUtil.h
│   ├── ApvlvView.cc
│   ├── ApvlvView.h
│   ├── ApvlvWeb.cc
│   ├── ApvlvWeb.h
│   ├── ApvlvWebViewWidget.cc
│   ├── ApvlvWebViewWidget.h
│   ├── ApvlvWidget.cc
│   ├── ApvlvWidget.h
│   ├── ApvlvWindow.cc
│   ├── ApvlvWindow.h
│   ├── CMakeLists.txt
│   ├── Engines.cmake
│   ├── Sources.cmake
│   ├── WindowsEngines.cmake
│   ├── config.h.in
│   ├── file/
│   │   ├── ApvlvAxOffice.cc
│   │   ├── ApvlvAxOffice.h
│   │   ├── ApvlvDjvu.cc
│   │   ├── ApvlvDjvu.h
│   │   ├── ApvlvEpub.cc
│   │   ├── ApvlvEpub.h
│   │   ├── ApvlvFb2.cc
│   │   ├── ApvlvFb2.h
│   │   ├── ApvlvHtm.cc
│   │   ├── ApvlvHtm.h
│   │   ├── ApvlvImage.cc
│   │   ├── ApvlvImage.h
│   │   ├── ApvlvLibreOffice.cc
│   │   ├── ApvlvLibreOffice.h
│   │   ├── ApvlvMuPdf.cc
│   │   ├── ApvlvMuPdf.h
│   │   ├── ApvlvPopplerPdf.cc
│   │   ├── ApvlvPopplerPdf.h
│   │   ├── ApvlvQtPdf.cc
│   │   ├── ApvlvQtPdf.h
│   │   ├── ApvlvTxt.cc
│   │   └── ApvlvTxt.h
│   ├── main.cc
│   └── testNote.cc
├── vcpkg-manifests/
│   └── system-qt/
│       └── vcpkg.json
├── vcpkg.json
└── zh_CN.ts
Download .txt
SYMBOL INDEX (221 symbols across 79 files)

FILE: share/scripts/internal.js
  function getSelectionOffset (line 2) | function getSelectionOffset(index) {
  function underlineTextNode (line 25) | function underlineTextNode(textNode, startOffset, endOffset, tooltipText) {
  function traverseTextNodes (line 60) | function traverseTextNodes(root, callback) {
  function underlineByOffset (line 69) | function underlineByOffset(startOffset, endOffset, tooltip_msg) {
  function scrollByTimes (line 115) | function scrollByTimes(times, h, v) {
  function scrollToAnchor (line 119) | function scrollToAnchor(anchor) {
  function scrollToPosition (line 128) | function scrollToPosition(xrate, yrate) {
  function dispatchKeydownEvent (line 134) | function dispatchKeydownEvent(keyCode) {

FILE: src/ApvlvCmds.cc
  type apvlv (line 35) | namespace apvlv
    function keyToControlChar (line 59) | static int
    function isModifierKey (line 77) | constexpr static bool
    function CmdType (line 109) | CmdType
    function Command (line 255) | Command *
    function CommandKeyList (line 261) | CommandKeyList *
    function CommandKeyList (line 267) | CommandKeyList
    function Command (line 273) | Command *
    function Command (line 415) | Command *
    function CmdReturn (line 432) | CmdReturn
    function Command (line 460) | Command *

FILE: src/ApvlvCmds.h
  type class (line 38) | enum class
  type class (line 45) | enum class
  type class (line 53) | enum class
  type class (line 61) | enum class
  function ctrlValue (line 70) | constexpr int
  function class (line 83) | class Command final

FILE: src/ApvlvCompletion.cc
  type apvlv (line 34) | namespace apvlv
    function string (line 38) | string

FILE: src/ApvlvCompletion.h
  function namespace (line 32) | namespace apvlv

FILE: src/ApvlvDirectory.cc
  type apvlv (line 44) | namespace apvlv
    function FileIndex (line 329) | FileIndex *
    function FileIndex (line 340) | FileIndex *
    function QTreeWidgetItem (line 413) | QTreeWidgetItem *
    function FileIndex (line 1049) | FileIndex *
    function FileIndex (line 1057) | FileIndex *

FILE: src/ApvlvDirectory.h
  function class (line 48) | class ContentTree : public QTreeWidget
  type class (line 65) | enum class
  type class (line 76) | enum class
  function setFrame (line 102) | void
  function focusFilter (line 108) | void
  function setActive (line 124) | void
  function isActive (line 137) | bool
  function ApvlvFrame (line 159) | ApvlvFrame *mFrame{ nullptr };

FILE: src/ApvlvDired.cc
  type apvlv (line 34) | namespace apvlv

FILE: src/ApvlvDired.h
  function namespace (line 39) | namespace apvlv

FILE: src/ApvlvEditor.cc
  type apvlv (line 30) | namespace apvlv

FILE: src/ApvlvEditor.h
  function namespace (line 33) | namespace apvlv

FILE: src/ApvlvFile.cc
  type apvlv (line 43) | namespace apvlv
    function ostream (line 84) | ostream &
    function string (line 257) | string

FILE: src/ApvlvFile.h
  function DISPLAY_TYPE (line 43) | enum class DISPLAY_TYPE

FILE: src/ApvlvFileIndex.cc
  type apvlv (line 37) | namespace apvlv

FILE: src/ApvlvFileIndex.h
  type class (line 39) | enum class
  function FileIndexType (line 66) | FileIndexType type{ FileIndexType::PAGE };

FILE: src/ApvlvFileWidget.cc
  type apvlv (line 31) | namespace apvlv

FILE: src/ApvlvFileWidget.h
  function virtual (line 56) | [[nodiscard]] virtual File *
  function virtual (line 62) | virtual void
  function virtual (line 68) | virtual int
  function virtual (line 74) | virtual std::string
  function virtual (line 80) | virtual double
  function virtual (line 86) | virtual void
  function virtual (line 93) | virtual void
  function virtual (line 114) | virtual void
  function virtual (line 120) | virtual void
  function virtual (line 126) | virtual int
  function setAnchor (line 132) | void
  function virtual (line 138) | virtual void
  function virtual (line 150) | virtual void
  function virtual (line 162) | virtual void
  function virtual (line 168) | virtual const WordListRectangle &
  function virtual (line 174) | virtual void
  function virtual (line 180) | virtual const std::vector<Rectangle> &
  function mScrollValue (line 193) | double mScrollValue{ 0.0f };

FILE: src/ApvlvFrame.cc
  type apvlv (line 44) | namespace apvlv
    function ApvlvFrame (line 285) | ApvlvFrame *
    function CmdReturn (line 428) | CmdReturn
    function CmdReturn (line 572) | CmdReturn
    function ApvlvFrame (line 777) | ApvlvFrame *

FILE: src/ApvlvFrame.h
  function namespace (line 47) | namespace apvlv

FILE: src/ApvlvImageWidget.cc
  type apvlv (line 39) | namespace apvlv
    function string (line 169) | string
    function imageSelect (line 400) | bool
    function imageUnderline (line 424) | bool
    function imageSelectSearch (line 448) | bool

FILE: src/ApvlvImageWidget.h
  function class (line 47) | class TextContainer : public Editor
  function class (line 57) | class ImageContainer : public QLabel

FILE: src/ApvlvInfo.cc
  type apvlv (line 37) | namespace apvlv

FILE: src/ApvlvInfo.h
  function namespace (line 34) | namespace apvlv

FILE: src/ApvlvLab.cc
  type apvlv (line 31) | namespace apvlv

FILE: src/ApvlvLab.h
  function namespace (line 33) | namespace apvlv

FILE: src/ApvlvLog.cc
  type apvlv (line 35) | namespace apvlv
    function ApvlvLog (line 88) | ApvlvLog *

FILE: src/ApvlvLog.h
  function namespace (line 35) | namespace apvlv

FILE: src/ApvlvMarkdown.cc
  type apvlv (line 36) | namespace apvlv
    function MarkdownNode (line 66) | MarkdownNode &
    function MarkdownNode (line 83) | MarkdownNode &
    function MarkdownNode (line 102) | MarkdownNode *
    function MarkdownNode (line 265) | MarkdownNode *
    function cmark_node (line 277) | cmark_node *
    function Markdown (line 334) | Markdown &
    function Markdown (line 345) | Markdown &
    function MarkdownNode (line 421) | MarkdownNode *

FILE: src/ApvlvMarkdown.h
  function namespace (line 36) | namespace apvlv

FILE: src/ApvlvNote.cc
  type apvlv (line 38) | namespace apvlv
    type tm (line 111) | struct tm

FILE: src/ApvlvNote.h
  type ApvlvPoint (line 44) | struct ApvlvPoint
  type Location (line 45) | struct Location
  function x (line 48) | double x{ 0 }
  function y (line 49) | double y{ 0 }
  function offset (line 50) | int offset{ -1 };
  type Comment (line 79) | struct Comment
  function class (line 97) | class Note

FILE: src/ApvlvNoteWidget.cc
  type apvlv (line 36) | namespace apvlv
    function QString (line 40) | QString

FILE: src/ApvlvNoteWidget.h
  function namespace (line 38) | namespace apvlv

FILE: src/ApvlvOCR.cc
  type apvlv (line 31) | namespace apvlv

FILE: src/ApvlvOCR.h
  function namespace (line 37) | namespace apvlv

FILE: src/ApvlvParams.cc
  type apvlv (line 37) | namespace apvlv
    function string (line 166) | string
    function string (line 192) | string

FILE: src/ApvlvParams.h
  function namespace (line 35) | namespace apvlv

FILE: src/ApvlvQueue.cc
  type apvlv (line 33) | namespace apvlv

FILE: src/ApvlvQueue.h
  function namespace (line 35) | namespace apvlv
  function class (line 119) | class Token final

FILE: src/ApvlvSearch.cc
  type apvlv (line 38) | namespace apvlv
    function grep (line 204) | vector<pair<size_t, size_t>>

FILE: src/ApvlvSearch.h
  function namespace (line 38) | namespace apvlv

FILE: src/ApvlvSearchDialog.cc
  type apvlv (line 34) | namespace apvlv

FILE: src/ApvlvSearchDialog.h
  function namespace (line 45) | namespace apvlv

FILE: src/ApvlvUtil.cc
  type apvlv (line 39) | namespace apvlv
    function getXdgOrHomeIni (line 58) | static void
    function getXdgOrCachePath (line 86) | static void
    function getRuntimePaths (line 108) | void
    function xmlContentGetElement (line 138) | optional<unique_ptr<QXmlStreamReader>>
    function string (line 175) | string
    function string (line 188) | string
    function string (line 200) | string
    function imageArgb32ToRgb32 (line 212) | void
    function string (line 230) | string
    function qint64 (line 245) | qint64
    function qint64 (line 278) | qint64

FILE: src/ApvlvUtil.h
  function namespace (line 37) | namespace apvlv

FILE: src/ApvlvView.cc
  type apvlv (line 41) | namespace apvlv
    function isAltEscape (line 46) | static bool
    function ApvlvWindow (line 184) | ApvlvWindow *
    function ApvlvFrame (line 793) | ApvlvFrame *
    function CmdReturn (line 807) | CmdReturn
    function CmdReturn (line 869) | CmdReturn
    type keyNode (line 1349) | struct keyNode

FILE: src/ApvlvView.h
  function namespace (line 47) | namespace apvlv

FILE: src/ApvlvWeb.cc
  type apvlv (line 32) | namespace apvlv

FILE: src/ApvlvWeb.h
  function namespace (line 35) | namespace apvlv

FILE: src/ApvlvWebViewWidget.cc
  type apvlv (line 41) | namespace apvlv

FILE: src/ApvlvWebViewWidget.h
  function class (line 48) | class ApvlvSchemeHandler : public QWebEngineUrlSchemeHandler
  function class (line 73) | class WebView : public QWebEngineView
  function QWidget (line 117) | [[nodiscard]] QWidget *
  function setFile (line 123) | void
  function setInternalScroll (line 146) | void
  function internalScroll (line 151) | bool
  function mIsInternalScroll (line 159) | bool mIsInternalScroll{ false };

FILE: src/ApvlvWidget.cc
  type apvlv (line 32) | namespace apvlv

FILE: src/ApvlvWidget.h
  function namespace (line 33) | namespace apvlv

FILE: src/ApvlvWindow.cc
  type apvlv (line 36) | namespace apvlv
    function CmdReturn (line 56) | CmdReturn
    function ApvlvWindow (line 90) | ApvlvWindow *
    function ApvlvWindow (line 118) | ApvlvWindow *
    function ApvlvWindow (line 156) | ApvlvWindow *
    function ApvlvWindow (line 186) | ApvlvWindow *
    function ApvlvWindow (line 216) | ApvlvWindow *
    function ApvlvWindow (line 240) | ApvlvWindow *
    function ApvlvWindow (line 347) | ApvlvWindow *
    function ApvlvFrame (line 452) | ApvlvFrame *
    function ApvlvFrame (line 464) | ApvlvFrame *
    function ApvlvWindow (line 472) | ApvlvWindow *
    function ApvlvWindow (line 480) | ApvlvWindow *
    function ApvlvWindow (line 488) | ApvlvWindow *
    function ApvlvWindow (line 498) | ApvlvWindow *
    function ApvlvWindow (line 507) | ApvlvWindow *

FILE: src/ApvlvWindow.h
  type class (line 50) | enum class
  function WindowType (line 56) | WindowType mType{ WindowType::FRAME };

FILE: src/file/ApvlvAxOffice.cc
  type apvlv (line 38) | namespace apvlv
    function SizeF (line 74) | SizeF
    function SizeF (line 258) | SizeF
    function ExcelWidget (line 294) | ExcelWidget *

FILE: src/file/ApvlvAxOffice.h
  function class (line 37) | class AxOffice
  function sum (line 63) | int sum () override;

FILE: src/file/ApvlvDjvu.cc
  type apvlv (line 32) | namespace apvlv
    function handleDdjvuMessages (line 36) | void
    function SizeF (line 108) | SizeF

FILE: src/file/ApvlvDjvu.h
  function namespace (line 34) | namespace apvlv

FILE: src/file/ApvlvEpub.cc
  type apvlv (line 39) | namespace apvlv
    function string (line 145) | string

FILE: src/file/ApvlvEpub.h
  function namespace (line 38) | namespace apvlv

FILE: src/file/ApvlvFb2.cc
  type apvlv (line 35) | namespace apvlv

FILE: src/file/ApvlvFb2.h
  function namespace (line 34) | namespace apvlv

FILE: src/file/ApvlvHtm.cc
  type apvlv (line 30) | namespace apvlv

FILE: src/file/ApvlvHtm.h
  function namespace (line 34) | namespace apvlv

FILE: src/file/ApvlvImage.cc
  type apvlv (line 29) | namespace apvlv

FILE: src/file/ApvlvImage.h
  function namespace (line 32) | namespace apvlv

FILE: src/file/ApvlvLibreOffice.cc
  type apvlv (line 35) | namespace apvlv

FILE: src/file/ApvlvLibreOffice.h
  function namespace (line 35) | namespace apvlv

FILE: src/file/ApvlvMuPdf.cc
  type apvlv (line 34) | namespace apvlv
    function SizeF (line 67) | SizeF
    function fz_catch (line 243) | fz_catch (mContext)

FILE: src/file/ApvlvMuPdf.h
  function namespace (line 35) | namespace apvlv

FILE: src/file/ApvlvPopplerPdf.cc
  type apvlv (line 36) | namespace apvlv
    function SizeF (line 64) | SizeF

FILE: src/file/ApvlvPopplerPdf.h
  function namespace (line 34) | namespace apvlv

FILE: src/file/ApvlvQtPdf.cc
  type apvlv (line 39) | namespace apvlv
    function PDFWidget (line 82) | PDFWidget *
    function SizeF (line 90) | SizeF

FILE: src/file/ApvlvQtPdf.h
  function namespace (line 36) | namespace apvlv

FILE: src/file/ApvlvTxt.cc
  type apvlv (line 34) | namespace apvlv

FILE: src/file/ApvlvTxt.h
  function namespace (line 32) | namespace apvlv

FILE: src/main.cc
  function registerUrlScheme (line 52) | static void
  function usageExit (line 60) | static void
  function versionExit (line 76) | static void
  function parseCommandLine (line 87) | static list<string>
  function loadTranslator (line 159) | static void
  function main (line 180) | int

FILE: src/testNote.cc
  type apvlv (line 33) | namespace apvlv
  function main (line 40) | int
Condensed preview — 106 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (531K chars).
[
  {
    "path": ".clang-format",
    "chars": 112,
    "preview": "BasedOnStyle: GNU\nColumnLimit: 78\nAllowShortFunctionsOnASingleLine: Empty\nAllowShortLambdasOnASingleLine: Empty\n"
  },
  {
    "path": ".dir-locals.el",
    "chars": 802,
    "preview": "((nil (eval . (setq-local flycheck-clang-include-path\n                          '(\"/usr/include/poppler/glib\"\n          "
  },
  {
    "path": ".github/workflows/cmake-multi-platform.yml",
    "chars": 3938,
    "preview": "# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if y"
  },
  {
    "path": ".gitignore",
    "chars": 117,
    "preview": "/build\n*~\ncscope.files\ncscope.out\n/.idea\n/.vscode\n/.cache\nshare/doc/apvlv/translations/*\nshare/doc/apvlv/Startup.pdf\n"
  },
  {
    "path": "AUTHORS",
    "chars": 24,
    "preview": "Alf <naihe2010@126.com>\n"
  },
  {
    "path": "CMakeLists.txt",
    "chars": 3167,
    "preview": "cmake_minimum_required(VERSION 3.16)\nproject(apvlv VERSION 0.7.0)\n\n# vcpkg integration\nif(DEFINED CMAKE_TOOLCHAIN_FILE)\n"
  },
  {
    "path": "COPYING",
    "chars": 17987,
    "preview": "\t\t    GNU GENERAL PUBLIC LICENSE\n\t\t       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc."
  },
  {
    "path": "NEWS",
    "chars": 6834,
    "preview": "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 mod"
  },
  {
    "path": "README.md",
    "chars": 2064,
    "preview": "# 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"
  },
  {
    "path": "THANKS",
    "chars": 1205,
    "preview": "I would like to give thanks to the following for their support and\ncontributions:\n\n        - Ian Munsie <darkstarsword@g"
  },
  {
    "path": "TODO",
    "chars": 16,
    "preview": ".      Bug fix.\n"
  },
  {
    "path": "apvlv.1",
    "chars": 5133,
    "preview": ".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 behavio"
  },
  {
    "path": "apvlvrc.example",
    "chars": 1183,
    "preview": "\" 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"
  },
  {
    "path": "cmake/CompileFlags.cmake",
    "chars": 561,
    "preview": "# Compiler flags configuration\n\n# C++ standard\nset(CMAKE_CXX_STANDARD 20)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\n# Common "
  },
  {
    "path": "cmake/Dependencies.cmake",
    "chars": 4627,
    "preview": "# Dependency management for apvlv\n\n# Platform-specific dependency handling\nif(WIN32)\n    # Windows - use vcpkg via find_"
  },
  {
    "path": "cmake/Options.cmake",
    "chars": 421,
    "preview": "# Build options for apvlv\noption(APVLV_WITH_MUPDF \"Enable MuPDF PDF engine\" ON)\noption(APVLV_WITH_POPPLER \"Enable Popple"
  },
  {
    "path": "scripts/build.ps1",
    "chars": 7403,
    "preview": "param(\n    [string]$BuildDir = \"\",\n    [string]$BuildType = \"Release\",\n    [string]$VisualStudioVersion = \"\",\n    [strin"
  },
  {
    "path": "scripts/build.sh",
    "chars": 15512,
    "preview": "#!/bin/bash\n# Build script for apvlv using bash\n# Usage: ./build.sh [debug clean test package deps help] [build_path]\n\ns"
  },
  {
    "path": "share/applications/apvlv.desktop",
    "chars": 659,
    "preview": "[Desktop Entry]\nVersion=1.0\nType=Application\nName=apvlv\nComment=A minimalistic document viewer\nComment[de]=Ein minimalis"
  },
  {
    "path": "share/scripts/internal.js",
    "chars": 4637,
    "preview": "// internal.js\nfunction getSelectionOffset(index) {\n    const selection = window.getSelection();\n\n    if (!selection.ran"
  },
  {
    "path": "src/ApvlvCmds.cc",
    "chars": 9463,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvCmds.h",
    "chars": 3410,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvCompletion.cc",
    "chars": 2291,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2008> Alf\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * Th"
  },
  {
    "path": "src/ApvlvCompletion.h",
    "chars": 1488,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2008> Alf\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * Th"
  },
  {
    "path": "src/ApvlvDirectory.cc",
    "chars": 31238,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvDirectory.h",
    "chars": 4765,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvDired.cc",
    "chars": 1364,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvDired.h",
    "chars": 1436,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvEditor.cc",
    "chars": 1660,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvEditor.h",
    "chars": 1289,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvFile.cc",
    "chars": 9550,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2008>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/ApvlvFile.h",
    "chars": 6661,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2008>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/ApvlvFileIndex.cc",
    "chars": 3785,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2008>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/ApvlvFileIndex.h",
    "chars": 2009,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2008>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/ApvlvFileWidget.cc",
    "chars": 4565,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvFileWidget.h",
    "chars": 3692,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvFrame.cc",
    "chars": 29491,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvFrame.h",
    "chars": 5669,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvImageWidget.cc",
    "chars": 12728,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvImageWidget.h",
    "chars": 3814,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvInfo.cc",
    "chars": 4199,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2008>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/ApvlvInfo.h",
    "chars": 1917,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2008>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/ApvlvLab.cc",
    "chars": 3942,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/ApvlvLab.h",
    "chars": 1327,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/ApvlvLog.cc",
    "chars": 2878,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2024> Alf\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * Th"
  },
  {
    "path": "src/ApvlvLog.h",
    "chars": 1626,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2024> Alf\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * Th"
  },
  {
    "path": "src/ApvlvMarkdown.cc",
    "chars": 9549,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvMarkdown.h",
    "chars": 3570,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvNote.cc",
    "chars": 11725,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvNote.h",
    "chars": 5548,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvNoteWidget.cc",
    "chars": 2401,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvNoteWidget.h",
    "chars": 1512,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvOCR.cc",
    "chars": 1884,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvOCR.h",
    "chars": 1419,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvParams.cc",
    "chars": 5527,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvParams.h",
    "chars": 2097,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvQueue.cc",
    "chars": 1659,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvQueue.h",
    "chars": 2912,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvSearch.cc",
    "chars": 6367,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvSearch.h",
    "chars": 2556,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvSearchDialog.cc",
    "chars": 5953,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvSearchDialog.h",
    "chars": 2203,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvUtil.cc",
    "chars": 7344,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvUtil.h",
    "chars": 2626,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvView.cc",
    "chars": 30424,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvView.h",
    "chars": 4957,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvWeb.cc",
    "chars": 1409,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/ApvlvWeb.h",
    "chars": 1632,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/ApvlvWebViewWidget.cc",
    "chars": 10819,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvWebViewWidget.h",
    "chars": 3892,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvWidget.cc",
    "chars": 1190,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvWidget.h",
    "chars": 1168,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvWindow.cc",
    "chars": 11598,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/ApvlvWindow.h",
    "chars": 2639,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/CMakeLists.txt",
    "chars": 3259,
    "preview": "# Simplified CMakeLists.txt for apvlv src directory\n\n# Include source and engine configurations\ninclude(${CMAKE_CURRENT_"
  },
  {
    "path": "src/Engines.cmake",
    "chars": 3374,
    "preview": "# Document engine configuration\n\n# Initialize engine-specific variables\nset(APVLV_ENGINE_HEADERS \"\")\nset(APVLV_ENGINE_SO"
  },
  {
    "path": "src/Sources.cmake",
    "chars": 1347,
    "preview": "# Source files configuration\n\n# Core headers\nset(HEADERS\n    ApvlvCmds.h\n    ApvlvFrame.h\n    ApvlvFile.h\n    ApvlvFileI"
  },
  {
    "path": "src/WindowsEngines.cmake",
    "chars": 759,
    "preview": "# Windows-specific document engines\n\nif(WIN32 AND APVLV_WITH_OFFICE)\n    message(STATUS \"Enable MSOffice as office file "
  },
  {
    "path": "src/config.h.in",
    "chars": 473,
    "preview": "#ifndef APVLV_CONFIG_H\n#define APVLV_CONFIG_H\n\n// Package information\n#define PACKAGE_NAME \"@PACKAGE_NAME@\"\n#define PACK"
  },
  {
    "path": "src/file/ApvlvAxOffice.cc",
    "chars": 9463,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvAxOffice.h",
    "chars": 3213,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvDjvu.cc",
    "chars": 4580,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvDjvu.h",
    "chars": 1467,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvEpub.cc",
    "chars": 9828,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvEpub.h",
    "chars": 2190,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvFb2.cc",
    "chars": 10006,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvFb2.h",
    "chars": 2251,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvHtm.cc",
    "chars": 1415,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvHtm.h",
    "chars": 1346,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvImage.cc",
    "chars": 1110,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvImage.h",
    "chars": 1219,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvLibreOffice.cc",
    "chars": 3634,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvLibreOffice.h",
    "chars": 1761,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvMuPdf.cc",
    "chars": 8369,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvMuPdf.h",
    "chars": 2244,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvPopplerPdf.cc",
    "chars": 4139,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvPopplerPdf.h",
    "chars": 1825,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvQtPdf.cc",
    "chars": 7844,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvQtPdf.h",
    "chars": 2819,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvTxt.cc",
    "chars": 1675,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/file/ApvlvTxt.h",
    "chars": 1507,
    "preview": "/*\n * This file is part of the apvlv package\n * Copyright (C) <2010>  <Alf>\n *\n * Contact: Alf <naihe2010@126.com>\n *\n *"
  },
  {
    "path": "src/main.cc",
    "chars": 6321,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "src/testNote.cc",
    "chars": 2330,
    "preview": "/*\n * This file is part of the apvlv package\n *\n * Copyright (C) 2008 Alf.\n *\n * Contact: Alf <naihe2010@126.com>\n *\n * "
  },
  {
    "path": "vcpkg-manifests/system-qt/vcpkg.json",
    "chars": 336,
    "preview": "{\n  \"name\": \"apvlv-system-qt\",\n  \"version\": \"0.7.0\",\n  \"description\": \"apvlv deps manifest when using system Qt (avoid b"
  },
  {
    "path": "vcpkg.json",
    "chars": 344,
    "preview": "{\n    \"name\": \"apvlv\",\n    \"version\": \"0.7.0\",\n    \"description\": \"Alf's PDF/DJVU/EPUB Viewer like Vim\",\n    \"homepage\":"
  },
  {
    "path": "zh_CN.ts",
    "chars": 18931,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"zh_CN\">\n<context>\n    <name>ApvlvSearch"
  }
]

About this extraction

This page contains the full source code of the naihe2010/apvlv GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 106 files (488.9 KB), approximately 134.8k tokens, and a symbol index with 221 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!