Compare commits
51
Commits
1.4.0
...
a01d640c27
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a01d640c27 | ||
|
|
16800df138 | ||
|
|
7e8fd4ea21 | ||
|
|
04fa491cd8 | ||
|
|
dbf8c09368 | ||
|
|
369a17ed7d | ||
|
|
b19a4f8723 | ||
|
|
a8e5464e9f | ||
|
|
46f46480cb | ||
|
|
c06f900ac5 | ||
|
|
31303a20c7 | ||
|
|
319fd64fc6 | ||
|
|
cdd0b764a7 | ||
|
|
2a69482adb | ||
|
|
fc3ef1cd7f | ||
|
|
1de1517a81 | ||
|
|
04a4ab9bff | ||
|
|
011c1e858f | ||
|
|
4fe09757b3 | ||
|
|
82f1f10cca | ||
|
|
60eebea741 | ||
|
|
9b151e2fea | ||
|
|
9db9c3e9f7 | ||
|
|
9a18a41b8f | ||
|
|
c93ed94210 | ||
|
|
f4d272e014 | ||
|
|
d536e8c611 | ||
|
|
c8dfccb460 | ||
|
|
6fde9474ae | ||
|
|
c19fcaa560 | ||
|
|
602e2cccf8 | ||
|
|
3c1a3c54a7 | ||
|
|
032752faf9 | ||
|
|
457647c59f | ||
|
|
821e0f9fd6 | ||
|
|
26f51e418d | ||
|
|
cd2eac44f1 | ||
|
|
f0bb945c7d | ||
|
|
870e472f53 | ||
|
|
76677381de | ||
|
|
d2bd521ad0 | ||
|
|
265e894b3f | ||
|
|
6339a93c6c | ||
|
|
6da53ced81 | ||
|
|
8f0d4da7ad | ||
|
|
9c8ffb6c1b | ||
|
|
4c5c5e6fc6 | ||
|
|
b8cb10bb33 | ||
|
|
6e2f40633e | ||
|
|
a0ba7c1d17 | ||
|
|
b6d759d7c6 |
@@ -1,35 +0,0 @@
|
|||||||
name: deepin 25 CI
|
|
||||||
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-deepin-25-deb:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: linuxdeepin/deepin:crimson
|
|
||||||
options: --user root
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Update package lists and install basic tools
|
|
||||||
run: |
|
|
||||||
apt update
|
|
||||||
apt install -y git ca-certificates curl file
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Get build dept.
|
|
||||||
run: |
|
|
||||||
apt install -y cmake qt6-base-dev qt6-svg-dev qt6-tools-dev libexiv2-dev
|
|
||||||
- name: Build it
|
|
||||||
run: |
|
|
||||||
git config --global --add safe.directory '*' # to avoid "fatal: detected dubious ownership in repository at xxx"
|
|
||||||
cmake -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr .
|
|
||||||
cmake --build build -j
|
|
||||||
cd build
|
|
||||||
cpack -G DEB
|
|
||||||
- name: Try install it
|
|
||||||
run: |
|
|
||||||
cd build
|
|
||||||
apt install -y ./*.deb
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: deepin-25-deb-package
|
|
||||||
path: build/*.deb
|
|
||||||
@@ -8,11 +8,11 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- name: Install Qt
|
- name: Install Qt
|
||||||
uses: jurplel/install-qt-action@v4
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: '6.10.2'
|
version: '6.10.3'
|
||||||
modules: 'qtimageformats'
|
modules: 'qtimageformats'
|
||||||
cache: true
|
cache: true
|
||||||
- name: Install Conan and Dependencies
|
- name: Install Conan and Dependencies
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
macdeployqt ./ppic.app -dmg
|
macdeployqt ./ppic.app -dmg
|
||||||
ls
|
ls
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: "macos-bundle"
|
name: "macos-bundle"
|
||||||
path: "*.dmg"
|
path: "*.dmg"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ jobs:
|
|||||||
ubuntu-24-04-build:
|
ubuntu-24-04-build:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- name: Get build dept.
|
- name: Get build dept.
|
||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt update
|
||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd build
|
cd build
|
||||||
sudo apt install ./*.deb
|
sudo apt install ./*.deb
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: ubuntu-24.04-deb-package
|
name: ubuntu-24.04-deb-package
|
||||||
path: build/*.deb
|
path: build/*.deb
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- qt_ver: '6.10.2'
|
- qt_ver: '6.10.3'
|
||||||
vs: '2022'
|
vs: '2022'
|
||||||
aqt_arch: 'win64_msvc2022_64'
|
aqt_arch: 'win64_msvc2022_64'
|
||||||
msvc_arch: 'x64'
|
msvc_arch: 'x64'
|
||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- name: Install Qt
|
- name: Install Qt
|
||||||
uses: jurplel/install-qt-action@v4
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
nmake
|
nmake
|
||||||
nmake clean
|
nmake clean
|
||||||
windeployqt --verbose=2 --no-quick-import --no-translations --no-opengl-sw --no-system-d3d-compiler --no-system-dxc-compiler --skip-plugin-types tls,networkinformation release\ppic.exe
|
windeployqt --verbose=2 --no-quick-import --no-translations --no-opengl-sw --no-system-d3d-compiler --no-system-dxc-compiler --skip-plugin-types tls,networkinformation release\ppic.exe
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: "windows-msvc${{ matrix.vs }}-qt${{ matrix.qt_ver }}-qmake-package"
|
name: "windows-msvc${{ matrix.vs }}-qt${{ matrix.qt_ver }}-qmake-package"
|
||||||
path: release/*
|
path: release/*
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- qt_ver: '6.10.2'
|
- qt_ver: '6.10.3'
|
||||||
vs: '2022'
|
vs: '2022'
|
||||||
aqt_arch: 'win64_msvc2022_64'
|
aqt_arch: 'win64_msvc2022_64'
|
||||||
msvc_arch: 'x64'
|
msvc_arch: 'x64'
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- name: Install Qt
|
- name: Install Qt
|
||||||
uses: jurplel/install-qt-action@v4
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
@@ -79,9 +79,9 @@ jobs:
|
|||||||
ren .\exiv2-0.28.7-2022msvc-AMD64 dependencies_bin
|
ren .\exiv2-0.28.7-2022msvc-AMD64 dependencies_bin
|
||||||
echo ::endgroup::
|
echo ::endgroup::
|
||||||
echo ::group::===== zlib =====
|
echo ::group::===== zlib =====
|
||||||
curl -fsSL -o zlib_src.zip https://zlib.net/zlib131.zip
|
curl -fsSL -o zlib_src.zip https://github.com/madler/zlib/releases/download/v1.3.2/zlib132.zip
|
||||||
7z x zlib_src.zip -y -o"dependencies_src"
|
7z x zlib_src.zip -y -o"dependencies_src"
|
||||||
ren .\dependencies_src\zlib-1.3.1 zlib || goto :error
|
ren .\dependencies_src\zlib-1.3.2 zlib || goto :error
|
||||||
cmake ./dependencies_src/zlib -Bbuild_dependencies/zlib -DCMAKE_INSTALL_PREFIX="dependencies_bin" || goto :error
|
cmake ./dependencies_src/zlib -Bbuild_dependencies/zlib -DCMAKE_INSTALL_PREFIX="dependencies_bin" || goto :error
|
||||||
cmake --build build_dependencies/zlib --config Release --target=install || goto :error
|
cmake --build build_dependencies/zlib --config Release --target=install || goto :error
|
||||||
echo ::endgroup::
|
echo ::endgroup::
|
||||||
@@ -125,17 +125,13 @@ jobs:
|
|||||||
cmake --build build --config Release
|
cmake --build build --config Release
|
||||||
cmake --install build --config Release --prefix "%PWD%\build"
|
cmake --install build --config Release --prefix "%PWD%\build"
|
||||||
echo ::endgroup::
|
echo ::endgroup::
|
||||||
echo ::group::===== passoc stuff =====
|
|
||||||
curl -fsSL -o passoc-standalone.zip https://github.com/BLumia/pineapple-assoc-manager/releases/latest/download/pineapple-assoc-manager-qt${{ matrix.qt_ver }}-standalone.zip
|
|
||||||
7z x passoc-standalone.zip passoc.exe -y -o"build/bin"
|
|
||||||
echo ::endgroup::
|
|
||||||
:: ------ pkg ------
|
:: ------ pkg ------
|
||||||
windeployqt --verbose=2 --no-compiler-runtime --no-quick-import --no-translations --no-network --no-opengl-sw --no-system-d3d-compiler --no-system-dxc-compiler --skip-plugin-types tls,networkinformation build\bin\ppic.exe
|
windeployqt --verbose=2 --no-compiler-runtime --no-quick-import --no-translations --no-network --no-opengl-sw --no-system-d3d-compiler --no-system-dxc-compiler --skip-plugin-types tls,networkinformation build\bin\ppic.exe
|
||||||
robocopy ./dependencies_bin/bin build/bin *.dll
|
robocopy ./dependencies_bin/bin build/bin *.dll
|
||||||
if ErrorLevel 8 (exit /B 1)
|
if ErrorLevel 8 (exit /B 1)
|
||||||
copy LICENSE build\bin
|
copy LICENSE build\bin
|
||||||
exit /B 0
|
exit /B 0
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: "windows-msvc${{ matrix.vs }}-qt${{ matrix.qt_ver }}-cmake-package"
|
name: "windows-msvc${{ matrix.vs }}-qt${{ matrix.qt_ver }}-cmake-package"
|
||||||
path: build/bin/*
|
path: build/bin/*
|
||||||
|
|||||||
+10
-16
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
project(pineapple-pictures VERSION 1.3.0) # don't forget to update NEWS file and AppStream metadata.
|
project(pineapple-pictures VERSION 1.5.0) # don't forget to update NEWS file and AppStream metadata.
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
include(FeatureSummary)
|
include(FeatureSummary)
|
||||||
@@ -16,10 +16,11 @@ set (CMAKE_CXX_STANDARD 17)
|
|||||||
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set (CMAKE_AUTOMOC ON)
|
set (CMAKE_AUTOMOC ON)
|
||||||
set (CMAKE_AUTORCC ON)
|
set (CMAKE_AUTORCC ON)
|
||||||
|
set (CMAKE_AUTOUIC ON)
|
||||||
|
|
||||||
find_package(QT NAMES Qt6 REQUIRED COMPONENTS Core)
|
find_package(QT NAMES Qt6 REQUIRED COMPONENTS Core)
|
||||||
|
|
||||||
set (QT_MINIMUM_VERSION "6.4")
|
set (QT_MINIMUM_VERSION "6.5")
|
||||||
|
|
||||||
find_package(Qt${QT_VERSION_MAJOR} ${QT_MINIMUM_VERSION} REQUIRED
|
find_package(Qt${QT_VERSION_MAJOR} ${QT_MINIMUM_VERSION} REQUIRED
|
||||||
COMPONENTS Widgets Svg SvgWidgets LinguistTools
|
COMPONENTS Widgets Svg SvgWidgets LinguistTools
|
||||||
@@ -38,15 +39,12 @@ endif ()
|
|||||||
|
|
||||||
set (PPIC_CPP_FILES
|
set (PPIC_CPP_FILES
|
||||||
app/main.cpp
|
app/main.cpp
|
||||||
app/framelesswindow.cpp
|
|
||||||
app/mainwindow.cpp
|
app/mainwindow.cpp
|
||||||
app/actionmanager.cpp
|
app/actionmanager.cpp
|
||||||
app/graphicsview.cpp
|
app/graphicsview.cpp
|
||||||
app/graphicsscene.cpp
|
app/graphicsscene.cpp
|
||||||
app/bottombuttongroup.cpp
|
app/bottombuttongroup.cpp
|
||||||
app/navigatorview.cpp
|
app/navigatorview.cpp
|
||||||
app/opacityhelper.cpp
|
|
||||||
app/toolbutton.cpp
|
|
||||||
app/settings.cpp
|
app/settings.cpp
|
||||||
app/settingsdialog.cpp
|
app/settingsdialog.cpp
|
||||||
app/aboutdialog.cpp
|
app/aboutdialog.cpp
|
||||||
@@ -54,20 +52,16 @@ set (PPIC_CPP_FILES
|
|||||||
app/metadatadialog.cpp
|
app/metadatadialog.cpp
|
||||||
app/exiv2wrapper.cpp
|
app/exiv2wrapper.cpp
|
||||||
app/playlistmanager.cpp
|
app/playlistmanager.cpp
|
||||||
app/shortcutedit.cpp
|
|
||||||
app/fileopeneventhandler.cpp
|
app/fileopeneventhandler.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set (PPIC_HEADER_FILES
|
set (PPIC_HEADER_FILES
|
||||||
app/framelesswindow.h
|
|
||||||
app/mainwindow.h
|
app/mainwindow.h
|
||||||
app/actionmanager.h
|
app/actionmanager.h
|
||||||
app/graphicsview.h
|
app/graphicsview.h
|
||||||
app/graphicsscene.h
|
app/graphicsscene.h
|
||||||
app/bottombuttongroup.h
|
app/bottombuttongroup.h
|
||||||
app/navigatorview.h
|
app/navigatorview.h
|
||||||
app/opacityhelper.h
|
|
||||||
app/toolbutton.h
|
|
||||||
app/settings.h
|
app/settings.h
|
||||||
app/settingsdialog.h
|
app/settingsdialog.h
|
||||||
app/aboutdialog.h
|
app/aboutdialog.h
|
||||||
@@ -75,10 +69,15 @@ set (PPIC_HEADER_FILES
|
|||||||
app/metadatadialog.h
|
app/metadatadialog.h
|
||||||
app/exiv2wrapper.h
|
app/exiv2wrapper.h
|
||||||
app/playlistmanager.h
|
app/playlistmanager.h
|
||||||
app/shortcutedit.h
|
|
||||||
app/fileopeneventhandler.h
|
app/fileopeneventhandler.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set (PPIC_UI_FILES
|
||||||
|
app/settingsdialog.ui
|
||||||
|
app/aboutdialog.ui
|
||||||
|
app/metadatadialog.ui
|
||||||
|
)
|
||||||
|
|
||||||
set (PPIC_QRC_FILES
|
set (PPIC_QRC_FILES
|
||||||
assets/resources.qrc
|
assets/resources.qrc
|
||||||
)
|
)
|
||||||
@@ -101,6 +100,7 @@ endif ()
|
|||||||
add_executable (${EXE_NAME}
|
add_executable (${EXE_NAME}
|
||||||
${PPIC_HEADER_FILES}
|
${PPIC_HEADER_FILES}
|
||||||
${PPIC_CPP_FILES}
|
${PPIC_CPP_FILES}
|
||||||
|
${PPIC_UI_FILES}
|
||||||
${PPIC_QRC_FILES}
|
${PPIC_QRC_FILES}
|
||||||
${PPIC_RC_FILES}
|
${PPIC_RC_FILES}
|
||||||
)
|
)
|
||||||
@@ -271,12 +271,6 @@ if (DEFINED QM_FILE_INSTALL_DIR)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
install(
|
|
||||||
FILES dist/passoc/ppic.pacfg
|
|
||||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
|
||||||
RENAME default-assoc.pacfg
|
|
||||||
)
|
|
||||||
|
|
||||||
install(
|
install(
|
||||||
FILES
|
FILES
|
||||||
dist/passoc/generic.ico
|
dist/passoc/generic.ico
|
||||||
|
|||||||
@@ -1,3 +1,32 @@
|
|||||||
|
Version 1.5.0
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
Released: 2026-07-04
|
||||||
|
|
||||||
|
Features:
|
||||||
|
* Introduced an optional titlebar that display file name and allows drag and double-click to move and toggle maximize state
|
||||||
|
* Windows: Now the application will use the system's built-in animation for open window animation
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Show empty url list should also reset window title to blank
|
||||||
|
|
||||||
|
Miscellaneous:
|
||||||
|
* Update translations
|
||||||
|
* Add vcs-browser link to AppStream metadata
|
||||||
|
|
||||||
|
Contributors:
|
||||||
|
Heimen Stoffels (vistaus), VenusGirl (VenusGirl)
|
||||||
|
|
||||||
|
Version 1.4.1
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
Released: 2026-04-05
|
||||||
|
|
||||||
|
Miscellaneous:
|
||||||
|
* Update translations
|
||||||
|
* Update Qt version to 6.10.3 for Windows and macOS pre-built binaries
|
||||||
|
|
||||||
|
Contributors:
|
||||||
|
Heimen Stoffels (vistaus), Loc Huynh (hthienloc)
|
||||||
|
|
||||||
Version 1.4.0
|
Version 1.4.0
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
Released: 2026-02-11
|
Released: 2026-02-11
|
||||||
|
|||||||
@@ -1,4 +1,17 @@
|
|||||||
Yet another image viewer.
|
# Sarasas Chip Workshop Picture
|
||||||
|
|
||||||
|
Yet another legacy Windows Photo Viewer with cross-platform support.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> SarasaCW Picture are still in refactoring. Please do not use it until this message removes.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Sarasas Chip Workshop Picture (abbr. SarasaCW Picture) is a lightweight image viewer that has cross-platform support and similar user interface about legacy Windows Photo Viewer. It allows you view JPEG, PNG, GIF, SVG, PSD, KRA, XCF, TGA, HDR, AVIF and some other frequently used image formats files quickly and easily without changing your long-term Windows usage experience.
|
||||||
|
|
||||||
|
SarasaCW Picture is the **hard fork** of [Pineapple Pictures](https://github.com/BLumia/pineapple-pictures) since Pineapple Pictures 1.4.1 version.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|CI|Build Status|
|
|CI|Build Status|
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -8,10 +21,6 @@ Yet another image viewer.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Pineapple Pictures is a lightweight image viewer that allows you view JPEG, PNG, GIF, SVG, PSD, KRA, XCF, TGA, HDR, AVIF and some other frequently used image formats files quickly and easily, and also provide a Stay-on-Top window setting that allows you pin the window so you can use it to pin a reference image at the top and then you can work with other software.
|
|
||||||
|
|
||||||
## Get it!
|
## Get it!
|
||||||
|
|
||||||
### Maintained by the original author
|
### Maintained by the original author
|
||||||
@@ -71,3 +80,5 @@ It's possible to build it under Windows, Linux, macOS, and maybe other desktop p
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
Pineapple Pictures as a whole is licensed under MIT license. Individual files may have a different, but compatible license.
|
Pineapple Pictures as a whole is licensed under MIT license. Individual files may have a different, but compatible license.
|
||||||
|
|
||||||
|
-->
|
||||||
|
|||||||
+17
-4
@@ -1,3 +1,18 @@
|
|||||||
|
# Sarasas Chip Workshop 照片查看器
|
||||||
|
|
||||||
|
简单轻量的、具有跨平台支持的、经典样式的 Windows 照片查看器。
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> SarasaCW 照片查看器还在重构中。在这条信息被移除之前请不要使用它。
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
|
||||||
|
Sarasas Chip Workshop 照片查看器(简称 SarasaCW 照片查看器)是一个轻量级的图片查看器。支持跨平台。界面风格类似于经典的 Windows 照片查看器。它可以让你快速轻松地浏览 JPEG、PNG、GIF、SVG、PSD、KRA、XCF、TGA、HDR、AVIF 以及其他一些常用的图片格式,且无需改变你长期的 Windows 使用习惯。
|
||||||
|
|
||||||
|
SarasaCW 照片查看器是[菠萝看图](https://github.com/BLumia/pineapple-pictures)的**硬分叉**,该分叉自菠萝看图 1.4.1 版本开始。
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
简单轻量的跨平台看图工具。
|
简单轻量的跨平台看图工具。
|
||||||
|
|
||||||
|CI|构建状态|
|
|CI|构建状态|
|
||||||
@@ -8,10 +23,6 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 简介
|
|
||||||
|
|
||||||
菠萝看图是一个轻量图像查看器,允许你简单快捷的查看 JPEG, PNG, GIF, SVG, PSD, KRA, XCF, TGA, HDR, AVIF 等常用格式的图像文件,并提供了置顶窗口的选项以便你在使用其它软件时也可以将参考图片固定在顶端。
|
|
||||||
|
|
||||||
## 立即获取!
|
## 立即获取!
|
||||||
|
|
||||||
### 由原作者维护
|
### 由原作者维护
|
||||||
@@ -72,3 +83,5 @@ $ cmake --build . # 如果你使用 Makefile 作为 CMake 生成器,也可以
|
|||||||
## 许可协议
|
## 许可协议
|
||||||
|
|
||||||
菠萝看图整体使用 MIT 协议进行发布。项目所随的部分源文件可能具备不同但与之兼容的许可协议。
|
菠萝看图整体使用 MIT 协议进行发布。项目所随的部分源文件可能具备不同但与之兼容的许可协议。
|
||||||
|
|
||||||
|
-->
|
||||||
|
|||||||
+62
-144
@@ -3,176 +3,94 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "aboutdialog.h"
|
#include "aboutdialog.h"
|
||||||
|
#include "ui_aboutdialog.h"
|
||||||
|
|
||||||
#include <QAbstractButton>
|
|
||||||
#include <QDialogButtonBox>
|
|
||||||
#include <QTextBrowser>
|
|
||||||
#include <QTabWidget>
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QLabel>
|
|
||||||
#include <QPushButton>
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
|
||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
|
|
||||||
|
static QString loadEmbeddedHtml(QString&& path) {
|
||||||
|
QFile htmlFile(path);
|
||||||
|
bool canOpenFile = htmlFile.open(QIODevice::ReadOnly);
|
||||||
|
QByteArray htmlRawContent = canOpenFile ? htmlFile.readAll() : QByteArrayLiteral("");
|
||||||
|
return QString::fromUtf8(htmlRawContent);
|
||||||
|
}
|
||||||
|
|
||||||
AboutDialog::AboutDialog(QWidget *parent)
|
AboutDialog::AboutDialog(QWidget *parent)
|
||||||
: QDialog(parent)
|
: QDialog(parent), ui(new QT_PREPEND_NAMESPACE(Ui::AboutDialog))
|
||||||
, m_tabWidget(new QTabWidget)
|
|
||||||
, m_buttonBox(new QDialogButtonBox)
|
|
||||||
, m_helpTextEdit(new QTextBrowser)
|
|
||||||
, m_aboutTextEdit(new QTextBrowser)
|
|
||||||
, m_specialThanksTextEdit(new QTextBrowser)
|
|
||||||
, m_licenseTextEdit(new QTextBrowser)
|
|
||||||
, m_3rdPartyLibsTextEdit(new QTextBrowser)
|
|
||||||
{
|
{
|
||||||
this->setWindowTitle(tr("About"));
|
ui->setupUi(this);
|
||||||
|
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
|
||||||
|
|
||||||
const QStringList helpStr {
|
// blumia: Chain two arg() here since it seems lupdate will remove one of them if we use
|
||||||
u"<p>%1</p>"_s.arg(tr("Launch application with image file path as argument to load the file.")),
|
// the old `arg(QCoreApp::translate(), tr())` way, but it's worth to mention
|
||||||
u"<p>%1</p>"_s.arg(tr("Drag and drop image file onto the window is also supported.")),
|
// `arg(QCoreApp::translate(), this->tr())` works, but lupdate will complain about the usage.
|
||||||
u"<p>%1</p>"_s.arg(tr("None of the operations in this application will alter the pictures on disk.")),
|
const QString HELP_HTML = loadEmbeddedHtml(u":/plain/help.html"_s)
|
||||||
u"<p>%1</p>"_s.arg(tr("Context menu option explanation:")),
|
.arg(tr("General Help"))
|
||||||
u"<ul>"_s,
|
.arg(tr("Launch application with image file path as argument to load the file."))
|
||||||
// blumia: Chain two arg() here since it seems lupdate will remove one of them if we use
|
.arg(tr("Passing in a single image file will load all the images in that directory and start viewing from that image."))
|
||||||
// the old `arg(QCoreApp::translate(), tr())` way, but it's worth to mention
|
.arg(tr("Passing in multiple image files lets you view them one by one."))
|
||||||
// `arg(QCoreApp::translate(), this->tr())` works, but lupdate will complain about the usage.
|
.arg(tr("Drag and drop image file onto the window is also supported."))
|
||||||
u"<li><b>%1</b>:<br/>%2</li>"_s
|
.arg(tr("None of the operations in this application will alter the pictures on disk."))
|
||||||
.arg(QCoreApplication::translate("MainWindow", "Stay on top"))
|
.arg(tr("Context Menu Help"))
|
||||||
.arg(tr("Make window stay on top of all other windows.")),
|
.arg(tr("Keep transformation", "The same meaning of MainWindow's Keep transformation."))
|
||||||
u"<li><b>%1</b>:<br/>%2</li>"_s
|
.arg(tr("Avoid resetting the zoom/rotation/flip state that was applied to the image view when switching between images."))
|
||||||
.arg(QCoreApplication::translate("MainWindow", "Protected mode"))
|
.arg(tr("Checkerboard", "The same meaning of MainWindow's Checkerboard."))
|
||||||
.arg(tr("Avoid close window accidentally. (eg. by double clicking the window)")),
|
.arg(tr("Checkerboard light and dark theme can be adjusted in the configuration window, or easily switch it by holding Shift and clicking the button."));
|
||||||
u"<li><b>%1</b>:<br/>%2</li>"_s
|
|
||||||
.arg(QCoreApplication::translate("MainWindow", "Keep transformation", "The 'transformation' means the flip/rotation status that currently applied to the image view"))
|
|
||||||
.arg(tr("Avoid resetting the zoom/rotation/flip state that was applied to the image view when switching between images.")),
|
|
||||||
u"</ul>"_s
|
|
||||||
};
|
|
||||||
|
|
||||||
const QStringList aboutStr {
|
const QString APP_NAME_ARG = qApp->applicationDisplayName();
|
||||||
u"<center><img width='128' height='128' src=':/icons/app-icon.svg'/><br/>"_s,
|
const QString APP_VERSION_ARG =
|
||||||
qApp->applicationDisplayName(),
|
|
||||||
(u"<br/>"_s + tr("Version: %1").arg(
|
|
||||||
#ifdef GIT_DESCRIBE_VERSION_STRING
|
#ifdef GIT_DESCRIBE_VERSION_STRING
|
||||||
GIT_DESCRIBE_VERSION_STRING
|
GIT_DESCRIBE_VERSION_STRING;
|
||||||
#else
|
#else
|
||||||
qApp->applicationVersion()
|
qApp->applicationVersion();
|
||||||
#endif // GIT_DESCRIBE_VERSION_STRING
|
#endif // GIT_DESCRIBE_VERSION_STRING
|
||||||
)),
|
const QString QT_VERSION_ARG = QT_VERSION_STR;
|
||||||
u"<hr/>"_s,
|
const QString QT_ARCH_ARG = QSysInfo::buildCpuArchitecture();
|
||||||
tr("Copyright (c) %1 %2", "%1 is year, %2 is the name of copyright holder(s)")
|
const QString ABOUT_HTML = loadEmbeddedHtml(u":/plain/about.html"_s)
|
||||||
.arg(u"2026"_s, u"<a href='https://github.com/BLumia'>@BLumia</a>"_s),
|
.arg(APP_NAME_ARG)
|
||||||
u"<br/>"_s,
|
.arg(tr("Version: %1").arg(APP_VERSION_ARG))
|
||||||
tr("Logo designed by %1").arg(u"<a href='https://github.com/Lovelyblack'>@Lovelyblack</a>"_s),
|
.arg(tr("Upstream author"))
|
||||||
u"<hr/>"_s,
|
.arg(tr("Hard fork author"))
|
||||||
tr("Built with Qt %1 (%2)").arg(QT_VERSION_STR, QSysInfo::buildCpuArchitecture()),
|
.arg(tr("Logo designer"))
|
||||||
QStringLiteral("<br/><a href='%1'>%2</a>").arg("https://github.com/BLumia/pineapple-pictures", tr("Source code")),
|
.arg(tr("Built with Qt %1 (%2)").arg(QT_VERSION_ARG).arg(QT_ARCH_ARG))
|
||||||
u"</center>"_s
|
.arg(tr("Source code"));
|
||||||
};
|
|
||||||
|
|
||||||
QFile translaterHtml(u":/plain/translators.html"_s);
|
const QString SPECIAL_THANKS_HTML = loadEmbeddedHtml(u":/plain/special-thanks.html"_s)
|
||||||
bool canOpenFile = translaterHtml.open(QIODevice::ReadOnly);
|
.arg(tr("Contributors"))
|
||||||
const QByteArray & translatorList = canOpenFile ? translaterHtml.readAll() : QByteArrayLiteral("");
|
.arg(tr("List of contributors on GitHub"))
|
||||||
|
.arg(tr("Thanks to all people who contributed to this project."))
|
||||||
|
.arg(tr("Translators"))
|
||||||
|
.arg(tr("I would like to thank the following people who volunteered to translate this application."));
|
||||||
|
|
||||||
const QStringList specialThanksStr {
|
const QString LICENSE_HTML = loadEmbeddedHtml(u":/plain/license.html"_s)
|
||||||
u"<h1 align='center'>%1</h1><a href='%2'>%3</a><p>%4</p>"_s.arg(
|
.arg(tr("License"))
|
||||||
tr("Contributors"),
|
.arg(tr("This application is released under the MIT License."))
|
||||||
u"https://github.com/BLumia/pineapple-pictures/graphs/contributors"_s,
|
.arg(tr("License Verbatim Copy"));
|
||||||
tr("List of contributors on GitHub"),
|
|
||||||
tr("Thanks to all people who contributed to this project.")
|
|
||||||
),
|
|
||||||
|
|
||||||
u"<h1 align='center'>%1</h1><p>%2</p>%3"_s.arg(
|
const QString THIRD_PARTY_LIBS_HTML = loadEmbeddedHtml(u":/plain/third-party-libs.html"_s)
|
||||||
tr("Translators"),
|
.arg(tr("Third-party Libraries"))
|
||||||
tr("I would like to thank the following people who volunteered to translate this application."),
|
.arg(tr("This application is built on the following free software libraries.", "Free as in freedom"))
|
||||||
translatorList
|
.arg(tr("Some of them are optional and might not be included with this distribution depending on the build environment."));
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
const QStringList licenseStr {
|
ui->m_helpTextEdit->setHtml(HELP_HTML);
|
||||||
u"<h1 align='center'><b>%1</b></h1>"_s.arg(tr("Your Rights")),
|
ui->m_aboutTextEdit->setHtml(ABOUT_HTML);
|
||||||
u"<p>%1</p><p>%2</p><ul><li>%3</li><li>%4</li><li>%5</li><li>%6</li></ul>"_s.arg(
|
ui->m_specialThanksTextEdit->setHtml(SPECIAL_THANKS_HTML);
|
||||||
tr("%1 is released under the MIT License."), // %1
|
ui->m_licenseTextEdit->setHtml(LICENSE_HTML);
|
||||||
tr("This license grants people a number of freedoms:"), // %2
|
ui->m_3rdPartyLibsTextEdit->setHtml(THIRD_PARTY_LIBS_HTML);
|
||||||
tr("You are free to use %1, for any purpose"), // %3
|
|
||||||
tr("You are free to distribute %1"), // %4
|
|
||||||
tr("You can study how %1 works and change it"), // %5
|
|
||||||
tr("You can distribute changed versions of %1") // %6
|
|
||||||
).arg(u"<i>%1</i>"_s),
|
|
||||||
u"<p>%1</p>"_s.arg(tr("The MIT license guarantees you this freedom. Nobody is ever permitted to take it away.")),
|
|
||||||
u"<hr/><pre>%2</pre>"_s
|
|
||||||
};
|
|
||||||
|
|
||||||
const QString mitLicense(QStringLiteral(R"(Expat/MIT License
|
connect(ui->m_buttonBox, QOverload<QAbstractButton *>::of(&QDialogButtonBox::clicked), this, [this](QAbstractButton * btn){
|
||||||
|
Q_UNUSED(btn)
|
||||||
Copyright © 2026 BLumia
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
)"));
|
|
||||||
|
|
||||||
const QStringList thirdPartyLibsStr {
|
|
||||||
u"<h1 align='center'><b>%1</b></h1>"_s.arg(tr("Third-party Libraries used by %1")),
|
|
||||||
tr("%1 is built on the following free software libraries:", "Free as in freedom"),
|
|
||||||
u"<ul>"_s,
|
|
||||||
#ifdef HAVE_EXIV2_VERSION
|
|
||||||
u"<li><a href='%1'>%2</a>: %3</li>"_s.arg("https://www.exiv2.org/", "Exiv2", "GPLv2"),
|
|
||||||
#endif // EXIV2_VERSION
|
|
||||||
u"<li><a href='%1'>%2</a>: %3</li>"_s.arg("https://www.qt.io/", "Qt", "GPLv2 + GPLv3 + LGPLv2.1 + LGPLv3"),
|
|
||||||
u"</ul>"_s
|
|
||||||
};
|
|
||||||
|
|
||||||
m_helpTextEdit->setText(helpStr.join('\n'));
|
|
||||||
|
|
||||||
m_aboutTextEdit->setText(aboutStr.join('\n'));
|
|
||||||
m_aboutTextEdit->setOpenExternalLinks(true);
|
|
||||||
|
|
||||||
m_specialThanksTextEdit->setText(specialThanksStr.join('\n'));
|
|
||||||
m_specialThanksTextEdit->setOpenExternalLinks(true);
|
|
||||||
|
|
||||||
m_licenseTextEdit->setText(licenseStr.join('\n').arg(qApp->applicationDisplayName(), mitLicense));
|
|
||||||
|
|
||||||
m_3rdPartyLibsTextEdit->setText(thirdPartyLibsStr.join('\n').arg(u"<i>%1</i>"_s).arg(qApp->applicationDisplayName()));
|
|
||||||
m_3rdPartyLibsTextEdit->setOpenExternalLinks(true);
|
|
||||||
|
|
||||||
m_tabWidget->addTab(m_helpTextEdit, tr("&Help"));
|
|
||||||
m_tabWidget->addTab(m_aboutTextEdit, tr("&About"));
|
|
||||||
m_tabWidget->addTab(m_specialThanksTextEdit, tr("&Special Thanks"));
|
|
||||||
m_tabWidget->addTab(m_licenseTextEdit, tr("&License"));
|
|
||||||
m_tabWidget->addTab(m_3rdPartyLibsTextEdit, tr("&Third-party Libraries"));
|
|
||||||
|
|
||||||
m_buttonBox->setStandardButtons(QDialogButtonBox::Close);
|
|
||||||
connect(m_buttonBox, QOverload<QAbstractButton *>::of(&QDialogButtonBox::clicked), this, [this](){
|
|
||||||
this->close();
|
this->close();
|
||||||
});
|
});
|
||||||
|
|
||||||
setLayout(new QVBoxLayout);
|
|
||||||
|
|
||||||
layout()->addWidget(m_tabWidget);
|
|
||||||
layout()->addWidget(m_buttonBox);
|
|
||||||
|
|
||||||
setMinimumSize(361, 161); // not sure why it complain "Unable to set geometry"
|
setMinimumSize(361, 161); // not sure why it complain "Unable to set geometry"
|
||||||
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AboutDialog::~AboutDialog()
|
AboutDialog::~AboutDialog()
|
||||||
{
|
{
|
||||||
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize AboutDialog::sizeHint() const
|
QSize AboutDialog::sizeHint() const
|
||||||
|
|||||||
+4
-11
@@ -8,9 +8,9 @@
|
|||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
class QTextBrowser;
|
namespace Ui {
|
||||||
class QTabWidget;
|
class AboutDialog;
|
||||||
class QDialogButtonBox;
|
}
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
class AboutDialog : public QDialog
|
class AboutDialog : public QDialog
|
||||||
@@ -23,14 +23,7 @@ public:
|
|||||||
QSize sizeHint() const override;
|
QSize sizeHint() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QTabWidget * m_tabWidget = nullptr;
|
QT_PREPEND_NAMESPACE(Ui::AboutDialog) *ui;
|
||||||
QDialogButtonBox * m_buttonBox = nullptr;
|
|
||||||
|
|
||||||
QTextBrowser * m_helpTextEdit = nullptr;
|
|
||||||
QTextBrowser * m_aboutTextEdit = nullptr;
|
|
||||||
QTextBrowser * m_specialThanksTextEdit = nullptr;
|
|
||||||
QTextBrowser * m_licenseTextEdit = nullptr;
|
|
||||||
QTextBrowser * m_3rdPartyLibsTextEdit = nullptr;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ABOUTDIALOG_H
|
#endif // ABOUTDIALOG_H
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>AboutDialog</class>
|
||||||
|
<widget class="QDialog" name="AboutDialog">
|
||||||
|
<property name="windowModality">
|
||||||
|
<enum>Qt::NonModal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>About</string>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>361</width>
|
||||||
|
<height>161</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QTabWidget" name="m_tabWidget">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="helpTab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>&Help</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QVBoxLayout" name="helpTabLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QTextBrowser" name="m_helpTextEdit">
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="aboutTab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>&About</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QVBoxLayout" name="aboutTabLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QTextBrowser" name="m_aboutTextEdit">
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="specialThanksTab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>&Special Thanks</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QVBoxLayout" name="specialThanksTabLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QTextBrowser" name="m_specialThanksTextEdit">
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="licenseTab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>&License</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QVBoxLayout" name="licenseTabLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QTextBrowser" name="m_licenseTextEdit">
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="thirdPartyLibsTab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>&Third-party Libraries</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QVBoxLayout" name="thirdPartyLibsTabLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QTextBrowser" name="m_3rdPartyLibsTextEdit">
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="m_buttonBox">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Close</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
+139
-83
@@ -4,19 +4,17 @@
|
|||||||
|
|
||||||
#include "actionmanager.h"
|
#include "actionmanager.h"
|
||||||
|
|
||||||
|
#include "settings.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
#include "playlistmanager.h"
|
||||||
|
#include "graphicsview.h"
|
||||||
|
#include "graphicsscene.h"
|
||||||
|
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QSvgRenderer>
|
#include <QSvgRenderer>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
||||||
#define ICON_NAME(name)\
|
static QIcon loadHidpiIcon(const QString &resp, QSize sz = QSize(32, 32))
|
||||||
QStringLiteral(":/icons/" #name ".svg")
|
|
||||||
|
|
||||||
#define ACTION_NAME(s) QStringLiteral(STRIFY(s))
|
|
||||||
#define STRIFY(s) #s
|
|
||||||
|
|
||||||
QIcon ActionManager::loadHidpiIcon(const QString &resp, QSize sz)
|
|
||||||
{
|
{
|
||||||
QSvgRenderer r(resp);
|
QSvgRenderer r(resp);
|
||||||
QPixmap pm = QPixmap(sz * qApp->devicePixelRatio());
|
QPixmap pm = QPixmap(sz * qApp->devicePixelRatio());
|
||||||
@@ -29,130 +27,188 @@ QIcon ActionManager::loadHidpiIcon(const QString &resp, QSize sz)
|
|||||||
|
|
||||||
void ActionManager::setupAction(MainWindow *mainWindow)
|
void ActionManager::setupAction(MainWindow *mainWindow)
|
||||||
{
|
{
|
||||||
|
// region: Action and Its Icon Creation
|
||||||
|
|
||||||
|
// Declare some convenient macros and lambdas.
|
||||||
|
|
||||||
|
#define ICON_NAME(name)\
|
||||||
|
QStringLiteral(":/icons/" #name ".svg")
|
||||||
|
|
||||||
|
#define ACTION_NAME(s) QStringLiteral(STRIFY(s))
|
||||||
|
#define STRIFY(s) #s
|
||||||
|
|
||||||
auto create_action = [] (QWidget *w, QAction **a, QString i, QString an, bool iconFromTheme = false) {
|
auto create_action = [] (QWidget *w, QAction **a, QString i, QString an, bool iconFromTheme = false) {
|
||||||
*a = new QAction(w);
|
*a = new QAction(w);
|
||||||
if (!i.isNull())
|
if (!i.isNull())
|
||||||
(*a)->setIcon(iconFromTheme ? QIcon::fromTheme(i) : ActionManager::loadHidpiIcon(i));
|
(*a)->setIcon(iconFromTheme ? QIcon::fromTheme(i) : loadHidpiIcon(i));
|
||||||
(*a)->setObjectName(an);
|
(*a)->setObjectName(an);
|
||||||
w->addAction(*a);
|
w->addAction(*a);
|
||||||
};
|
};
|
||||||
#define CREATE_NEW_ICON_ACTION(w, a, i) create_action(w, &a, ICON_NAME(i), ACTION_NAME(a))
|
|
||||||
|
/// This macro for register normal action.
|
||||||
|
#define CREATE_NEW_ACTION(w, a) create_action(w, &a, QString(), ACTION_NAME(a))
|
||||||
|
/// This macro for register Qt theme action.
|
||||||
|
#define CREATE_NEW_THEMEICON_ACTION(w, a, i) create_action(w, &a, QLatin1String(STRIFY(i)), ACTION_NAME(a), true)
|
||||||
|
/// This macro for register action with our custom icon.
|
||||||
|
#define CREATE_NEW_ICON_ACTION(w, a, i) create_action(w, &a, ICON_NAME(i), ACTION_NAME(a), false)
|
||||||
|
|
||||||
|
// TODO: We may replace all Qt theme icons to our custom icon for visual consistency.
|
||||||
|
// TODO: Some icons are invalid.
|
||||||
|
|
||||||
|
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionCopyPixmap, edit-copy);
|
||||||
|
CREATE_NEW_ACTION(mainWindow, actionCopyFilePath);
|
||||||
|
|
||||||
|
CREATE_NEW_ICON_ACTION(mainWindow, actionPrevPicture, go-previous);
|
||||||
|
CREATE_NEW_ICON_ACTION(mainWindow, actionNextPicture, go-next);
|
||||||
|
|
||||||
CREATE_NEW_ICON_ACTION(mainWindow, actionActualSize, zoom-original);
|
CREATE_NEW_ICON_ACTION(mainWindow, actionActualSize, zoom-original);
|
||||||
CREATE_NEW_ICON_ACTION(mainWindow, actionToggleMaximize, view-fullscreen);
|
CREATE_NEW_ICON_ACTION(mainWindow, actionFitToScreen, app-icon);
|
||||||
|
CREATE_NEW_ICON_ACTION(mainWindow, actionFitByWidth, app-icon);
|
||||||
|
CREATE_NEW_ICON_ACTION(mainWindow, actionFitByHeight, app-icon);
|
||||||
CREATE_NEW_ICON_ACTION(mainWindow, actionZoomIn, zoom-in);
|
CREATE_NEW_ICON_ACTION(mainWindow, actionZoomIn, zoom-in);
|
||||||
CREATE_NEW_ICON_ACTION(mainWindow, actionZoomOut, zoom-out);
|
CREATE_NEW_ICON_ACTION(mainWindow, actionZoomOut, zoom-out);
|
||||||
CREATE_NEW_ICON_ACTION(mainWindow, actionToggleCheckerboard, view-background-checkerboard);
|
|
||||||
|
CREATE_NEW_ICON_ACTION(mainWindow, actionFlipHorizontal, app-icon);
|
||||||
|
CREATE_NEW_ICON_ACTION(mainWindow, actionFlipVertical, app-icon);
|
||||||
CREATE_NEW_ICON_ACTION(mainWindow, actionRotateClockwise, object-rotate-right);
|
CREATE_NEW_ICON_ACTION(mainWindow, actionRotateClockwise, object-rotate-right);
|
||||||
#undef CREATE_NEW_ICON_ACTION
|
CREATE_NEW_ICON_ACTION(mainWindow, actionRotateCounterClockwise, app-icon);
|
||||||
|
|
||||||
#define CREATE_NEW_ACTION(w, a) create_action(w, &a, QString(), ACTION_NAME(a))
|
|
||||||
#define CREATE_NEW_THEMEICON_ACTION(w, a, i) create_action(w, &a, QLatin1String(STRIFY(i)), ACTION_NAME(a), true)
|
|
||||||
CREATE_NEW_ACTION(mainWindow, actionRotateCounterClockwise);
|
|
||||||
CREATE_NEW_ACTION(mainWindow, actionPrevPicture);
|
|
||||||
CREATE_NEW_ACTION(mainWindow, actionNextPicture);
|
|
||||||
|
|
||||||
|
CREATE_NEW_ACTION(mainWindow, actionToggleAvoidResetTransform);
|
||||||
|
CREATE_NEW_ICON_ACTION(mainWindow, actionToggleCheckerboard, view-background-checkerboard);
|
||||||
CREATE_NEW_ACTION(mainWindow, actionTogglePauseAnimation);
|
CREATE_NEW_ACTION(mainWindow, actionTogglePauseAnimation);
|
||||||
CREATE_NEW_ACTION(mainWindow, actionAnimationNextFrame);
|
CREATE_NEW_ACTION(mainWindow, actionAnimationNextFrame);
|
||||||
|
|
||||||
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionOpen, document-open);
|
|
||||||
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionSaveAs, document-save-as);
|
|
||||||
CREATE_NEW_ACTION(mainWindow, actionHorizontalFlip);
|
|
||||||
CREATE_NEW_ACTION(mainWindow, actionFitInView);
|
|
||||||
CREATE_NEW_ACTION(mainWindow, actionFitByWidth);
|
|
||||||
CREATE_NEW_ACTION(mainWindow, actionFitLongImage);
|
|
||||||
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionCopyPixmap, edit-copy);
|
|
||||||
CREATE_NEW_ACTION(mainWindow, actionCopyFilePath);
|
|
||||||
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionPaste, edit-paste);
|
|
||||||
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionTrash, edit-delete);
|
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionTrash, edit-delete);
|
||||||
CREATE_NEW_ACTION(mainWindow, actionToggleStayOnTop);
|
|
||||||
CREATE_NEW_ACTION(mainWindow, actionToggleProtectMode);
|
|
||||||
CREATE_NEW_ACTION(mainWindow, actionToggleAvoidResetTransform);
|
|
||||||
CREATE_NEW_ACTION(mainWindow, actionSettings);
|
|
||||||
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionHelp, system-help);
|
|
||||||
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionLocateInFileManager, system-file-manager);
|
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionLocateInFileManager, system-file-manager);
|
||||||
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionProperties, document-properties);
|
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionProperties, document-properties);
|
||||||
CREATE_NEW_ACTION(mainWindow, actionQuitApp);
|
|
||||||
#undef CREATE_NEW_ACTION
|
CREATE_NEW_ACTION(mainWindow, actionSettings);
|
||||||
|
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionHelp, system-help);
|
||||||
|
|
||||||
|
// Remove convenient macros
|
||||||
|
|
||||||
|
#undef CREATE_NEW_ICON_ACTION
|
||||||
#undef CREATE_NEW_THEMEICON_ACTION
|
#undef CREATE_NEW_THEMEICON_ACTION
|
||||||
|
#undef CREATE_NEW_ACTION
|
||||||
|
|
||||||
retranslateUi(mainWindow);
|
#undef STRIFY
|
||||||
|
#undef ACTION_NAME
|
||||||
|
#undef ICON_NAME
|
||||||
|
|
||||||
QMetaObject::connectSlotsByName(mainWindow);
|
// endregion
|
||||||
}
|
|
||||||
|
|
||||||
void ActionManager::retranslateUi(MainWindow *mainWindow)
|
// region: Setup Checkable Action
|
||||||
{
|
|
||||||
Q_UNUSED(mainWindow);
|
|
||||||
|
|
||||||
actionOpen->setText(QCoreApplication::translate("MainWindow", "&Open...", nullptr));
|
actionToggleAvoidResetTransform->setCheckable(true);
|
||||||
actionSaveAs->setText(QCoreApplication::translate("MainWindow", "Save &As...", nullptr));
|
actionToggleCheckerboard->setCheckable(true);
|
||||||
|
actionTogglePauseAnimation->setCheckable(true);
|
||||||
|
|
||||||
actionActualSize->setText(QCoreApplication::translate("MainWindow", "Actual size", nullptr));
|
// endregion
|
||||||
actionToggleMaximize->setText(QCoreApplication::translate("MainWindow", "Toggle maximize", nullptr));
|
|
||||||
actionZoomIn->setText(QCoreApplication::translate("MainWindow", "Zoom in", nullptr));
|
// region: Action Translatable Text Setup
|
||||||
actionZoomOut->setText(QCoreApplication::translate("MainWindow", "Zoom out", nullptr));
|
|
||||||
actionToggleCheckerboard->setText(QCoreApplication::translate("MainWindow", "Toggle Checkerboard", nullptr));
|
actionCopyPixmap->setText(QCoreApplication::translate("MainWindow", "Copy P&ixmap", nullptr));
|
||||||
actionRotateClockwise->setText(QCoreApplication::translate("MainWindow", "Rotate right", nullptr));
|
actionCopyFilePath->setText(QCoreApplication::translate("MainWindow", "Copy &File Path", nullptr));
|
||||||
actionRotateCounterClockwise->setText(QCoreApplication::translate("MainWindow", "Rotate left", nullptr));
|
|
||||||
|
|
||||||
actionPrevPicture->setText(QCoreApplication::translate("MainWindow", "Previous image", nullptr));
|
actionPrevPicture->setText(QCoreApplication::translate("MainWindow", "Previous image", nullptr));
|
||||||
actionNextPicture->setText(QCoreApplication::translate("MainWindow", "Next image", nullptr));
|
actionNextPicture->setText(QCoreApplication::translate("MainWindow", "Next image", nullptr));
|
||||||
|
|
||||||
|
actionActualSize->setText(QCoreApplication::translate("MainWindow", "Actual size", nullptr));
|
||||||
|
actionFitToScreen->setText(QCoreApplication::translate("MainWindow", "Fit to screen", nullptr));
|
||||||
|
actionFitByWidth->setText(QCoreApplication::translate("MainWindow", "Fit by width", nullptr));
|
||||||
|
actionFitByHeight->setText(QCoreApplication::translate("MainWindow", "Fit by height", nullptr));
|
||||||
|
actionZoomIn->setText(QCoreApplication::translate("MainWindow", "Zoom in", nullptr));
|
||||||
|
actionZoomOut->setText(QCoreApplication::translate("MainWindow", "Zoom out", nullptr));
|
||||||
|
|
||||||
|
actionFlipHorizontal->setText(QCoreApplication::translate("MainWindow", "Flip &Horizontally", nullptr));
|
||||||
|
actionFlipVertical->setText(QCoreApplication::translate("MainWindow", "Flip &Vertically", nullptr));
|
||||||
|
actionRotateClockwise->setText(QCoreApplication::translate("MainWindow", "Rotate right", nullptr));
|
||||||
|
actionRotateCounterClockwise->setText(QCoreApplication::translate("MainWindow", "Rotate left", nullptr));
|
||||||
|
|
||||||
|
actionToggleAvoidResetTransform->setText(QCoreApplication::translate("MainWindow", "Keep transformation", "The 'transformation' means the flip/rotation status that currently applied to the image view"));
|
||||||
|
actionToggleCheckerboard->setText(QCoreApplication::translate("MainWindow", "Toggle Checkerboard", nullptr));
|
||||||
actionTogglePauseAnimation->setText(QCoreApplication::translate("MainWindow", "Pause/Resume Animation", nullptr));
|
actionTogglePauseAnimation->setText(QCoreApplication::translate("MainWindow", "Pause/Resume Animation", nullptr));
|
||||||
actionAnimationNextFrame->setText(QCoreApplication::translate("MainWindow", "Animation Go to Next Frame", nullptr));
|
actionAnimationNextFrame->setText(QCoreApplication::translate("MainWindow", "Animation Go to Next Frame", nullptr));
|
||||||
|
|
||||||
actionHorizontalFlip->setText(QCoreApplication::translate("MainWindow", "Flip &Horizontally", nullptr));
|
|
||||||
actionFitInView->setText(QCoreApplication::translate("MainWindow", "Fit to view", nullptr));
|
|
||||||
actionFitByWidth->setText(QCoreApplication::translate("MainWindow", "Fit to width", nullptr));
|
|
||||||
actionFitLongImage->setText(QCoreApplication::translate("MainWindow", "Fit long image", nullptr));
|
|
||||||
actionCopyPixmap->setText(QCoreApplication::translate("MainWindow", "Copy P&ixmap", nullptr));
|
|
||||||
actionCopyFilePath->setText(QCoreApplication::translate("MainWindow", "Copy &File Path", nullptr));
|
|
||||||
actionPaste->setText(QCoreApplication::translate("MainWindow", "&Paste", nullptr));
|
|
||||||
actionTrash->setText(QCoreApplication::translate("MainWindow", "Move to Trash", nullptr));
|
actionTrash->setText(QCoreApplication::translate("MainWindow", "Move to Trash", nullptr));
|
||||||
actionToggleStayOnTop->setText(QCoreApplication::translate("MainWindow", "Stay on top", nullptr));
|
|
||||||
actionToggleProtectMode->setText(QCoreApplication::translate("MainWindow", "Protected mode", nullptr));
|
|
||||||
actionToggleAvoidResetTransform->setText(QCoreApplication::translate("MainWindow", "Keep transformation", "The 'transformation' means the flip/rotation status that currently applied to the image view"));
|
|
||||||
actionSettings->setText(QCoreApplication::translate("MainWindow", "Configure...", nullptr));
|
|
||||||
actionHelp->setText(QCoreApplication::translate("MainWindow", "Help", nullptr));
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
actionLocateInFileManager->setText(
|
actionLocateInFileManager->setText(
|
||||||
QCoreApplication::translate(
|
QCoreApplication::translate(
|
||||||
"MainWindow", "Show in File Explorer",
|
"MainWindow", "Show in File Explorer",
|
||||||
"File Explorer is the name of explorer.exe under Windows"
|
"File Explorer is the name of explorer.exe under Windows"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
#else
|
#else
|
||||||
actionLocateInFileManager->setText(QCoreApplication::translate("MainWindow", "Show in directory", nullptr));
|
actionLocateInFileManager->setText(QCoreApplication::translate("MainWindow", "Show in directory", nullptr));
|
||||||
#endif // Q_OS_WIN
|
#endif // Q_OS_WIN
|
||||||
actionProperties->setText(QCoreApplication::translate("MainWindow", "Properties", nullptr));
|
actionProperties->setText(QCoreApplication::translate("MainWindow", "Properties", nullptr));
|
||||||
actionQuitApp->setText(QCoreApplication::translate("MainWindow", "Quit", nullptr));
|
|
||||||
|
actionSettings->setText(QCoreApplication::translate("MainWindow", "Configure...", nullptr));
|
||||||
|
actionHelp->setText(QCoreApplication::translate("MainWindow", "Help", nullptr));
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
QMetaObject::connectSlotsByName(mainWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActionManager::setupShortcuts()
|
void ActionManager::setupShortcuts()
|
||||||
{
|
{
|
||||||
actionOpen->setShortcut(QKeySequence::Open);
|
actionCopyPixmap->setShortcut(QKeySequence::Copy);
|
||||||
actionSaveAs->setShortcut(QKeySequence::SaveAs);
|
|
||||||
|
actionPrevPicture->setShortcuts({
|
||||||
|
QKeySequence(Qt::Key_Left),
|
||||||
|
QKeySequence(Qt::Key_PageUp),
|
||||||
|
});
|
||||||
|
actionNextPicture->setShortcuts({
|
||||||
|
QKeySequence(Qt::Key_Right),
|
||||||
|
QKeySequence(Qt::Key_PageDown),
|
||||||
|
});
|
||||||
|
|
||||||
actionActualSize->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_0));
|
actionActualSize->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_0));
|
||||||
actionZoomIn->setShortcut(QKeySequence::ZoomIn);
|
actionZoomIn->setShortcut(QKeySequence::ZoomIn);
|
||||||
actionZoomOut->setShortcut(QKeySequence::ZoomOut);
|
actionZoomOut->setShortcut(QKeySequence::ZoomOut);
|
||||||
actionPrevPicture->setShortcuts({
|
|
||||||
QKeySequence(Qt::Key_PageUp),
|
|
||||||
QKeySequence(Qt::Key_Left),
|
|
||||||
});
|
|
||||||
actionNextPicture->setShortcuts({
|
|
||||||
QKeySequence(Qt::Key_PageDown),
|
|
||||||
QKeySequence(Qt::Key_Right),
|
|
||||||
});
|
|
||||||
actionHorizontalFlip->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_R));
|
|
||||||
actionCopyPixmap->setShortcut(QKeySequence::Copy);
|
|
||||||
actionPaste->setShortcut(QKeySequence::Paste);
|
|
||||||
actionTrash->setShortcut(QKeySequence::Delete);
|
actionTrash->setShortcut(QKeySequence::Delete);
|
||||||
actionHelp->setShortcut(QKeySequence::HelpContents);
|
|
||||||
actionSettings->setShortcut(QKeySequence::Preferences);
|
|
||||||
actionProperties->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_I));
|
actionProperties->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_I));
|
||||||
actionQuitApp->setShortcuts({
|
|
||||||
QKeySequence(Qt::Key_Space),
|
actionSettings->setShortcut(QKeySequence::Preferences);
|
||||||
QKeySequence(Qt::Key_Escape)
|
actionHelp->setShortcut(QKeySequence::HelpContents);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
void ActionManager::updateActionState(PlaylistManager* pm, GraphicsView* gv)
|
||||||
|
{
|
||||||
|
const auto* gs = gv->scene();
|
||||||
|
const auto* settings = Settings::instance();
|
||||||
|
|
||||||
|
// Update copy actions
|
||||||
|
bool canCopy = pm->currentIndex().has_value();
|
||||||
|
actionCopyPixmap->setEnabled(canCopy);
|
||||||
|
actionCopyFilePath->setEnabled(canCopy);
|
||||||
|
|
||||||
|
// Update Prev/Next Picture State
|
||||||
|
const int galleryFileCount = pm->totalCount();
|
||||||
|
const bool loopGallery = settings->loopGallery();
|
||||||
|
actionPrevPicture->setEnabled(galleryFileCount > 1 && (loopGallery || !pm->isFirstIndex()));
|
||||||
|
actionNextPicture->setEnabled(galleryFileCount > 1 && (loopGallery || !pm->isLastIndex()));
|
||||||
|
|
||||||
|
// Update Avoid Reset Transform
|
||||||
|
actionToggleAvoidResetTransform->setChecked(gv->isAvoidResetTransform());
|
||||||
|
// Update Checkerboard
|
||||||
|
actionToggleCheckerboard->setChecked(gv->isCheckerboard());
|
||||||
|
// Update Animation Graphics Action
|
||||||
|
bool canPauseAnimation = gs->canPauseAnimation();
|
||||||
|
actionTogglePauseAnimation->setEnabled(canPauseAnimation);
|
||||||
|
if (canPauseAnimation) {
|
||||||
|
actionTogglePauseAnimation->setChecked(gs->isPauseAnimation());
|
||||||
|
} else {
|
||||||
|
actionTogglePauseAnimation->setChecked(false);
|
||||||
|
}
|
||||||
|
bool canSkipAnimation = gs->canSkipAnimationFrame();
|
||||||
|
actionAnimationNextFrame->setEnabled(canSkipAnimation);
|
||||||
|
|
||||||
|
// Update trash, located in explorer and properties.
|
||||||
|
bool hasLoadedFile = pm->currentIndex().has_value();
|
||||||
|
actionTrash->setEnabled(hasLoadedFile);
|
||||||
|
actionLocateInFileManager->setEnabled(hasLoadedFile);
|
||||||
|
actionProperties->setEnabled(hasLoadedFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-25
@@ -8,7 +8,11 @@
|
|||||||
#include <QAction>
|
#include <QAction>
|
||||||
|
|
||||||
class MainWindow;
|
class MainWindow;
|
||||||
|
class PlaylistManager;
|
||||||
|
class GraphicsView;
|
||||||
|
|
||||||
|
/// Action Manager is separated from Main Window
|
||||||
|
/// to manage all actions specifically.
|
||||||
class ActionManager
|
class ActionManager
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -16,45 +20,41 @@ public:
|
|||||||
~ActionManager() = default;
|
~ActionManager() = default;
|
||||||
|
|
||||||
void setupAction(MainWindow * mainWindow);
|
void setupAction(MainWindow * mainWindow);
|
||||||
void retranslateUi(MainWindow *MainWindow);
|
|
||||||
void setupShortcuts();
|
void setupShortcuts();
|
||||||
|
|
||||||
static QIcon loadHidpiIcon(const QString &resp, QSize sz = QSize(32, 32));
|
public slots:
|
||||||
|
void updateActionState(PlaylistManager* pm, GraphicsView* gv);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QAction *actionOpen;
|
QAction *actionCopyPixmap;
|
||||||
QAction *actionSaveAs;
|
QAction *actionCopyFilePath;
|
||||||
|
|
||||||
QAction *actionActualSize;
|
|
||||||
QAction *actionToggleMaximize;
|
|
||||||
QAction *actionZoomIn;
|
|
||||||
QAction *actionZoomOut;
|
|
||||||
QAction *actionToggleCheckerboard;
|
|
||||||
QAction *actionRotateClockwise;
|
|
||||||
QAction *actionRotateCounterClockwise;
|
|
||||||
|
|
||||||
QAction *actionPrevPicture;
|
QAction *actionPrevPicture;
|
||||||
QAction *actionNextPicture;
|
QAction *actionNextPicture;
|
||||||
|
|
||||||
|
QAction *actionActualSize;
|
||||||
|
QAction *actionFitToScreen;
|
||||||
|
QAction *actionFitByWidth;
|
||||||
|
QAction *actionFitByHeight;
|
||||||
|
QAction *actionZoomIn;
|
||||||
|
QAction *actionZoomOut;
|
||||||
|
|
||||||
|
QAction *actionFlipHorizontal;
|
||||||
|
QAction *actionFlipVertical;
|
||||||
|
QAction *actionRotateClockwise;
|
||||||
|
QAction *actionRotateCounterClockwise;
|
||||||
|
|
||||||
|
QAction *actionToggleAvoidResetTransform;
|
||||||
|
QAction *actionToggleCheckerboard;
|
||||||
QAction *actionTogglePauseAnimation;
|
QAction *actionTogglePauseAnimation;
|
||||||
QAction *actionAnimationNextFrame;
|
QAction *actionAnimationNextFrame;
|
||||||
|
|
||||||
QAction *actionHorizontalFlip;
|
|
||||||
QAction *actionFitInView;
|
|
||||||
QAction *actionFitByWidth;
|
|
||||||
QAction *actionFitLongImage;
|
|
||||||
QAction *actionCopyPixmap;
|
|
||||||
QAction *actionCopyFilePath;
|
|
||||||
QAction *actionPaste;
|
|
||||||
QAction *actionTrash;
|
QAction *actionTrash;
|
||||||
QAction *actionToggleStayOnTop;
|
|
||||||
QAction *actionToggleProtectMode;
|
|
||||||
QAction *actionToggleAvoidResetTransform;
|
|
||||||
QAction *actionSettings;
|
|
||||||
QAction *actionHelp;
|
|
||||||
QAction *actionLocateInFileManager;
|
QAction *actionLocateInFileManager;
|
||||||
QAction *actionProperties;
|
QAction *actionProperties;
|
||||||
QAction *actionQuitApp;
|
|
||||||
|
QAction *actionSettings;
|
||||||
|
QAction *actionHelp;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ACTIONMANAGER_H
|
#endif // ACTIONMANAGER_H
|
||||||
|
|||||||
+14
-32
@@ -4,54 +4,36 @@
|
|||||||
|
|
||||||
#include "bottombuttongroup.h"
|
#include "bottombuttongroup.h"
|
||||||
|
|
||||||
#include "opacityhelper.h"
|
|
||||||
|
|
||||||
#include <QToolButton>
|
#include <QToolButton>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
BottomButtonGroup::BottomButtonGroup(const std::vector<QAction *> &actionList, QWidget *parent)
|
BottomButtonGroup::BottomButtonGroup(const std::vector<QAction *> &actionList, QWidget *parent)
|
||||||
: QGroupBox (parent)
|
: QGroupBox (parent)
|
||||||
, m_opacityHelper(new OpacityHelper(this))
|
|
||||||
{
|
{
|
||||||
QHBoxLayout * mainLayout = new QHBoxLayout(this);
|
QHBoxLayout * mainLayout = new QHBoxLayout(this);
|
||||||
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||||
this->setLayout(mainLayout);
|
this->setLayout(mainLayout);
|
||||||
this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||||
this->setStyleSheet("BottomButtonGroup {"
|
this->setStyleSheet(R"#(
|
||||||
"border: 1px solid gray;"
|
BottomButtonGroup {
|
||||||
"border-top-left-radius: 10px;"
|
border-style: none;
|
||||||
"border-top-right-radius: 10px;"
|
background: transparent;
|
||||||
"border-style: none;"
|
}
|
||||||
"background-color:rgba(0,0,0,120)"
|
QToolButton {
|
||||||
"}"
|
background: transparent;
|
||||||
"QToolButton {"
|
border-style: none;
|
||||||
"background:transparent;"
|
}
|
||||||
"}"
|
)#");
|
||||||
"QToolButton:!focus {"
|
|
||||||
"border-style: none;"
|
|
||||||
"}");
|
|
||||||
|
|
||||||
auto newActionBtn = [this](QAction * action) -> QToolButton * {
|
for (QAction * action : actionList) {
|
||||||
|
// Create button
|
||||||
QToolButton * btn = new QToolButton(this);
|
QToolButton * btn = new QToolButton(this);
|
||||||
btn->setDefaultAction(action);
|
btn->setDefaultAction(action);
|
||||||
btn->setIconSize(QSize(32, 32));
|
btn->setIconSize(QSize(32, 32));
|
||||||
btn->setFixedSize(40, 40);
|
btn->setFixedSize(40, 40);
|
||||||
return btn;
|
// Add button
|
||||||
};
|
layout()->addWidget(btn);
|
||||||
|
|
||||||
for (QAction * action : actionList) {
|
|
||||||
addButton(newActionBtn(action));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void BottomButtonGroup::setOpacity(qreal opacity, bool animated)
|
|
||||||
{
|
|
||||||
m_opacityHelper->setOpacity(opacity, animated);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BottomButtonGroup::addButton(QAbstractButton *button)
|
|
||||||
{
|
|
||||||
layout()->addWidget(button);
|
|
||||||
updateGeometry();
|
updateGeometry();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,18 +10,11 @@
|
|||||||
#include <QAbstractButton>
|
#include <QAbstractButton>
|
||||||
#include <QGroupBox>
|
#include <QGroupBox>
|
||||||
|
|
||||||
class OpacityHelper;
|
|
||||||
class BottomButtonGroup : public QGroupBox
|
class BottomButtonGroup : public QGroupBox
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit BottomButtonGroup(const std::vector<QAction *> & actionList, QWidget *parent = nullptr);
|
explicit BottomButtonGroup(const std::vector<QAction *> & actionList, QWidget *parent = nullptr);
|
||||||
|
|
||||||
void setOpacity(qreal opacity, bool animated = true);
|
|
||||||
void addButton(QAbstractButton *button);
|
|
||||||
|
|
||||||
private:
|
|
||||||
OpacityHelper * m_opacityHelper;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BOTTOMBUTTONGROUP_H
|
#endif // BOTTOMBUTTONGROUP_H
|
||||||
|
|||||||
@@ -1,136 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
|
||||||
// SPDX-FileCopyrightText: 2023 Tad Young <yyc12321@outlook.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#include "framelesswindow.h"
|
|
||||||
|
|
||||||
#include <QMouseEvent>
|
|
||||||
#include <QHoverEvent>
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
#include <QWindow>
|
|
||||||
|
|
||||||
FramelessWindow::FramelessWindow(QWidget *parent)
|
|
||||||
: QWidget(parent)
|
|
||||||
, m_centralLayout(new QVBoxLayout(this))
|
|
||||||
, m_oldCursorShape(Qt::ArrowCursor)
|
|
||||||
, m_oldEdges()
|
|
||||||
{
|
|
||||||
this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
|
|
||||||
this->setMouseTracking(true);
|
|
||||||
this->setAttribute(Qt::WA_Hover, true);
|
|
||||||
this->installEventFilter(this);
|
|
||||||
|
|
||||||
m_centralLayout->setContentsMargins(QMargins());
|
|
||||||
}
|
|
||||||
|
|
||||||
void FramelessWindow::setCentralWidget(QWidget *widget)
|
|
||||||
{
|
|
||||||
if (m_centralWidget) {
|
|
||||||
m_centralLayout->removeWidget(m_centralWidget);
|
|
||||||
m_centralWidget->deleteLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_centralLayout->addWidget(widget);
|
|
||||||
m_centralWidget = widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FramelessWindow::installResizeCapture(QObject* widget)
|
|
||||||
{
|
|
||||||
widget->installEventFilter(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FramelessWindow::eventFilter(QObject* o, QEvent* e)
|
|
||||||
{
|
|
||||||
switch (e->type()) {
|
|
||||||
case QEvent::HoverMove:
|
|
||||||
{
|
|
||||||
QWidget* wg = qobject_cast<QWidget*>(o);
|
|
||||||
if (wg != nullptr)
|
|
||||||
return mouseHover(static_cast<QHoverEvent*>(e), wg);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case QEvent::MouseButtonPress:
|
|
||||||
return mousePress(static_cast<QMouseEvent*>(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
return QWidget::eventFilter(o, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FramelessWindow::mouseHover(QHoverEvent* event, QWidget* wg)
|
|
||||||
{
|
|
||||||
if (!isMaximized() && !isFullScreen()) {
|
|
||||||
QWindow* win = window()->windowHandle();
|
|
||||||
Qt::Edges edges = this->getEdgesByPos(wg->mapToGlobal(event->oldPos()), win->frameGeometry());
|
|
||||||
|
|
||||||
// backup & restore cursor shape
|
|
||||||
if (edges && !m_oldEdges)
|
|
||||||
// entering the edge. backup cursor shape
|
|
||||||
m_oldCursorShape = win->cursor().shape();
|
|
||||||
if (!edges && m_oldEdges)
|
|
||||||
// leaving the edge. restore cursor shape
|
|
||||||
win->setCursor(m_oldCursorShape);
|
|
||||||
|
|
||||||
// save the latest edges status
|
|
||||||
m_oldEdges = edges;
|
|
||||||
|
|
||||||
// show resize cursor shape if cursor is within border
|
|
||||||
if (edges) {
|
|
||||||
win->setCursor(this->getCursorByEdge(edges, Qt::ArrowCursor));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FramelessWindow::mousePress(QMouseEvent* event)
|
|
||||||
{
|
|
||||||
if (event->buttons() & Qt::LeftButton && !isMaximized() && !isFullScreen()) {
|
|
||||||
QWindow* win = window()->windowHandle();
|
|
||||||
Qt::Edges edges = this->getEdgesByPos(event->globalPosition().toPoint(), win->frameGeometry());
|
|
||||||
if (edges) {
|
|
||||||
win->startSystemResize(edges);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Qt::CursorShape FramelessWindow::getCursorByEdge(const Qt::Edges& edges, Qt::CursorShape default_cursor)
|
|
||||||
{
|
|
||||||
if ((edges == (Qt::TopEdge | Qt::LeftEdge)) || (edges == (Qt::RightEdge | Qt::BottomEdge)))
|
|
||||||
return Qt::SizeFDiagCursor;
|
|
||||||
else if ((edges == (Qt::TopEdge | Qt::RightEdge)) || (edges == (Qt::LeftEdge | Qt::BottomEdge)))
|
|
||||||
return Qt::SizeBDiagCursor;
|
|
||||||
else if (edges & (Qt::TopEdge | Qt::BottomEdge))
|
|
||||||
return Qt::SizeVerCursor;
|
|
||||||
else if (edges & (Qt::LeftEdge | Qt::RightEdge))
|
|
||||||
return Qt::SizeHorCursor;
|
|
||||||
else
|
|
||||||
return default_cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
Qt::Edges FramelessWindow::getEdgesByPos(const QPoint gpos, const QRect& winrect)
|
|
||||||
{
|
|
||||||
const int borderWidth = 8;
|
|
||||||
Qt::Edges edges;
|
|
||||||
|
|
||||||
int x = gpos.x() - winrect.x();
|
|
||||||
int y = gpos.y() - winrect.y();
|
|
||||||
|
|
||||||
if (x < borderWidth)
|
|
||||||
edges |= Qt::LeftEdge;
|
|
||||||
if (x > (winrect.width() - borderWidth))
|
|
||||||
edges |= Qt::RightEdge;
|
|
||||||
if (y < borderWidth)
|
|
||||||
edges |= Qt::TopEdge;
|
|
||||||
if (y > (winrect.height() - borderWidth))
|
|
||||||
edges |= Qt::BottomEdge;
|
|
||||||
|
|
||||||
return edges;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#ifndef FRAMELESSWINDOW_H
|
|
||||||
#define FRAMELESSWINDOW_H
|
|
||||||
|
|
||||||
#include <QWidget>
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
|
||||||
class QVBoxLayout;
|
|
||||||
QT_END_NAMESPACE
|
|
||||||
|
|
||||||
class FramelessWindow : public QWidget
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit FramelessWindow(QWidget *parent = nullptr);
|
|
||||||
|
|
||||||
void setCentralWidget(QWidget * widget);
|
|
||||||
void installResizeCapture(QObject* widget);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool eventFilter(QObject *o, QEvent *e) override;
|
|
||||||
bool mouseHover(QHoverEvent* event, QWidget* wg);
|
|
||||||
bool mousePress(QMouseEvent* event);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Qt::Edges m_oldEdges;
|
|
||||||
Qt::CursorShape m_oldCursorShape;
|
|
||||||
|
|
||||||
Qt::CursorShape getCursorByEdge(const Qt::Edges& edges, Qt::CursorShape default_cursor);
|
|
||||||
Qt::Edges getEdgesByPos(const QPoint pos, const QRect& winrect);
|
|
||||||
|
|
||||||
QVBoxLayout * m_centralLayout = nullptr;
|
|
||||||
QWidget * m_centralWidget = nullptr; // just a pointer, doesn't take the ownership.
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // FRAMELESSWINDOW_H
|
|
||||||
+85
-13
@@ -6,6 +6,9 @@
|
|||||||
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
|
#include <QStyleHints>
|
||||||
|
#include <QOperatingSystemVersion>
|
||||||
|
#include <QGuiApplication>
|
||||||
#include <QGraphicsSceneMouseEvent>
|
#include <QGraphicsSceneMouseEvent>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
@@ -16,6 +19,33 @@
|
|||||||
#include <QMovie>
|
#include <QMovie>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
||||||
|
static const QColor& ColorSchemaToTextForeground(Qt::ColorScheme colorScheme) {
|
||||||
|
// YYC MARK:
|
||||||
|
// If we are using Windows 10 and lower system or Qt which do not support "Windows 11"
|
||||||
|
// theme (lower than Qt 6.7), we should only support light mode.
|
||||||
|
// Because Qt's dark-mode-aware "Windows 11" theme is only well-supported on Windows 11.
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
|
||||||
|
colorScheme = Qt::ColorScheme::Light;
|
||||||
|
#else
|
||||||
|
if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows11) {
|
||||||
|
colorScheme = Qt::ColorScheme::Light;
|
||||||
|
}
|
||||||
|
#endif // QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
|
||||||
|
#endif // defined(Q_OS_WIN)
|
||||||
|
|
||||||
|
switch (colorScheme) {
|
||||||
|
case Qt::ColorScheme::Dark:
|
||||||
|
static QColor WHITE = QColor("White");
|
||||||
|
return WHITE;
|
||||||
|
case Qt::ColorScheme::Light:
|
||||||
|
case Qt::ColorScheme::Unknown:
|
||||||
|
default:
|
||||||
|
static QColor BLACK = QColor("Black");
|
||||||
|
return BLACK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class PGraphicsPixmapItem : public QGraphicsPixmapItem
|
class PGraphicsPixmapItem : public QGraphicsPixmapItem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -99,7 +129,8 @@ private:
|
|||||||
GraphicsScene::GraphicsScene(QObject *parent)
|
GraphicsScene::GraphicsScene(QObject *parent)
|
||||||
: QGraphicsScene(parent)
|
: QGraphicsScene(parent)
|
||||||
{
|
{
|
||||||
showText(tr("Drag image here"));
|
showText(tr("Drag image here", "The same meaning of MainWindow's hint."));
|
||||||
|
connect(qGuiApp->styleHints(), &QStyleHints::colorSchemeChanged, this, &GraphicsScene::updateStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphicsScene::~GraphicsScene()
|
GraphicsScene::~GraphicsScene()
|
||||||
@@ -121,7 +152,7 @@ void GraphicsScene::showText(const QString &text)
|
|||||||
{
|
{
|
||||||
this->clear();
|
this->clear();
|
||||||
QGraphicsTextItem * textItem = this->addText(text);
|
QGraphicsTextItem * textItem = this->addText(text);
|
||||||
textItem->setDefaultTextColor(QColor("White"));
|
textItem->setDefaultTextColor(ColorSchemaToTextForeground(qGuiApp->styleHints()->colorScheme()));
|
||||||
m_theThing = textItem;
|
m_theThing = textItem;
|
||||||
this->setSceneRect(m_theThing->boundingRect());
|
this->setSceneRect(m_theThing->boundingRect());
|
||||||
}
|
}
|
||||||
@@ -169,27 +200,57 @@ bool GraphicsScene::trySetTransformationMode(Qt::TransformationMode mode, float
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetch graphics item as our custom animated item.
|
||||||
|
/// Return nullptr means this item is not animated item.
|
||||||
|
static PGraphicsMovieItem* fetchAnimatedItem(QGraphicsItem *item) {
|
||||||
|
return qgraphicsitem_cast<PGraphicsMovieItem *>(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GraphicsScene::canPauseAnimation() const {
|
||||||
|
return fetchAnimatedItem(m_theThing) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GraphicsScene::isPauseAnimation() const {
|
||||||
|
auto* animatedItem = fetchAnimatedItem(m_theThing);
|
||||||
|
if (animatedItem != nullptr) {
|
||||||
|
return animatedItem->movie()->state() == QMovie::Paused;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool GraphicsScene::togglePauseAnimation()
|
bool GraphicsScene::togglePauseAnimation()
|
||||||
{
|
{
|
||||||
PGraphicsMovieItem * animatedItem = qgraphicsitem_cast<PGraphicsMovieItem *>(m_theThing);
|
auto* animatedItem = fetchAnimatedItem(m_theThing);
|
||||||
if (animatedItem) {
|
if (animatedItem != nullptr) {
|
||||||
animatedItem->movie()->setPaused(animatedItem->movie()->state() != QMovie::Paused);
|
animatedItem->movie()->setPaused(animatedItem->movie()->state() != QMovie::Paused);
|
||||||
return true;
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
bool GraphicsScene::canSkipAnimationFrame() const {
|
||||||
|
return isPauseAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GraphicsScene::skipAnimationFrame(int delta)
|
bool GraphicsScene::skipAnimationFrame(int delta)
|
||||||
{
|
{
|
||||||
PGraphicsMovieItem * animatedItem = qgraphicsitem_cast<PGraphicsMovieItem *>(m_theThing);
|
auto* animatedItem = fetchAnimatedItem(m_theThing);
|
||||||
if (animatedItem) {
|
|
||||||
const int frameCount = animatedItem->movie()->frameCount();
|
// Only paused animated item can skip frame
|
||||||
const int currentFrame = animatedItem->movie()->currentFrameNumber();
|
if (animatedItem == nullptr) {
|
||||||
const int targetFrame = (currentFrame + delta) % frameCount;
|
return false;
|
||||||
animatedItem->movie()->setPaused(true);
|
|
||||||
return animatedItem->movie()->jumpToFrame(targetFrame);
|
|
||||||
}
|
}
|
||||||
return false;
|
if (animatedItem->movie()->state() != QMovie::Paused) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform skip
|
||||||
|
const int frameCount = animatedItem->movie()->frameCount();
|
||||||
|
const int currentFrame = animatedItem->movie()->currentFrameNumber();
|
||||||
|
const int targetFrame = (currentFrame + delta) % frameCount;
|
||||||
|
return animatedItem->movie()->jumpToFrame(targetFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmap GraphicsScene::renderToPixmap()
|
QPixmap GraphicsScene::renderToPixmap()
|
||||||
@@ -206,3 +267,14 @@ QPixmap GraphicsScene::renderToPixmap()
|
|||||||
|
|
||||||
return pixmap;
|
return pixmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GraphicsScene::updateStyle(Qt::ColorScheme colorScheme) {
|
||||||
|
Q_UNUSED(colorScheme)
|
||||||
|
|
||||||
|
QGraphicsItem * item = this->m_theThing;
|
||||||
|
if (item == nullptr) return;
|
||||||
|
QGraphicsTextItem * textItem = qgraphicsitem_cast<QGraphicsTextItem*>(item);
|
||||||
|
if (textItem == nullptr) return;
|
||||||
|
|
||||||
|
textItem->setDefaultTextColor(ColorSchemaToTextForeground(colorScheme));
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,11 +21,26 @@ public:
|
|||||||
|
|
||||||
bool trySetTransformationMode(Qt::TransformationMode mode, float scaleHint);
|
bool trySetTransformationMode(Qt::TransformationMode mode, float scaleHint);
|
||||||
|
|
||||||
|
/// Check whether current thing is animation.
|
||||||
|
/// Only animation thing can be paused.
|
||||||
|
bool canPauseAnimation() const;
|
||||||
|
/// Check whether we are pasuing an animation graphics.
|
||||||
|
bool isPauseAnimation() const;
|
||||||
|
/// Toggle animation graphics pause state.
|
||||||
|
/// Return true for successfully toggle, otherwise false.
|
||||||
bool togglePauseAnimation();
|
bool togglePauseAnimation();
|
||||||
|
/// Check whether we can skip animation frame.
|
||||||
|
/// Only paused animation graphics can skip frame.
|
||||||
|
bool canSkipAnimationFrame() const;
|
||||||
|
/// Skip paused animation graphics with given frame.
|
||||||
|
/// Return true for successfully skip, otherwise false.
|
||||||
bool skipAnimationFrame(int delta = 1);
|
bool skipAnimationFrame(int delta = 1);
|
||||||
|
|
||||||
QPixmap renderToPixmap();
|
QPixmap renderToPixmap();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void updateStyle(Qt::ColorScheme colorScheme);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QGraphicsItem * m_theThing = nullptr;
|
QGraphicsItem * m_theThing = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
+55
-58
@@ -7,6 +7,8 @@
|
|||||||
#include "graphicsscene.h"
|
#include "graphicsscene.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
|
#include <QStyleHints>
|
||||||
|
#include <QOperatingSystemVersion>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QColorSpace>
|
#include <QColorSpace>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
@@ -23,11 +25,12 @@ GraphicsView::GraphicsView(QWidget *parent)
|
|||||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
setResizeAnchor(QGraphicsView::AnchorUnderMouse);
|
setResizeAnchor(QGraphicsView::AnchorUnderMouse);
|
||||||
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
|
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
|
||||||
setStyleSheet("background-color: rgba(0, 0, 0, 220);"
|
|
||||||
"border-radius: 3px;");
|
|
||||||
setAcceptDrops(false);
|
setAcceptDrops(false);
|
||||||
setCheckerboardEnabled(false);
|
setCheckerboardEnabled(false);
|
||||||
|
|
||||||
|
updateStyle(qGuiApp->styleHints()->colorScheme());
|
||||||
|
connect(qGuiApp->styleHints(), &QStyleHints::colorSchemeChanged, this, &GraphicsView::updateStyle);
|
||||||
|
|
||||||
connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, &GraphicsView::viewportRectChanged);
|
connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, &GraphicsView::viewportRectChanged);
|
||||||
connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &GraphicsView::viewportRectChanged);
|
connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &GraphicsView::viewportRectChanged);
|
||||||
}
|
}
|
||||||
@@ -137,8 +140,7 @@ void GraphicsView::resetTransform()
|
|||||||
|
|
||||||
void GraphicsView::zoomView(qreal scaleFactor)
|
void GraphicsView::zoomView(qreal scaleFactor)
|
||||||
{
|
{
|
||||||
m_enableFitInView = false;
|
m_enableFitToScreen = false;
|
||||||
m_longImageMode = false;
|
|
||||||
scale(scaleFactor, scaleFactor);
|
scale(scaleFactor, scaleFactor);
|
||||||
applyTransformationModeByScaleFactor();
|
applyTransformationModeByScaleFactor();
|
||||||
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
||||||
@@ -221,6 +223,8 @@ void GraphicsView::fitByOrientation(Qt::Orientation ori, bool scaleDownOnly)
|
|||||||
// Position the image correctly based on orientation with rotation consideration
|
// Position the image correctly based on orientation with rotation consideration
|
||||||
QRectF originalScene = sceneRect();
|
QRectF originalScene = sceneRect();
|
||||||
QTransform currentTransform = transform();
|
QTransform currentTransform = transform();
|
||||||
|
|
||||||
|
// TODO: May be optimized by matrix arithmetic operation?
|
||||||
|
|
||||||
if (ori == Qt::Horizontal) {
|
if (ori == Qt::Horizontal) {
|
||||||
// For horizontal fit (fit by width), position at top (for tall images)
|
// For horizontal fit (fit by width), position at top (for tall images)
|
||||||
@@ -274,37 +278,12 @@ void GraphicsView::fitByOrientation(Qt::Orientation ori, bool scaleDownOnly)
|
|||||||
centerOn(sceneLeftCenter);
|
centerOn(sceneLeftCenter);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_enableFitInView = false;
|
m_enableFitToScreen = false;
|
||||||
|
|
||||||
applyTransformationModeByScaleFactor();
|
applyTransformationModeByScaleFactor();
|
||||||
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GraphicsView::isLongImage() const
|
|
||||||
{
|
|
||||||
// Get the transformed image size (considering rotation and other transforms)
|
|
||||||
QRectF transformedRect = transform().mapRect(sceneRect());
|
|
||||||
QSizeF imageSize = transformedRect.size();
|
|
||||||
|
|
||||||
if (imageSize.isEmpty()) return false;
|
|
||||||
|
|
||||||
qreal aspectRatio = imageSize.width() / imageSize.height();
|
|
||||||
|
|
||||||
// Check if aspect ratio exceeds 5:2 (wide) or 2:5 (tall)
|
|
||||||
return aspectRatio > 2.5 || aspectRatio < 0.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GraphicsView::fitLongImage()
|
|
||||||
{
|
|
||||||
QRectF transformedRect = transform().mapRect(sceneRect());
|
|
||||||
|
|
||||||
if (transformedRect.width() < transformedRect.height()) {
|
|
||||||
fitByOrientation(Qt::Horizontal, true);
|
|
||||||
} else {
|
|
||||||
fitByOrientation(Qt::Vertical, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GraphicsView::displayScene()
|
void GraphicsView::displayScene()
|
||||||
{
|
{
|
||||||
if (shouldAvoidTransform()) {
|
if (shouldAvoidTransform()) {
|
||||||
@@ -312,14 +291,6 @@ void GraphicsView::displayScene()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if should apply long image mode
|
|
||||||
if (Settings::instance()->autoLongImageMode() && isLongImage()) {
|
|
||||||
m_longImageMode = true;
|
|
||||||
m_firstUserMediaLoaded = true;
|
|
||||||
if (isSceneBiggerThanView()) fitLongImage();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSceneBiggerThanView()) {
|
if (isSceneBiggerThanView()) {
|
||||||
// Do fit-in-view
|
// Do fit-in-view
|
||||||
fitInView(sceneRect(), Qt::KeepAspectRatio);
|
fitInView(sceneRect(), Qt::KeepAspectRatio);
|
||||||
@@ -330,8 +301,7 @@ void GraphicsView::displayScene()
|
|||||||
emit navigatorViewRequired(false, transform());
|
emit navigatorViewRequired(false, transform());
|
||||||
}
|
}
|
||||||
|
|
||||||
m_longImageMode = false;
|
m_enableFitToScreen = true;
|
||||||
m_enableFitInView = true;
|
|
||||||
m_firstUserMediaLoaded = true;
|
m_firstUserMediaLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,17 +315,12 @@ bool GraphicsView::isSceneBiggerThanView() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Automately do fit in view when viewport(window) smaller than image original size.
|
// Automately do fit in view when viewport(window) smaller than image original size.
|
||||||
void GraphicsView::setEnableAutoFitInView(bool enable)
|
void GraphicsView::setEnableAutoFitToScreen(bool enable)
|
||||||
{
|
{
|
||||||
m_enableFitInView = enable;
|
m_enableFitToScreen = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::setLongImageMode(bool enable)
|
bool GraphicsView::isAvoidResetTransform() const
|
||||||
{
|
|
||||||
m_longImageMode = enable;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GraphicsView::avoidResetTransform() const
|
|
||||||
{
|
{
|
||||||
return m_avoidResetTransform;
|
return m_avoidResetTransform;
|
||||||
}
|
}
|
||||||
@@ -365,6 +330,15 @@ void GraphicsView::setAvoidResetTransform(bool avoidReset)
|
|||||||
m_avoidResetTransform = avoidReset;
|
m_avoidResetTransform = avoidReset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GraphicsView::isCheckerboard() const {
|
||||||
|
return m_checkerboardEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsView::toggleCheckerboard(bool invertCheckerboardColor)
|
||||||
|
{
|
||||||
|
setCheckerboardEnabled(!m_checkerboardEnabled, invertCheckerboardColor);
|
||||||
|
}
|
||||||
|
|
||||||
inline double zeroOrOne(double number)
|
inline double zeroOrOne(double number)
|
||||||
{
|
{
|
||||||
return qFuzzyIsNull(number) ? 0 : (number > 0 ? 1 : -1);
|
return qFuzzyIsNull(number) ? 0 : (number > 0 ? 1 : -1);
|
||||||
@@ -379,9 +353,37 @@ QTransform GraphicsView::resetScale(const QTransform & orig)
|
|||||||
orig.dx(), orig.dy());
|
orig.dx(), orig.dy());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::toggleCheckerboard(bool invertCheckerboardColor)
|
void GraphicsView::updateStyle(Qt::ColorScheme colorScheme) {
|
||||||
{
|
// YYC MARK:
|
||||||
setCheckerboardEnabled(!m_checkerboardEnabled, invertCheckerboardColor);
|
// If we are using Windows 10 and lower system or Qt which do not support "Windows 11"
|
||||||
|
// theme (lower than Qt 6.7), we should only support light mode.
|
||||||
|
// Because Qt's dark-mode-aware "Windows 11" theme is only well-supported on Windows 11.
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
|
||||||
|
colorScheme = Qt::ColorScheme::Light;
|
||||||
|
#else
|
||||||
|
if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows11) {
|
||||||
|
colorScheme = Qt::ColorScheme::Light;
|
||||||
|
}
|
||||||
|
#endif // QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
|
||||||
|
#endif // defined(Q_OS_WIN)
|
||||||
|
|
||||||
|
switch (colorScheme) {
|
||||||
|
case Qt::ColorScheme::Dark:
|
||||||
|
setStyleSheet(R"#(
|
||||||
|
border-style: none;
|
||||||
|
background-color: black;
|
||||||
|
)#");
|
||||||
|
break;
|
||||||
|
case Qt::ColorScheme::Light:
|
||||||
|
case Qt::ColorScheme::Unknown:
|
||||||
|
default:
|
||||||
|
setStyleSheet(R"#(
|
||||||
|
border-style: none;
|
||||||
|
background-color: white;
|
||||||
|
)#");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::mousePressEvent(QMouseEvent *event)
|
void GraphicsView::mousePressEvent(QMouseEvent *event)
|
||||||
@@ -427,16 +429,11 @@ void GraphicsView::wheelEvent(QWheelEvent *event)
|
|||||||
|
|
||||||
void GraphicsView::resizeEvent(QResizeEvent *event)
|
void GraphicsView::resizeEvent(QResizeEvent *event)
|
||||||
{
|
{
|
||||||
if (m_longImageMode) {
|
if (m_enableFitToScreen) {
|
||||||
// In long image mode, reapply long image logic on resize
|
|
||||||
// We directly apply the long image mode logic without rechecking
|
|
||||||
// if we should enter long image mode, as the mode is already active
|
|
||||||
fitLongImage();
|
|
||||||
} else if (m_enableFitInView) {
|
|
||||||
bool originalSizeSmallerThanWindow = isThingSmallerThanWindowWith(resetScale(transform()));
|
bool originalSizeSmallerThanWindow = isThingSmallerThanWindowWith(resetScale(transform()));
|
||||||
if (originalSizeSmallerThanWindow && scaleFactor() >= 1) {
|
if (originalSizeSmallerThanWindow && scaleFactor() >= 1) {
|
||||||
// no longer need to do fitInView()
|
// no longer need to do fitInView()
|
||||||
// but we leave the m_enableFitInView value unchanged in case
|
// but we leave the m_enableToScreen value unchanged in case
|
||||||
// user resize down the window again.
|
// user resize down the window again.
|
||||||
} else if (originalSizeSmallerThanWindow && scaleFactor() < 1) {
|
} else if (originalSizeSmallerThanWindow && scaleFactor() < 1) {
|
||||||
resetScale();
|
resetScale();
|
||||||
|
|||||||
+7
-10
@@ -38,24 +38,22 @@ public:
|
|||||||
|
|
||||||
void displayScene();
|
void displayScene();
|
||||||
bool isSceneBiggerThanView() const;
|
bool isSceneBiggerThanView() const;
|
||||||
void setEnableAutoFitInView(bool enable = true);
|
void setEnableAutoFitToScreen(bool enable = true);
|
||||||
void setLongImageMode(bool enable = true);
|
|
||||||
|
|
||||||
bool avoidResetTransform() const;
|
bool isAvoidResetTransform() const;
|
||||||
void setAvoidResetTransform(bool avoidReset);
|
void setAvoidResetTransform(bool avoidReset);
|
||||||
|
|
||||||
static QTransform resetScale(const QTransform & orig);
|
bool isCheckerboard() const;
|
||||||
|
void toggleCheckerboard(bool invertCheckerboardColor = false);
|
||||||
|
|
||||||
// Long image mode support
|
static QTransform resetScale(const QTransform & orig);
|
||||||
bool isLongImage() const;
|
|
||||||
void fitLongImage();
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void navigatorViewRequired(bool required, QTransform transform);
|
void navigatorViewRequired(bool required, QTransform transform);
|
||||||
void viewportRectChanged();
|
void viewportRectChanged();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void toggleCheckerboard(bool invertCheckerboardColor = false);
|
void updateStyle(Qt::ColorScheme colorScheme);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void mousePressEvent(QMouseEvent * event) override;
|
void mousePressEvent(QMouseEvent * event) override;
|
||||||
@@ -74,8 +72,7 @@ private:
|
|||||||
// Consider switch to 3 state for "no fit", "always fit" and "fit when view is smaller"?
|
// Consider switch to 3 state for "no fit", "always fit" and "fit when view is smaller"?
|
||||||
// ... or even more? e.g. "fit/snap width" things...
|
// ... or even more? e.g. "fit/snap width" things...
|
||||||
// Currently it's "no fit" when it's false and "fit when view is smaller" when it's true.
|
// Currently it's "no fit" when it's false and "fit when view is smaller" when it's true.
|
||||||
bool m_enableFitInView = false;
|
bool m_enableFitToScreen = false;
|
||||||
bool m_longImageMode = false;
|
|
||||||
bool m_avoidResetTransform = false;
|
bool m_avoidResetTransform = false;
|
||||||
bool m_checkerboardEnabled = false;
|
bool m_checkerboardEnabled = false;
|
||||||
bool m_useLightCheckerboard = false;
|
bool m_useLightCheckerboard = false;
|
||||||
|
|||||||
+5
-12
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
|
||||||
#include "playlistmanager.h"
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
@@ -68,21 +67,19 @@ int main(int argc, char *argv[])
|
|||||||
a.installEventFilter(fileOpenEventHandler);
|
a.installEventFilter(fileOpenEventHandler);
|
||||||
a.connect(fileOpenEventHandler, &FileOpenEventHandler::fileOpen, [&w](const QUrl & url){
|
a.connect(fileOpenEventHandler, &FileOpenEventHandler::fileOpen, [&w](const QUrl & url){
|
||||||
if (w.isHidden()) {
|
if (w.isHidden()) {
|
||||||
w.setWindowOpacity(1);
|
|
||||||
w.showNormal();
|
w.showNormal();
|
||||||
} else {
|
} else {
|
||||||
w.activateWindow();
|
w.activateWindow();
|
||||||
}
|
}
|
||||||
w.showUrls({url});
|
w.showFiles({url});
|
||||||
w.initWindowSize();
|
w.initWindowSize();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle dock icon clicks to show hidden window
|
// Handle dock icon clicks to show hidden window
|
||||||
a.connect(&a, &QApplication::applicationStateChanged, [&w](Qt::ApplicationState state) {
|
a.connect(&a, &QApplication::applicationStateChanged, [&w](Qt::ApplicationState state) {
|
||||||
if (state == Qt::ApplicationActive && w.isHidden()) {
|
if (state == Qt::ApplicationActive && w.isHidden()) {
|
||||||
w.showUrls({});
|
w.showFiles({});
|
||||||
w.galleryCurrent(true, true);
|
w.galleryCurrent(false);
|
||||||
w.setWindowOpacity(1);
|
|
||||||
w.showNormal();
|
w.showNormal();
|
||||||
w.raise();
|
w.raise();
|
||||||
w.activateWindow();
|
w.activateWindow();
|
||||||
@@ -90,12 +87,8 @@ int main(int argc, char *argv[])
|
|||||||
});
|
});
|
||||||
#endif // Q_OS_MACOS
|
#endif // Q_OS_MACOS
|
||||||
|
|
||||||
QStringList urlStrList = parser.positionalArguments();
|
QStringList fileList = parser.positionalArguments();
|
||||||
QList<QUrl> && urlList = PlaylistManager::convertToUrlList(urlStrList);
|
w.showFiles(fileList);
|
||||||
|
|
||||||
if (!urlList.isEmpty()) {
|
|
||||||
w.showUrls(urlList);
|
|
||||||
}
|
|
||||||
|
|
||||||
w.initWindowSize();
|
w.initWindowSize();
|
||||||
|
|
||||||
|
|||||||
+240
-596
File diff suppressed because it is too large
Load Diff
+30
-55
@@ -5,25 +5,23 @@
|
|||||||
#ifndef MAINWINDOW_H
|
#ifndef MAINWINDOW_H
|
||||||
#define MAINWINDOW_H
|
#define MAINWINDOW_H
|
||||||
|
|
||||||
#include "framelesswindow.h"
|
|
||||||
|
|
||||||
#include <QParallelAnimationGroup>
|
#include <QParallelAnimationGroup>
|
||||||
#include <QPropertyAnimation>
|
#include <QPropertyAnimation>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
class QGraphicsOpacityEffect;
|
class QMenu;
|
||||||
|
class QVBoxLayout;
|
||||||
class QGraphicsView;
|
class QGraphicsView;
|
||||||
class QFileSystemWatcher;
|
class QFileSystemWatcher;
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
class ActionManager;
|
class ActionManager;
|
||||||
class PlaylistManager;
|
class PlaylistManager;
|
||||||
class ToolButton;
|
|
||||||
class GraphicsView;
|
class GraphicsView;
|
||||||
class NavigatorView;
|
class NavigatorView;
|
||||||
class BottomButtonGroup;
|
class BottomButtonGroup;
|
||||||
class MainWindow : public FramelessWindow
|
class MainWindow : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@@ -31,25 +29,20 @@ public:
|
|||||||
explicit MainWindow(QWidget *parent = nullptr);
|
explicit MainWindow(QWidget *parent = nullptr);
|
||||||
~MainWindow() override;
|
~MainWindow() override;
|
||||||
|
|
||||||
void showUrls(const QList<QUrl> &urls);
|
void showFiles(const QStringList& files);
|
||||||
|
void showFiles(const QList<QUrl>& urls);
|
||||||
void initWindowSize();
|
void initWindowSize();
|
||||||
void adjustWindowSizeBySceneRect();
|
void adjustWindowSizeBySceneRect();
|
||||||
QUrl currentImageFileUrl() const;
|
|
||||||
|
|
||||||
void clearGallery();
|
void clearGallery();
|
||||||
void galleryPrev();
|
void galleryPrev();
|
||||||
void galleryNext();
|
void galleryNext();
|
||||||
void galleryCurrent(bool showLoadImageHintWhenEmpty, bool reloadImage);
|
void galleryCurrent(bool fromFileWatcher);
|
||||||
|
|
||||||
static QStringList supportedImageFormats();
|
static QStringList supportedImageFormats();
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void showEvent(QShowEvent *event) override;
|
void showEvent(QShowEvent *event) override;
|
||||||
void enterEvent(QEnterEvent *event) override;
|
|
||||||
void leaveEvent(QEvent *event) override;
|
|
||||||
void mousePressEvent(QMouseEvent *event) override;
|
|
||||||
void mouseMoveEvent(QMouseEvent *event) override;
|
|
||||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
|
||||||
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||||
void wheelEvent(QWheelEvent *event) override;
|
void wheelEvent(QWheelEvent *event) override;
|
||||||
void resizeEvent(QResizeEvent *event) override;
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
@@ -61,12 +54,7 @@ protected slots:
|
|||||||
void centerWindow();
|
void centerWindow();
|
||||||
void closeWindow();
|
void closeWindow();
|
||||||
void updateWidgetsPosition();
|
void updateWidgetsPosition();
|
||||||
void toggleProtectedMode();
|
|
||||||
void toggleStayOnTop();
|
|
||||||
void toggleAvoidResetTransform();
|
void toggleAvoidResetTransform();
|
||||||
bool stayOnTop() const;
|
|
||||||
bool canPaste() const;
|
|
||||||
void quitAppAction(bool force = false);
|
|
||||||
void toggleFullscreen();
|
void toggleFullscreen();
|
||||||
void toggleMaximize();
|
void toggleMaximize();
|
||||||
|
|
||||||
@@ -74,63 +62,50 @@ protected:
|
|||||||
QSize sizeHint() const override;
|
QSize sizeHint() const override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void on_actionOpen_triggered();
|
void on_actionCopyPixmap_triggered();
|
||||||
void on_actionSaveAs_triggered();
|
void on_actionCopyFilePath_triggered();
|
||||||
|
|
||||||
void on_actionActualSize_triggered();
|
|
||||||
void on_actionToggleMaximize_triggered();
|
|
||||||
void on_actionZoomIn_triggered();
|
|
||||||
void on_actionZoomOut_triggered();
|
|
||||||
void on_actionToggleCheckerboard_triggered();
|
|
||||||
void on_actionRotateClockwise_triggered();
|
|
||||||
void on_actionRotateCounterClockwise_triggered();
|
|
||||||
|
|
||||||
void on_actionPrevPicture_triggered();
|
void on_actionPrevPicture_triggered();
|
||||||
void on_actionNextPicture_triggered();
|
void on_actionNextPicture_triggered();
|
||||||
|
|
||||||
|
void on_actionActualSize_triggered();
|
||||||
|
void on_actionFitToScreen_triggered();
|
||||||
|
void on_actionFitByWidth_triggered();
|
||||||
|
void on_actionFitByHeight_triggered();
|
||||||
|
void on_actionZoomIn_triggered();
|
||||||
|
void on_actionZoomOut_triggered();
|
||||||
|
|
||||||
|
void on_actionFlipHorizontal_triggered();
|
||||||
|
void on_actionFlipVertical_triggered();
|
||||||
|
void on_actionRotateClockwise_triggered();
|
||||||
|
void on_actionRotateCounterClockwise_triggered();
|
||||||
|
|
||||||
|
void on_actionToggleAvoidResetTransform_triggered();
|
||||||
|
void on_actionToggleCheckerboard_triggered();
|
||||||
void on_actionTogglePauseAnimation_triggered();
|
void on_actionTogglePauseAnimation_triggered();
|
||||||
void on_actionAnimationNextFrame_triggered();
|
void on_actionAnimationNextFrame_triggered();
|
||||||
|
|
||||||
void on_actionHorizontalFlip_triggered();
|
|
||||||
void on_actionFitInView_triggered();
|
|
||||||
void on_actionFitByWidth_triggered();
|
|
||||||
void on_actionFitLongImage_triggered();
|
|
||||||
void on_actionCopyPixmap_triggered();
|
|
||||||
void on_actionCopyFilePath_triggered();
|
|
||||||
void on_actionPaste_triggered();
|
|
||||||
void on_actionTrash_triggered();
|
void on_actionTrash_triggered();
|
||||||
void on_actionToggleStayOnTop_triggered();
|
void on_actionLocateInFileManager_triggered();
|
||||||
void on_actionToggleProtectMode_triggered();
|
void on_actionProperties_triggered();
|
||||||
void on_actionToggleAvoidResetTransform_triggered();
|
|
||||||
void on_actionSettings_triggered();
|
void on_actionSettings_triggered();
|
||||||
void on_actionHelp_triggered();
|
void on_actionHelp_triggered();
|
||||||
void on_actionProperties_triggered();
|
|
||||||
void on_actionLocateInFileManager_triggered();
|
|
||||||
void on_actionQuitApp_triggered();
|
|
||||||
|
|
||||||
void doCloseWindow();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool updateFileWatcher(const QString & basePath = QString());
|
bool updateFileWatcher(const QString & basePath = QString());
|
||||||
void updateGalleryButtonsVisibility();
|
void updateActionState();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ActionManager *m_am;
|
ActionManager *m_am;
|
||||||
PlaylistManager *m_pm;
|
PlaylistManager *m_pm;
|
||||||
|
GraphicsView *m_gv;
|
||||||
|
NavigatorView *m_nav;
|
||||||
|
|
||||||
QPoint m_oldMousePos;
|
QMenu *m_menu;
|
||||||
QPropertyAnimation *m_fadeOutAnimation;
|
QVBoxLayout * m_centralLayout = nullptr;
|
||||||
QPropertyAnimation *m_floatUpAnimation;
|
|
||||||
QParallelAnimationGroup *m_exitAnimationGroup;
|
|
||||||
QFileSystemWatcher *m_fileSystemWatcher;
|
QFileSystemWatcher *m_fileSystemWatcher;
|
||||||
ToolButton *m_closeButton;
|
|
||||||
ToolButton *m_prevButton;
|
|
||||||
ToolButton *m_nextButton;
|
|
||||||
GraphicsView *m_graphicsView;
|
|
||||||
NavigatorView *m_gv;
|
|
||||||
BottomButtonGroup *m_bottomButtonGroup;
|
BottomButtonGroup *m_bottomButtonGroup;
|
||||||
bool m_protectedMode = false;
|
|
||||||
bool m_clickedOnWindow = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MAINWINDOW_H
|
#endif // MAINWINDOW_H
|
||||||
|
|||||||
+18
-15
@@ -3,12 +3,12 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "metadatadialog.h"
|
#include "metadatadialog.h"
|
||||||
|
#include "ui_metadatadialog.h"
|
||||||
|
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QStyledItemDelegate>
|
#include <QStyledItemDelegate>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
#include <QVBoxLayout>
|
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
|
|
||||||
#include "metadatamodel.h"
|
#include "metadatamodel.h"
|
||||||
@@ -73,30 +73,33 @@ protected:
|
|||||||
};
|
};
|
||||||
|
|
||||||
MetadataDialog::MetadataDialog(QWidget *parent)
|
MetadataDialog::MetadataDialog(QWidget *parent)
|
||||||
: QDialog(parent)
|
: QDialog(parent), ui(new QT_PREPEND_NAMESPACE(Ui::MetadataDialog))
|
||||||
, m_treeView(new PropertyTreeView(this))
|
|
||||||
{
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
|
||||||
|
|
||||||
|
// YYC MARK:
|
||||||
|
// Because we use custom QTreeView and we do not want to expose it to public domain.
|
||||||
|
// So I have to use this way as the compromise.
|
||||||
|
|
||||||
|
// Create our custom QTreeView and insert them
|
||||||
|
m_treeView = new PropertyTreeView(this);
|
||||||
|
auto* layout = ui->verticalLayout;
|
||||||
|
const int index = layout->indexOf(ui->m_buttonBox);
|
||||||
|
layout->insertWidget(index, m_treeView);
|
||||||
|
|
||||||
|
// Setup our custom QTreeView
|
||||||
m_treeView->setRootIsDecorated(false);
|
m_treeView->setRootIsDecorated(false);
|
||||||
m_treeView->setIndentation(0);
|
m_treeView->setIndentation(0);
|
||||||
m_treeView->setItemDelegate(new PropertyTreeItemDelegate(m_treeView));
|
m_treeView->setItemDelegate(new PropertyTreeItemDelegate(m_treeView));
|
||||||
m_treeView->header()->resizeSection(0, sizeHint().width() / 2);
|
m_treeView->header()->resizeSection(0, sizeHint().width() / 2);
|
||||||
|
|
||||||
setWindowTitle(tr("Image Metadata"));
|
connect(ui->m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::close);
|
||||||
|
|
||||||
QDialogButtonBox * buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
|
|
||||||
|
|
||||||
setLayout(new QVBoxLayout);
|
|
||||||
layout()->addWidget(m_treeView);
|
|
||||||
layout()->addWidget(buttonBox);
|
|
||||||
|
|
||||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::close);
|
|
||||||
|
|
||||||
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MetadataDialog::~MetadataDialog()
|
MetadataDialog::~MetadataDialog()
|
||||||
{
|
{
|
||||||
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MetadataDialog::setMetadataModel(MetadataModel * model)
|
void MetadataDialog::setMetadataModel(MetadataModel * model)
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ QT_BEGIN_NAMESPACE
|
|||||||
class QTreeView;
|
class QTreeView;
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
namespace Ui {
|
||||||
|
class MetadataDialog;
|
||||||
|
}
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
class MetadataModel;
|
class MetadataModel;
|
||||||
class MetadataDialog : public QDialog
|
class MetadataDialog : public QDialog
|
||||||
{
|
{
|
||||||
@@ -25,6 +31,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
QTreeView * m_treeView = nullptr;
|
QTreeView * m_treeView = nullptr;
|
||||||
|
QT_PREPEND_NAMESPACE(Ui::MetadataDialog) *ui;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // METADATADIALOG_H
|
#endif // METADATADIALOG_H
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>MetadataDialog</class>
|
||||||
|
<widget class="QDialog" name="MetadataDialog">
|
||||||
|
<property name="windowModality">
|
||||||
|
<enum>Qt::NonModal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Image Metadata</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="m_buttonBox">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Close</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
+38
-9
@@ -5,8 +5,9 @@
|
|||||||
#include "navigatorview.h"
|
#include "navigatorview.h"
|
||||||
|
|
||||||
#include "graphicsview.h"
|
#include "graphicsview.h"
|
||||||
#include "opacityhelper.h"
|
|
||||||
|
|
||||||
|
#include <QStyleHints>
|
||||||
|
#include <QOperatingSystemVersion>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
@@ -14,12 +15,12 @@
|
|||||||
NavigatorView::NavigatorView(QWidget *parent)
|
NavigatorView::NavigatorView(QWidget *parent)
|
||||||
: QGraphicsView (parent)
|
: QGraphicsView (parent)
|
||||||
, m_viewportRegion(this->rect())
|
, m_viewportRegion(this->rect())
|
||||||
, m_opacityHelper(new OpacityHelper(this))
|
|
||||||
{
|
{
|
||||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
setStyleSheet("background-color: rgba(0, 0, 0, 120);"
|
|
||||||
"border-radius: 3px;");
|
updateStyle(qGuiApp->styleHints()->colorScheme());
|
||||||
|
connect(qGuiApp->styleHints(), &QStyleHints::colorSchemeChanged, this, &NavigatorView::updateStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// doesn't take or manage its ownership
|
// doesn't take or manage its ownership
|
||||||
@@ -28,11 +29,6 @@ void NavigatorView::setMainView(GraphicsView *mainView)
|
|||||||
m_mainView = mainView;
|
m_mainView = mainView;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavigatorView::setOpacity(qreal opacity, bool animated)
|
|
||||||
{
|
|
||||||
m_opacityHelper->setOpacity(opacity, animated);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NavigatorView::updateMainViewportRegion()
|
void NavigatorView::updateMainViewportRegion()
|
||||||
{
|
{
|
||||||
// Use QTimer::singleShot with lambda to delay the update
|
// Use QTimer::singleShot with lambda to delay the update
|
||||||
@@ -45,6 +41,39 @@ void NavigatorView::updateMainViewportRegion()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NavigatorView::updateStyle(Qt::ColorScheme colorScheme) {
|
||||||
|
// YYC MARK:
|
||||||
|
// If we are using Windows 10 and lower system or Qt which do not support "Windows 11"
|
||||||
|
// theme (lower than Qt 6.7), we should only support light mode.
|
||||||
|
// Because Qt's dark-mode-aware "Windows 11" theme is only well-supported on Windows 11.
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
|
||||||
|
colorScheme = Qt::ColorScheme::Light;
|
||||||
|
#else
|
||||||
|
if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows11) {
|
||||||
|
colorScheme = Qt::ColorScheme::Light;
|
||||||
|
}
|
||||||
|
#endif // QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
|
||||||
|
#endif // defined(Q_OS_WIN)
|
||||||
|
|
||||||
|
switch (colorScheme) {
|
||||||
|
case Qt::ColorScheme::Dark:
|
||||||
|
setStyleSheet(R"#(
|
||||||
|
border-style: none;
|
||||||
|
background-color: rgba(255, 255, 255, 70);
|
||||||
|
)#");
|
||||||
|
break;
|
||||||
|
case Qt::ColorScheme::Light:
|
||||||
|
case Qt::ColorScheme::Unknown:
|
||||||
|
default:
|
||||||
|
setStyleSheet(R"#(
|
||||||
|
border-style: none;
|
||||||
|
background-color: rgba(0, 0, 0, 70);
|
||||||
|
)#");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void NavigatorView::mousePressEvent(QMouseEvent *event)
|
void NavigatorView::mousePressEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
m_mouseDown = true;
|
m_mouseDown = true;
|
||||||
|
|||||||
+1
-3
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#include <QGraphicsView>
|
#include <QGraphicsView>
|
||||||
|
|
||||||
class OpacityHelper;
|
|
||||||
class GraphicsView;
|
class GraphicsView;
|
||||||
class NavigatorView : public QGraphicsView
|
class NavigatorView : public QGraphicsView
|
||||||
{
|
{
|
||||||
@@ -16,10 +15,10 @@ public:
|
|||||||
NavigatorView(QWidget *parent = nullptr);
|
NavigatorView(QWidget *parent = nullptr);
|
||||||
|
|
||||||
void setMainView(GraphicsView *mainView);
|
void setMainView(GraphicsView *mainView);
|
||||||
void setOpacity(qreal opacity, bool animated = true);
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void updateMainViewportRegion();
|
void updateMainViewportRegion();
|
||||||
|
void updateStyle(Qt::ColorScheme colorScheme);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void mousePressEvent(QMouseEvent * event) override;
|
void mousePressEvent(QMouseEvent * event) override;
|
||||||
@@ -32,7 +31,6 @@ private:
|
|||||||
bool m_mouseDown = false;
|
bool m_mouseDown = false;
|
||||||
QPolygon m_viewportRegion;
|
QPolygon m_viewportRegion;
|
||||||
QGraphicsView *m_mainView = nullptr;
|
QGraphicsView *m_mainView = nullptr;
|
||||||
OpacityHelper *m_opacityHelper = nullptr;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // NAVIGATORVIEW_H
|
#endif // NAVIGATORVIEW_H
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#include "opacityhelper.h"
|
|
||||||
|
|
||||||
#include <QGraphicsOpacityEffect>
|
|
||||||
#include <QPropertyAnimation>
|
|
||||||
|
|
||||||
OpacityHelper::OpacityHelper(QWidget *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
, m_opacityFx(new QGraphicsOpacityEffect(parent))
|
|
||||||
, m_opacityAnimation(new QPropertyAnimation(m_opacityFx, "opacity"))
|
|
||||||
{
|
|
||||||
parent->setGraphicsEffect(m_opacityFx);
|
|
||||||
|
|
||||||
m_opacityAnimation->setDuration(300);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpacityHelper::setOpacity(qreal opacity, bool animated)
|
|
||||||
{
|
|
||||||
if (!animated) {
|
|
||||||
m_opacityFx->setOpacity(opacity);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_opacityAnimation->stop();
|
|
||||||
m_opacityAnimation->setStartValue(m_opacityFx->opacity());
|
|
||||||
m_opacityAnimation->setEndValue(opacity);
|
|
||||||
m_opacityAnimation->start();
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#ifndef OPACITYHELPER_H
|
|
||||||
#define OPACITYHELPER_H
|
|
||||||
|
|
||||||
#include <QWidget>
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
|
||||||
class QGraphicsOpacityEffect;
|
|
||||||
class QPropertyAnimation;
|
|
||||||
QT_END_NAMESPACE
|
|
||||||
|
|
||||||
class OpacityHelper : QObject
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
OpacityHelper(QWidget * parent);
|
|
||||||
|
|
||||||
void setOpacity(qreal opacity, bool animated = true);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QGraphicsOpacityEffect * m_opacityFx;
|
|
||||||
QPropertyAnimation * m_opacityAnimation;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // OPACITYHELPER_H
|
|
||||||
+232
-201
@@ -9,152 +9,13 @@
|
|||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
PlaylistModel::PlaylistModel(QObject *parent)
|
|
||||||
: QAbstractListModel(parent)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
PlaylistModel::~PlaylistModel()
|
|
||||||
= default;
|
|
||||||
|
|
||||||
void PlaylistModel::setPlaylist(const QList<QUrl> &urls)
|
|
||||||
{
|
|
||||||
beginResetModel();
|
|
||||||
m_playlist = urls;
|
|
||||||
endResetModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
QModelIndex PlaylistModel::loadPlaylist(const QList<QUrl> & urls)
|
|
||||||
{
|
|
||||||
if (urls.isEmpty()) return {};
|
|
||||||
if (urls.count() == 1) {
|
|
||||||
return loadPlaylist(urls.constFirst());
|
|
||||||
} else {
|
|
||||||
setPlaylist(urls);
|
|
||||||
return index(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QModelIndex PlaylistModel::loadPlaylist(const QUrl &url)
|
|
||||||
{
|
|
||||||
QFileInfo info(url.toLocalFile());
|
|
||||||
QDir dir(info.path());
|
|
||||||
QString && currentFileName = info.fileName();
|
|
||||||
|
|
||||||
if (dir.path() == m_currentDir) {
|
|
||||||
int idx = indexOf(url);
|
|
||||||
return idx == -1 ? appendToPlaylist(url) : index(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList entryList = dir.entryList(
|
|
||||||
m_autoLoadSuffixes,
|
|
||||||
QDir::Files | QDir::NoSymLinks, QDir::NoSort);
|
|
||||||
|
|
||||||
QCollator collator;
|
|
||||||
collator.setNumericMode(true);
|
|
||||||
|
|
||||||
std::sort(entryList.begin(), entryList.end(), collator);
|
|
||||||
|
|
||||||
QList<QUrl> playlist;
|
|
||||||
|
|
||||||
int idx = -1;
|
|
||||||
for (int i = 0; i < entryList.count(); i++) {
|
|
||||||
const QString & fileName = entryList.at(i);
|
|
||||||
const QString & oneEntry = dir.absoluteFilePath(fileName);
|
|
||||||
const QUrl & url = QUrl::fromLocalFile(oneEntry);
|
|
||||||
playlist.append(url);
|
|
||||||
if (fileName == currentFileName) {
|
|
||||||
idx = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (idx == -1) {
|
|
||||||
idx = playlist.count();
|
|
||||||
playlist.append(url);
|
|
||||||
}
|
|
||||||
m_currentDir = dir.path();
|
|
||||||
|
|
||||||
setPlaylist(playlist);
|
|
||||||
|
|
||||||
return index(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
QModelIndex PlaylistModel::appendToPlaylist(const QUrl &url)
|
|
||||||
{
|
|
||||||
const int lastIndex = rowCount();
|
|
||||||
beginInsertRows(QModelIndex(), lastIndex, lastIndex);
|
|
||||||
m_playlist.append(url);
|
|
||||||
endInsertRows();
|
|
||||||
return index(lastIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PlaylistModel::removeAt(int index)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= rowCount()) return false;
|
|
||||||
beginRemoveRows(QModelIndex(), index, index);
|
|
||||||
m_playlist.removeAt(index);
|
|
||||||
endRemoveRows();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int PlaylistModel::indexOf(const QUrl &url) const
|
|
||||||
{
|
|
||||||
return m_playlist.indexOf(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
QUrl PlaylistModel::urlByIndex(int index) const
|
|
||||||
{
|
|
||||||
return m_playlist.value(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList PlaylistModel::autoLoadFilterSuffixes() const
|
|
||||||
{
|
|
||||||
return m_autoLoadSuffixes;
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> PlaylistModel::roleNames() const
|
|
||||||
{
|
|
||||||
QHash<int, QByteArray> result = QAbstractListModel::roleNames();
|
|
||||||
result.insert(UrlRole, "url");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int PlaylistModel::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
return m_playlist.count();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant PlaylistModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (!index.isValid()) return {};
|
|
||||||
|
|
||||||
switch (role) {
|
|
||||||
case Qt::DisplayRole:
|
|
||||||
return m_playlist.at(index.row()).fileName();
|
|
||||||
case UrlRole:
|
|
||||||
return m_playlist.at(index.row());
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
PlaylistManager::PlaylistManager(QObject *parent)
|
PlaylistManager::PlaylistManager(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
|
, m_currentIndex(std::nullopt)
|
||||||
|
, m_playlist()
|
||||||
|
, m_allowedSuffixes()
|
||||||
{
|
{
|
||||||
connect(&m_model, &PlaylistModel::rowsRemoved, this,
|
|
||||||
[this](const QModelIndex &, int, int) {
|
|
||||||
if (m_model.rowCount() <= m_currentIndex) {
|
|
||||||
setProperty("currentIndex", m_currentIndex - 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
auto onRowCountChanged = [this](){
|
|
||||||
emit totalCountChanged(m_model.rowCount());
|
|
||||||
};
|
|
||||||
|
|
||||||
connect(&m_model, &PlaylistModel::rowsInserted, this, onRowCountChanged);
|
|
||||||
connect(&m_model, &PlaylistModel::rowsRemoved, this, onRowCountChanged);
|
|
||||||
connect(&m_model, &PlaylistModel::modelReset, this, onRowCountChanged);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistManager::~PlaylistManager()
|
PlaylistManager::~PlaylistManager()
|
||||||
@@ -162,35 +23,106 @@ PlaylistManager::~PlaylistManager()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistModel *PlaylistManager::model()
|
// region: Allowed Suffixes Operations
|
||||||
{
|
|
||||||
return &m_model;
|
const QStringList& PlaylistManager::allowedSuffixes() const {
|
||||||
|
return m_allowedSuffixes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistManager::setPlaylist(const QList<QUrl> &urls)
|
void PlaylistManager::setAllowedSuffixes(const QStringList& nameFilters) {
|
||||||
{
|
m_allowedSuffixes = nameFilters;
|
||||||
m_model.setPlaylist(urls);
|
emit allowedSuffixesChanged(m_allowedSuffixes);
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::loadPlaylist(const QList<QUrl> &urls)
|
// endregion
|
||||||
{
|
|
||||||
QModelIndex idx = m_model.loadPlaylist(urls);
|
// region: Playlist Loader and Reset
|
||||||
setProperty("currentIndex", idx.row());
|
|
||||||
return idx;
|
bool PlaylistManager::loadPlaylist(const QStringList& files) {
|
||||||
|
// Convert string list to url list first
|
||||||
|
QList<QUrl> urls;
|
||||||
|
for (const QString & file : std::as_const(files)) {
|
||||||
|
urls.append(urlFromString(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadPlaylist(urls);
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::loadPlaylist(const QUrl &url)
|
bool PlaylistManager::loadPlaylist(const QList<QUrl>& urls) {
|
||||||
{
|
// Check its count, and check M3U8 playlist for single file case.
|
||||||
QModelIndex idx = m_model.loadPlaylist(url);
|
if (urls.isEmpty()) {
|
||||||
setProperty("currentIndex", idx.row());
|
clearPlaylist();
|
||||||
return idx;
|
return true;
|
||||||
|
} else if (urls.count() == 1) {
|
||||||
|
const QUrl& firstUrl = urls.first();
|
||||||
|
const QString lowerCaseUrlPath(firstUrl.path().toLower());
|
||||||
|
if (lowerCaseUrlPath.endsWith(".m3u8") || lowerCaseUrlPath.endsWith(".m3u")) {
|
||||||
|
return loadM3U8Playlist(firstUrl);
|
||||||
|
} else {
|
||||||
|
return loadSingleFilePlaylist(firstUrl);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return loadMultipleFilesPlaylist(std::move(urls));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::loadM3U8Playlist(const QUrl &url)
|
bool PlaylistManager::loadSingleFilePlaylist(const QUrl& url) {
|
||||||
{
|
// Check URL at first
|
||||||
|
if (!checkUrl(url))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QFileInfo info(url.toLocalFile());
|
||||||
|
QDir dir(info.path());
|
||||||
|
QString currentFileName = info.fileName();
|
||||||
|
|
||||||
|
QStringList entryList = dir.entryList(
|
||||||
|
m_allowedSuffixes, QDir::Files | QDir::NoSymLinks, QDir::NoSort);
|
||||||
|
|
||||||
|
QCollator collator;
|
||||||
|
collator.setNumericMode(true);
|
||||||
|
|
||||||
|
std::sort(entryList.begin(), entryList.end(), collator);
|
||||||
|
|
||||||
|
QList<QUrl> playlist;
|
||||||
|
std::optional<qsizetype> idx = std::nullopt;
|
||||||
|
for (qsizetype i = 0; i < entryList.count(); i++) {
|
||||||
|
const QString &fileName = entryList.at(i);
|
||||||
|
const QString &oneEntry = dir.absoluteFilePath(fileName);
|
||||||
|
const QUrl &url = QUrl::fromLocalFile(oneEntry);
|
||||||
|
if (!checkUrl(url)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
playlist.append(url);
|
||||||
|
if (fileName == currentFileName) {
|
||||||
|
idx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setPlaylist(std::move(playlist), idx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlaylistManager::loadMultipleFilesPlaylist(const QList<QUrl>&& urls) {
|
||||||
|
// Check all of them are valid
|
||||||
|
for (const auto& url : std::as_const(urls)) {
|
||||||
|
if (!checkUrl(url)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set playlist and return
|
||||||
|
setPlaylist(std::move(urls));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlaylistManager::loadM3U8Playlist(const QUrl& url) {
|
||||||
|
// Check URL at first
|
||||||
|
if (!checkUrl(url))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QList<QUrl> urls;
|
||||||
QFile file(url.toLocalFile());
|
QFile file(url.toLocalFile());
|
||||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
QList<QUrl> urls;
|
|
||||||
while (!file.atEnd()) {
|
while (!file.atEnd()) {
|
||||||
QString line = file.readLine();
|
QString line = file.readLine();
|
||||||
if (line.startsWith('#')) {
|
if (line.startsWith('#')) {
|
||||||
@@ -198,86 +130,185 @@ QModelIndex PlaylistManager::loadM3U8Playlist(const QUrl &url)
|
|||||||
}
|
}
|
||||||
QFileInfo fileInfo(file);
|
QFileInfo fileInfo(file);
|
||||||
QUrl item = QUrl::fromUserInput(line, fileInfo.absolutePath());
|
QUrl item = QUrl::fromUserInput(line, fileInfo.absolutePath());
|
||||||
|
if (!checkUrl(item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
urls.append(item);
|
urls.append(item);
|
||||||
}
|
}
|
||||||
return loadPlaylist(urls);
|
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPlaylist(std::move(urls));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaylistManager::setPlaylist(const QList<QUrl>&& urls, std::optional<qsizetype> indexHint) {
|
||||||
|
// Setup playlist
|
||||||
|
m_playlist = std::move(urls);
|
||||||
|
|
||||||
|
// Set index with hint
|
||||||
|
if (indexHint.has_value()) {
|
||||||
|
m_currentIndex = *indexHint;
|
||||||
|
}
|
||||||
|
sanitizeIndex();
|
||||||
|
|
||||||
|
// Raise signal
|
||||||
|
emit playlistChanged(totalCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaylistManager::clearPlaylist() {
|
||||||
|
m_playlist.clear();
|
||||||
|
sanitizeIndex();
|
||||||
|
emit playlistChanged(this->totalCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Playlist Operations
|
||||||
|
|
||||||
|
void PlaylistManager::appendToPlaylist(const QString &file)
|
||||||
|
{
|
||||||
|
return appendToPlaylist(urlFromString(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaylistManager::appendToPlaylist(const QUrl &url)
|
||||||
|
{
|
||||||
|
m_playlist.append(url);
|
||||||
|
sanitizeIndex();
|
||||||
|
emit playlistChanged(totalCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlaylistManager::insertIntoPlaylist(qsizetype index, const QString &file)
|
||||||
|
{
|
||||||
|
return insertIntoPlaylist(index, urlFromString(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlaylistManager::insertIntoPlaylist(qsizetype index, const QUrl &url)
|
||||||
|
{
|
||||||
|
if (index < 0 || index > totalCount()) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
m_playlist.insert(index, url);
|
||||||
|
sanitizeIndex();
|
||||||
|
emit playlistChanged(totalCount());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int PlaylistManager::totalCount() const
|
bool PlaylistManager::removeFromPlaylist(qsizetype index)
|
||||||
{
|
{
|
||||||
return m_model.rowCount();
|
if (index < 0 || index >= totalCount()) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
m_playlist.removeAt(index);
|
||||||
|
sanitizeIndex();
|
||||||
|
emit playlistChanged(totalCount());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::previousIndex() const
|
// endregion
|
||||||
{
|
|
||||||
int count = totalCount();
|
|
||||||
if (count == 0) return {};
|
|
||||||
|
|
||||||
return m_model.index(isFirstIndex() ? count - 1 : m_currentIndex - 1);
|
// region: Index Operations
|
||||||
|
|
||||||
|
qsizetype PlaylistManager::totalCount() const
|
||||||
|
{
|
||||||
|
return m_playlist.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::nextIndex() const
|
std::optional<qsizetype> PlaylistManager::previousIndex() const
|
||||||
{
|
{
|
||||||
int count = totalCount();
|
if (!m_currentIndex.has_value() || totalCount() <= 0) return std::nullopt;
|
||||||
if (count == 0) return {};
|
return (isFirstIndex() ? totalCount() - 1 : *m_currentIndex - 1);
|
||||||
|
|
||||||
return m_model.index(isLastIndex() ? 0 : m_currentIndex + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::curIndex() const
|
std::optional<qsizetype> PlaylistManager::nextIndex() const
|
||||||
{
|
{
|
||||||
return m_model.index(m_currentIndex);
|
if (!m_currentIndex.has_value() || totalCount() <= 0) return std::nullopt;
|
||||||
|
return (isLastIndex() ? 0 : *m_currentIndex + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PlaylistManager::isFirstIndex() const
|
bool PlaylistManager::isFirstIndex() const
|
||||||
{
|
{
|
||||||
return m_currentIndex == 0;
|
return m_currentIndex.has_value() && *m_currentIndex == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PlaylistManager::isLastIndex() const
|
bool PlaylistManager::isLastIndex() const
|
||||||
{
|
{
|
||||||
return m_currentIndex + 1 == totalCount();
|
return m_currentIndex.has_value() && *m_currentIndex + 1 == totalCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistManager::setCurrentIndex(const QModelIndex &index)
|
std::optional<qsizetype> PlaylistManager::currentIndex() const
|
||||||
{
|
{
|
||||||
if (index.isValid() && index.row() >= 0 && index.row() < totalCount()) {
|
return m_currentIndex;
|
||||||
setProperty("currentIndex", index.row());
|
}
|
||||||
|
|
||||||
|
bool PlaylistManager::setCurrentIndex(qsizetype index)
|
||||||
|
{
|
||||||
|
if (!m_currentIndex.has_value()) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
if (index < 0 || index >= totalCount()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (*m_currentIndex == index) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_currentIndex = index;
|
||||||
|
emit currentIndexChanged(index);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl PlaylistManager::urlByIndex(const QModelIndex &index)
|
QUrl PlaylistManager::urlByIndex(qsizetype index) const
|
||||||
{
|
{
|
||||||
return m_model.urlByIndex(index.row());
|
return m_playlist.value(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString PlaylistManager::localFileByIndex(const QModelIndex &index)
|
QString PlaylistManager::localFileByIndex(qsizetype index) const
|
||||||
{
|
{
|
||||||
return urlByIndex(index).toLocalFile();
|
return urlByIndex(index).toLocalFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PlaylistManager::removeAt(const QModelIndex &index)
|
// endregion
|
||||||
{
|
|
||||||
return m_model.removeAt(index.row());
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlaylistManager::setAutoLoadFilterSuffixes(const QStringList &nameFilters)
|
// region: Misc
|
||||||
{
|
|
||||||
m_model.setProperty("autoLoadFilterSuffixes", nameFilters);
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<QUrl> PlaylistManager::convertToUrlList(const QStringList &files)
|
void PlaylistManager::sanitizeIndex()
|
||||||
{
|
{
|
||||||
QList<QUrl> urlList;
|
auto cnt = totalCount();
|
||||||
for (const QString & str : std::as_const(files)) {
|
if (cnt == 0) {
|
||||||
QUrl url = QUrl::fromLocalFile(str);
|
// If playlist is empty, current index should be invalid.
|
||||||
if (url.isValid()) {
|
m_currentIndex = std::nullopt;
|
||||||
urlList.append(url);
|
} else {
|
||||||
|
if (m_currentIndex.has_value()) {
|
||||||
|
// Clamp current index in playlist range.
|
||||||
|
// If it is already in range, not change was done.
|
||||||
|
auto currentIndex = *m_currentIndex;
|
||||||
|
if (currentIndex < 0) {
|
||||||
|
m_currentIndex = 0;
|
||||||
|
} else if (currentIndex >= cnt) {
|
||||||
|
m_currentIndex = cnt - 1;
|
||||||
|
} else {
|
||||||
|
; // No change.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If there is no set value for current index,
|
||||||
|
// set it at the beginning of playlist.
|
||||||
|
m_currentIndex = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return urlList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PlaylistManager::checkUrl(const QUrl& url)
|
||||||
|
{
|
||||||
|
return url.isValid() && url.isLocalFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl PlaylistManager::urlFromString(const QString& file)
|
||||||
|
{
|
||||||
|
return QUrl::fromLocalFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|||||||
+107
-65
@@ -5,84 +5,126 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QAbstractListModel>
|
#include <QObject>
|
||||||
|
#include <optional>
|
||||||
class PlaylistModel : public QAbstractListModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
enum PlaylistRole {
|
|
||||||
UrlRole = Qt::UserRole
|
|
||||||
};
|
|
||||||
Q_ENUM(PlaylistRole)
|
|
||||||
Q_PROPERTY(QStringList autoLoadFilterSuffixes MEMBER m_autoLoadSuffixes NOTIFY autoLoadFilterSuffixesChanged)
|
|
||||||
|
|
||||||
explicit PlaylistModel(QObject *parent = nullptr);
|
|
||||||
~PlaylistModel() override;
|
|
||||||
|
|
||||||
void setPlaylist(const QList<QUrl> & urls);
|
|
||||||
QModelIndex loadPlaylist(const QList<QUrl> & urls);
|
|
||||||
QModelIndex loadPlaylist(const QUrl & url);
|
|
||||||
QModelIndex appendToPlaylist(const QUrl & url);
|
|
||||||
bool removeAt(int index);
|
|
||||||
int indexOf(const QUrl & url) const;
|
|
||||||
QUrl urlByIndex(int index) const;
|
|
||||||
QStringList autoLoadFilterSuffixes() const;
|
|
||||||
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void autoLoadFilterSuffixesChanged(QStringList suffixes);
|
|
||||||
|
|
||||||
private:
|
|
||||||
// model data
|
|
||||||
QList<QUrl> m_playlist;
|
|
||||||
// properties
|
|
||||||
QStringList m_autoLoadSuffixes = {};
|
|
||||||
// internal
|
|
||||||
QString m_currentDir;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
/// The manager of playlist.
|
||||||
class PlaylistManager : public QObject
|
class PlaylistManager : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
Q_PROPERTY(int currentIndex MEMBER m_currentIndex NOTIFY currentIndexChanged)
|
/// Constructs a PlaylistManager with an optional parent QObject.
|
||||||
Q_PROPERTY(QStringList autoLoadFilterSuffixes WRITE setAutoLoadFilterSuffixes)
|
explicit PlaylistManager(QObject* parent = nullptr);
|
||||||
Q_PROPERTY(PlaylistModel * model READ model CONSTANT)
|
/// Destructor.
|
||||||
|
|
||||||
explicit PlaylistManager(QObject *parent = nullptr);
|
|
||||||
~PlaylistManager();
|
~PlaylistManager();
|
||||||
|
|
||||||
PlaylistModel * model();
|
/// Get the allowed file suffixes of this playlist.
|
||||||
|
/// @return The current list of allowed suffixes.
|
||||||
|
const QStringList& allowedSuffixes() const;
|
||||||
|
/// Set the allowed file suffixes of this playlist.
|
||||||
|
/// @remark This only works for "load the whole directory by one file" case.
|
||||||
|
/// @param[in] nameFilters The new list of allowed suffixes.
|
||||||
|
void setAllowedSuffixes(const QStringList& nameFilters);
|
||||||
|
|
||||||
void setPlaylist(const QList<QUrl> & url);
|
/// Load user specified files as playlist.
|
||||||
Q_INVOKABLE QModelIndex loadPlaylist(const QList<QUrl> & urls);
|
/// @remark The wrapper for another overload.
|
||||||
Q_INVOKABLE QModelIndex loadPlaylist(const QUrl & url);
|
/// @return True for successful load, otherwise false.
|
||||||
Q_INVOKABLE QModelIndex loadM3U8Playlist(const QUrl & url);
|
bool loadPlaylist(const QStringList& files);
|
||||||
|
/// Load user specified URLs as playlist.
|
||||||
|
/// @remark This function automatically dispatch these files into correct sub-load functions.
|
||||||
|
/// @return True for successful load, otherwise false.
|
||||||
|
bool loadPlaylist(const QList<QUrl>& urls);
|
||||||
|
/// Clear the entire playlist and reset current index.
|
||||||
|
void clearPlaylist();
|
||||||
|
|
||||||
int totalCount() const;
|
/// Append a local file path to the playlist.
|
||||||
QModelIndex previousIndex() const;
|
void appendToPlaylist(const QString& file);
|
||||||
QModelIndex nextIndex() const;
|
/// Append a URL to the playlist.
|
||||||
QModelIndex curIndex() const;
|
void appendToPlaylist(const QUrl& url);
|
||||||
|
/// Insert a local file path at the given index.
|
||||||
|
/// @return True if inserted successfully, false if index is out of range.
|
||||||
|
bool insertIntoPlaylist(qsizetype index, const QString& file);
|
||||||
|
/// Insert a URL at the given index.
|
||||||
|
/// @return True if inserted successfully, false if index is out of range.
|
||||||
|
bool insertIntoPlaylist(qsizetype index, const QUrl& url);
|
||||||
|
/// Remove the entry at the given index.
|
||||||
|
/// @return True if removed successfully, false if index is out of range.
|
||||||
|
bool removeFromPlaylist(qsizetype index);
|
||||||
|
|
||||||
|
/// Returns the total number of items in the playlist.
|
||||||
|
/// @return Total count of entries in the playlist.
|
||||||
|
qsizetype totalCount() const;
|
||||||
|
/// Try fetching previous index.
|
||||||
|
/// @details This previous index is gallery-loop-aware.
|
||||||
|
/// @return std::nullopt if playlist is empty, otherwise the previous index considering gallery loop.
|
||||||
|
std::optional<qsizetype> previousIndex() const;
|
||||||
|
/// Try fetching next index.
|
||||||
|
/// @details This next index is gallery-loop-aware.
|
||||||
|
/// @return std::nullopt if playlist is empty, otherwise the next index considering gallery loop.
|
||||||
|
std::optional<qsizetype> nextIndex() const;
|
||||||
|
/// Check whether current index is the first index.
|
||||||
|
/// @return False if current index is not the first index or playlist is empty, otherwise true.
|
||||||
bool isFirstIndex() const;
|
bool isFirstIndex() const;
|
||||||
|
/// Check whether current index is the last index.
|
||||||
|
/// @return False if current index is not the last index or playlist is empty, otherwise true.
|
||||||
bool isLastIndex() const;
|
bool isLastIndex() const;
|
||||||
void setCurrentIndex(const QModelIndex & index);
|
/// Returns the current index.
|
||||||
QUrl urlByIndex(const QModelIndex & index);
|
/// @return std::nullopt if the playlist is empty, otherwise the current index.
|
||||||
QString localFileByIndex(const QModelIndex & index);
|
std::optional<qsizetype> currentIndex() const;
|
||||||
bool removeAt(const QModelIndex & index);
|
/// Set the current index.
|
||||||
|
/// @remark Index only can be set when playlist is not empty.
|
||||||
void setAutoLoadFilterSuffixes(const QStringList &nameFilters);
|
/// @return True if successful set, including "not changed" set, otherwise false.
|
||||||
|
/// This return value only indicates whether we can set index and given index is valid for setting.
|
||||||
static QList<QUrl> convertToUrlList(const QStringList & files);
|
/// If "no changed" set occurs, this function returns true, but doesn't raise current index changed signal.
|
||||||
|
bool setCurrentIndex(qsizetype index);
|
||||||
|
/// Returns the URL at the given index.
|
||||||
|
/// @return The URL if index is valid.
|
||||||
|
/// This class guarantee that this QUrl is pointing to a local file.
|
||||||
|
QUrl urlByIndex(qsizetype index) const;
|
||||||
|
/// Returns the local file path at the given index.
|
||||||
|
/// @return The local file path if index is valid.
|
||||||
|
/// This class guarantee that local file path always is valid.
|
||||||
|
QString localFileByIndex(qsizetype index) const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void currentIndexChanged(int index);
|
void allowedSuffixesChanged(const QStringList& suffixes);
|
||||||
void totalCountChanged(int count);
|
/// Raised when index was changed bu setter.
|
||||||
|
/// The argument holds the new current index.
|
||||||
|
/// Please note that this signal will NOT be raised by the whole playlist change caused index change.
|
||||||
|
void currentIndexChanged(qsizetype index);
|
||||||
|
/// The whole playlist was changed, involving loading, editing and deleting some or full entries.
|
||||||
|
/// The argument holds the total count of new play list.
|
||||||
|
void playlistChanged(qsizetype count);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_currentIndex = -1;
|
/// The fundamental playlist setter.
|
||||||
PlaylistModel m_model;
|
/// @details All playlist load functions will call this function to set playlist at last.
|
||||||
|
/// @param[in] urls ALl urls in this list should be checked by checkUrl() function.
|
||||||
|
/// @param[in] indexHint Try to set current index to this index if possible.
|
||||||
|
void setPlaylist(const QList<QUrl>&& urls, std::optional<qsizetype> indexHint = std::nullopt);
|
||||||
|
/// Load playlist from a single file.
|
||||||
|
/// @details This function scans the same directory for siblings matching allowed suffixes, sorted naturally.
|
||||||
|
/// @return True if the playlist was loaded successfully, false otherwise.
|
||||||
|
bool loadSingleFilePlaylist(const QUrl& url);
|
||||||
|
/// Load playlist from multiple URLs directly.
|
||||||
|
/// @return True if all URLs are valid, false otherwise.
|
||||||
|
bool loadMultipleFilesPlaylist(const QList<QUrl>&& urls);
|
||||||
|
/// Load playlist from an M3U/M3U8 playlist file.
|
||||||
|
/// @return True if loaded successfully, false otherwise.
|
||||||
|
bool loadM3U8Playlist(const QUrl& url);
|
||||||
|
|
||||||
|
/// Make sure that current index is valid.
|
||||||
|
/// @details This function usually is used by those functions manipulating playlist.
|
||||||
|
void sanitizeIndex();
|
||||||
|
|
||||||
|
/// Check whether given QUrl is proper to be put into playlist.
|
||||||
|
/// @details Only the url pointing to local file can be put into playlist.
|
||||||
|
static bool checkUrl(const QUrl& url);
|
||||||
|
/// Convert a string to local file into a QUrl.
|
||||||
|
/// @param[in] file The string pointing to local file.
|
||||||
|
static QUrl urlFromString(const QString& file);
|
||||||
|
|
||||||
|
std::optional<qsizetype> m_currentIndex;
|
||||||
|
QList<QUrl> m_playlist;
|
||||||
|
QStringList m_allowedSuffixes;
|
||||||
};
|
};
|
||||||
|
|||||||
+16
-97
@@ -13,6 +13,14 @@
|
|||||||
#include <QKeySequence>
|
#include <QKeySequence>
|
||||||
#include <QMetaEnum>
|
#include <QMetaEnum>
|
||||||
|
|
||||||
|
Settings *Settings::instance()
|
||||||
|
{
|
||||||
|
// SAFETY:
|
||||||
|
// This is a global singleton and it is leaked by design.
|
||||||
|
static Settings* INSTANCE = new Settings(nullptr);
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
namespace QEnumHelper
|
namespace QEnumHelper
|
||||||
{
|
{
|
||||||
template <typename E>
|
template <typename E>
|
||||||
@@ -34,27 +42,6 @@ namespace QEnumHelper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings *Settings::m_settings_instance = nullptr;
|
|
||||||
|
|
||||||
Settings *Settings::instance()
|
|
||||||
{
|
|
||||||
if (!m_settings_instance) {
|
|
||||||
m_settings_instance = new Settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_settings_instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Settings::stayOnTop() const
|
|
||||||
{
|
|
||||||
return m_qsettings->value("stay_on_top", true).toBool();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Settings::useBuiltInCloseAnimation() const
|
|
||||||
{
|
|
||||||
return m_qsettings->value("use_built_in_close_animation", true).toBool();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Settings::useLightCheckerboard() const
|
bool Settings::useLightCheckerboard() const
|
||||||
{
|
{
|
||||||
return m_qsettings->value("use_light_checkerboard", false).toBool();
|
return m_qsettings->value("use_light_checkerboard", false).toBool();
|
||||||
@@ -65,11 +52,6 @@ bool Settings::loopGallery() const
|
|||||||
return m_qsettings->value("loop_gallery", true).toBool();
|
return m_qsettings->value("loop_gallery", true).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::autoLongImageMode() const
|
|
||||||
{
|
|
||||||
return m_qsettings->value("auto_long_image_mode", true).toBool();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Settings::svgTiny12Only() const
|
bool Settings::svgTiny12Only() const
|
||||||
{
|
{
|
||||||
// Qt 6.7.0's SVG support is terrible caused by huge memory usage, see QTBUG-124287
|
// Qt 6.7.0's SVG support is terrible caused by huge memory usage, see QTBUG-124287
|
||||||
@@ -85,44 +67,28 @@ bool Settings::svgTiny12Only() const
|
|||||||
|
|
||||||
Settings::DoubleClickBehavior Settings::doubleClickBehavior() const
|
Settings::DoubleClickBehavior Settings::doubleClickBehavior() const
|
||||||
{
|
{
|
||||||
QString result = m_qsettings->value("double_click_behavior", "Close").toString();
|
QString result = m_qsettings->value("double_click_behavior", "").toString();
|
||||||
|
return QEnumHelper::fromString<DoubleClickBehavior>(result, DoubleClickBehavior::Ignore);
|
||||||
return QEnumHelper::fromString<DoubleClickBehavior>(result, DoubleClickBehavior::Close);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings::MouseWheelBehavior Settings::mouseWheelBehavior() const
|
Settings::MouseWheelBehavior Settings::mouseWheelBehavior() const
|
||||||
{
|
{
|
||||||
QString result = m_qsettings->value("mouse_wheel_behavior", "Zoom").toString();
|
QString result = m_qsettings->value("mouse_wheel_behavior", "").toString();
|
||||||
|
|
||||||
return QEnumHelper::fromString<MouseWheelBehavior>(result, MouseWheelBehavior::Zoom);
|
return QEnumHelper::fromString<MouseWheelBehavior>(result, MouseWheelBehavior::Zoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings::WindowSizeBehavior Settings::initWindowSizeBehavior() const
|
Settings::WindowSizeBehavior Settings::initWindowSizeBehavior() const
|
||||||
{
|
{
|
||||||
QString result = m_qsettings->value("init_window_size_behavior", "Auto").toString();
|
QString result = m_qsettings->value("init_window_size_behavior", "").toString();
|
||||||
|
return QEnumHelper::fromString<WindowSizeBehavior>(result, WindowSizeBehavior::Windowed);
|
||||||
return QEnumHelper::fromString<WindowSizeBehavior>(result, WindowSizeBehavior::Auto);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Qt::HighDpiScaleFactorRoundingPolicy Settings::hiDpiScaleFactorBehavior() const
|
Qt::HighDpiScaleFactorRoundingPolicy Settings::hiDpiScaleFactorBehavior() const
|
||||||
{
|
{
|
||||||
QString result = m_qsettings->value("hidpi_scale_factor_behavior", "PassThrough").toString();
|
QString result = m_qsettings->value("hidpi_scale_factor_behavior", "").toString();
|
||||||
|
|
||||||
return QEnumHelper::fromString<Qt::HighDpiScaleFactorRoundingPolicy>(result, Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
return QEnumHelper::fromString<Qt::HighDpiScaleFactorRoundingPolicy>(result, Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setStayOnTop(bool on)
|
|
||||||
{
|
|
||||||
m_qsettings->setValue("stay_on_top", on);
|
|
||||||
m_qsettings->sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Settings::setUseBuiltInCloseAnimation(bool on)
|
|
||||||
{
|
|
||||||
m_qsettings->setValue("use_built_in_close_animation", on);
|
|
||||||
m_qsettings->sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Settings::setUseLightCheckerboard(bool light)
|
void Settings::setUseLightCheckerboard(bool light)
|
||||||
{
|
{
|
||||||
m_qsettings->setValue("use_light_checkerboard", light);
|
m_qsettings->setValue("use_light_checkerboard", light);
|
||||||
@@ -135,12 +101,6 @@ void Settings::setLoopGallery(bool on)
|
|||||||
m_qsettings->sync();
|
m_qsettings->sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setAutoLongImageMode(bool on)
|
|
||||||
{
|
|
||||||
m_qsettings->setValue("auto_long_image_mode", on);
|
|
||||||
m_qsettings->sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Settings::setSvgTiny12Only(bool on)
|
void Settings::setSvgTiny12Only(bool on)
|
||||||
{
|
{
|
||||||
m_qsettings->setValue("svg_tiny12_only", on);
|
m_qsettings->setValue("svg_tiny12_only", on);
|
||||||
@@ -171,47 +131,6 @@ void Settings::setHiDpiScaleFactorBehavior(Qt::HighDpiScaleFactorRoundingPolicy
|
|||||||
m_qsettings->sync();
|
m_qsettings->sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::applyUserShortcuts(QWidget *widget)
|
|
||||||
{
|
|
||||||
m_qsettings->beginGroup("shortcuts");
|
|
||||||
const QStringList shortcutNames = m_qsettings->allKeys();
|
|
||||||
for (const QString & name : shortcutNames) {
|
|
||||||
QList<QKeySequence> shortcuts = m_qsettings->value(name).value<QList<QKeySequence>>();
|
|
||||||
setShortcutsForAction(widget, name, shortcuts, false);
|
|
||||||
}
|
|
||||||
m_qsettings->endGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Settings::setShortcutsForAction(QWidget *widget, const QString &objectName,
|
|
||||||
QList<QKeySequence> shortcuts, bool writeConfig)
|
|
||||||
{
|
|
||||||
QAction * targetAction = nullptr;
|
|
||||||
for (QAction * action : widget->actions()) {
|
|
||||||
if (action->objectName() == objectName) {
|
|
||||||
targetAction = action;
|
|
||||||
} else {
|
|
||||||
for (const QKeySequence & shortcut : std::as_const(shortcuts)) {
|
|
||||||
if (action->shortcuts().contains(shortcut)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetAction) {
|
|
||||||
targetAction->setShortcuts(shortcuts);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetAction && writeConfig) {
|
|
||||||
m_qsettings->beginGroup("shortcuts");
|
|
||||||
m_qsettings->setValue(objectName, QVariant::fromValue(shortcuts));
|
|
||||||
m_qsettings->endGroup();
|
|
||||||
m_qsettings->sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(FLAG_PORTABLE_MODE_SUPPORT) && defined(Q_OS_WIN)
|
#if defined(FLAG_PORTABLE_MODE_SUPPORT) && defined(Q_OS_WIN)
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
// QCoreApplication::applicationDirPath() parses the "applicationDirPath" from arg0, which...
|
// QCoreApplication::applicationDirPath() parses the "applicationDirPath" from arg0, which...
|
||||||
@@ -230,8 +149,8 @@ QString getApplicationDirPath()
|
|||||||
}
|
}
|
||||||
#endif // defined(FLAG_PORTABLE_MODE_SUPPORT) && defined(Q_OS_WIN)
|
#endif // defined(FLAG_PORTABLE_MODE_SUPPORT) && defined(Q_OS_WIN)
|
||||||
|
|
||||||
Settings::Settings()
|
Settings::Settings(QObject *parent)
|
||||||
: QObject(qApp)
|
: QObject(parent)
|
||||||
{
|
{
|
||||||
QString configPath;
|
QString configPath;
|
||||||
|
|
||||||
|
|||||||
+2
-13
@@ -25,6 +25,7 @@ public:
|
|||||||
};
|
};
|
||||||
Q_ENUM(MouseWheelBehavior)
|
Q_ENUM(MouseWheelBehavior)
|
||||||
|
|
||||||
|
// TODO: I want to remove Auto behavior in future.
|
||||||
enum WindowSizeBehavior {
|
enum WindowSizeBehavior {
|
||||||
Auto,
|
Auto,
|
||||||
Maximized,
|
Maximized,
|
||||||
@@ -34,36 +35,24 @@ public:
|
|||||||
|
|
||||||
static Settings *instance();
|
static Settings *instance();
|
||||||
|
|
||||||
bool stayOnTop() const;
|
|
||||||
bool useBuiltInCloseAnimation() const;
|
|
||||||
bool useLightCheckerboard() const;
|
bool useLightCheckerboard() const;
|
||||||
bool loopGallery() const;
|
bool loopGallery() const;
|
||||||
bool autoLongImageMode() const;
|
|
||||||
bool svgTiny12Only() const;
|
bool svgTiny12Only() const;
|
||||||
DoubleClickBehavior doubleClickBehavior() const;
|
DoubleClickBehavior doubleClickBehavior() const;
|
||||||
MouseWheelBehavior mouseWheelBehavior() const;
|
MouseWheelBehavior mouseWheelBehavior() const;
|
||||||
WindowSizeBehavior initWindowSizeBehavior() const;
|
WindowSizeBehavior initWindowSizeBehavior() const;
|
||||||
Qt::HighDpiScaleFactorRoundingPolicy hiDpiScaleFactorBehavior() const;
|
Qt::HighDpiScaleFactorRoundingPolicy hiDpiScaleFactorBehavior() const;
|
||||||
|
|
||||||
void setStayOnTop(bool on);
|
|
||||||
void setUseBuiltInCloseAnimation(bool on);
|
|
||||||
void setUseLightCheckerboard(bool light);
|
void setUseLightCheckerboard(bool light);
|
||||||
void setLoopGallery(bool on);
|
void setLoopGallery(bool on);
|
||||||
void setAutoLongImageMode(bool on);
|
|
||||||
void setSvgTiny12Only(bool on);
|
void setSvgTiny12Only(bool on);
|
||||||
void setDoubleClickBehavior(DoubleClickBehavior dcb);
|
void setDoubleClickBehavior(DoubleClickBehavior dcb);
|
||||||
void setMouseWheelBehavior(MouseWheelBehavior mwb);
|
void setMouseWheelBehavior(MouseWheelBehavior mwb);
|
||||||
void setInitWindowSizeBehavior(WindowSizeBehavior wsb);
|
void setInitWindowSizeBehavior(WindowSizeBehavior wsb);
|
||||||
void setHiDpiScaleFactorBehavior(Qt::HighDpiScaleFactorRoundingPolicy hidpi);
|
void setHiDpiScaleFactorBehavior(Qt::HighDpiScaleFactorRoundingPolicy hidpi);
|
||||||
|
|
||||||
void applyUserShortcuts(QWidget * widget);
|
|
||||||
bool setShortcutsForAction(QWidget * widget, const QString & objectName,
|
|
||||||
QList<QKeySequence> shortcuts, bool writeConfig = true);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Settings();
|
Settings(QObject *parent = nullptr);
|
||||||
|
|
||||||
static Settings *m_settings_instance;
|
|
||||||
|
|
||||||
QSettings *m_qsettings;
|
QSettings *m_qsettings;
|
||||||
|
|
||||||
|
|||||||
+113
-170
@@ -3,158 +3,107 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "settingsdialog.h"
|
#include "settingsdialog.h"
|
||||||
|
#include "ui_settingsdialog.h"
|
||||||
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "shortcutedit.h"
|
|
||||||
|
|
||||||
#include <QAction>
|
#include <QApplication>
|
||||||
#include <QCheckBox>
|
#include <QTimer>
|
||||||
#include <QComboBox>
|
|
||||||
#include <QFormLayout>
|
|
||||||
#include <QKeySequenceEdit>
|
|
||||||
#include <QScrollArea>
|
|
||||||
#include <QSplitter>
|
|
||||||
#include <QStringListModel>
|
#include <QStringListModel>
|
||||||
#include <QMessageBox>
|
#include <type_traits>
|
||||||
|
|
||||||
|
template <typename E, typename = std::enable_if_t<std::is_enum_v<E>>>
|
||||||
|
class EnumComboBoxTransformer {
|
||||||
|
public:
|
||||||
|
using Pair = QPair<E, QString>;
|
||||||
|
using Pairs = QList<QPair<E, QString>>;
|
||||||
|
|
||||||
|
EnumComboBoxTransformer(Pairs &&pairs) : pairs(std::move(pairs)) {}
|
||||||
|
~EnumComboBoxTransformer() = default;
|
||||||
|
Q_DISABLE_COPY_MOVE(EnumComboBoxTransformer)
|
||||||
|
|
||||||
|
/// Build Qt model used for QComboBox
|
||||||
|
QStringListModel* buildComboBoxModel(QObject *parent = nullptr) const {
|
||||||
|
QStringList dropDownEntries;
|
||||||
|
for (const auto& pair : std::as_const(this->pairs)) {
|
||||||
|
dropDownEntries.append(pair.second);
|
||||||
|
}
|
||||||
|
return new QStringListModel(dropDownEntries, parent);
|
||||||
|
}
|
||||||
|
/// Convert QComboBox index to enum value.
|
||||||
|
E to_value(int index) const {
|
||||||
|
return this->pairs.at(static_cast<qsizetype>(index)).first;
|
||||||
|
}
|
||||||
|
/// Convert enum value to QComboBox index.
|
||||||
|
int to_index(E value) const {
|
||||||
|
for (qsizetype i = 0; i < pairs.size(); ++i) {
|
||||||
|
if (pairs.at(i).first == value) {
|
||||||
|
return static_cast<int>(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Value not found
|
||||||
|
throw std::invalid_argument("invalid enum value for finding index");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Pairs pairs;
|
||||||
|
};
|
||||||
|
|
||||||
|
static EnumComboBoxTransformer<Settings::DoubleClickBehavior> DC_OPTIONS {
|
||||||
|
{
|
||||||
|
{Settings::DoubleClickBehavior::Ignore, QCoreApplication::translate("SettingsDialog", "Do nothing")},
|
||||||
|
{Settings::DoubleClickBehavior::Close, QCoreApplication::translate("SettingsDialog", "Close the window")},
|
||||||
|
{Settings::DoubleClickBehavior::Maximize, QCoreApplication::translate("SettingsDialog", "Toggle maximize")},
|
||||||
|
{Settings::DoubleClickBehavior::FullScreen, QCoreApplication::translate("SettingsDialog", "Toggle fullscreen")}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static EnumComboBoxTransformer<Settings::MouseWheelBehavior, QString> MW_OPTIONS {
|
||||||
|
{
|
||||||
|
{ Settings::MouseWheelBehavior::Zoom, QCoreApplication::translate("SettingsDialog", "Zoom in and out") },
|
||||||
|
{ Settings::MouseWheelBehavior::Switch, QCoreApplication::translate("SettingsDialog", "View next or previous item") }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static EnumComboBoxTransformer<Settings::WindowSizeBehavior, QString> IWS_OPTIONS {
|
||||||
|
{
|
||||||
|
{ Settings::WindowSizeBehavior::Auto, QCoreApplication::translate("SettingsDialog", "Auto size") },
|
||||||
|
{ Settings::WindowSizeBehavior::Maximized, QCoreApplication::translate("SettingsDialog", "Maximized") },
|
||||||
|
{ Settings::WindowSizeBehavior::Windowed, QCoreApplication::translate("SettingsDialog", "Windowed") }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static EnumComboBoxTransformer<Qt::HighDpiScaleFactorRoundingPolicy, QString> HIDPI_OPTIONS {
|
||||||
|
{
|
||||||
|
{ Qt::HighDpiScaleFactorRoundingPolicy::Round, QCoreApplication::translate("SettingsDialog", "Round (Integer scaling)", "This option means round up for .5 and above") },
|
||||||
|
{ Qt::HighDpiScaleFactorRoundingPolicy::Ceil, QCoreApplication::translate("SettingsDialog", "Ceil (Integer scaling)", "This option means always round up") },
|
||||||
|
{ Qt::HighDpiScaleFactorRoundingPolicy::Floor, QCoreApplication::translate("SettingsDialog", "Floor (Integer scaling)", "This option means always round down") },
|
||||||
|
{ Qt::HighDpiScaleFactorRoundingPolicy::PassThrough, QCoreApplication::translate("SettingsDialog", "Follow system (Fractional scaling)", "This option means don't round") }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
SettingsDialog::SettingsDialog(QWidget *parent)
|
SettingsDialog::SettingsDialog(QWidget *parent)
|
||||||
: QDialog(parent)
|
: QDialog(parent), ui(new QT_PREPEND_NAMESPACE(Ui::SettingsDialog))
|
||||||
, m_stayOnTop(new QCheckBox)
|
|
||||||
, m_useBuiltInCloseAnimation(new QCheckBox)
|
|
||||||
, m_useLightCheckerboard(new QCheckBox)
|
|
||||||
, m_loopGallery(new QCheckBox)
|
|
||||||
, m_autoLongImageMode(new QCheckBox)
|
|
||||||
, m_svgTiny12Only(new QCheckBox)
|
|
||||||
, m_doubleClickBehavior(new QComboBox)
|
|
||||||
, m_mouseWheelBehavior(new QComboBox)
|
|
||||||
, m_initWindowSizeBehavior(new QComboBox)
|
|
||||||
, m_hiDpiRoundingPolicyBehavior(new QComboBox)
|
|
||||||
{
|
{
|
||||||
this->setWindowTitle(tr("Settings"));
|
ui->setupUi(this);
|
||||||
|
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
|
||||||
|
|
||||||
QHBoxLayout * mainLayout = new QHBoxLayout(this);
|
auto* settings = Settings::instance();
|
||||||
QTabWidget * settingsTabs = new QTabWidget(this);
|
ui->m_useLightCheckerboard->setChecked(settings->useLightCheckerboard());
|
||||||
mainLayout->addWidget(settingsTabs);
|
ui->m_loopGallery->setChecked(settings->loopGallery());
|
||||||
|
ui->m_svgTiny12Only->setChecked(settings->svgTiny12Only());
|
||||||
QWidget * settingsFormHolder = new QWidget;
|
ui->m_doubleClickBehavior->setModel(DC_OPTIONS.buildComboBoxModel(this));
|
||||||
QFormLayout * settingsForm = new QFormLayout(settingsFormHolder);
|
Settings::DoubleClickBehavior dcb = settings->doubleClickBehavior();
|
||||||
settingsTabs->addTab(settingsFormHolder, tr("Options"));
|
ui->m_doubleClickBehavior->setCurrentIndex(DC_OPTIONS.to_index(dcb));
|
||||||
|
ui->m_mouseWheelBehavior->setModel(MW_OPTIONS.buildComboBoxModel(this));
|
||||||
QSplitter * shortcutEditorSplitter = new QSplitter;
|
Settings::MouseWheelBehavior mwb = settings->mouseWheelBehavior();
|
||||||
shortcutEditorSplitter->setOrientation(Qt::Vertical);
|
ui->m_mouseWheelBehavior->setCurrentIndex(MW_OPTIONS.to_index(mwb));
|
||||||
shortcutEditorSplitter->setChildrenCollapsible(false);
|
ui->m_initWindowSizeBehavior->setModel(IWS_OPTIONS.buildComboBoxModel(this));
|
||||||
QScrollArea * shortcutScrollArea = new QScrollArea;
|
Settings::WindowSizeBehavior iwsb = settings->initWindowSizeBehavior();
|
||||||
shortcutEditorSplitter->addWidget(shortcutScrollArea);
|
ui->m_initWindowSizeBehavior->setCurrentIndex(IWS_OPTIONS.to_index(iwsb));
|
||||||
shortcutScrollArea->setWidgetResizable(true);
|
ui->m_hiDpiRoundingPolicyBehavior->setModel(HIDPI_OPTIONS.buildComboBoxModel(this));
|
||||||
shortcutScrollArea->setMinimumHeight(200);
|
Qt::HighDpiScaleFactorRoundingPolicy hidpi = settings->hiDpiScaleFactorBehavior();
|
||||||
QWidget * shortcutsFormHolder = new QWidget;
|
ui->m_hiDpiRoundingPolicyBehavior->setCurrentIndex(HIDPI_OPTIONS.to_index(hidpi));
|
||||||
QFormLayout * shortcutsForm = new QFormLayout(shortcutsFormHolder);
|
|
||||||
shortcutScrollArea->setWidget(shortcutsFormHolder);
|
|
||||||
settingsTabs->addTab(shortcutEditorSplitter, tr("Shortcuts"));
|
|
||||||
|
|
||||||
for (const QAction * action : parent->actions()) {
|
|
||||||
ShortcutEdit * shortcutEdit = new ShortcutEdit;
|
|
||||||
shortcutEdit->setObjectName(QLatin1String("shortcut_") + action->objectName());
|
|
||||||
shortcutEdit->setShortcuts(action->shortcuts());
|
|
||||||
shortcutsForm->addRow(action->text(), shortcutEdit);
|
|
||||||
connect(shortcutEdit, &ShortcutEdit::editButtonClicked, this, [=](){
|
|
||||||
if (shortcutEditorSplitter->count() == 1) shortcutEditorSplitter->addWidget(new QWidget);
|
|
||||||
ShortcutEditor * shortcutEditor = new ShortcutEditor(shortcutEdit);
|
|
||||||
shortcutEditor->setDescription(tr("Editing shortcuts for action \"%1\":").arg(action->text()));
|
|
||||||
QWidget * oldEditor = shortcutEditorSplitter->replaceWidget(1, shortcutEditor);
|
|
||||||
shortcutEditorSplitter->setSizes({shortcutEditorSplitter->height(), 1});
|
|
||||||
oldEditor->deleteLater();
|
|
||||||
});
|
|
||||||
connect(shortcutEdit, &ShortcutEdit::applyShortcutsRequested, this, [=](QList<QKeySequence> newShortcuts){
|
|
||||||
bool succ = Settings::instance()->setShortcutsForAction(parent, shortcutEdit->objectName().mid(9),
|
|
||||||
newShortcuts);
|
|
||||||
if (!succ) {
|
|
||||||
QMessageBox::warning(this, tr("Failed to set shortcuts"),
|
|
||||||
tr("Please check if shortcuts are duplicated with existing shortcuts."));
|
|
||||||
}
|
|
||||||
shortcutEdit->setShortcuts(action->shortcuts());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static QList< QPair<Settings::DoubleClickBehavior, QString> > _dc_options {
|
|
||||||
{ Settings::DoubleClickBehavior::Ignore, tr("Do nothing") },
|
|
||||||
{ Settings::DoubleClickBehavior::Close, tr("Close the window") },
|
|
||||||
{ Settings::DoubleClickBehavior::Maximize, tr("Toggle maximize") },
|
|
||||||
{ Settings::DoubleClickBehavior::FullScreen, tr("Toggle fullscreen") }
|
|
||||||
};
|
|
||||||
|
|
||||||
static QList< QPair<Settings::MouseWheelBehavior, QString> > _mw_options {
|
|
||||||
{ Settings::MouseWheelBehavior::Zoom, tr("Zoom in and out") },
|
|
||||||
{ Settings::MouseWheelBehavior::Switch, tr("View next or previous item") }
|
|
||||||
};
|
|
||||||
|
|
||||||
static QList< QPair<Settings::WindowSizeBehavior, QString> > _iws_options {
|
|
||||||
{ Settings::WindowSizeBehavior::Auto, tr("Auto size") },
|
|
||||||
{ Settings::WindowSizeBehavior::Maximized, tr("Maximized") },
|
|
||||||
{ Settings::WindowSizeBehavior::Windowed, tr("Windowed") }
|
|
||||||
};
|
|
||||||
|
|
||||||
static QList< QPair<Qt::HighDpiScaleFactorRoundingPolicy, QString> > _hidpi_options {
|
|
||||||
{ Qt::HighDpiScaleFactorRoundingPolicy::Round, tr("Round (Integer scaling)", "This option means round up for .5 and above") },
|
|
||||||
{ Qt::HighDpiScaleFactorRoundingPolicy::Ceil, tr("Ceil (Integer scaling)", "This option means always round up") },
|
|
||||||
{ Qt::HighDpiScaleFactorRoundingPolicy::Floor, tr("Floor (Integer scaling)", "This option means always round down") },
|
|
||||||
{ Qt::HighDpiScaleFactorRoundingPolicy::PassThrough, tr("Follow system (Fractional scaling)", "This option means don't round") }
|
|
||||||
};
|
|
||||||
|
|
||||||
QStringList dcbDropDown;
|
|
||||||
for (const QPair<Settings::DoubleClickBehavior, QString> & dcOption : _dc_options) {
|
|
||||||
dcbDropDown.append(dcOption.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList mwbDropDown;
|
|
||||||
for (const QPair<Settings::MouseWheelBehavior, QString> & mwOption : _mw_options) {
|
|
||||||
mwbDropDown.append(mwOption.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList iwsbDropDown;
|
|
||||||
for (const QPair<Settings::WindowSizeBehavior, QString> & iwsOption : _iws_options) {
|
|
||||||
iwsbDropDown.append(iwsOption.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList hidpiDropDown;
|
|
||||||
for (const QPair<Qt::HighDpiScaleFactorRoundingPolicy, QString> & hidpiOption : _hidpi_options) {
|
|
||||||
hidpiDropDown.append(hidpiOption.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
settingsForm->addRow(tr("Stay on top when start-up"), m_stayOnTop);
|
|
||||||
settingsForm->addRow(tr("Use built-in close window animation"), m_useBuiltInCloseAnimation);
|
|
||||||
settingsForm->addRow(tr("Use light-color checkerboard"), m_useLightCheckerboard);
|
|
||||||
settingsForm->addRow(tr("Loop the loaded gallery"), m_loopGallery);
|
|
||||||
settingsForm->addRow(tr("Auto long image mode"), m_autoLongImageMode);
|
|
||||||
settingsForm->addRow(tr("Limit SVG support to SVG Tiny 1.2"), m_svgTiny12Only);
|
|
||||||
settingsForm->addRow(tr("Double-click behavior"), m_doubleClickBehavior);
|
|
||||||
settingsForm->addRow(tr("Mouse wheel behavior"), m_mouseWheelBehavior);
|
|
||||||
settingsForm->addRow(tr("Default window size"), m_initWindowSizeBehavior);
|
|
||||||
settingsForm->addRow(tr("HiDPI scale factor rounding policy"), m_hiDpiRoundingPolicyBehavior);
|
|
||||||
|
|
||||||
m_stayOnTop->setChecked(Settings::instance()->stayOnTop());
|
|
||||||
m_useBuiltInCloseAnimation->setChecked(Settings::instance()->useBuiltInCloseAnimation());
|
|
||||||
m_useLightCheckerboard->setChecked(Settings::instance()->useLightCheckerboard());
|
|
||||||
m_loopGallery->setChecked(Settings::instance()->loopGallery());
|
|
||||||
m_autoLongImageMode->setChecked(Settings::instance()->autoLongImageMode());
|
|
||||||
m_svgTiny12Only->setChecked(Settings::instance()->svgTiny12Only());
|
|
||||||
m_doubleClickBehavior->setModel(new QStringListModel(dcbDropDown));
|
|
||||||
Settings::DoubleClickBehavior dcb = Settings::instance()->doubleClickBehavior();
|
|
||||||
m_doubleClickBehavior->setCurrentIndex(static_cast<int>(dcb));
|
|
||||||
m_mouseWheelBehavior->setModel(new QStringListModel(mwbDropDown));
|
|
||||||
Settings::MouseWheelBehavior mwb = Settings::instance()->mouseWheelBehavior();
|
|
||||||
m_mouseWheelBehavior->setCurrentIndex(static_cast<int>(mwb));
|
|
||||||
m_initWindowSizeBehavior->setModel(new QStringListModel(iwsbDropDown));
|
|
||||||
Settings::WindowSizeBehavior iwsb = Settings::instance()->initWindowSizeBehavior();
|
|
||||||
m_initWindowSizeBehavior->setCurrentIndex(static_cast<int>(iwsb));
|
|
||||||
m_hiDpiRoundingPolicyBehavior->setModel(new QStringListModel(hidpiDropDown));
|
|
||||||
Qt::HighDpiScaleFactorRoundingPolicy hidpi = Settings::instance()->hiDpiScaleFactorBehavior();
|
|
||||||
for (int i = 0; i < _hidpi_options.count(); i++) {
|
|
||||||
if (_hidpi_options.at(i).first == hidpi) {
|
|
||||||
m_hiDpiRoundingPolicyBehavior->setCurrentIndex(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||||
# define QCHECKBOX_CHECKSTATECHANGED QCheckBox::checkStateChanged
|
# define QCHECKBOX_CHECKSTATECHANGED QCheckBox::checkStateChanged
|
||||||
@@ -164,51 +113,45 @@ SettingsDialog::SettingsDialog(QWidget *parent)
|
|||||||
# define QT_CHECKSTATE int
|
# define QT_CHECKSTATE int
|
||||||
#endif // QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
#endif // QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||||
|
|
||||||
connect(m_stayOnTop, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
connect(ui->m_useLightCheckerboard, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
||||||
Settings::instance()->setStayOnTop(state == Qt::Checked);
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(m_useBuiltInCloseAnimation, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
|
||||||
Settings::instance()->setUseBuiltInCloseAnimation(state == Qt::Checked);
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(m_useLightCheckerboard, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
|
||||||
Settings::instance()->setUseLightCheckerboard(state == Qt::Checked);
|
Settings::instance()->setUseLightCheckerboard(state == Qt::Checked);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_loopGallery, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
connect(ui->m_loopGallery, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
||||||
Settings::instance()->setLoopGallery(state == Qt::Checked);
|
Settings::instance()->setLoopGallery(state == Qt::Checked);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_autoLongImageMode, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
connect(ui->m_svgTiny12Only, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
||||||
Settings::instance()->setAutoLongImageMode(state == Qt::Checked);
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(m_svgTiny12Only, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
|
||||||
Settings::instance()->setSvgTiny12Only(state == Qt::Checked);
|
Settings::instance()->setSvgTiny12Only(state == Qt::Checked);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_doubleClickBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
|
connect(ui->m_doubleClickBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
|
||||||
Settings::instance()->setDoubleClickBehavior(_dc_options.at(index).first);
|
Settings::instance()->setDoubleClickBehavior(DC_OPTIONS.to_value(index));
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_mouseWheelBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
|
connect(ui->m_mouseWheelBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
|
||||||
Settings::instance()->setMouseWheelBehavior(_mw_options.at(index).first);
|
Settings::instance()->setMouseWheelBehavior(MW_OPTIONS.to_value(index));
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_initWindowSizeBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
|
connect(ui->m_initWindowSizeBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
|
||||||
Settings::instance()->setInitWindowSizeBehavior(_iws_options.at(index).first);
|
Settings::instance()->setInitWindowSizeBehavior(IWS_OPTIONS.to_value(index));
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_hiDpiRoundingPolicyBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
|
connect(ui->m_hiDpiRoundingPolicyBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
|
||||||
Settings::instance()->setHiDpiScaleFactorBehavior(_hidpi_options.at(index).first);
|
Settings::instance()->setHiDpiScaleFactorBehavior(HIDPI_OPTIONS.to_value(index));
|
||||||
});
|
});
|
||||||
|
|
||||||
adjustSize();
|
// YYC MARK:
|
||||||
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
|
// Use adjustSize() to make sure all ComboBox are expanded by its items.
|
||||||
|
// Because items are added after inserting them into layout.
|
||||||
|
// It is inviable to call this function in ctor because this relys on Qt event system.
|
||||||
|
// It only can be done by QTimer::singleShot().
|
||||||
|
QTimer::singleShot(0, this, [this](){
|
||||||
|
this->adjustSize();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsDialog::~SettingsDialog()
|
SettingsDialog::~SettingsDialog()
|
||||||
{
|
{
|
||||||
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-17
@@ -5,11 +5,14 @@
|
|||||||
#ifndef SETTINGSDIALOG_H
|
#ifndef SETTINGSDIALOG_H
|
||||||
#define SETTINGSDIALOG_H
|
#define SETTINGSDIALOG_H
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
|
||||||
class QCheckBox;
|
QT_BEGIN_NAMESPACE
|
||||||
class QComboBox;
|
namespace Ui {
|
||||||
|
class SettingsDialog;
|
||||||
|
}
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
class SettingsDialog : public QDialog
|
class SettingsDialog : public QDialog
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -17,21 +20,8 @@ public:
|
|||||||
explicit SettingsDialog(QWidget *parent = nullptr);
|
explicit SettingsDialog(QWidget *parent = nullptr);
|
||||||
~SettingsDialog();
|
~SettingsDialog();
|
||||||
|
|
||||||
signals:
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QCheckBox * m_stayOnTop = nullptr;
|
QT_PREPEND_NAMESPACE(Ui::SettingsDialog) *ui;
|
||||||
QCheckBox * m_useBuiltInCloseAnimation = nullptr;
|
|
||||||
QCheckBox * m_useLightCheckerboard = nullptr;
|
|
||||||
QCheckBox * m_loopGallery = nullptr;
|
|
||||||
QCheckBox * m_autoLongImageMode = nullptr;
|
|
||||||
QCheckBox * m_svgTiny12Only = nullptr;
|
|
||||||
QComboBox * m_doubleClickBehavior = nullptr;
|
|
||||||
QComboBox * m_mouseWheelBehavior = nullptr;
|
|
||||||
QComboBox * m_initWindowSizeBehavior = nullptr;
|
|
||||||
QComboBox * m_hiDpiRoundingPolicyBehavior = nullptr;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SETTINGSDIALOG_H
|
#endif // SETTINGSDIALOG_H
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>SettingsDialog</class>
|
||||||
|
<widget class="QDialog" name="SettingsDialog">
|
||||||
|
<property name="windowModality">
|
||||||
|
<enum>Qt::NonModal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Settings</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="mainLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QTabWidget" name="settingsTabs">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="optionsTab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Options</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QFormLayout" name="settingsForm">
|
||||||
|
<item row="0" column="0" colspan="2">
|
||||||
|
<widget class="QCheckBox" name="m_useLightCheckerboard">
|
||||||
|
<property name="text">
|
||||||
|
<string>Use light-color checkerboard</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0" colspan="2">
|
||||||
|
<widget class="QCheckBox" name="m_loopGallery">
|
||||||
|
<property name="text">
|
||||||
|
<string>Loop the loaded gallery</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0" colspan="2">
|
||||||
|
<widget class="QCheckBox" name="m_svgTiny12Only">
|
||||||
|
<property name="text">
|
||||||
|
<string>Limit SVG support to SVG Tiny 1.2</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="m_doubleClickBehaviorLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Double-click behavior</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QComboBox" name="m_doubleClickBehavior"/>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="m_mouseWheelBehaviorLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Mouse wheel behavior</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="QComboBox" name="m_mouseWheelBehavior"/>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="m_initWindowSizeBehaviorLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Default window size</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="QComboBox" name="m_initWindowSizeBehavior"/>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
|
<widget class="QLabel" name="m_hiDpiRoundingPolicyBehaviorLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>HiDPI scale factor rounding policy</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1">
|
||||||
|
<widget class="QComboBox" name="m_hiDpiRoundingPolicyBehavior"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 Gary Wang <opensource@blumia.net>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#include "shortcutedit.h"
|
|
||||||
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QToolButton>
|
|
||||||
#include <QHBoxLayout>
|
|
||||||
#include <QFormLayout>
|
|
||||||
#include <QDialogButtonBox>
|
|
||||||
#include <QKeySequenceEdit>
|
|
||||||
|
|
||||||
ShortcutEditor::ShortcutEditor(ShortcutEdit * shortcutEdit, QWidget * parent)
|
|
||||||
: QWidget(parent)
|
|
||||||
, m_descriptionLabel(new QLabel)
|
|
||||||
, m_shortcutEdit(shortcutEdit)
|
|
||||||
, m_shortcutLayout(new QFormLayout)
|
|
||||||
{
|
|
||||||
Q_CHECK_PTR(m_shortcutEdit);
|
|
||||||
|
|
||||||
QDialogButtonBox * buttons = new QDialogButtonBox(QDialogButtonBox::Apply | QDialogButtonBox::Discard);
|
|
||||||
|
|
||||||
QVBoxLayout * layout = new QVBoxLayout(this);
|
|
||||||
layout->addWidget(m_descriptionLabel);
|
|
||||||
layout->addLayout(m_shortcutLayout);
|
|
||||||
layout->addWidget(buttons);
|
|
||||||
|
|
||||||
connect(buttons, &QDialogButtonBox::clicked, this, [=](QAbstractButton *button){
|
|
||||||
if ((QPushButton *)button == buttons->button(QDialogButtonBox::Apply)) {
|
|
||||||
applyShortcuts();
|
|
||||||
} else {
|
|
||||||
reloadShortcuts();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
connect(shortcutEdit, &ShortcutEdit::shortcutsChanged, this, &ShortcutEditor::reloadShortcuts);
|
|
||||||
|
|
||||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
|
|
||||||
|
|
||||||
reloadShortcuts();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShortcutEditor::setDescription(const QString &desc)
|
|
||||||
{
|
|
||||||
m_descriptionLabel->setText(desc);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShortcutEditor::reloadShortcuts()
|
|
||||||
{
|
|
||||||
if (!m_keySequenceEdits.isEmpty()) {
|
|
||||||
for (QKeySequenceEdit * keyseqEdit : m_keySequenceEdits) {
|
|
||||||
m_shortcutLayout->removeRow(keyseqEdit);
|
|
||||||
}
|
|
||||||
m_keySequenceEdits.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<QKeySequence> shortcuts = m_shortcutEdit->shortcuts();
|
|
||||||
shortcuts.append(QKeySequence());
|
|
||||||
for (const QKeySequence & shortcut : shortcuts) {
|
|
||||||
QKeySequenceEdit * keyseqEdit = new QKeySequenceEdit(this);
|
|
||||||
keyseqEdit->setClearButtonEnabled(true);
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
|
||||||
keyseqEdit->setMaximumSequenceLength(1);
|
|
||||||
#endif // QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
|
||||||
keyseqEdit->setKeySequence(shortcut);
|
|
||||||
m_keySequenceEdits.append(keyseqEdit);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < m_keySequenceEdits.count(); i++) {
|
|
||||||
m_shortcutLayout->addRow(tr("Shortcut #%1").arg(i + 1), m_keySequenceEdits.at(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShortcutEditor::applyShortcuts()
|
|
||||||
{
|
|
||||||
QList<QKeySequence> shortcuts;
|
|
||||||
for (const QKeySequenceEdit * keyseqEdit : m_keySequenceEdits) {
|
|
||||||
if (!keyseqEdit->keySequence().isEmpty() && !shortcuts.contains(keyseqEdit->keySequence())) {
|
|
||||||
shortcuts.append(keyseqEdit->keySequence());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emit m_shortcutEdit->applyShortcutsRequested(shortcuts);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
ShortcutEdit::ShortcutEdit(QWidget *parent)
|
|
||||||
: QWidget(parent)
|
|
||||||
, m_shortcutsLabel(new QLabel(this))
|
|
||||||
, m_setShortcutButton(new QToolButton(this))
|
|
||||||
{
|
|
||||||
m_setShortcutButton->setText("...");
|
|
||||||
|
|
||||||
QHBoxLayout * layout = new QHBoxLayout(this);
|
|
||||||
layout->setContentsMargins(0, 0, 0, 0);
|
|
||||||
layout->addWidget(m_shortcutsLabel, 1);
|
|
||||||
layout->addWidget(m_setShortcutButton);
|
|
||||||
|
|
||||||
connect(this, &ShortcutEdit::shortcutsChanged, this, [=](){
|
|
||||||
QStringList shortcutTexts;
|
|
||||||
for (const QKeySequence & shortcut : std::as_const(m_shortcuts)) {
|
|
||||||
shortcutTexts.append(shortcut.toString(QKeySequence::NativeText));
|
|
||||||
}
|
|
||||||
m_shortcutsLabel->setText(shortcutTexts.isEmpty() ? tr("No shortcuts") : shortcutTexts.join(", "));
|
|
||||||
m_shortcutsLabel->setDisabled(shortcutTexts.isEmpty());
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(m_setShortcutButton, &QToolButton::clicked, this, &ShortcutEdit::editButtonClicked);
|
|
||||||
|
|
||||||
adjustSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
ShortcutEdit::~ShortcutEdit()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<QKeySequence> ShortcutEdit::shortcuts() const
|
|
||||||
{
|
|
||||||
return m_shortcuts;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShortcutEdit::setShortcuts(const QList<QKeySequence> &shortcuts)
|
|
||||||
{
|
|
||||||
m_shortcuts = shortcuts;
|
|
||||||
emit shortcutsChanged();
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 Gary Wang <opensource@blumia.net>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QList>
|
|
||||||
#include <QKeySequence>
|
|
||||||
|
|
||||||
class QLabel;
|
|
||||||
class QFormLayout;
|
|
||||||
class QToolButton;
|
|
||||||
class QKeySequenceEdit;
|
|
||||||
class ShortcutEdit;
|
|
||||||
class ShortcutEditor : public QWidget
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit ShortcutEditor(ShortcutEdit * shortcutEdit, QWidget * parent = nullptr);
|
|
||||||
~ShortcutEditor() = default;
|
|
||||||
|
|
||||||
void setDescription(const QString & desc);
|
|
||||||
|
|
||||||
void reloadShortcuts();
|
|
||||||
void applyShortcuts();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QLabel * m_descriptionLabel;
|
|
||||||
ShortcutEdit * m_shortcutEdit;
|
|
||||||
QFormLayout * m_shortcutLayout;
|
|
||||||
QList<QKeySequenceEdit *> m_keySequenceEdits;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ShortcutEdit : public QWidget
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
Q_PROPERTY(QList<QKeySequence> shortcuts MEMBER m_shortcuts WRITE setShortcuts NOTIFY shortcutsChanged)
|
|
||||||
public:
|
|
||||||
explicit ShortcutEdit(QWidget * parent = nullptr);
|
|
||||||
~ShortcutEdit();
|
|
||||||
|
|
||||||
QList<QKeySequence> shortcuts() const;
|
|
||||||
void setShortcuts(const QList<QKeySequence> &shortcuts);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void shortcutsChanged();
|
|
||||||
void editButtonClicked();
|
|
||||||
void applyShortcutsRequested(QList<QKeySequence> newShortcuts);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QList<QKeySequence> m_shortcuts;
|
|
||||||
QLabel * m_shortcutsLabel;
|
|
||||||
QToolButton * m_setShortcutButton;
|
|
||||||
};
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#include "toolbutton.h"
|
|
||||||
|
|
||||||
#include "actionmanager.h"
|
|
||||||
#include "opacityhelper.h"
|
|
||||||
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QGraphicsOpacityEffect>
|
|
||||||
#include <QPropertyAnimation>
|
|
||||||
|
|
||||||
ToolButton::ToolButton(bool hoverColor, QWidget *parent)
|
|
||||||
: QPushButton(parent)
|
|
||||||
, m_opacityHelper(new OpacityHelper(this))
|
|
||||||
{
|
|
||||||
setFlat(true);
|
|
||||||
QString qss = "QPushButton {"
|
|
||||||
"background: transparent;"
|
|
||||||
"}";
|
|
||||||
if (hoverColor) {
|
|
||||||
qss += "QPushButton:hover {"
|
|
||||||
"background: red;"
|
|
||||||
"}";
|
|
||||||
}
|
|
||||||
setStyleSheet(qss);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ToolButton::setIconResourcePath(const QString &iconp)
|
|
||||||
{
|
|
||||||
this->setIcon(ActionManager::loadHidpiIcon(iconp, this->iconSize()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ToolButton::setOpacity(qreal opacity, bool animated)
|
|
||||||
{
|
|
||||||
m_opacityHelper->setOpacity(opacity, animated);
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#ifndef TOOLBUTTON_H
|
|
||||||
#define TOOLBUTTON_H
|
|
||||||
|
|
||||||
#include <QPushButton>
|
|
||||||
|
|
||||||
class OpacityHelper;
|
|
||||||
class ToolButton : public QPushButton
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
ToolButton(bool hoverColor = false, QWidget * parent = nullptr);
|
|
||||||
void setIconResourcePath(const QString &iconp);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void setOpacity(qreal opacity, bool animated = true);
|
|
||||||
|
|
||||||
private:
|
|
||||||
OpacityHelper * m_opacityHelper;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // TOOLBUTTON_H
|
|
||||||
@@ -236,27 +236,27 @@
|
|||||||
<location filename="../mainwindow.cpp" line="734"/>
|
<location filename="../mainwindow.cpp" line="734"/>
|
||||||
<location filename="../mainwindow.cpp" line="764"/>
|
<location filename="../mainwindow.cpp" line="764"/>
|
||||||
<source>Save As</source>
|
<source>Save As</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>다른 이름으로 저장</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../mainwindow.cpp" line="710"/>
|
<location filename="../mainwindow.cpp" line="710"/>
|
||||||
<source>No image is currently open.</source>
|
<source>No image is currently open.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>현재 열려 있는 이미지가 없습니다.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../mainwindow.cpp" line="722"/>
|
<location filename="../mainwindow.cpp" line="722"/>
|
||||||
<source>%1 Image (*.%2)</source>
|
<source>%1 Image (*.%2)</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>%1 이미지 (*.%2)</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../mainwindow.cpp" line="728"/>
|
<location filename="../mainwindow.cpp" line="728"/>
|
||||||
<source>No supported image formats are available.</source>
|
<source>No supported image formats are available.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>지원되는 이미지 형식을 사용할 수 없습니다.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../mainwindow.cpp" line="765"/>
|
<location filename="../mainwindow.cpp" line="765"/>
|
||||||
<source>Failed to save image: %1</source>
|
<source>Failed to save image: %1</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>이미지 저장 실패: %1</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../mainwindow.cpp" line="854"/>
|
<location filename="../mainwindow.cpp" line="854"/>
|
||||||
@@ -320,7 +320,7 @@
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../actionmanager.cpp" line="88"/>
|
<location filename="../actionmanager.cpp" line="88"/>
|
||||||
<source>Save &As...</source>
|
<source>Save &As...</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>다른 이름으로 저장(&A)...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../actionmanager.cpp" line="93"/>
|
<location filename="../actionmanager.cpp" line="93"/>
|
||||||
|
|||||||
@@ -240,27 +240,27 @@
|
|||||||
<location filename="../mainwindow.cpp" line="734"/>
|
<location filename="../mainwindow.cpp" line="734"/>
|
||||||
<location filename="../mainwindow.cpp" line="764"/>
|
<location filename="../mainwindow.cpp" line="764"/>
|
||||||
<source>Save As</source>
|
<source>Save As</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Opslaan als</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../mainwindow.cpp" line="710"/>
|
<location filename="../mainwindow.cpp" line="710"/>
|
||||||
<source>No image is currently open.</source>
|
<source>No image is currently open.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Er is geen afbeelding geopend.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../mainwindow.cpp" line="722"/>
|
<location filename="../mainwindow.cpp" line="722"/>
|
||||||
<source>%1 Image (*.%2)</source>
|
<source>%1 Image (*.%2)</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>%1-afbeelding (*.%2)</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../mainwindow.cpp" line="728"/>
|
<location filename="../mainwindow.cpp" line="728"/>
|
||||||
<source>No supported image formats are available.</source>
|
<source>No supported image formats are available.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Er zijn geen ondersteunde formaten beschikbaar.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../mainwindow.cpp" line="765"/>
|
<location filename="../mainwindow.cpp" line="765"/>
|
||||||
<source>Failed to save image: %1</source>
|
<source>Failed to save image: %1</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>De afbeelding kan niet worden opgeslagen: %1</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../mainwindow.cpp" line="854"/>
|
<location filename="../mainwindow.cpp" line="854"/>
|
||||||
@@ -324,7 +324,7 @@
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../actionmanager.cpp" line="88"/>
|
<location filename="../actionmanager.cpp" line="88"/>
|
||||||
<source>Save &As...</source>
|
<source>Save &As...</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Opslaan &als…</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../actionmanager.cpp" line="93"/>
|
<location filename="../actionmanager.cpp" line="93"/>
|
||||||
@@ -359,7 +359,7 @@
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../actionmanager.cpp" line="107"/>
|
<location filename="../actionmanager.cpp" line="107"/>
|
||||||
<source>Fit long image</source>
|
<source>Fit long image</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Lange afbeelding inpassen</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../actionmanager.cpp" line="110"/>
|
<location filename="../actionmanager.cpp" line="110"/>
|
||||||
@@ -891,7 +891,7 @@
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../settingsdialog.cpp" line="129"/>
|
<location filename="../settingsdialog.cpp" line="129"/>
|
||||||
<source>Limit SVG support to SVG Tiny 1.2</source>
|
<source>Limit SVG support to SVG Tiny 1.2</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Svg-ondersteuning beperken tot SVG Tiny 1.2</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../settingsdialog.cpp" line="130"/>
|
<location filename="../settingsdialog.cpp" line="130"/>
|
||||||
|
|||||||
@@ -225,27 +225,27 @@ Loc Huynh <https://github.com/hthienloc></translation>
|
|||||||
<location filename="../mainwindow.cpp" line="734"/>
|
<location filename="../mainwindow.cpp" line="734"/>
|
||||||
<location filename="../mainwindow.cpp" line="764"/>
|
<location filename="../mainwindow.cpp" line="764"/>
|
||||||
<source>Save As</source>
|
<source>Save As</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Lưu dưới dạng</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../mainwindow.cpp" line="710"/>
|
<location filename="../mainwindow.cpp" line="710"/>
|
||||||
<source>No image is currently open.</source>
|
<source>No image is currently open.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Hiện tại không có hình ảnh nào được mở.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../mainwindow.cpp" line="722"/>
|
<location filename="../mainwindow.cpp" line="722"/>
|
||||||
<source>%1 Image (*.%2)</source>
|
<source>%1 Image (*.%2)</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Hình ảnh %1 (*.%2)</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../mainwindow.cpp" line="728"/>
|
<location filename="../mainwindow.cpp" line="728"/>
|
||||||
<source>No supported image formats are available.</source>
|
<source>No supported image formats are available.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Không có định dạng hình ảnh được hỗ trợ.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../mainwindow.cpp" line="765"/>
|
<location filename="../mainwindow.cpp" line="765"/>
|
||||||
<source>Failed to save image: %1</source>
|
<source>Failed to save image: %1</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Không lưu được hình ảnh: %1</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../mainwindow.cpp" line="854"/>
|
<location filename="../mainwindow.cpp" line="854"/>
|
||||||
@@ -309,7 +309,7 @@ Loc Huynh <https://github.com/hthienloc></translation>
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../actionmanager.cpp" line="88"/>
|
<location filename="../actionmanager.cpp" line="88"/>
|
||||||
<source>Save &As...</source>
|
<source>Save &As...</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation>Lưu &dưới dạng...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../actionmanager.cpp" line="93"/>
|
<location filename="../actionmanager.cpp" line="93"/>
|
||||||
|
|||||||
-158
@@ -1,158 +0,0 @@
|
|||||||
image:
|
|
||||||
- Visual Studio 2022
|
|
||||||
environment:
|
|
||||||
CMAKE_INSTALL_PREFIX: C:\projects\cmake
|
|
||||||
LIBZ: C:\projects\zlib
|
|
||||||
LIBEXPAT: C:\projects\libexpat
|
|
||||||
LIBAVIF: C:\projects\libavif
|
|
||||||
LIBEXIV2: C:\projects\exiv2
|
|
||||||
PPKG: C:\projects\ppkg
|
|
||||||
matrix:
|
|
||||||
- job_name: mingw_64_qt6_8
|
|
||||||
QTDIR: C:\Qt\6.8\mingw_64
|
|
||||||
MINGW64: C:\Qt\mingw_64\Tools\mingw1310_64
|
|
||||||
KF_BRANCH: master
|
|
||||||
EXIV2_VERSION: "0.28.7"
|
|
||||||
EXIV2_CMAKE_OPTIONS: "-DEXIV2_ENABLE_BROTLI=OFF -DEXIV2_ENABLE_INIH=OFF -DEXIV2_BUILD_EXIV2_COMMAND=OFF"
|
|
||||||
PPIC_CMAKE_OPTIONS: "-DPREFER_QT_5=OFF"
|
|
||||||
WINDEPLOYQT_ARGS: "--verbose=2 --no-quick-import --no-translations --no-opengl-sw --no-system-d3d-compiler --skip-plugin-types tls,networkinformation"
|
|
||||||
|
|
||||||
install:
|
|
||||||
- mkdir %CMAKE_INSTALL_PREFIX%
|
|
||||||
- mkdir %LIBZ%
|
|
||||||
- mkdir %LIBEXPAT%
|
|
||||||
- mkdir %LIBAVIF%
|
|
||||||
- mkdir %LIBEXIV2%
|
|
||||||
- mkdir %PPKG%
|
|
||||||
- cd %APPVEYOR_BUILD_FOLDER%
|
|
||||||
- git submodule update --init --recursive
|
|
||||||
- set PATH=%PATH%;%CMAKE_INSTALL_PREFIX%;%QTDIR%\bin;%MINGW64%\bin;%PPKG%
|
|
||||||
- set CC=%MINGW64%\bin\gcc.exe
|
|
||||||
- set CXX=%MINGW64%\bin\g++.exe
|
|
||||||
|
|
||||||
build_script:
|
|
||||||
# prepare
|
|
||||||
- mkdir 3rdparty
|
|
||||||
- choco install ninja
|
|
||||||
- cd %PPKG%
|
|
||||||
- curl -fsSL -o ppkg.exe https://github.com/BLumia/pineapple-package-manager/releases/latest/download/ppkg.exe
|
|
||||||
- cd %APPVEYOR_BUILD_FOLDER%
|
|
||||||
# download and install zlib for KArchive
|
|
||||||
- cd %LIBZ%
|
|
||||||
- curl -fsSL -o zlib131.zip https://zlib.net/zlib131.zip
|
|
||||||
- 7z x zlib131.zip -y
|
|
||||||
- cd zlib-1.3.1
|
|
||||||
- mkdir build
|
|
||||||
- cd build
|
|
||||||
- cmake .. -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=%CMAKE_INSTALL_PREFIX%
|
|
||||||
- cmake --build . --config Release
|
|
||||||
- cmake --build . --config Release --target install/strip
|
|
||||||
- cd %APPVEYOR_BUILD_FOLDER%
|
|
||||||
# install ECM so we can build KImageFormats
|
|
||||||
- cd 3rdparty
|
|
||||||
- git clone -b %KF_BRANCH% -q https://invent.kde.org/frameworks/extra-cmake-modules.git
|
|
||||||
- git rev-parse HEAD
|
|
||||||
- cd extra-cmake-modules
|
|
||||||
- cmake -G "Ninja" . -DCMAKE_INSTALL_PREFIX=%CMAKE_INSTALL_PREFIX% -DBUILD_TESTING=OFF
|
|
||||||
- cmake --build .
|
|
||||||
- cmake --build . --target install
|
|
||||||
- cd %APPVEYOR_BUILD_FOLDER%
|
|
||||||
# install AOM for libavif AV1 decoding support...
|
|
||||||
- cd 3rdparty
|
|
||||||
#- git clone -b v3.9.1 --depth 1 https://aomedia.googlesource.com/aom
|
|
||||||
#- cd aom
|
|
||||||
#- mkdir build.aom
|
|
||||||
#- cd build.aom
|
|
||||||
#- cmake .. -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=%CMAKE_INSTALL_PREFIX% -DENABLE_DOCS=OFF -DBUILD_SHARED_LIBS=ON -DAOM_TARGET_CPU=generic -DENABLE_TESTS=OFF -DENABLE_TESTDATA=OFF -DENABLE_TOOLS=OFF -DENABLE_EXAMPLES=0
|
|
||||||
#- cmake --build . --config Release
|
|
||||||
#- cmake --build . --config Release --target install/strip
|
|
||||||
- mkdir aom
|
|
||||||
- cd aom
|
|
||||||
- curl -fsSL -o ppkg-aom.zip https://sourceforge.net/projects/pineapple-package-manager/files/packages/mingw-w64-x86_64-windows/aom-3.9.1-2.zip
|
|
||||||
- ppkg ppkg-aom.zip
|
|
||||||
- 7z x ppkg-aom.zip LICENSE -y
|
|
||||||
- cd %APPVEYOR_BUILD_FOLDER%
|
|
||||||
# install libavif for avif format support of KImageFormats
|
|
||||||
- cd %LIBAVIF%
|
|
||||||
- curl -fsSL -o libavif-v1_3_0.zip https://github.com/AOMediaCodec/libavif/archive/v1.3.0.zip
|
|
||||||
- 7z x libavif-v1_3_0.zip -y
|
|
||||||
- cd libavif-1.3.0
|
|
||||||
- mkdir build
|
|
||||||
- cd build
|
|
||||||
- cmake .. -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=%CMAKE_INSTALL_PREFIX% -DAVIF_CODEC_AOM=SYSTEM -DAVIF_CODEC_AOM_DECODE=ON -DAVIF_CODEC_AOM_ENCODE=ON -DAVIF_LIBYUV=LOCAL
|
|
||||||
- cmake --build . --config Release
|
|
||||||
- cmake --build . --config Release --target install/strip
|
|
||||||
- cd %APPVEYOR_BUILD_FOLDER%
|
|
||||||
# install KArchive for kra format support of KImageFormats
|
|
||||||
- cd 3rdparty
|
|
||||||
- git clone -b %KF_BRANCH% -q https://invent.kde.org/frameworks/karchive.git
|
|
||||||
- git rev-parse HEAD
|
|
||||||
- cd karchive
|
|
||||||
- mkdir build
|
|
||||||
- cd build
|
|
||||||
- cmake .. -G "Ninja" -DBUILD_TESTING=OFF -DWITH_LIBZSTD=OFF -DWITH_BZIP2=OFF -DWITH_LIBLZMA=OFF -DWITH_OPENSSL=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=%CMAKE_INSTALL_PREFIX%
|
|
||||||
- cmake --build . --config Release
|
|
||||||
- cmake --build . --config Release --target install/strip
|
|
||||||
- cd %APPVEYOR_BUILD_FOLDER%
|
|
||||||
# build libexpat for libexiv2
|
|
||||||
- cd %LIBEXPAT%
|
|
||||||
- curl -fsSL -o R_2_7_3.zip https://github.com/libexpat/libexpat/archive/R_2_7_3.zip
|
|
||||||
- 7z x R_2_7_3.zip -y
|
|
||||||
- cd libexpat-R_2_7_3/expat/
|
|
||||||
- cmake -G "Ninja" . -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=%CMAKE_INSTALL_PREFIX% -DEXPAT_BUILD_EXAMPLES=OFF -DEXPAT_BUILD_TESTS=OFF -DEXPAT_BUILD_TOOLS=OFF
|
|
||||||
- cmake --build . --target install/strip
|
|
||||||
- cd %APPVEYOR_BUILD_FOLDER%
|
|
||||||
# build libexiv2
|
|
||||||
- cd %LIBEXIV2%
|
|
||||||
- curl -fsSL -o v%EXIV2_VERSION%.zip https://github.com/Exiv2/exiv2/archive/v%EXIV2_VERSION%.zip
|
|
||||||
- 7z x v%EXIV2_VERSION%.zip -y
|
|
||||||
- cd exiv2-%EXIV2_VERSION%
|
|
||||||
- cmake -G "Ninja" . -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=%CMAKE_INSTALL_PREFIX% %EXIV2_CMAKE_OPTIONS%
|
|
||||||
- cmake --build . --target install/strip
|
|
||||||
- cd %APPVEYOR_BUILD_FOLDER%
|
|
||||||
# install KImageFormats
|
|
||||||
- cd 3rdparty
|
|
||||||
- git clone -b %KF_BRANCH% -q https://invent.kde.org/frameworks/kimageformats.git
|
|
||||||
- git rev-parse HEAD
|
|
||||||
- cd kimageformats
|
|
||||||
- mkdir build
|
|
||||||
- cd build
|
|
||||||
- cmake .. -G "Ninja" -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DKDE_INSTALL_QTPLUGINDIR=%QTDIR%\plugins
|
|
||||||
- cmake --build . --config Release
|
|
||||||
- cmake --build . --config Release --target install/strip
|
|
||||||
- cd %APPVEYOR_BUILD_FOLDER%
|
|
||||||
# finally...
|
|
||||||
- mkdir build
|
|
||||||
- cd build
|
|
||||||
- cmake .. -G "Ninja" %PPIC_CMAKE_OPTIONS% -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=%CMAKE_INSTALL_PREFIX% -DCMAKE_INSTALL_PREFIX='%cd%'
|
|
||||||
- cmake --build . --config Release
|
|
||||||
- cmake --build . --config Release --target install/strip
|
|
||||||
# fixme: I don't know how to NOT make the binary installed to the ./bin/ folder...
|
|
||||||
- cd bin
|
|
||||||
- copy %APPVEYOR_BUILD_FOLDER%\LICENSE
|
|
||||||
- copy %CMAKE_INSTALL_PREFIX%\bin\libaom.dll
|
|
||||||
- copy %CMAKE_INSTALL_PREFIX%\bin\libexpat-1.dll
|
|
||||||
- copy %CMAKE_INSTALL_PREFIX%\bin\libexiv2.dll
|
|
||||||
- copy %CMAKE_INSTALL_PREFIX%\bin\libavif.dll
|
|
||||||
- copy %CMAKE_INSTALL_PREFIX%\bin\libzlib.dll
|
|
||||||
- copy %CMAKE_INSTALL_PREFIX%\bin\libKF?Archive.dll
|
|
||||||
- windeployqt %WINDEPLOYQT_ARGS% .\ppic.exe
|
|
||||||
# copy 3rdparty licenses for the libs we vendored for windows...
|
|
||||||
- mkdir licenses
|
|
||||||
- cd licenses
|
|
||||||
- copy %APPVEYOR_BUILD_FOLDER%\3rdparty\aom\LICENSE License.aom.txt
|
|
||||||
- copy %APPVEYOR_BUILD_FOLDER%\3rdparty\karchive\LICENSES\LGPL-2.0-or-later.txt License.KArchive.txt
|
|
||||||
- copy %APPVEYOR_BUILD_FOLDER%\3rdparty\kimageformats\LICENSES\LGPL-2.1-or-later.txt License.kimageformats.txt
|
|
||||||
- copy %LIBEXPAT%\libexpat-R_2_7_3\expat\COPYING License.expat.txt
|
|
||||||
- copy %LIBAVIF%\libavif-1.3.0\LICENSE License.libavif.txt
|
|
||||||
- copy %LIBEXIV2%\exiv2-%EXIV2_VERSION%\COPYING License.exiv2.txt
|
|
||||||
# TODO: Qt, zlib
|
|
||||||
- cd ..
|
|
||||||
# for debug..
|
|
||||||
- tree /f
|
|
||||||
- cd %APPVEYOR_BUILD_FOLDER%
|
|
||||||
- xcopy %CMAKE_INSTALL_PREFIX% .\cmake-prefix-copy /E /H /C /I
|
|
||||||
|
|
||||||
artifacts:
|
|
||||||
- path: build\bin
|
|
||||||
- path: cmake-prefix-copy
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<center>
|
||||||
|
<p>
|
||||||
|
<img width='128' height='128' src=':/icons/app-icon.svg'/>
|
||||||
|
<br/>
|
||||||
|
<!-- Application name -->
|
||||||
|
<span>%1</span>
|
||||||
|
<br/>
|
||||||
|
<!-- Version info -->
|
||||||
|
<span>%2</span>
|
||||||
|
</p>
|
||||||
|
<hr/>
|
||||||
|
<p>
|
||||||
|
<!-- Upstream author and fork author -->
|
||||||
|
<span>%3</span><span>: </span><a href='https://github.com/BLumia'>@BLumia</a>
|
||||||
|
<br/>
|
||||||
|
<span>%4</span><span>: </span><a href='https://github.com/yyc12345'>@yyc12345</a>
|
||||||
|
<br/>
|
||||||
|
<!-- Icon author -->
|
||||||
|
<span>%5</span><span>: </span><a href='https://github.com/Lovelyblack'>@Lovelyblack</a>
|
||||||
|
</p>
|
||||||
|
<hr/>
|
||||||
|
<p>
|
||||||
|
<!-- Built Qt version info -->
|
||||||
|
<span>%6</span>
|
||||||
|
<br/>
|
||||||
|
<!-- Source code -->
|
||||||
|
<span>%7</span><span>: </span><a href='https://github.com/BLumia/pineapple-pictures'>GitHub</a>
|
||||||
|
</p>
|
||||||
|
</center>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<!-- General help -->
|
||||||
|
<h1 align='center'>%1</h1>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span>%2</span>
|
||||||
|
<ul>
|
||||||
|
<li>%3</li>
|
||||||
|
<li>%4</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>%5</li>
|
||||||
|
<li>%6</li>
|
||||||
|
</ul>
|
||||||
|
<hr/>
|
||||||
|
<!-- Context menu option help -->
|
||||||
|
<h1 align='center'>%7</h1>
|
||||||
|
<!-- Context menu option list -->
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>%8</b><span>:</span>
|
||||||
|
<br/>
|
||||||
|
<span>%9</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>%10</b><span>:</span>
|
||||||
|
<br/>
|
||||||
|
<span>%11</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<!-- License -->
|
||||||
|
<h1 align='center'><b>%1</b></h1>
|
||||||
|
<p>%2</p>
|
||||||
|
<hr/>
|
||||||
|
<!-- VErbatime license -->
|
||||||
|
<h1 align='center'><b>%3</b></h1>
|
||||||
|
<pre>
|
||||||
|
Expat/MIT License
|
||||||
|
|
||||||
|
Copyright © 2026 BLumia
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
</pre>
|
||||||
@@ -1,3 +1,14 @@
|
|||||||
|
<!-- Contributor title -->
|
||||||
|
<h1 align='center'>%1</h1>
|
||||||
|
<!-- Contributor list -->
|
||||||
|
<a href='https://github.com/BLumia/pineapple-pictures/graphs/contributors'>%2</a>
|
||||||
|
<!-- The message to thank these contributors -->
|
||||||
|
<p>%3</p>
|
||||||
|
|
||||||
|
<!-- Translator title -->
|
||||||
|
<h1 align='center'>%4</h1>
|
||||||
|
<!-- The message to thank these translators -->
|
||||||
|
<p>%5</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><u>Catalan</u>: Toni Estévez</li>
|
<li><u>Catalan</u>: Toni Estévez</li>
|
||||||
<li><u>Chinese (Simplified)</u>: Percy Hong</li>
|
<li><u>Chinese (Simplified)</u>: Percy Hong</li>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<!-- Title -->
|
||||||
|
<h1 align='center'><b>%1</b></h1>
|
||||||
|
<!-- List intro message -->
|
||||||
|
<p>%2</p>
|
||||||
|
<p>%3</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href='https://www.qt.io/'>Qt</a><span>: </span>
|
||||||
|
<span>GPLv2 + GPLv3 + LGPLv2.1 + LGPLv3</span>
|
||||||
|
</li>
|
||||||
|
<li style="display: none;">
|
||||||
|
<a href='https://www.exiv2.org/'>Exiv2</a><span>: </span>
|
||||||
|
<span>GPLv2</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
@@ -10,6 +10,10 @@
|
|||||||
<file>icons/window-close.svg</file>
|
<file>icons/window-close.svg</file>
|
||||||
<file>icons/go-next.svg</file>
|
<file>icons/go-next.svg</file>
|
||||||
<file>icons/go-previous.svg</file>
|
<file>icons/go-previous.svg</file>
|
||||||
<file>plain/translators.html</file>
|
<file>plain/help.html</file>
|
||||||
|
<file>plain/about.html</file>
|
||||||
|
<file>plain/special-thanks.html</file>
|
||||||
|
<file>plain/license.html</file>
|
||||||
|
<file>plain/third-party-libs.html</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@@ -4,18 +4,26 @@
|
|||||||
<name>Pineapple Pictures</name>
|
<name>Pineapple Pictures</name>
|
||||||
<name xml:lang="es">Pineapple Pictures</name>
|
<name xml:lang="es">Pineapple Pictures</name>
|
||||||
<name xml:lang="ja">Pineapple Pictures</name>
|
<name xml:lang="ja">Pineapple Pictures</name>
|
||||||
|
<name xml:lang="ko">Pineapple Pictures</name>
|
||||||
<name xml:lang="nl">Pineapple Afbeeldingen</name>
|
<name xml:lang="nl">Pineapple Afbeeldingen</name>
|
||||||
<name xml:lang="ru">Pineapple Pictures</name>
|
<name xml:lang="ru">Pineapple Pictures</name>
|
||||||
|
<name xml:lang="sl">Pineapple Slike</name>
|
||||||
<name xml:lang="ta">அன்னாசி படங்கள்</name>
|
<name xml:lang="ta">அன்னாசி படங்கள்</name>
|
||||||
|
<name xml:lang="tr">Pineapple Resimler</name>
|
||||||
<name xml:lang="uk">Pineapple Pictures</name>
|
<name xml:lang="uk">Pineapple Pictures</name>
|
||||||
|
<name xml:lang="vi">Pineapple Pictures</name>
|
||||||
<name xml:lang="zh-CN">菠萝看图</name>
|
<name xml:lang="zh-CN">菠萝看图</name>
|
||||||
<summary>Image Viewer</summary>
|
<summary>Image Viewer</summary>
|
||||||
<summary xml:lang="es">Visor de imágenes</summary>
|
<summary xml:lang="es">Visor de imágenes</summary>
|
||||||
<summary xml:lang="ja">画像ビューアー</summary>
|
<summary xml:lang="ja">画像ビューアー</summary>
|
||||||
|
<summary xml:lang="ko">이미지 뷰어</summary>
|
||||||
<summary xml:lang="nl">Afbeeldingsweergave</summary>
|
<summary xml:lang="nl">Afbeeldingsweergave</summary>
|
||||||
<summary xml:lang="ru">Просмотр изображений</summary>
|
<summary xml:lang="ru">Просмотр изображений</summary>
|
||||||
|
<summary xml:lang="sl">Pregledovalnik slik</summary>
|
||||||
<summary xml:lang="ta">பட பார்வையாளர்</summary>
|
<summary xml:lang="ta">பட பார்வையாளர்</summary>
|
||||||
|
<summary xml:lang="tr">Resim Görüntüleyici</summary>
|
||||||
<summary xml:lang="uk">Переглядач зображень</summary>
|
<summary xml:lang="uk">Переглядач зображень</summary>
|
||||||
|
<summary xml:lang="vi">Trình xem ảnh</summary>
|
||||||
<summary xml:lang="zh-CN">图像查看器</summary>
|
<summary xml:lang="zh-CN">图像查看器</summary>
|
||||||
<metadata_license>CC0-1.0</metadata_license>
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
<project_license>MIT</project_license>
|
<project_license>MIT</project_license>
|
||||||
@@ -23,10 +31,13 @@
|
|||||||
<p>Pineapple Pictures is a lightweight and easy-to-use image viewer that comes with a handy navigation thumbnail when zoom-in, and doesn't contain any image management support.</p>
|
<p>Pineapple Pictures is a lightweight and easy-to-use image viewer that comes with a handy navigation thumbnail when zoom-in, and doesn't contain any image management support.</p>
|
||||||
<p xml:lang="es">Pineapple Pictures es un visor de imágenes ligero y fácil de usar que viene con una práctica miniatura de navegación al hacer zoom, y no contiene ningún soporte de gestión de imágenes.</p>
|
<p xml:lang="es">Pineapple Pictures es un visor de imágenes ligero y fácil de usar que viene con una práctica miniatura de navegación al hacer zoom, y no contiene ningún soporte de gestión de imágenes.</p>
|
||||||
<p xml:lang="ja">Pineapple Picturesは、ズームイン時に便利なナビゲーションサムネイルを備えた軽量で使いやすい画像ビューアです。画像管理のサポートは含まれていません。</p>
|
<p xml:lang="ja">Pineapple Picturesは、ズームイン時に便利なナビゲーションサムネイルを備えた軽量で使いやすい画像ビューアです。画像管理のサポートは含まれていません。</p>
|
||||||
|
<p xml:lang="ko">Pineapple Pictures는 가볍고 사용하기 쉬운 이미지 뷰어로, 확대 시 편리한 내비게이션 썸네일과 함께 제공되며 이미지 관리 지원이 포함되어 있지 않습니다.</p>
|
||||||
<p xml:lang="nl">Pineapple Afbeeldingen is een licht en eenvoudig te gebruiken afbeeldingsweergaveprogramma met miniatuurnavigatie na inzoomen. Het programma heeft echter geen fotobeheermogelijkheid.</p>
|
<p xml:lang="nl">Pineapple Afbeeldingen is een licht en eenvoudig te gebruiken afbeeldingsweergaveprogramma met miniatuurnavigatie na inzoomen. Het programma heeft echter geen fotobeheermogelijkheid.</p>
|
||||||
<p xml:lang="ru">Pineapple Pictures - это легкий и простой в использовании просмотрщик изображений, оснащенный удобной навигацией по миниатюрам при увеличении масштаба и не содержащий никакой поддержки управления изображениями.</p>
|
<p xml:lang="ru">Pineapple Pictures - это легкий и простой в использовании просмотрщик изображений, оснащенный удобной навигацией по миниатюрам при увеличении масштаба и не содержащий никакой поддержки управления изображениями.</p>
|
||||||
|
<p xml:lang="sl">Pineapple Pictures je lahek in enostaven pregledovalnik slik, ki ima pri povečavi priročno sličico za navigacijo in ne vsebuje nobene podpore za upravljanje slik.</p>
|
||||||
<p xml:lang="ta">அன்னாசி படங்கள் ஒரு இலகுரக மற்றும் பயன்படுத்த எளிதான பட பார்வையாளராகும், இது பெரிதாக்கும்போது ஒரு எளிமையான வழிசெலுத்தல் சிறுபடத்துடன் வருகிறது, மேலும் எந்த பட மேலாண்மை ஆதரவையும் கொண்டிருக்கவில்லை.</p>
|
<p xml:lang="ta">அன்னாசி படங்கள் ஒரு இலகுரக மற்றும் பயன்படுத்த எளிதான பட பார்வையாளராகும், இது பெரிதாக்கும்போது ஒரு எளிமையான வழிசெலுத்தல் சிறுபடத்துடன் வருகிறது, மேலும் எந்த பட மேலாண்மை ஆதரவையும் கொண்டிருக்கவில்லை.</p>
|
||||||
<p xml:lang="uk">Pineapple Pictures – це легкий і простий у використанні переглядач зображень, який постачається зі зручною навігаційною мініатюрою при збільшенні масштабу і не містить жодної підтримки керування зображеннями.</p>
|
<p xml:lang="uk">Pineapple Pictures – це легкий і простий у використанні переглядач зображень, який постачається зі зручною навігаційною мініатюрою при збільшенні масштабу і не містить жодної підтримки керування зображеннями.</p>
|
||||||
|
<p xml:lang="vi">Pineapple Pictures là trình xem hình ảnh nhẹ và dễ sử dụng, đi kèm với hình thu nhỏ điều hướng tiện dụng khi phóng to và không chứa bất kỳ hỗ trợ quản lý hình ảnh nào.</p>
|
||||||
<p xml:lang="zh-CN">菠萝看图是一个轻量级易用的图像查看器,在图片放大时提供了方便的鸟瞰导航功能,且不包含任何图片管理功能。</p>
|
<p xml:lang="zh-CN">菠萝看图是一个轻量级易用的图像查看器,在图片放大时提供了方便的鸟瞰导航功能,且不包含任何图片管理功能。</p>
|
||||||
</description>
|
</description>
|
||||||
<developer id="net.blumia">
|
<developer id="net.blumia">
|
||||||
@@ -42,6 +53,7 @@
|
|||||||
<url type="bugtracker">https://github.com/BLumia/pineapple-pictures/issues</url>
|
<url type="bugtracker">https://github.com/BLumia/pineapple-pictures/issues</url>
|
||||||
<url type="donation">https://ko-fi.com/blumia</url>
|
<url type="donation">https://ko-fi.com/blumia</url>
|
||||||
<url type="translate">https://hosted.weblate.org/projects/pineapple-pictures/</url>
|
<url type="translate">https://hosted.weblate.org/projects/pineapple-pictures/</url>
|
||||||
|
<url type="vcs-browser">https://github.com/BLumia/pineapple-pictures</url>
|
||||||
<provides>
|
<provides>
|
||||||
<binary>ppic</binary>
|
<binary>ppic</binary>
|
||||||
</provides>
|
</provides>
|
||||||
@@ -50,10 +62,13 @@
|
|||||||
<caption>Main window when an image file is loaded</caption>
|
<caption>Main window when an image file is loaded</caption>
|
||||||
<caption xml:lang="es">Ventana principal cuando se carga un archivo de imagen</caption>
|
<caption xml:lang="es">Ventana principal cuando se carga un archivo de imagen</caption>
|
||||||
<caption xml:lang="ja">画像ファイル読み込み時のメインウィンドウ</caption>
|
<caption xml:lang="ja">画像ファイル読み込み時のメインウィンドウ</caption>
|
||||||
|
<caption xml:lang="ko">이미지 파일이 로드될 때 기본 창</caption>
|
||||||
<caption xml:lang="nl">Hoofdvenster na het laden van een afbeelding</caption>
|
<caption xml:lang="nl">Hoofdvenster na het laden van een afbeelding</caption>
|
||||||
<caption xml:lang="ru">Основное окно после загрузки файла изображения</caption>
|
<caption xml:lang="ru">Основное окно после загрузки файла изображения</caption>
|
||||||
|
<caption xml:lang="sl">Glavno okno ob nalaganju slikovne datoteke</caption>
|
||||||
<caption xml:lang="ta">ஒரு படக் கோப்பு ஏற்றப்படும் போது முதன்மையான சாளரம்</caption>
|
<caption xml:lang="ta">ஒரு படக் கோப்பு ஏற்றப்படும் போது முதன்மையான சாளரம்</caption>
|
||||||
<caption xml:lang="uk">Головне вікно після завантаження файлу зображення</caption>
|
<caption xml:lang="uk">Головне вікно після завантаження файлу зображення</caption>
|
||||||
|
<caption xml:lang="vi">Cửa sổ chính khi tải tệp hình ảnh</caption>
|
||||||
<caption xml:lang="zh-CN">加载图片后的主窗口</caption>
|
<caption xml:lang="zh-CN">加载图片后的主窗口</caption>
|
||||||
<image type="source" width="1503" height="640">https://pineapple-pictures.sourceforge.io/ppic-gui-static.png</image>
|
<image type="source" width="1503" height="640">https://pineapple-pictures.sourceforge.io/ppic-gui-static.png</image>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
@@ -61,10 +76,13 @@
|
|||||||
<caption>Zooming in a raster image</caption>
|
<caption>Zooming in a raster image</caption>
|
||||||
<caption xml:lang="es">Ampliar una imagen rasterizada</caption>
|
<caption xml:lang="es">Ampliar una imagen rasterizada</caption>
|
||||||
<caption xml:lang="ja">ラスター画像の拡大</caption>
|
<caption xml:lang="ja">ラスター画像の拡大</caption>
|
||||||
|
<caption xml:lang="ko">래스터 이미지 확대하기</caption>
|
||||||
<caption xml:lang="nl">Inzoomen op een roosterafbeelding</caption>
|
<caption xml:lang="nl">Inzoomen op een roosterafbeelding</caption>
|
||||||
<caption xml:lang="ru">Масштабирование растрового изображения</caption>
|
<caption xml:lang="ru">Масштабирование растрового изображения</caption>
|
||||||
|
<caption xml:lang="sl">Povečevanje rastrske slike</caption>
|
||||||
<caption xml:lang="ta">ராச்டர் படத்தில் பெரிதாக்குதல்</caption>
|
<caption xml:lang="ta">ராச்டர் படத்தில் பெரிதாக்குதல்</caption>
|
||||||
<caption xml:lang="uk">Масштабування растрового зображення</caption>
|
<caption xml:lang="uk">Масштабування растрового зображення</caption>
|
||||||
|
<caption xml:lang="vi">Phóng to hình ảnh raster</caption>
|
||||||
<caption xml:lang="zh-CN">放大浏览位图</caption>
|
<caption xml:lang="zh-CN">放大浏览位图</caption>
|
||||||
<image type="source" width="771" height="553">https://pineapple-pictures.sourceforge.io/ppic-zoom-raster.png</image>
|
<image type="source" width="771" height="553">https://pineapple-pictures.sourceforge.io/ppic-zoom-raster.png</image>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
@@ -72,15 +90,49 @@
|
|||||||
<caption>Zooming in a vector image</caption>
|
<caption>Zooming in a vector image</caption>
|
||||||
<caption xml:lang="es">Ampliar una imagen vectorial</caption>
|
<caption xml:lang="es">Ampliar una imagen vectorial</caption>
|
||||||
<caption xml:lang="ja">ベクター画像の拡大</caption>
|
<caption xml:lang="ja">ベクター画像の拡大</caption>
|
||||||
|
<caption xml:lang="ko">벡터 이미지 확대하기</caption>
|
||||||
<caption xml:lang="nl">Inzoomen op een vectorafbeelding</caption>
|
<caption xml:lang="nl">Inzoomen op een vectorafbeelding</caption>
|
||||||
<caption xml:lang="ru">Масштабирование векторного изображения</caption>
|
<caption xml:lang="ru">Масштабирование векторного изображения</caption>
|
||||||
|
<caption xml:lang="sl">Povečevanje vektorske slike</caption>
|
||||||
<caption xml:lang="ta">ஒரு திசையன் படத்தில் பெரிதாக்குதல்</caption>
|
<caption xml:lang="ta">ஒரு திசையன் படத்தில் பெரிதாக்குதல்</caption>
|
||||||
<caption xml:lang="uk">Масштабування векторного зображення</caption>
|
<caption xml:lang="uk">Масштабування векторного зображення</caption>
|
||||||
|
<caption xml:lang="vi">Phóng to hình ảnh vector</caption>
|
||||||
<caption xml:lang="zh-CN">放大浏览矢量图</caption>
|
<caption xml:lang="zh-CN">放大浏览矢量图</caption>
|
||||||
<image type="source" width="771" height="553">https://pineapple-pictures.sourceforge.io/ppic-zoom-svg.png</image>
|
<image type="source" width="771" height="553">https://pineapple-pictures.sourceforge.io/ppic-zoom-svg.png</image>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
</screenshots>
|
</screenshots>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release type="stable" version="1.5.0" date="2026-07-04T00:00:00Z">
|
||||||
|
<description>
|
||||||
|
<p>This release adds the following features:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Introduced an optional titlebar that display file name and allows drag and double-click to move and toggle maximize state</li>
|
||||||
|
<li>Windows: Now the application will use the system's built-in animation for open window animation</li>
|
||||||
|
</ul>
|
||||||
|
<p>This release fixes the following bug:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Show empty url list should also reset window title to blank</li>
|
||||||
|
</ul>
|
||||||
|
<p>This release includes the following changes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Update translations</li>
|
||||||
|
<li>Add vcs-browser link to AppStream metadata</li>
|
||||||
|
</ul>
|
||||||
|
<p>With contributions from:</p>
|
||||||
|
<p>Heimen Stoffels (vistaus), VenusGirl (VenusGirl)</p>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release type="stable" version="1.4.1" date="2026-04-05T00:00:00Z">
|
||||||
|
<description>
|
||||||
|
<p>This release includes the following changes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Update translations</li>
|
||||||
|
<li>Update Qt version to 6.10.3 for Windows and macOS pre-built binaries</li>
|
||||||
|
</ul>
|
||||||
|
<p>With contributions from:</p>
|
||||||
|
<p>Heimen Stoffels (vistaus), Loc Huynh (hthienloc)</p>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release type="stable" version="1.4.0" date="2026-02-11T00:00:00Z">
|
<release type="stable" version="1.4.0" date="2026-02-11T00:00:00Z">
|
||||||
<description>
|
<description>
|
||||||
<p>This release adds the following features:</p>
|
<p>This release adds the following features:</p>
|
||||||
|
|||||||
Vendored
-118
@@ -1,118 +0,0 @@
|
|||||||
targetApp=ppic.exe
|
|
||||||
friendlyAppName=Pineapple Pictures
|
|
||||||
friendlyAppName[zh_CN]=菠萝看图
|
|
||||||
openCommand="\"{targetAppFullPath}\" \"%1\""
|
|
||||||
|
|
||||||
[ProgId/jpeg]
|
|
||||||
name=JPEG Image
|
|
||||||
name[zh_CN]=JPEG 图像
|
|
||||||
extensions=jpg,jpeg,jfif
|
|
||||||
|
|
||||||
[ProgId/gif]
|
|
||||||
name=GIF Animation Image
|
|
||||||
name[zh_CN]=GIF 动图
|
|
||||||
|
|
||||||
[ProgId/png]
|
|
||||||
name=PNG Image
|
|
||||||
name[zh_CN]=PNG 图像
|
|
||||||
|
|
||||||
[ProgId/svg]
|
|
||||||
name=Scalable Vector Graphics
|
|
||||||
name[zh_CN]=可缩放矢量图像
|
|
||||||
|
|
||||||
[ProgId/webp]
|
|
||||||
name=WebP Image
|
|
||||||
name[zh_CN]=WebP 图像
|
|
||||||
extensions=webp
|
|
||||||
|
|
||||||
[ProgId/avif]
|
|
||||||
name=AV1 Image File
|
|
||||||
name[zh_CN]=AV1 图像文件
|
|
||||||
|
|
||||||
[ProgId/ico]
|
|
||||||
name=Windows Icon Image
|
|
||||||
name[zh_CN]=Windows 图标图像
|
|
||||||
|
|
||||||
[ProgId/icns]
|
|
||||||
name=Apple Icon Image
|
|
||||||
name[zh_CN]=苹果图标图像
|
|
||||||
|
|
||||||
[ProgId/psd]
|
|
||||||
name=Photoshop Document
|
|
||||||
name[zh_CN]=Photoshop 文档
|
|
||||||
extensions=psd,psb,pdd,psdt
|
|
||||||
|
|
||||||
[ProgId/kra]
|
|
||||||
name=Krita Document
|
|
||||||
name[zh_CN]=Krita 文档
|
|
||||||
|
|
||||||
[ProgId/xcf]
|
|
||||||
name=GIMP Document
|
|
||||||
name[zh_CN]=GIMP 文档
|
|
||||||
|
|
||||||
[ProgId/tga]
|
|
||||||
name=TARGA Image
|
|
||||||
name[zh_CN]=TARGA 图像
|
|
||||||
|
|
||||||
[ProgId/tif]
|
|
||||||
name=Tagged Image File Format
|
|
||||||
name[zh_CN]=标签图像文件格式
|
|
||||||
extensions=tif,tiff
|
|
||||||
|
|
||||||
[ProgId/bmp]
|
|
||||||
name=Bitmap
|
|
||||||
name[zh_CN]=位图
|
|
||||||
|
|
||||||
[ProgId/wbmp]
|
|
||||||
name=Wireless Bitmap
|
|
||||||
name[zh_CN]=无线位图
|
|
||||||
|
|
||||||
[ProgId/dds]
|
|
||||||
name=DirectDraw Surface
|
|
||||||
|
|
||||||
[ProgId/hdr]
|
|
||||||
name=High Dynamic Range Image
|
|
||||||
name[zh_CN]=高动态范围图像
|
|
||||||
|
|
||||||
[ProgId/iff]
|
|
||||||
name=Interchange File Format
|
|
||||||
name[zh_CN]=可交换文件格式
|
|
||||||
|
|
||||||
[ProgId/pic]
|
|
||||||
name=Softimage Picture
|
|
||||||
name[zh_CN]=Softimage 图像
|
|
||||||
|
|
||||||
[ProgId/pcx]
|
|
||||||
name=PiCture eXchange PC Paintbrush Image
|
|
||||||
name[zh_CN]=PC Paintbrush 图像
|
|
||||||
|
|
||||||
[ProgId/qoi]
|
|
||||||
name=Quite OK Image
|
|
||||||
name[zh_CN]=挺 OK 图像
|
|
||||||
|
|
||||||
[ProgId/ras]
|
|
||||||
name=Sun Raster Image
|
|
||||||
name[zh_CN]=Sun 栅格图象
|
|
||||||
extensions=ras,sun
|
|
||||||
|
|
||||||
[ProgId/ora]
|
|
||||||
name=OpenRaster Image
|
|
||||||
name[zh_CN]=OpenRaster 图象
|
|
||||||
|
|
||||||
[ProgId/ani]
|
|
||||||
name=Animated Cursor
|
|
||||||
name[zh_CN]=动态光标
|
|
||||||
|
|
||||||
[ProgId/pfm]
|
|
||||||
name=Portable Float Map
|
|
||||||
extensions=pfm,phm
|
|
||||||
|
|
||||||
[ProgId/rgb]
|
|
||||||
name=Silicon Graphics Image
|
|
||||||
extensions=rgb,rgba,bw,sgi
|
|
||||||
|
|
||||||
[ProgId/pxr]
|
|
||||||
name=Pixar Raster Image
|
|
||||||
|
|
||||||
[ProgId/sct]
|
|
||||||
name=Scitex Continuous Tone
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2025 Gary Wang <git@blumia.net>
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
QT += core widgets gui svg svgwidgets
|
|
||||||
|
|
||||||
TARGET = ppic
|
|
||||||
TEMPLATE = app
|
|
||||||
DEFINES += PPIC_VERSION_STRING=\\\"x.y.z\\\"
|
|
||||||
|
|
||||||
win32 {
|
|
||||||
DEFINES += FLAG_PORTABLE_MODE_SUPPORT=1
|
|
||||||
}
|
|
||||||
|
|
||||||
# The following define makes your compiler emit warnings if you use
|
|
||||||
# any feature of Qt which has been marked as deprecated (the exact warnings
|
|
||||||
# depend on your compiler). Please consult the documentation of the
|
|
||||||
# deprecated API in order to know how to port your code away from it.
|
|
||||||
DEFINES += QT_DEPRECATED_WARNINGS
|
|
||||||
|
|
||||||
# You can also make your code fail to compile if you use deprecated APIs.
|
|
||||||
# In order to do so, uncomment the following line.
|
|
||||||
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
|
||||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
|
||||||
|
|
||||||
CONFIG += c++17 lrelease embed_translations
|
|
||||||
|
|
||||||
SOURCES += \
|
|
||||||
app/aboutdialog.cpp \
|
|
||||||
app/main.cpp \
|
|
||||||
app/framelesswindow.cpp \
|
|
||||||
app/mainwindow.cpp \
|
|
||||||
app/graphicsview.cpp \
|
|
||||||
app/bottombuttongroup.cpp \
|
|
||||||
app/graphicsscene.cpp \
|
|
||||||
app/navigatorview.cpp \
|
|
||||||
app/opacityhelper.cpp \
|
|
||||||
app/toolbutton.cpp \
|
|
||||||
app/settings.cpp \
|
|
||||||
app/settingsdialog.cpp \
|
|
||||||
app/metadatamodel.cpp \
|
|
||||||
app/metadatadialog.cpp \
|
|
||||||
app/exiv2wrapper.cpp \
|
|
||||||
app/actionmanager.cpp \
|
|
||||||
app/playlistmanager.cpp \
|
|
||||||
app/shortcutedit.cpp \
|
|
||||||
app/fileopeneventhandler.cpp
|
|
||||||
|
|
||||||
HEADERS += \
|
|
||||||
app/aboutdialog.h \
|
|
||||||
app/framelesswindow.h \
|
|
||||||
app/mainwindow.h \
|
|
||||||
app/graphicsview.h \
|
|
||||||
app/bottombuttongroup.h \
|
|
||||||
app/graphicsscene.h \
|
|
||||||
app/navigatorview.h \
|
|
||||||
app/opacityhelper.h \
|
|
||||||
app/toolbutton.h \
|
|
||||||
app/settings.h \
|
|
||||||
app/settingsdialog.h \
|
|
||||||
app/metadatamodel.h \
|
|
||||||
app/metadatadialog.h \
|
|
||||||
app/exiv2wrapper.h \
|
|
||||||
app/actionmanager.h \
|
|
||||||
app/playlistmanager.h \
|
|
||||||
app/shortcutedit.h \
|
|
||||||
app/fileopeneventhandler.h
|
|
||||||
|
|
||||||
TRANSLATIONS = \
|
|
||||||
app/translations/PineapplePictures_en.ts \
|
|
||||||
app/translations/PineapplePictures_zh_CN.ts \
|
|
||||||
app/translations/PineapplePictures_de.ts \
|
|
||||||
app/translations/PineapplePictures_es.ts \
|
|
||||||
app/translations/PineapplePictures_fr.ts \
|
|
||||||
app/translations/PineapplePictures_nb_NO.ts \
|
|
||||||
app/translations/PineapplePictures_nl.ts \
|
|
||||||
app/translations/PineapplePictures_ru.ts \
|
|
||||||
app/translations/PineapplePictures_si.ts \
|
|
||||||
app/translations/PineapplePictures_id.ts
|
|
||||||
|
|
||||||
# Default rules for deployment.
|
|
||||||
qnx: target.path = /tmp/$${TARGET}/bin
|
|
||||||
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
|
||||||
!isEmpty(target.path): INSTALLS += target
|
|
||||||
|
|
||||||
RESOURCES += \
|
|
||||||
assets/resources.qrc
|
|
||||||
|
|
||||||
# Generate from svg:
|
|
||||||
# magick -density 512x512 -background none app-icon.svg -define icon:auto-resize app-icon.ico
|
|
||||||
RC_ICONS = assets/icons/app-icon.ico
|
|
||||||
|
|
||||||
# Windows only, for rc file (we're not going to use the .rc file in this repo)
|
|
||||||
QMAKE_TARGET_PRODUCT = Pineapple Pictures
|
|
||||||
QMAKE_TARGET_DESCRIPTION = Pineapple Pictures - Image Viewer
|
|
||||||
QMAKE_TARGET_COPYRIGHT = MIT/Expat License - Copyright (C) 2024 Gary Wang
|
|
||||||
|
|
||||||
# MSVC only, since QMake doesn't have a CMAKE_CXX_STANDARD_LIBRARIES or cpp_winlibs similar thing
|
|
||||||
win32-msvc* {
|
|
||||||
LIBS += -luser32
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user