diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..b7762b4a1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,1131 @@ +name: Build + +on: + push: + branches: + - master + - develop + pull_request: + branches: + - master + - develop + +env: + IS_ORIGINAL_REPO: ${{ github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') }} + IS_FORK: ${{ github.repository != 'YACReader/yacreader' || (github.ref != 'refs/heads/master' && github.ref != 'refs/heads/develop') }} + +jobs: + # Build number generation + initialization: + name: Initialization + runs-on: windows-latest + outputs: + build_number: ${{ steps.build_number.outputs.build_number }} + steps: + - name: Generate Build Number + id: build_number + shell: pwsh + run: | + $date = (Get-Date).ToString("yyMMdd") + $revision = "${{ github.run_number }}" + $buildNumber = "$date$revision" + echo "build_number=$buildNumber" >> $env:GITHUB_OUTPUT + echo "Build Number: $buildNumber" + + # Code format validation + code-format-validation: + name: Code Format Validation + runs-on: macos-latest + needs: initialization + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: brew install clang-format + + - name: Run clang-format + run: | + find . \( -name '*.h' -or -name '*.cpp' -or -name '*.c' -or -name '*.mm' -or -name '*.m' \) -print0 | xargs -0 clang-format -style=file -i + git diff ${{ github.sha }} + if [ "$(git diff ${{ github.sha }})" != "" ]; then exit 1; fi + + # Linux build (Qt5 with unarr) + linux: + name: Linux (Qt5) + runs-on: ubuntu-22.04 + needs: [initialization, code-format-validation] + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y qtchooser qtbase5-dev-tools qt5-qmake \ + qtbase5-dev qtmultimedia5-dev libpoppler-qt5-dev qttools5-dev-tools \ + libqt5opengl5-dev libunarr-dev qtdeclarative5-dev libqt5svg5-dev qtquickcontrols2-5-dev + + - name: Create tarball + run: | + VERSION="$(cat common/yacreader_global.h | grep '#define VERSION "' | tr -d '#define VERSION' | tr -d '"' )" + ./mktarball.sh $VERSION + mkdir tarball + cp yacreader-*-src.tar.xz* tarball/ + + - name: Build + run: | + export DEFINES_VAR=DEFINES+=\"BUILD_NUMBER=\\\\\\\"${{ needs.initialization.outputs.build_number }}\\\\\\\"\" + qmake CONFIG+="unarr" $DEFINES_VAR + make + + - name: Run tests + run: make check TESTARGS="-maxwarnings 100000" + + - name: Upload tarball + uses: actions/upload-artifact@v4 + with: + name: src-${{ needs.initialization.outputs.build_number }}-tarball + path: tarball/* + + # Linux Qt6 build + linux-qt6: + name: Linux (Qt6) + runs-on: ubuntu-24.04 + needs: [initialization, code-format-validation] + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y qtchooser qt6-tools-dev qt6-base-dev-tools qmake6 qmake6-bin \ + qt6-base-dev qt6-multimedia-dev qt6-tools-dev-tools libgl-dev qt6-l10n-tools \ + libqt6opengl6-dev libunarr-dev qt6-declarative-dev libqt6svg6-dev libqt6core5compat6-dev libpoppler-qt6-dev + + - name: Build + run: | + qtchooser -list-versions + export DEFINES_VAR=DEFINES+=\"BUILD_NUMBER=\\\\\\\"${{ needs.initialization.outputs.build_number }}\\\\\\\"\" + qmake6 CONFIG+="unarr" $DEFINES_VAR + qmake6 -v + make + + - name: Run tests + run: make check TESTARGS="-maxwarnings 100000" + + # Linux Qt6 with 7zip + linux-qt6-7zip: + name: Linux (Qt6 + 7zip) + runs-on: ubuntu-24.04 + needs: [initialization, code-format-validation] + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y qtchooser qt6-tools-dev qt6-base-dev-tools qmake6 qmake6-bin \ + qt6-base-dev qt6-multimedia-dev qt6-tools-dev-tools libgl-dev qt6-l10n-tools \ + libqt6opengl6-dev libunarr-dev qt6-declarative-dev libqt6svg6-dev libqt6core5compat6-dev libpoppler-qt6-dev + mkdir -p ${{ github.workspace }}/compressed_archive + wget "https://github.com/YACReader/yacreader-7z-deps/blob/main/7z2301-src.7z?raw=true" -O ${{ github.workspace }}/compressed_archive/7z2301-src.7z + 7z x ${{ github.workspace }}/compressed_archive/7z2301-src.7z -o${{ github.workspace }}/compressed_archive/lib7zip + + - name: Build + run: | + qtchooser -list-versions + export DEFINES_VAR=DEFINES+=\"BUILD_NUMBER=\\\\\\\"${{ needs.initialization.outputs.build_number }}\\\\\\\"\" + qmake6 CONFIG+="7zip" $DEFINES_VAR + qmake6 -v + make + + - name: Run tests + run: make check TESTARGS="-maxwarnings 100000" + + # macOS Qt6 Universal build + macos-qt6-universal: + name: macOS (Qt6 Universal) + runs-on: macos-15 + needs: [initialization, code-format-validation] + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Install dependencies + run: | + pip3 install --break-system-packages aqtinstall + python3 -m aqt install-qt mac desktop 6.9.3 -m qt5compat qtmultimedia qtimageformats qtshadertools + echo "${{ github.workspace }}/6.9.3/macos/bin" >> $GITHUB_PATH + npm install -g appdmg + mkdir -p ${{ github.workspace }}/compressed_archive + wget "https://github.com/YACReader/yacreader-7z-deps/blob/main/7z2301-src.7z?raw=true" -O ${{ github.workspace }}/compressed_archive/7z2301-src.7z + 7z x ${{ github.workspace }}/compressed_archive/7z2301-src.7z -o${{ github.workspace }}/compressed_archive/lib7zip + + - name: Import Code Signing Certificate + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: apple-actions/import-codesign-certs@v2 + with: + p12-file-base64: ${{ secrets.MACOS_CERTIFICATE_P12_BASE64 }} + p12-password: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} + + - name: Build + env: + MACOSX_DEPLOYMENT_TARGET: "11" + run: | + VERSION="$(cat common/yacreader_global.h | grep '#define VERSION "' | tr -d '#define VERSION' | tr -d '"' )" + SKIP_CODESIGN="${{ env.IS_FORK }}" + SKIP_CODESIGN=$(echo "$SKIP_CODESIGN" | tr '[:upper:]' '[:lower:]') + ./compileOSX.sh $VERSION ${{ needs.initialization.outputs.build_number }} $SKIP_CODESIGN Qt6 universal + + - name: Build and run tests + run: | + cd tests + qmake + make check TESTARGS="-maxwarnings 100000" + + - name: Notarize + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + run: | + xcrun notarytool submit *.dmg --apple-id "${{ secrets.MACOS_APPLE_ID }}" --team-id "${{ secrets.MACOS_TEAM_ID }}" --password "${{ secrets.MACOS_APP_PASSWORD }}" --wait + xcrun stapler staple *.dmg + + - name: Upload DMG + uses: actions/upload-artifact@v4 + with: + name: macos-qt6-universal-${{ needs.initialization.outputs.build_number }}-dmg + path: "*.dmg" + + # macOS Qt5 build + macos: + name: macOS (Qt5) + runs-on: macos-15-intel + needs: [initialization, code-format-validation] + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Install dependencies + run: | + brew install qt@5 + brew link qt@5 --force + npm install -g appdmg + mkdir -p ${{ github.workspace }}/compressed_archive + wget "https://github.com/YACReader/yacreader-7z-deps/blob/main/7z2301-src.7z?raw=true" -O ${{ github.workspace }}/compressed_archive/7z2301-src.7z + 7z x ${{ github.workspace }}/compressed_archive/7z2301-src.7z -o${{ github.workspace }}/compressed_archive/lib7zip + + - name: Import Code Signing Certificate + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: apple-actions/import-codesign-certs@v2 + with: + p12-file-base64: ${{ secrets.MACOS_CERTIFICATE_P12_BASE64 }} + p12-password: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} + + - name: Build + env: + MACOSX_DEPLOYMENT_TARGET: "10.13" + run: | + VERSION="$(cat common/yacreader_global.h | grep '#define VERSION "' | tr -d '#define VERSION' | tr -d '"' )" + SKIP_CODESIGN="${{ env.IS_FORK }}" + SKIP_CODESIGN=$(echo "$SKIP_CODESIGN" | tr '[:upper:]' '[:lower:]') + ./compileOSX.sh $VERSION ${{ needs.initialization.outputs.build_number }} $SKIP_CODESIGN Qt5 x86_64 + + - name: Build and run tests + run: | + cd tests + qmake + make check TESTARGS="-maxwarnings 100000" + + - name: Notarize + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + run: | + xcrun notarytool submit *.dmg --apple-id "${{ secrets.MACOS_APPLE_ID }}" --team-id "${{ secrets.MACOS_TEAM_ID }}" --password "${{ secrets.MACOS_APP_PASSWORD }}" --wait + xcrun stapler staple *.dmg + + - name: Upload DMG + uses: actions/upload-artifact@v4 + with: + name: macos-${{ needs.initialization.outputs.build_number }}-dmg + path: "*.dmg" + + # Windows x64 Qt5 build + windows-x64: + name: Windows x64 (Qt5) + runs-on: windows-2022 + needs: [initialization, code-format-validation] + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + architecture: 'x64' + + - name: Install dependencies + shell: cmd + run: | + pip install -U pip + pip install aqtinstall + mkdir C:\Qt + python -m aqt install-qt windows desktop 5.15.2 win64_msvc2019_64 -O c:\Qt + dir C:\Qt\5.15.2\msvc2019_64\bin + choco install -y wget + choco install innosetup + mkdir %GITHUB_WORKSPACE%\compressed_archive + wget "https://github.com/YACReader/yacreader-7z-deps/blob/main/7z2301-src.7z?raw=true" -O %GITHUB_WORKSPACE%\compressed_archive\7z2301-src.7z + 7z x %GITHUB_WORKSPACE%\compressed_archive\7z2301-src.7z -o%GITHUB_WORKSPACE%\compressed_archive\lib7zip + wget "https://aka.ms/vs/17/release/vc_redist.x64.exe" -O %GITHUB_WORKSPACE%\vc_redist.x64.exe + + - name: Build + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + set PATH=C:\Qt\5.15.2\msvc2019_64\bin;%PATH% + set DEFINES_VAR=DEFINES+="BUILD_NUMBER=\\\\\\\"${{ needs.initialization.outputs.build_number }}\\\\\\\"" + qmake CONFIG+="7zip" %DEFINES_VAR% + nmake + + - name: Run tests + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + set PATH=C:\Qt\5.15.2\msvc2019_64\bin;%PATH% + nmake check TESTARGS="-maxwarnings 100000" + + - name: Upload executables for signing + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: actions/upload-artifact@v4 + id: upload_executables + with: + name: windows-x64-executables-unsigned-${{ needs.initialization.outputs.build_number }} + path: | + release64/YACReader.exe + release64/YACReaderLibrary.exe + release64/YACReaderLibraryServer.exe + + - name: Sign executables with SignPath + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: signpath/github-action-submit-signing-request@v1 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }} + project-slug: 'yacreader' + signing-policy-slug: 'release-signing' + artifact-configuration-slug: 'zipped-files' + github-artifact-id: ${{ steps.upload_executables.outputs.artifact-id }} + wait-for-completion: true + wait-for-completion-timeout-in-seconds: "3600" + output-artifact-directory: release64/signed + + - name: Replace with signed executables + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + shell: pwsh + run: | + Write-Host "=== Replacing executables with signed versions ===" + Get-ChildItem -Path "release64/signed" -Filter "*.exe" | ForEach-Object { + $destPath = "release64/$($_.Name)" + Write-Host "Moving signed: $($_.Name) -> $destPath" + Move-Item -Path $_.FullName -Destination $destPath -Force + Write-Host " Moved successfully" + } + Remove-Item -Path "release64/signed" -Recurse -Force -ErrorAction SilentlyContinue + Write-Host "Signed executables are ready for installer creation" + + - name: Create installer + shell: cmd + working-directory: ci/win + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + set PATH=C:\Qt\5.15.2\msvc2019_64\bin;%PATH% + .\create_installer.cmd x64 7z ${{ needs.initialization.outputs.build_number }} qt5 + + - name: Verify installer was created + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + shell: pwsh + run: | + if (-not (Test-Path "ci/win/Output/YACReader*.exe")) { + throw "Installer file was not created" + } + Get-ChildItem "ci/win/Output/YACReader*.exe" + + - name: Upload unsigned installer + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: actions/upload-artifact@v4 + id: upload_unsigned + with: + name: windows-x64-unsigned-${{ needs.initialization.outputs.build_number }} + path: ci/win/Output/YACReader*.exe + + - name: Submit to SignPath + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: signpath/github-action-submit-signing-request@v1 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }} + project-slug: 'yacreader' + signing-policy-slug: 'release-signing' + artifact-configuration-slug: 'zipped-files' + github-artifact-id: ${{ steps.upload_unsigned.outputs.artifact-id }} + wait-for-completion: true + wait-for-completion-timeout-in-seconds: "3600" + output-artifact-directory: ci/win/Output/signed + + - name: Replace with signed installer + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + shell: pwsh + run: | + Write-Host "=== Files in signed directory before move ===" + Get-ChildItem -Path "ci/win/Output/signed" -Filter "*.exe" | ForEach-Object { Write-Host " $($_.Name) - $($_.Length) bytes" } + + $signedFiles = Get-ChildItem -Path "ci/win/Output/signed" -Filter "*.exe" + foreach ($signedFile in $signedFiles) { + $destPath = "ci/win/Output/$($signedFile.Name)" + Write-Host "Moving signed: $($signedFile.Name) -> $destPath" + Move-Item -Path $signedFile.FullName -Destination $destPath -Force + Write-Host " Moved successfully" + } + + Write-Host "=== Files in Output directory after move ===" + Get-ChildItem -Path "ci/win/Output" -Filter "*.exe" | ForEach-Object { Write-Host " $($_.Name) - $($_.Length) bytes" } + + Remove-Item -Path "ci/win/Output/signed" -Recurse -Force -ErrorAction SilentlyContinue + Write-Host "Cleaned up signed directory" + + - name: Upload installer + uses: actions/upload-artifact@v4 + with: + name: windows-x64-${{ needs.initialization.outputs.build_number }} + path: ci/win/Output/YACReader*.exe + + # Windows x64 Qt6 build + windows-x64-qt6: + name: Windows x64 (Qt6) + runs-on: windows-2022 + needs: [initialization, code-format-validation] + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + architecture: 'x64' + + - name: Install dependencies + shell: cmd + run: | + pip install -U pip + pip install aqtinstall + mkdir C:\Qt + python -m aqt install-qt windows desktop 6.9.3 win64_msvc2022_64 -O c:\Qt -m qt5compat qtmultimedia qtimageformats qtshadertools + dir C:\Qt\6.9.3\msvc2022_64\bin + choco install -y wget + choco install innosetup + mkdir %GITHUB_WORKSPACE%\compressed_archive + wget "https://github.com/YACReader/yacreader-7z-deps/blob/main/7z2301-src.7z?raw=true" -O %GITHUB_WORKSPACE%\compressed_archive\7z2301-src.7z + 7z x %GITHUB_WORKSPACE%\compressed_archive\7z2301-src.7z -o%GITHUB_WORKSPACE%\compressed_archive\lib7zip + wget "https://aka.ms/vs/17/release/vc_redist.x64.exe" -O %GITHUB_WORKSPACE%\vc_redist.x64.exe + + - name: Check MSVC installations + shell: pwsh + run: | + Write-Host "=== Checking for VS 2019 installation ===" + if (Test-Path "C:\Program Files (x86)\Microsoft Visual Studio\2019") { + Get-ChildItem "C:\Program Files (x86)\Microsoft Visual Studio\2019" -Recurse -Depth 2 + } else { + Write-Host "VS 2019 path does not exist" + } + Write-Host "`n=== Checking VS 2022 MSVC Tools ===" + if (Test-Path "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC") { + Get-ChildItem "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC" + } + Write-Host "`n=== Testing vcvars with -vcvars_ver=14.29 ===" + cmd /c '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" -vcvars_ver=14.29 && set' | Select-String -Pattern "MSVC|VCTools" + + - name: Build + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + set PATH=C:\Qt\6.9.3\msvc2022_64\bin;%PATH% + set DEFINES_VAR=DEFINES+="BUILD_NUMBER=\\\\\\\"${{ needs.initialization.outputs.build_number }}\\\\\\\"" + qmake CONFIG+="7zip" %DEFINES_VAR% + nmake + + - name: Run tests + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + set PATH=C:\Qt\6.9.3\msvc2022_64\bin;%PATH% + nmake check TESTARGS="-maxwarnings 100000" + + - name: Upload executables for signing + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: actions/upload-artifact@v4 + id: upload_executables + with: + name: windows-x64-qt6-executables-unsigned-${{ needs.initialization.outputs.build_number }} + path: | + release64/YACReader.exe + release64/YACReaderLibrary.exe + release64/YACReaderLibraryServer.exe + + - name: Sign executables with SignPath + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: signpath/github-action-submit-signing-request@v1 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }} + project-slug: 'yacreader' + signing-policy-slug: 'release-signing' + artifact-configuration-slug: 'zipped-files' + github-artifact-id: ${{ steps.upload_executables.outputs.artifact-id }} + wait-for-completion: true + wait-for-completion-timeout-in-seconds: "3600" + output-artifact-directory: release64/signed + + - name: Replace with signed executables + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + shell: pwsh + run: | + Write-Host "=== Replacing executables with signed versions ===" + Get-ChildItem -Path "release64/signed" -Filter "*.exe" | ForEach-Object { + $destPath = "release64/$($_.Name)" + Write-Host "Moving signed: $($_.Name) -> $destPath" + Move-Item -Path $_.FullName -Destination $destPath -Force + Write-Host " Moved successfully" + } + Remove-Item -Path "release64/signed" -Recurse -Force -ErrorAction SilentlyContinue + Write-Host "Signed executables are ready for installer creation" + + - name: Create installer + shell: cmd + working-directory: ci/win + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + set PATH=C:\Qt\6.9.3\msvc2022_64\bin;%PATH% + .\create_installer.cmd x64 7z ${{ needs.initialization.outputs.build_number }} qt6 + + - name: Verify installer was created + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + shell: pwsh + run: | + if (-not (Test-Path "ci/win/Output/YACReader*.exe")) { + throw "Installer file was not created" + } + Get-ChildItem "ci/win/Output/YACReader*.exe" + + - name: Upload unsigned installer + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: actions/upload-artifact@v4 + id: upload_unsigned + with: + name: windows-x64-qt6-unsigned-${{ needs.initialization.outputs.build_number }} + path: ci/win/Output/YACReader*.exe + + - name: Submit to SignPath + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: signpath/github-action-submit-signing-request@v1 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }} + project-slug: 'yacreader' + signing-policy-slug: 'release-signing' + artifact-configuration-slug: 'zipped-files' + github-artifact-id: ${{ steps.upload_unsigned.outputs.artifact-id }} + wait-for-completion: true + wait-for-completion-timeout-in-seconds: "3600" + output-artifact-directory: ci/win/Output/signed + + - name: Replace with signed installer + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + shell: pwsh + run: | + Write-Host "=== Files in signed directory before move ===" + Get-ChildItem -Path "ci/win/Output/signed" -Filter "*.exe" | ForEach-Object { Write-Host " $($_.Name) - $($_.Length) bytes" } + + $signedFiles = Get-ChildItem -Path "ci/win/Output/signed" -Filter "*.exe" + foreach ($signedFile in $signedFiles) { + $destPath = "ci/win/Output/$($signedFile.Name)" + Write-Host "Moving signed: $($signedFile.Name) -> $destPath" + Move-Item -Path $signedFile.FullName -Destination $destPath -Force + Write-Host " Moved successfully" + } + + Write-Host "=== Files in Output directory after move ===" + Get-ChildItem -Path "ci/win/Output" -Filter "*.exe" | ForEach-Object { Write-Host " $($_.Name) - $($_.Length) bytes" } + + Remove-Item -Path "ci/win/Output/signed" -Recurse -Force -ErrorAction SilentlyContinue + Write-Host "Cleaned up signed directory" + + - name: Upload installer + uses: actions/upload-artifact@v4 + with: + name: windows-x64-qt6-${{ needs.initialization.outputs.build_number }} + path: ci/win/Output/YACReader*.exe + + # Windows ARM64 Qt6 build + windows-arm64-qt6: + name: Windows ARM64 (Qt6) + runs-on: windows-2022 + needs: [initialization, code-format-validation] + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + architecture: 'x64' + + - name: Install dependencies + shell: cmd + run: | + pip install aqtinstall + mkdir C:\Qt + python -m aqt install-qt windows desktop 6.9.3 win64_msvc2022_64 -O c:\Qt -m qt5compat qtmultimedia qtimageformats qtshadertools + python -m aqt install-qt windows desktop 6.9.3 win64_msvc2022_arm64_cross_compiled -O c:\Qt -m qt5compat qtmultimedia qtimageformats qtshadertools + dir C:\Qt\6.9.3\msvc2022_arm64\bin + choco install -y wget + choco install innosetup + mkdir %GITHUB_WORKSPACE%\compressed_archive + wget "https://github.com/YACReader/yacreader-7z-deps/blob/main/7z2301-src.7z?raw=true" -O %GITHUB_WORKSPACE%\compressed_archive\7z2301-src.7z + 7z x %GITHUB_WORKSPACE%\compressed_archive\7z2301-src.7z -o%GITHUB_WORKSPACE%\compressed_archive\lib7zip + wget "https://aka.ms/vs/17/release/vc_redist.arm64.exe" -O %GITHUB_WORKSPACE%\vc_redist.arm64.exe + + - name: Prepare Build + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64_arm64 + set PATH=C:\Qt\6.9.3\msvc2022_arm64\bin;%PATH% + set DEFINES_VAR=DEFINES+="BUILD_NUMBER=\\\\\\\"${{ needs.initialization.outputs.build_number }}\\\\\\\"" + qmake CONFIG+="7zip" %DEFINES_VAR% + + - name: Build + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64_arm64 + set PATH=C:\Qt\6.9.3\msvc2022_arm64\bin;%PATH% + nmake + + - name: Upload executables for signing + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: actions/upload-artifact@v4 + id: upload_executables + with: + name: windows-arm64-qt6-executables-unsigned-${{ needs.initialization.outputs.build_number }} + path: | + release/YACReader.exe + release/YACReaderLibrary.exe + release/YACReaderLibraryServer.exe + + - name: Submit to SignPath + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: signpath/github-action-submit-signing-request@v1 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }} + project-slug: 'yacreader' + signing-policy-slug: 'release-signing' + artifact-configuration-slug: 'zipped-files' + github-artifact-id: ${{ steps.upload_executables.outputs.artifact-id }} + wait-for-completion: true + output-artifact-directory: 'release/signed' + + - name: Replace executables with signed versions + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + shell: pwsh + run: | + Copy-Item "release/signed/YACReader.exe" "release/YACReader.exe" -Force + Copy-Item "release/signed/YACReaderLibrary.exe" "release/YACReaderLibrary.exe" -Force + Copy-Item "release/signed/YACReaderLibraryServer.exe" "release/YACReaderLibraryServer.exe" -Force + Remove-Item -Path "release/signed" -Recurse -Force -ErrorAction SilentlyContinue + Write-Host "Signed executables are ready for installer creation" + + - name: Create installer + shell: cmd + working-directory: ci/win + run: | + set PATH=C:\Qt\6.9.3\msvc2022_64\bin;%PATH% + .\create_installer.cmd arm64 7z ${{ needs.initialization.outputs.build_number }} qt6 + + - name: Verify installer was created + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + shell: pwsh + run: | + if (-not (Test-Path "ci/win/Output/YACReader*.exe")) { + throw "Installer file was not created" + } + Get-ChildItem "ci/win/Output/YACReader*.exe" + + - name: Upload unsigned installer + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: actions/upload-artifact@v4 + id: upload_unsigned + with: + name: windows-arm64-qt6-unsigned-${{ needs.initialization.outputs.build_number }} + path: ci/win/Output/YACReader*.exe + + - name: Submit to SignPath + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: signpath/github-action-submit-signing-request@v1 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }} + project-slug: 'yacreader' + signing-policy-slug: 'release-signing' + artifact-configuration-slug: 'zipped-files' + github-artifact-id: ${{ steps.upload_unsigned.outputs.artifact-id }} + wait-for-completion: true + output-artifact-directory: 'ci/win/Output/signed' + + - name: Replace with signed installer and cleanup + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + shell: pwsh + working-directory: ci/win/Output + run: | + $signedFiles = Get-ChildItem "signed/YACReader*.exe" + foreach ($file in $signedFiles) { + $destName = $file.Name + Copy-Item $file.FullName $destName -Force + Write-Host "Replaced with signed: $destName" + } + Remove-Item -Path "signed" -Recurse -Force -ErrorAction SilentlyContinue + Write-Host "Cleaned up signed directory" + + - name: Upload installer + uses: actions/upload-artifact@v4 + with: + name: windows-arm64-qt6-${{ needs.initialization.outputs.build_number }} + path: ci/win/Output/YACReader*.exe + + # Windows x86 Qt5 build + windows-x86: + name: Windows x86 (Qt5) + runs-on: windows-2022 + needs: [initialization, code-format-validation] + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + architecture: 'x64' + + - name: Install dependencies + shell: cmd + run: | + pip install -U pip + pip install aqtinstall + mkdir C:\Qt + python -m aqt install-qt windows desktop 5.15.2 win32_msvc2019 -O c:\Qt + dir C:\Qt\5.15.2\msvc2019\bin + choco install -y wget + choco install innosetup + mkdir %GITHUB_WORKSPACE%\compressed_archive + wget "https://github.com/YACReader/yacreader-7z-deps/blob/main/7z2301-src.7z?raw=true" -O %GITHUB_WORKSPACE%\compressed_archive\7z2301-src.7z + 7z x %GITHUB_WORKSPACE%\compressed_archive\7z2301-src.7z -o%GITHUB_WORKSPACE%\compressed_archive\lib7zip + wget "https://aka.ms/vs/17/release/vc_redist.x86.exe" -O %GITHUB_WORKSPACE%\vc_redist.x86.exe + + - name: Build + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars32.bat" + set PATH=C:\Qt\5.15.2\msvc2019\bin;%PATH% + set DEFINES_VAR=DEFINES+="BUILD_NUMBER=\\\\\\\"${{ needs.initialization.outputs.build_number }}\\\\\\\"" + qmake CONFIG+="7zip" %DEFINES_VAR% + nmake + + - name: Run tests + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars32.bat" + set PATH=C:\Qt\5.15.2\msvc2019\bin;%PATH% + nmake check TESTARGS="-maxwarnings 100000" + + - name: Upload executables for signing + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: actions/upload-artifact@v4 + id: upload_executables + with: + name: windows-x86-executables-unsigned-${{ needs.initialization.outputs.build_number }} + path: | + release/YACReader.exe + release/YACReaderLibrary.exe + release/YACReaderLibraryServer.exe + + - name: Sign executables with SignPath + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: signpath/github-action-submit-signing-request@v1 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }} + project-slug: 'yacreader' + signing-policy-slug: 'release-signing' + artifact-configuration-slug: 'zipped-files' + github-artifact-id: ${{ steps.upload_executables.outputs.artifact-id }} + wait-for-completion: true + wait-for-completion-timeout-in-seconds: "3600" + output-artifact-directory: release/signed + + - name: Replace with signed executables + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + shell: pwsh + run: | + Write-Host "=== Replacing executables with signed versions ===" + Get-ChildItem -Path "release/signed" -Filter "*.exe" | ForEach-Object { + $destPath = "release/$($_.Name)" + Write-Host "Moving signed: $($_.Name) -> $destPath" + Move-Item -Path $_.FullName -Destination $destPath -Force + Write-Host " Moved successfully" + } + Remove-Item -Path "release/signed" -Recurse -Force -ErrorAction SilentlyContinue + Write-Host "Signed executables are ready for installer creation" + + - name: Create installer + shell: cmd + working-directory: ci/win + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars32.bat" + set PATH=C:\Qt\5.15.2\msvc2019\bin;%PATH% + .\create_installer.cmd x86 7z ${{ needs.initialization.outputs.build_number }} qt5 + + - name: Verify installer was created + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + shell: pwsh + run: | + if (-not (Test-Path "ci/win/Output/YACReader*.exe")) { + throw "Installer file was not created" + } + Get-ChildItem "ci/win/Output/YACReader*.exe" + + - name: Upload unsigned installer + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: actions/upload-artifact@v4 + id: upload_unsigned + with: + name: windows-x86-unsigned-${{ needs.initialization.outputs.build_number }} + path: ci/win/Output/YACReader*.exe + + - name: Submit to SignPath + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: signpath/github-action-submit-signing-request@v1 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }} + project-slug: 'yacreader' + signing-policy-slug: 'release-signing' + artifact-configuration-slug: 'zipped-files' + github-artifact-id: ${{ steps.upload_unsigned.outputs.artifact-id }} + wait-for-completion: true + wait-for-completion-timeout-in-seconds: "3600" + output-artifact-directory: ci/win/Output/signed + + - name: Replace with signed installer + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + shell: pwsh + run: | + Write-Host "=== Files in signed directory before move ===" + Get-ChildItem -Path "ci/win/Output/signed" -Filter "*.exe" | ForEach-Object { Write-Host " $($_.Name) - $($_.Length) bytes" } + + $signedFiles = Get-ChildItem -Path "ci/win/Output/signed" -Filter "*.exe" + foreach ($signedFile in $signedFiles) { + $destPath = "ci/win/Output/$($signedFile.Name)" + Write-Host "Moving signed: $($signedFile.Name) -> $destPath" + Move-Item -Path $signedFile.FullName -Destination $destPath -Force + Write-Host " Moved successfully" + } + + Write-Host "=== Files in Output directory after move ===" + Get-ChildItem -Path "ci/win/Output" -Filter "*.exe" | ForEach-Object { Write-Host " $($_.Name) - $($_.Length) bytes" } + + Remove-Item -Path "ci/win/Output/signed" -Recurse -Force -ErrorAction SilentlyContinue + Write-Host "Cleaned up signed directory" + + - name: Upload installer + uses: actions/upload-artifact@v4 + with: + name: windows-x86-${{ needs.initialization.outputs.build_number }} + path: ci/win/Output/YACReader*.exe + + # Docker amd64 build + docker-amd64: + name: Docker amd64 Image + runs-on: ubuntu-latest + needs: [initialization, code-format-validation] + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build amd64 Image + working-directory: docker + run: | + docker build --no-cache --platform linux/amd64 -f Dockerfile -t yacreader/yacreaderlibraryserver:develop-amd64 . + docker save yacreader/yacreaderlibraryserver:develop-amd64 -o amd64.tar + + - name: Upload Docker Image + uses: actions/upload-artifact@v4 + with: + name: docker-amd64 + path: docker/amd64.tar + + # Docker arm64 build (native) + docker-arm64: + name: Docker arm64 Image + runs-on: ubuntu-24.04-arm + needs: [initialization, code-format-validation] + steps: + - uses: actions/checkout@v4 + + - name: Build arm64 Image (native) + working-directory: docker + run: | + docker build --no-cache -f Dockerfile.aarch64 -t yacreader/yacreaderlibraryserver:develop-arm64 . + docker save yacreader/yacreaderlibraryserver:develop-arm64 -o arm64.tar + + - name: Upload Docker Image + uses: actions/upload-artifact@v4 + with: + name: docker-arm64 + path: docker/arm64.tar + + # Publish dev builds + publish-dev-builds: + name: Publish Dev Builds + if: github.repository == 'YACReader/yacreader' && github.ref == 'refs/heads/develop' + runs-on: ubuntu-24.04 + needs: + - initialization + - linux + - linux-qt6 + - linux-qt6-7zip + - macos + - macos-qt6-universal + - windows-x86 + - windows-x64 + - windows-x64-qt6 + - windows-arm64-qt6 + - docker-amd64 + - docker-arm64 + steps: + - uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: List downloaded artifacts + run: | + echo "=== All artifacts downloaded ===" + ls -lR artifacts/ + echo "" + echo "=== Windows artifacts only ===" + ls -l artifacts/windows-*/ + + - name: Flatten artifacts (exclude unsigned Windows installers) + run: | + mkdir -p staging + # Copy all files except those from unsigned Windows artifact directories + find artifacts -type f ! -path "*/windows-*-unsigned-*/*" -exec cp {} staging/ \; + echo "" + echo "=== Files copied to staging ===" + ls -lh staging/ + echo "" + echo "=== Windows installers in staging ===" + ls -lh staging/YACReader*.exe || echo "No Windows installers found" + + - name: Verify Windows installer signatures + run: | + echo "=== Installing osslsigncode to verify signatures ===" + sudo apt-get update + sudo apt-get install -y osslsigncode + + echo "" + echo "=== Checking signatures on Windows installers ===" + for installer in staging/YACReader*.exe; do + if [ -f "$installer" ]; then + echo "Checking: $(basename $installer)" + echo "File size: $(stat -c%s $installer) bytes" + + # Try to extract signature info + if osslsigncode verify -in "$installer" 2>&1 | grep -q "Signature verification: ok"; then + echo " ✓ SIGNED - Signature verified successfully" + osslsigncode verify -in "$installer" 2>&1 | grep -E "(Signed|Signer|Timestamp)" + else + echo " ✗ UNSIGNED or INVALID - No valid signature found" + osslsigncode verify -in "$installer" 2>&1 | head -20 + fi + echo "" + fi + done + + echo "=== Summary ===" + echo "Total installers in staging: $(ls staging/YACReader*.exe 2>/dev/null | wc -l)" + + - name: Get version + id: version + run: | + VERSION="$(cat common/yacreader_global.h | grep '#define VERSION "' | tr -d '#define VERSION' | tr -d '"' ).${{ needs.initialization.outputs.build_number }}" + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Version: $VERSION" + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Push Docker Images + run: | + for arch in amd64 arm64; do + docker load -i staging/${arch}.tar + docker push yacreader/yacreaderlibraryserver:develop-${arch} + rm staging/${arch}.tar + done + + docker buildx imagetools create \ + -t yacreader/yacreaderlibraryserver:develop \ + yacreader/yacreaderlibraryserver:develop-amd64 \ + yacreader/yacreaderlibraryserver:develop-arm64 + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + repository: YACReader/yacreader-dev-builds + tag_name: ${{ steps.version.outputs.version }} + name: ${{ steps.version.outputs.version }} + target_commitish: 25313e3d4d03fcbe44d3943db23bc03bbd1a5205 + files: staging/* + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} + + # Publish release builds + publish-release: + name: Publish Release + if: github.repository == 'YACReader/yacreader' && github.ref == 'refs/heads/master' + runs-on: ubuntu-24.04 + needs: + - initialization + - linux + - linux-qt6 + - linux-qt6-7zip + - macos + - macos-qt6-universal + - windows-x86 + - windows-x64 + - windows-x64-qt6 + - windows-arm64-qt6 + - docker-amd64 + - docker-arm64 + steps: + - uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: List downloaded artifacts + run: | + echo "=== All artifacts downloaded ===" + ls -lR artifacts/ + echo "" + echo "=== Windows artifacts only ===" + ls -l artifacts/windows-*/ + + - name: Flatten artifacts (exclude unsigned Windows installers) + run: | + mkdir -p staging + # Copy all files except those from unsigned Windows artifact directories + find artifacts -type f ! -path "*/windows-*-unsigned-*/*" -exec cp {} staging/ \; + echo "" + echo "=== Files copied to staging ===" + ls -lh staging/ + echo "" + echo "=== Windows installers in staging ===" + ls -lh staging/YACReader*.exe || echo "No Windows installers found" + + - name: Verify Windows installer signatures + run: | + echo "=== Installing osslsigncode to verify signatures ===" + sudo apt-get update + sudo apt-get install -y osslsigncode + + echo "" + echo "=== Checking signatures on Windows installers ===" + for installer in staging/YACReader*.exe; do + if [ -f "$installer" ]; then + echo "Checking: $(basename $installer)" + echo "File size: $(stat -c%s $installer) bytes" + + # Try to extract signature info + if osslsigncode verify -in "$installer" 2>&1 | grep -q "Signature verification: ok"; then + echo " ✓ SIGNED - Signature verified successfully" + osslsigncode verify -in "$installer" 2>&1 | grep -E "(Signed|Signer|Timestamp)" + else + echo " ✗ UNSIGNED or INVALID - No valid signature found" + osslsigncode verify -in "$installer" 2>&1 | head -20 + fi + echo "" + fi + done + + echo "=== Summary ===" + echo "Total installers in staging: $(ls staging/YACReader*.exe 2>/dev/null | wc -l)" + + - name: Get version + id: version + run: | + VERSION="$(cat common/yacreader_global.h | grep '#define VERSION "' | tr -d '#define VERSION' | tr -d '"' )" + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Version: $VERSION" + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Push Docker Images + run: | + for arch in amd64 arm64; do + docker load -i staging/${arch}.tar + docker tag yacreader/yacreaderlibraryserver:develop-${arch} yacreader/yacreaderlibraryserver:latest-${arch} + docker push yacreader/yacreaderlibraryserver:latest-${arch} + rm staging/${arch}.tar + done + + docker buildx imagetools create \ + -t yacreader/yacreaderlibraryserver:latest \ + yacreader/yacreaderlibraryserver:latest-amd64 \ + yacreader/yacreaderlibraryserver:latest-arm64 + + docker buildx imagetools create \ + -t yacreader/yacreaderlibraryserver:${{ steps.version.outputs.version }} \ + yacreader/yacreaderlibraryserver:latest-amd64 \ + yacreader/yacreaderlibraryserver:latest-arm64 + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ steps.version.outputs.version }} + name: ${{ steps.version.outputs.version }} + files: staging/* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index c2e46186d..397e9743e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ libp7zip YACReader/build YACReaderLibrary/build YACReaderLibraryServer/build +build/ # C++ objects and libs *.slo @@ -21,6 +22,7 @@ YACReaderLibraryServer/build *.dll *.dylib *.pch +*.obj # Qt-es object_script.*.Release diff --git a/CHANGELOG.md b/CHANGELOG.md index b695de3c7..e7917223e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,35 @@ Version counting is based on semantic versioning (Major.Feature.Patch) +## 9.16.0 + +### YACReader +* Don't use scroll animations on macos by default, it where hdpi scroll is most likely to be used. +* New toolbar on macos. +* New mouse modes to turn pages. You can setup the app to use the left/right buttons to turn pages directly or you can click on the left/right part of the screen to turn pages. +* New setting to control whether the time is shown in the 'current page/total' label. + +### YACReaderLibrary +* Improve flexibility of the open comic in third party app setting so more complex commands can be used. e.g. `open -a "/Applications/My Reader.app" "{comic_file_path}"`. +* Fix setting the comic rating in the table view. +* Log libraries validation when the app starts. +* New toolbar on macos. +* New setting in Comic Vine scraper to force exact volume matches. +* Better default search query in the Comic Vine scraper. +* Improved navigation in Comic Vine scraper, including keeping the current query around to make edits and refined searches easier. +* Add support for custom covers for any folder using the context menu. +* The edit cover buttons now support looping through pages, going forward from the last returns to the first, and going backward from the first jumps to the last. +* Add support for custom covers for comics using the edit metadata dialog, you can use a pick file button or drag&drop an image into the cover view in the dialog. +* Covers can be set in bulk for various comics at once. +* New button to reset to the default cover of a comic. +* Support for storing the new image filters from iOS and Android apps. + +### YACReaderLibraryServer +* Log libraries validation when the app starts. + +### All apps +* PDF libraries have been updated for all Windows builds. + ## 9.15.0 ### YACReader diff --git a/README.md b/README.md index 2b43d64ba..8df7d43ef 100644 --- a/README.md +++ b/README.md @@ -75,3 +75,6 @@ YACReader is free but it needs money to keep being alive, so please, if you like ## Hacktoberfest If you are interested in YACReader, please contact me so we can discuss your next steps. + +## Sponsors +Free code signing on Windows provided by [SignPath.io](https://signpath.io/), certificate by [SignPath Foundation](https://signpath.org/) \ No newline at end of file diff --git a/YACReader/YACReader.pro b/YACReader/YACReader.pro index b57683da8..d50940f8f 100644 --- a/YACReader/YACReader.pro +++ b/YACReader/YACReader.pro @@ -80,6 +80,7 @@ HEADERS += ../common/comic.h \ goto_dialog.h \ magnifying_glass.h \ main_window_viewer.h \ + mouse_handler.h \ viewer.h \ goto_flow.h \ options_dialog.h \ @@ -119,6 +120,7 @@ SOURCES += ../common/comic.cpp \ goto_dialog.cpp \ magnifying_glass.cpp \ main_window_viewer.cpp \ + mouse_handler.cpp \ viewer.cpp \ goto_flow.cpp \ options_dialog.cpp \ diff --git a/YACReader/configuration.h b/YACReader/configuration.h index a16941176..2845b1227 100644 --- a/YACReader/configuration.h +++ b/YACReader/configuration.h @@ -1,5 +1,6 @@ #ifndef __CONFIGURATION_H #define __CONFIGURATION_H + #include #include #include @@ -15,6 +16,21 @@ using namespace YACReader; +namespace YACReader { + +enum FitMode { + ToWidth = 0x01, + ToHeight = 0x02, + FullRes = 0x03, + FullPage = 0x04 +}; + +enum MouseMode { + Normal, + LeftRightNavigation, + HotAreas +}; + class Configuration : public QObject { Q_OBJECT @@ -75,6 +91,8 @@ class Configuration : public QObject void setShowToolbars(bool b) { settings->setValue(SHOW_TOOLBARS, b); } bool getShowInformation() { return settings->value(SHOW_INFO, false).toBool(); } void setShowInformation(bool b) { settings->setValue(SHOW_INFO, b); } + bool getShowTimeInInformation() { return settings->value(SHOW_TIME_IN_INFO, true).toBool(); } + void setShowTimeInInformation(bool b) { settings->setValue(SHOW_TIME_IN_INFO, b); } QDate getLastVersionCheck() { return settings->value(LAST_VERSION_CHECK).toDate(); } void setLastVersionCheck(const QDate &date) { settings->setValue(LAST_VERSION_CHECK, date); } int getNumDaysBetweenVersionChecks() { return settings->value(NUM_DAYS_BETWEEN_VERSION_CHECKS, 1).toInt(); } @@ -84,7 +102,21 @@ class Configuration : public QObject bool getDoNotTurnPageOnScroll() { return settings->value(DO_NOT_TURN_PAGE_ON_SCROLL, false).toBool(); } bool getUseSingleScrollStepToTurnPage() { return settings->value(USE_SINGLE_SCROLL_STEP_TO_TURN_PAGE, false).toBool(); } void setDisableScrollAnimation(bool b) { settings->setValue(DISABLE_SCROLL_ANIMATION, b); } - bool getDisableScrollAnimation() { return settings->value(DISABLE_SCROLL_ANIMATION, false).toBool(); } + bool getDisableScrollAnimation() + { +#ifdef Q_OS_MACOS + auto defaultValue = true; +#else + auto defaultValue = false; +#endif + + return settings->value(DISABLE_SCROLL_ANIMATION, defaultValue).toBool(); + } + + MouseMode getMouseMode() { return static_cast(settings->value(MOUSE_MODE, MouseMode::Normal).toInt()); } + void setMouseMode(MouseMode mouseMode) { settings->setValue(MOUSE_MODE, static_cast(mouseMode)); } }; +} + #endif diff --git a/YACReader/main_window_viewer.cpp b/YACReader/main_window_viewer.cpp index 1ac26eb53..2c8331449 100644 --- a/YACReader/main_window_viewer.cpp +++ b/YACReader/main_window_viewer.cpp @@ -486,7 +486,10 @@ void MainWindowViewer::createToolBars() #endif #ifndef Y_MAC_UI - comicToolBar->setStyleSheet("QToolBar{border:none;}"); + comicToolBar->setStyleSheet(R"( + QToolBar { border: none; } + QToolButton:checked { background-color: #cccccc; } + )"); comicToolBar->setIconSize(QSize(18, 18)); #endif @@ -625,7 +628,7 @@ void MainWindowViewer::createToolBars() viewer->addAction(closeAction); - viewer->setContextMenuPolicy(Qt::ActionsContextMenu); + updateContextMenuPolicy(); // MacOSX app menus #ifdef Q_OS_MACOS @@ -777,9 +780,25 @@ void MainWindowViewer::openComicFromRecentAction(QAction *action) void MainWindowViewer::reloadOptions() { + updateContextMenuPolicy(); viewer->updateConfig(settings); } +void MainWindowViewer::updateContextMenuPolicy() +{ + auto mouseMode = Configuration::getConfiguration().getMouseMode(); + switch (mouseMode) { + + case Normal: + case HotAreas: + viewer->setContextMenuPolicy(Qt::ActionsContextMenu); + break; + case LeftRightNavigation: + viewer->setContextMenuPolicy(Qt::NoContextMenu); + break; + } +} + void MainWindowViewer::open() { QFileDialog openDialog; @@ -970,9 +989,11 @@ void MainWindowViewer::disablePreviousNextComicActions() void MainWindowViewer::mouseDoubleClickEvent(QMouseEvent *event) { - if (event->button() == Qt::LeftButton) { - toggleFullScreen(); - event->accept(); + if (Configuration::getConfiguration().getMouseMode() == MouseMode::Normal) { + if (event->button() == Qt::LeftButton) { + toggleFullScreen(); + event->accept(); + } } } @@ -1319,7 +1340,11 @@ void MainWindowViewer::toggleFitToWidthSlider() if (zoomSliderAction->isVisible()) { zoomSliderAction->hide(); } else { +#if defined(Y_MAC_UI) && (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + zoomSliderAction->move((this->width() - zoomSliderAction->width()) / 2, y); +#else zoomSliderAction->move(250, y); +#endif zoomSliderAction->show(); } } diff --git a/YACReader/main_window_viewer.h b/YACReader/main_window_viewer.h index 17011c36d..b2d086636 100644 --- a/YACReader/main_window_viewer.h +++ b/YACReader/main_window_viewer.h @@ -67,6 +67,7 @@ public slots: void increasePageZoomLevel(); void decreasePageZoomLevel(); void reloadOptions(); + void updateContextMenuPolicy(); void fitToWidth(); void fitToHeight(); void toggleWidthHeight(); diff --git a/YACReader/mouse_handler.cpp b/YACReader/mouse_handler.cpp new file mode 100644 index 000000000..d4b7a6c1a --- /dev/null +++ b/YACReader/mouse_handler.cpp @@ -0,0 +1,149 @@ +#include "mouse_handler.h" + +#include + +#include "configuration.h" +#include "magnifying_glass.h" +#include "render.h" +#include "viewer.h" + +#include "goto_flow.h" +#ifndef NO_OPENGL +#include "goto_flow_gl.h" +#else +#include +#endif + +using namespace YACReader; + +YACReader::MouseHandler::MouseHandler(Viewer *viewer) + : viewer(viewer) +{ +} + +void YACReader::MouseHandler::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + viewer->drag = true; +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + auto position = event->position(); +#else + auto position = QPointF(event->x(), event->y()); +#endif + dragOrigin = dragLatestPosition = position; + viewer->setCursor(Qt::ClosedHandCursor); + event->accept(); + return; + } +} + +void YACReader::MouseHandler::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() == Qt::ForwardButton) { + viewer->right(); + event->accept(); + return; + } + + if (event->button() == Qt::BackButton) { + viewer->left(); + event->accept(); + return; + } + + auto wasDragging = viewer->drag; + + if (event->button() == Qt::LeftButton) { + viewer->drag = false; + viewer->setCursor(Qt::OpenHandCursor); + event->accept(); + } + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + auto position = event->position(); +#else + auto position = QPointF(event->x(), event->y()); +#endif + auto dragDistance = QLineF(position, dragOrigin).length(); + + auto mouseMode = Configuration::getConfiguration().getMouseMode(); + switch (mouseMode) { + case Normal: + return; + case LeftRightNavigation: + if (wasDragging && (dragDistance > 25)) { + return; + } + + if (event->button() == Qt::LeftButton) { + viewer->left(); + event->accept(); + return; + } + + if (event->button() == Qt::RightButton) { + viewer->right(); + event->accept(); + return; + } + + break; + case HotAreas: + if (wasDragging && (dragDistance > 25)) { + return; + } + + if (event->button() == Qt::LeftButton) { + if (position.x() < viewer->width() / 2) { + viewer->left(); + } else { + viewer->right(); + } + } + + break; + }; +} + +void YACReader::MouseHandler::mouseMoveEvent(QMouseEvent *event) +{ + viewer->showCursor(); + viewer->hideCursorTimer->start(2500); + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + auto position = event->position(); +#else + auto position = QPointF(event->x(), event->y()); +#endif + + if (viewer->magnifyingGlassShown) + viewer->mglass->move(static_cast(position.x() - float(viewer->mglass->width()) / 2), static_cast(position.y() - float(viewer->mglass->height()) / 2)); + + if (viewer->render->hasLoadedComic()) { + if (viewer->showGoToFlowAnimation->state() != QPropertyAnimation::Running) { + if (Configuration::getConfiguration().getDisableShowOnMouseOver() == false) { + if (viewer->goToFlow->isVisible()) { + QPoint gtfPos = viewer->goToFlow->mapFrom(this->viewer, event->pos()); + if (gtfPos.y() < 0 || gtfPos.x() < 0 || gtfPos.x() > viewer->goToFlow->width()) // TODO this extra check is for Mavericks (mouseMove over goToFlowGL seems to be broken) + viewer->animateHideGoToFlow(); + // goToFlow->hide(); + } else { + int umbral = (viewer->width() - viewer->goToFlow->width()) / 2; + if ((position.y() > viewer->height() - 15) && (position.x() > umbral) && (position.x() < viewer->width() - umbral)) { + + viewer->animateShowGoToFlow(); + viewer->hideCursorTimer->stop(); + } + } + } + } + + if (viewer->drag) { + int currentPosY = viewer->verticalScrollBar()->sliderPosition(); + int currentPosX = viewer->horizontalScrollBar()->sliderPosition(); + viewer->verticalScrollBar()->setSliderPosition(currentPosY + (dragLatestPosition.y() - position.y())); + viewer->horizontalScrollBar()->setSliderPosition(currentPosX + (dragLatestPosition.x() - position.x())); + dragLatestPosition = position; + } + } +} diff --git a/YACReader/mouse_handler.h b/YACReader/mouse_handler.h new file mode 100644 index 000000000..1c5345b8d --- /dev/null +++ b/YACReader/mouse_handler.h @@ -0,0 +1,25 @@ +#ifndef MOUSE_HANDLER_H +#define MOUSE_HANDLER_H + +#include + +class Viewer; + +namespace YACReader { +class MouseHandler +{ +public: + MouseHandler(Viewer *viewer); + + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + +private: + Viewer *viewer; + QPointF dragOrigin; + QPointF dragLatestPosition; +}; +} + +#endif // MOUSE_HANDLER_H diff --git a/YACReader/options_dialog.cpp b/YACReader/options_dialog.cpp index 5d006a6a9..611ce8d38 100644 --- a/YACReader/options_dialog.cpp +++ b/YACReader/options_dialog.cpp @@ -38,6 +38,12 @@ OptionsDialog::OptionsDialog(QWidget *parent) path->addWidget(pathFindButton = new QPushButton(QIcon(":/images/find_folder.png"), "")); pathBox->setLayout(path); + QGroupBox *displayBox = new QGroupBox(tr("Display")); + auto displayLayout = new QHBoxLayout(); + showTimeInInformationLabel = new QCheckBox(tr("Show time in current page information label")); + displayLayout->addWidget(showTimeInInformationLabel); + displayBox->setLayout(displayLayout); + connect(pathFindButton, &QAbstractButton::clicked, this, &OptionsDialog::findFolder); QGroupBox *slideSizeBox = new QGroupBox(tr("\"Go to flow\" size")); @@ -81,11 +87,26 @@ OptionsDialog::OptionsDialog(QWidget *parent) scrollBox->setLayout(scrollLayout); + auto mouseModeBox = new QGroupBox(tr("Mouse mode")); + auto mouseModeLayout = new QVBoxLayout(); + + normalMouseModeRadioButton = new QRadioButton(tr("Only Back/Forward buttons can turn pages")); + leftRightNavigationMouseModeRadioButton = new QRadioButton(tr("Use the Left/Right buttons to turn pages.")); + hotAreasMouseModeRadioButton = new QRadioButton(tr("Click left or right half of the screen to turn pages.")); + + mouseModeLayout->addWidget(normalMouseModeRadioButton); + mouseModeLayout->addWidget(leftRightNavigationMouseModeRadioButton); + mouseModeLayout->addWidget(hotAreasMouseModeRadioButton); + + mouseModeBox->setLayout(mouseModeLayout); + layoutGeneral->addWidget(pathBox); + layoutGeneral->addWidget(displayBox); layoutGeneral->addWidget(slideSizeBox); // layoutGeneral->addWidget(fitBox); layoutGeneral->addWidget(colorBox); layoutGeneral->addWidget(scrollBox); + layoutGeneral->addWidget(mouseModeBox); layoutGeneral->addWidget(shortcutsBox); layoutGeneral->addStretch(); @@ -237,6 +258,8 @@ void OptionsDialog::saveOptions() settings->setValue(PATH, pathEdit->text()); + Configuration::getConfiguration().setShowTimeInInformation(showTimeInInformationLabel->isChecked()); + settings->setValue(BACKGROUND_COLOR, currentColor); // settings->setValue(FIT_TO_WIDTH_RATIO,fitToWidthRatioS->sliderPosition()/100.0); settings->setValue(QUICK_NAVI_MODE, quickNavi->isChecked()); @@ -246,6 +269,18 @@ void OptionsDialog::saveOptions() settings->setValue(USE_SINGLE_SCROLL_STEP_TO_TURN_PAGE, useSingleScrollStepToTurnPage->isChecked()); settings->setValue(DISABLE_SCROLL_ANIMATION, disableScrollAnimations->isChecked()); + // get checked radio button to get the mouse mode + YACReader::MouseMode mouseMode = Normal; + if (normalMouseModeRadioButton->isChecked()) { + mouseMode = Normal; + ; + } else if (leftRightNavigationMouseModeRadioButton->isChecked()) { + mouseMode = LeftRightNavigation; + } else if (hotAreasMouseModeRadioButton->isChecked()) { + mouseMode = HotAreas; + } + Configuration::getConfiguration().setMouseMode(mouseMode); + YACReaderOptionsDialog::saveOptions(); } @@ -271,6 +306,8 @@ void OptionsDialog::restoreOptions(QSettings *settings) pathEdit->setText(settings->value(PATH).toString()); + showTimeInInformationLabel->setChecked(Configuration::getConfiguration().getShowTimeInInformation()); + updateColor(settings->value(BACKGROUND_COLOR).value()); // fitToWidthRatioS->setSliderPosition(settings->value(FIT_TO_WIDTH_RATIO).toFloat()*100); @@ -286,7 +323,27 @@ void OptionsDialog::restoreOptions(QSettings *settings) doNotTurnPageOnScroll->setChecked(settings->value(DO_NOT_TURN_PAGE_ON_SCROLL, false).toBool()); useSingleScrollStepToTurnPage->setChecked(settings->value(USE_SINGLE_SCROLL_STEP_TO_TURN_PAGE, false).toBool()); - disableScrollAnimations->setChecked(settings->value(DISABLE_SCROLL_ANIMATION, false).toBool()); + +#ifdef Q_OS_MACOS + auto defaultDisableScrollAnimationsValue = true; +#else + auto defaultDisableScrollAnimationsValue = false; +#endif + disableScrollAnimations->setChecked(settings->value(DISABLE_SCROLL_ANIMATION, defaultDisableScrollAnimationsValue).toBool()); + + auto mouseMode = Configuration::getConfiguration().getMouseMode(); + + switch (mouseMode) { + case Normal: + normalMouseModeRadioButton->setChecked(true); + break; + case LeftRightNavigation: + leftRightNavigationMouseModeRadioButton->setChecked(true); + break; + case HotAreas: + hotAreasMouseModeRadioButton->setChecked(true); + break; + } } void OptionsDialog::updateColor(const QColor &color) diff --git a/YACReader/options_dialog.h b/YACReader/options_dialog.h index db1e858c4..0f15e196b 100644 --- a/YACReader/options_dialog.h +++ b/YACReader/options_dialog.h @@ -22,6 +22,9 @@ class OptionsDialog : public YACReaderOptionsDialog // QLabel * pathLabel; QLineEdit *pathEdit; QPushButton *pathFindButton; + + QCheckBox *showTimeInInformationLabel; + QCheckBox *quickNavi; QCheckBox *disableShowOnMouseOver; QCheckBox *scaleCheckbox; @@ -51,6 +54,11 @@ class OptionsDialog : public YACReaderOptionsDialog YACReaderSpinSliderWidget *gammaS; QColor currentColor; + + QRadioButton *normalMouseModeRadioButton; + QRadioButton *leftRightNavigationMouseModeRadioButton; + QRadioButton *hotAreasMouseModeRadioButton; + public slots: void saveOptions() override; void restoreOptions(QSettings *settings) override; diff --git a/YACReader/page_label_widget.cpp b/YACReader/page_label_widget.cpp index f257a2017..e0a7ee345 100644 --- a/YACReader/page_label_widget.cpp +++ b/YACReader/page_label_widget.cpp @@ -18,32 +18,35 @@ PageLabelWidget::PageLabelWidget(QWidget *parent) auto layout = new QHBoxLayout; layout->setContentsMargins(0, 0, 0, 0); - setContentsMargins(0, 0, 0, 0); - - QSize labelSize; - if (verticalRes <= 1024) - labelSize = QSize(135, 30); - else if (verticalRes <= 1200) - labelSize = QSize(170, 35); - else - labelSize = QSize(205, 45); textLabel = new QLabel(this); textLabel->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); - if (verticalRes <= 1024) - textLabel->setStyleSheet("QLabel { color : white; font-size:12px; padding-left:8px; }"); - else if (verticalRes <= 1200) - textLabel->setStyleSheet("QLabel { color : white; font-size:16px; padding-left:8px;}"); - else - textLabel->setStyleSheet("QLabel { color : white; font-size:20px; padding-left:8px; }"); + textLabel->setWordWrap(true); // Allow wrapping + + int contentMargin = 0; + if (verticalRes <= 1024) { + textLabel->setStyleSheet("QLabel { color : white; font-size:12px; }"); + contentMargin = 12; + } else if (verticalRes <= 1200) { + textLabel->setStyleSheet("QLabel { color : white; font-size:16px; }"); + contentMargin = 16; + } else { + textLabel->setStyleSheet("QLabel { color : white; font-size:20px; }"); + contentMargin = 20; + } - setFixedSize(labelSize); - - if (parent != nullptr) - move(QPoint((parent->geometry().size().width() - this->width()), -this->height())); + setContentsMargins(contentMargin * 2.3, contentMargin / 2.3, contentMargin * 2.3, contentMargin / 2.3); + // Instead of fixed size, allow dynamic sizing + textLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); layout->addWidget(textLabel, 0, Qt::AlignCenter); setLayout(layout); + + adjustSize(); // Resize to fit content + + if (parent != nullptr) + move(QPoint((parent->geometry().size().width() - this->width()), -this->height())); } void PageLabelWidget::show() diff --git a/YACReader/viewer.cpp b/YACReader/viewer.cpp index 7d6fe242f..b8ac3ccdc 100644 --- a/YACReader/viewer.cpp +++ b/YACReader/viewer.cpp @@ -1,5 +1,4 @@ #include "viewer.h" -#include "magnifying_glass.h" #include "configuration.h" #include "magnifying_glass.h" #include "goto_flow.h" @@ -38,7 +37,8 @@ Viewer::Viewer(QWidget *parent) shouldOpenNext(false), shouldOpenPrevious(false), magnifyingGlassShown(false), - restoreMagnifyingGlass(false) + restoreMagnifyingGlass(false), + mouseHandler(std::make_unique(this)) { translator = new YACReaderTranslator(this); translator->hide(); @@ -260,6 +260,10 @@ void Viewer::processCRCError(QString message) void Viewer::next() { + if (!render->hasLoadedComic()) { + return; + } + direction = 1; if (doublePage && render->currentPageIsDoublePage()) { render->nextDoublePage(); @@ -272,6 +276,10 @@ void Viewer::next() void Viewer::left() { + if (!render->hasLoadedComic()) { + return; + } + if (doubleMangaPage) { next(); } else { @@ -281,6 +289,10 @@ void Viewer::left() void Viewer::right() { + if (!render->hasLoadedComic()) { + return; + } + if (doubleMangaPage) { prev(); } else { @@ -290,6 +302,10 @@ void Viewer::right() void Viewer::prev() { + if (!render->hasLoadedComic()) { + return; + } + direction = -1; if (doublePage && render->previousPageIsDoublePage()) { render->previousDoublePage(); @@ -767,44 +783,6 @@ void Viewer::resizeEvent(QResizeEvent *event) QScrollArea::resizeEvent(event); } -void Viewer::mouseMoveEvent(QMouseEvent *event) -{ - showCursor(); - hideCursorTimer->start(2500); - - if (magnifyingGlassShown) - mglass->move(static_cast(event->x() - float(mglass->width()) / 2), static_cast(event->y() - float(mglass->height()) / 2)); - - if (render->hasLoadedComic()) { - if (showGoToFlowAnimation->state() != QPropertyAnimation::Running) { - if (Configuration::getConfiguration().getDisableShowOnMouseOver() == false) { - if (goToFlow->isVisible()) { - QPoint gtfPos = goToFlow->mapFrom(this, event->pos()); - if (gtfPos.y() < 0 || gtfPos.x() < 0 || gtfPos.x() > goToFlow->width()) // TODO this extra check is for Mavericks (mouseMove over goToFlowGL seems to be broken) - animateHideGoToFlow(); - // goToFlow->hide(); - } else { - int umbral = (width() - goToFlow->width()) / 2; - if ((event->y() > height() - 15) && (event->x() > umbral) && (event->x() < width() - umbral)) { - - animateShowGoToFlow(); - hideCursorTimer->stop(); - } - } - } - } - - if (drag) { - int currentPosY = verticalScrollBar()->sliderPosition(); - int currentPosX = horizontalScrollBar()->sliderPosition(); - verticalScrollBar()->setSliderPosition(currentPosY + (yDragOrigin - event->y())); - horizontalScrollBar()->setSliderPosition(currentPosX + (xDragOrigin - event->x())); - yDragOrigin = event->y(); - xDragOrigin = event->x(); - } - } -} - QPixmap Viewer::pixmap() const { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) @@ -847,6 +825,7 @@ void Viewer::setMagnifyingGlassShown(bool shown) void Viewer::informationSwitch() { + informationLabel->updatePosition(); information ? informationLabel->hide() : informationLabel->show(); // informationLabel->move(QPoint((width()-informationLabel->width())/2,0)); information = !information; @@ -859,9 +838,16 @@ void Viewer::informationSwitch() void Viewer::updateInformation() { if (render->hasLoadedComic()) { - informationLabel->setText(render->getCurrentPagesInformation() + " - " + QTime::currentTime().toString("HH:mm")); + auto displayTime = Configuration::getConfiguration().getShowTimeInInformation(); + if (displayTime) { + informationLabel->setText(render->getCurrentPagesInformation() + " - " + QTime::currentTime().toString("HH:mm")); + } else { + informationLabel->setText(render->getCurrentPagesInformation()); + } + informationLabel->adjustSize(); informationLabel->update(); // TODO it shouldn't be neccesary + informationLabel->updatePosition(); } } @@ -1046,10 +1032,10 @@ void Viewer::showCursor() void Viewer::updateOptions() { - goToFlow->setFlowType(Configuration::getConfiguration().getFlowType()); updateBackgroundColor(Configuration::getConfiguration().getBackgroundColor()); updateContentSize(); + updateInformation(); } void Viewer::updateBackgroundColor(const QColor &color) @@ -1093,36 +1079,17 @@ void Viewer::animateHideTranslator() void Viewer::mousePressEvent(QMouseEvent *event) { - if (event->button() == Qt::LeftButton) { - drag = true; - yDragOrigin = event->y(); - xDragOrigin = event->x(); - setCursor(Qt::ClosedHandCursor); - event->accept(); - return; - } + mouseHandler->mousePressEvent(event); } void Viewer::mouseReleaseEvent(QMouseEvent *event) { - if (event->button() == Qt::LeftButton) { - drag = false; - setCursor(Qt::OpenHandCursor); - event->accept(); - return; - } - - if (event->button() == Qt::ForwardButton) { - right(); - event->accept(); - return; - } + mouseHandler->mouseReleaseEvent(event); +} - if (event->button() == Qt::BackButton) { - left(); - event->accept(); - return; - } +void Viewer::mouseMoveEvent(QMouseEvent *event) +{ + mouseHandler->mouseMoveEvent(event); } void Viewer::updateZoomRatio(int ratio) diff --git a/YACReader/viewer.h b/YACReader/viewer.h index 5dd43348c..c2bb183b3 100644 --- a/YACReader/viewer.h +++ b/YACReader/viewer.h @@ -17,6 +17,7 @@ #include #include "scroll_management.h" +#include "mouse_handler.h" class ComicDB; class Comic; @@ -147,9 +148,6 @@ public slots: int translatorXPos; QPropertyAnimation *translatorAnimation; - int yDragOrigin; - int xDragOrigin; - NotificationsLabelWidget *notificationsLabel; bool shouldOpenNext; @@ -185,6 +183,9 @@ public slots: int animationDuration() const; void animateScroll(QPropertyAnimation &scroller, const QScrollBar &scrollBar, int delta); + //! Mouse handler + std::unique_ptr mouseHandler; + public: Viewer(QWidget *parent = nullptr); ~Viewer(); @@ -213,6 +214,8 @@ public slots: void magnifyingGlassZoomIn(); void magnifyingGlassZoomOut(); void resetMagnifyingGlass(); + + friend class YACReader::MouseHandler; }; #endif diff --git a/YACReader/yacreader_de.ts b/YACReader/yacreader_de.ts index ae49b8eb8..5fdbbb2e1 100644 --- a/YACReader/yacreader_de.ts +++ b/YACReader/yacreader_de.ts @@ -559,12 +559,12 @@ OptionsDialog - + Gamma Gamma - + Reset Zurücksetzen @@ -574,112 +574,142 @@ Meine Comics-Pfad - + Image adjustment Bildanpassung - + "Go to flow" size "Go to flow" Größe - + Choose Auswählen - + Image options Bilderoptionen - + Contrast Kontrast - + Options Optionen - + Comics directory Comics-Verzeichnis - + Background color Hintergrundfarbe - + Page Flow Page Flow - + General Allgemein - + Brightness Helligkeit - + Restart is needed Neustart erforderlich - + Quick Navigation Mode Schnellnavigations-Modus - + + Display + + + + + Show time in current page information label + + + + Scroll behaviour - + Disable scroll animations and smooth scrolling - + Do not turn page using scroll - + Use single scroll step to turn page - + + Mouse mode + + + + + Only Back/Forward buttons can turn pages + + + + + Use the Left/Right buttons to turn pages. + + + + + Click left or right half of the screen to turn pages. + + + + Disable mouse over activation Aktivierung durch Maus deaktivieren - + Fit options Anpassungsoptionen - + Enlarge images to fit width/height Bilder vergrößern, um sie Breite/Höhe anzupassen - + Double Page options Doppelseiten-Einstellungen - + Show covers as single page Cover als eine Seite darstellen @@ -726,6 +756,16 @@ Fatal Fatal + + + Select custom cover + + + + + Images (%1) + + QsLogging::LogWindowModel @@ -786,13 +826,13 @@ Viewer - + Page not available! Seite nicht verfügbar! - + Press 'O' to open comic. 'O' drücken, um Comic zu öffnen. @@ -802,7 +842,7 @@ Fehler beim Öffnen des Comics - + Cover! Titelseite! @@ -822,12 +862,12 @@ Nicht gefunden - + Last page! Letzte Seite! - + Loading...please wait! Ladevorgang... Bitte warten! @@ -886,7 +926,7 @@ - + Save current page Aktuelle Seite speichern @@ -897,8 +937,8 @@ - - + + Open previous comic Vorherigen Comic öffnen @@ -909,8 +949,8 @@ - - + + Open next comic Nächsten Comic öffnen @@ -921,8 +961,8 @@ - - + + Go to previous page Zur vorherigen Seite gehen @@ -933,8 +973,8 @@ - - + + Go to next page Zur nächsten Seite gehen @@ -1040,7 +1080,7 @@ - + Help Hilfe @@ -1115,217 +1155,217 @@ &Datei - - + + Open recent Kürzlich geöffnet - + File Datei - + Edit Ändern - + View Anzeigen - + Go Los - + Window Fenster - - - + + + Open Comic Comic öffnen - - - + + + Comic files Comic-Dateien - + Open folder Ordner öffnen - + page_%1.jpg Seite_%1.jpg - + Image files (*.jpg) Bildateien (*.jpg) - + Comics Comics - + Toggle fullscreen mode Vollbild-Modus umschalten - + Hide/show toolbar Symbolleiste anzeigen/verstecken - + General Allgemein - + Size up magnifying glass Vergrößerungsglas vergrößern - + Size down magnifying glass Vergrößerungsglas verkleinern - + Zoom in magnifying glass Vergrößerungsglas reinzoomen - + Zoom out magnifying glass Vergrößerungsglas rauszoomen - + Reset magnifying glass - + Magnifiying glass Vergrößerungsglas - + Toggle between fit to width and fit to height Zwischen Anpassung an Seite und Höhe wechseln - + Page adjustement Seitenanpassung - + Autoscroll down Automatisches Runterscrollen - + Autoscroll up Automatisches Raufscrollen - + Autoscroll forward, horizontal first Automatisches Vorwärtsscrollen, horizontal zuerst - + Autoscroll backward, horizontal first Automatisches Zurückscrollen, horizontal zuerst - + Autoscroll forward, vertical first Automatisches Vorwärtsscrollen, vertikal zuerst - + Autoscroll backward, vertical first Automatisches Zurückscrollen, vertikal zuerst - + Move down Nach unten - + Move up Nach oben - + Move left Nach links - + Move right Nach rechts - + Go to the first page Zur ersten Seite gehen - + Go to the last page Zur letzten Seite gehen - + Offset double page to the left - + Offset double page to the right - + Reading Lesend - + There is a new version available Neue Version verfügbar - + Do you want to download the new version? Möchten Sie die neue Version herunterladen? - + Remind me in 14 days In 14 Tagen erneut erinnern - + Not now Nicht jetzt @@ -1333,7 +1373,7 @@ YACReader::WhatsNewDialog - + Close Schließen diff --git a/YACReader/yacreader_en.ts b/YACReader/yacreader_en.ts index 1b6ae968a..bd065a64c 100644 --- a/YACReader/yacreader_en.ts +++ b/YACReader/yacreader_en.ts @@ -180,7 +180,7 @@ OptionsDialog - + "Go to flow" size @@ -190,117 +190,147 @@ - + Background color - + Choose - + Quick Navigation Mode - + Disable mouse over activation - + Restart is needed - + Brightness - + + Display + + + + + Show time in current page information label + + + + Scroll behaviour - + Disable scroll animations and smooth scrolling - + Do not turn page using scroll - + Use single scroll step to turn page - + + Mouse mode + + + + + Only Back/Forward buttons can turn pages + + + + + Use the Left/Right buttons to turn pages. + + + + + Click left or right half of the screen to turn pages. + + + + Contrast - + Gamma - + Reset - + Image options - + Fit options - + Enlarge images to fit width/height - + Double Page options - + Show covers as single page - + General - + Page Flow - + Image adjustment - + Options - + Comics directory @@ -347,6 +377,16 @@ Fatal + + + Select custom cover + + + + + Images (%1) + + QsLogging::LogWindowModel @@ -393,7 +433,7 @@ Viewer - + Press 'O' to open comic. @@ -418,22 +458,22 @@ - + Loading...please wait! - + Page not available! - + Cover! - + Last page! @@ -492,7 +532,7 @@ - + Save current page @@ -503,8 +543,8 @@ - - + + Open previous comic @@ -515,8 +555,8 @@ - - + + Open next comic @@ -527,8 +567,8 @@ - - + + Go to previous page @@ -539,8 +579,8 @@ - - + + Go to next page @@ -646,7 +686,7 @@ - + Help @@ -721,217 +761,217 @@ - - + + Open recent - + File - + Edit - + View - + Go - + Window - - - + + + Open Comic - - - + + + Comic files - + Open folder - + page_%1.jpg - + Image files (*.jpg) - + Comics - + Toggle fullscreen mode - + Hide/show toolbar - + General - + Size up magnifying glass - + Size down magnifying glass - + Zoom in magnifying glass - + Zoom out magnifying glass - + Reset magnifying glass - + Magnifiying glass - + Toggle between fit to width and fit to height - + Page adjustement - + Autoscroll down - + Autoscroll up - + Autoscroll forward, horizontal first - + Autoscroll backward, horizontal first - + Autoscroll forward, vertical first - + Autoscroll backward, vertical first - + Move down - + Move up - + Move left - + Move right - + Go to the first page - + Go to the last page - + Offset double page to the left - + Offset double page to the right - + Reading - + There is a new version available - + Do you want to download the new version? - + Remind me in 14 days - + Not now @@ -939,7 +979,7 @@ YACReader::WhatsNewDialog - + Close diff --git a/YACReader/yacreader_es.ts b/YACReader/yacreader_es.ts index 007a6a387..56605c326 100644 --- a/YACReader/yacreader_es.ts +++ b/YACReader/yacreader_es.ts @@ -100,7 +100,7 @@ Total pages : - Páginas totales: + Páginas totales : @@ -110,7 +110,7 @@ Page : - Página : + Página : @@ -395,12 +395,12 @@ OptionsDialog - + Gamma Gamma - + Reset Reset @@ -410,112 +410,142 @@ Ruta a mis cómics - + Image adjustment Ajustes de imagen - + "Go to flow" size Tamaño de "Go to flow" - + Choose Elegir - + Image options Opciones de imagen - + Contrast Contraste - + Options Opciones - + Comics directory Directorio de cómics - + Background color Color de fondo - + Page Flow Page Flow - + General General - + Brightness Brillo - + Restart is needed Es necesario reiniciar - + Quick Navigation Mode Modo de navegación rápida - + + Display + Visualización + + + + Show time in current page information label + Mostrar la hora en la etiqueta de información de la página actual + + + Scroll behaviour Comportamiento del scroll - + Disable scroll animations and smooth scrolling Desactivar animaciones de desplazamiento y desplazamiento suave - + Do not turn page using scroll No cambiar de página usando el scroll - + Use single scroll step to turn page Usar un solo paso de desplazamiento para cambiar de página - + + Mouse mode + Modo del ratón + + + + Only Back/Forward buttons can turn pages + Solo los botones Atrás/Adelante pueden cambiar de página + + + + Use the Left/Right buttons to turn pages. + Usar los botones Izquierda/Derecha para cambiar de página. + + + + Click left or right half of the screen to turn pages. + Hacer clic en la mitad izquierda o derecha de la pantalla para cambiar de página. + + + Disable mouse over activation Desactivar activación al pasar el ratón - + Fit options Opciones de ajuste - + Enlarge images to fit width/height Ampliar imágenes para ajustarse al ancho/alto - + Double Page options Opciones de doble página - + Show covers as single page Mostrar portadas como página única @@ -562,6 +592,16 @@ Fatal + + + Select custom cover + + + + + Images (%1) + + QsLogging::LogWindowModel @@ -622,13 +662,13 @@ Viewer - + Page not available! ¡Página no disponible! - + Press 'O' to open comic. Pulsa 'O' para abrir un fichero. @@ -638,7 +678,7 @@ Error abriendo cómic - + Cover! ¡Portada! @@ -658,12 +698,12 @@ No encontrado - + Last page! ¡Última página! - + Loading...please wait! Cargando...espere, por favor! @@ -722,7 +762,7 @@ - + Save current page Guardar la página actual @@ -733,8 +773,8 @@ - - + + Open previous comic Abrir cómic anterior @@ -745,8 +785,8 @@ - - + + Open next comic Abrir siguiente cómic @@ -757,8 +797,8 @@ - - + + Go to previous page Ir a la página anterior @@ -769,8 +809,8 @@ - - + + Go to next page Ir a la página siguiente @@ -876,7 +916,7 @@ - + Help Ayuda @@ -951,217 +991,217 @@ &Archivo - - + + Open recent Abrir reciente - + File Archivo - + Edit Editar - + View Ver - + Go Ir - + Window Ventana - - - + + + Open Comic Abrir cómic - - - + + + Comic files Archivos de cómic - + Open folder Abrir carpeta - + page_%1.jpg página_%1.jpg - + Image files (*.jpg) Archivos de imagen (*.jpg) - + Comics Cómics - + Toggle fullscreen mode Alternar modo de pantalla completa - + Hide/show toolbar Ocultar/mostrar barra de herramientas - + General General - + Size up magnifying glass Aumentar tamaño de la lupa - + Size down magnifying glass Disminuir tamaño de lupa - + Zoom in magnifying glass Incrementar el aumento de la lupa - + Zoom out magnifying glass Reducir el aumento de la lupa - + Reset magnifying glass - + Resetear lupa - + Magnifiying glass Lupa - + Toggle between fit to width and fit to height Alternar entre ajuste al ancho y ajuste al alto - + Page adjustement Ajuste de página - + Autoscroll down Desplazamiento automático hacia abajo - + Autoscroll up Desplazamiento automático hacia arriba - + Autoscroll forward, horizontal first Desplazamiento automático hacia adelante, primero horizontal - + Autoscroll backward, horizontal first Desplazamiento automático hacia atrás, primero horizontal - + Autoscroll forward, vertical first Desplazamiento automático hacia adelante, primero vertical - + Autoscroll backward, vertical first Desplazamiento automático hacia atrás, primero vertical - + Move down Mover abajo - + Move up Mover arriba - + Move left Mover a la izquierda - + Move right Mover a la derecha - + Go to the first page Ir a la primera página - + Go to the last page Ir a la última página - + Offset double page to the left Mover una página a la izquierda - + Offset double page to the right Mover una página a la derecha - + Reading Leyendo - + There is a new version available Hay una nueva versión disponible - + Do you want to download the new version? ¿Desea descargar la nueva versión? - + Remind me in 14 days Recordar en 14 días - + Not now Ahora no @@ -1169,7 +1209,7 @@ YACReader::WhatsNewDialog - + Close Cerrar diff --git a/YACReader/yacreader_fr.ts b/YACReader/yacreader_fr.ts index ddf47b10b..967308c28 100644 --- a/YACReader/yacreader_fr.ts +++ b/YACReader/yacreader_fr.ts @@ -551,12 +551,12 @@ OptionsDialog - + Gamma Gamma - + Reset Remise à zéro @@ -566,112 +566,142 @@ Chemin de mes bandes dessinées - + Image adjustment Ajustement de l'image - + "Go to flow" size Taille du flux - + Choose Choisir - + Image options Option de l'image - + Contrast Contraste - + Options Options - + Comics directory Répertoire des bandes dessinées - + Quick Navigation Mode Mode navigation rapide - + + Display + + + + + Show time in current page information label + + + + Background color Couleur d'arrière plan - + Scroll behaviour - + Disable scroll animations and smooth scrolling - + Do not turn page using scroll - + Use single scroll step to turn page - + + Mouse mode + + + + + Only Back/Forward buttons can turn pages + + + + + Use the Left/Right buttons to turn pages. + + + + + Click left or right half of the screen to turn pages. + + + + Disable mouse over activation Désactiver la souris sur l'activation - + Page Flow Flux des pages - + General Général - + Brightness Luminosité - + Restart is needed Redémarrage nécessaire - + Fit options - + Enlarge images to fit width/height - + Double Page options - + Show covers as single page @@ -718,6 +748,16 @@ Fatal + + + Select custom cover + + + + + Images (%1) + + QsLogging::LogWindowModel @@ -778,13 +818,13 @@ Viewer - + Page not available! Page non disponible ! - + Press 'O' to open comic. Appuyez sur "O" pour ouvrir une bande dessinée. @@ -794,7 +834,7 @@ Erreur d'ouverture de la bande dessinée - + Cover! Couverture! @@ -814,12 +854,12 @@ Introuvable - + Last page! Dernière page! - + Loading...please wait! Chargement... Patientez @@ -878,7 +918,7 @@ - + Save current page Sauvegarder la page actuelle @@ -889,8 +929,8 @@ - - + + Open previous comic Ouvrir la bande dessiné précédente @@ -901,8 +941,8 @@ - - + + Open next comic Ouvrir la bande dessinée suivante @@ -913,8 +953,8 @@ - - + + Go to previous page Aller à la page précédente @@ -925,8 +965,8 @@ - - + + Go to next page Aller à la page suivante @@ -1032,7 +1072,7 @@ - + Help Aide @@ -1107,217 +1147,217 @@ &Fichier - - + + Open recent Ouvrir récent - + File Fichier - + Edit Editer - + View Vue - + Go Aller - + Window Fenêtre - - - + + + Open Comic Ouvrir la bande dessinée - - - + + + Comic files Bande dessinée - + Open folder Ouvirir le dossier - + page_%1.jpg page_%1.jpg - + Image files (*.jpg) Image(*.jpg) - + Comics Bandes dessinées - + Toggle fullscreen mode Basculer en mode plein écran - + Hide/show toolbar Masquer / afficher la barre d'outils - + General Général - + Size up magnifying glass Augmenter la taille de la loupe - + Size down magnifying glass Réduire la taille de la loupe - + Zoom in magnifying glass Zoomer - + Zoom out magnifying glass Dézoomer - + Reset magnifying glass - + Magnifiying glass Loupe - + Toggle between fit to width and fit to height Basculer entre adapter à la largeur et adapter à la hauteur - + Page adjustement Ajustement de la page - + Autoscroll down Défilement automatique vers le bas - + Autoscroll up Défilement automatique vers le haut - + Autoscroll forward, horizontal first Défilement automatique en avant, horizontal - + Autoscroll backward, horizontal first Défilement automatique en arrière horizontal - + Autoscroll forward, vertical first Défilement automatique en avant, vertical - + Autoscroll backward, vertical first Défilement automatique en arrière, verticak - + Move down Descendre - + Move up Monter - + Move left Déplacer à gauche - + Move right Déplacer à droite - + Go to the first page Aller à la première page - + Go to the last page Aller à la dernière page - + Offset double page to the left - + Offset double page to the right - + Reading Lecture - + There is a new version available Une nouvelle version est disponible - + Do you want to download the new version? Voulez-vous télécharger la nouvelle version? - + Remind me in 14 days Rappelez-moi dans 14 jours - + Not now Pas maintenant @@ -1325,7 +1365,7 @@ YACReader::WhatsNewDialog - + Close Fermer diff --git a/YACReader/yacreader_it.ts b/YACReader/yacreader_it.ts index b8ef64813..902b65ff0 100644 --- a/YACReader/yacreader_it.ts +++ b/YACReader/yacreader_it.ts @@ -555,12 +555,12 @@ OptionsDialog - + Gamma Gamma - + Reset Reset @@ -570,112 +570,142 @@ Percorso dei miei fumetti - + Image adjustment Correzioni immagine - + "Go to flow" size Dimensione "Vai all'elenco" - + Choose Scegli - + Image options Opzione immagine - + Contrast Contrasto - + Options Opzioni - + Comics directory Cartella Fumetti - + Quick Navigation Mode Modo navigazione rapida - + + Display + + + + + Show time in current page information label + + + + Background color Colore di sfondo - + Scroll behaviour - + Disable scroll animations and smooth scrolling - + Do not turn page using scroll - + Use single scroll step to turn page - + + Mouse mode + + + + + Only Back/Forward buttons can turn pages + + + + + Use the Left/Right buttons to turn pages. + + + + + Click left or right half of the screen to turn pages. + + + + Disable mouse over activation Disabilita il mouse all'attivazione - + Page Flow Flusso pagine - + General Generale - + Brightness Luminosità - + Restart is needed Riavvio Necessario - + Fit options - + Enlarge images to fit width/height - + Double Page options - + Show covers as single page @@ -722,6 +752,16 @@ Fatal + + + Select custom cover + + + + + Images (%1) + + QsLogging::LogWindowModel @@ -782,13 +822,13 @@ Viewer - + Page not available! Pagina non disponibile! - + Press 'O' to open comic. Premi "O" per aprire il fumettto. @@ -798,7 +838,7 @@ Errore nell'apertura - + Cover! Copertina! @@ -818,12 +858,12 @@ Non trovato - + Last page! Ultima pagina! - + Loading...please wait! In caricamento...Attendi! @@ -882,7 +922,7 @@ - + Save current page Salva la pagina corrente @@ -893,8 +933,8 @@ - - + + Open previous comic Apri il fumetto precendente @@ -905,8 +945,8 @@ - - + + Open next comic Apri il prossimo fumetto @@ -917,8 +957,8 @@ - - + + Go to previous page Vai alla pagina precedente @@ -929,8 +969,8 @@ - - + + Go to next page Vai alla prossima Pagina @@ -1036,7 +1076,7 @@ - + Help Aiuto @@ -1111,217 +1151,217 @@ &File - - + + Open recent Apri i recenti - + File File - + Edit Edit - + View Mostra - + Go Vai - + Window Finestra - - - + + + Open Comic Apri Fumetto - - - + + + Comic files File Fumetto - + Open folder Apri cartella - + page_%1.jpg Pagina_%1.jpg - + Image files (*.jpg) File immagine (*.jpg) - + Comics Fumetto - + Toggle fullscreen mode Attiva/Disattiva schermo intero - + Hide/show toolbar Mostra/Nascondi Barra strumenti - + General Generale - + Size up magnifying glass Ingrandisci lente ingrandimento - + Size down magnifying glass Riduci lente ingrandimento - + Zoom in magnifying glass Ingrandisci in lente di ingrandimento - + Zoom out magnifying glass Riduci in lente di ingrandimento - + Reset magnifying glass - + Magnifiying glass Lente ingrandimento - + Toggle between fit to width and fit to height Passa tra adatta in larghezza ad altezza - + Page adjustement Correzioni di pagna - + Autoscroll down Autoscorri Giù - + Autoscroll up Autoscorri Sù - + Autoscroll forward, horizontal first Autoscorri avanti, priorità Orizzontale - + Autoscroll backward, horizontal first Autoscorri indietro, priorità Orizzontale - + Autoscroll forward, vertical first Autoscorri avanti, priorità Verticale - + Autoscroll backward, vertical first Autoscorri indietro, priorità Verticale - + Move down Muovi Giù - + Move up Muovi Sù - + Move left Muovi Sinistra - + Move right Muovi Destra - + Go to the first page Vai alla pagina iniziale - + Go to the last page Vai all'ultima pagina - + Offset double page to the left - + Offset double page to the right - + Reading Leggi - + There is a new version available Nuova versione disponibile - + Do you want to download the new version? Vuoi scaricare la nuova versione? - + Remind me in 14 days Ricordamelo in 14 giorni - + Not now Non ora @@ -1329,7 +1369,7 @@ YACReader::WhatsNewDialog - + Close Chiudi diff --git a/YACReader/yacreader_nl.ts b/YACReader/yacreader_nl.ts index 7ed5b7d34..30db16399 100644 --- a/YACReader/yacreader_nl.ts +++ b/YACReader/yacreader_nl.ts @@ -379,12 +379,12 @@ OptionsDialog - + Gamma Gamma - + Reset Standaardwaarden terugzetten @@ -394,112 +394,142 @@ Pad naar mijn strips - + Image adjustment Beeldaanpassing - + "Go to flow" size "Naar Omslagbrowser" afmetingen - + Choose Kies - + Image options Afbeelding opties - + Contrast Contrast - + Options Opties - + Comics directory Strips map - + Background color Achtergrondkleur - + Page Flow Omslagbrowser - + General Algemeen - + Brightness Helderheid - + Restart is needed Herstart is nodig - + Quick Navigation Mode - + + Display + + + + + Show time in current page information label + + + + Scroll behaviour - + Disable scroll animations and smooth scrolling - + Do not turn page using scroll - + Use single scroll step to turn page - + + Mouse mode + + + + + Only Back/Forward buttons can turn pages + + + + + Use the Left/Right buttons to turn pages. + + + + + Click left or right half of the screen to turn pages. + + + + Disable mouse over activation - + Fit options - + Enlarge images to fit width/height - + Double Page options - + Show covers as single page @@ -546,6 +576,16 @@ Fatal + + + Select custom cover + + + + + Images (%1) + + QsLogging::LogWindowModel @@ -607,12 +647,12 @@ Viewer - + Press 'O' to open comic. Druk 'O' om een strip te openen. - + Cover! Omslag! @@ -627,12 +667,12 @@ Niet gevonden - + Last page! Laatste pagina! - + Loading...please wait! Inladen...even wachten! @@ -647,7 +687,7 @@ - + Page not available! @@ -706,7 +746,7 @@ - + Save current page Bewaren huidige pagina @@ -717,8 +757,8 @@ - - + + Open previous comic Open de vorige strip @@ -729,8 +769,8 @@ - - + + Open next comic Open volgende strip @@ -741,8 +781,8 @@ - - + + Go to previous page Ga naar de vorige pagina @@ -753,8 +793,8 @@ - - + + Go to next page Ga naar de volgende pagina @@ -860,7 +900,7 @@ - + Help Help @@ -935,217 +975,217 @@ &Bestand - - + + Open recent - + File - + Edit - + View - + Go - + Window - - - + + + Open Comic Open een Strip - - - + + + Comic files Strip bestanden - + Open folder Open een Map - + page_%1.jpg pagina_%1.jpg - + Image files (*.jpg) Afbeelding bestanden (*.jpg) - + Comics - + Toggle fullscreen mode - + Hide/show toolbar - + General Algemeen - + Size up magnifying glass - + Size down magnifying glass - + Zoom in magnifying glass - + Zoom out magnifying glass - + Reset magnifying glass - + Magnifiying glass - + Toggle between fit to width and fit to height - + Page adjustement - + Autoscroll down - + Autoscroll up - + Autoscroll forward, horizontal first - + Autoscroll backward, horizontal first - + Autoscroll forward, vertical first - + Autoscroll backward, vertical first - + Move down - + Move up - + Move left - + Move right - + Go to the first page - + Go to the last page - + Offset double page to the left - + Offset double page to the right - + Reading - + There is a new version available Er is een nieuwe versie beschikbaar - + Do you want to download the new version? Wilt u de nieuwe versie downloaden? - + Remind me in 14 days - + Not now @@ -1153,7 +1193,7 @@ YACReader::WhatsNewDialog - + Close Sluiten diff --git a/YACReader/yacreader_pt.ts b/YACReader/yacreader_pt.ts index 1b4aed5f2..fce9c111e 100644 --- a/YACReader/yacreader_pt.ts +++ b/YACReader/yacreader_pt.ts @@ -344,122 +344,152 @@ Meu caminho de quadrinhos - + "Go to flow" size Tamanho do "Ir para cheia" - + Options Opções - + Comics directory Diretório de quadrinhos - + Restart is needed Reiniciar é necessário - + + Display + + + + + Show time in current page information label + + + + Background color - + Choose - + Scroll behaviour - + Disable scroll animations and smooth scrolling - + Do not turn page using scroll - + Use single scroll step to turn page - + + Mouse mode + + + + + Only Back/Forward buttons can turn pages + + + + + Use the Left/Right buttons to turn pages. + + + + + Click left or right half of the screen to turn pages. + + + + Quick Navigation Mode - + Disable mouse over activation - + Brightness - + Contrast - + Gamma - + Reset - + Image options - + Fit options - + Enlarge images to fit width/height - + Double Page options - + Show covers as single page - + General - + Page Flow - + Image adjustment @@ -506,6 +536,16 @@ Fatal + + + Select custom cover + + + + + Images (%1) + + QsLogging::LogWindowModel @@ -563,12 +603,12 @@ Viewer - + Press 'O' to open comic. Pressione 'O' para abrir um quadrinho. - + Loading...please wait! Carregando... por favor, aguarde! @@ -593,17 +633,17 @@ - + Page not available! - + Cover! - + Last page! @@ -662,7 +702,7 @@ - + Save current page Salvar página atual @@ -673,8 +713,8 @@ - - + + Open previous comic Abrir quadrinho anterior @@ -685,8 +725,8 @@ - - + + Open next comic Abrir próximo quadrinho @@ -697,8 +737,8 @@ - - + + Go to previous page Ir para a página anterior @@ -709,8 +749,8 @@ - - + + Go to next page Ir para a próxima página @@ -816,7 +856,7 @@ - + Help Ajuda @@ -891,217 +931,217 @@ &Arquivo - - + + Open recent - + File - + Edit - + View - + Go - + Window - - - + + + Open Comic Abrir Quadrinho - - - + + + Comic files - + Open folder Abrir pasta - + page_%1.jpg - + Image files (*.jpg) Arquivos de imagem (*.jpg) - + Comics - + Toggle fullscreen mode - + Hide/show toolbar - + General - + Size up magnifying glass - + Size down magnifying glass - + Zoom in magnifying glass - + Zoom out magnifying glass - + Reset magnifying glass - + Magnifiying glass - + Toggle between fit to width and fit to height - + Page adjustement - + Autoscroll down - + Autoscroll up - + Autoscroll forward, horizontal first - + Autoscroll backward, horizontal first - + Autoscroll forward, vertical first - + Autoscroll backward, vertical first - + Move down - + Move up - + Move left - + Move right - + Go to the first page - + Go to the last page - + Offset double page to the left - + Offset double page to the right - + Reading - + There is a new version available Há uma nova versão disponível - + Do you want to download the new version? Você deseja baixar a nova versão? - + Remind me in 14 days - + Not now @@ -1109,7 +1149,7 @@ YACReader::WhatsNewDialog - + Close Fechar diff --git a/YACReader/yacreader_ru.ts b/YACReader/yacreader_ru.ts index b758a26a5..875b78020 100644 --- a/YACReader/yacreader_ru.ts +++ b/YACReader/yacreader_ru.ts @@ -555,12 +555,12 @@ OptionsDialog - + Gamma Гамма - + Reset Вернуть к первоначальным значениям @@ -570,112 +570,142 @@ Папка комиксов - + Image adjustment Настройка изображения - + "Go to flow" size Размер потока страниц - + Choose Выбрать - + Image options Настройки изображения - + Contrast Контраст - + Options Настройки - + Comics directory Папка комиксов - + Quick Navigation Mode Ползунок для быстрой навигации по страницам - + + Display + + + + + Show time in current page information label + + + + Background color Фоновый цвет - + Scroll behaviour - + Disable scroll animations and smooth scrolling - + Do not turn page using scroll - + Use single scroll step to turn page - + + Mouse mode + + + + + Only Back/Forward buttons can turn pages + + + + + Use the Left/Right buttons to turn pages. + + + + + Click left or right half of the screen to turn pages. + + + + Disable mouse over activation Отключить активацию потока при наведении мыши - + Page Flow Поток Страниц - + General Общие - + Brightness Яркость - + Restart is needed - + Fit options - + Enlarge images to fit width/height - + Double Page options - + Show covers as single page @@ -722,6 +752,16 @@ Fatal + + + Select custom cover + + + + + Images (%1) + + QsLogging::LogWindowModel @@ -782,13 +822,13 @@ Viewer - + Page not available! Страница недоступна! - + Press 'O' to open comic. Нажмите "O" чтобы открыть комикс. @@ -798,7 +838,7 @@ Ошибка открытия комикса - + Cover! Начало! @@ -818,12 +858,12 @@ Не найдено - + Last page! Конец! - + Loading...please wait! Загрузка... Пожалуйста подождите! @@ -882,7 +922,7 @@ - + Save current page Сохранить текущию страницу @@ -893,8 +933,8 @@ - - + + Open previous comic Открыть предыдуший комикс @@ -905,8 +945,8 @@ - - + + Open next comic Открыть следующий комикс @@ -917,8 +957,8 @@ - - + + Go to previous page Перейти к предыдущей странице @@ -929,8 +969,8 @@ - - + + Go to next page Перейти к следующей странице @@ -1036,7 +1076,7 @@ - + Help Справка @@ -1111,217 +1151,217 @@ &Отображать панель инструментов - - + + Open recent Открыть недавние - + File Файл - + Edit Редактировать - + View Посмотреть - + Go Перейти - + Window Окно - - - + + + Open Comic Открыть комикс - - - + + + Comic files Файлы комикса - + Open folder Открыть папку - + page_%1.jpg страница_%1.jpg - + Image files (*.jpg) Файлы изображений (*.jpg) - + Comics Комикс - + Toggle fullscreen mode Полноэкранный режим включить/выключить - + Hide/show toolbar Показать/скрыть панель инструментов - + General Общие - + Size up magnifying glass Увеличение размера окошка увеличительного стекла - + Size down magnifying glass Уменьшение размера окошка увеличительного стекла - + Zoom in magnifying glass Увеличить - + Zoom out magnifying glass Уменьшить - + Reset magnifying glass - + Magnifiying glass Увеличительное стекло - + Toggle between fit to width and fit to height Переключение режима подгонки страницы по ширине/высоте - + Page adjustement Настройка страницы - + Autoscroll down Автопрокрутка вниз - + Autoscroll up Автопрокрутка вверх - + Autoscroll forward, horizontal first Автопрокрутка вперед, горизонтальная - + Autoscroll backward, horizontal first Автопрокрутка назад, горизонтальная - + Autoscroll forward, vertical first Автопрокрутка вперед, вертикальная - + Autoscroll backward, vertical first Автопрокрутка назад, вертикальная - + Move down Переместить вниз - + Move up Переместить вверх - + Move left Переместить влево - + Move right Переместить вправо - + Go to the first page Перейти к первой странице - + Go to the last page Перейти к последней странице - + Offset double page to the left - + Offset double page to the right - + Reading Чтение - + There is a new version available Доступна новая версия - + Do you want to download the new version? Хотите загрузить новую версию ? - + Remind me in 14 days Напомнить через 14 дней - + Not now Не сейчас @@ -1329,7 +1369,7 @@ YACReader::WhatsNewDialog - + Close Закрыть diff --git a/YACReader/yacreader_tr.ts b/YACReader/yacreader_tr.ts index 005d69b71..105ce7f30 100644 --- a/YACReader/yacreader_tr.ts +++ b/YACReader/yacreader_tr.ts @@ -559,12 +559,12 @@ OptionsDialog - + Gamma Gama - + Reset Yeniden başlat @@ -574,112 +574,142 @@ Çizgi Romanlarım - + Image adjustment Resim ayarları - + "Go to flow" size Akış görünümüne git - + Choose Seç - + Image options Sayfa ayarları - + Contrast Kontrast - + Options Ayarlar - + Comics directory Çizgi roman konumu - + Background color Arka plan rengi - + Page Flow Sayfa akışı - + General Genel - + Brightness Parlaklık - + Restart is needed Yeniden başlatılmalı - + Quick Navigation Mode Hızlı Gezinti Kipi - + + Display + + + + + Show time in current page information label + + + + Scroll behaviour - + Disable scroll animations and smooth scrolling - + Do not turn page using scroll - + Use single scroll step to turn page - + + Mouse mode + + + + + Only Back/Forward buttons can turn pages + + + + + Use the Left/Right buttons to turn pages. + + + + + Click left or right half of the screen to turn pages. + + + + Disable mouse over activation Etkinleştirme üzerinde fareyi devre dışı bırak - + Fit options Sığdırma seçenekleri - + Enlarge images to fit width/height Genişliğe/yüksekliği sığmaları için resimleri genişlet - + Double Page options Çift Sayfa seçenekleri - + Show covers as single page Kapakları tek sayfa olarak göster @@ -726,6 +756,16 @@ Fatal Ölümcül + + + Select custom cover + + + + + Images (%1) + + QsLogging::LogWindowModel @@ -787,12 +827,12 @@ Viewer - + Press 'O' to open comic. 'O'ya basarak aç. - + Cover! Kapak! @@ -807,12 +847,12 @@ Bulunamadı - + Last page! Son sayfa! - + Loading...please wait! Yükleniyor... lütfen bekleyin! @@ -827,7 +867,7 @@ CRC Hatası - + Page not available! Sayfa bulunamadı! @@ -886,7 +926,7 @@ - + Save current page Geçerli sayfayı kaydet @@ -897,8 +937,8 @@ - - + + Open previous comic Önceki çizgi romanı aç @@ -909,8 +949,8 @@ - - + + Open next comic Sıradaki çizgi romanı aç @@ -921,8 +961,8 @@ - - + + Go to previous page Önceki sayfaya dön @@ -933,8 +973,8 @@ - - + + Go to next page Sonra ki sayfaya geç @@ -1040,7 +1080,7 @@ - + Help Yardım @@ -1115,217 +1155,217 @@ &Dosya - - + + Open recent Son dosyaları aç - + File Dosya - + Edit Düzen - + View Görünüm - + Go Git - + Window Pencere - - - + + + Open Comic Çizgi Romanı Aç - - - + + + Comic files Çizgi Roman Dosyaları - + Open folder Dosyayı aç - + page_%1.jpg sayfa_%1.jpg - + Image files (*.jpg) Resim dosyaları (*.jpg) - + Comics Çizgi Roman - + Toggle fullscreen mode Tam ekran kipini aç/kapat - + Hide/show toolbar Araç çubuğunu göster/gizle - + General Genel - + Size up magnifying glass Büyüteci büyüt - + Size down magnifying glass Büyüteci küçült - + Zoom in magnifying glass Büyüteci yakınlaştır - + Zoom out magnifying glass Büyüteci uzaklaştır - + Reset magnifying glass - + Magnifiying glass Büyüteç - + Toggle between fit to width and fit to height Genişliğe sığdır ile yüksekliğe sığdır arasında geçiş yap - + Page adjustement Sayfa ayarı - + Autoscroll down Otomatik aşağı kaydır - + Autoscroll up Otomatik yukarı kaydır - + Autoscroll forward, horizontal first Otomatik ileri kaydır, önce yatay - + Autoscroll backward, horizontal first Otomatik geri kaydır, önce yatay - + Autoscroll forward, vertical first Otomatik ileri kaydır, önce dikey - + Autoscroll backward, vertical first Otomatik geri kaydır, önce dikey - + Move down Aşağı git - + Move up Yukarı git - + Move left Sola git - + Move right Sağa git - + Go to the first page İlk sayfaya git - + Go to the last page En son sayfaya git - + Offset double page to the left - + Offset double page to the right - + Reading Okuma - + There is a new version available Yeni versiyon mevcut - + Do you want to download the new version? Yeni versiyonu indirmek ister misin ? - + Remind me in 14 days 14 gün içinde hatırlat - + Not now Şimdi değil @@ -1333,7 +1373,7 @@ YACReader::WhatsNewDialog - + Close Kapat diff --git a/YACReader/yacreader_zh_CN.ts b/YACReader/yacreader_zh_CN.ts index f0956755f..713495301 100644 --- a/YACReader/yacreader_zh_CN.ts +++ b/YACReader/yacreader_zh_CN.ts @@ -180,27 +180,27 @@ OptionsDialog - + Gamma Gamma值 - + Reset 重置 - + Enlarge images to fit width/height 放大图片以适应宽度/高度 - + Disable scroll animations and smooth scrolling 禁用滚动动画和平滑滚动 - + Use single scroll step to turn page 使用单滚动步骤翻页 @@ -210,97 +210,127 @@ 我的漫画路径 - + Image adjustment 图像调整 - + "Go to flow" size 页面流尺寸 - + Choose 选择 - + Show covers as single page 显示封面为单页 - + Do not turn page using scroll 滚动时不翻页 - + Fit options 适应项 - + Image options 图片选项 - + + Display + + + + + Show time in current page information label + + + + + Mouse mode + + + + + Only Back/Forward buttons can turn pages + + + + + Use the Left/Right buttons to turn pages. + + + + + Click left or right half of the screen to turn pages. + + + + Contrast 对比度 - + Options 选项 - + Comics directory 漫画目录 - + Quick Navigation Mode 快速导航模式 - + Background color 背景颜色 - + Double Page options 双页选项 - + Scroll behaviour 滚动效果 - + Disable mouse over activation 禁用鼠标激活 - + Page Flow 页面流 - + General 常规 - + Brightness 亮度 - + Restart is needed 需要重启 @@ -347,6 +377,16 @@ Warning 警告 + + + Select custom cover + + + + + Images (%1) + + QsLogging::LogWindowModel @@ -392,13 +432,13 @@ Viewer - + Page not available! 页面不可用! - + Press 'O' to open comic. 按下 'O' 以打开漫画. @@ -408,7 +448,7 @@ 打开漫画时发生错误 - + Cover! 封面! @@ -428,12 +468,12 @@ 未找到 - + Last page! 尾页! - + Loading...please wait! 载入中... 请稍候! @@ -441,23 +481,23 @@ YACReader::MainWindowViewer - + Go 转到 - + Edit 编辑 - + File 文件 - + Help 帮助 @@ -467,7 +507,7 @@ 保存 - + View 查看 @@ -497,9 +537,9 @@ 关闭 - - - + + + Open Comic 打开漫画 @@ -524,12 +564,12 @@ 打开图片文件夹 - + Size down magnifying glass 减小放大镜尺寸 - + Zoom out magnifying glass 减小缩放级别 @@ -544,7 +584,7 @@ 打开最近的漫画 - + Autoscroll up 向上自动滚动 @@ -554,12 +594,12 @@ 设置书签 - + page_%1.jpg page_%1.jpg - + Autoscroll forward, vertical first 向前自动滚动,垂直优先 @@ -570,12 +610,12 @@ - + Save current page 保存当前页面 - + Size up magnifying glass 增大放大镜尺寸 @@ -585,7 +625,7 @@ 双页模式 - + Move up 向上移动 @@ -600,12 +640,12 @@ 打开文件夹 - + Comics 漫画 - + Offset double page to the right 双页向右偏移 @@ -615,36 +655,36 @@ 适应高度 - + Autoscroll backward, vertical first 向后自动滚动,垂直优先 - - - + + + Comic files 漫画文件 - + Not now 现在不 - + Go to the first page 转到第一页 - - + + Go to previous page 转至上一页 - + Window 窗口 @@ -659,7 +699,7 @@ 打开漫画 - + Image files (*.jpg) 图像文件 (*.jpg) @@ -684,7 +724,7 @@ 显示信息 - + Open folder 打开文件夹 @@ -694,7 +734,7 @@ 跳转至页面 ... - + Magnifiying glass 放大镜 @@ -704,38 +744,38 @@ 缩放图片以适应宽度 - + Toggle fullscreen mode 切换全屏模式 - + Toggle between fit to width and fit to height 切换显示为"适应宽度"或"适应高度" - + Move right 向右移动 - + Zoom in magnifying glass 增大缩放级别 - - + + Open recent 最近打开的文件 - + Offset double page to the left 双页向左偏移 - + Reading 阅读 @@ -745,14 +785,14 @@ 上一页(&P) - + Autoscroll forward, horizontal first 向前自动滚动,水平优先 - - + + Go to next page 转至下一页 @@ -767,24 +807,24 @@ 双页漫画模式 - + There is a new version available 有新版本可用 - + Autoscroll down 向下自动滚动 - - + + Open next comic 打开下一个漫画 - + Remind me in 14 days 14天后提醒我 @@ -800,8 +840,8 @@ - - + + Open previous comic 打开上一个漫画 @@ -831,17 +871,17 @@ 显示字典 - + Reset magnifying glass - + Move down 向下移动 - + Move left 向左移动 @@ -881,7 +921,7 @@ 显示全尺寸 - + Hide/show toolbar 隐藏/显示 工具栏 @@ -896,7 +936,7 @@ 编辑快捷键 - + General 常规 @@ -906,7 +946,7 @@ 在当前页面设置书签 - + Page adjustement 页面调整 @@ -916,12 +956,12 @@ 显示缩放滑块 - + Go to the last page 转到最后一页 - + Do you want to download the new version? 你要下载新版本吗? @@ -931,7 +971,7 @@ 向右旋转图片 - + Autoscroll backward, horizontal first 向后自动滚动,水平优先 @@ -939,7 +979,7 @@ YACReader::WhatsNewDialog - + Close 关闭 diff --git a/YACReader/yacreader_zh_HK.ts b/YACReader/yacreader_zh_HK.ts index 3fdfa8f68..fc14d3407 100644 --- a/YACReader/yacreader_zh_HK.ts +++ b/YACReader/yacreader_zh_HK.ts @@ -180,7 +180,7 @@ OptionsDialog - + "Go to flow" size 頁面流尺寸 @@ -190,117 +190,147 @@ 我的漫畫路徑 - + Background color 背景顏色 - + Choose 選擇 - + Quick Navigation Mode 快速導航模式 - + Disable mouse over activation 禁用滑鼠啟動 - + Restart is needed 需要重啟 - + Brightness 亮度 - + + Display + + + + + Show time in current page information label + + + + Scroll behaviour 滾動效果 - + Disable scroll animations and smooth scrolling - + Do not turn page using scroll 滾動時不翻頁 - + Use single scroll step to turn page 使用單滾動步驟翻頁 - + + Mouse mode + + + + + Only Back/Forward buttons can turn pages + + + + + Use the Left/Right buttons to turn pages. + + + + + Click left or right half of the screen to turn pages. + + + + Contrast 對比度 - + Gamma Gamma值 - + Reset 重置 - + Image options 圖片選項 - + Fit options 適應項 - + Enlarge images to fit width/height 放大圖片以適應寬度/高度 - + Double Page options 雙頁選項 - + Show covers as single page 顯示封面為單頁 - + General 常規 - + Page Flow 頁面流 - + Image adjustment 圖像調整 - + Options 選項 - + Comics directory 漫畫目錄 @@ -347,6 +377,16 @@ Fatal 嚴重錯誤 + + + Select custom cover + + + + + Images (%1) + + QsLogging::LogWindowModel @@ -408,7 +448,7 @@ Viewer - + Press 'O' to open comic. 按下 'O' 以打開漫畫. @@ -433,22 +473,22 @@ CRC 校驗失敗 - + Loading...please wait! 載入中... 請稍候! - + Page not available! 頁面不可用! - + Cover! 封面! - + Last page! 尾頁! @@ -507,7 +547,7 @@ - + Save current page 保存當前頁面 @@ -518,8 +558,8 @@ - - + + Open previous comic 打開上一個漫畫 @@ -530,8 +570,8 @@ - - + + Open next comic 打開下一個漫畫 @@ -542,8 +582,8 @@ - - + + Go to previous page 轉至上一頁 @@ -554,8 +594,8 @@ - - + + Go to next page 轉至下一頁 @@ -661,7 +701,7 @@ - + Help 幫助 @@ -736,217 +776,217 @@ 檔(&F) - - + + Open recent 最近打開的檔 - + File - + Edit 編輯 - + View 查看 - + Go 轉到 - + Window 窗口 - - - + + + Open Comic 打開漫畫 - - - + + + Comic files 漫畫檔 - + Open folder 打開檔夾 - + page_%1.jpg page_%1.jpg - + Image files (*.jpg) 圖像檔 (*.jpg) - + Comics 漫畫 - + Toggle fullscreen mode 切換全屏模式 - + Hide/show toolbar 隱藏/顯示 工具欄 - + General 常規 - + Size up magnifying glass 增大放大鏡尺寸 - + Size down magnifying glass 減小放大鏡尺寸 - + Zoom in magnifying glass 增大縮放級別 - + Zoom out magnifying glass 減小縮放級別 - + Reset magnifying glass - + Magnifiying glass 放大鏡 - + Toggle between fit to width and fit to height 切換顯示為"適應寬度"或"適應高度" - + Page adjustement 頁面調整 - + Autoscroll down 向下自動滾動 - + Autoscroll up 向上自動滾動 - + Autoscroll forward, horizontal first 向前自動滾動,水準優先 - + Autoscroll backward, horizontal first 向後自動滾動,水準優先 - + Autoscroll forward, vertical first 向前自動滾動,垂直優先 - + Autoscroll backward, vertical first 向後自動滾動,垂直優先 - + Move down 向下移動 - + Move up 向上移動 - + Move left 向左移動 - + Move right 向右移動 - + Go to the first page 轉到第一頁 - + Go to the last page 轉到最後一頁 - + Offset double page to the left - + Offset double page to the right - + Reading 閱讀 - + There is a new version available 有新版本可用 - + Do you want to download the new version? 你要下載新版本嗎? - + Remind me in 14 days 14天後提醒我 - + Not now 現在不 @@ -954,7 +994,7 @@ YACReader::WhatsNewDialog - + Close 關閉 diff --git a/YACReader/yacreader_zh_TW.ts b/YACReader/yacreader_zh_TW.ts index 64383381d..4745cbaff 100644 --- a/YACReader/yacreader_zh_TW.ts +++ b/YACReader/yacreader_zh_TW.ts @@ -180,7 +180,7 @@ OptionsDialog - + "Go to flow" size 頁面流尺寸 @@ -190,117 +190,147 @@ 我的漫畫路徑 - + Background color 背景顏色 - + Choose 選擇 - + Quick Navigation Mode 快速導航模式 - + Disable mouse over activation 禁用滑鼠啟動 - + Restart is needed 需要重啟 - + Brightness 亮度 - + + Display + + + + + Show time in current page information label + + + + Scroll behaviour 滾動效果 - + Disable scroll animations and smooth scrolling - + Do not turn page using scroll 滾動時不翻頁 - + Use single scroll step to turn page 使用單滾動步驟翻頁 - + + Mouse mode + + + + + Only Back/Forward buttons can turn pages + + + + + Use the Left/Right buttons to turn pages. + + + + + Click left or right half of the screen to turn pages. + + + + Contrast 對比度 - + Gamma Gamma值 - + Reset 重置 - + Image options 圖片選項 - + Fit options 適應項 - + Enlarge images to fit width/height 放大圖片以適應寬度/高度 - + Double Page options 雙頁選項 - + Show covers as single page 顯示封面為單頁 - + General 常規 - + Page Flow 頁面流 - + Image adjustment 圖像調整 - + Options 選項 - + Comics directory 漫畫目錄 @@ -347,6 +377,16 @@ Fatal 嚴重錯誤 + + + Select custom cover + + + + + Images (%1) + + QsLogging::LogWindowModel @@ -408,7 +448,7 @@ Viewer - + Press 'O' to open comic. 按下 'O' 以打開漫畫. @@ -433,22 +473,22 @@ CRC 校驗失敗 - + Loading...please wait! 載入中... 請稍候! - + Page not available! 頁面不可用! - + Cover! 封面! - + Last page! 尾頁! @@ -507,7 +547,7 @@ - + Save current page 保存當前頁面 @@ -518,8 +558,8 @@ - - + + Open previous comic 打開上一個漫畫 @@ -530,8 +570,8 @@ - - + + Open next comic 打開下一個漫畫 @@ -542,8 +582,8 @@ - - + + Go to previous page 轉至上一頁 @@ -554,8 +594,8 @@ - - + + Go to next page 轉至下一頁 @@ -661,7 +701,7 @@ - + Help 幫助 @@ -736,217 +776,217 @@ 檔(&F) - - + + Open recent 最近打開的檔 - + File - + Edit 編輯 - + View 查看 - + Go 轉到 - + Window 窗口 - - - + + + Open Comic 打開漫畫 - - - + + + Comic files 漫畫檔 - + Open folder 打開檔夾 - + page_%1.jpg page_%1.jpg - + Image files (*.jpg) 圖像檔 (*.jpg) - + Comics 漫畫 - + Toggle fullscreen mode 切換全屏模式 - + Hide/show toolbar 隱藏/顯示 工具欄 - + General 常規 - + Size up magnifying glass 增大放大鏡尺寸 - + Size down magnifying glass 減小放大鏡尺寸 - + Zoom in magnifying glass 增大縮放級別 - + Zoom out magnifying glass 減小縮放級別 - + Reset magnifying glass - + Magnifiying glass 放大鏡 - + Toggle between fit to width and fit to height 切換顯示為"適應寬度"或"適應高度" - + Page adjustement 頁面調整 - + Autoscroll down 向下自動滾動 - + Autoscroll up 向上自動滾動 - + Autoscroll forward, horizontal first 向前自動滾動,水準優先 - + Autoscroll backward, horizontal first 向後自動滾動,水準優先 - + Autoscroll forward, vertical first 向前自動滾動,垂直優先 - + Autoscroll backward, vertical first 向後自動滾動,垂直優先 - + Move down 向下移動 - + Move up 向上移動 - + Move left 向左移動 - + Move right 向右移動 - + Go to the first page 轉到第一頁 - + Go to the last page 轉到最後一頁 - + Offset double page to the left - + Offset double page to the right - + Reading 閱讀 - + There is a new version available 有新版本可用 - + Do you want to download the new version? 你要下載新版本嗎? - + Remind me in 14 days 14天後提醒我 - + Not now 現在不 @@ -954,7 +994,7 @@ YACReader::WhatsNewDialog - + Close 關閉 diff --git a/YACReaderLibrary/YACReaderLibrary.pro b/YACReaderLibrary/YACReaderLibrary.pro index d4ebf6d80..2330a7825 100644 --- a/YACReaderLibrary/YACReaderLibrary.pro +++ b/YACReaderLibrary/YACReaderLibrary.pro @@ -74,6 +74,7 @@ greaterThan(QT_MAJOR_VERSION, 5): QT += openglwidgets core5compat # Input HEADERS += comic_flow.h \ ../common/concurrent_queue.h \ + ../common/cover_utils.h \ create_library_dialog.h \ db/comic_query_result_processor.h \ db/folder_query_result_processor.h \ @@ -163,6 +164,7 @@ HEADERS += comic_flow.h \ SOURCES += comic_flow.cpp \ ../common/concurrent_queue.cpp \ + ../common/cover_utils.cpp \ create_library_dialog.cpp \ db/comic_query_result_processor.cpp \ db/folder_query_result_processor.cpp \ diff --git a/YACReaderLibrary/comic_vine/comic_vine.pri b/YACReaderLibrary/comic_vine/comic_vine.pri index 1c84f9385..978360233 100644 --- a/YACReaderLibrary/comic_vine/comic_vine.pri +++ b/YACReaderLibrary/comic_vine/comic_vine.pri @@ -1,6 +1,9 @@ HEADERS += \ $$PWD/comic_vine_json_parser.h \ + $$PWD/model/selected_volume_info.h \ + $$PWD/model/volume_search_query.h \ + $$PWD/scraper_checkbox.h \ comic_vine/comic_vine_dialog.h \ comic_vine/comic_vine_client.h \ comic_vine/scraper_lineedit.h \ @@ -20,12 +23,12 @@ HEADERS += \ comic_vine/model/volume_comics_model.h \ comic_vine/scraper_scroll_label.h \ comic_vine/scraper_results_paginator.h \ - comic_vine/scraper_selector.h \ comic_vine/api_key_dialog.h \ $$PWD/comic_vine_all_volume_comics_retriever.h SOURCES += \ $$PWD/comic_vine_json_parser.cpp \ + $$PWD/scraper_checkbox.cpp \ comic_vine/comic_vine_dialog.cpp \ comic_vine/comic_vine_client.cpp \ comic_vine/scraper_lineedit.cpp \ @@ -45,6 +48,5 @@ SOURCES += \ comic_vine/model/volume_comics_model.cpp \ comic_vine/scraper_scroll_label.cpp \ comic_vine/scraper_results_paginator.cpp \ - comic_vine/scraper_selector.cpp \ comic_vine/api_key_dialog.cpp \ $$PWD/comic_vine_all_volume_comics_retriever.cpp diff --git a/YACReaderLibrary/comic_vine/comic_vine_client.cpp b/YACReaderLibrary/comic_vine/comic_vine_client.cpp index 10bae8fe6..75e2dde94 100644 --- a/YACReaderLibrary/comic_vine/comic_vine_client.cpp +++ b/YACReaderLibrary/comic_vine/comic_vine_client.cpp @@ -18,6 +18,13 @@ static const QString CV_SEARCH = CV_WEB_ADDRESS + "/search/?api_key=" + CV_API_K "&query=%1&page=%2"; // http://www.comicvine.com/api/search/?api_key=46680bebb358f1de690a5a365e15d325f9649f91&format=json&limit=100&resources=volume&field_list=name,start_year,publisher,id,image,count_of_issues,deck&query=superman +// look for exact match volumes +static const QString CV_EXACT_VOLUME_SEARCH = CV_WEB_ADDRESS + "/volumes/?api_key=" + CV_API_KEY + + "&format=json&limit=100" + "&field_list=name,start_year,publisher,id,image,count_of_issues,deck" + "&sort=name:asc" + "&filter=name:%1&offset=%2"; + // gets the detail for a volume %1 static const QString CV_SERIES_DETAIL = CV_WEB_ADDRESS + "/volume/4050-%1/?api_key=" + CV_API_KEY + "&format=json&field_list=name,start_year,publisher,image,count_of_issues,id,description"; @@ -69,6 +76,17 @@ void ComicVineClient::search(const QString &query, int page) connect(search, &QThread::finished, search, &QObject::deleteLater); search->get(); } + +// CV_EXACT_VOLUME_SEARCH +void ComicVineClient::searchExactVolume(const QString &query, int page) +{ + HttpWorker *search = new HttpWorker(QString(CV_EXACT_VOLUME_SEARCH).replace(CV_WEB_ADDRESS, baseURL).replace(CV_API_KEY, settings->value(COMIC_VINE_API_KEY, CV_API_KEY_DEFAULT).toString()).arg(query).arg((page - 1) * 100)); + connect(search, &HttpWorker::dataReady, this, &ComicVineClient::proccessVolumesSearchData); + connect(search, &HttpWorker::timeout, this, &ComicVineClient::timeOut); + connect(search, &QThread::finished, search, &QObject::deleteLater); + search->get(); +} + // CV_SEARCH result void ComicVineClient::proccessVolumesSearchData(const QByteArray &data) { @@ -120,7 +138,7 @@ void ComicVineClient::getSeriesCover(const QString &url) // CV_COMIC_IDS void ComicVineClient::getVolumeComicsInfo(const QString &idVolume, int page) { - HttpWorker *search = new HttpWorker(QString(CV_COMICS_INFO).replace(CV_WEB_ADDRESS, baseURL).replace(CV_API_KEY, settings->value(COMIC_VINE_API_KEY, CV_API_KEY_DEFAULT).toString()).arg(idVolume).arg((page - 1) * 100)); // page doesn't work for search, using offset instead + HttpWorker *search = new HttpWorker(QString(CV_COMICS_INFO).replace(CV_WEB_ADDRESS, baseURL).replace(CV_API_KEY, settings->value(COMIC_VINE_API_KEY, CV_API_KEY_DEFAULT).toString()).arg(idVolume).arg((page - 1) * 100)); connect(search, &HttpWorker::dataReady, this, &ComicVineClient::processVolumeComicsInfo); connect(search, &HttpWorker::timeout, this, &ComicVineClient::timeOut); // TODO connect(search, &QThread::finished, search, &QObject::deleteLater); diff --git a/YACReaderLibrary/comic_vine/comic_vine_client.h b/YACReaderLibrary/comic_vine/comic_vine_client.h index 6b36806e4..43c4ba5b0 100644 --- a/YACReaderLibrary/comic_vine/comic_vine_client.h +++ b/YACReaderLibrary/comic_vine/comic_vine_client.h @@ -24,6 +24,7 @@ class ComicVineClient : public QObject void finished(); public slots: void search(const QString &query, int page = 1); + void searchExactVolume(const QString &query, int page = 1); void getSeriesDetail(const QString &id); void getSeriesCover(const QString &url); void getVolumeComicsInfo(const QString &idVolume, int page = 1); diff --git a/YACReaderLibrary/comic_vine/comic_vine_dialog.cpp b/YACReaderLibrary/comic_vine/comic_vine_dialog.cpp index 025b9b768..1faa0aae1 100644 --- a/YACReaderLibrary/comic_vine/comic_vine_dialog.cpp +++ b/YACReaderLibrary/comic_vine/comic_vine_dialog.cpp @@ -22,6 +22,7 @@ #include "search_volume.h" #include "select_comic.h" #include "select_volume.h" +#include "selected_volume_info.h" #include "sort_volume_comics.h" #include "db_helper.h" #include "response_parser.h" @@ -93,7 +94,7 @@ void ComicVineDialog::doLayout() setLayout(mainLayout); - setWindowTitle("Comic Vine Scraper (beta)"); + setWindowTitle("Comic Vine Scraper"); } void ComicVineDialog::doStackedWidgets() @@ -115,9 +116,9 @@ void ComicVineDialog::doConnections() connect(searchButton, &QAbstractButton::clicked, this, &ComicVineDialog::search); connect(skipButton, &QAbstractButton::clicked, this, &ComicVineDialog::goToNextComic); - connect(selectVolumeWidget, &ScraperSelector::loadPage, this, &ComicVineDialog::searchVolume); - connect(selectComicWidget, &ScraperSelector::loadPage, this, &ComicVineDialog::getVolumeComicsInfo); - connect(sortVolumeComicsWidget, &ScraperSelector::loadPage, this, &ComicVineDialog::getVolumeComicsInfo); + connect(selectVolumeWidget, &SelectVolume::loadPage, this, &ComicVineDialog::searchVolume); + connect(selectComicWidget, &SelectComic::loadPage, this, &ComicVineDialog::getVolumeComicsInfo); + connect(sortVolumeComicsWidget, &SortVolumeComics::loadPage, this, &ComicVineDialog::getVolumeComicsInfo); connect(this, &QDialog::accepted, this, &QWidget::close, Qt::QueuedConnection); } @@ -130,21 +131,22 @@ void ComicVineDialog::goNext() if (content->currentWidget() == seriesQuestionWidget) { if (seriesQuestionWidget->getYes()) { QString volumeSearchString = comics[0].getParentFolderName(); - mode = Volume; + mode = ScraperMode::Volume; showSearchVolume(volumeSearchString); } else { - status = AutoSearching; - mode = SingleComicInList; + status = ScraperStatus::AutoSearching; + mode = ScraperMode::SingleComicInList; ComicDB comic = comics[currentIndex]; QString title = comic.getTitleOrFileName(); titleHeader->setSubTitle(tr("comic %1 of %2 - %3").arg(currentIndex + 1).arg(comics.length()).arg(title)); showLoading(tr("Looking for volume...")); - searchVolume(title); + + searchVolume({ volumeSearchStringFromComic(comic), 1, true }); } } else if (content->currentWidget() == selectVolumeWidget) { - currentVolumeId = selectVolumeWidget->getSelectedVolumeId(); + currentVolumeId = selectVolumeWidget->getSelectedVolumeInfo().id; getVolumeComicsInfo(currentVolumeId); } else if (content->currentWidget() == sortVolumeComicsWidget) { @@ -152,24 +154,23 @@ void ComicVineDialog::goNext() // ComicDB-ComicVineID QList> matchingInfo = sortVolumeComicsWidget->getMatchingInfo(); - int count = selectVolumeWidget->getSelectedVolumeNumIssues(); - QString publisher = selectVolumeWidget->getSelectedVolumePublisher(); + auto volumeInfo = selectVolumeWidget->getSelectedVolumeInfo(); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - QtConcurrent::run(&ComicVineDialog::getComicsInfo, this, matchingInfo, count, publisher); + QtConcurrent::run(&ComicVineDialog::getComicsInfo, this, matchingInfo, volumeInfo); #else - QtConcurrent::run(this, &ComicVineDialog::getComicsInfo, matchingInfo, count, publisher); + QtConcurrent::run(this, &ComicVineDialog::getComicsInfo, matchingInfo, volumeInfo); #endif } else if (content->currentWidget() == selectComicWidget) { showLoading(); QString comicId = selectComicWidget->getSelectedComicId(); - int count = selectVolumeWidget->getSelectedVolumeNumIssues(); - QString publisher = selectVolumeWidget->getSelectedVolumePublisher(); + auto volumeInfo = selectVolumeWidget->getSelectedVolumeInfo(); + #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - QtConcurrent::run(&ComicVineDialog::getComicInfo, this, comicId, count, publisher); + QtConcurrent::run(&ComicVineDialog::getComicInfo, this, comicId, volumeInfo); #else - QtConcurrent::run(this, &ComicVineDialog::getComicInfo, comicId, count, publisher); + QtConcurrent::run(this, &ComicVineDialog::getComicInfo, comicId, volumeInfo); #endif } } @@ -179,30 +180,35 @@ void ComicVineDialog::goBack() clearState(); switch (status) { - case SelectingSeries: - if (mode == Volume) - showSearchVolume(); + case ScraperStatus::SelectingSeries: + if (mode == ScraperMode::Volume) + showSearchVolume(currentVolumeSearchQuery.volume); else - showSearchSingleComic(); + showSearchSingleComic(currentVolumeSearchQuery.volume); break; - case SortingComics: + case ScraperStatus::SortingComics: showSelectVolume(); break; - case SelectingComic: - if (mode == SingleComic) + case ScraperStatus::SelectingComic: + if (mode == ScraperMode::SingleComic) showSelectVolume(); break; - case AutoSearching: - if (mode == Volume) - showSearchVolume(); + case ScraperStatus::AutoSearching: + if (mode == ScraperMode::Volume) + showSearchVolume(currentVolumeSearchQuery.volume); else - showSearchSingleComic(); + showSearchSingleComic(currentVolumeSearchQuery.volume); break; - default: - if (mode == Volume) - showSearchVolume(); + + case ScraperStatus::AskingForInfo: + case ScraperStatus::SearchingSingleComic: + case ScraperStatus::SearchingVolume: + case ScraperStatus::SearchingExactVolume: + case ScraperStatus::GettingVolumeComics: + if (mode == ScraperMode::Volume) + showSearchVolume(currentVolumeSearchQuery.volume); else - showSearchSingleComic(); + showSearchSingleComic(currentVolumeSearchQuery.volume); break; } } @@ -248,16 +254,15 @@ void ComicVineDialog::show() searchVolumeWidget->clean(); if (comics.length() == 1) { - status = AutoSearching; - mode = SingleComic; + status = ScraperStatus::AskingForInfo; + mode = ScraperMode::SingleComic; ComicDB singleComic = comics[0]; + QString title = singleComic.getTitleOrFileName(); titleHeader->setSubTitle(title); - showLoading(tr("Looking for volume...")); - searchVolume(singleComic.getParentFolderName()); - QLOG_TRACE() << singleComic.getParentFolderName(); + showSearchSingleComic(volumeSearchStringFromComic(singleComic)); } else if (comics.length() > 1) { titleHeader->setSubTitle(tr("%1 comics selected").arg(comics.length())); showSeriesQuestion(); @@ -286,7 +291,7 @@ void ComicVineDialog::doLoading() content->addWidget(w); } -void ComicVineDialog::debugClientResults(const QString &string) +void ComicVineDialog::processClientResults(const QString &string) { ResponseParser p; p.loadJSONResponse(string); @@ -295,19 +300,20 @@ void ComicVineDialog::debugClientResults(const QString &string) QMessageBox::critical(0, tr("Error connecting to ComicVine"), p.errorDescription()); goBack(); } else { + switch (mode) { - case SingleComic: - case SingleComicInList: + case ScraperMode::SingleComic: + case ScraperMode::SingleComicInList: if (p.getNumResults() == 0) - showSearchSingleComic(); - else if (status == SearchingVolume) + showSearchSingleComic(currentVolumeSearchQuery.volume); + else if (status == ScraperStatus::SearchingVolume || status == ScraperStatus::SearchingExactVolume) showSelectVolume(string); else showSelectComic(string); break; - case Volume: + case ScraperMode::Volume: if (p.getNumResults() == 0) - showSearchVolume(); + showSearchVolume(currentVolumeSearchQuery.volume); else showSelectVolume(string); break; @@ -317,7 +323,7 @@ void ComicVineDialog::debugClientResults(const QString &string) void ComicVineDialog::showSeriesQuestion() { - status = AskingForInfo; + status = ScraperStatus::AskingForInfo; content->setCurrentWidget(seriesQuestionWidget); backButton->setHidden(true); skipButton->setHidden(true); @@ -330,9 +336,11 @@ void ComicVineDialog::showSeriesQuestion() toggleSkipButton(); } -void ComicVineDialog::showSearchSingleComic() +void ComicVineDialog::showSearchSingleComic(const QString &volume) { - status = AskingForInfo; + searchSingleComicWidget->setVolumeInfo(volume); + + status = ScraperStatus::AskingForInfo; content->setCurrentWidget(searchSingleComicWidget); backButton->setHidden(true); skipButton->setHidden(true); @@ -349,7 +357,7 @@ void ComicVineDialog::showSearchVolume(const QString &volume) { searchVolumeWidget->setVolumeInfo(volume); - status = AskingForInfo; + status = ScraperStatus::AskingForInfo; content->setCurrentWidget(searchVolumeWidget); backButton->setHidden(true); nextButton->setHidden(true); @@ -364,12 +372,12 @@ void ComicVineDialog::showSearchVolume(const QString &volume) void ComicVineDialog::showSelectVolume(const QString &json) { showSelectVolume(); - selectVolumeWidget->load(json, currentVolumeSearchString); + selectVolumeWidget->load(json, currentVolumeSearchQuery); } void ComicVineDialog::showSelectVolume() { - status = SelectingSeries; + status = ScraperStatus::SelectingSeries; content->setCurrentWidget(selectVolumeWidget); @@ -385,7 +393,7 @@ void ComicVineDialog::showSelectVolume() void ComicVineDialog::showSelectComic(const QString &json) { - status = SelectingComic; + status = ScraperStatus::SelectingComic; content->setCurrentWidget(selectComicWidget); selectComicWidget->load(json, currentVolumeId); @@ -402,7 +410,7 @@ void ComicVineDialog::showSelectComic(const QString &json) void ComicVineDialog::showSortVolumeComics(const QString &json) { - status = SortingComics; + status = ScraperStatus::SortingComics; content->setCurrentWidget(sortVolumeComicsWidget); @@ -423,30 +431,35 @@ void ComicVineDialog::queryTimeOut() QMessageBox::warning(this, "Comic Vine error", "Time out connecting to Comic Vine"); switch (status) { - case AutoSearching: - if (mode == Volume) - showSearchVolume(); + case ScraperStatus::AutoSearching: + if (mode == ScraperMode::Volume) + showSearchVolume(currentVolumeSearchQuery.volume); else - showSearchSingleComic(); + showSearchSingleComic(currentVolumeSearchQuery.volume); break; - case SearchingVolume: - if (mode == Volume) - showSearchVolume(); + case ScraperStatus::SearchingVolume: + case ScraperStatus::SearchingExactVolume: + if (mode == ScraperMode::Volume) + showSearchVolume(currentVolumeSearchQuery.volume); else - showSearchSingleComic(); + showSearchSingleComic(currentVolumeSearchQuery.volume); break; - case SearchingSingleComic: - showSearchSingleComic(); + case ScraperStatus::SearchingSingleComic: + showSearchSingleComic(currentVolumeSearchQuery.volume); break; - case GettingVolumeComics: + case ScraperStatus::GettingVolumeComics: showSelectVolume(); break; - default: + + case ScraperStatus::AskingForInfo: + case ScraperStatus::SelectingComic: + case ScraperStatus::SelectingSeries: + case ScraperStatus::SortingComics: break; } } -void ComicVineDialog::getComicsInfo(QList> matchingInfo, int count, const QString &publisher) +void ComicVineDialog::getComicsInfo(QList> matchingInfo, const SelectedVolumeInfo &volumeInfo) { QPair p; QList comics; @@ -460,7 +473,7 @@ void ComicVineDialog::getComicsInfo(QList> matchingInfo, QByteArray result = comicVineClient->getComicDetail(p.second, error, timeout); // TODO check timeOut or Connection error if (error || timeout) continue; // TODO - ComicDB comic = YACReader::parseCVJSONComicInfo(p.first, result, count, publisher); // TODO check result error + ComicDB comic = YACReader::parseCVJSONComicInfo(p.first, result, volumeInfo); // TODO check result error comic.info.comicVineID = p.second; comics.push_back(comic); @@ -472,7 +485,7 @@ void ComicVineDialog::getComicsInfo(QList> matchingInfo, emit accepted(); } -void ComicVineDialog::getComicInfo(const QString &comicId, int count, const QString &publisher) +void ComicVineDialog::getComicInfo(const QString &comicId, const SelectedVolumeInfo &volumeInfo) { auto comicVineClient = new ComicVineClient; @@ -481,14 +494,14 @@ void ComicVineDialog::getComicInfo(const QString &comicId, int count, const QStr QByteArray result = comicVineClient->getComicDetail(comicId, error, timeout); // TODO check timeOut or Connection error if (error || timeout) { // TODO - if (mode == SingleComic || currentIndex == (comics.count() - 1)) { + if (mode == ScraperMode::SingleComic || currentIndex == (comics.count() - 1)) { emit accepted(); } else { goToNextComic(); } } - ComicDB comic = YACReader::parseCVJSONComicInfo(comics[currentIndex], result, count, publisher); // TODO check result error + ComicDB comic = YACReader::parseCVJSONComicInfo(comics[currentIndex], result, volumeInfo); // TODO check result error comic.info.comicVineID = comicId; setLoadingMessage(tr("Retrieving tags for : %1").arg(comics[currentIndex].getFileName())); QString connectionName = ""; @@ -504,7 +517,7 @@ void ComicVineDialog::getComicInfo(const QString &comicId, int count, const QStr } QSqlDatabase::removeDatabase(connectionName); - if (mode == SingleComic || currentIndex == (comics.count() - 1)) { + if (mode == ScraperMode::SingleComic || currentIndex == (comics.count() - 1)) { emit accepted(); } else { goToNextComic(); @@ -513,26 +526,46 @@ void ComicVineDialog::getComicInfo(const QString &comicId, int count, const QStr void ComicVineDialog::toggleSkipButton() { - if (mode == SingleComicInList) + if (mode == ScraperMode::SingleComicInList) skipButton->setVisible(true); else skipButton->setHidden(true); } +QString ComicVineDialog::volumeSearchStringFromComic(const ComicDB &comic) +{ + auto volume = comic.info.volume.toString().trimmed(); + if (!volume.isEmpty()) + return volume; + + auto series = comic.info.series.toString().trimmed(); + if (!series.isEmpty()) + return series; + + auto alternateSeries = comic.info.alternateSeries.toString().trimmed(); + if (!alternateSeries.isEmpty()) + return alternateSeries; + + // extract information from file name + auto parentFolderName = comic.getParentFolderName(); + return parentFolderName; +} + void ComicVineDialog::goToNextComic() { - if (mode == SingleComic || currentIndex == (comics.count() - 1)) { + if (mode == ScraperMode::SingleComic || currentIndex == (comics.count() - 1)) { emit accepted(); return; } currentIndex++; - showSearchSingleComic(); - ComicDB comic = comics[currentIndex]; + QString title = comic.getTitleOrFileName(); titleHeader->setSubTitle(tr("comic %1 of %2 - %3").arg(currentIndex + 1).arg(comics.length()).arg(title)); + + showSearchSingleComic(volumeSearchStringFromComic(comic)); } void ComicVineDialog::clearState() @@ -559,38 +592,44 @@ void ComicVineDialog::setLoadingMessage(const QString &message) void ComicVineDialog::search() { switch (mode) { - case Volume: + case ScraperMode::Volume: launchSearchVolume(); break; - default: + + case ScraperMode::SingleComic: + case ScraperMode::SingleComicInList: launchSearchComic(); break; } } -void ComicVineDialog::searchVolume(const QString &v, int page) +void ComicVineDialog::searchVolume(const VolumeSearchQuery &query) { showLoading(tr("Looking for volume...")); - currentVolumeSearchString = v; + currentVolumeSearchQuery = query; auto comicVineClient = new ComicVineClient; - connect(comicVineClient, &ComicVineClient::searchResult, this, &ComicVineDialog::debugClientResults); + connect(comicVineClient, &ComicVineClient::searchResult, this, &ComicVineDialog::processClientResults); connect(comicVineClient, &ComicVineClient::timeOut, this, &ComicVineDialog::queryTimeOut); connect(comicVineClient, &ComicVineClient::finished, comicVineClient, &QObject::deleteLater); - comicVineClient->search(v, page); - - status = SearchingVolume; + if (query.exactMatch) { + status = ScraperStatus::SearchingExactVolume; + comicVineClient->searchExactVolume(query.volume, query.page); + } else { + status = ScraperStatus::SearchingVolume; + comicVineClient->search(query.volume, query.page); + } } void ComicVineDialog::getVolumeComicsInfo(const QString &vID, int /* page */) { showLoading(tr("Retrieving volume info...")); - status = GettingVolumeComics; + status = ScraperStatus::GettingVolumeComics; auto comicVineClient = new ComicVineClient; - if (mode == Volume) + if (mode == ScraperMode::Volume) connect(comicVineClient, &ComicVineClient::volumeComicsInfo, this, &ComicVineDialog::showSortVolumeComics); else connect(comicVineClient, &ComicVineClient::volumeComicsInfo, this, &ComicVineDialog::showSelectComic); @@ -602,21 +641,28 @@ void ComicVineDialog::getVolumeComicsInfo(const QString &vID, int /* page */) comicVineClient->getAllVolumeComicsInfo(vID); } +// TODO: get the search configuration for exact match or not void ComicVineDialog::launchSearchVolume() { showLoading(tr("Looking for volume...")); // TODO: check if volume info is empty. - searchVolume(searchVolumeWidget->getVolumeInfo()); + + QString volumeInfo = searchVolumeWidget->getVolumeInfo(); + bool exactMatch = searchVolumeWidget->getExactMatch(); + + searchVolume({ volumeInfo, 1, exactMatch }); } +// TODO: get the search configuration for exact match or not void ComicVineDialog::launchSearchComic() { showLoading(tr("Looking for comic...")); QString volumeInfo = searchSingleComicWidget->getVolumeInfo(); + bool exactMatch = searchSingleComicWidget->getExactMatch(); // QString comicInfo = searchSingleComicWidget->getComicInfo(); // int comicNumber = searchSingleComicWidget->getComicNumber(); // if(comicInfo.isEmpty() && comicNumber == -1) - searchVolume(volumeInfo); + searchVolume({ volumeInfo, 1, exactMatch }); } diff --git a/YACReaderLibrary/comic_vine/comic_vine_dialog.h b/YACReaderLibrary/comic_vine/comic_vine_dialog.h index fa6861509..e6ccfc78a 100644 --- a/YACReaderLibrary/comic_vine/comic_vine_dialog.h +++ b/YACReaderLibrary/comic_vine/comic_vine_dialog.h @@ -4,6 +4,7 @@ #include #include "comic_db.h" +#include "volume_search_query.h" class QPushButton; class QStackedWidget; @@ -17,7 +18,9 @@ class SearchSingleComic; class SearchVolume; class SelectComic; class SelectVolume; +struct SelectedVolumeInfo; class SortVolumeComics; +struct VolumeSearchQuery; // TODO this should use a QStateMachine //---------------------------------------- @@ -31,8 +34,8 @@ class ComicVineDialog : public QDialog void setComics(const QList &comics); QSize sizeHint() const override; QSize minimumSizeHint() const override; - void getComicsInfo(QList> matchingInfo, int count, const QString &publisher); - void getComicInfo(const QString &comicId, int count, const QString &publisher); + void getComicsInfo(QList> matchingInfo, const SelectedVolumeInfo &volumeInfo); + void getComicInfo(const QString &comicId, const SelectedVolumeInfo &volumeInfo); void closeEvent(QCloseEvent *event) override; signals: @@ -42,14 +45,14 @@ public slots: protected slots: void goNext(); void goBack(); - void debugClientResults(const QString &string); + void processClientResults(const QString &string); // show widget methods void showSeriesQuestion(); - void showSearchSingleComic(); + void showSearchSingleComic(const QString &volume = ""); void showSearchVolume(const QString &volume = ""); void showLoading(const QString &message = ""); void search(); - void searchVolume(const QString &v, int page = 1); + void searchVolume(const VolumeSearchQuery &query); void getVolumeComicsInfo(const QString &vID, int page = 1); void launchSearchVolume(); void launchSearchComic(); @@ -63,22 +66,23 @@ protected slots: private: void clearState(); - void toggleSkipButton(); + QString volumeSearchStringFromComic(const ComicDB &comic); - enum ScraperMode { + enum class ScraperMode { SingleComic, // the scraper has been opened for a single comic Volume, // the scraper is trying to get comics info for a whole volume SingleComicInList // the scraper has been opened for a list of unrelated comics }; - enum ScraperStatus { - AutoSearching, - AskingForInfo, + enum class ScraperStatus { + AutoSearching, // Searching for volumes maching a single comic + AskingForInfo, // The dialog is showing some UI to ask the user for some info SelectingComic, SelectingSeries, SearchingSingleComic, SearchingVolume, + SearchingExactVolume, SortingComics, GettingVolumeComics }; @@ -118,7 +122,7 @@ protected slots: SelectComic *selectComicWidget; SortVolumeComics *sortVolumeComicsWidget; - QString currentVolumeSearchString; + VolumeSearchQuery currentVolumeSearchQuery; QString currentVolumeId; }; diff --git a/YACReaderLibrary/comic_vine/comic_vine_json_parser.cpp b/YACReaderLibrary/comic_vine/comic_vine_json_parser.cpp index f6a038134..90ac8244a 100644 --- a/YACReaderLibrary/comic_vine/comic_vine_json_parser.cpp +++ b/YACReaderLibrary/comic_vine/comic_vine_json_parser.cpp @@ -2,6 +2,7 @@ #include "comic_vine_json_parser.h" #include "comic_vine_client.h" +#include "selected_volume_info.h" #include #include @@ -12,7 +13,7 @@ QPair getFirstStoryArcIdAndName(const QVariant &json_story_arc QPair getArcNumberAndArcCount(const QString &storyArcId, const QString &comicId); QList getNamesFromList(const QVariant &json_list); -ComicDB YACReader::parseCVJSONComicInfo(ComicDB &comic, const QString &json, int count, const QString &publisher) +ComicDB YACReader::parseCVJSONComicInfo(ComicDB &comic, const QString &json, const SelectedVolumeInfo &volumeInfo) { QJsonParseError Err; @@ -61,8 +62,12 @@ ComicDB YACReader::parseCVJSONComicInfo(ComicDB &comic, const QString &json, int } } - if (result.contains("description") && !result.value("description").isNull()) { + if (result.contains("description") && !result.value("description").isNull() && !result.value("description").toString().trimmed().isEmpty()) { comic.info.synopsis = result.value("description"); + } else if (result.contains("deck") && !result.value("deck").isNull() && !result.value("deck").toString().trimmed().isEmpty()) { + comic.info.synopsis = result.value("deck"); + } else if (!volumeInfo.description.trimmed().isEmpty() && volumeInfo.numIssues < 2) { + comic.info.synopsis = volumeInfo.description.trimmed(); } if (result.contains("character_credits") && !result.value("character_credits").isNull()) { @@ -101,9 +106,9 @@ ComicDB YACReader::parseCVJSONComicInfo(ComicDB &comic, const QString &json, int comic.info.characters = getNamesFromList(result.value("character_credits")).join("\n"); } - comic.info.count = count; + comic.info.count = volumeInfo.numIssues; - comic.info.publisher = publisher; + comic.info.publisher = volumeInfo.publisher; comic.info.edited = true; } diff --git a/YACReaderLibrary/comic_vine/comic_vine_json_parser.h b/YACReaderLibrary/comic_vine/comic_vine_json_parser.h index 6b7bf4c1c..8f395759e 100644 --- a/YACReaderLibrary/comic_vine/comic_vine_json_parser.h +++ b/YACReaderLibrary/comic_vine/comic_vine_json_parser.h @@ -4,9 +4,11 @@ #include "comic_db.h" +struct SelectedVolumeInfo; + namespace YACReader { -ComicDB parseCVJSONComicInfo(ComicDB &comic, const QString &json, int count, const QString &publisher); +ComicDB parseCVJSONComicInfo(ComicDB &comic, const QString &json, const SelectedVolumeInfo &volumeInfo); } diff --git a/YACReaderLibrary/comic_vine/model/response_parser.cpp b/YACReaderLibrary/comic_vine/model/response_parser.cpp index 033fd6077..dd2582120 100644 --- a/YACReaderLibrary/comic_vine/model/response_parser.cpp +++ b/YACReaderLibrary/comic_vine/model/response_parser.cpp @@ -2,6 +2,7 @@ #include #include #include +#include ResponseParser::ResponseParser(QObject *parent) : QObject(parent), error(false), errorTxt("None"), numResults(-1), currentPage(-1), totalPages(-1) diff --git a/YACReaderLibrary/comic_vine/model/selected_volume_info.h b/YACReaderLibrary/comic_vine/model/selected_volume_info.h new file mode 100644 index 000000000..7bf76b407 --- /dev/null +++ b/YACReaderLibrary/comic_vine/model/selected_volume_info.h @@ -0,0 +1,13 @@ +#ifndef SELECTED_VOLUME_INFO_H +#define SELECTED_VOLUME_INFO_H + +#include + +struct SelectedVolumeInfo { + QString id; + int numIssues; + QString publisher; + QString description; +}; + +#endif // SELECTED_VOLUME_INFO_H diff --git a/YACReaderLibrary/comic_vine/model/volume_search_query.h b/YACReaderLibrary/comic_vine/model/volume_search_query.h new file mode 100644 index 000000000..d93c72330 --- /dev/null +++ b/YACReaderLibrary/comic_vine/model/volume_search_query.h @@ -0,0 +1,12 @@ +#ifndef VOLUME_SEARCH_QUERY_H +#define VOLUME_SEARCH_QUERY_H + +#include + +struct VolumeSearchQuery { + QString volume; + int page; + bool exactMatch; +}; + +#endif // VOLUME_SEARCH_QUERY_H diff --git a/YACReaderLibrary/comic_vine/scraper_checkbox.cpp b/YACReaderLibrary/comic_vine/scraper_checkbox.cpp new file mode 100644 index 000000000..9a5381217 --- /dev/null +++ b/YACReaderLibrary/comic_vine/scraper_checkbox.cpp @@ -0,0 +1,27 @@ +#include "scraper_checkbox.h" +#include "qwidget.h" + +ScraperCheckBox::ScraperCheckBox(const QString &text, QWidget *parent) + : QCheckBox(text, parent) +{ + setStyleSheet( + "QCheckBox {" + " color: white;" + " font-size: 12px;" + " font-family: Arial;" + " spacing: 10px;" + "}" + "QCheckBox::indicator {" + " width: 13px;" + " height: 13px;" + " border: 1px solid #242424;" + " background: #2e2e2e;" + "}" + "QCheckBox::indicator:checked {" + " image: url(:/images/comic_vine/checkBoxTick.svg);" + " background: #2e2e2e;" + "}" + "QCheckBox::indicator:unchecked {" + " background: #2e2e2e;" + "}"); +} diff --git a/YACReaderLibrary/comic_vine/scraper_checkbox.h b/YACReaderLibrary/comic_vine/scraper_checkbox.h new file mode 100644 index 000000000..ad6846709 --- /dev/null +++ b/YACReaderLibrary/comic_vine/scraper_checkbox.h @@ -0,0 +1,12 @@ +#ifndef SCRAPER_CHECKBOX_H +#define SCRAPER_CHECKBOX_H + +#include + +class ScraperCheckBox : public QCheckBox +{ +public: + ScraperCheckBox(const QString &text, QWidget *parent = nullptr); +}; + +#endif // SCRAPER_CHECKBOX_H diff --git a/YACReaderLibrary/comic_vine/scraper_selector.cpp b/YACReaderLibrary/comic_vine/scraper_selector.cpp deleted file mode 100644 index 8e84a3110..000000000 --- a/YACReaderLibrary/comic_vine/scraper_selector.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "scraper_selector.h" - -ScraperSelector::ScraperSelector(QWidget *parent) - : QWidget(parent) -{ - paginator = new ScraperResultsPaginator; - connect(paginator, &ScraperResultsPaginator::loadNextPage, this, &ScraperSelector::loadNextPage); - connect(paginator, &ScraperResultsPaginator::loadPreviousPage, this, &ScraperSelector::loadPreviousPage); -} - -void ScraperSelector::load(const QString &json, const QString &searchString) -{ - currentSearchString = searchString; - paginator->update(json); -} - -void ScraperSelector::loadNextPage() -{ - emit loadPage(currentSearchString, paginator->getCurrentPage() + 1); -} - -void ScraperSelector::loadPreviousPage() -{ - emit loadPage(currentSearchString, paginator->getCurrentPage() - 1); -} diff --git a/YACReaderLibrary/comic_vine/scraper_selector.h b/YACReaderLibrary/comic_vine/scraper_selector.h deleted file mode 100644 index 312dcb464..000000000 --- a/YACReaderLibrary/comic_vine/scraper_selector.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef SCRAPER_SELECTOR_H -#define SCRAPER_SELECTOR_H - -#include - -#include "scraper_results_paginator.h" - -class ScraperSelector : public QWidget -{ - Q_OBJECT -public: - explicit ScraperSelector(QWidget *parent = nullptr); - virtual void load(const QString &json, const QString &searchString); -public slots: - -signals: - void loadPage(QString, int); - -private slots: - void loadNextPage(); - void loadPreviousPage(); - -protected: - QString currentSearchString; - ScraperResultsPaginator *paginator; -}; - -#endif // SCRAPER_SELECTOR_H diff --git a/YACReaderLibrary/comic_vine/scraper_tableview.cpp b/YACReaderLibrary/comic_vine/scraper_tableview.cpp index 08f232964..eec6da838 100644 --- a/YACReaderLibrary/comic_vine/scraper_tableview.cpp +++ b/YACReaderLibrary/comic_vine/scraper_tableview.cpp @@ -54,6 +54,8 @@ ScraperTableView::ScraperTableView(QWidget *parent) setAlternatingRowColors(true); + horizontalHeader()->setMinimumSectionSize(85); + verticalHeader()->hide(); setSelectionMode(QAbstractItemView::SingleSelection); diff --git a/YACReaderLibrary/comic_vine/search_single_comic.cpp b/YACReaderLibrary/comic_vine/search_single_comic.cpp index de11a5861..1d637990d 100644 --- a/YACReaderLibrary/comic_vine/search_single_comic.cpp +++ b/YACReaderLibrary/comic_vine/search_single_comic.cpp @@ -11,12 +11,16 @@ SearchSingleComic::SearchSingleComic(QWidget *parent) { // QLabel * label = new QLabel(tr("Please provide some additional information. At least one field is needed.")); - QLabel *label = new QLabel(tr("Please provide some additional information.")); + QLabel *label = new QLabel(tr("Please provide some additional information for this comic.")); label->setStyleSheet("QLabel {color:white; font-size:12px;font-family:Arial;}"); // titleEdit = new ScraperLineEdit(tr("Title:")); // numberEdit = new ScraperLineEdit(tr("Number:")); volumeEdit = new ScraperLineEdit(tr("Series:")); + volumeEdit->setClearButtonEnabled(true); + + exactMatchCheckBox = new ScraperCheckBox(tr("Use exact match search. Disable if you want to find volumes that match some of the words in the name.")); + exactMatchCheckBox->setChecked(true); // numberEdit->setMaximumWidth(126); @@ -29,6 +33,7 @@ SearchSingleComic::SearchSingleComic(QWidget *parent) l->addWidget(label); // l->addLayout(hl); l->addWidget(volumeEdit); + l->addWidget(exactMatchCheckBox); l->addStretch(); l->setContentsMargins(0, 0, 0, 0); @@ -36,11 +41,16 @@ SearchSingleComic::SearchSingleComic(QWidget *parent) setContentsMargins(0, 0, 0, 0); } -QString SearchSingleComic::getVolumeInfo() +QString SearchSingleComic::getVolumeInfo() const { return volumeEdit->text(); } +void SearchSingleComic::setVolumeInfo(const QString &volume) +{ + volumeEdit->setText(volume); +} + QString SearchSingleComic::getComicInfo() { // return titleEdit->text(); diff --git a/YACReaderLibrary/comic_vine/search_single_comic.h b/YACReaderLibrary/comic_vine/search_single_comic.h index 58dfa44af..7a8d7c107 100644 --- a/YACReaderLibrary/comic_vine/search_single_comic.h +++ b/YACReaderLibrary/comic_vine/search_single_comic.h @@ -1,7 +1,9 @@ #ifndef SEARCH_SINGLE_COMIC_H #define SEARCH_SINGLE_COMIC_H -#include +#include + +#include "scraper_checkbox.h" class ScraperLineEdit; @@ -10,14 +12,17 @@ class SearchSingleComic : public QWidget Q_OBJECT public: SearchSingleComic(QWidget *parent = nullptr); - QString getVolumeInfo(); + QString getVolumeInfo() const; + bool getExactMatch() const { return exactMatchCheckBox->isChecked(); } + void setVolumeInfo(const QString &volume); QString getComicInfo(); int getComicNumber(); void clean(); private: - ScraperLineEdit *titleEdit; - ScraperLineEdit *numberEdit; + // ScraperLineEdit *titleEdit; + // ScraperLineEdit *numberEdit; ScraperLineEdit *volumeEdit; + ScraperCheckBox *exactMatchCheckBox; }; #endif // SEARCH_SINGLE_COMIC_H diff --git a/YACReaderLibrary/comic_vine/search_volume.cpp b/YACReaderLibrary/comic_vine/search_volume.cpp index 0e7fc11c7..bace44252 100644 --- a/YACReaderLibrary/comic_vine/search_volume.cpp +++ b/YACReaderLibrary/comic_vine/search_volume.cpp @@ -1,6 +1,7 @@ #include "search_volume.h" #include "scraper_lineedit.h" +#include "scraper_checkbox.h" #include #include @@ -12,12 +13,17 @@ SearchVolume::SearchVolume(QWidget *parent) label->setStyleSheet("QLabel {color:white; font-size:12px;font-family:Arial;}"); volumeEdit = new ScraperLineEdit(tr("Series:")); + volumeEdit->setClearButtonEnabled(true); + + exactMatchCheckBox = new ScraperCheckBox(tr("Use exact match search. Disable if you want to find volumes that match some of the words in the name."), this); + exactMatchCheckBox->setChecked(true); QVBoxLayout *l = new QVBoxLayout; l->addSpacing(35); l->addWidget(label); l->addWidget(volumeEdit); + l->addWidget(exactMatchCheckBox); l->addStretch(); l->setContentsMargins(0, 0, 0, 0); diff --git a/YACReaderLibrary/comic_vine/search_volume.h b/YACReaderLibrary/comic_vine/search_volume.h index 6a9f1ee79..4ad8db80d 100644 --- a/YACReaderLibrary/comic_vine/search_volume.h +++ b/YACReaderLibrary/comic_vine/search_volume.h @@ -1,9 +1,11 @@ #ifndef SEARCH_VOLUME_H #define SEARCH_VOLUME_H -#include +#include +#include "scraper_checkbox.h" class ScraperLineEdit; +class ScraperCheckBox; class SearchVolume : public QWidget { @@ -13,9 +15,11 @@ class SearchVolume : public QWidget void clean(); void setVolumeInfo(const QString &volume); QString getVolumeInfo() const; + bool getExactMatch() const { return exactMatchCheckBox->isChecked(); } private: ScraperLineEdit *volumeEdit; + ScraperCheckBox *exactMatchCheckBox; }; #endif // SEARCH_VOLUME_H diff --git a/YACReaderLibrary/comic_vine/select_comic.cpp b/YACReaderLibrary/comic_vine/select_comic.cpp index 68b568e69..70b64359c 100644 --- a/YACReaderLibrary/comic_vine/select_comic.cpp +++ b/YACReaderLibrary/comic_vine/select_comic.cpp @@ -11,7 +11,7 @@ #include SelectComic::SelectComic(QWidget *parent) - : ScraperSelector(parent), model(0) + : QWidget(parent), model(0) { QString labelStylesheet = "QLabel {color:white; font-size:12px;font-family:Arial;}"; @@ -35,6 +35,9 @@ SelectComic::SelectComic(QWidget *parent) // connections connect(tableComics, &QAbstractItemView::clicked, this, &SelectComic::loadComicInfo); + paginator = new ScraperResultsPaginator; + connect(paginator, &ScraperResultsPaginator::loadNextPage, this, &SelectComic::loadNextPage); + connect(paginator, &ScraperResultsPaginator::loadPreviousPage, this, &SelectComic::loadPreviousPage); paginator->setCustomLabel(tr("comics")); left->addWidget(cover); @@ -62,7 +65,7 @@ SelectComic::SelectComic(QWidget *parent) setContentsMargins(0, 0, 0, 0); } -void SelectComic::load(const QString &json, const QString &searchString) +void SelectComic::load(const QString &json, const QString &volumeId) { auto tempM = new VolumeComicsModel(); tempM->load(json); @@ -80,7 +83,18 @@ void SelectComic::load(const QString &json, const QString &searchString) tableComics->resizeColumnToContents(0); - ScraperSelector::load(json, searchString); + currentVolumeId = volumeId; + paginator->update(json); +} + +void SelectComic::loadNextPage() +{ + emit loadPage(currentVolumeId, paginator->getCurrentPage() + 1); +} + +void SelectComic::loadPreviousPage() +{ + emit loadPage(currentVolumeId, paginator->getCurrentPage() - 1); } SelectComic::~SelectComic() { } @@ -130,9 +144,20 @@ void SelectComic::setDescription(const QString &jsonDetail) return; } - QVariant descriptionValues = sc.value("results").toMap().value("description"); - bool valid = !descriptionValues.isNull() && descriptionValues.isValid(); - detailLabel->setText(valid ? descriptionValues.toString().replace("setText(description.replace("setText(deck.replace("setText(tr("comic description unavailable")); + } } QString SelectComic::getSelectedComicId() diff --git a/YACReaderLibrary/comic_vine/select_comic.h b/YACReaderLibrary/comic_vine/select_comic.h index 5a8343785..427c3e290 100644 --- a/YACReaderLibrary/comic_vine/select_comic.h +++ b/YACReaderLibrary/comic_vine/select_comic.h @@ -1,7 +1,9 @@ #ifndef SELECT_COMIC_H #define SELECT_COMIC_H -#include "scraper_selector.h" +#include + +#include "scraper_results_paginator.h" class QLabel; class VolumeComicsModel; @@ -10,12 +12,12 @@ class QModelIndex; class ScraperScrollLabel; class ScraperTableView; -class SelectComic : public ScraperSelector +class SelectComic : public QWidget { Q_OBJECT public: SelectComic(QWidget *parent = nullptr); - void load(const QString &json, const QString &searchString) override; + void load(const QString &json, const QString &volumeId); virtual ~SelectComic(); public slots: @@ -24,11 +26,20 @@ public slots: void setDescription(const QString &jsonDetail); QString getSelectedComicId(); +signals: + void loadPage(QString, int); + +private slots: + void loadNextPage(); + void loadPreviousPage(); + private: QLabel *cover; ScraperScrollLabel *detailLabel; ScraperTableView *tableComics; VolumeComicsModel *model; + QString currentVolumeId; + ScraperResultsPaginator *paginator; }; #endif // SELECT_COMIC_H diff --git a/YACReaderLibrary/comic_vine/select_volume.cpp b/YACReaderLibrary/comic_vine/select_volume.cpp index d934d4fc2..86bd49ac6 100644 --- a/YACReaderLibrary/comic_vine/select_volume.cpp +++ b/YACReaderLibrary/comic_vine/select_volume.cpp @@ -24,8 +24,10 @@ #include "response_parser.h" #include "scraper_results_paginator.h" +#include "selected_volume_info.h" + SelectVolume::SelectVolume(QWidget *parent) - : ScraperSelector(parent), model(0) + : QWidget(parent), model(0) { proxyModel = new QSortFilterProxyModel; proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); @@ -61,6 +63,9 @@ SelectVolume::SelectVolume(QWidget *parent) connect(tableVolumes->horizontalHeader(), qOverload(&QHeaderView::sortIndicatorChanged), tableVolumes, qOverload(&QTableView::sortByColumn)); connect(tableVolumes, &QAbstractItemView::clicked, this, &SelectVolume::loadVolumeInfo); + paginator = new ScraperResultsPaginator; + connect(paginator, &ScraperResultsPaginator::loadNextPage, this, &SelectVolume::loadNextPage); + connect(paginator, &ScraperResultsPaginator::loadPreviousPage, this, &SelectVolume::loadPreviousPage); paginator->setCustomLabel(tr("volumes")); top->addWidget(label); @@ -91,7 +96,7 @@ SelectVolume::SelectVolume(QWidget *parent) setContentsMargins(0, 0, 0, 0); } -void SelectVolume::load(const QString &json, const QString &searchString) +void SelectVolume::load(const QString &json, const VolumeSearchQuery &searchQuery) { auto tempM = new VolumesModel(); tempM->load(json); @@ -114,7 +119,18 @@ void SelectVolume::load(const QString &json, const QString &searchString) tableVolumes->setColumnWidth(0, 350); - ScraperSelector::load(json, searchString); + currentSearchQuery = searchQuery; + paginator->update(json); +} + +void SelectVolume::loadNextPage() +{ + emit loadPage({ currentSearchQuery.volume, paginator->getCurrentPage() + 1, currentSearchQuery.exactMatch }); +} + +void SelectVolume::loadPreviousPage() +{ + emit loadPage({ currentSearchQuery.volume, paginator->getCurrentPage() - 1, currentSearchQuery.exactMatch }); } void SelectVolume::clearFilter() @@ -176,22 +192,29 @@ void SelectVolume::setDescription(const QString &jsonDetail) return; } - QVariant descriptionValues = sc.value("results").toMap().value("description"); - bool valid = !descriptionValues.isNull() && descriptionValues.isValid(); - detailLabel->setText(valid ? descriptionValues.toString().replace("getVolumeId(proxyModel->mapToSource(tableVolumes->currentIndex())); + auto resultMap = sc.value("results").toMap(); + QVariant descriptionValues = resultMap.value("description"); + auto description = descriptionValues.toString().trimmed(); + QVariant deckValues = resultMap.value("deck"); + auto deck = deckValues.toString().trimmed(); + bool valid = !descriptionValues.isNull() && descriptionValues.isValid() && !description.isEmpty(); + bool validDeck = !deckValues.isNull() && deckValues.isValid() && !deck.isEmpty(); + if (valid) { + selectedVolumeDescription = description; + detailLabel->setText(description.replace("setText(deck.replace("setText(tr("volume description unavailable")); + } } -int SelectVolume::getSelectedVolumeNumIssues() +SelectedVolumeInfo SelectVolume::getSelectedVolumeInfo() { - return model->getNumIssues(proxyModel->mapToSource(tableVolumes->currentIndex())); -} + auto volumeId = model->getVolumeId(proxyModel->mapToSource(tableVolumes->currentIndex())); + auto numIssues = model->getNumIssues(proxyModel->mapToSource(tableVolumes->currentIndex())); + auto publisher = model->getPublisher(proxyModel->mapToSource(tableVolumes->currentIndex())); -QString SelectVolume::getSelectedVolumePublisher() -{ - return model->getPublisher(proxyModel->mapToSource(tableVolumes->currentIndex())); + return { volumeId, numIssues, publisher, selectedVolumeDescription }; } diff --git a/YACReaderLibrary/comic_vine/select_volume.h b/YACReaderLibrary/comic_vine/select_volume.h index e6e00d001..944c833be 100644 --- a/YACReaderLibrary/comic_vine/select_volume.h +++ b/YACReaderLibrary/comic_vine/select_volume.h @@ -1,7 +1,11 @@ #ifndef SELECT_VOLUME_H #define SELECT_VOLUME_H -#include "scraper_selector.h" +#include + +#include "scraper_results_paginator.h" +#include "selected_volume_info.h" +#include "volume_search_query.h" class QLabel; class VolumesModel; @@ -13,12 +17,12 @@ class ScraperScrollLabel; class ScraperTableView; class ScraperLineEdit; -class SelectVolume : public ScraperSelector +class SelectVolume : public QWidget { Q_OBJECT public: SelectVolume(QWidget *parent = nullptr); - void load(const QString &json, const QString &searchString) override; + void load(const QString &json, const VolumeSearchQuery &searchQuery); void clearFilter(); virtual ~SelectVolume(); @@ -26,9 +30,14 @@ public slots: void loadVolumeInfo(const QModelIndex &mi); void setCover(const QByteArray &); void setDescription(const QString &jsonDetail); - QString getSelectedVolumeId(); - int getSelectedVolumeNumIssues(); - QString getSelectedVolumePublisher(); + SelectedVolumeInfo getSelectedVolumeInfo(); + +signals: + void loadPage(VolumeSearchQuery); + +private slots: + void loadNextPage(); + void loadPreviousPage(); private: QLabel *cover; @@ -37,6 +46,9 @@ public slots: VolumesModel *model; QSortFilterProxyModel *proxyModel; ScraperLineEdit *filterEdit; + QString selectedVolumeDescription; + VolumeSearchQuery currentSearchQuery; + ScraperResultsPaginator *paginator; }; #endif // SELECT_VOLUME_H diff --git a/YACReaderLibrary/comic_vine/sort_volume_comics.cpp b/YACReaderLibrary/comic_vine/sort_volume_comics.cpp index 3b3e2fba1..fedd54a93 100644 --- a/YACReaderLibrary/comic_vine/sort_volume_comics.cpp +++ b/YACReaderLibrary/comic_vine/sort_volume_comics.cpp @@ -11,7 +11,7 @@ #include "volume_comics_model.h" SortVolumeComics::SortVolumeComics(QWidget *parent) - : ScraperSelector(parent) + : QWidget(parent) { QString labelStylesheet = "QLabel {color:white; font-size:12px;font-family:Arial;}"; @@ -55,6 +55,9 @@ SortVolumeComics::SortVolumeComics(QWidget *parent) // connect(tableVolumeComics, SIGNAL(pressed(QModelIndex)), tableFiles, SLOT(setCurrentIndex(QModelIndex))); // connect(tableFiles, SIGNAL(pressed(QModelIndex)), tableVolumeComics, SLOT(setCurrentIndex(QModelIndex))); + paginator = new ScraperResultsPaginator; + connect(paginator, &ScraperResultsPaginator::loadNextPage, this, &SortVolumeComics::loadNextPage); + connect(paginator, &ScraperResultsPaginator::loadPreviousPage, this, &SortVolumeComics::loadPreviousPage); paginator->setCustomLabel(tr("issues")); paginator->setMinimumWidth(422); @@ -77,7 +80,6 @@ SortVolumeComics::SortVolumeComics(QWidget *parent) l->addWidget(label, 0); l->addSpacing(5); l->addLayout(content, 1); - l->addLayout(sortButtonsLayout, 0); l->setContentsMargins(0, 0, 0, 0); setLayout(l); @@ -120,7 +122,18 @@ void SortVolumeComics::setData(QList &comics, const QString &json, cons tableVolumeComics->resizeColumnToContents(0); - ScraperSelector::load(json, vID); + currentVolumeId = vID; + paginator->update(json); +} + +void SortVolumeComics::loadNextPage() +{ + emit loadPage(currentVolumeId, paginator->getCurrentPage() + 1); +} + +void SortVolumeComics::loadPreviousPage() +{ + emit loadPage(currentVolumeId, paginator->getCurrentPage() - 1); } void SortVolumeComics::synchronizeScroll(int pos) diff --git a/YACReaderLibrary/comic_vine/sort_volume_comics.h b/YACReaderLibrary/comic_vine/sort_volume_comics.h index bacadcb1c..2a6003415 100644 --- a/YACReaderLibrary/comic_vine/sort_volume_comics.h +++ b/YACReaderLibrary/comic_vine/sort_volume_comics.h @@ -1,13 +1,13 @@ #ifndef SORT_VOLUME_COMICS_H #define SORT_VOLUME_COMICS_H -#include "scraper_selector.h" - +#include #include #include #include #include "comic_db.h" +#include "scraper_results_paginator.h" class ScraperTableView; class LocalComicListModel; @@ -63,14 +63,12 @@ class ScrapperToolButton : public QPushButton Appearance appearance; }; -class SortVolumeComics : public ScraperSelector +class SortVolumeComics : public QWidget { Q_OBJECT public: explicit SortVolumeComics(QWidget *parent = nullptr); -signals: - public slots: void setData(QList &comics, const QString &json, const QString &vID); QList> getMatchingInfo(); @@ -86,6 +84,13 @@ protected slots: void restoreAllComics(); void showRemovedComicsSelector(); +signals: + void loadPage(QString, int); + +private slots: + void loadNextPage(); + void loadPreviousPage(); + private: ScraperTableView *tableFiles; ScraperTableView *tableVolumeComics; @@ -97,6 +102,9 @@ protected slots: ScrapperToolButton *moveDownButtonCL; ScrapperToolButton *moveUpButtonIL; ScrapperToolButton *moveDownButtonIL; + + QString currentVolumeId; + ScraperResultsPaginator *paginator; }; #endif // SORT_VOLUME_COMICS_H diff --git a/YACReaderLibrary/create_library_dialog.cpp b/YACReaderLibrary/create_library_dialog.cpp index 397ae6369..8fbce0930 100644 --- a/YACReaderLibrary/create_library_dialog.cpp +++ b/YACReaderLibrary/create_library_dialog.cpp @@ -1,4 +1,5 @@ #include "create_library_dialog.h" +#include "yacreader_global.h" #include #include @@ -6,6 +7,8 @@ #include #include +using namespace YACReader; + CreateLibraryDialog::CreateLibraryDialog(QWidget *parent) : QDialog(parent) { @@ -87,7 +90,7 @@ void CreateLibraryDialog::create() QFileInfo f(path->text()); if (f.exists() && f.isDir() && f.isWritable()) { if (!libraries.contains(nameEdit->text())) { - emit createLibrary(QDir::cleanPath(path->text()), QDir::cleanPath(path->text()) + "/.yacreaderlibrary", nameEdit->text()); + emit createLibrary(QDir::cleanPath(path->text()), LibraryPaths::libraryDataPath(QDir::cleanPath(path->text())), nameEdit->text()); close(); } else emit libraryExists(nameEdit->text()); diff --git a/YACReaderLibrary/db/comic_model.cpp b/YACReaderLibrary/db/comic_model.cpp index cf75b03e8..b0a401298 100644 --- a/YACReaderLibrary/db/comic_model.cpp +++ b/YACReaderLibrary/db/comic_model.cpp @@ -11,6 +11,7 @@ #include "comic_db.h" #include "db_helper.h" #include "reading_list_model.h" + #ifdef use_unarr #include #endif @@ -489,11 +490,10 @@ int ComicModel::rowCount(const QModelIndex &parent) const QStringList ComicModel::getPaths(const QString &_source) { QStringList paths; - QString source = _source + "/.yacreaderlibrary/covers/"; QList::ConstIterator itr; for (itr = _data.constBegin(); itr != _data.constEnd(); itr++) { QString hash = (*itr)->data(ComicModel::Hash).toString(); - paths << source + hash + ".jpg"; + paths << LibraryPaths::coverPath(_source, hash); } return paths; @@ -1228,10 +1228,10 @@ void ComicModel::notifyCoverChange(const ComicDB &comic) // this doesn't work in QML -> emit dataChanged(index(itemIndex, 0), index(itemIndex, 0), QVector() << CoverPathRole); } -// ???? QUrl ComicModel::getCoverUrlPathForComicHash(const QString &hash) const { - return QUrl::fromLocalFile(_databasePath + "/covers/" + hash + ".jpg"); + auto coverPath = LibraryPaths::coverPathFromLibraryDataPath(_databasePath, hash); + return QUrl::fromLocalFile(coverPath); } void ComicModel::addComicsToFavorites(const QList &comicIds) diff --git a/YACReaderLibrary/db/data_base_management.cpp b/YACReaderLibrary/db/data_base_management.cpp index 18c6229c1..9ed48ef9b 100644 --- a/YACReaderLibrary/db/data_base_management.cpp +++ b/YACReaderLibrary/db/data_base_management.cpp @@ -4,6 +4,7 @@ #include "initial_comic_info_extractor.h" #include "check_new_version.h" #include "db_helper.h" +#include "yacreader_libraries.h" #include "QsLog.h" @@ -80,7 +81,13 @@ static QString fields = "title," "seriesGroup," "mainCharacterOrTeam," "review," - "tags"; + "tags," + // new 9.16 fields + "imageFiltersJson," + "lastTimeImageFiltersSet," + "lastTimeCoverSet," + "usesExternalCover," + "lastTimeMetadataSet"; DataBaseManagement::DataBaseManagement() : QObject(), dataBasesList() @@ -118,15 +125,15 @@ QSqlDatabase DataBaseManagement::createDatabase(QString dest) return db; } -QSqlDatabase DataBaseManagement::loadDatabase(QString path) +QSqlDatabase DataBaseManagement::loadDatabase(QString libraryDataPath) { - if (!QFile::exists(path + "/library.ydb")) { + if (!QFile::exists(libraryDataPath + "/library.ydb")) { return QSqlDatabase(); } QString threadId = QString::number((long long)QThread::currentThreadId(), 16); - QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", path + threadId); - db.setDatabaseName(path + "/library.ydb"); + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", libraryDataPath + threadId); + db.setDatabaseName(libraryDataPath + "/library.ydb"); if (!db.open()) { return QSqlDatabase(); } @@ -283,7 +290,13 @@ bool DataBaseManagement::createComicInfoTable(QSqlDatabase &database, QString ta "seriesGroup TEXT," "mainCharacterOrTeam TEXT," "review TEXT," - "tags TEXT" + "tags TEXT," + // new 9.16 fields + "imageFiltersJson TEXT," + "lastTimeImageFiltersSet INTEGER DEFAULT 0," + "lastTimeCoverSet INTEGER DEFAULT 0," + "usesExternalCover BOOLEAN DEFAULT 0," + "lastTimeMetadataSet INTEGER DEFAULT 0" ")"); return queryComicInfo.exec(); @@ -667,7 +680,8 @@ bool DataBaseManagement::importComicsInfo(QString source, QString dest) QString basePath = QString(dest).remove("/.yacreaderlibrary/library.ydb"); QString path = basePath + getComic.record().value("path").toString(); int coverPage = getComic.record().value("coverPage").toInt(); - InitialComicInfoExtractor ie(path, basePath + "/.yacreaderlibrary/covers/" + hash + ".jpg", coverPage); + auto coverPath = LibraryPaths::coverPath(basePath, hash); + InitialComicInfoExtractor ie(path, coverPath, coverPage); ie.extract(); } } @@ -851,7 +865,7 @@ int DataBaseManagement::compareVersions(const QString &v1, const QString v2) return 0; } -bool DataBaseManagement::updateToCurrentVersion(const QString &path) +bool DataBaseManagement::updateToCurrentVersion(const QString &libraryPath) { bool pre7 = false; bool pre7_1 = false; @@ -860,231 +874,256 @@ bool DataBaseManagement::updateToCurrentVersion(const QString &path) bool pre9_8 = false; bool pre9_13 = false; bool pre9_14 = false; + bool pre9_16 = false; - QString fullPath = path + "/library.ydb"; + QString libraryDatabasePath = LibraryPaths::libraryDatabasePath(libraryPath); - if (compareVersions(DataBaseManagement::checkValidDB(fullPath), "7.0.0") < 0) + if (compareVersions(DataBaseManagement::checkValidDB(libraryDatabasePath), "7.0.0") < 0) pre7 = true; - if (compareVersions(DataBaseManagement::checkValidDB(fullPath), "7.0.3") < 0) + if (compareVersions(DataBaseManagement::checkValidDB(libraryDatabasePath), "7.0.3") < 0) pre7_1 = true; - if (compareVersions(DataBaseManagement::checkValidDB(fullPath), "8.0.0") < 0) + if (compareVersions(DataBaseManagement::checkValidDB(libraryDatabasePath), "8.0.0") < 0) pre8 = true; - if (compareVersions(DataBaseManagement::checkValidDB(fullPath), "9.5.0") < 0) + if (compareVersions(DataBaseManagement::checkValidDB(libraryDatabasePath), "9.5.0") < 0) pre9_5 = true; - if (compareVersions(DataBaseManagement::checkValidDB(fullPath), "9.8.0") < 0) + if (compareVersions(DataBaseManagement::checkValidDB(libraryDatabasePath), "9.8.0") < 0) pre9_8 = true; - if (compareVersions(DataBaseManagement::checkValidDB(fullPath), "9.13.0") < 0) + if (compareVersions(DataBaseManagement::checkValidDB(libraryDatabasePath), "9.13.0") < 0) pre9_13 = true; - if (compareVersions(DataBaseManagement::checkValidDB(fullPath), "9.14.0") < 0) + if (compareVersions(DataBaseManagement::checkValidDB(libraryDatabasePath), "9.14.0") < 0) pre9_14 = true; + if (compareVersions(DataBaseManagement::checkValidDB(libraryDatabasePath), "9.16.0") < 0) + pre9_16 = true; QString connectionName = ""; bool returnValue = true; { - QSqlDatabase db = loadDatabaseFromFile(fullPath); + QSqlDatabase db = loadDatabaseFromFile(libraryDatabasePath); if (db.isValid() && db.isOpen()) { - if (pre7) // TODO: execute only if previous version was < 7.0 - { - // new 7.0 fields - QStringList columnDefs; - columnDefs << "hasBeenOpened BOOLEAN DEFAULT 0" - << "rating INTEGER DEFAULT 0" - << "currentPage INTEGER DEFAULT 1" - << "bookmark1 INTEGER DEFAULT -1" - << "bookmark2 INTEGER DEFAULT -1" - << "bookmark3 INTEGER DEFAULT -1" - << "brightness INTEGER DEFAULT -1" - << "contrast INTEGER DEFAULT -1" - << "gamma INTEGER DEFAULT -1"; - - bool successAddingColumns = addColumns("comic_info", columnDefs, db); - returnValue = returnValue && successAddingColumns; - } - // TODO update hasBeenOpened value - - if (pre7_1) { - { + if (!db.transaction()) { + QLOG_ERROR() << "Failed to start transaction for database update"; + returnValue = false; + } else { + if (pre7) { + // new 7.0 fields QStringList columnDefs; - columnDefs << "finished BOOLEAN DEFAULT 0" - << "completed BOOLEAN DEFAULT 1"; - bool successAddingColumns = addColumns("folder", columnDefs, db); - returnValue = returnValue && successAddingColumns; - } + columnDefs << "hasBeenOpened BOOLEAN DEFAULT 0" + << "rating INTEGER DEFAULT 0" + << "currentPage INTEGER DEFAULT 1" + << "bookmark1 INTEGER DEFAULT -1" + << "bookmark2 INTEGER DEFAULT -1" + << "bookmark3 INTEGER DEFAULT -1" + << "brightness INTEGER DEFAULT -1" + << "contrast INTEGER DEFAULT -1" + << "gamma INTEGER DEFAULT -1"; - { // comic_info - QStringList columnDefs; - columnDefs << "comicVineID TEXT DEFAULT NULL"; bool successAddingColumns = addColumns("comic_info", columnDefs, db); returnValue = returnValue && successAddingColumns; } - } - if (pre8) { - bool successCreatingNewTables = createV8Tables(db); - returnValue = returnValue && successCreatingNewTables; - } + if (pre7_1) { + { + QStringList columnDefs; + columnDefs << "finished BOOLEAN DEFAULT 0" + << "completed BOOLEAN DEFAULT 1"; + bool successAddingColumns = addColumns("folder", columnDefs, db); + returnValue = returnValue && successAddingColumns; + } - if (pre9_5) { - { // folder - QStringList columnDefs; - // a full library update is needed after updating the table - columnDefs << "numChildren INTEGER"; - columnDefs << "firstChildHash TEXT"; - columnDefs << "customImage TEXT"; - bool successAddingColumns = addColumns("folder", columnDefs, db); - returnValue = returnValue && successAddingColumns; + { // comic_info + QStringList columnDefs; + columnDefs << "comicVineID TEXT DEFAULT NULL"; + bool successAddingColumns = addColumns("comic_info", columnDefs, db); + returnValue = returnValue && successAddingColumns; + } } - { // comic_info - QStringList columnDefs; - columnDefs << "lastTimeOpened INTEGER"; - columnDefs << "coverSizeRatio REAL"; - columnDefs << "originalCoverSize TEXT"; - bool successAddingColumns = addColumns("comic_info", columnDefs, db); - returnValue = returnValue && successAddingColumns; - - QSqlQuery queryIndexLastTimeOpened(db); - bool successCreatingIndex = queryIndexLastTimeOpened.exec("CREATE INDEX last_time_opened_index ON comic_info (lastTimeOpened)"); - returnValue = returnValue && successCreatingIndex; + if (pre8) { + bool successCreatingNewTables = createV8Tables(db); + returnValue = returnValue && successCreatingNewTables; } - // update folders info - { - DBHelper::updateChildrenInfo(db); - } + if (pre9_5) { + { // folder + QStringList columnDefs; + // a full library update is needed after updating the table + columnDefs << "numChildren INTEGER"; + columnDefs << "firstChildHash TEXT"; + columnDefs << "customImage TEXT"; + bool successAddingColumns = addColumns("folder", columnDefs, db); + returnValue = returnValue && successAddingColumns; + } - { - QSqlQuery selectQuery(db); - selectQuery.prepare("SELECT id, hash FROM comic_info"); - selectQuery.exec(); + { // comic_info + QStringList columnDefs; + columnDefs << "lastTimeOpened INTEGER"; + columnDefs << "coverSizeRatio REAL"; + columnDefs << "originalCoverSize TEXT"; + bool successAddingColumns = addColumns("comic_info", columnDefs, db); + returnValue = returnValue && successAddingColumns; + + QSqlQuery queryIndexLastTimeOpened(db); + bool successCreatingIndex = queryIndexLastTimeOpened.exec("CREATE INDEX last_time_opened_index ON comic_info (lastTimeOpened)"); + returnValue = returnValue && successCreatingIndex; + } - db.transaction(); + // update folders info + { + DBHelper::updateChildrenInfo(db); + } - QSqlQuery updateCoverInfo(db); - updateCoverInfo.prepare("UPDATE comic_info SET coverSizeRatio = :coverSizeRatio WHERE id = :id"); + { + QSqlQuery selectQuery(db); + selectQuery.prepare("SELECT id, hash FROM comic_info"); + selectQuery.exec(); - QImageReader thumbnail; - while (selectQuery.next()) { - thumbnail.setFileName(path % "/covers/" % selectQuery.value(1).toString() % ".jpg"); + db.transaction(); - float coverSizeRatio = static_cast(thumbnail.size().width()) / thumbnail.size().height(); - updateCoverInfo.bindValue(":coverSizeRatio", coverSizeRatio); - updateCoverInfo.bindValue(":id", selectQuery.value(0)); + QSqlQuery updateCoverInfo(db); + updateCoverInfo.prepare("UPDATE comic_info SET coverSizeRatio = :coverSizeRatio WHERE id = :id"); - updateCoverInfo.exec(); - } + QImageReader thumbnail; + while (selectQuery.next()) { + auto coverPath = LibraryPaths::coverPath(libraryPath, selectQuery.value(1).toString()); + thumbnail.setFileName(coverPath); - db.commit(); - } - } + float coverSizeRatio = static_cast(thumbnail.size().width()) / thumbnail.size().height(); + updateCoverInfo.bindValue(":coverSizeRatio", coverSizeRatio); + updateCoverInfo.bindValue(":id", selectQuery.value(0)); - if (pre9_8) { - { // comic_info - QStringList columnDefs; - columnDefs << "manga BOOLEAN DEFAULT 0"; - bool successAddingColumns = addColumns("comic_info", columnDefs, db); - returnValue = returnValue && successAddingColumns; + updateCoverInfo.exec(); + } + + db.commit(); + } } - { // folder - QStringList columnDefs; - columnDefs << "manga BOOLEAN DEFAULT 0"; - bool successAddingColumns = addColumns("folder", columnDefs, db); - returnValue = returnValue && successAddingColumns; + + if (pre9_8) { + { // comic_info + QStringList columnDefs; + columnDefs << "manga BOOLEAN DEFAULT 0"; + bool successAddingColumns = addColumns("comic_info", columnDefs, db); + returnValue = returnValue && successAddingColumns; + } + { // folder + QStringList columnDefs; + columnDefs << "manga BOOLEAN DEFAULT 0"; + bool successAddingColumns = addColumns("folder", columnDefs, db); + returnValue = returnValue && successAddingColumns; + } } - } - if (pre9_13) { - { // comic_info - QStringList columnDefs; - columnDefs << "added INTEGER"; - columnDefs << "type INTEGER DEFAULT 0"; // 0 = comic, 1 = manga, 2 = manga left to right, 3 = webcomic, - columnDefs << "editor TEXT"; - columnDefs << "imprint TEXT"; - columnDefs << "teams TEXT"; - columnDefs << "locations TEXT"; - columnDefs << "series TEXT"; - columnDefs << "alternateSeries TEXT"; - columnDefs << "alternateNumber TEXT"; - columnDefs << "alternateCount INTEGER"; - columnDefs << "languageISO TEXT"; - columnDefs << "seriesGroup TEXT"; - columnDefs << "mainCharacterOrTeam TEXT"; - columnDefs << "review TEXT"; - columnDefs << "tags TEXT"; - bool successAddingColumns = addColumns("comic_info", columnDefs, db); - returnValue = returnValue && successAddingColumns; + if (pre9_13) { + { // comic_info + QStringList columnDefs; + columnDefs << "added INTEGER"; + columnDefs << "type INTEGER DEFAULT 0"; // 0 = comic, 1 = manga, 2 = manga left to right, 3 = webcomic, + columnDefs << "editor TEXT"; + columnDefs << "imprint TEXT"; + columnDefs << "teams TEXT"; + columnDefs << "locations TEXT"; + columnDefs << "series TEXT"; + columnDefs << "alternateSeries TEXT"; + columnDefs << "alternateNumber TEXT"; + columnDefs << "alternateCount INTEGER"; + columnDefs << "languageISO TEXT"; + columnDefs << "seriesGroup TEXT"; + columnDefs << "mainCharacterOrTeam TEXT"; + columnDefs << "review TEXT"; + columnDefs << "tags TEXT"; + bool successAddingColumns = addColumns("comic_info", columnDefs, db); + returnValue = returnValue && successAddingColumns; + + QSqlQuery updateTypeQueryToManga(db); + updateTypeQueryToManga.prepare("UPDATE comic_info SET type = manga"); + bool successMigratingManga = updateTypeQueryToManga.exec(); + returnValue = returnValue && successMigratingManga; + + QSqlQuery updateNumberQueryToBis(db); + updateNumberQueryToBis.prepare("UPDATE comic_info SET number = number + 0.5 WHERE isBis = 1"); + bool successMigratingBis = updateNumberQueryToBis.exec(); + returnValue = returnValue && successMigratingBis; + } + { // folder + QStringList columnDefs; + columnDefs << "added INTEGER"; + columnDefs << "updated INTEGER"; + columnDefs << "type INTEGER DEFAULT 0"; + + bool successAddingColumns = addColumns("folder", columnDefs, db); + returnValue = returnValue && successAddingColumns; + + QSqlQuery updateTypeQueryToManga(db); + updateTypeQueryToManga.prepare("UPDATE folder SET type = manga"); + bool successMigratingManga = updateTypeQueryToManga.exec(); + returnValue = returnValue && successMigratingManga; + } + } - QSqlQuery updateTypeQueryToManga(db); - updateTypeQueryToManga.prepare("UPDATE comic_info SET type = manga"); - bool successMigratingManga = updateTypeQueryToManga.exec(); - returnValue = returnValue && successMigratingManga; + // ensure that INTEGER types migrated to TEXT are actually changed in the table definition to avoid internal type castings, this happened in 9.13 but a migration wasn't shipped with that version. + if (pre9_14) { + { + bool pre9_14_successfulMigration = true; - QSqlQuery updateNumberQueryToBis(db); - updateNumberQueryToBis.prepare("UPDATE comic_info SET number = number + 0.5 WHERE isBis = 1"); - bool successMigratingBis = updateNumberQueryToBis.exec(); - returnValue = returnValue && successMigratingBis; - } - { // folder - QStringList columnDefs; - columnDefs << "added INTEGER"; - columnDefs << "updated INTEGER"; - columnDefs << "type INTEGER DEFAULT 0"; + QSqlQuery pragmaFKOFF(db); + pragmaFKOFF.prepare("PRAGMA foreign_keys=OFF"); + pre9_14_successfulMigration = pre9_14_successfulMigration && pragmaFKOFF.exec(); - bool successAddingColumns = addColumns("folder", columnDefs, db); - returnValue = returnValue && successAddingColumns; + pre9_14_successfulMigration = pre9_14_successfulMigration && createComicInfoTable(db, "comic_info_migration"); - QSqlQuery updateTypeQueryToManga(db); - updateTypeQueryToManga.prepare("UPDATE folder SET type = manga"); - bool successMigratingManga = updateTypeQueryToManga.exec(); - returnValue = returnValue && successMigratingManga; - } - } + QSqlQuery copyComicInfoToComicInfoMigration(db); + copyComicInfoToComicInfoMigration.prepare("INSERT INTO comic_info_migration SELECT * FROM comic_info"); + pre9_14_successfulMigration = pre9_14_successfulMigration && copyComicInfoToComicInfoMigration.exec(); - // ensure that INTEGER types migrated to TEXT are actually changed in the table definition to avoid internal type castings, this happened in 9.13 but a migration wasn't shipped with that version. - if (pre9_14) { - { - bool pre9_14_successfulMigration = true; + QSqlQuery dropComicInfo(db); + dropComicInfo.prepare("DROP TABLE comic_info"); + pre9_14_successfulMigration = pre9_14_successfulMigration && dropComicInfo.exec(); - QSqlQuery pragmaFKOFF(db); - pragmaFKOFF.prepare("PRAGMA foreign_keys=OFF"); - pre9_14_successfulMigration = pre9_14_successfulMigration && pragmaFKOFF.exec(); + QSqlQuery renameComicInfoMigrationToComicInfo(db); + renameComicInfoMigrationToComicInfo.prepare("ALTER TABLE comic_info_migration RENAME TO comic_info"); + pre9_14_successfulMigration = pre9_14_successfulMigration && renameComicInfoMigrationToComicInfo.exec(); - db.transaction(); + QSqlQuery pragmaFKON1("PRAGMA foreign_keys=ON", db); - pre9_14_successfulMigration = pre9_14_successfulMigration && createComicInfoTable(db, "comic_info_migration"); + returnValue = returnValue && pre9_14_successfulMigration; + } + } - QSqlQuery copyComicInfoToComicInfoMigration(db); - copyComicInfoToComicInfoMigration.prepare("INSERT INTO comic_info_migration SELECT * FROM comic_info"); - pre9_14_successfulMigration = pre9_14_successfulMigration && copyComicInfoToComicInfoMigration.exec(); + if (pre9_16) { + { // comic_info + QStringList columnDefs; + columnDefs << "imageFiltersJson TEXT"; + columnDefs << "lastTimeImageFiltersSet INTEGER DEFAULT 0"; - QSqlQuery dropComicInfo(db); - dropComicInfo.prepare("DROP TABLE comic_info"); - pre9_14_successfulMigration = pre9_14_successfulMigration && dropComicInfo.exec(); + columnDefs << "lastTimeCoverSet INTEGER DEFAULT 0"; + columnDefs << "usesExternalCover BOOLEAN DEFAULT 0"; - QSqlQuery renameComicInfoMigrationToComicInfo(db); - renameComicInfoMigrationToComicInfo.prepare("ALTER TABLE comic_info_migration RENAME TO comic_info"); - pre9_14_successfulMigration = pre9_14_successfulMigration && renameComicInfoMigrationToComicInfo.exec(); + columnDefs << "lastTimeMetadataSet INTEGER DEFAULT 0"; - if (pre9_14_successfulMigration) - db.commit(); - else - db.rollback(); + bool successAddingColumns = addColumns("comic_info", columnDefs, db); + returnValue = returnValue && successAddingColumns; + } + } - QSqlQuery pragmaFKON1("PRAGMA foreign_keys=ON", db); + if (returnValue) { + QSqlQuery updateVersion(db); + updateVersion.prepare("UPDATE db_info SET " + "version = :version"); + updateVersion.bindValue(":version", DB_VERSION); + updateVersion.exec(); - returnValue = returnValue && pre9_14_successfulMigration; + returnValue = updateVersion.numRowsAffected() > 0; } } if (returnValue) { - QSqlQuery updateVersion(db); - updateVersion.prepare("UPDATE db_info SET " - "version = :version"); - updateVersion.bindValue(":version", DB_VERSION); - updateVersion.exec(); - - returnValue = updateVersion.numRowsAffected() > 0; + if (!db.commit()) { + QLOG_ERROR() << "Failed to commit transaction for database update"; + returnValue = false; + } + } else { + db.rollback(); } } connectionName = db.connectionName(); @@ -1094,6 +1133,52 @@ bool DataBaseManagement::updateToCurrentVersion(const QString &path) return returnValue; } +DatabaseAccess DataBaseManagement::getDatabaseAccess(const QString &libraryPath) +{ + DatabaseAccess access = { false, false, false, false }; + + auto libraryDataPath = LibraryPaths::libraryDataPath(libraryPath); + auto libraryDatabasePath = LibraryPaths::libraryDatabasePath(libraryPath); + + QFile libraryDatabase(libraryDatabasePath); + if (!libraryDatabase.exists()) { + return access; + } + + access.libraryExists = true; + + QDir libraryData(libraryDataPath); + QFile testFile(libraryData.filePath("test")); + if (testFile.open(QIODevice::WriteOnly)) { + access.canWriteToFolder = true; + testFile.close(); + testFile.remove(); + } + + QString connectionName = "test"; + { + QSqlDatabase db = DataBaseManagement::loadDatabaseFromFile(libraryDatabasePath); + + QSqlQuery versionQuery(db); + bool read = versionQuery.exec("SELECT version FROM db_info"); + + read = read && versionQuery.next(); + read = read && !versionQuery.record().value(0).toString().isEmpty(); + + access.canRead = read; + + QSqlQuery writeQuery(db); + bool write = db.transaction(); + write = write && writeQuery.exec("CREATE TABLE test_write (id INTEGER);"); + write = write && db.rollback(); + + access.canWrite = write; + } + QSqlDatabase::removeDatabase(connectionName); + + return access; +} + // COMICS_INFO_EXPORTER ComicsInfoExporter::ComicsInfoExporter() : QThread() diff --git a/YACReaderLibrary/db/data_base_management.h b/YACReaderLibrary/db/data_base_management.h index e24505020..e9c67a71b 100644 --- a/YACReaderLibrary/db/data_base_management.h +++ b/YACReaderLibrary/db/data_base_management.h @@ -29,6 +29,27 @@ class ComicsInfoImporter : public QThread void run() override; }; +struct DatabaseAccess { + bool libraryExists; + bool canRead; // db read + bool canWrite; // db write + bool canWriteToFolder; // disk write + + operator QString() const + { + if (libraryExists && canRead && canWrite && canWriteToFolder) { + return "OK"; + } else if (!libraryExists) { + return "WARNING! Library does not exist on disk"; + } else { + return QString("WARNING! DB read access: %1, DB write access: %2, can write to disk: %3") + .arg(canRead ? "YES" : "NO") + .arg(canWrite ? "YES" : "NO") + .arg(canWriteToFolder ? "YES" : "NO"); + } + } +}; + class DataBaseManagement : public QObject { Q_OBJECT @@ -46,7 +67,7 @@ class DataBaseManagement : public QObject static QSqlDatabase createDatabase(QString name, QString path); static QSqlDatabase createDatabase(QString dest); // carga una base de datos desde la ruta path - static QSqlDatabase loadDatabase(QString path); + static QSqlDatabase loadDatabase(QString libraryDataPath); static QSqlDatabase loadDatabaseFromFile(QString path); static bool createTables(QSqlDatabase &database); static bool createComicInfoTable(QSqlDatabase &database, QString tableName); @@ -57,7 +78,9 @@ class DataBaseManagement : public QObject static QString checkValidDB(const QString &fullPath); // retorna "" si la DB es inválida ó la versión si es válida. static int compareVersions(const QString &v1, const QString v2); // retorna <0 si v1 < v2, 0 si v1 = v2 y >0 si v1 > v2 - static bool updateToCurrentVersion(const QString &path); + static bool updateToCurrentVersion(const QString &libraryPath); + + static DatabaseAccess getDatabaseAccess(const QString &libraryPath); }; #endif diff --git a/YACReaderLibrary/db/folder_model.cpp b/YACReaderLibrary/db/folder_model.cpp index ca7466213..ae24d0f92 100644 --- a/YACReaderLibrary/db/folder_model.cpp +++ b/YACReaderLibrary/db/folder_model.cpp @@ -13,6 +13,8 @@ #include +using namespace YACReader; + #ifdef Y_MAC_UI #include QIcon finishedFolderIcon; @@ -368,8 +370,12 @@ QVariant FolderModel::data(const QModelIndex &index, int role) const if (role == FolderModel::IdRole) return item->id; - if (role == FolderModel::CoverPathRole) - return getCoverUrlPathForComicHash(item->data(FirstChildHash).toString()); + if (role == FolderModel::CoverPathRole) { + if (item->data(FolderModel::CustomImage).toString().isEmpty()) + return getCoverUrlPathForComicHash(item->data(FirstChildHash).toString()); + else + return getCoverUrlPathForFolderId(item->id); + } if (role == FolderModel::NumChildrenRole) return item->data(NumChildren); @@ -673,6 +679,50 @@ void FolderModel::updateTreeType(YACReader::FileType type) QSqlDatabase::removeDatabase(connectionName); } +void FolderModel::setCustomFolderCover(const QModelIndex &index, const QString &path) +{ + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); + db.transaction(); + + auto item = static_cast(index.internalPointer()); + item->setData(FolderModel::CustomImage, path); + + Folder f = DBHelper::loadFolder(item->id, db); + f.customImage = path; + DBHelper::update(f, db); + + db.commit(); + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + + emit dataChanged(index, index); +} + +void FolderModel::resetFolderCover(const QModelIndex &index) +{ + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); + db.transaction(); + + auto item = static_cast(index.internalPointer()); + item->setData(FolderModel::CustomImage, ""); + + Folder f = DBHelper::loadFolder(item->id, db); + f.customImage = ""; + DBHelper::update(f, db); + + db.commit(); + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + + emit dataChanged(index, index); +} + QStringList FolderModel::getSubfoldersNames(const QModelIndex &mi) { QStringList result; @@ -852,7 +902,14 @@ QModelIndex FolderModel::addFolderAtParent(const QString &folderName, const QMod QUrl FolderModel::getCoverUrlPathForComicHash(const QString &hash) const { - return QUrl::fromLocalFile(_databasePath + "/covers/" + hash + ".jpg"); + auto coverPath = LibraryPaths::coverPathFromLibraryDataPath(_databasePath, hash); + return QUrl::fromLocalFile(coverPath); +} + +QUrl FolderModel::getCoverUrlPathForFolderId(qulonglong folderId) const +{ + auto coverPath = LibraryPaths::customFolderCoverPathFromDataPath(_databasePath, QString::number(folderId)); + return QUrl::fromLocalFile(coverPath); } void FolderModel::setShowRecent(bool showRecent) diff --git a/YACReaderLibrary/db/folder_model.h b/YACReaderLibrary/db/folder_model.h index a0f9801b1..2de737f1a 100644 --- a/YACReaderLibrary/db/folder_model.h +++ b/YACReaderLibrary/db/folder_model.h @@ -70,6 +70,8 @@ class FolderModel : public QAbstractItemModel void updateFolderFinishedStatus(const QModelIndexList &list, bool status); void updateFolderType(const QModelIndexList &list, YACReader::FileType type); void updateTreeType(YACReader::FileType type); + void setCustomFolderCover(const QModelIndex &index, const QString &path); + void resetFolderCover(const QModelIndex &index); QStringList getSubfoldersNames(const QModelIndex &mi); FolderModel *getSubfoldersModel(const QModelIndex &mi); // it creates a model that contains just the direct subfolders @@ -81,6 +83,7 @@ class FolderModel : public QAbstractItemModel QModelIndex addFolderAtParent(const QString &folderName, const QModelIndex &parent); Q_INVOKABLE QUrl getCoverUrlPathForComicHash(const QString &hash) const; + Q_INVOKABLE QUrl getCoverUrlPathForFolderId(qulonglong folderId) const; void setShowRecent(bool showRecent); void setRecentRange(int days); diff --git a/YACReaderLibrary/db_helper.cpp b/YACReaderLibrary/db_helper.cpp index 320402724..42d1bf822 100644 --- a/YACReaderLibrary/db_helper.cpp +++ b/YACReaderLibrary/db_helper.cpp @@ -21,10 +21,14 @@ #include "data_base_management.h" #include "folder.h" #include "yacreader_libraries.h" +#include "yacreader_global.h" #include "qnaturalsorting.h" #include "QsLog.h" + +using namespace YACReader; + // server YACReaderLibraries DBHelper::getLibraries() @@ -40,7 +44,7 @@ QList DBHelper::getFolderSubfoldersFromLibrary(qulonglong library QString connectionName = ""; QList list; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); list = DBHelper::getFoldersFromParent(folderId, db, false); connectionName = db.connectionName(); @@ -82,7 +86,7 @@ QList DBHelper::getFolderComicsFromLibrary(qulonglong libraryId, QString connectionName = ""; QList list; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); list = DBHelper::getComicsFromParent(folderId, db, sort); connectionName = db.connectionName(); @@ -98,7 +102,7 @@ quint32 DBHelper::getNumChildrenFromFolder(qulonglong libraryId, qulonglong fold QString connectionName = ""; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); QSqlQuery selectQuery(db); selectQuery.prepare("SELECT count(*) FROM folder WHERE parentId = :parentId and id <> 1"); @@ -126,7 +130,7 @@ qulonglong DBHelper::getParentFromComicFolderId(qulonglong libraryId, qulonglong QString connectionName = ""; Folder f; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); f = DBHelper::loadFolder(id, db); connectionName = db.connectionName(); @@ -141,7 +145,7 @@ ComicDB DBHelper::getComicInfo(qulonglong libraryId, qulonglong id) QString connectionName = ""; ComicDB comic; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); bool found; comic = DBHelper::loadComic(id, db, found); @@ -157,7 +161,7 @@ QList DBHelper::getSiblings(qulonglong libraryId, qulonglong parentId) QString connectionName = ""; QList comics; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); comics = DBHelper::getSortedComicsFromParent(parentId, db); connectionName = db.connectionName(); } @@ -174,7 +178,7 @@ QString DBHelper::getFolderName(qulonglong libraryId, qulonglong id) QString connectionName = ""; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); QSqlQuery selectQuery(db); // TODO check selectQuery.prepare("SELECT name FROM folder WHERE id = :id"); selectQuery.bindValue(":id", id); @@ -198,7 +202,7 @@ Folder DBHelper::getFolder(qulonglong libraryId, qulonglong id) QString connectionName = ""; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); QSqlQuery selectQuery(db); // TODO check selectQuery.prepare("SELECT * FROM folder WHERE id = :id"); selectQuery.bindValue(":id", id); @@ -259,7 +263,7 @@ QList DBHelper::getLabelComics(qulonglong libraryId, qulonglong labelId QString connectionName = ""; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); QSqlQuery selectQuery(db); selectQuery.prepare("SELECT c.id,c.fileName,ci.title,ci.currentPage,ci.numPages,ci.hash,ci.read,ci.coverSizeRatio " "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " @@ -300,7 +304,7 @@ QList DBHelper::getFavorites(qulonglong libraryId) QString connectionName = ""; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); QSqlQuery selectQuery(db); selectQuery.prepare("SELECT c.id,c.fileName,ci.title,ci.currentPage,ci.numPages,ci.hash,ci.read,ci.coverSizeRatio " "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " @@ -341,7 +345,7 @@ QList DBHelper::getReading(qulonglong libraryId) QString connectionName = ""; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); QSqlQuery selectQuery(db); selectQuery.prepare("SELECT c.id,c.parentId,c.fileName,ci.title,ci.currentPage,ci.numPages,ci.hash,ci.read,ci.coverSizeRatio,ci.number " "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " @@ -381,7 +385,7 @@ QList DBHelper::getReadingLists(qulonglong libraryId) QList list; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); QSqlQuery selectQuery("SELECT * from reading_list WHERE parentId IS NULL ORDER BY name DESC", db); @@ -420,7 +424,7 @@ QList DBHelper::getReadingListFullContent(qulonglong libraryId, qulongl QString connectionName = ""; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); QList ids; ids << readingListId; @@ -618,7 +622,7 @@ void DBHelper::update(qulonglong libraryId, ComicInfo &comicInfo) QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); DBHelper::update(&comicInfo, db); connectionName = db.connectionName(); } @@ -708,7 +712,14 @@ void DBHelper::update(ComicInfo *comicInfo, QSqlDatabase &db) "seriesGroup = :seriesGroup," "mainCharacterOrTeam = :mainCharacterOrTeam," "review = :review," - "tags = :tags" + "tags = :tags," + + // new 9.16 fields + "imageFiltersJson = :imageFiltersJson," + "lastTimeImageFiltersSet = :lastTimeImageFiltersSet," + "lastTimeCoverSet = :lastTimeCoverSet," + "usesExternalCover = :usesExternalCover," + "lastTimeMetadataSet = :lastTimeMetadataSet" //-- " WHERE id = :id"); @@ -786,6 +797,12 @@ void DBHelper::update(ComicInfo *comicInfo, QSqlDatabase &db) updateComicInfo.bindValue(":review", comicInfo->review); updateComicInfo.bindValue(":tags", comicInfo->tags); + updateComicInfo.bindValue(":imageFiltersJson", comicInfo->imageFiltersJson); + updateComicInfo.bindValue(":lastTimeImageFiltersSet", comicInfo->lastTimeImageFiltersSet); + updateComicInfo.bindValue(":lastTimeCoverSet", comicInfo->lastTimeCoverSet); + updateComicInfo.bindValue(":usesExternalCover", comicInfo->usesExternalCover); + updateComicInfo.bindValue(":lastTimeMetadataSet", comicInfo->lastTimeMetadataSet); + updateComicInfo.exec(); QLOG_INFO() << updateComicInfo.lastError().databaseText(); @@ -820,13 +837,34 @@ void DBHelper::updateAdded(ComicInfo *comicInfo, QSqlDatabase &db) void DBHelper::update(const Folder &folder, QSqlDatabase &db) { QSqlQuery updateFolderInfo(db); + updateFolderInfo.prepare("UPDATE folder SET " + "parentId = :parentId, " + "name = :name, " + "path = :path, " "finished = :finished, " - "completed = :completed " - "WHERE id = :id "); + "completed = :completed, " + "numChildren = :numChildren, " + "firstChildHash = :firstChildHash, " + "customImage = :customImage, " + "type = :type, " + "added = :added, " + "updated = :updated " + "WHERE id = :id"); + + updateFolderInfo.bindValue(":parentId", folder.parentId); + updateFolderInfo.bindValue(":name", folder.name); + updateFolderInfo.bindValue(":path", folder.path); updateFolderInfo.bindValue(":finished", folder.finished ? 1 : 0); updateFolderInfo.bindValue(":completed", folder.completed ? 1 : 0); + updateFolderInfo.bindValue(":numChildren", folder.numChildren); + updateFolderInfo.bindValue(":firstChildHash", folder.firstChildHash); + updateFolderInfo.bindValue(":customImage", folder.customImage); + updateFolderInfo.bindValue(":type", static_cast(folder.type)); + updateFolderInfo.bindValue(":added", folder.added); + updateFolderInfo.bindValue(":updated", folder.updated); updateFolderInfo.bindValue(":id", folder.id); + updateFolderInfo.exec(); } @@ -872,6 +910,10 @@ Folder DBHelper::updateChildrenInfo(qulonglong folderId, QSqlDatabase &db) } } + if (folder.numChildren == subfolders.count() + comics.count() && folder.firstChildHash == coverHash) { + return folder; + } + folder.numChildren = subfolders.count() + comics.count(); folder.firstChildHash = coverHash; @@ -894,6 +936,9 @@ Folder DBHelper::updateChildrenInfo(qulonglong folderId, QSqlDatabase &db) void DBHelper::updateChildrenInfo(QSqlDatabase &db) { + // measure time + auto start = std::chrono::high_resolution_clock::now(); + QSqlQuery selectQuery(db); // TODO check selectQuery.prepare("SELECT id FROM folder f WHERE f.parentId = 1 AND f.id <> 1"); selectQuery.exec(); @@ -901,6 +946,12 @@ void DBHelper::updateChildrenInfo(QSqlDatabase &db) while (selectQuery.next()) { DBHelper::updateChildrenInfo(selectQuery.value(0).toULongLong(), db); } + + auto end = std::chrono::high_resolution_clock::now(); + + QString time = QString::number(std::chrono::duration_cast(end - start).count()); + QString message = "updateChildrenInfo took " + time + "ms"; + QLOG_INFO() << message; } void DBHelper::updateProgress(qulonglong libraryId, const ComicInfo &comicInfo) @@ -908,7 +959,7 @@ void DBHelper::updateProgress(qulonglong libraryId, const ComicInfo &comicInfo) QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); bool found; ComicDB comic = DBHelper::loadComic(comicInfo.id, db, found); @@ -924,12 +975,47 @@ void DBHelper::updateProgress(qulonglong libraryId, const ComicInfo &comicInfo) QSqlDatabase::removeDatabase(connectionName); } +void DBHelper::updateImageFilters(qulonglong libraryId, const ComicInfo &comicInfo) +{ + QString libraryPath = DBHelper::getLibraries().getPath(libraryId); + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); + + bool found; + ComicDB comic = DBHelper::loadComic(comicInfo.id, db, found); + + if (found && comic.info.lastTimeImageFiltersSet.toULongLong() < comicInfo.lastTimeImageFiltersSet.toULongLong()) { + QSqlQuery updateComicInfo(db); + updateComicInfo.prepare("UPDATE comic_info SET " + "lastTimeImageFiltersSet = :lastTimeImageFiltersSet, " + "imageFiltersJson = :imageFiltersJson " + " WHERE id = :id "); + + updateComicInfo.bindValue(":lastTimeImageFiltersSet", comicInfo.lastTimeImageFiltersSet.toULongLong()); + updateComicInfo.bindValue(":imageFiltersJson", comicInfo.imageFiltersJson); + updateComicInfo.bindValue(":id", comic.info.id); + + auto ret = updateComicInfo.exec(); + if (!ret) { + QLOG_ERROR() << "Error updating image filters for comic ID:" << comicInfo.id; + QLOG_ERROR() << updateComicInfo.lastError().databaseText(); + QLOG_ERROR() << updateComicInfo.lastError().text(); + } + } + + connectionName = db.connectionName(); + } + + QSqlDatabase::removeDatabase(connectionName); +} + void DBHelper::setComicAsReading(qulonglong libraryId, const ComicInfo &comicInfo) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); bool found; ComicDB comic = DBHelper::loadComic(comicInfo.id, db, found); @@ -970,7 +1056,7 @@ void DBHelper::updateFromRemoteClient(qulonglong libraryId, const ComicInfo &com QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); bool found; ComicDB comic = DBHelper::loadComic(comicInfo.id, db, found); @@ -998,7 +1084,7 @@ void DBHelper::updateFromRemoteClient(qulonglong libraryId, const ComicInfo &com QSqlDatabase::removeDatabase(connectionName); } -QMap> DBHelper::updateFromRemoteClient(const QMap> &comics, bool clientSendsHasBeenOpened) +QMap> DBHelper::updateFromRemoteClient(const QMap> &comics, bool clientSendsHasBeenOpened, bool clientSendsImageFilters) { QMap> moreRecentComics; @@ -1009,7 +1095,7 @@ QMap> DBHelper::updateFromRemoteClient(const QMap> DBHelper::updateFromRemoteClient(const QMap> DBHelper::updateFromRemoteClient(const QMap comicInfo.lastTimeImageFiltersSet.toULongLong()) { + isMoreRecent = true; + } + } + comic.info.currentPage = qMax(comic.info.currentPage, comicInfo.currentPage); if (comic.info.currentPage == comic.info.numPages) @@ -1062,12 +1156,21 @@ QMap> DBHelper::updateFromRemoteClient(const QMap 0) comic.info.rating = comicInfo.rating; + if (clientSendsImageFilters) { + if (comic.info.lastTimeImageFiltersSet.toULongLong() < comicInfo.lastTimeImageFiltersSet.toULongLong()) { + comic.info.imageFiltersJson = comicInfo.imageFiltersJson; + comic.info.lastTimeImageFiltersSet = comicInfo.lastTimeImageFiltersSet; + } + } + updateComicInfo.bindValue(":read", comic.info.read ? 1 : 0); updateComicInfo.bindValue(":currentPage", comic.info.currentPage); updateComicInfo.bindValue(":hasBeenOpened", comic.info.hasBeenOpened ? 1 : 0); updateComicInfo.bindValue(":lastTimeOpened", comic.info.lastTimeOpened); updateComicInfo.bindValue(":id", comic.info.id); updateComicInfo.bindValue(":rating", comic.info.rating); + updateComicInfo.bindValue(":imageFiltersJson", comic.info.imageFiltersJson); + updateComicInfo.bindValue(":lastTimeImageFiltersSet", comic.info.lastTimeImageFiltersSet); updateComicInfo.exec(); if (isMoreRecent) { @@ -1100,7 +1203,7 @@ void DBHelper::updateFromRemoteClientWithHash(const QList &comics) QString libraryPath = DBHelper::getLibraries().getPath(libraries.getId(name)); QString connectionName = ""; { - QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary"); + QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); db.transaction(); @@ -1595,7 +1698,7 @@ QList