63 Commits

Author SHA1 Message Date
83246f04ae WIP: properties dialog 2020-10-13 00:28:03 +08:00
54c54e2adf fix: styling for some text 2020-10-09 22:09:09 +08:00
6da9c16c4f chore: the way we setup aboutdialog layout 2020-10-08 22:24:54 +08:00
5ace4e219c chore: about dialog strings, also add a thanks tab 2020-10-05 20:43:30 +08:00
c78eb1a272 fix(cpack): update feedback/contact url 2020-09-30 23:54:24 +08:00
7c39668eb6 feat: display git describe version string in app if possible 2020-09-29 09:45:07 +08:00
4c3e370f70 fix: missing translation strings 2020-09-28 21:44:52 +08:00
f7aaa76985 feat: new about dialog 2020-09-28 20:10:33 +08:00
5ff49f4885 fix: drag on image transparent pixels for panning viewport 2020-09-26 10:47:28 +08:00
36c7eb1891 chore: some qmake love 2020-09-26 10:39:36 +08:00
58210691ed chore: rename the desktop file 2020-09-15 12:03:03 +08:00
0eb9886394 fix: appstream data summary language not using english 2020-09-15 11:31:52 +08:00
16ccec0d12 feat: add basic support for AppStream 2020-09-14 18:34:30 +08:00
e2fb1813f1 feat: load gallery for wsl$ location 2020-08-31 20:02:49 +08:00
7245a4e212 docs: add readme for zh_CN language 2020-08-29 10:59:44 +08:00
9f67be61fb fix: cannot load images under WSL2 network location
workaround resolve GH-7
2020-08-28 15:25:07 +08:00
761f5e064c feat(i18n): add zh_CN translation for .desktop file 2020-08-26 19:06:41 +08:00
84e100c174 Update README.md 2020-08-23 23:46:12 +08:00
d64e9cf276 build(ci): deb package for ubuntu 20.04 2020-08-13 19:45:38 +08:00
dc82115e1f fix: don't show what's this button from setting dialog. 2020-08-11 09:08:31 +08:00
3b94eecde2 chore: only enable portable mode support for windows 2020-08-10 23:41:59 +08:00
f3b3ad7b8a Correct a typo in PineapplePictures.pro 2020-08-06 19:40:44 +08:00
1a511ddb02 CI: for windows build, also copy LICENSE file to binary dir 2020-08-04 19:15:10 +08:00
1cb67b48f4 fix: cannot minimize window by clicking taskbar icon 2020-08-04 19:10:02 +08:00
90d0869b5e doc: prepare for repo url change 2020-07-29 13:26:54 +08:00
7004e74165 i18n: update translations for new strings 2020-07-29 13:23:31 +08:00
a2adb0e1d4 feat: simple config dialog 2020-07-29 00:57:43 +08:00
6709c21d70 config file support 2020-07-28 21:29:37 +08:00
31fae2cc8c misc: lower minimum window size limit 2020-07-27 20:47:54 +08:00
20be5e6f4f fix: windows icon size not correct in explorer 2020-07-22 19:02:23 +08:00
6009c2682e ci(windows): build with karchive to support kra and ora formats 2020-07-22 13:46:21 +08:00
92ded932ec feat: add version info datat for windows, and application display name 2020-07-21 20:47:06 +08:00
cd942b4fcc fix: try to fix icon resolution issue under windows 2020-07-21 14:01:27 +08:00
6fc1b2e7c8 Merge pull request #5 from Lovelyblack/master
new icon :)
2020-07-21 13:44:35 +08:00
278988b628 Add files via upload 2020-07-21 13:36:47 +08:00
9b19d13ee2 New Ico 2020-07-21 13:35:57 +08:00
c271fde194 chore: stop using QWheelEvent::delta() 2020-07-12 23:46:00 +08:00
fb24e54579 feat: add prev and next button 2020-07-04 13:49:12 +08:00
3b1af64397 fix(CI): since KDE are using their new GitLab instance 2020-06-18 23:13:39 +08:00
4dbfb2f881 CI: add GitHub Action ubuntu build 2020-06-18 23:06:15 +08:00
fe0b36dc34 feat: add a fullscreen shortcut 2020-05-19 14:08:29 +08:00
1864ff5b7f test: Create macos.yml (#3)
Use GitHub Action to test building under macOS
2020-04-25 23:42:21 +08:00
fb1ad2ba06 misc: cpack info for debian package 2020-04-20 10:57:27 +08:00
e3dfc9c673 feat(ci): build and use kimageformats plugins 2020-03-03 13:51:25 +08:00
eb648e3112 fix: use format() to check if we support such format 2020-03-03 09:53:23 +08:00
5495fa06d3 fix: okay it's jfif... 2020-02-24 19:48:22 +08:00
41e75f10b5 misc: add jiff as a valid image file suffix 2020-02-23 23:04:54 +08:00
6b0e4b6767 fix: should clear gallery when paste image 2020-02-22 17:09:36 +08:00
f49ed645fc fix: use SmoothTransformation when scale factor less than 100% 2020-02-13 19:45:14 +08:00
8fe860e4bf build: requires at least Qt 5.10 for building 2020-01-30 11:35:15 +08:00
2d07290fbe adjust checkerboard alpha channel value 2020-01-27 17:11:24 +08:00
5cbcbfecba Update README.md 2020-01-05 15:36:22 +08:00
03bb25c3ae fix: build under non win32 platform 2020-01-05 15:22:47 +08:00
a41f2af021 i18n: update translate strings 2020-01-05 15:07:08 +08:00
a694c2b037 fix: when copy pixmap, keep the alpha channel 2020-01-05 14:38:39 +08:00
3a6a4cbc68 feat: i18n support again. 2020-01-05 14:30:33 +08:00
a6cfd1a714 feat: basic support for cpack 2020-01-04 00:10:54 +08:00
11ea4c9063 Update README.md 2020-01-03 15:58:15 +08:00
c51a603ec9 feat: support basic image copy and paste 2020-01-01 14:51:46 +08:00
4a5fe4c289 fix: avoid console window under Windows with cmake build 2019-11-24 23:31:45 +08:00
6074a00b33 chore: cmake ci build test 2019-11-24 22:26:01 +08:00
b5dc020795 fix: missing app icon under windows 2019-11-24 14:52:17 +08:00
3f91d8271b chore: migrate to CMake 2019-11-23 20:51:58 +08:00
40 changed files with 2873 additions and 236 deletions

17
.github/workflows/macos.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: macOS CI
on: [push]
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v1
- name: Install Qt
uses: jurplel/install-qt-action@v2.2.1
- name: Run a qt project
run: |
cmake ./
make

24
.github/workflows/ubuntu.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: Ubuntu 20.04 CI
on: [push]
jobs:
build:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v1
- name: Get build dept.
run: sudo apt install cmake qtbase5-dev libqt5svg5-dev qttools5-dev
- name: Build it
run: |
mkdir build
cd build
cmake ../
make
cpack -G DEB
- uses: actions/upload-artifact@v2
with:
name: ubuntu-20.04-deb-package
path: build/*.deb

3
.gitignore vendored
View File

@ -3,3 +3,6 @@
# Translation files
*.qm
# Generic Build Dir
[Bb]uild/

209
CMakeLists.txt Normal file
View File

@ -0,0 +1,209 @@
project (pineapple-pictures)
cmake_minimum_required (VERSION 3.9.5)
include (GNUInstallDirs)
include (FeatureSummary)
set (CMAKE_AUTOMOC ON)
set (CMAKE_AUTORCC ON)
set (QT_MINIMUM_VERSION "5.10")
find_package(Qt5 ${QT_MINIMUM_VERSION} CONFIG REQUIRED Widgets Svg LinguistTools)
set (PPIC_CPP_FILES
main.cpp
mainwindow.cpp
graphicsview.cpp
graphicsscene.cpp
bottombuttongroup.cpp
navigatorview.cpp
opacityhelper.cpp
toolbutton.cpp
settings.cpp
settingsdialog.cpp
aboutdialog.cpp
)
set (PPIC_HEADER_FILES
mainwindow.h
graphicsview.h
graphicsscene.h
bottombuttongroup.h
navigatorview.h
opacityhelper.h
toolbutton.h
settings.h
settingsdialog.h
aboutdialog.h
)
set (PPIC_QRC_FILES
resources.qrc
)
set (PPIC_RC_FILES
# yeah, it's empty.
)
set (EXE_NAME ppic)
# Translation
file (GLOB PPIC_TS_FILES languages/*.ts)
set (PPIC_CPP_FILES_FOR_I18N ${PPIC_CPP_FILES})
qt5_create_translation(PPIC_QM_FILES ${PPIC_CPP_FILES_FOR_I18N} ${PPIC_TS_FILES})
if (WIN32)
list(APPEND PPIC_RC_FILES pineapple-pictures.rc)
endif ()
add_executable (${EXE_NAME}
${PPIC_HEADER_FILES}
${PPIC_CPP_FILES}
${PPIC_QRC_FILES}
${PPIC_RC_FILES}
${PPIC_QM_FILES}
)
target_link_libraries (${EXE_NAME} Qt5::Widgets Qt5::Svg)
# Extra build settings
if (WIN32)
set_property (
TARGET ${EXE_NAME}
PROPERTY WIN32_EXECUTABLE true
)
target_compile_definitions(${EXE_NAME} PRIVATE
FLAG_PORTABLE_MODE_SUPPORT=1
)
endif ()
# Helper macros for parsing and setting project version from `git describe --long` result
macro (ppic_set_version_via_describe _describe_long)
string (
REGEX REPLACE
"^([0-9a-z.]*)-[0-9]+-g[0-9a-f]*$"
"\\1"
_tag_parts
"${_describe_long}"
)
list (GET _tag_parts 0 _matched_tag_version)
if ("${_matched_tag_version}" MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+$")
string (
REGEX REPLACE
"^([0-9]+)\\.([0-9]+)\\.([0-9]+).*$"
"\\1;\\2;\\3"
_ver_parts
"${_matched_tag_version}"
)
list (GET _ver_parts 0 CPACK_PACKAGE_VERSION_MAJOR)
list (GET _ver_parts 1 CPACK_PACKAGE_VERSION_MINOR)
list (GET _ver_parts 2 CPACK_PACKAGE_VERSION_PATCH)
endif ()
endmacro ()
# Version setup
if (EXISTS "${CMAKE_SOURCE_DIR}/.git")
find_package(Git)
set_package_properties(Git PROPERTIES TYPE OPTIONAL PURPOSE "Determine exact build version.")
if (GIT_FOUND)
execute_process (
COMMAND ${GIT_EXECUTABLE} describe --tags --always --long
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE _git_describe_long
)
string (REGEX REPLACE "\n" "" _git_describe_long "${_git_describe_long}")
ppic_set_version_via_describe(${_git_describe_long})
target_compile_definitions(${EXE_NAME} PRIVATE
GIT_DESCRIBE_VERSION_STRING="${_git_describe_long}"
)
endif ()
endif ()
# Helper macros for install settings
macro (ppic_convert_to_relative_path _var)
# Make sure _var is a relative path
if (IS_ABSOLUTE "${${_var}}")
file (RELATIVE_PATH ${_var} "${CMAKE_INSTALL_PREFIX}" "${${_var}}")
endif ()
endmacro ()
# Install settings
if (WIN32)
# FIXME: try to avoid install to a "bin" subfolder under windows...
# when fixed, don't forget to update the CI config file...
set (BIN_INSTALL_DIR "") # seems useless, don't know why...
elseif (UNIX)
if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX /usr)
endif ()
set (BIN_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}") # relative, usually "bin"
ppic_convert_to_relative_path(BIN_INSTALL_DIR)
set (LIB_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}") # "lib" or "lib64"
ppic_convert_to_relative_path(LIB_INSTALL_DIR)
# install icon
install (
FILES icons/app-icon.svg
DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps"
RENAME pineapple-pictures.svg
)
# install shortcut
install (
FILES dist/net.blumia.pineapple-pictures.desktop
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications"
)
# install app metadata file for appstream (and some other stuff using this metadata like snapcraft)
install (
FILES dist/appstream/net.blumia.pineapple-pictures.appdata.xml
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo"
)
endif()
set (INSTALL_TARGETS_DEFAULT_ARGS
RUNTIME DESTINATION ${BIN_INSTALL_DIR}
LIBRARY DESTINATION ${LIB_INSTALL_DIR}
ARCHIVE DESTINATION ${LIB_INSTALL_DIR} COMPONENT Devel
)
install (
TARGETS ${EXE_NAME}
${INSTALL_TARGETS_DEFAULT_ARGS}
)
if (WIN32)
set (QM_FILE_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}/translations")
else ()
set (QM_FILE_INSTALL_DIR "${CMAKE_INSTALL_FULL_DATADIR}/pineapple-pictures/translations")
target_compile_definitions(${EXE_NAME}
PRIVATE QM_FILE_INSTALL_DIR=${QM_FILE_INSTALL_DIR}
)
endif ()
install (
FILES ${PPIC_QM_FILES}
DESTINATION ${QM_FILE_INSTALL_DIR}
)
# CPACK: General Settings
set (CPACK_GENERATOR "TBZ2")
set (CPACK_PACKAGE_NAME "pineapple-pictures")
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Yet another image viewer")
set (CPACK_PACKAGE_VENDOR "Gary Wang")
set (CPACK_PACKAGE_CONTACT "https://github.com/BLumia/pineapple-pictures/issues/")
if (WIN32)
# ...
elseif (APPLE)
# ...
elseif (UNIX)
set (CPACK_SYSTEM_NAME "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
set (CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5svg5")
set (CPACK_DEBIAN_PACKAGE_RECOMMENDS "kimageformat-plugins")
endif()
include(CPack)

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019 BLumia
Copyright (c) 2020 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

View File

@ -1,11 +1,66 @@
Yet another image viewer.
## Uncleaned shits inside(TM):
|CI|Build Status|
|---|---|
|Windows Build|[![Windows build status](https://ci.appveyor.com/api/projects/status/dbd8clww3cit6oa0/branch/master?svg=true)](https://ci.appveyor.com/project/BLumia/pineapplepictures/branch/master)|
|macOS Build|![macOS CI](https://github.com/BLumia/pineapple-pictures/workflows/macOS%20CI/badge.svg)|
|Ubuntu 20.04 Build|![Ubuntu 20.04 CI](https://github.com/BLumia/pineapple-pictures/workflows/Ubuntu%2020.04%20CI/badge.svg)|
- Mixed `CR LF` and `LF`.
- Ugly action icons.
- For windows build, win32 APIs are used.
- No drag-window-border-to-resize support under non-windows platforms (I use <kbd>Meta+Drag</kbd> to resize window under my x11 desktop).
![Pineapple Pictures - Main Window](https://repository-images.githubusercontent.com/211888654/21fb6300-269f-11ea-8e85-953e5d57da44)
## Get it!
- [GitHub Release Page](https://github.com/BLumia/pineapple-pictures/releases)
- Archlinux AUR: [pineapple-pictures-git](https://aur.archlinux.org/packages/pineapple-pictures-git/)
## Build it manually:
Current state, we need:
- `cmake`: as the build system.
- `qt5` with `qt5-svg` and `qt5-tools`: since the app is using Qt.
Then we can build it with any proper c++ compiler like g++ or msvc.
Building it just requires normal cmake building steps:
``` bash
$ mkdir build && cd build
$ cmake ..
$ cmake --build . # or simply using `make` if you are using Makefile as the cmake generator.
```
After that, a `ppic` executable file will be available to use. You can also optionally install it by using the target `install` (or simply `make install` in case you are using Makefile). After the build process, you can also use `cpack` to make a package.
Image formats supports rely on Qt's imageformats plugins, just get the plugins you need from your distro's package manager will be fine. For Windows user, you may need build and install the imageformats plugin manually, read the content below.
### Linux
Just normal build process as other program will be fine. Nothing special ;)
For Archlinux there are also a [PKGBUILD](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=pineapple-pictures-git) you can use.
For packaging to debian-based distro, the `CMakeLists.txt` provides some cpack configurations for generating a `.deb` package. After the build process, use `cpack -G DEB` to generate the package. You can also take `.github/workflows/ubuntu.yml` as a reference.
For this project, `DEB` is the only supported cpack generator in current state, feel free to submit a PR if you like improving `cpack` support for this project.
### Windows
The normal build steps for Linux is also applied to Windows, but since Windows doesn't have a decent package manager, so if you need any other image formats support other than the supported formats which Qt provided, you need to get and build these imageformats plugins manually and vendor it. It's optional and can be skipped if you don't need extra image formats support.
For the Windows binary I provided, kimageformats plugin is used (for formats like kra, xcf, psd and etc.). You can take `appveyor.yml` as a reference to learn what I did when building the Windows binary.
[KDE Craft](https://community.kde.org/Craft) environment also can be used to build and package this program. I did also created a blueprint for building this project, but since I don't have a CI to run KDE Craft build, the blueprint repo are not provided here. Maybe sometimes later.
### macOS
I don't have a mac, so no support at all. There is also a GitHub Action (see `.github/workflows/macos.yml`) running macOS build though so at least it can build. Feel free to submit a PR if you would like to give some love to the macOS build ;P
## Help Translation!
[Translate this project on Transifex!](https://www.transifex.com/blumia/pineapple-pictures/)
Feel free to open up an issue to request a new language to translate.
## License

67
README.zh_CN.md Normal file
View File

@ -0,0 +1,67 @@
简单轻量的跨平台看图工具。
|CI|构建状态|
|---|---|
|Windows Build|[![Windows build status](https://ci.appveyor.com/api/projects/status/dbd8clww3cit6oa0/branch/master?svg=true)](https://ci.appveyor.com/project/BLumia/pineapplepictures/branch/master)|
|macOS Build|![macOS CI](https://github.com/BLumia/pineapple-pictures/workflows/macOS%20CI/badge.svg)|
|Ubuntu 20.04 Build|![Ubuntu 20.04 CI](https://github.com/BLumia/pineapple-pictures/workflows/Ubuntu%2020.04%20CI/badge.svg)|
![Pineapple Pictures - Main Window](https://repository-images.githubusercontent.com/211888654/21fb6300-269f-11ea-8e85-953e5d57da44)
## 立即获取!
- [GitHub Release 页面](https://github.com/BLumia/pineapple-pictures/releases) | [gitee 发布页面](https://gitee.com/blumia/pineapple-pictures/releases)
- Archlinux AUR: [pineapple-pictures-git](https://aur.archlinux.org/packages/pineapple-pictures-git/)
## 手动构建步骤:
当前状态,我们需要先确保如下依赖可用:
- `cmake`: 我们所使用的构建系统
- 包含 `qt5-svg``qt5-tools` 组件的 `qt5`: 此应用基于 Qt
然后我们就可以使用任何常规的 c++ 编译器如 g++ 或 msvc 来进行构建了
构建过程就是常规的 CMake 应用构建过程:
``` bash
$ mkdir build && cd build
$ cmake ..
$ cmake --build . # 如果你使用 Makefile 作为 CMake 生成器,也可以直接简单的使用 `make`
```
完毕后,一个名为 `ppic` 的可执行程序即会被生成以供使用。您也可以选择通过使用 CMake 生成的 `install` 目标继续将其安装到您的设备上(假设您使用 Makefile即可执行 `make install` 来进行安装)。构建步骤完毕后,您也可以使用 `cpack` 来对应用程序进行打包。
此应用的图片格式支持依赖于 Qt 的 imageformats 插件,直接从您所用的发行版获取对应的图像格式插件即可。对于 Windows 用户,您可能需要手动构建和使用图像格式插件。下方给出了进一步的说明。
### Linux
常规的构建步骤即可完成构建,不需要额外的处理步骤 ;)
对于 Archlinux 发行版的用户,这里还有一个 [PKGBUILD](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=pineapple-pictures-git) 可供使用和参考。
对于在基于 debian 的发行版中进行打包的需求, `CMakeLists.txt` 已经提供了一些基本的 cpack 配置以便生成一个有效的 `.deb` 软件包。在构建步骤完毕后,使用 `cpack -G DEB` 即可生成 DEB 软件包。您也可以参考 `.github/workflows/ubuntu.yml` 来查看当前正在使用的 CI 配置是如何进行打包的。
目前,`DEB` 是当前唯一受到直接支持的 cpack 生成目标。若希望为此项目添加其它的 cpack 目标支持,欢迎发起合并请求。
### Windows
上述的构建步骤在 Windows 中也适用,但由于 Windows 中不具备类如大多 Linux 发行版中所提供的方便的软件包管理机制,故如果您需要任何 Qt 官方支持之外的图像格式例如 psdxcfkra 等格式的支持,你就可能需要自行获取并构建对应的 imageformats 插件,并在您最终生成的可执行文件中一并提供这些插件。若您不需要这些额外的图像格式支持,这个步骤也可以直接跳过。
我们所提供的预编译好的 Windows 程序包含了 kimageformats 插件来提供额外kra, xcf, psd 等)格式的支持。您可以参考 `appveyor.yml` 来查看我们是如何构建并打包 Windows 可执行程序的。
[KDE Craft](https://community.kde.org/Craft) 环境也可以被用来构建此应用程序。我也创建了一个蓝图来进行此项目的构建和打包。但由于暂时并未配置 CI 部署此环境来进行 KDE Craft 环境下的构建,故对应的蓝图仓库也尚未公开提供,或许后续会开放出来。
### macOS
由于我没有 mac 设备,故 macOS 暂时不受任何支持。不过我们目前有一个 GitHub Action 来执行 macOS 环境下的构建(见 `.github/workflows/macos.yml`)所以至少 macOS 下是可以顺利进行构建的。如果您想完善对 macOS 的支持,也欢迎您创建合并请求 ;P
## 帮助翻译!
[在 Transifex 上帮助此项目翻译到更多语言!](https://www.transifex.com/blumia/pineapple-pictures/)
若 Transifex 上没有您所希望进行翻译的语言,请通过 Issue 的形式告诉我。
## 许可协议
此项目使用 MIT 协议进行发布。

157
aboutdialog.cpp Normal file
View File

@ -0,0 +1,157 @@
#include "aboutdialog.h"
#include <QAbstractButton>
#include <QDialogButtonBox>
#include <QTextBrowser>
#include <QTabWidget>
#include <QVBoxLayout>
#include <QApplication>
#include <QLabel>
#include <QPushButton>
AboutDialog::AboutDialog(QWidget *parent)
: QDialog(parent)
, 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"));
QStringList helpStr {
QStringLiteral("<p>%1</p>").arg(tr("Launch application with image file path as argument to load the file.")),
QStringLiteral("<p>%1</p>").arg(tr("Drag and drop image file onto the window is also supported.")),
QStringLiteral("<p>%1</p>").arg(tr("Context menu option explanation:")),
QStringLiteral("<ul>"),
QStringLiteral("<li><b>%1</b>:<br/>%2</li>").arg(
QCoreApplication::translate("MainWindow", "Stay on top"),
this->tr("Make window stay on top of all other windows.")
),
QStringLiteral("<li><b>%1</b>:<br/>%2</li>").arg(
QCoreApplication::translate("MainWindow", "Protected mode"),
this->tr("Avoid close window accidentally. (eg. by double clicking the window)")
),
QStringLiteral("</ul>")
};
QStringList aboutStr {
QStringLiteral("<center><img width='128' height='128' src=':/icons/app-icon.svg'/><br/>"),
qApp->applicationDisplayName(),
#ifdef GIT_DESCRIBE_VERSION_STRING
(QStringLiteral("<br/>") + tr("Version: %1").arg(GIT_DESCRIBE_VERSION_STRING)),
#endif // GIT_DESCRIBE_VERSION_STRING
QStringLiteral("<hr/>"),
tr("Copyright (c) 2020 %1").arg(QStringLiteral("<a href='https://github.com/BLumia'>@BLumia</a>")),
QStringLiteral("<br/>"),
tr("Logo designed by %1").arg(QStringLiteral("<a href='https://github.com/Lovelyblack'>@Lovelyblack</a>")),
QStringLiteral("<hr/>"),
tr("Built with Qt %1 (%2)").arg(QT_VERSION_STR, QSysInfo::buildCpuArchitecture()),
QStringLiteral("<br/><a href='%1'>%2</a>").arg("https://github.com/BLumia/pineapple-pictures", tr("Source code")),
QStringLiteral("</center>")
};
QStringList specialThanksStr {
QStringLiteral("<h1 align='center'>%1</h1><a href='%2'>%3</a><p>%4</p>").arg(
tr("Contributors"),
QStringLiteral("https://github.com/BLumia/pineapple-pictures/graphs/contributors"),
tr("List of contributors on GitHub"),
tr("Thanks to all people who contributed to this project.")
),
#if 0
QStringLiteral("<h1 align='center'>%1</h1><p>%2</p>").arg(
tr("Translators"),
tr("I would like to thank the following people who volunteered to translate this application.")
),
#endif
};
QStringList licenseStr {
QStringLiteral("<h1 align='center'><b>%1</b></h1>").arg(tr("Your Rights")),
QStringLiteral("<p>%1</p><p>%2</p><ul><li>%3</li><li>%4</li><li>%5</li><li>%6</li></ul>").arg(
tr("%1 is released under the MIT License."), // %1
tr("This license grants people a number of freedoms:"), // %2
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(QStringLiteral("<i>%1</i>")),
QStringLiteral("<p>%1</p>").arg(tr("The MIT license guarantees you this freedom. Nobody is ever permitted to take it away.")),
QStringLiteral("<hr/><pre>%2</pre>")
};
QString mitLicense(QStringLiteral(R"(Expat/MIT License
Copyright (c) 2020 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.
)"));
QStringList thirdPartyLibsStr {
QStringLiteral("<h1 align='center'><b>%1</b></h1>").arg(tr("Third-party Libraries used by %1")),
tr("%1 is built on the following free software libraries:"),
QStringLiteral("<ul>"),
QStringLiteral("<li><a href='%1'>%2</a>: %3</li>").arg("https://www.qt.io/", "Qt", "GPLv2 + GPLv3 + LGPLv2.1 + LGPLv3"),
QStringLiteral("</ul>")
};
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(QStringLiteral("<i>%1</i>")).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();
});
setLayout(new QVBoxLayout);
layout()->addWidget(m_tabWidget);
layout()->addWidget(m_buttonBox);
setMinimumSize(361, 161); // not sure why it complain "Unable to set geometry"
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
}
AboutDialog::~AboutDialog()
{
}
QSize AboutDialog::sizeHint() const
{
return QSize(520, 350);
}

32
aboutdialog.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef ABOUTDIALOG_H
#define ABOUTDIALOG_H
#include <QDialog>
QT_BEGIN_NAMESPACE
class QTextBrowser;
class QTabWidget;
class QDialogButtonBox;
QT_END_NAMESPACE
class AboutDialog : public QDialog
{
Q_OBJECT
public:
explicit AboutDialog(QWidget *parent = nullptr);
~AboutDialog() override;
QSize sizeHint() const override;
private:
QTabWidget * m_tabWidget = nullptr;
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

View File

@ -1,25 +1,69 @@
environment:
CMAKE_INSTALL_ROOT: C:\projects\cmake
ZLIB_ROOT: C:\projects\zlib
matrix:
- build_name: mingw73_32_qt5_12_5
QTPATH: C:\Qt\5.12.5\mingw73_32
- build_name: mingw73_32_qt5_12_6
QTPATH: C:\Qt\5.12.6\mingw73_32
MINGW32: C:\Qt\Tools\mingw730_32
# - build_name: msvc2017_64
# QTPATH: C:\Qt\5.11.2\msvc2017_64
# MINGW32: C:\Qt\Tools\mingw530_32
install:
- mkdir %CMAKE_INSTALL_ROOT%
- mkdir %ZLIB_ROOT%
- cd %APPVEYOR_BUILD_FOLDER%
- git submodule update --init --recursive
- set PATH=%PATH%;%QTPATH%\bin;%MINGW32%\bin
- set PATH=%PATH%;%CMAKE_INSTALL_ROOT%;%QTPATH%\bin;%MINGW32%\bin
build_script:
# prepare
- mkdir 3rdparty
- cinst ninja
# install ECM so we can build KImageFormats
- cd 3rdparty
- git clone -q https://invent.kde.org/frameworks/extra-cmake-modules.git
- cd extra-cmake-modules
- cmake -G "Ninja" . -DCMAKE_INSTALL_PREFIX=%CMAKE_INSTALL_ROOT%
- cmake --build .
- cmake --build . --target install
- cd %APPVEYOR_BUILD_FOLDER%
# download and install zlib for KArchive
- cd %ZLIB_ROOT%
- curl -fsS -o zlib128-dll.zip http://zlib.net/zlib128-dll.zip
- 7z e zlib128-dll.zip
- cd %APPVEYOR_BUILD_FOLDER%
# install KArchive for kra format support of KImageFormats
- cd 3rdparty
- git clone -q https://invent.kde.org/frameworks/karchive.git
- cd karchive
- mkdir build
- cd build
- qmake ..\PineapplePictures.pro
- cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=%CMAKE_INSTALL_ROOT% -DCMAKE_CXX_FLAGS_RELEASE="-s" -DCMAKE_MAKE_PROGRAM=mingw32-make -DZLIB_ROOT=%ZLIB_ROOT%
# -DCMAKE_PREFIX_PATH=%CMAKE_INSTALL_ROOT%
- cmake --build . --config Release
- cmake --build . --config Release --target install
- cd %APPVEYOR_BUILD_FOLDER%
# install KImageFormats
- cd 3rdparty
- git clone -q https://invent.kde.org/frameworks/kimageformats.git
- cd kimageformats
- mkdir build
- cd build
- cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS_RELEASE="-s" -DCMAKE_MAKE_PROGRAM=mingw32-make -DQT_PLUGIN_INSTALL_DIR=%QTPATH%\plugins
- cmake --build . --config Release
- cmake --build . --config Release --target install
- cd %APPVEYOR_BUILD_FOLDER%
# finally...
- mkdir build
- cd build
- cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_MAKE_PROGRAM=mingw32-make -DCMAKE_INSTALL_PREFIX='%cd%'
- mingw32-make
- cd release
- del /a /f /q "*.o" "*.cpp" "*.h"
- windeployqt --no-quick-import --no-translations --no-opengl-sw --no-angle --no-system-d3d-compiler --release .\PineapplePictures.exe
- mingw32-make install
# fixme: I don't know how to NOT make the binary installed to the ./bin/ folder...
- cd bin
- copy %APPVEYOR_BUILD_FOLDER%\LICENSE .
- copy C:\projects\cmake\bin\libKF5Archive.dll .
- windeployqt --verbose=2 --no-quick-import --no-translations --no-opengl-sw --no-angle --no-system-d3d-compiler --release .\ppic.exe
# for debug..
- tree /f
artifacts:
- path: build\release
- path: build\bin

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<component type="desktop-application">
<id>net.blumia.pineapple-pictures</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0+</project_license>
<name>Pineapple Pictures</name>
<name xml:lang="zh-CN">菠萝看图</name>
<summary>Image Viewer</summary>
<summary xml:lang="zh-CN">图像查看器</summary>
<description>
<p>Pineapple Pictures is a lightweight and easy to use image viewer, comes with a handy navigation thumbnail when zoom-in, and doesn't contains any image management support.</p>
<p xml:lang="zh-CN">菠萝看图是一个轻量级易用的图像查看器,在图片放大时提供了方便的鸟瞰导航功能,且不包含任何图片管理功能。</p>
</description>
<url type="homepage">https://github.com/BLumia/pineapple-pictures</url>
<url type="bugtracker">https://github.com/BLumia/pineapple-pictures/issues</url>
<provides>
<binary>ppic</binary>
</provides>
</component>

View File

@ -0,0 +1,15 @@
[Desktop Entry]
Categories=Graphics;
Comment=Pineapple Pictures is a lightweight image viewer
Comment[zh_CN]=菠萝看图是一个轻量级的图像查看器
Exec=ppic %F
GenericName=Image Viewer
GenericName[zh_CN]=图像查看器
Icon=pineapple-pictures
Keywords=Picture;Image;Viewer;Jpg;Jpeg;Png;
MimeType=image/bmp;image/bmp24;image/jpg;image/jpe;image/jpeg;image/jpeg24;image/jng;image/pcd;image/pcx;image/png;image/tif;image/tiff;image/tiff24;image/dds;image/gif;image/sgi;image/j2k;image/jp2;image/pct;image/wdp;image/arw;image/icb;image/dng;image/vda;image/vst;image/svg;image/ptif;image/mef;image/xbm;image/svg+xml;
Name=Pineapple Pictures
Name[zh_CN]=菠萝看图
StartupNotify=false
Type=Application
Terminal=false

View File

@ -8,6 +8,7 @@
#include <QGraphicsSvgItem>
#include <QMovie>
#include <QLabel>
#include <QPainter>
GraphicsScene::GraphicsScene(QObject *parent)
: QGraphicsScene(parent)
@ -23,7 +24,9 @@ GraphicsScene::~GraphicsScene()
void GraphicsScene::showImage(const QPixmap &pixmap)
{
this->clear();
m_theThing = this->addPixmap(pixmap);
QGraphicsPixmapItem * pixmapItem = this->addPixmap(pixmap);
pixmapItem->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
m_theThing = pixmapItem;
this->setSceneRect(m_theThing->boundingRect());
}
@ -58,3 +61,24 @@ void GraphicsScene::showGif(const QString &filepath)
QPen(Qt::transparent));
this->setSceneRect(m_theThing->boundingRect());
}
bool GraphicsScene::trySetTransformationMode(Qt::TransformationMode mode)
{
QGraphicsPixmapItem * pixmapItem = qgraphicsitem_cast<QGraphicsPixmapItem *>(m_theThing);
if (pixmapItem) {
pixmapItem->setTransformationMode(mode);
return true;
}
return false;
}
QPixmap GraphicsScene::renderToPixmap()
{
QPixmap pixmap(sceneRect().toRect().size());
pixmap.fill(Qt::transparent);
QPainter p(&pixmap);
render(&p, sceneRect());
return pixmap;
}

View File

@ -15,6 +15,10 @@ public:
void showSvg(const QString &filepath);
void showGif(const QString &filepath);
bool trySetTransformationMode(Qt::TransformationMode mode);
QPixmap renderToPixmap();
private:
QGraphicsItem * m_theThing;
};

View File

@ -31,6 +31,13 @@ void GraphicsView::showFileFromUrl(const QUrl &url, bool doRequestGallery)
QString filePath(url.toLocalFile());
#ifdef Q_OS_WIN
// TODO: remove this workaround when M$ change the "wsl$" hostname.
if (Q_UNLIKELY(url.scheme() == QStringLiteral("qtbug-86277"))) {
filePath = url.path();
}
#endif // Q_OS_WIN
if (filePath.endsWith(".svg")) {
showSvg(filePath);
} else if (filePath.endsWith(".gif")) {
@ -39,8 +46,10 @@ void GraphicsView::showFileFromUrl(const QUrl &url, bool doRequestGallery)
QImageReader imageReader(filePath);
imageReader.setAutoTransform(true);
imageReader.setDecideFormatFromContent(true);
QImage::Format imageFormat = imageReader.imageFormat();
if (imageFormat == QImage::Format_Invalid) {
// Since if the image format / plugin does not support this feature, imageFormat() will returns an invalid format.
// So we cannot use imageFormat() and check if it returns QImage::Format_Invalid to detect if we support the file.
// QImage::Format imageFormat = imageReader.imageFormat();
if (imageReader.format().isEmpty()) {
showText(tr("File is not a valid image"));
} else {
showImage(QPixmap::fromImageReader(&imageReader));
@ -59,6 +68,13 @@ void GraphicsView::showImage(const QPixmap &pixmap)
checkAndDoFitInView();
}
void GraphicsView::showImage(const QImage &image)
{
resetTransform();
scene()->showImage(QPixmap::fromImage(image));
checkAndDoFitInView();
}
void GraphicsView::showText(const QString &text)
{
resetTransform();
@ -110,6 +126,7 @@ void GraphicsView::zoomView(qreal scaleFactor)
{
m_enableFitInView = false;
scale(scaleFactor, scaleFactor);
applyTransformationModeByScaleFactor();
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), m_rotateAngle);
}
@ -129,14 +146,19 @@ void GraphicsView::rotateView(qreal rotateAngel)
void GraphicsView::fitInView(const QRectF &rect, Qt::AspectRatioMode aspectRadioMode)
{
QGraphicsView::fitInView(rect, aspectRadioMode);
applyTransformationModeByScaleFactor();
}
void GraphicsView::checkAndDoFitInView()
void GraphicsView::checkAndDoFitInView(bool markItOnAnyway)
{
if (!isThingSmallerThanWindowWith(transform())) {
m_enableFitInView = true;
fitInView(sceneRect(), Qt::KeepAspectRatio);
}
if (markItOnAnyway) {
m_enableFitInView = true;
}
}
void GraphicsView::toggleCheckerboard()
@ -206,9 +228,9 @@ void GraphicsView::dragEnterEvent(QDragEnterEvent *event)
} else {
event->ignore();
}
qDebug() << event->mimeData() << "Drag Enter Event"
<< event->mimeData()->hasUrls() << event->mimeData()->hasImage()
<< event->mimeData()->formats() << event->mimeData()->hasFormat("text/uri-list");
// qDebug() << event->mimeData() << "Drag Enter Event"
// << event->mimeData()->hasUrls() << event->mimeData()->hasImage()
// << event->mimeData()->formats() << event->mimeData()->hasFormat("text/uri-list");
return QGraphicsView::dragEnterEvent(event);
}
@ -231,7 +253,7 @@ void GraphicsView::dropEvent(QDropEvent *event)
if (urls.isEmpty()) {
showText(tr("File url list is empty"));
} else {
showFileFromUrl(urls.first());
showFileFromUrl(urls.first(), true);
}
} else if (mimeData->hasImage()) {
QImage img = qvariant_cast<QImage>(mimeData->imageData());
@ -278,9 +300,9 @@ void GraphicsView::setCheckerboardEnabled(bool enabled)
if (m_checkerboardEnabled) {
// Prepare background check-board pattern
QPixmap tilePixmap(0x20, 0x20);
tilePixmap.fill(QColor(35, 35, 35, 110));
tilePixmap.fill(QColor(35, 35, 35, 170));
QPainter tilePainter(&tilePixmap);
QColor color(40, 40, 40, 110);
QColor color(45, 45, 45, 170);
tilePainter.fillRect(0, 0, 0x10, 0x10, color);
tilePainter.fillRect(0x10, 0x10, 0x10, 0x10, color);
tilePainter.end();
@ -291,6 +313,15 @@ void GraphicsView::setCheckerboardEnabled(bool enabled)
}
}
void GraphicsView::applyTransformationModeByScaleFactor()
{
if (this->scaleFactor() < 1) {
scene()->trySetTransformationMode(Qt::SmoothTransformation);
} else {
scene()->trySetTransformationMode(Qt::FastTransformation);
}
}
void GraphicsView::resetWithScaleAndRotate(qreal scaleFactor, qreal rotateAngle)
{
QGraphicsView::resetTransform();

View File

@ -14,6 +14,7 @@ public:
void showFileFromUrl(const QUrl &url, bool requestGallery = false);
void showImage(const QPixmap &pixmap);
void showImage(const QImage &image);
void showText(const QString &text);
void showSvg(const QString &filepath);
void showGif(const QString &filepath);
@ -29,7 +30,7 @@ public:
void rotateView(qreal rotateAngel);
void fitInView(const QRectF &rect, Qt::AspectRatioMode aspectRadioMode = Qt::IgnoreAspectRatio);
void checkAndDoFitInView();
void checkAndDoFitInView(bool markItOnAnyway = true);
signals:
void navigatorViewRequired(bool required, qreal angle);
@ -53,6 +54,7 @@ private:
bool isThingSmallerThanWindowWith(const QTransform &transform) const;
bool shouldIgnoreMousePressMoveEvent(const QMouseEvent *event) const;
void setCheckerboardEnabled(bool enabled);
void applyTransformationModeByScaleFactor();
void resetWithScaleAndRotate(qreal scaleFactor, qreal rotateAngle);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 212 KiB

View File

@ -1,102 +1,469 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32"
height="32"
viewBox="0 0 8.4666665 8.4666669"
version="1.1"
id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="view-fullscreen - 副本.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#000000"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.69803922"
inkscape:pageshadow="2"
inkscape:zoom="22.4"
inkscape:cx="11.283707"
inkscape:cy="14.931415"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:pagecheckerboard="true"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-288.5333)">
<path
style="fill:none;stroke:#ffffff;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 1.6393869,293.16031 1.615848,290.14918 H 3.6380209"
id="path837"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<path
style="fill:none;stroke:#ffffff;stroke-width:0.26531255px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 6.9097992,291.96816 0.014252,-1.85441 -3.2860303,0.0354"
id="path837-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<path
style="fill:none;stroke:#ffffff;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 6.9097992,291.96816 0.035006,3.10579 H 4.9226322"
id="path837-9"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<path
style="fill:none;stroke:#ffffff;stroke-width:0.26531255px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 1.6393869,293.16031 -0.00244,1.90166 3.2856855,0.012"
id="path837-6-4"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<path
style="fill:none;stroke:#ffffff;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 1.6418341,292.9131 0.8740699,-0.51972 c 0,0 0.4960938,-0.29529 0.8622581,-0.11812 0.3661646,0.17718 0.094494,0.0236 0.3661646,0.17718 0.2716702,0.15355 0.2716702,0.16537 0.2716702,0.16537 l 0.5669645,0.60239 c 0,0 0.9921875,1.16937 1.1339284,1.37017 l 0.1299292,0.21261 0.1417411,0.30711"
id="path819"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccscc" />
<path
style="fill:none;stroke:#ffffff;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 5.1144902,293.84623 0.4488469,-0.62603 c 0,0 0.3071055,-0.22442 0.5669642,-0.2008 l 0.2598587,0.0236 0.3425409,0.12993 v 0 l 0.2480469,0.14174"
id="path821"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
<ellipse
style="fill:#ffffff;fill-opacity:0.87058824;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path823"
cx="5.0849609"
cy="291.8028"
rx="0.86816406"
ry="0.8976934" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW 2018 (评估版) -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="86.6986mm" height="86.6986mm" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 8669.86 8669.86"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.str0 {stroke:#332C2B;stroke-width:20;stroke-miterlimit:22.9256}
.fil124 {fill:#FEFEFE}
.fil118 {fill:#F6CAA9}
.fil117 {fill:#F7CAA8}
.fil116 {fill:#F6CAA8}
.fil115 {fill:#F6CAA7}
.fil114 {fill:#F6C9A7}
.fil113 {fill:#F6C9A6}
.fil112 {fill:#F6C8A5}
.fil111 {fill:#F6C7A4}
.fil110 {fill:#F6C7A3}
.fil109 {fill:#F6C6A3}
.fil108 {fill:#F6C6A2}
.fil107 {fill:#F5C6A2}
.fil106 {fill:#F5C5A2}
.fil105 {fill:#F5C5A1}
.fil104 {fill:#F5C5A1}
.fil103 {fill:#F5C5A0}
.fil102 {fill:#F5C49F}
.fil101 {fill:#F5C49E}
.fil100 {fill:#F5C39E}
.fil99 {fill:#F5C39D}
.fil98 {fill:#F5C29D}
.fil97 {fill:#F5C29C}
.fil96 {fill:#F5C19B}
.fil95 {fill:#F5C19B}
.fil94 {fill:#F5C19A}
.fil93 {fill:#F5C19A}
.fil92 {fill:#F5C099}
.fil91 {fill:#F5C098}
.fil90 {fill:#F4BF98}
.fil89 {fill:#F4BF97}
.fil88 {fill:#F4BE97}
.fil87 {fill:#F4BE96}
.fil1 {fill:#F4BD95}
.fil2 {fill:#F4BD95}
.fil3 {fill:#F4BD94}
.fil4 {fill:#F4BD94}
.fil5 {fill:#F4BC93}
.fil6 {fill:#F4BC93}
.fil7 {fill:#F4BB92}
.fil8 {fill:#F4BB91}
.fil9 {fill:#F4BA90}
.fil10 {fill:#F4B98F}
.fil11 {fill:#F4B98F}
.fil12 {fill:#F4B98E}
.fil13 {fill:#F3B88E}
.fil14 {fill:#F3B88D}
.fil15 {fill:#F3B78C}
.fil16 {fill:#F3B78B}
.fil17 {fill:#F3B68B}
.fil18 {fill:#F3B68A}
.fil19 {fill:#F3B589}
.fil20 {fill:#F2B488}
.fil21 {fill:#F2B487}
.fil22 {fill:#F2B386}
.fil23 {fill:#F2B385}
.fil24 {fill:#F2B285}
.fil25 {fill:#F2B284}
.fil26 {fill:#F2B183}
.fil27 {fill:#F2B082}
.fil28 {fill:#F2B081}
.fil29 {fill:#F2B081}
.fil30 {fill:#F2B080}
.fil31 {fill:#F2AF7F}
.fil32 {fill:#F2AE7F}
.fil33 {fill:#F2AE7E}
.fil34 {fill:#F2AD7D}
.fil35 {fill:#F2AD7D}
.fil36 {fill:#F1AC7D}
.fil37 {fill:#F1AC7C}
.fil38 {fill:#F1AC7C}
.fil39 {fill:#F1AC7B}
.fil40 {fill:#F1AB7B}
.fil41 {fill:#F1AA7A}
.fil42 {fill:#F0AA79}
.fil43 {fill:#F0AA78}
.fil44 {fill:#F0A978}
.fil45 {fill:#F0A977}
.fil46 {fill:#F0A877}
.fil47 {fill:#F0A876}
.fil48 {fill:#F0A776}
.fil49 {fill:#F0A775}
.fil50 {fill:#F0A674}
.fil51 {fill:#F0A674}
.fil52 {fill:#F0A673}
.fil53 {fill:#F0A673}
.fil54 {fill:#F0A572}
.fil55 {fill:#F0A572}
.fil56 {fill:#F0A471}
.fil57 {fill:#F0A470}
.fil58 {fill:#F0A370}
.fil59 {fill:#F0A36F}
.fil60 {fill:#F0A36F}
.fil61 {fill:#F0A26E}
.fil62 {fill:#EFA26E}
.fil63 {fill:#EFA26D}
.fil64 {fill:#EFA16C}
.fil65 {fill:#EFA06B}
.fil66 {fill:#EFA06B}
.fil67 {fill:#EF9F6A}
.fil68 {fill:#EF9F6A}
.fil69 {fill:#EF9E69}
.fil70 {fill:#EF9E69}
.fil71 {fill:#EF9E68}
.fil72 {fill:#EE9D68}
.fil73 {fill:#EE9D67}
.fil74 {fill:#EE9D67}
.fil75 {fill:#EE9C66}
.fil76 {fill:#EE9C65}
.fil77 {fill:#EE9B65}
.fil78 {fill:#EE9B64}
.fil79 {fill:#EE9A63}
.fil80 {fill:#EE9963}
.fil81 {fill:#EE9962}
.fil82 {fill:#EE9962}
.fil83 {fill:#EE9961}
.fil84 {fill:#ED9860}
.fil85 {fill:#ED975F}
.fil120 {fill:#7789A4}
.fil122 {fill:#664840}
.fil123 {fill:#9A8276}
.fil86 {fill:#EC9760}
.fil0 {fill:url(#id1)}
.fil119 {fill:url(#id2)}
.fil121 {fill:url(#id3)}
]]>
</style>
<clipPath id="id0">
<path d="M3369.27 5277.36l110.2 -99.02c247.94,-533.39 647.21,-1047.01 1246.8,-1534.38 399.32,-324.56 1700.72,1992.38 1629.16,2055.54 -1059.82,254.09 -2048.61,72.9 -2986.16,-422.14z"/>
</clipPath>
<linearGradient id="id1" gradientUnits="userSpaceOnUse" x1="768.62" y1="3552.57" x2="7059.64" y2="3552.57">
<stop offset="0" style="stop-opacity:1; stop-color:#28AFEA"/>
<stop offset="0.192157" style="stop-opacity:1; stop-color:#46A5E8"/>
<stop offset="0.368627" style="stop-opacity:1; stop-color:#28AFEA"/>
<stop offset="1" style="stop-opacity:1; stop-color:#649AE6"/>
</linearGradient>
<linearGradient id="id2" gradientUnits="userSpaceOnUse" x1="3022.74" y1="6181.99" x2="3647.6" y2="2505.13">
<stop offset="0" style="stop-opacity:1; stop-color:#664840"/>
<stop offset="0.870588" style="stop-opacity:1; stop-color:#B2A39F"/>
<stop offset="1" style="stop-opacity:1; stop-color:#FEFEFE"/>
</linearGradient>
<radialGradient id="id3" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.999999 -0 -0 0.999999 0 0)" cx="3914.13" cy="3552.57" r="3081.87" fx="3914.13" fy="3552.57">
<stop offset="0" style="stop-opacity:1; stop-color:#FEFEFE"/>
<stop offset="0.368627" style="stop-opacity:1; stop-color:#BAC3D1"/>
<stop offset="1" style="stop-opacity:1; stop-color:#7789A4"/>
</radialGradient>
</defs>
<g id="图层_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<circle class="fil0 str0" cx="3914.13" cy="3552.57" r="3145.51"/>
<g>
<g style="clip-path:url(#id0)">
<polygon class="fil1" points="4049.35,6758.42 4047.39,6754.15 6436.52,5657.1 6438.48,5661.36 "/>
<polygon id="1" class="fil1" points="4049.31,6758.34 4043.55,6745.78 6432.68,5648.73 6438.44,5661.28 "/>
<polygon id="2" class="fil2" points="4045.47,6749.97 4039.71,6737.41 6428.84,5640.36 6434.6,5652.91 "/>
<polygon id="3" class="fil3" points="4041.63,6741.6 4035.86,6729.05 6424.99,5631.99 6430.76,5644.54 "/>
<polygon id="4" class="fil4" points="4037.79,6733.23 4032.02,6720.68 6421.15,5623.62 6426.91,5636.18 "/>
<polygon id="5" class="fil5" points="4033.94,6724.86 4028.18,6712.31 6417.31,5615.25 6423.07,5627.81 "/>
<polygon id="6" class="fil6" points="4030.1,6716.49 4024.34,6703.94 6413.46,5606.89 6419.23,5619.44 "/>
<polygon id="7" class="fil6" points="4026.26,6708.12 4020.49,6695.57 6409.62,5598.52 6415.39,5611.07 "/>
<polygon id="8" class="fil7" points="4022.41,6699.76 4016.65,6687.2 6405.78,5590.15 6411.54,5602.7 "/>
<polygon id="9" class="fil7" points="4018.57,6691.39 4012.81,6678.83 6401.94,5581.78 6407.7,5594.33 "/>
<polygon id="10" class="fil8" points="4014.73,6683.02 4008.97,6670.47 6398.09,5573.41 6403.86,5585.96 "/>
<polygon id="11" class="fil9" points="4010.89,6674.65 4005.12,6662.1 6394.25,5565.04 6400.02,5577.6 "/>
<polygon id="12" class="fil9" points="4007.04,6666.28 4001.28,6653.73 6390.41,5556.67 6396.17,5569.23 "/>
<polygon id="13" class="fil10" points="4003.2,6657.91 3997.44,6645.36 6386.57,5548.31 6392.33,5560.86 "/>
<polygon id="14" class="fil10" points="3999.36,6649.55 3993.59,6636.99 6382.72,5539.94 6388.49,5552.49 "/>
<polygon id="15" class="fil11" points="3995.52,6641.18 3989.75,6628.62 6378.88,5531.57 6384.64,5544.12 "/>
<polygon id="16" class="fil12" points="3991.67,6632.81 3985.91,6620.26 6375.04,5523.2 6380.8,5535.75 "/>
<polygon id="17" class="fil13" points="3987.83,6624.44 3982.07,6611.89 6371.2,5514.83 6376.96,5527.38 "/>
<polygon id="18" class="fil13" points="3983.99,6616.07 3978.22,6603.52 6367.35,5506.46 6373.12,5519.02 "/>
<polygon id="19" class="fil14" points="3980.14,6607.7 3974.38,6595.15 6363.51,5498.09 6369.27,5510.65 "/>
<polygon id="20" class="fil14" points="3976.3,6599.33 3970.54,6586.78 6359.67,5489.73 6365.43,5502.28 "/>
<polygon id="21" class="fil15" points="3972.46,6590.97 3966.7,6578.41 6355.82,5481.36 6361.59,5493.91 "/>
<polygon id="22" class="fil15" points="3968.62,6582.6 3962.85,6570.04 6351.98,5472.99 6357.75,5485.54 "/>
<polygon id="23" class="fil16" points="3964.77,6574.23 3959.01,6561.68 6348.14,5464.62 6353.9,5477.17 "/>
<polygon id="24" class="fil17" points="3960.93,6565.86 3955.17,6553.31 6344.3,5456.25 6350.06,5468.81 "/>
<polygon id="25" class="fil18" points="3957.09,6557.49 3951.32,6544.94 6340.45,5447.88 6346.22,5460.44 "/>
<polygon id="26" class="fil18" points="3953.25,6549.12 3947.48,6536.57 6336.61,5439.52 6342.37,5452.07 "/>
<polygon id="27" class="fil19" points="3949.4,6540.75 3943.64,6528.2 6332.77,5431.15 6338.53,5443.7 "/>
<polygon id="28" class="fil19" points="3945.56,6532.39 3939.8,6519.83 6328.93,5422.78 6334.69,5435.33 "/>
<polygon id="29" class="fil20" points="3941.72,6524.02 3935.95,6511.46 6325.08,5414.41 6330.85,5426.96 "/>
<polygon id="30" class="fil20" points="3937.88,6515.65 3932.11,6503.1 6321.24,5406.04 6327,5418.59 "/>
<polygon id="31" class="fil20" points="3934.03,6507.28 3928.27,6494.73 6317.4,5397.67 6323.16,5410.23 "/>
<polygon id="32" class="fil21" points="3930.19,6498.91 3924.43,6486.36 6313.55,5389.3 6319.32,5401.86 "/>
<polygon id="33" class="fil21" points="3926.35,6490.54 3920.58,6477.99 6309.71,5380.94 6315.48,5393.49 "/>
<polygon id="34" class="fil22" points="3922.5,6482.18 3916.74,6469.62 6305.87,5372.57 6311.63,5385.12 "/>
<polygon id="35" class="fil22" points="3918.66,6473.81 3912.9,6461.25 6302.03,5364.2 6307.79,5376.75 "/>
<polygon id="36" class="fil23" points="3914.82,6465.44 3909.05,6452.89 6298.18,5355.83 6303.95,5368.38 "/>
<polygon id="37" class="fil24" points="3910.98,6457.07 3905.21,6444.52 6294.34,5347.46 6300.11,5360.01 "/>
<polygon id="38" class="fil25" points="3907.13,6448.7 3901.37,6436.15 6290.5,5339.09 6296.26,5351.65 "/>
<polygon id="39" class="fil25" points="3903.29,6440.33 3897.53,6427.78 6286.66,5330.73 6292.42,5343.28 "/>
<polygon id="40" class="fil26" points="3899.45,6431.96 3893.68,6419.41 6282.81,5322.36 6288.58,5334.91 "/>
<polygon id="41" class="fil26" points="3895.61,6423.6 3889.84,6411.04 6278.97,5313.99 6284.73,5326.54 "/>
<polygon id="42" class="fil27" points="3891.76,6415.23 3886,6402.67 6275.13,5305.62 6280.89,5318.17 "/>
<polygon id="43" class="fil27" points="3887.92,6406.86 3882.16,6394.31 6271.29,5297.25 6277.05,5309.8 "/>
<polygon id="44" class="fil28" points="3884.08,6398.49 3878.31,6385.94 6267.44,5288.88 6273.21,5301.44 "/>
<polygon id="45" class="fil29" points="3880.23,6390.12 3874.47,6377.57 6263.6,5280.51 6269.36,5293.07 "/>
<polygon id="46" class="fil30" points="3876.39,6381.75 3870.63,6369.2 6259.76,5272.15 6265.52,5284.7 "/>
<polygon id="47" class="fil30" points="3872.55,6373.38 3866.79,6360.83 6255.91,5263.78 6261.68,5276.33 "/>
<polygon id="48" class="fil31" points="3868.71,6365.02 3862.94,6352.46 6252.07,5255.41 6257.84,5267.96 "/>
<polygon id="49" class="fil31" points="3864.86,6356.65 3859.1,6344.09 6248.23,5247.04 6253.99,5259.59 "/>
<polygon id="50" class="fil32" points="3861.02,6348.28 3855.26,6335.73 6244.39,5238.67 6250.15,5251.22 "/>
<polygon id="51" class="fil33" points="3857.18,6339.91 3851.41,6327.36 6240.54,5230.3 6246.31,5242.86 "/>
<polygon id="52" class="fil33" points="3853.34,6331.54 3847.57,6318.99 6236.7,5221.93 6242.46,5234.49 "/>
<polygon id="53" class="fil34" points="3849.49,6323.17 3843.73,6310.62 6232.86,5213.57 6238.62,5226.12 "/>
<polygon id="54" class="fil34" points="3845.65,6314.81 3839.89,6302.25 6229.02,5205.2 6234.78,5217.75 "/>
<polygon id="55" class="fil35" points="3841.81,6306.44 3836.04,6293.88 6225.17,5196.83 6230.94,5209.38 "/>
<polygon id="56" class="fil36" points="3837.97,6298.07 3832.2,6285.52 6221.33,5188.46 6227.09,5201.01 "/>
<polygon id="57" class="fil37" points="3834.12,6289.7 3828.36,6277.15 6217.49,5180.09 6223.25,5192.64 "/>
<polygon id="58" class="fil38" points="3830.28,6281.33 3824.52,6268.78 6213.64,5171.72 6219.41,5184.28 "/>
<polygon id="59" class="fil39" points="3826.44,6272.96 3820.67,6260.41 6209.8,5163.36 6215.57,5175.91 "/>
<polygon id="60" class="fil40" points="3822.59,6264.59 3816.83,6252.04 6205.96,5154.99 6211.72,5167.54 "/>
<polygon id="61" class="fil41" points="3818.75,6256.23 3812.99,6243.67 6202.12,5146.62 6207.88,5159.17 "/>
<polygon id="62" class="fil41" points="3814.91,6247.86 3809.14,6235.3 6198.27,5138.25 6204.04,5150.8 "/>
<polygon id="63" class="fil42" points="3811.07,6239.49 3805.3,6226.94 6194.43,5129.88 6200.2,5142.43 "/>
<polygon id="64" class="fil42" points="3807.22,6231.12 3801.46,6218.57 6190.59,5121.51 6196.35,5134.07 "/>
<polygon id="65" class="fil43" points="3803.38,6222.75 3797.62,6210.2 6186.75,5113.14 6192.51,5125.7 "/>
<polygon id="66" class="fil44" points="3799.54,6214.38 3793.77,6201.83 6182.9,5104.78 6188.67,5117.33 "/>
<polygon id="67" class="fil45" points="3795.7,6206.01 3789.93,6193.46 6179.06,5096.41 6184.82,5108.96 "/>
<polygon id="68" class="fil45" points="3791.85,6197.65 3786.09,6185.09 6175.22,5088.04 6180.98,5100.59 "/>
<polygon id="69" class="fil46" points="3788.01,6189.28 3782.25,6176.73 6171.37,5079.67 6177.14,5092.22 "/>
<polygon id="70" class="fil47" points="3784.17,6180.91 3778.4,6168.36 6167.53,5071.3 6173.3,5083.85 "/>
<polygon id="71" class="fil48" points="3780.32,6172.54 3774.56,6159.99 6163.69,5062.93 6169.45,5075.49 "/>
<polygon id="72" class="fil49" points="3776.48,6164.17 3770.72,6151.62 6159.85,5054.56 6165.61,5067.12 "/>
<polygon id="73" class="fil49" points="3772.64,6155.8 3766.88,6143.25 6156,5046.2 6161.77,5058.75 "/>
<polygon id="74" class="fil50" points="3768.8,6147.44 3763.03,6134.88 6152.16,5037.83 6157.93,5050.38 "/>
<polygon id="75" class="fil50" points="3764.95,6139.07 3759.19,6126.51 6148.32,5029.46 6154.08,5042.01 "/>
<polygon id="76" class="fil51" points="3761.11,6130.7 3755.35,6118.15 6144.48,5021.09 6150.24,5033.64 "/>
<polygon id="77" class="fil52" points="3757.27,6122.33 3751.5,6109.78 6140.63,5012.72 6146.4,5025.27 "/>
<polygon id="78" class="fil53" points="3753.43,6113.96 3747.66,6101.41 6136.79,5004.35 6142.55,5016.91 "/>
<polygon id="79" class="fil54" points="3749.58,6105.59 3743.82,6093.04 6132.95,4995.99 6138.71,5008.54 "/>
<polygon id="80" class="fil55" points="3745.74,6097.22 3739.98,6084.67 6129.11,4987.62 6134.87,5000.17 "/>
<polygon id="81" class="fil55" points="3741.9,6088.86 3736.13,6076.3 6125.26,4979.25 6131.03,4991.8 "/>
<polygon id="82" class="fil56" points="3738.05,6080.49 3732.29,6067.93 6121.42,4970.88 6127.18,4983.43 "/>
<polygon id="83" class="fil56" points="3734.21,6072.12 3728.45,6059.57 6117.58,4962.51 6123.34,4975.06 "/>
<polygon id="84" class="fil57" points="3730.37,6063.75 3724.61,6051.2 6113.73,4954.14 6119.5,4966.7 "/>
<polygon id="85" class="fil58" points="3726.53,6055.38 3720.76,6042.83 6109.89,4945.77 6115.66,4958.33 "/>
<polygon id="86" class="fil59" points="3722.68,6047.01 3716.92,6034.46 6106.05,4937.41 6111.81,4949.96 "/>
<polygon id="87" class="fil60" points="3718.84,6038.64 3713.08,6026.09 6102.21,4929.04 6107.97,4941.59 "/>
<polygon id="88" class="fil60" points="3715,6030.28 3709.23,6017.72 6098.36,4920.67 6104.13,4933.22 "/>
<polygon id="89" class="fil61" points="3711.16,6021.91 3705.39,6009.36 6094.52,4912.3 6100.29,4924.85 "/>
<polygon id="90" class="fil62" points="3707.31,6013.54 3701.55,6000.99 6090.68,4903.93 6096.44,4916.48 "/>
<polygon id="91" class="fil63" points="3703.47,6005.17 3697.71,5992.62 6086.84,4895.56 6092.6,4908.12 "/>
<polygon id="92" class="fil63" points="3699.63,5996.8 3693.86,5984.25 6082.99,4887.19 6088.76,4899.75 "/>
<polygon id="93" class="fil64" points="3695.79,5988.43 3690.02,5975.88 6079.15,4878.83 6084.91,4891.38 "/>
<polygon id="94" class="fil64" points="3691.94,5980.07 3686.18,5967.51 6075.31,4870.46 6081.07,4883.01 "/>
<polygon id="95" class="fil65" points="3688.1,5971.7 3682.34,5959.14 6071.46,4862.09 6077.23,4874.64 "/>
<polygon id="96" class="fil65" points="3684.26,5963.33 3678.49,5950.78 6067.62,4853.72 6073.39,4866.27 "/>
<polygon id="97" class="fil66" points="3680.41,5954.96 3674.65,5942.41 6063.78,4845.35 6069.54,4857.91 "/>
<polygon id="98" class="fil67" points="3676.57,5946.59 3670.81,5934.04 6059.94,4836.98 6065.7,4849.54 "/>
<polygon id="99" class="fil68" points="3672.73,5938.22 3666.97,5925.67 6056.09,4828.62 6061.86,4841.17 "/>
<polygon id="100" class="fil69" points="3668.89,5929.85 3663.12,5917.3 6052.25,4820.25 6058.02,4832.8 "/>
<polygon id="101" class="fil70" points="3665.04,5921.49 3659.28,5908.93 6048.41,4811.88 6054.17,4824.43 "/>
<polygon id="102" class="fil70" points="3661.2,5913.12 3655.44,5900.56 6044.57,4803.51 6050.33,4816.06 "/>
<polygon id="103" class="fil71" points="3657.36,5904.75 3651.59,5892.2 6040.72,4795.14 6046.49,4807.69 "/>
<polygon id="104" class="fil72" points="3653.52,5896.38 3647.75,5883.83 6036.88,4786.77 6042.64,4799.33 "/>
<polygon id="105" class="fil73" points="3649.67,5888.01 3643.91,5875.46 6033.04,4778.4 6038.8,4790.96 "/>
<polygon id="106" class="fil74" points="3645.83,5879.64 3640.07,5867.09 6029.2,4770.04 6034.96,4782.59 "/>
<polygon id="107" class="fil74" points="3641.99,5871.27 3636.22,5858.72 6025.35,4761.67 6031.12,4774.22 "/>
<polygon id="108" class="fil75" points="3638.14,5862.91 3632.38,5850.35 6021.51,4753.3 6027.27,4765.85 "/>
<polygon id="109" class="fil75" points="3634.3,5854.54 3628.54,5841.99 6017.67,4744.93 6023.43,4757.48 "/>
<polygon id="110" class="fil76" points="3630.46,5846.17 3624.7,5833.62 6013.82,4736.56 6019.59,4749.11 "/>
<polygon id="111" class="fil77" points="3626.62,5837.8 3620.85,5825.25 6009.98,4728.19 6015.75,4740.75 "/>
<polygon id="112" class="fil78" points="3622.77,5829.43 3617.01,5816.88 6006.14,4719.82 6011.9,4732.38 "/>
<polygon id="113" class="fil78" points="3618.93,5821.06 3613.17,5808.51 6002.3,4711.46 6008.06,4724.01 "/>
<polygon id="114" class="fil79" points="3615.09,5812.7 3609.32,5800.14 5998.45,4703.09 6004.22,4715.64 "/>
<polygon id="115" class="fil79" points="3611.25,5804.33 3605.48,5791.77 5994.61,4694.72 6000.37,4707.27 "/>
<polygon id="116" class="fil80" points="3607.4,5795.96 3601.64,5783.41 5990.77,4686.35 5996.53,4698.9 "/>
<polygon id="117" class="fil80" points="3603.56,5787.59 3597.8,5775.04 5986.93,4677.98 5992.69,4690.54 "/>
<polygon id="118" class="fil81" points="3599.72,5779.22 3593.95,5766.67 5983.08,4669.61 5988.85,4682.17 "/>
<polygon id="119" class="fil82" points="3595.88,5770.85 3590.11,5758.3 5979.24,4661.25 5985,4673.8 "/>
<polygon id="120" class="fil83" points="3592.03,5762.48 3586.27,5749.93 5975.4,4652.88 5981.16,4665.43 "/>
<polygon id="121" class="fil83" points="3588.19,5754.12 3582.43,5741.56 5971.55,4644.51 5977.32,4657.06 "/>
<polygon id="122" class="fil84" points="3584.35,5745.75 3578.58,5733.19 5967.71,4636.14 5973.48,4648.69 "/>
<polygon id="123" class="fil84" points="3580.5,5737.38 3574.74,5724.83 5963.87,4627.77 5969.63,4640.32 "/>
<polygon id="124" class="fil85" points="3576.66,5729.01 3570.9,5716.46 5960.03,4619.4 5965.79,4631.96 "/>
<polygon id="125" class="fil86" points="3572.82,5720.64 3567.05,5708.09 5956.18,4611.03 5961.95,4623.59 "/>
<polygon id="126" class="fil85" points="3568.98,5712.27 3563.21,5699.72 5952.34,4602.67 5958.11,4615.22 "/>
<polygon id="127" class="fil84" points="3565.13,5703.91 3559.37,5691.35 5948.5,4594.3 5954.26,4606.85 "/>
<polygon id="128" class="fil84" points="3561.29,5695.54 3555.53,5682.98 5944.66,4585.93 5950.42,4598.48 "/>
<polygon id="129" class="fil83" points="3557.45,5687.17 3551.68,5674.62 5940.81,4577.56 5946.58,4590.11 "/>
<polygon id="130" class="fil83" points="3553.61,5678.8 3547.84,5666.25 5936.97,4569.19 5942.73,4581.74 "/>
<polygon id="131" class="fil82" points="3549.76,5670.43 3544,5657.88 5933.13,4560.82 5938.89,4573.38 "/>
<polygon id="132" class="fil81" points="3545.92,5662.06 3540.16,5649.51 5929.29,4552.45 5935.05,4565.01 "/>
<polygon id="133" class="fil80" points="3542.08,5653.69 3536.31,5641.14 5925.44,4544.09 5931.21,4556.64 "/>
<polygon id="134" class="fil80" points="3538.23,5645.33 3532.47,5632.77 5921.6,4535.72 5927.36,4548.27 "/>
<polygon id="135" class="fil79" points="3534.39,5636.96 3528.63,5624.4 5917.76,4527.35 5923.52,4539.9 "/>
<polygon id="136" class="fil79" points="3530.55,5628.59 3524.79,5616.04 5913.91,4518.98 5919.68,4531.53 "/>
<polygon id="137" class="fil78" points="3526.71,5620.22 3520.94,5607.67 5910.07,4510.61 5915.84,4523.17 "/>
<polygon id="138" class="fil78" points="3522.86,5611.85 3517.1,5599.3 5906.23,4502.24 5911.99,4514.8 "/>
<polygon id="139" class="fil77" points="3519.02,5603.48 3513.26,5590.93 5902.39,4493.88 5908.15,4506.43 "/>
<polygon id="140" class="fil76" points="3515.18,5595.11 3509.41,5582.56 5898.54,4485.51 5904.31,4498.06 "/>
<polygon id="141" class="fil75" points="3511.34,5586.75 3505.57,5574.19 5894.7,4477.14 5900.46,4489.69 "/>
<polygon id="142" class="fil75" points="3507.49,5578.38 3501.73,5565.82 5890.86,4468.77 5896.62,4481.32 "/>
<polygon id="143" class="fil74" points="3503.65,5570.01 3497.89,5557.46 5887.02,4460.4 5892.78,4472.95 "/>
<polygon id="144" class="fil74" points="3499.81,5561.64 3494.04,5549.09 5883.17,4452.03 5888.94,4464.59 "/>
<polygon id="145" class="fil73" points="3495.97,5553.27 3490.2,5540.72 5879.33,4443.66 5885.09,4456.22 "/>
<polygon id="146" class="fil72" points="3492.12,5544.9 3486.36,5532.35 5875.49,4435.3 5881.25,4447.85 "/>
<polygon id="147" class="fil71" points="3488.28,5536.54 3482.52,5523.98 5871.64,4426.93 5877.41,4439.48 "/>
<polygon id="148" class="fil70" points="3484.44,5528.17 3478.67,5515.61 5867.8,4418.56 5873.57,4431.11 "/>
<polygon id="149" class="fil70" points="3480.59,5519.8 3474.83,5507.25 5863.96,4410.19 5869.72,4422.74 "/>
<polygon id="150" class="fil69" points="3476.75,5511.43 3470.99,5498.88 5860.12,4401.82 5865.88,4414.37 "/>
<polygon id="151" class="fil68" points="3472.91,5503.06 3467.14,5490.51 5856.27,4393.45 5862.04,4406.01 "/>
<polygon id="152" class="fil67" points="3469.07,5494.69 3463.3,5482.14 5852.43,4385.09 5858.2,4397.64 "/>
<polygon id="153" class="fil66" points="3465.22,5486.32 3459.46,5473.77 5848.59,4376.72 5854.35,4389.27 "/>
<polygon id="154" class="fil65" points="3461.38,5477.96 3455.62,5465.4 5844.75,4368.35 5850.51,4380.9 "/>
<polygon id="155" class="fil65" points="3457.54,5469.59 3451.77,5457.03 5840.9,4359.98 5846.67,4372.53 "/>
<polygon id="156" class="fil64" points="3453.7,5461.22 3447.93,5448.67 5837.06,4351.61 5842.82,4364.16 "/>
<polygon id="157" class="fil64" points="3449.85,5452.85 3444.09,5440.3 5833.22,4343.24 5838.98,4355.8 "/>
<polygon id="158" class="fil63" points="3446.01,5444.48 3440.25,5431.93 5829.37,4334.87 5835.14,4347.43 "/>
<polygon id="159" class="fil63" points="3442.17,5436.11 3436.4,5423.56 5825.53,4326.51 5831.3,4339.06 "/>
<polygon id="160" class="fil62" points="3438.32,5427.74 3432.56,5415.19 5821.69,4318.14 5827.45,4330.69 "/>
<polygon id="161" class="fil61" points="3434.48,5419.38 3428.72,5406.82 5817.85,4309.77 5823.61,4322.32 "/>
<polygon id="162" class="fil60" points="3430.64,5411.01 3424.88,5398.45 5814,4301.4 5819.77,4313.95 "/>
<polygon id="163" class="fil60" points="3426.8,5402.64 3421.03,5390.09 5810.16,4293.03 5815.93,4305.58 "/>
<polygon id="164" class="fil59" points="3422.95,5394.27 3417.19,5381.72 5806.32,4284.66 5812.08,4297.22 "/>
<polygon id="165" class="fil58" points="3419.11,5385.9 3413.35,5373.35 5802.48,4276.29 5808.24,4288.85 "/>
<polygon id="166" class="fil57" points="3415.27,5377.53 3409.5,5364.98 5798.63,4267.93 5804.4,4280.48 "/>
<polygon id="167" class="fil56" points="3411.43,5369.17 3405.66,5356.61 5794.79,4259.56 5800.55,4272.11 "/>
<polygon id="168" class="fil56" points="3407.58,5360.8 3401.82,5348.24 5790.95,4251.19 5796.71,4263.74 "/>
<polygon id="169" class="fil55" points="3403.74,5352.43 3397.98,5339.88 5787.11,4242.82 5792.87,4255.37 "/>
<polygon id="170" class="fil55" points="3399.9,5344.06 3394.13,5331.51 5783.26,4234.45 5789.03,4247 "/>
<polygon id="171" class="fil54" points="3396.05,5335.69 3390.29,5323.14 5779.42,4226.08 5785.18,4238.64 "/>
<polygon id="172" class="fil53" points="3392.21,5327.32 3386.45,5314.77 5775.58,4217.72 5781.34,4230.27 "/>
<polygon id="173" class="fil52" points="3388.37,5318.95 3382.61,5306.4 5771.73,4209.35 5777.5,4221.9 "/>
<polygon id="174" class="fil51" points="3384.53,5310.59 3378.76,5298.03 5767.89,4200.98 5773.66,4213.53 "/>
<polygon id="175" class="fil50" points="3380.68,5302.22 3374.92,5289.66 5764.05,4192.61 5769.81,4205.16 "/>
<polygon id="176" class="fil50" points="3376.84,5293.85 3371.08,5281.3 5760.21,4184.24 5765.97,4196.79 "/>
<polygon id="177" class="fil49" points="3373,5285.48 3367.23,5272.93 5756.36,4175.87 5762.13,4188.43 "/>
<polygon id="178" class="fil49" points="3369.16,5277.11 3363.39,5264.56 5752.52,4167.5 5758.28,4180.06 "/>
<polygon id="179" class="fil48" points="3365.31,5268.74 3359.55,5256.19 5748.68,4159.14 5754.44,4171.69 "/>
<polygon id="180" class="fil47" points="3361.47,5260.37 3355.71,5247.82 5744.84,4150.77 5750.6,4163.32 "/>
<polygon id="181" class="fil46" points="3357.63,5252.01 3351.86,5239.45 5740.99,4142.4 5746.76,4154.95 "/>
<polygon id="182" class="fil45" points="3353.79,5243.64 3348.02,5231.09 5737.15,4134.03 5742.91,4146.58 "/>
<polygon id="183" class="fil45" points="3349.94,5235.27 3344.18,5222.72 5733.31,4125.66 5739.07,4138.21 "/>
<polygon id="184" class="fil44" points="3346.1,5226.9 3340.34,5214.35 5729.46,4117.29 5735.23,4129.85 "/>
<polygon id="185" class="fil43" points="3342.26,5218.53 3336.49,5205.98 5725.62,4108.92 5731.39,4121.48 "/>
<polygon id="186" class="fil42" points="3338.41,5210.16 3332.65,5197.61 5721.78,4100.56 5727.54,4113.11 "/>
<polygon id="187" class="fil42" points="3334.57,5201.8 3328.81,5189.24 5717.94,4092.19 5723.7,4104.74 "/>
<polygon id="188" class="fil41" points="3330.73,5193.43 3324.96,5180.87 5714.09,4083.82 5719.86,4096.37 "/>
<polygon id="189" class="fil41" points="3326.89,5185.06 3321.12,5172.51 5710.25,4075.45 5716.02,4088 "/>
<polygon id="190" class="fil40" points="3323.04,5176.69 3317.28,5164.14 5706.41,4067.08 5712.17,4079.63 "/>
<polygon id="191" class="fil39" points="3319.2,5168.32 3313.44,5155.77 5702.57,4058.71 5708.33,4071.27 "/>
<polygon id="192" class="fil38" points="3315.36,5159.95 3309.59,5147.4 5698.72,4050.35 5704.49,4062.9 "/>
<polygon id="193" class="fil37" points="3311.52,5151.58 3305.75,5139.03 5694.88,4041.98 5700.64,4054.53 "/>
<polygon id="194" class="fil36" points="3307.67,5143.22 3301.91,5130.66 5691.04,4033.61 5696.8,4046.16 "/>
<polygon id="195" class="fil35" points="3303.83,5134.85 3298.07,5122.29 5687.2,4025.24 5692.96,4037.79 "/>
<polygon id="196" class="fil34" points="3299.99,5126.48 3294.22,5113.93 5683.35,4016.87 5689.12,4029.42 "/>
<polygon id="197" class="fil34" points="3296.14,5118.11 3290.38,5105.56 5679.51,4008.5 5685.27,4021.06 "/>
<polygon id="198" class="fil33" points="3292.3,5109.74 3286.54,5097.19 5675.67,4000.13 5681.43,4012.69 "/>
<polygon id="199" class="fil33" points="3288.46,5101.37 3282.7,5088.82 5671.82,3991.77 5677.59,4004.32 "/>
<polygon id="200" class="fil32" points="3284.62,5093 3278.85,5080.45 5667.98,3983.4 5673.75,3995.95 "/>
<polygon id="201" class="fil31" points="3280.77,5084.64 3275.01,5072.08 5664.14,3975.03 5669.9,3987.58 "/>
<polygon id="202" class="fil31" points="3276.93,5076.27 3271.17,5063.72 5660.3,3966.66 5666.06,3979.21 "/>
<polygon id="203" class="fil30" points="3273.09,5067.9 3267.32,5055.35 5656.45,3958.29 5662.22,3970.84 "/>
<polygon id="204" class="fil30" points="3269.25,5059.53 3263.48,5046.98 5652.61,3949.92 5658.37,3962.48 "/>
<polygon id="205" class="fil29" points="3265.4,5051.16 3259.64,5038.61 5648.77,3941.55 5654.53,3954.11 "/>
<polygon id="206" class="fil28" points="3261.56,5042.79 3255.8,5030.24 5644.93,3933.19 5650.69,3945.74 "/>
<polygon id="207" class="fil27" points="3257.72,5034.43 3251.95,5021.87 5641.08,3924.82 5646.85,3937.37 "/>
<polygon id="208" class="fil27" points="3253.88,5026.06 3248.11,5013.5 5637.24,3916.45 5643,3929 "/>
<polygon id="209" class="fil26" points="3250.03,5017.69 3244.27,5005.14 5633.4,3908.08 5639.16,3920.63 "/>
<polygon id="210" class="fil26" points="3246.19,5009.32 3240.43,4996.77 5629.55,3899.71 5635.32,3912.27 "/>
<polygon id="211" class="fil25" points="3242.35,5000.95 3236.58,4988.4 5625.71,3891.34 5631.48,3903.9 "/>
<polygon id="212" class="fil25" points="3238.5,4992.58 3232.74,4980.03 5621.87,3882.98 5627.63,3895.53 "/>
<polygon id="213" class="fil24" points="3234.66,4984.21 3228.9,4971.66 5618.03,3874.61 5623.79,3887.16 "/>
<polygon id="214" class="fil23" points="3230.82,4975.85 3225.05,4963.29 5614.18,3866.24 5619.95,3878.79 "/>
<polygon id="215" class="fil22" points="3226.98,4967.48 3221.21,4954.92 5610.34,3857.87 5616.11,3870.42 "/>
<polygon id="216" class="fil22" points="3223.13,4959.11 3217.37,4946.56 5606.5,3849.5 5612.26,3862.05 "/>
<polygon id="217" class="fil21" points="3219.29,4950.74 3213.53,4938.19 5602.66,3841.13 5608.42,3853.69 "/>
<polygon id="218" class="fil21" points="3215.45,4942.37 3209.68,4929.82 5598.81,3832.76 5604.58,3845.32 "/>
<polygon id="219" class="fil20" points="3211.61,4934 3205.84,4921.45 5594.97,3824.4 5600.73,3836.95 "/>
<polygon id="220" class="fil20" points="3207.76,4925.63 3202,4913.08 5591.13,3816.03 5596.89,3828.58 "/>
<polygon id="221" class="fil20" points="3203.92,4917.27 3198.16,4904.71 5587.28,3807.66 5593.05,3820.21 "/>
<polygon id="222" class="fil19" points="3200.08,4908.9 3194.31,4896.35 5583.44,3799.29 5589.21,3811.84 "/>
<polygon id="223" class="fil19" points="3196.23,4900.53 3190.47,4887.98 5579.6,3790.92 5585.36,3803.47 "/>
<polygon id="224" class="fil18" points="3192.39,4892.16 3186.63,4879.61 5575.76,3782.55 5581.52,3795.11 "/>
<polygon id="225" class="fil18" points="3188.55,4883.79 3182.79,4871.24 5571.91,3774.18 5577.68,3786.74 "/>
<polygon id="226" class="fil17" points="3184.71,4875.42 3178.94,4862.87 5568.07,3765.82 5573.84,3778.37 "/>
<polygon id="227" class="fil16" points="3180.86,4867.06 3175.1,4854.5 5564.23,3757.45 5569.99,3770 "/>
<polygon id="228" class="fil15" points="3177.02,4858.69 3171.26,4846.13 5560.39,3749.08 5566.15,3761.63 "/>
<polygon id="229" class="fil15" points="3173.18,4850.32 3167.41,4837.77 5556.54,3740.71 5562.31,3753.26 "/>
<polygon id="230" class="fil14" points="3169.34,4841.95 3163.57,4829.4 5552.7,3732.34 5558.46,3744.9 "/>
<polygon id="231" class="fil14" points="3165.49,4833.58 3159.73,4821.03 5548.86,3723.97 5554.62,3736.53 "/>
<polygon id="232" class="fil13" points="3161.65,4825.21 3155.89,4812.66 5545.02,3715.61 5550.78,3728.16 "/>
<polygon id="233" class="fil13" points="3157.81,4816.84 3152.04,4804.29 5541.17,3707.24 5546.94,3719.79 "/>
<polygon id="234" class="fil12" points="3153.96,4808.48 3148.2,4795.92 5537.33,3698.87 5543.09,3711.42 "/>
<polygon id="235" class="fil11" points="3150.12,4800.11 3144.36,4787.55 5533.49,3690.5 5539.25,3703.05 "/>
<polygon id="236" class="fil10" points="3146.28,4791.74 3140.52,4779.19 5529.64,3682.13 5535.41,3694.68 "/>
<polygon id="237" class="fil10" points="3142.44,4783.37 3136.67,4770.82 5525.8,3673.76 5531.57,3686.32 "/>
<polygon id="238" class="fil9" points="3138.59,4775 3132.83,4762.45 5521.96,3665.39 5527.72,3677.95 "/>
<polygon id="239" class="fil9" points="3134.75,4766.63 3128.99,4754.08 5518.12,3657.03 5523.88,3669.58 "/>
<polygon id="240" class="fil8" points="3130.91,4758.27 3125.14,4745.71 5514.27,3648.66 5520.04,3661.21 "/>
<polygon id="241" class="fil7" points="3127.07,4749.9 3121.3,4737.34 5510.43,3640.29 5516.2,3652.84 "/>
<polygon id="242" class="fil7" points="3123.22,4741.53 3117.46,4728.98 5506.59,3631.92 5512.35,3644.47 "/>
<polygon id="243" class="fil6" points="3119.38,4733.16 3113.62,4720.61 5502.75,3623.55 5508.51,3636.1 "/>
<polygon id="244" class="fil6" points="3115.54,4724.79 3109.77,4712.24 5498.9,3615.18 5504.67,3627.74 "/>
<polygon id="245" class="fil5" points="3111.7,4716.42 3105.93,4703.87 5495.06,3606.81 5500.82,3619.37 "/>
<polygon id="246" class="fil4" points="3107.85,4708.05 3102.09,4695.5 5491.22,3598.45 5496.98,3611 "/>
<polygon id="247" class="fil3" points="3104.01,4699.69 3098.25,4687.13 5487.37,3590.08 5493.14,3602.63 "/>
<polygon id="248" class="fil2" points="3100.17,4691.32 3094.4,4678.76 5483.53,3581.71 5489.3,3594.26 "/>
<polygon id="249" class="fil1" points="3096.32,4682.95 3090.56,4670.4 5479.69,3573.34 5485.45,3585.89 "/>
<polygon id="250" class="fil1" points="3092.48,4674.58 3086.72,4662.03 5475.85,3564.97 5481.61,3577.53 "/>
<polygon id="251" class="fil87" points="3088.64,4666.21 3082.88,4653.66 5472,3556.6 5477.77,3569.16 "/>
<polygon id="252" class="fil87" points="3084.8,4657.84 3079.03,4645.29 5468.16,3548.24 5473.93,3560.79 "/>
<polygon id="253" class="fil88" points="3080.95,4649.47 3075.19,4636.92 5464.32,3539.87 5470.08,3552.42 "/>
<polygon id="254" class="fil89" points="3077.11,4641.11 3071.35,4628.55 5460.48,3531.5 5466.24,3544.05 "/>
<polygon id="255" class="fil90" points="3073.27,4632.74 3067.5,4620.18 5456.63,3523.13 5462.4,3535.68 "/>
<polygon id="256" class="fil91" points="3069.43,4624.37 3063.66,4611.82 5452.79,3514.76 5458.55,3527.31 "/>
<polygon id="257" class="fil92" points="3065.58,4616 3059.82,4603.45 5448.95,3506.39 5454.71,3518.95 "/>
<polygon id="258" class="fil92" points="3061.74,4607.63 3055.98,4595.08 5445.11,3498.02 5450.87,3510.58 "/>
<polygon id="259" class="fil93" points="3057.9,4599.26 3052.13,4586.71 5441.26,3489.66 5447.03,3502.21 "/>
<polygon id="260" class="fil94" points="3054.05,4590.9 3048.29,4578.34 5437.42,3481.29 5443.18,3493.84 "/>
<polygon id="261" class="fil95" points="3050.21,4582.53 3044.45,4569.97 5433.58,3472.92 5439.34,3485.47 "/>
<polygon id="262" class="fil96" points="3046.37,4574.16 3040.61,4561.61 5429.73,3464.55 5435.5,3477.1 "/>
<polygon id="263" class="fil96" points="3042.53,4565.79 3036.76,4553.24 5425.89,3456.18 5431.66,3468.73 "/>
<polygon id="264" class="fil97" points="3038.68,4557.42 3032.92,4544.87 5422.05,3447.81 5427.81,3460.37 "/>
<polygon id="265" class="fil97" points="3034.84,4549.05 3029.08,4536.5 5418.21,3439.45 5423.97,3452 "/>
<polygon id="266" class="fil98" points="3031,4540.68 3025.23,4528.13 5414.36,3431.08 5420.13,3443.63 "/>
<polygon id="267" class="fil99" points="3027.16,4532.32 3021.39,4519.76 5410.52,3422.71 5416.28,3435.26 "/>
<polygon id="268" class="fil100" points="3023.31,4523.95 3017.55,4511.39 5406.68,3414.34 5412.44,3426.89 "/>
<polygon id="269" class="fil101" points="3019.47,4515.58 3013.71,4503.03 5402.84,3405.97 5408.6,3418.52 "/>
<polygon id="270" class="fil102" points="3015.63,4507.21 3009.86,4494.66 5398.99,3397.6 5404.76,3410.16 "/>
<polygon id="271" class="fil102" points="3011.79,4498.84 3006.02,4486.29 5395.15,3389.23 5400.91,3401.79 "/>
<polygon id="272" class="fil103" points="3007.94,4490.47 3002.18,4477.92 5391.31,3380.87 5397.07,3393.42 "/>
<polygon id="273" class="fil103" points="3004.1,4482.1 2998.34,4469.55 5387.46,3372.5 5393.23,3385.05 "/>
<polygon id="274" class="fil104" points="3000.26,4473.74 2994.49,4461.18 5383.62,3364.13 5389.39,3376.68 "/>
<polygon id="275" class="fil105" points="2996.41,4465.37 2990.65,4452.81 5379.78,3355.76 5385.54,3368.31 "/>
<polygon id="276" class="fil106" points="2992.57,4457 2986.81,4444.45 5375.94,3347.39 5381.7,3359.94 "/>
<polygon id="277" class="fil107" points="2988.73,4448.63 2982.96,4436.08 5372.09,3339.02 5377.86,3351.58 "/>
<polygon id="278" class="fil108" points="2984.89,4440.26 2979.12,4427.71 5368.25,3330.65 5374.02,3343.21 "/>
<polygon id="279" class="fil109" points="2981.04,4431.89 2975.28,4419.34 5364.41,3322.29 5370.17,3334.84 "/>
<polygon id="280" class="fil110" points="2977.2,4423.53 2971.44,4410.97 5360.57,3313.92 5366.33,3326.47 "/>
<polygon id="281" class="fil111" points="2973.36,4415.16 2967.59,4402.6 5356.72,3305.55 5362.49,3318.1 "/>
<polygon id="282" class="fil111" points="2969.52,4406.79 2963.75,4394.24 5352.88,3297.18 5358.64,3309.73 "/>
<polygon id="283" class="fil112" points="2965.67,4398.42 2959.91,4385.87 5349.04,3288.81 5354.8,3301.36 "/>
<polygon id="284" class="fil112" points="2961.83,4390.05 2956.07,4377.5 5345.2,3280.44 5350.96,3293 "/>
<polygon id="285" class="fil113" points="2957.99,4381.68 2952.22,4369.13 5341.35,3272.08 5347.12,3284.63 "/>
<polygon id="286" class="fil113" points="2954.14,4373.31 2948.38,4360.76 5337.51,3263.71 5343.27,3276.26 "/>
<polygon id="287" class="fil114" points="2950.3,4364.95 2944.54,4352.39 5333.67,3255.34 5339.43,3267.89 "/>
<polygon id="288" class="fil115" points="2946.46,4356.58 2940.7,4344.02 5329.82,3246.97 5335.59,3259.52 "/>
<polygon id="289" class="fil116" points="2942.62,4348.21 2936.85,4335.66 5325.98,3238.6 5331.75,3251.15 "/>
<polygon id="290" class="fil117" points="2938.77,4339.84 2933.01,4327.29 5322.14,3230.23 5327.9,3242.79 "/>
<polygon id="291" class="fil118" points="2934.93,4331.47 2929.17,4318.92 5318.3,3221.86 5324.06,3234.42 "/>
<polygon id="292" class="fil118" points="2931.09,4323.1 2932.6,4326.39 5321.73,3229.34 5320.22,3226.05 "/>
</g>
</g>
<path class="fil119" d="M1339.29 5096.79l147.16 -132.24c331.13,-712.35 864.35,-1398.28 1665.1,-2049.16 533.28,-433.45 2271.3,2660.8 2175.74,2745.15 -1415.39,339.35 -2735.92,97.36 -3988,-563.75z"/>
<path class="fil120" d="M5777.71 5348.83l1969.84 2220.65c479.99,541.1 -404.26,1279.68 -856.98,769.31l-1969.83 -2220.66c-266.41,-300.32 569.96,-1092.85 856.97,-769.3z"/>
<path class="fil121" d="M3914.13 251.11c1823.36,0 3301.46,1478.1 3301.46,3301.46 0,1823.35 -1478.1,3301.46 -3301.46,3301.46 -1823.35,0 -3301.46,-1478.11 -3301.46,-3301.46 0,-1823.36 1478.11,-3301.46 3301.46,-3301.46zm0 1233.51c1142.11,0 2067.95,925.84 2067.95,2067.95 0,1142.1 -925.84,2067.94 -2067.95,2067.94 -1142.1,0 -2067.94,-925.84 -2067.94,-2067.94 0,-1142.11 925.84,-2067.95 2067.94,-2067.95z"/>
<path class="fil122" d="M5216.44 5089.76c-705.89,-446.6 -1224.17,-746.12 -2074.57,-200.94 -105.07,67.36 -339.17,246.82 -378.72,340.57 1315.26,-254.1 1488.29,846.64 2369.25,1306.63l175.15 82.52c2.14,1.03 5.14,2.45 7.28,3.48 2.13,1.02 5.16,2.42 7.29,3.44 191.64,92.05 571.91,156.13 782,138.63 292.82,-24.4 537.94,-70.37 803.82,-204.52 1278.38,-645.04 1437.6,-2188.86 503.59,-3180.72 -112.81,-119.83 -302.36,-249.02 -451.7,-328.53l-78.33 -40.45c-54.75,44.07 -331.37,352.18 -350.07,446.51 468.29,-198.36 -35.84,2442.68 -1314.99,1633.38z"/>
<path class="fil122" d="M6228.5 4045.78c0.78,53.81 -23.23,88.5 -46.67,141.03 -16.11,36.13 -12.77,29.87 -40.67,72.34 -132.98,202.44 -305.91,160.42 -494.69,182.97 -91.79,10.96 -158.44,-22.84 -213.41,-135.29 -138.4,-283.06 50.54,-483.16 286.65,-369.98 101.89,43.62 100.38,54 226.66,82.9l282.13 26.03z"/>
<path class="fil122" d="M5418.96 2298.56l-188.14 182.21 -225.43 236.76c531.39,179.74 805.77,453.61 1395.48,979.08 376.13,-340.71 826.26,-819.43 1384.13,-972.25 -96.28,-158.86 -236.47,-283.8 -400.75,-390.14 -21.71,-295.47 -93.01,-560.02 -202.94,-800.48 -210.83,155.54 -376.53,320.82 -496.06,496.07 -200.75,-39.41 -386.95,-31.78 -561.04,14.8 -117.53,-193.54 -294.47,-352.08 -496.07,-496.08 -138.4,234.36 -205.41,485.01 -209.18,750.03z"/>
<path class="fil123" d="M5296.62 2417.04l-65.8 63.73 -225.43 236.76c531.39,179.74 805.77,453.61 1395.48,979.08 192.15,-492.44 527.57,-929.99 1109.2,-1273.12 110.05,86.07 204.31,184.35 274.93,300.87 -557.87,152.82 -1008,631.54 -1384.13,972.25 -411.25,-601.43 -781.81,-1037.87 -1104.25,-1279.57z"/>
<path class="fil86" d="M6018.42 1885.17c-207.79,29.82 -420.94,313.33 -599.46,413.39 -7.67,-192.25 68.85,-593.17 211.14,-757.55 115.67,91.61 242.75,198.57 388.32,344.16z"/>
<path class="fil86" d="M7399.24 2344.03c-199.67,-64.85 -424.66,-438.85 -629,-427.72 106.24,-155.58 243.18,-274.57 411.07,-382.57 102.23,128.45 189.13,612.09 217.93,810.29z"/>
<circle class="fil124" cx="4355.68" cy="2693.31" r="315.68"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 40 KiB

79
icons/go-next.svg Normal file
View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="75"
height="75"
viewBox="0 0 19.84375 19.843751"
version="1.1"
id="svg8"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="go-next.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#282929"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.76862745"
inkscape:pageshadow="2"
inkscape:zoom="16.000001"
inkscape:cx="35.333099"
inkscape:cy="37.582065"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:pagecheckerboard="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1">
<sodipodi:guide
position="11.453565,9.9198082"
orientation="0,-1"
id="guide857" />
<sodipodi:guide
position="9.9229088,19.564698"
orientation="1,0"
id="guide837" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="图层 1"
inkscape:groupmode="layer"
id="layer1">
<ellipse
style="opacity:1;fill:#000000;fill-opacity:0.15678;stroke:#ffffff;stroke-width:0.396875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path849"
cx="9.9368067"
cy="9.915391"
rx="8.6764698"
ry="8.9158831" />
<path
style="fill:none;stroke:#ffffff;stroke-width:0.396875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 8.3550725,6.9255115 11.353504,9.9239423 8.3550725,12.91402"
id="path867" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

79
icons/go-previous.svg Normal file
View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="go-previous.svg"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
id="svg8"
version="1.1"
viewBox="0 0 19.84375 19.843751"
height="75"
width="75">
<defs
id="defs2" />
<sodipodi:namedview
inkscape:window-maximized="1"
inkscape:window-y="-9"
inkscape:window-x="-9"
inkscape:window-height="1001"
inkscape:window-width="1920"
inkscape:guide-bbox="true"
showguides="true"
inkscape:pagecheckerboard="false"
units="px"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="px"
inkscape:cy="45.995918"
inkscape:cx="29.794477"
inkscape:zoom="11.313709"
inkscape:pageshadow="2"
inkscape:pageopacity="0.76862745"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#282929"
id="base">
<sodipodi:guide
id="guide857"
orientation="0,-1"
position="11.453565,9.9198082" />
<sodipodi:guide
id="guide837"
orientation="1,0"
position="9.9229088,19.564698" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="图层 1">
<ellipse
ry="8.9158831"
rx="8.6764698"
cy="9.915391"
cx="9.9368067"
id="path849"
style="opacity:1;fill:#000000;fill-opacity:0.15678;stroke:#ffffff;stroke-width:0.396875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path867"
d="M 11.494016,6.9255115 8.4955849,9.9239423 11.494016,12.91402"
style="fill:none;stroke:#ffffff;stroke-width:0.396875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,10 +1,168 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>AboutDialog</name>
<message>
<location filename="../aboutdialog.cpp" line="22"/>
<source>About</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="25"/>
<source>Launch application with image file path as argument to load the file.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="26"/>
<source>Drag and drop image file onto the window is also supported.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="27"/>
<source>Context menu option explanation:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="31"/>
<source>Make window stay on top of all other windows.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="35"/>
<source>Avoid close window accidentally. (eg. by double clicking the window)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="44"/>
<source>Version: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="47"/>
<source>Copyright (c) 2020 %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="49"/>
<source>Logo designed by %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="51"/>
<source>Built with Qt %1 (%2)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="52"/>
<source>Source code</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="58"/>
<source>Contributors</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="60"/>
<source>List of contributors on GitHub</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="61"/>
<source>Thanks to all people who contributed to this project.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="65"/>
<source>Translators</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="66"/>
<source>I would like to thank the following people who volunteered to translate this application.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="131"/>
<source>&amp;Special Thanks</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="133"/>
<source>&amp;Third-party Libraries</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="72"/>
<source>Your Rights</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="74"/>
<source>%1 is released under the MIT License.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="75"/>
<source>This license grants people a number of freedoms:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="76"/>
<source>You are free to use %1, for any purpose</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="77"/>
<source>You are free to distribute %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="78"/>
<source>You can study how %1 works and change it</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="79"/>
<source>You can distribute changed versions of %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="81"/>
<source>The MIT license guarantees you this freedom. Nobody is ever permitted to take it away.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="109"/>
<source>Third-party Libraries used by %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="110"/>
<source>%1 is built on the following free software libraries:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="129"/>
<source>&amp;Help</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="130"/>
<source>&amp;About</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="132"/>
<source>&amp;License</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>GraphicsScene</name>
<message>
<location filename="../graphicsscene.cpp" line="15"/>
<location filename="../graphicsscene.cpp" line="16"/>
<source>Drag image here</source>
<translation type="unfinished"></translation>
</message>
@ -12,22 +170,22 @@
<context>
<name>GraphicsView</name>
<message>
<location filename="../graphicsview.cpp" line="32"/>
<location filename="../graphicsview.cpp" line="254"/>
<source>File url list is empty</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../graphicsview.cpp" line="46"/>
<location filename="../graphicsview.cpp" line="53"/>
<source>File is not a valid image</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../graphicsview.cpp" line="238"/>
<location filename="../graphicsview.cpp" line="262"/>
<source>Image data is invalid</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../graphicsview.cpp" line="245"/>
<location filename="../graphicsview.cpp" line="269"/>
<source>Not supported mimedata: %1</source>
<translation type="unfinished"></translation>
</message>
@ -35,52 +193,100 @@
<context>
<name>MainWindow</name>
<message>
<location filename="../mainwindow.cpp" line="229"/>
<location filename="../mainwindow.cpp" line="248"/>
<location filename="../mainwindow.cpp" line="175"/>
<source>File url list is empty</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="408"/>
<source>&amp;Copy</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="429"/>
<source>Copy P&amp;ixmap</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="434"/>
<source>Copy &amp;File Path</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="444"/>
<source>&amp;Paste Image</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="450"/>
<source>&amp;Paste Image File</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="30"/>
<location filename="../mainwindow.cpp" line="455"/>
<source>Stay on top</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="235"/>
<location filename="../mainwindow.cpp" line="249"/>
<location filename="../aboutdialog.cpp" line="34"/>
<location filename="../mainwindow.cpp" line="462"/>
<source>Protected mode</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="241"/>
<location filename="../mainwindow.cpp" line="469"/>
<source>Configure...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="476"/>
<source>Help</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsDialog</name>
<message>
<location filename="../mainwindow.cpp" line="244"/>
<source>Launch application with image file path as argument to load the file.</source>
<location filename="../settingsdialog.cpp" line="15"/>
<source>Settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="245"/>
<source>Drag and drop image file onto the window is also supported.</source>
<location filename="../settingsdialog.cpp" line="20"/>
<source>Do nothing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="247"/>
<source>Context menu option explanation:</source>
<location filename="../settingsdialog.cpp" line="21"/>
<source>Close the window</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="248"/>
<source>Make window stay on top of all other windows.</source>
<location filename="../settingsdialog.cpp" line="22"/>
<source>Toggle maximize</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="249"/>
<source>Avoid close window accidentally. (eg. by double clicking the window)</source>
<location filename="../settingsdialog.cpp" line="30"/>
<source>Stay on top when start-up</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../settingsdialog.cpp" line="31"/>
<source>Double-click behavior</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>main</name>
<message>
<location filename="../main.cpp" line="18"/>
<location filename="../main.cpp" line="27"/>
<source>Pineapple Pictures</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../main.cpp" line="31"/>
<source>File list.</source>
<translation type="unfinished"></translation>
</message>

View File

@ -1,10 +1,168 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<context>
<name>AboutDialog</name>
<message>
<location filename="../aboutdialog.cpp" line="22"/>
<source>About</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="25"/>
<source>Launch application with image file path as argument to load the file.</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="26"/>
<source>Drag and drop image file onto the window is also supported.</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="27"/>
<source>Context menu option explanation:</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="31"/>
<source>Make window stay on top of all other windows.</source>
<translation>使</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="35"/>
<source>Avoid close window accidentally. (eg. by double clicking the window)</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="44"/>
<source>Version: %1</source>
<translation>: %1</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="47"/>
<source>Copyright (c) 2020 %1</source>
<translation> (c) 2020 %1</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="49"/>
<source>Logo designed by %1</source>
<translation>Logo %1 </translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="51"/>
<source>Built with Qt %1 (%2)</source>
<translation>使 Qt %1 (%2) </translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="52"/>
<source>Source code</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="58"/>
<source>Contributors</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="60"/>
<source>List of contributors on GitHub</source>
<translation>GitHub </translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="61"/>
<source>Thanks to all people who contributed to this project.</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="65"/>
<source>Translators</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="66"/>
<source>I would like to thank the following people who volunteered to translate this application.</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="131"/>
<source>&amp;Special Thanks</source>
<translation>(&amp;S)</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="133"/>
<source>&amp;Third-party Libraries</source>
<translation>(&amp;T)</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="72"/>
<source>Your Rights</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="74"/>
<source>%1 is released under the MIT License.</source>
<translation>%1 MIT </translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="75"/>
<source>This license grants people a number of freedoms:</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="76"/>
<source>You are free to use %1, for any purpose</source>
<translation>使 %1</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="77"/>
<source>You are free to distribute %1</source>
<translation> %1</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="78"/>
<source>You can study how %1 works and change it</source>
<translation> %1 </translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="79"/>
<source>You can distribute changed versions of %1</source>
<translation> %1 </translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="81"/>
<source>The MIT license guarantees you this freedom. Nobody is ever permitted to take it away.</source>
<translation> MIT </translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="109"/>
<source>Third-party Libraries used by %1</source>
<translation>%1 使</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="110"/>
<source>%1 is built on the following free software libraries:</source>
<translation>%1 </translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="129"/>
<source>&amp;Help</source>
<translation>(&amp;H)</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="130"/>
<source>&amp;About</source>
<translation>(&amp;A)</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="132"/>
<source>&amp;License</source>
<translation>(&amp;L)</translation>
</message>
</context>
<context>
<name>GraphicsScene</name>
<message>
<location filename="../graphicsscene.cpp" line="15"/>
<location filename="../graphicsscene.cpp" line="16"/>
<source>Drag image here</source>
<translation></translation>
</message>
@ -12,22 +170,22 @@
<context>
<name>GraphicsView</name>
<message>
<location filename="../graphicsview.cpp" line="32"/>
<location filename="../graphicsview.cpp" line="254"/>
<source>File url list is empty</source>
<translation> URL </translation>
</message>
<message>
<location filename="../graphicsview.cpp" line="46"/>
<location filename="../graphicsview.cpp" line="53"/>
<source>File is not a valid image</source>
<translation></translation>
</message>
<message>
<location filename="../graphicsview.cpp" line="238"/>
<location filename="../graphicsview.cpp" line="262"/>
<source>Image data is invalid</source>
<translation></translation>
</message>
<message>
<location filename="../graphicsview.cpp" line="245"/>
<location filename="../graphicsview.cpp" line="269"/>
<source>Not supported mimedata: %1</source>
<translation> MimeData %1</translation>
</message>
@ -35,52 +193,100 @@
<context>
<name>MainWindow</name>
<message>
<location filename="../mainwindow.cpp" line="229"/>
<location filename="../mainwindow.cpp" line="248"/>
<location filename="../mainwindow.cpp" line="175"/>
<source>File url list is empty</source>
<translation> URL </translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="408"/>
<source>&amp;Copy</source>
<translation>(&amp;C)</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="429"/>
<source>Copy P&amp;ixmap</source>
<translation>(&amp;I)</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="434"/>
<source>Copy &amp;File Path</source>
<translation>(&amp;F)</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="444"/>
<source>&amp;Paste Image</source>
<translation>(&amp;P)</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="450"/>
<source>&amp;Paste Image File</source>
<translation>(&amp;P)</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="30"/>
<location filename="../mainwindow.cpp" line="455"/>
<source>Stay on top</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="235"/>
<location filename="../mainwindow.cpp" line="249"/>
<location filename="../aboutdialog.cpp" line="34"/>
<location filename="../mainwindow.cpp" line="462"/>
<source>Protected mode</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="241"/>
<location filename="../mainwindow.cpp" line="469"/>
<source>Configure...</source>
<translation>...</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="476"/>
<source>Help</source>
<translation></translation>
</message>
</context>
<context>
<name>SettingsDialog</name>
<message>
<location filename="../mainwindow.cpp" line="244"/>
<source>Launch application with image file path as argument to load the file.</source>
<translation></translation>
<location filename="../settingsdialog.cpp" line="15"/>
<source>Settings</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="245"/>
<source>Drag and drop image file onto the window is also supported.</source>
<translation></translation>
<location filename="../settingsdialog.cpp" line="20"/>
<source>Do nothing</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="247"/>
<source>Context menu option explanation:</source>
<translation></translation>
<location filename="../settingsdialog.cpp" line="21"/>
<source>Close the window</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="248"/>
<source>Make window stay on top of all other windows.</source>
<translation>使</translation>
<location filename="../settingsdialog.cpp" line="22"/>
<source>Toggle maximize</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="249"/>
<source>Avoid close window accidentally. (eg. by double clicking the window)</source>
<translation></translation>
<location filename="../settingsdialog.cpp" line="30"/>
<source>Stay on top when start-up</source>
<translation></translation>
</message>
<message>
<location filename="../settingsdialog.cpp" line="31"/>
<source>Double-click behavior</source>
<translation></translation>
</message>
</context>
<context>
<name>main</name>
<message>
<location filename="../main.cpp" line="18"/>
<location filename="../main.cpp" line="27"/>
<source>Pineapple Pictures</source>
<translation></translation>
</message>
<message>
<location filename="../main.cpp" line="31"/>
<source>File list.</source>
<translation></translation>
</message>

View File

@ -1,17 +1,30 @@
#include "mainwindow.h"
#include <QApplication>
#include <QCommandLineParser>
#include <QDir>
#include <QTranslator>
#include <QUrl>
// QM_FILE_INSTALL_DIR should be defined from the CMakeLists file.
#ifndef QM_FILE_INSTALL_DIR
#define QM_FILE_INSTALL_DIR ":/i18n/"
#endif // QM_FILE_INSTALL_DIR
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// since we did `CONFIG += lrelease embed_translations`...
QTranslator translator;
translator.load(QString("PineapplePictures_%1").arg(QLocale::system().name()), ":/i18n/");
QString qmDir;
#ifdef _WIN32
qmDir = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath("translations");
#else
qmDir = QT_STRINGIFY(QM_FILE_INSTALL_DIR);
#endif
translator.load(QString("PineapplePictures_%1").arg(QLocale::system().name()), qmDir);
a.installTranslator(&translator);
a.setApplicationName("Pineapple Pictures");
a.setApplicationDisplayName(QCoreApplication::translate("main", "Pineapple Pictures"));
// parse commandline arguments
QCommandLineParser parser;
@ -24,6 +37,14 @@ int main(int argc, char *argv[])
QList<QUrl> urlList;
for (const QString & str : urlStrList) {
QUrl url = QUrl::fromLocalFile(str);
#ifdef Q_OS_WIN
// TODO: remove this workaround when M$ change the "wsl$" hostname.
if (Q_UNLIKELY(str.startsWith(R"(\\wsl$\)"))) {
url.clear();
url.setScheme(QStringLiteral("qtbug-86277"));
url.setPath(str);
}
#endif // Q_OS_WIN
if (url.isValid()) {
urlList.append(url);
}

View File

@ -1,10 +1,15 @@
#include "mainwindow.h"
#include "settings.h"
#include "toolbutton.h"
#include "bottombuttongroup.h"
#include "graphicsview.h"
#include "navigatorview.h"
#include "graphicsscene.h"
#include "settingsdialog.h"
#include "aboutdialog.h"
#include "metadatadialog.h"
#include "metadatamodel.h"
#include <QMouseEvent>
#include <QMovie>
@ -17,6 +22,8 @@
#include <QShortcut>
#include <QDir>
#include <QCollator>
#include <QClipboard>
#include <QMimeData>
#ifdef _WIN32
#include <windows.h>
@ -25,10 +32,16 @@
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
if (Settings::instance()->stayOnTop()) {
this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinimizeButtonHint | Qt::WindowStaysOnTopHint);
} else {
this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinimizeButtonHint);
}
this->setAttribute(Qt::WA_TranslucentBackground, true);
this->setMinimumSize(710, 530);
this->setMinimumSize(350, 330);
this->setWindowIcon(QIcon(":/icons/app-icon.svg"));
this->setMouseTracking(true);
m_fadeOutAnimation = new QPropertyAnimation(this, "windowOpacity");
m_fadeOutAnimation->setDuration(300);
@ -70,25 +83,35 @@ MainWindow::MainWindow(QWidget *parent) :
connect(m_graphicsView, &GraphicsView::requestGallery,
this, &MainWindow::loadGalleryBySingleLocalFile);
m_closeButton = new ToolButton(m_graphicsView);
m_closeButton = new ToolButton(true, m_graphicsView);
m_closeButton->setIcon(QIcon(":/icons/window-close"));
m_closeButton->setIconSize(QSize(50, 50));
connect(m_closeButton, &QAbstractButton::clicked,
this, &MainWindow::closeWindow);
m_prevButton = new ToolButton(false, m_graphicsView);
m_prevButton->setIcon(QIcon(":/icons/go-previous"));
m_prevButton->setIconSize(QSize(75, 75));
m_prevButton->setVisible(false);
m_prevButton->setOpacity(0, false);
m_nextButton = new ToolButton(false, m_graphicsView);
m_nextButton->setIcon(QIcon(":/icons/go-next"));
m_nextButton->setIconSize(QSize(75, 75));
m_nextButton->setVisible(false);
m_nextButton->setOpacity(0, false);
connect(m_prevButton, &QAbstractButton::clicked,
this, &MainWindow::galleryPrev);
connect(m_nextButton, &QAbstractButton::clicked,
this, &MainWindow::galleryNext);
m_bottomButtonGroup = new BottomButtonGroup(this);
connect(m_bottomButtonGroup, &BottomButtonGroup::resetToOriginalBtnClicked,
this, [ = ](){ m_graphicsView->resetScale(); });
connect(m_bottomButtonGroup, &BottomButtonGroup::toggleWindowMaximum,
this, [ = ](){
if (isMaximized()) {
showNormal();
} else {
showMaximized();
}
});
this, &MainWindow::toggleMaximize);
connect(m_bottomButtonGroup, &BottomButtonGroup::zoomInBtnClicked,
this, [ = ](){ m_graphicsView->zoomView(1.25); });
connect(m_bottomButtonGroup, &BottomButtonGroup::zoomOutBtnClicked,
@ -107,10 +130,19 @@ MainWindow::MainWindow(QWidget *parent) :
m_gv->setOpacity(0, false);
m_closeButton->setOpacity(0, false);
connect(this, &MainWindow::galleryLoaded, this, [this]() {
m_prevButton->setVisible(isGalleryAvailable());
m_nextButton->setVisible(isGalleryAvailable());
});
QShortcut * quitAppShorucut = new QShortcut(QKeySequence(Qt::Key_Space), this);
connect(quitAppShorucut, &QShortcut::activated,
std::bind(&MainWindow::quitAppAction, this, false));
QShortcut * quitAppShorucut2 = new QShortcut(QKeySequence(Qt::Key_Escape), this);
connect(quitAppShorucut2, &QShortcut::activated,
std::bind(&MainWindow::quitAppAction, this, false));
QShortcut * prevPictureShorucut = new QShortcut(QKeySequence(Qt::Key_PageUp), this);
connect(prevPictureShorucut, &QShortcut::activated,
this, &MainWindow::galleryPrev);
@ -119,6 +151,10 @@ MainWindow::MainWindow(QWidget *parent) :
connect(nextPictureShorucut, &QShortcut::activated,
this, &MainWindow::galleryNext);
QShortcut * fullscreenShorucut = new QShortcut(QKeySequence(QKeySequence::FullScreen), this);
connect(fullscreenShorucut, &QShortcut::activated,
this, &MainWindow::toggleFullscreen);
centerWindow();
}
@ -155,11 +191,11 @@ void MainWindow::adjustWindowSizeBySceneRect()
QSize screenSize = qApp->screenAt(QCursor::pos())->availableSize();
if (screenSize.expandedTo(sceneSize) == screenSize) {
// we can show the picture by increase the window size.
if (screenSize.expandedTo(sceneSizeWithMargins) == screenSize) {
this->resize(sceneSizeWithMargins);
} else {
this->resize(screenSize);
}
QSize finalSize = (screenSize.expandedTo(sceneSizeWithMargins) == screenSize) ?
sceneSizeWithMargins : screenSize;
// We have a very reasonable sizeHint() value ;P
this->resize(finalSize.expandedTo(this->sizeHint()));
// We're sure the window can display the whole thing with 1:1 scale.
// The old window size may cause fitInView call from resize() and the
// above resize() call won't reset the scale back to 1:1, so we
@ -173,12 +209,28 @@ void MainWindow::adjustWindowSizeBySceneRect()
}
}
// can be empty if it is NOT from a local file.
QUrl MainWindow::currentImageFileUrl() const
{
if (m_currentFileIndex != -1) {
return m_files.value(m_currentFileIndex);
}
return QUrl();
}
void MainWindow::clearGallery()
{
m_currentFileIndex = -1;
m_files.clear();
}
void MainWindow::loadGalleryBySingleLocalFile(const QString &path)
{
QFileInfo info(path);
QDir dir(info.path());
QString currentFileName = info.fileName();
QStringList entryList = dir.entryList({"*.jpg", "*.jpeg", "*.png", "*.gif", "*.svg"},
QStringList entryList = dir.entryList({"*.jpg", "*.jpeg", "*.jfif", "*.png", "*.gif", "*.svg", "*.bmp"},
QDir::Files | QDir::NoSymLinks, QDir::NoSort);
QCollator collator;
@ -186,24 +238,34 @@ void MainWindow::loadGalleryBySingleLocalFile(const QString &path)
std::sort(entryList.begin(), entryList.end(), collator);
m_currentFileIndex = -1;
m_files.clear();
clearGallery();
for (int i = 0; i < entryList.count(); i++) {
const QString & oneEntry = entryList.at(i);
m_files.append(QUrl::fromLocalFile(dir.absoluteFilePath(oneEntry)));
if (oneEntry == currentFileName) {
const QString & fileName = entryList.at(i);
const QString & oneEntry = dir.absoluteFilePath(fileName);
QUrl url = QUrl::fromLocalFile(oneEntry);
#ifdef Q_OS_WIN
// TODO: remove this workaround when M$ change the "wsl$" hostname.
// Qt will convert path "\\wsl$\" to "//wsl$/"...
if (Q_UNLIKELY(oneEntry.startsWith(R"(//wsl$/)"))) {
url.clear();
url.setScheme(QStringLiteral("qtbug-86277"));
url.setPath(oneEntry);
}
#endif // Q_OS_WIN
m_files.append(url);
if (fileName == currentFileName) {
m_currentFileIndex = i;
}
}
qDebug() << m_files << m_currentFileIndex;
emit galleryLoaded();
}
void MainWindow::galleryPrev()
{
int count = m_files.count();
if (m_currentFileIndex < 0 || m_files.isEmpty() || m_currentFileIndex >= m_files.count()) {
if (!isGalleryAvailable()) {
return;
}
@ -215,7 +277,7 @@ void MainWindow::galleryPrev()
void MainWindow::galleryNext()
{
int count = m_files.count();
if (m_currentFileIndex < 0 || m_files.isEmpty() || m_currentFileIndex >= m_files.count()) {
if (!isGalleryAvailable()) {
return;
}
@ -224,6 +286,14 @@ void MainWindow::galleryNext()
m_graphicsView->showFileFromUrl(m_files.at(m_currentFileIndex), false);
}
bool MainWindow::isGalleryAvailable()
{
if (m_currentFileIndex < 0 || m_files.isEmpty() || m_currentFileIndex >= m_files.count()) {
return false;
}
return true;
}
void MainWindow::showEvent(QShowEvent *event)
{
updateWidgetsPosition();
@ -237,6 +307,8 @@ void MainWindow::enterEvent(QEvent *event)
m_gv->setOpacity(1);
m_closeButton->setOpacity(1);
m_prevButton->setOpacity(1);
m_nextButton->setOpacity(1);
return QMainWindow::enterEvent(event);
}
@ -247,6 +319,8 @@ void MainWindow::leaveEvent(QEvent *event)
m_gv->setOpacity(0);
m_closeButton->setOpacity(0);
m_prevButton->setOpacity(0);
m_nextButton->setOpacity(0);
return QMainWindow::leaveEvent(event);
}
@ -256,8 +330,8 @@ void MainWindow::mousePressEvent(QMouseEvent *event)
if (event->buttons() & Qt::LeftButton && !isMaximized()) {
m_clickedOnWindow = true;
m_oldMousePos = event->pos();
qDebug() << m_oldMousePos << m_graphicsView->transform().m11()
<< m_graphicsView->transform().m22() << m_graphicsView->matrix().m12();
// qDebug() << m_oldMousePos << m_graphicsView->transform().m11()
// << m_graphicsView->transform().m22() << m_graphicsView->matrix().m12();
event->accept();
}
@ -266,7 +340,7 @@ void MainWindow::mousePressEvent(QMouseEvent *event)
void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton && m_clickedOnWindow) {
if (event->buttons() & Qt::LeftButton && m_clickedOnWindow && !isMaximized()) {
move(event->globalPos() - m_oldMousePos);
event->accept();
}
@ -283,17 +357,43 @@ void MainWindow::mouseReleaseEvent(QMouseEvent *event)
void MainWindow::mouseDoubleClickEvent(QMouseEvent *event)
{
quitAppAction();
switch (Settings::instance()->doubleClickBehavior()) {
case ActionCloseWindow:
quitAppAction();
event->accept();
break;
case ActionMaximizeWindow:
toggleMaximize();
event->accept();
break;
case ActionDoNothing:
break;
}
return QMainWindow::mouseDoubleClickEvent(event);
// blumia: don't call parent constructor here, seems it will cause mouse move
// event get called even if we set event->accept();
// return QMainWindow::mouseDoubleClickEvent(event);
}
void MainWindow::wheelEvent(QWheelEvent *event)
{
if (event->delta() > 0) {
m_graphicsView->zoomView(1.25);
QPoint numDegrees = event->angleDelta() / 8;
bool needZoom = false, zoomIn = false;
// NOTE: Only checking angleDelta since the QWheelEvent::pixelDelta() doc says
// pixelDelta() value is driver specific and unreliable on X11...
// We are not scrolling the canvas, just zoom in or out, so it probably
// doesn't matter here.
if (!numDegrees.isNull() && numDegrees.y() != 0) {
needZoom = true;
zoomIn = numDegrees.y() > 0;
}
if (needZoom) {
m_graphicsView->zoomView(zoomIn ? 1.25 : 0.8);
event->accept();
} else {
m_graphicsView->zoomView(0.8);
QMainWindow::wheelEvent(event);
}
}
@ -307,34 +407,112 @@ void MainWindow::resizeEvent(QResizeEvent *event)
void MainWindow::contextMenuEvent(QContextMenuEvent *event)
{
QMenu * menu = new QMenu;
QMenu * copyMenu = new QMenu(tr("&Copy"));
QUrl currentFileUrl = currentImageFileUrl();
QImage clipboardImage;
QUrl clipboardFileUrl;
const QMimeData * clipboardData = QApplication::clipboard()->mimeData();
if (clipboardData->hasImage()) {
QVariant imageVariant(clipboardData->imageData());
if (imageVariant.isValid()) {
clipboardImage = qvariant_cast<QImage>(imageVariant);
}
} else if (clipboardData->hasText()) {
QString clipboardText(clipboardData->text());
if (clipboardText.startsWith("PICTURE:")) {
QString maybeFilename(clipboardText.mid(8));
if (QFile::exists(maybeFilename)) {
clipboardFileUrl = QUrl::fromLocalFile(maybeFilename);
}
}
}
QAction * copyPixmap = new QAction(tr("Copy P&ixmap"));
connect(copyPixmap, &QAction::triggered, this, [ = ](){
QClipboard *cb = QApplication::clipboard();
cb->setPixmap(m_graphicsView->scene()->renderToPixmap());
});
QAction * copyFilePath = new QAction(tr("Copy &File Path"));
connect(copyFilePath, &QAction::triggered, this, [ = ](){
QClipboard *cb = QApplication::clipboard();
cb->setText(currentFileUrl.toLocalFile());
});
copyMenu->addAction(copyPixmap);
if (currentFileUrl.isValid()) {
copyMenu->addAction(copyFilePath);
}
QAction * pasteImage = new QAction(tr("&Paste Image"));
connect(pasteImage, &QAction::triggered, this, [ = ](){
clearGallery();
m_graphicsView->showImage(clipboardImage);
});
QAction * pasteImageFile = new QAction(tr("&Paste Image File"));
connect(pasteImageFile, &QAction::triggered, this, [ = ](){
m_graphicsView->showFileFromUrl(clipboardFileUrl, true);
});
QAction * stayOnTopMode = new QAction(tr("Stay on top"));
connect(stayOnTopMode, &QAction::triggered, this, [ = ](){
toggleStayOnTop();
});
stayOnTopMode->setCheckable(true);
stayOnTopMode->setChecked(stayOnTop());
QAction * protectedMode = new QAction(tr("Protected mode"));
connect(protectedMode, &QAction::triggered, this, [ = ](){
toggleProtectedMode();
});
protectedMode->setCheckable(true);
protectedMode->setChecked(m_protectedMode);
QAction * toggleSettings = new QAction(tr("Configure..."));
connect(toggleSettings, &QAction::triggered, this, [ = ](){
SettingsDialog * sd = new SettingsDialog(this);
sd->exec();
sd->deleteLater();
});
QAction * helpAction = new QAction(tr("Help"));
connect(helpAction, &QAction::triggered, this, [ = ](){
QStringList sl {
tr("Launch application with image file path as argument to load the file."),
tr("Drag and drop image file onto the window is also supported."),
"",
tr("Context menu option explanation:"),
(tr("Stay on top") + " : " + tr("Make window stay on top of all other windows.")),
(tr("Protected mode") + " : " + tr("Avoid close window accidentally. (eg. by double clicking the window)"))
};
m_graphicsView->showText(sl.join('\n'));
AboutDialog * ad = new AboutDialog(this);
ad->exec();
ad->deleteLater();
});
QAction * propertiesAction = new QAction(tr("Properties"));
connect(propertiesAction, &QAction::triggered, this, [ = ](){
MetadataModel * md = new MetadataModel();
md->setFile(currentFileUrl.toLocalFile());
MetadataDialog * ad = new MetadataDialog(this);
ad->setMetadataModel(md);
ad->exec();
ad->deleteLater();
});
if (copyMenu->actions().count() == 1) {
menu->addActions(copyMenu->actions());
} else {
menu->addMenu(copyMenu);
}
if (!clipboardImage.isNull()) {
menu->addAction(pasteImage);
} else if (clipboardFileUrl.isValid()) {
menu->addAction(pasteImageFile);
}
menu->addSeparator();
menu->addAction(stayOnTopMode);
menu->addAction(protectedMode);
menu->addSeparator();
menu->addAction(toggleSettings);
menu->addAction(helpAction);
if (currentFileUrl.isValid()) {
menu->addSeparator();
menu->addAction(propertiesAction);
}
menu->exec(mapToGlobal(event->pos()));
menu->deleteLater();
@ -428,6 +606,11 @@ bool MainWindow::nativeEvent(const QByteArray &eventType, void *message, long *r
#endif // _WIN32
}
QSize MainWindow::sizeHint() const
{
return QSize(710, 530);
}
void MainWindow::centerWindow()
{
this->setGeometry(
@ -453,6 +636,9 @@ void MainWindow::closeWindow()
void MainWindow::updateWidgetsPosition()
{
m_closeButton->move(width() - m_closeButton->width(), 0);
m_prevButton->move(25, (height() - m_prevButton->height()) / 2);
m_nextButton->move(width() - m_nextButton->width() - 25,
(height() - m_prevButton->height()) / 2);
m_bottomButtonGroup->move((width() - m_bottomButtonGroup->width()) / 2,
height() - m_bottomButtonGroup->height());
m_gv->move(width() - m_gv->width(), height() - m_gv->height());
@ -462,6 +648,8 @@ void MainWindow::toggleProtectedMode()
{
m_protectedMode = !m_protectedMode;
m_closeButton->setVisible(!m_protectedMode);
m_prevButton->setVisible(!m_protectedMode);
m_nextButton->setVisible(!m_protectedMode);
}
void MainWindow::toggleStayOnTop()
@ -481,3 +669,21 @@ void MainWindow::quitAppAction(bool force)
closeWindow();
}
}
void MainWindow::toggleFullscreen()
{
if (isFullScreen()) {
showNormal();
} else {
showFullScreen();
}
}
void MainWindow::toggleMaximize()
{
if (isMaximized()) {
showNormal();
} else {
showMaximized();
}
}

View File

@ -25,10 +25,16 @@ public:
void showUrls(const QList<QUrl> &urls);
void adjustWindowSizeBySceneRect();
QUrl currentImageFileUrl() const;
void clearGallery();
void loadGalleryBySingleLocalFile(const QString &path);
void galleryPrev();
void galleryNext();
bool isGalleryAvailable();
signals:
void galleryLoaded();
protected slots:
void showEvent(QShowEvent *event) override;
@ -44,6 +50,8 @@ protected slots:
bool nativeEvent(const QByteArray& eventType, void* message, long* result) override;
QSize sizeHint() const override;
void centerWindow();
void closeWindow();
void updateWidgetsPosition();
@ -51,6 +59,8 @@ protected slots:
void toggleStayOnTop();
bool stayOnTop();
void quitAppAction(bool force = false);
void toggleFullscreen();
void toggleMaximize();
private:
QPoint m_oldMousePos;
@ -58,6 +68,8 @@ private:
QPropertyAnimation *m_floatUpAnimation;
QParallelAnimationGroup *m_exitAnimationGroup;
ToolButton *m_closeButton;
ToolButton *m_prevButton;
ToolButton *m_nextButton;
GraphicsView *m_graphicsView;
NavigatorView *m_gv;
BottomButtonGroup *m_bottomButtonGroup;

81
metadata.cpp Normal file
View File

@ -0,0 +1,81 @@
#include "metadata.h"
Section::Section()
{
}
Section::~Section()
{
}
// Warning! this method won't copy anything from the given argument!
// we actually doesn't care about this, but since QList and QMap are not movable
// so there won't be a (not ill-formed) default ctor to use and we won't get a
// proper copy ctor, thus the operator= will be deleted.
// When accessing QMap value if not exist, we just use this to create a new Section.
Section & Section::operator=(const Section &)
{
return *this;
}
int Section::count() const
{
return m_propertiesValueMap.count();
}
void Section::setSectionName(const QString &displayName)
{
m_displayName = displayName;
}
void Section::setValue(PropertyType type, const QString &value)
{
if (!m_propertiesValueMap.contains(type)) {
m_propertyIndexes.append(type);
}
m_propertiesValueMap[type] = value;
}
QString Section::valueAt(int index) const
{
Q_ASSERT(index >= 0 && index < m_propertyIndexes.count());
return m_propertiesValueMap[m_propertyIndexes[index]];
}
QString Section::value(Section::PropertyType type) const
{
return m_propertiesValueMap.value(type, tr("Unknown"));
}
QString Section::propertyName(PropertyType type) const
{
return m_propertiesOverrideDisplayNameMap.value(
type,
m_builtinPropDisplayNameMap.value(type, tr("Unknown"))
);
}
Metadata::Metadata()
{
}
Metadata::~Metadata()
{
}
void Metadata::setSectionName(SectionType type, const QString &displayName)
{
m_sections[type].setSectionName(displayName);
}
void Metadata::setPropertyValue(Metadata::SectionType type, Section::PropertyType propType, QString value)
{
m_sections[type].setValue(propType, value);
}

66
metadata.h Normal file
View File

@ -0,0 +1,66 @@
#ifndef METADATA_H
#define METADATA_H
#include <QMap>
#include <QPair>
#include <QCoreApplication>
class Section
{
Q_DECLARE_TR_FUNCTIONS(Section)
public:
Section();
~Section();
Section & operator=(const Section& other);
// TODO: maybe use QString instead of a enum? different section won't share a same
// enum value in any way...
enum PropertyType : int {
NameProp,
FileSizeProp,
LastModifiedProp,
ImageSizeProp,
};
int count() const;
void setSectionName(const QString & displayName);
void setValue(PropertyType type, const QString & value);
QString value(PropertyType type) const;
QString valueAt(int index) const;
QString propertyName(PropertyType type) const;
private:
QString m_displayName;
QList<PropertyType> m_propertyIndexes;
QMap<PropertyType, QString> m_propertiesValueMap;
QMap<PropertyType, QString> m_propertiesOverrideDisplayNameMap;
const QMap<PropertyType, QString> m_builtinPropDisplayNameMap {
{NameProp, tr("Name")},
{FileSizeProp, tr("File Size")},
{LastModifiedProp, tr("Last Modified")},
{ImageSizeProp, tr("Image Size")},
};
};
class Metadata
{
public:
enum SectionType : int {
GeneralSection,
ExifSection,
};
Metadata();
~Metadata();
private:
QList<SectionType> m_sectionIndexes;
QMap<SectionType, Section> m_sections;
void setSectionName(SectionType type, const QString & displayName);
void setPropertyValue(SectionType type, Section::PropertyType propType, QString value);
};
#endif // METADATA_H

41
metadatadialog.cpp Normal file
View File

@ -0,0 +1,41 @@
#include "metadatadialog.h"
#include <QDialogButtonBox>
#include <QTreeView>
#include <QVBoxLayout>
#include "metadatamodel.h"
MetadataDialog::MetadataDialog(QWidget *parent)
: QDialog(parent)
, m_treeView(new QTreeView(this))
{
// m_treeView->setRootIsDecorated(false);
m_treeView->setIndentation(0);
setWindowTitle(tr("Image Metadata"));
QDialogButtonBox * buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
setLayout(new QVBoxLayout);
layout()->addWidget(m_treeView);
layout()->addWidget(buttonBox);
connect(buttonBox, &QDialogButtonBox::close, this, &QDialog::close);
}
MetadataDialog::~MetadataDialog()
{
}
void MetadataDialog::setMetadataModel(MetadataModel * model)
{
m_treeView->setModel(model);
m_treeView->expandAll();
}
QSize MetadataDialog::sizeHint() const
{
return QSize(520, 350);
}

26
metadatadialog.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef METADATADIALOG_H
#define METADATADIALOG_H
#include <QDialog>
QT_BEGIN_NAMESPACE
class QTreeView;
QT_END_NAMESPACE
class MetadataModel;
class MetadataDialog : public QDialog
{
Q_OBJECT
public:
explicit MetadataDialog(QWidget * parent);
~MetadataDialog() override;
void setMetadataModel(MetadataModel * model);
QSize sizeHint() const override;
private:
QTreeView * m_treeView = nullptr;
};
#endif // METADATADIALOG_H

267
metadatamodel.cpp Normal file
View File

@ -0,0 +1,267 @@
#include "metadatamodel.h"
#include <QFileInfo>
#include <QDateTime>
#include <QLocale>
#include <QSize>
#include <QImageReader>
// This model is very similar to imagemetainfomodel.cpp in
// Gwenview, since We don't care about ABI here and we won't
// have any KDE lib as dept (KIO is a tier 3 framework from
// KDE for example) which might cause this project no longer
// tiny and easy to maintain, I'll just create a simpler
// implementation than do a simple copy-paste...
enum Sections : int {
GeneralSection,
ExifSection,
CustomSection = 61,
};
class MetadataSection
{
public:
class Entry
{
public:
// key: for internal indexing.
// label: display name of the key.
Entry(const QString & key, const QString & label, const QString & value)
: m_key(key)
, m_label(label.trimmed())
, m_value(value.trimmed())
{}
QString m_key;
QString m_label;
QString m_value;
};
MetadataSection(const QString & sectionName)
: m_sectionName(sectionName)
{}
~MetadataSection()
{
qDeleteAll(m_entries);
}
void clear()
{
qDeleteAll(m_entries);
m_entries.clear();
}
void addEnrty(const QString & key, const QString & label, const QString & value = QString())
{
Entry * e = new Entry(key, label, value);
m_entries << e;
m_keyEntryMap[e->m_key] = m_entries.size() - 1;
}
QString keyAt(int index) const
{
Q_ASSERT(index < m_entries.size());
return m_entries[index]->m_key;
}
QString valueAt(int index) const
{
Q_ASSERT(index < m_entries.size());
return m_entries[index]->m_value;
}
void setValueAt(int index, const QString & value)
{
Q_ASSERT(index < m_entries.size());
m_entries[index]->m_value = value;
}
QString labelAt(int index) const
{
Q_ASSERT(index < m_entries.size());
return m_entries[index]->m_label;
}
int keyIndex(const QString & key) {
return m_keyEntryMap.value(key, -1);
}
QString sectionName() const
{
return m_sectionName;
}
int size() const
{
return m_entries.size();
}
private:
QList<Entry*> m_entries;
QHash<QString, int> m_keyEntryMap;
QString m_sectionName;
};
MetadataModel::MetadataModel()
{
sectionRegister(GeneralSection, new MetadataSection(tr("General", "General info about the image, section name in metadata dialog")));
initGeneralSection();
}
MetadataModel::~MetadataModel()
{
qDeleteAll(m_sections);
}
void MetadataModel::setFile(const QString &path)
{
QFileInfo fileInfo(path);
const QString sizeString = QLocale().formattedDataSize(fileInfo.size());
const QString timeString = QLocale().toString(fileInfo.lastModified(), QLocale::LongFormat);
// FIXME: this implementation is very dirty!
QImageReader imgReader(path);
setImageSize(imgReader.size());
setSectionEntryValue(GeneralSection, QStringLiteral("General.Name"), fileInfo.fileName());
setSectionEntryValue(GeneralSection, QStringLiteral("General.Size"), sizeString);
setSectionEntryValue(GeneralSection, QStringLiteral("General.Time"), timeString);
}
void MetadataModel::setImageSize(const QSize &size)
{
QString imageSize;
if (size.isValid()) {
imageSize = tr("%1 x %2").arg(QString::number(size.width()), QString::number(size.height()));
} else {
imageSize = QLatin1Char('-');
}
setSectionEntryValue(GeneralSection, QStringLiteral("General.ImageSize"), imageSize);
}
QModelIndex MetadataModel::index(int row, int col, const QModelIndex &parent) const
{
if (col < 0 || col > 1) {
return QModelIndex();
}
if (!parent.isValid()) {
// This is a group
if (row < 0 || row >= m_sections.size()) {
return QModelIndex();
}
return createIndex(row, col, col == 0 ? 2 : 3); // ????????
} else {
// This is an entry
int group = parent.row();
if (row < 0 || row >= m_sections[group]->size()) {
return QModelIndex();
}
return createIndex(row, col, static_cast<quintptr>(group));
}
}
QModelIndex MetadataModel::parent(const QModelIndex & index) const
{
if (!index.isValid()) {
return QModelIndex();
}
if (index.internalId() == 2 || index.internalId() == 3) {
return QModelIndex();
} else {
return createIndex(static_cast<int>(index.internalId()), 0, 2);
}
}
int MetadataModel::rowCount(const QModelIndex & parent) const
{
if (!parent.isValid()) {
return m_sections.size();
} else if (parent.internalId() == 2) {
return m_sections[parent.row()]->size();
} else {
return 0;
}
}
int MetadataModel::columnCount(const QModelIndex &) const
{
return 2;
}
QVariant MetadataModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
switch (role) {
case Qt::DisplayRole:
return displayData(index);
default:
return QVariant();
}
}
void MetadataModel::initGeneralSection()
{
MetadataSection * s = section(GeneralSection);
s->addEnrty(QStringLiteral("General.Name"), tr("File Name"));
s->addEnrty(QStringLiteral("General.Size"), tr("File Size"));
s->addEnrty(QStringLiteral("General.Time"), tr("Last Modified"));
s->addEnrty(QStringLiteral("General.ImageSize"), tr("Image Size"));
}
bool MetadataModel::sectionRegister(Sections sectionType, MetadataSection *section)
{
if (m_sectionEnumIndexMap.contains(sectionType)) {
return false;
}
m_sections.append(section);
m_sectionEnumIndexMap[sectionType] = section;
return true;
}
void MetadataModel::setSectionEntryValue(Sections sectionType, const QString &key, const QString &value)
{
MetadataSection * s = section(sectionType);
int entryIndex = s->keyIndex(key);
if (entryIndex < 0) {
// no such entry
return;
}
s->setValueAt(entryIndex, value);
}
MetadataSection *MetadataModel::section(Sections sectionType)
{
return m_sectionEnumIndexMap[sectionType];
}
QVariant MetadataModel::displayData(const QModelIndex &index) const
{
if (index.internalId() == 2) {
if (index.column() != 0) {
return QVariant();
}
QString label = m_sections[index.row()]->sectionName();
return QVariant(label);
}
if (index.internalId() == 3) {
return QString();
}
MetadataSection* group = m_sections[index.internalId()];
if (index.column() == 0) {
return group->labelAt(index.row());
} else {
return group->valueAt(index.row());
}
}

36
metadatamodel.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef METADATAMODEL_H
#define METADATAMODEL_H
#include <QVector>
#include <QAbstractItemModel>
enum Sections : int;
class MetadataSection;
class MetadataModel : public QAbstractItemModel
{
Q_OBJECT
public:
MetadataModel();
~MetadataModel() override;
void setFile(const QString & url);
void setImageSize(const QSize & size);
QModelIndex index(int row, int col, const QModelIndex& parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex & index) const override;
int rowCount(const QModelIndex & parent = QModelIndex()) const override;
int columnCount(const QModelIndex & = QModelIndex()) const override;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
private:
QList<MetadataSection *> m_sections; // associated pointers free at destructor function.
QMap<Sections, MetadataSection *> m_sectionEnumIndexMap; // pointer shared with m_sections
void initGeneralSection();
bool sectionRegister(Sections sectionType, MetadataSection * section);
void setSectionEntryValue(Sections sectionType, const QString & key, const QString & value);
MetadataSection * section(Sections sectionType);
QVariant displayData(const QModelIndex & index) const;
};
#endif // METADATAMODEL_H

View File

@ -4,11 +4,9 @@
#
#-------------------------------------------------
QT += core gui svg
QT += core widgets gui svg
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = PineapplePictures
TARGET = ppic
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
@ -25,23 +23,35 @@ DEFINES += QT_DEPRECATED_WARNINGS
CONFIG += c++11 lrelease embed_translations
SOURCES += \
aboutdialog.cpp \
main.cpp \
mainwindow.cpp \
graphicsview.cpp \
bottombuttongroup.cpp \
graphicsscene.cpp \
metadata.cpp \
metadatadialog.cpp \
metadatamodel.cpp \
navigatorview.cpp \
opacityhelper.cpp \
toolbutton.cpp
toolbutton.cpp \
settings.cpp \
settingsdialog.cpp
HEADERS += \
aboutdialog.h \
mainwindow.h \
graphicsview.h \
bottombuttongroup.h \
graphicsscene.h \
metadata.h \
metadatadialog.h \
metadatamodel.h \
navigatorview.h \
opacityhelper.h \
toolbutton.h
toolbutton.h \
settings.h \
settingsdialog.h
TRANSLATIONS = \
languages/PineapplePictures.ts \
@ -55,6 +65,11 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
RESOURCES += \
resources.qrc
# Generate fron svg:
# magick convert -background none ./app-icon.svg -define icon:auto-resize=64,48,32,16 app-icon.ico
# Generate from svg:
# magick convert -background none app-icon.svg -define icon:auto-resize="16,32,48,64,128,256" app-icon.ico
RC_ICONS = 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) 2020 Gary Wang

17
pineapple-pictures.rc Normal file
View File

@ -0,0 +1,17 @@
IDI_ICON1 ICON DISCARDABLE "icons/app-icon.ico"
1 VERSIONINFO
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "FileDescription", "Pineapple Pictures - Image Viewer"
VALUE "LegalCopyright", "MIT/Expat License - Copyright (C) 2020 Gary Wang"
VALUE "ProductName", "Pineapple Pictures"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

View File

@ -8,5 +8,7 @@
<file>icons/view-background-checkerboard.svg</file>
<file>icons/app-icon.svg</file>
<file>icons/window-close.svg</file>
<file>icons/go-next.svg</file>
<file>icons/go-previous.svg</file>
</qresource>
</RCC>

86
settings.cpp Normal file
View File

@ -0,0 +1,86 @@
#include "settings.h"
#include <QApplication>
#include <QStandardPaths>
#include <QDebug>
#include <QDir>
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()
{
return m_qsettings->value("stay_on_top", true).toBool();
}
DoubleClickBehavior Settings::doubleClickBehavior()
{
QString result = m_qsettings->value("double_click_behavior", "close").toString().toLower();
return stringToDoubleClickBehavior(result);
}
void Settings::setStayOnTop(bool on)
{
m_qsettings->setValue("stay_on_top", on);
m_qsettings->sync();
}
void Settings::setDoubleClickBehavior(DoubleClickBehavior dcb)
{
m_qsettings->setValue("double_click_behavior", doubleClickBehaviorToString(dcb));
m_qsettings->sync();
}
QString Settings::doubleClickBehaviorToString(DoubleClickBehavior dcb)
{
static QMap<DoubleClickBehavior, QString> _map {
{ActionCloseWindow, "close"},
{ActionMaximizeWindow, "maximize"},
{ActionDoNothing, "ignore"}
};
return _map.value(dcb, "close");
}
DoubleClickBehavior Settings::stringToDoubleClickBehavior(QString str)
{
static QMap<QString, DoubleClickBehavior> _map {
{"close", ActionCloseWindow},
{"maximize", ActionMaximizeWindow},
{"ignore", ActionDoNothing}
};
return _map.value(str, ActionCloseWindow);
}
Settings::Settings()
: QObject(qApp)
{
QString configPath;
#ifdef FLAG_PORTABLE_MODE_SUPPORT
QString portableConfigDirPath = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath("data");
QFileInfo portableConfigDirInfo(portableConfigDirPath);
if (portableConfigDirInfo.exists() && portableConfigDirInfo.isDir() && portableConfigDirInfo.isWritable()) {
// we can use it.
configPath = portableConfigDirPath;
}
#endif // FLAG_PORTABLE_MODE_SUPPORT
// %LOCALAPPDATA% under Windows.
if (configPath.isEmpty()) {
configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
}
m_qsettings = new QSettings(QDir(configPath).absoluteFilePath("config.ini"), QSettings::IniFormat, this);
}

41
settings.h Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#include <QObject>
#include <QSettings>
enum DoubleClickBehavior {
ActionDoNothing,
ActionCloseWindow,
ActionMaximizeWindow,
ActionStart = ActionDoNothing,
ActionEnd = ActionMaximizeWindow
};
class Settings : public QObject
{
Q_OBJECT
public:
static Settings *instance();
bool stayOnTop();
DoubleClickBehavior doubleClickBehavior();
void setStayOnTop(bool on);
void setDoubleClickBehavior(DoubleClickBehavior dcb);
static QString doubleClickBehaviorToString(DoubleClickBehavior dcb);
static DoubleClickBehavior stringToDoubleClickBehavior(QString str);
private:
Settings();
static Settings *m_settings_instance;
QSettings *m_qsettings;
signals:
public slots:
};

53
settingsdialog.cpp Normal file
View File

@ -0,0 +1,53 @@
#include "settingsdialog.h"
#include "settings.h"
#include <QCheckBox>
#include <QComboBox>
#include <QFormLayout>
#include <QStringListModel>
SettingsDialog::SettingsDialog(QWidget *parent)
: QDialog(parent)
, m_stayOnTop(new QCheckBox)
, m_doubleClickBehavior(new QComboBox)
{
this->setWindowTitle(tr("Settings"));
QFormLayout * settingsForm = new QFormLayout(this);
static QMap<DoubleClickBehavior, QString> _map {
{ ActionDoNothing, tr("Do nothing") },
{ ActionCloseWindow, tr("Close the window") },
{ ActionMaximizeWindow, tr("Toggle maximize") }
};
QStringList dropDown;
for (int dcb = ActionStart; dcb <= ActionEnd; dcb++) {
dropDown.append(_map.value(static_cast<DoubleClickBehavior>(dcb)));
}
settingsForm->addRow(tr("Stay on top when start-up"), m_stayOnTop);
settingsForm->addRow(tr("Double-click behavior"), m_doubleClickBehavior);
m_stayOnTop->setChecked(Settings::instance()->stayOnTop());
m_doubleClickBehavior->setModel(new QStringListModel(dropDown));
DoubleClickBehavior dcb = Settings::instance()->doubleClickBehavior();
m_doubleClickBehavior->setCurrentIndex(static_cast<int>(dcb));
connect(m_stayOnTop, &QCheckBox::stateChanged, this, [ = ](int state){
Settings::instance()->setStayOnTop(state == Qt::Checked);
});
connect(m_doubleClickBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
Settings::instance()->setDoubleClickBehavior(static_cast<DoubleClickBehavior>(index));
});
this->setMinimumSize(300, 61); // not sure why it complain "Unable to set geometry"
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
}
SettingsDialog::~SettingsDialog()
{
}

25
settingsdialog.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef SETTINGSDIALOG_H
#define SETTINGSDIALOG_H
#include <QObject>
#include <QDialog>
class QCheckBox;
class QComboBox;
class SettingsDialog : public QDialog
{
Q_OBJECT
public:
explicit SettingsDialog(QWidget *parent = nullptr);
~SettingsDialog();
signals:
public slots:
private:
QCheckBox * m_stayOnTop = nullptr;
QComboBox * m_doubleClickBehavior = nullptr;
};
#endif // SETTINGSDIALOG_H

View File

@ -6,18 +6,20 @@
#include <QGraphicsOpacityEffect>
#include <QPropertyAnimation>
ToolButton::ToolButton(QWidget *parent)
ToolButton::ToolButton(bool hoverColor, QWidget *parent)
: QPushButton(parent)
, m_opacityHelper(new OpacityHelper(this))
{
setFlat(true);
setFixedSize(50, 50);
setStyleSheet("QPushButton {"
QString qss = "QPushButton {"
"background: transparent;"
"}"
"QPushButton:hover {"
"background: red;"
"}");
"}";
if (hoverColor) {
qss += "QPushButton:hover {"
"background: red;"
"}";
}
setStyleSheet(qss);
}
void ToolButton::setOpacity(qreal opacity, bool animated)

View File

@ -8,7 +8,7 @@ class ToolButton : public QPushButton
{
Q_OBJECT
public:
ToolButton(QWidget * parent = nullptr);
ToolButton(bool hoverColor = false, QWidget * parent = nullptr);
public slots:
void setOpacity(qreal opacity, bool animated = true);