chore: let's use LF all the time
This commit is contained in:
42
LICENSE
42
LICENSE
@ -1,21 +1,21 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2025 BLumia
|
Copyright (c) 2025 BLumia
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
in the Software without restriction, including without limitation the rights
|
in the Software without restriction, including without limitation the rights
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
furnished to do so, subject to the following conditions:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
The above copyright notice and this permission notice shall be included in all
|
||||||
copies or substantial portions of the Software.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
66
REUSE.toml
66
REUSE.toml
@ -1,33 +1,33 @@
|
|||||||
version = 1
|
version = 1
|
||||||
SPDX-PackageName = "Pineapple Pictures"
|
SPDX-PackageName = "Pineapple Pictures"
|
||||||
SPDX-PackageDownloadLocation = "https://github.com/BLumia/pineapple-pictures"
|
SPDX-PackageDownloadLocation = "https://github.com/BLumia/pineapple-pictures"
|
||||||
|
|
||||||
[[annotations]]
|
[[annotations]]
|
||||||
path = [".gitignore", "appveyor.yml", ".github/**"]
|
path = [".gitignore", "appveyor.yml", ".github/**"]
|
||||||
precedence = "aggregate"
|
precedence = "aggregate"
|
||||||
SPDX-FileCopyrightText = "None"
|
SPDX-FileCopyrightText = "None"
|
||||||
SPDX-License-Identifier = "CC0-1.0"
|
SPDX-License-Identifier = "CC0-1.0"
|
||||||
|
|
||||||
[[annotations]]
|
[[annotations]]
|
||||||
path = ["README**.md", "NEWS", "assets/**.rc", "assets/**.qrc", "dist/**"]
|
path = ["README**.md", "NEWS", "assets/**.rc", "assets/**.qrc", "dist/**"]
|
||||||
precedence = "aggregate"
|
precedence = "aggregate"
|
||||||
SPDX-FileCopyrightText = "None"
|
SPDX-FileCopyrightText = "None"
|
||||||
SPDX-License-Identifier = "CC0-1.0"
|
SPDX-License-Identifier = "CC0-1.0"
|
||||||
|
|
||||||
[[annotations]]
|
[[annotations]]
|
||||||
path = ["app/translations/**.ts", "assets/plain/translators.html"]
|
path = ["app/translations/**.ts", "assets/plain/translators.html"]
|
||||||
precedence = "aggregate"
|
precedence = "aggregate"
|
||||||
SPDX-FileCopyrightText = "Translators from hosted.weblate.org"
|
SPDX-FileCopyrightText = "Translators from hosted.weblate.org"
|
||||||
SPDX-License-Identifier = "MIT"
|
SPDX-License-Identifier = "MIT"
|
||||||
|
|
||||||
[[annotations]]
|
[[annotations]]
|
||||||
path = "assets/icons/**.svg"
|
path = "assets/icons/**.svg"
|
||||||
precedence = "aggregate"
|
precedence = "aggregate"
|
||||||
SPDX-FileCopyrightText = "2022 Gary Wang"
|
SPDX-FileCopyrightText = "2022 Gary Wang"
|
||||||
SPDX-License-Identifier = "MIT"
|
SPDX-License-Identifier = "MIT"
|
||||||
|
|
||||||
[[annotations]]
|
[[annotations]]
|
||||||
path = "assets/icons/app-icon.**"
|
path = "assets/icons/app-icon.**"
|
||||||
precedence = "aggregate"
|
precedence = "aggregate"
|
||||||
SPDX-FileCopyrightText = "2020 Lovelyblack"
|
SPDX-FileCopyrightText = "2020 Lovelyblack"
|
||||||
SPDX-License-Identifier = "MIT"
|
SPDX-License-Identifier = "MIT"
|
||||||
|
@ -1,181 +1,181 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "aboutdialog.h"
|
#include "aboutdialog.h"
|
||||||
|
|
||||||
#include <QAbstractButton>
|
#include <QAbstractButton>
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
#include <QTextBrowser>
|
#include <QTextBrowser>
|
||||||
#include <QTabWidget>
|
#include <QTabWidget>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
|
||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
|
|
||||||
AboutDialog::AboutDialog(QWidget *parent)
|
AboutDialog::AboutDialog(QWidget *parent)
|
||||||
: QDialog(parent)
|
: QDialog(parent)
|
||||||
, m_tabWidget(new QTabWidget)
|
, m_tabWidget(new QTabWidget)
|
||||||
, m_buttonBox(new QDialogButtonBox)
|
, m_buttonBox(new QDialogButtonBox)
|
||||||
, m_helpTextEdit(new QTextBrowser)
|
, m_helpTextEdit(new QTextBrowser)
|
||||||
, m_aboutTextEdit(new QTextBrowser)
|
, m_aboutTextEdit(new QTextBrowser)
|
||||||
, m_specialThanksTextEdit(new QTextBrowser)
|
, m_specialThanksTextEdit(new QTextBrowser)
|
||||||
, m_licenseTextEdit(new QTextBrowser)
|
, m_licenseTextEdit(new QTextBrowser)
|
||||||
, m_3rdPartyLibsTextEdit(new QTextBrowser)
|
, m_3rdPartyLibsTextEdit(new QTextBrowser)
|
||||||
{
|
{
|
||||||
this->setWindowTitle(tr("About"));
|
this->setWindowTitle(tr("About"));
|
||||||
|
|
||||||
const QStringList helpStr {
|
const QStringList helpStr {
|
||||||
u"<p>%1</p>"_s.arg(tr("Launch application with image file path as argument to load the file.")),
|
u"<p>%1</p>"_s.arg(tr("Launch application with image file path as argument to load the file.")),
|
||||||
u"<p>%1</p>"_s.arg(tr("Drag and drop image file onto the window is also supported.")),
|
u"<p>%1</p>"_s.arg(tr("Drag and drop image file onto the window is also supported.")),
|
||||||
u"<p>%1</p>"_s.arg(tr("None of the operations in this application will alter the pictures on disk.")),
|
u"<p>%1</p>"_s.arg(tr("None of the operations in this application will alter the pictures on disk.")),
|
||||||
u"<p>%1</p>"_s.arg(tr("Context menu option explanation:")),
|
u"<p>%1</p>"_s.arg(tr("Context menu option explanation:")),
|
||||||
u"<ul>"_s,
|
u"<ul>"_s,
|
||||||
// blumia: Chain two arg() here since it seems lupdate will remove one of them if we use
|
// blumia: Chain two arg() here since it seems lupdate will remove one of them if we use
|
||||||
// the old `arg(QCoreApp::translate(), tr())` way, but it's worth to mention
|
// the old `arg(QCoreApp::translate(), tr())` way, but it's worth to mention
|
||||||
// `arg(QCoreApp::translate(), this->tr())` works, but lupdate will complain about the usage.
|
// `arg(QCoreApp::translate(), this->tr())` works, but lupdate will complain about the usage.
|
||||||
u"<li><b>%1</b>:<br/>%2</li>"_s
|
u"<li><b>%1</b>:<br/>%2</li>"_s
|
||||||
.arg(QCoreApplication::translate("MainWindow", "Stay on top"))
|
.arg(QCoreApplication::translate("MainWindow", "Stay on top"))
|
||||||
.arg(tr("Make window stay on top of all other windows.")),
|
.arg(tr("Make window stay on top of all other windows.")),
|
||||||
u"<li><b>%1</b>:<br/>%2</li>"_s
|
u"<li><b>%1</b>:<br/>%2</li>"_s
|
||||||
.arg(QCoreApplication::translate("MainWindow", "Protected mode"))
|
.arg(QCoreApplication::translate("MainWindow", "Protected mode"))
|
||||||
.arg(tr("Avoid close window accidentally. (eg. by double clicking the window)")),
|
.arg(tr("Avoid close window accidentally. (eg. by double clicking the window)")),
|
||||||
u"<li><b>%1</b>:<br/>%2</li>"_s
|
u"<li><b>%1</b>:<br/>%2</li>"_s
|
||||||
.arg(QCoreApplication::translate("MainWindow", "Keep transformation", "The 'transformation' means the flip/rotation status that currently applied to the image view"))
|
.arg(QCoreApplication::translate("MainWindow", "Keep transformation", "The 'transformation' means the flip/rotation status that currently applied to the image view"))
|
||||||
.arg(tr("Avoid resetting the zoom/rotation/flip state that was applied to the image view when switching between images.")),
|
.arg(tr("Avoid resetting the zoom/rotation/flip state that was applied to the image view when switching between images.")),
|
||||||
u"</ul>"_s
|
u"</ul>"_s
|
||||||
};
|
};
|
||||||
|
|
||||||
const QStringList aboutStr {
|
const QStringList aboutStr {
|
||||||
u"<center><img width='128' height='128' src=':/icons/app-icon.svg'/><br/>"_s,
|
u"<center><img width='128' height='128' src=':/icons/app-icon.svg'/><br/>"_s,
|
||||||
qApp->applicationDisplayName(),
|
qApp->applicationDisplayName(),
|
||||||
(u"<br/>"_s + tr("Version: %1").arg(
|
(u"<br/>"_s + tr("Version: %1").arg(
|
||||||
#ifdef GIT_DESCRIBE_VERSION_STRING
|
#ifdef GIT_DESCRIBE_VERSION_STRING
|
||||||
GIT_DESCRIBE_VERSION_STRING
|
GIT_DESCRIBE_VERSION_STRING
|
||||||
#else
|
#else
|
||||||
qApp->applicationVersion()
|
qApp->applicationVersion()
|
||||||
#endif // GIT_DESCRIBE_VERSION_STRING
|
#endif // GIT_DESCRIBE_VERSION_STRING
|
||||||
)),
|
)),
|
||||||
u"<hr/>"_s,
|
u"<hr/>"_s,
|
||||||
tr("Copyright (c) %1 %2", "%1 is year, %2 is the name of copyright holder(s)")
|
tr("Copyright (c) %1 %2", "%1 is year, %2 is the name of copyright holder(s)")
|
||||||
.arg(u"2025"_s, u"<a href='https://github.com/BLumia'>@BLumia</a>"_s),
|
.arg(u"2025"_s, u"<a href='https://github.com/BLumia'>@BLumia</a>"_s),
|
||||||
u"<br/>"_s,
|
u"<br/>"_s,
|
||||||
tr("Logo designed by %1").arg(u"<a href='https://github.com/Lovelyblack'>@Lovelyblack</a>"_s),
|
tr("Logo designed by %1").arg(u"<a href='https://github.com/Lovelyblack'>@Lovelyblack</a>"_s),
|
||||||
u"<hr/>"_s,
|
u"<hr/>"_s,
|
||||||
tr("Built with Qt %1 (%2)").arg(QT_VERSION_STR, QSysInfo::buildCpuArchitecture()),
|
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("<br/><a href='%1'>%2</a>").arg("https://github.com/BLumia/pineapple-pictures", tr("Source code")),
|
||||||
u"</center>"_s
|
u"</center>"_s
|
||||||
};
|
};
|
||||||
|
|
||||||
QFile translaterHtml(u":/plain/translators.html"_s);
|
QFile translaterHtml(u":/plain/translators.html"_s);
|
||||||
bool canOpenFile = translaterHtml.open(QIODevice::ReadOnly);
|
bool canOpenFile = translaterHtml.open(QIODevice::ReadOnly);
|
||||||
const QByteArray & translatorList = canOpenFile ? translaterHtml.readAll() : QByteArrayLiteral("");
|
const QByteArray & translatorList = canOpenFile ? translaterHtml.readAll() : QByteArrayLiteral("");
|
||||||
|
|
||||||
const QStringList specialThanksStr {
|
const QStringList specialThanksStr {
|
||||||
u"<h1 align='center'>%1</h1><a href='%2'>%3</a><p>%4</p>"_s.arg(
|
u"<h1 align='center'>%1</h1><a href='%2'>%3</a><p>%4</p>"_s.arg(
|
||||||
tr("Contributors"),
|
tr("Contributors"),
|
||||||
u"https://github.com/BLumia/pineapple-pictures/graphs/contributors"_s,
|
u"https://github.com/BLumia/pineapple-pictures/graphs/contributors"_s,
|
||||||
tr("List of contributors on GitHub"),
|
tr("List of contributors on GitHub"),
|
||||||
tr("Thanks to all people who contributed to this project.")
|
tr("Thanks to all people who contributed to this project.")
|
||||||
),
|
),
|
||||||
|
|
||||||
u"<h1 align='center'>%1</h1><p>%2</p>%3"_s.arg(
|
u"<h1 align='center'>%1</h1><p>%2</p>%3"_s.arg(
|
||||||
tr("Translators"),
|
tr("Translators"),
|
||||||
tr("I would like to thank the following people who volunteered to translate this application."),
|
tr("I would like to thank the following people who volunteered to translate this application."),
|
||||||
translatorList
|
translatorList
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
const QStringList licenseStr {
|
const QStringList licenseStr {
|
||||||
u"<h1 align='center'><b>%1</b></h1>"_s.arg(tr("Your Rights")),
|
u"<h1 align='center'><b>%1</b></h1>"_s.arg(tr("Your Rights")),
|
||||||
u"<p>%1</p><p>%2</p><ul><li>%3</li><li>%4</li><li>%5</li><li>%6</li></ul>"_s.arg(
|
u"<p>%1</p><p>%2</p><ul><li>%3</li><li>%4</li><li>%5</li><li>%6</li></ul>"_s.arg(
|
||||||
tr("%1 is released under the MIT License."), // %1
|
tr("%1 is released under the MIT License."), // %1
|
||||||
tr("This license grants people a number of freedoms:"), // %2
|
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 use %1, for any purpose"), // %3
|
||||||
tr("You are free to distribute %1"), // %4
|
tr("You are free to distribute %1"), // %4
|
||||||
tr("You can study how %1 works and change it"), // %5
|
tr("You can study how %1 works and change it"), // %5
|
||||||
tr("You can distribute changed versions of %1") // %6
|
tr("You can distribute changed versions of %1") // %6
|
||||||
).arg(u"<i>%1</i>"_s),
|
).arg(u"<i>%1</i>"_s),
|
||||||
u"<p>%1</p>"_s.arg(tr("The MIT license guarantees you this freedom. Nobody is ever permitted to take it away.")),
|
u"<p>%1</p>"_s.arg(tr("The MIT license guarantees you this freedom. Nobody is ever permitted to take it away.")),
|
||||||
u"<hr/><pre>%2</pre>"_s
|
u"<hr/><pre>%2</pre>"_s
|
||||||
};
|
};
|
||||||
|
|
||||||
const QString mitLicense(QStringLiteral(R"(Expat/MIT License
|
const QString mitLicense(QStringLiteral(R"(Expat/MIT License
|
||||||
|
|
||||||
Copyright (c) 2025 BLumia
|
Copyright (c) 2025 BLumia
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
in the Software without restriction, including without limitation the rights
|
in the Software without restriction, including without limitation the rights
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
furnished to do so, subject to the following conditions:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
The above copyright notice and this permission notice shall be included in all
|
||||||
copies or substantial portions of the Software.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
)"));
|
)"));
|
||||||
|
|
||||||
const QStringList thirdPartyLibsStr {
|
const QStringList thirdPartyLibsStr {
|
||||||
u"<h1 align='center'><b>%1</b></h1>"_s.arg(tr("Third-party Libraries used by %1")),
|
u"<h1 align='center'><b>%1</b></h1>"_s.arg(tr("Third-party Libraries used by %1")),
|
||||||
tr("%1 is built on the following free software libraries:", "Free as in freedom"),
|
tr("%1 is built on the following free software libraries:", "Free as in freedom"),
|
||||||
u"<ul>"_s,
|
u"<ul>"_s,
|
||||||
#ifdef HAVE_EXIV2_VERSION
|
#ifdef HAVE_EXIV2_VERSION
|
||||||
u"<li><a href='%1'>%2</a>: %3</li>"_s.arg("https://www.exiv2.org/", "Exiv2", "GPLv2"),
|
u"<li><a href='%1'>%2</a>: %3</li>"_s.arg("https://www.exiv2.org/", "Exiv2", "GPLv2"),
|
||||||
#endif // EXIV2_VERSION
|
#endif // EXIV2_VERSION
|
||||||
u"<li><a href='%1'>%2</a>: %3</li>"_s.arg("https://www.qt.io/", "Qt", "GPLv2 + GPLv3 + LGPLv2.1 + LGPLv3"),
|
u"<li><a href='%1'>%2</a>: %3</li>"_s.arg("https://www.qt.io/", "Qt", "GPLv2 + GPLv3 + LGPLv2.1 + LGPLv3"),
|
||||||
u"</ul>"_s
|
u"</ul>"_s
|
||||||
};
|
};
|
||||||
|
|
||||||
m_helpTextEdit->setText(helpStr.join('\n'));
|
m_helpTextEdit->setText(helpStr.join('\n'));
|
||||||
|
|
||||||
m_aboutTextEdit->setText(aboutStr.join('\n'));
|
m_aboutTextEdit->setText(aboutStr.join('\n'));
|
||||||
m_aboutTextEdit->setOpenExternalLinks(true);
|
m_aboutTextEdit->setOpenExternalLinks(true);
|
||||||
|
|
||||||
m_specialThanksTextEdit->setText(specialThanksStr.join('\n'));
|
m_specialThanksTextEdit->setText(specialThanksStr.join('\n'));
|
||||||
m_specialThanksTextEdit->setOpenExternalLinks(true);
|
m_specialThanksTextEdit->setOpenExternalLinks(true);
|
||||||
|
|
||||||
m_licenseTextEdit->setText(licenseStr.join('\n').arg(qApp->applicationDisplayName(), mitLicense));
|
m_licenseTextEdit->setText(licenseStr.join('\n').arg(qApp->applicationDisplayName(), mitLicense));
|
||||||
|
|
||||||
m_3rdPartyLibsTextEdit->setText(thirdPartyLibsStr.join('\n').arg(u"<i>%1</i>"_s).arg(qApp->applicationDisplayName()));
|
m_3rdPartyLibsTextEdit->setText(thirdPartyLibsStr.join('\n').arg(u"<i>%1</i>"_s).arg(qApp->applicationDisplayName()));
|
||||||
m_3rdPartyLibsTextEdit->setOpenExternalLinks(true);
|
m_3rdPartyLibsTextEdit->setOpenExternalLinks(true);
|
||||||
|
|
||||||
m_tabWidget->addTab(m_helpTextEdit, tr("&Help"));
|
m_tabWidget->addTab(m_helpTextEdit, tr("&Help"));
|
||||||
m_tabWidget->addTab(m_aboutTextEdit, tr("&About"));
|
m_tabWidget->addTab(m_aboutTextEdit, tr("&About"));
|
||||||
m_tabWidget->addTab(m_specialThanksTextEdit, tr("&Special Thanks"));
|
m_tabWidget->addTab(m_specialThanksTextEdit, tr("&Special Thanks"));
|
||||||
m_tabWidget->addTab(m_licenseTextEdit, tr("&License"));
|
m_tabWidget->addTab(m_licenseTextEdit, tr("&License"));
|
||||||
m_tabWidget->addTab(m_3rdPartyLibsTextEdit, tr("&Third-party Libraries"));
|
m_tabWidget->addTab(m_3rdPartyLibsTextEdit, tr("&Third-party Libraries"));
|
||||||
|
|
||||||
m_buttonBox->setStandardButtons(QDialogButtonBox::Close);
|
m_buttonBox->setStandardButtons(QDialogButtonBox::Close);
|
||||||
connect(m_buttonBox, QOverload<QAbstractButton *>::of(&QDialogButtonBox::clicked), this, [this](){
|
connect(m_buttonBox, QOverload<QAbstractButton *>::of(&QDialogButtonBox::clicked), this, [this](){
|
||||||
this->close();
|
this->close();
|
||||||
});
|
});
|
||||||
|
|
||||||
setLayout(new QVBoxLayout);
|
setLayout(new QVBoxLayout);
|
||||||
|
|
||||||
layout()->addWidget(m_tabWidget);
|
layout()->addWidget(m_tabWidget);
|
||||||
layout()->addWidget(m_buttonBox);
|
layout()->addWidget(m_buttonBox);
|
||||||
|
|
||||||
setMinimumSize(361, 161); // not sure why it complain "Unable to set geometry"
|
setMinimumSize(361, 161); // not sure why it complain "Unable to set geometry"
|
||||||
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
|
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
AboutDialog::~AboutDialog()
|
AboutDialog::~AboutDialog()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize AboutDialog::sizeHint() const
|
QSize AboutDialog::sizeHint() const
|
||||||
{
|
{
|
||||||
return QSize(520, 350);
|
return QSize(520, 350);
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,36 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef ABOUTDIALOG_H
|
#ifndef ABOUTDIALOG_H
|
||||||
#define ABOUTDIALOG_H
|
#define ABOUTDIALOG_H
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
class QTextBrowser;
|
class QTextBrowser;
|
||||||
class QTabWidget;
|
class QTabWidget;
|
||||||
class QDialogButtonBox;
|
class QDialogButtonBox;
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
class AboutDialog : public QDialog
|
class AboutDialog : public QDialog
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit AboutDialog(QWidget *parent = nullptr);
|
explicit AboutDialog(QWidget *parent = nullptr);
|
||||||
~AboutDialog() override;
|
~AboutDialog() override;
|
||||||
|
|
||||||
QSize sizeHint() const override;
|
QSize sizeHint() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QTabWidget * m_tabWidget = nullptr;
|
QTabWidget * m_tabWidget = nullptr;
|
||||||
QDialogButtonBox * m_buttonBox = nullptr;
|
QDialogButtonBox * m_buttonBox = nullptr;
|
||||||
|
|
||||||
QTextBrowser * m_helpTextEdit = nullptr;
|
QTextBrowser * m_helpTextEdit = nullptr;
|
||||||
QTextBrowser * m_aboutTextEdit = nullptr;
|
QTextBrowser * m_aboutTextEdit = nullptr;
|
||||||
QTextBrowser * m_specialThanksTextEdit = nullptr;
|
QTextBrowser * m_specialThanksTextEdit = nullptr;
|
||||||
QTextBrowser * m_licenseTextEdit = nullptr;
|
QTextBrowser * m_licenseTextEdit = nullptr;
|
||||||
QTextBrowser * m_3rdPartyLibsTextEdit = nullptr;
|
QTextBrowser * m_3rdPartyLibsTextEdit = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ABOUTDIALOG_H
|
#endif // ABOUTDIALOG_H
|
||||||
|
@ -1,153 +1,153 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "actionmanager.h"
|
#include "actionmanager.h"
|
||||||
|
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QSvgRenderer>
|
#include <QSvgRenderer>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
||||||
#define ICON_NAME(name)\
|
#define ICON_NAME(name)\
|
||||||
QStringLiteral(":/icons/" #name ".svg")
|
QStringLiteral(":/icons/" #name ".svg")
|
||||||
|
|
||||||
#define ACTION_NAME(s) QStringLiteral(STRIFY(s))
|
#define ACTION_NAME(s) QStringLiteral(STRIFY(s))
|
||||||
#define STRIFY(s) #s
|
#define STRIFY(s) #s
|
||||||
|
|
||||||
QIcon ActionManager::loadHidpiIcon(const QString &resp, QSize sz)
|
QIcon ActionManager::loadHidpiIcon(const QString &resp, QSize sz)
|
||||||
{
|
{
|
||||||
QSvgRenderer r(resp);
|
QSvgRenderer r(resp);
|
||||||
QPixmap pm = QPixmap(sz * qApp->devicePixelRatio());
|
QPixmap pm = QPixmap(sz * qApp->devicePixelRatio());
|
||||||
pm.fill(Qt::transparent);
|
pm.fill(Qt::transparent);
|
||||||
QPainter p(&pm);
|
QPainter p(&pm);
|
||||||
r.render(&p);
|
r.render(&p);
|
||||||
pm.setDevicePixelRatio(qApp->devicePixelRatio());
|
pm.setDevicePixelRatio(qApp->devicePixelRatio());
|
||||||
return QIcon(pm);
|
return QIcon(pm);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActionManager::setupAction(MainWindow *mainWindow)
|
void ActionManager::setupAction(MainWindow *mainWindow)
|
||||||
{
|
{
|
||||||
auto create_action = [] (QWidget *w, QAction **a, QString i, QString an, bool iconFromTheme = false) {
|
auto create_action = [] (QWidget *w, QAction **a, QString i, QString an, bool iconFromTheme = false) {
|
||||||
*a = new QAction(w);
|
*a = new QAction(w);
|
||||||
if (!i.isNull())
|
if (!i.isNull())
|
||||||
(*a)->setIcon(iconFromTheme ? QIcon::fromTheme(i) : ActionManager::loadHidpiIcon(i));
|
(*a)->setIcon(iconFromTheme ? QIcon::fromTheme(i) : ActionManager::loadHidpiIcon(i));
|
||||||
(*a)->setObjectName(an);
|
(*a)->setObjectName(an);
|
||||||
w->addAction(*a);
|
w->addAction(*a);
|
||||||
};
|
};
|
||||||
#define CREATE_NEW_ICON_ACTION(w, a, i) create_action(w, &a, ICON_NAME(i), ACTION_NAME(a))
|
#define CREATE_NEW_ICON_ACTION(w, a, i) create_action(w, &a, ICON_NAME(i), ACTION_NAME(a))
|
||||||
CREATE_NEW_ICON_ACTION(mainWindow, actionActualSize, zoom-original);
|
CREATE_NEW_ICON_ACTION(mainWindow, actionActualSize, zoom-original);
|
||||||
CREATE_NEW_ICON_ACTION(mainWindow, actionToggleMaximize, view-fullscreen);
|
CREATE_NEW_ICON_ACTION(mainWindow, actionToggleMaximize, view-fullscreen);
|
||||||
CREATE_NEW_ICON_ACTION(mainWindow, actionZoomIn, zoom-in);
|
CREATE_NEW_ICON_ACTION(mainWindow, actionZoomIn, zoom-in);
|
||||||
CREATE_NEW_ICON_ACTION(mainWindow, actionZoomOut, zoom-out);
|
CREATE_NEW_ICON_ACTION(mainWindow, actionZoomOut, zoom-out);
|
||||||
CREATE_NEW_ICON_ACTION(mainWindow, actionToggleCheckerboard, view-background-checkerboard);
|
CREATE_NEW_ICON_ACTION(mainWindow, actionToggleCheckerboard, view-background-checkerboard);
|
||||||
CREATE_NEW_ICON_ACTION(mainWindow, actionRotateClockwise, object-rotate-right);
|
CREATE_NEW_ICON_ACTION(mainWindow, actionRotateClockwise, object-rotate-right);
|
||||||
#undef CREATE_NEW_ICON_ACTION
|
#undef CREATE_NEW_ICON_ACTION
|
||||||
|
|
||||||
#define CREATE_NEW_ACTION(w, a) create_action(w, &a, QString(), ACTION_NAME(a))
|
#define CREATE_NEW_ACTION(w, a) create_action(w, &a, QString(), ACTION_NAME(a))
|
||||||
#define CREATE_NEW_THEMEICON_ACTION(w, a, i) create_action(w, &a, QLatin1String(STRIFY(i)), ACTION_NAME(a), true)
|
#define CREATE_NEW_THEMEICON_ACTION(w, a, i) create_action(w, &a, QLatin1String(STRIFY(i)), ACTION_NAME(a), true)
|
||||||
CREATE_NEW_ACTION(mainWindow, actionRotateCounterClockwise);
|
CREATE_NEW_ACTION(mainWindow, actionRotateCounterClockwise);
|
||||||
CREATE_NEW_ACTION(mainWindow, actionPrevPicture);
|
CREATE_NEW_ACTION(mainWindow, actionPrevPicture);
|
||||||
CREATE_NEW_ACTION(mainWindow, actionNextPicture);
|
CREATE_NEW_ACTION(mainWindow, actionNextPicture);
|
||||||
|
|
||||||
CREATE_NEW_ACTION(mainWindow, actionTogglePauseAnimation);
|
CREATE_NEW_ACTION(mainWindow, actionTogglePauseAnimation);
|
||||||
CREATE_NEW_ACTION(mainWindow, actionAnimationNextFrame);
|
CREATE_NEW_ACTION(mainWindow, actionAnimationNextFrame);
|
||||||
|
|
||||||
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionOpen, document-open);
|
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionOpen, document-open);
|
||||||
CREATE_NEW_ACTION(mainWindow, actionHorizontalFlip);
|
CREATE_NEW_ACTION(mainWindow, actionHorizontalFlip);
|
||||||
CREATE_NEW_ACTION(mainWindow, actionFitInView);
|
CREATE_NEW_ACTION(mainWindow, actionFitInView);
|
||||||
CREATE_NEW_ACTION(mainWindow, actionFitByWidth);
|
CREATE_NEW_ACTION(mainWindow, actionFitByWidth);
|
||||||
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionCopyPixmap, edit-copy);
|
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionCopyPixmap, edit-copy);
|
||||||
CREATE_NEW_ACTION(mainWindow, actionCopyFilePath);
|
CREATE_NEW_ACTION(mainWindow, actionCopyFilePath);
|
||||||
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionPaste, edit-paste);
|
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionPaste, edit-paste);
|
||||||
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionTrash, edit-delete);
|
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionTrash, edit-delete);
|
||||||
CREATE_NEW_ACTION(mainWindow, actionToggleStayOnTop);
|
CREATE_NEW_ACTION(mainWindow, actionToggleStayOnTop);
|
||||||
CREATE_NEW_ACTION(mainWindow, actionToggleProtectMode);
|
CREATE_NEW_ACTION(mainWindow, actionToggleProtectMode);
|
||||||
CREATE_NEW_ACTION(mainWindow, actionToggleAvoidResetTransform);
|
CREATE_NEW_ACTION(mainWindow, actionToggleAvoidResetTransform);
|
||||||
CREATE_NEW_ACTION(mainWindow, actionSettings);
|
CREATE_NEW_ACTION(mainWindow, actionSettings);
|
||||||
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionHelp, system-help);
|
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionHelp, system-help);
|
||||||
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionLocateInFileManager, system-file-manager);
|
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionLocateInFileManager, system-file-manager);
|
||||||
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionProperties, document-properties);
|
CREATE_NEW_THEMEICON_ACTION(mainWindow, actionProperties, document-properties);
|
||||||
CREATE_NEW_ACTION(mainWindow, actionQuitApp);
|
CREATE_NEW_ACTION(mainWindow, actionQuitApp);
|
||||||
#undef CREATE_NEW_ACTION
|
#undef CREATE_NEW_ACTION
|
||||||
#undef CREATE_NEW_THEMEICON_ACTION
|
#undef CREATE_NEW_THEMEICON_ACTION
|
||||||
|
|
||||||
retranslateUi(mainWindow);
|
retranslateUi(mainWindow);
|
||||||
|
|
||||||
QMetaObject::connectSlotsByName(mainWindow);
|
QMetaObject::connectSlotsByName(mainWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActionManager::retranslateUi(MainWindow *mainWindow)
|
void ActionManager::retranslateUi(MainWindow *mainWindow)
|
||||||
{
|
{
|
||||||
Q_UNUSED(mainWindow);
|
Q_UNUSED(mainWindow);
|
||||||
|
|
||||||
actionOpen->setText(QCoreApplication::translate("MainWindow", "&Open...", nullptr));
|
actionOpen->setText(QCoreApplication::translate("MainWindow", "&Open...", nullptr));
|
||||||
|
|
||||||
actionActualSize->setText(QCoreApplication::translate("MainWindow", "Actual size", nullptr));
|
actionActualSize->setText(QCoreApplication::translate("MainWindow", "Actual size", nullptr));
|
||||||
actionToggleMaximize->setText(QCoreApplication::translate("MainWindow", "Toggle maximize", nullptr));
|
actionToggleMaximize->setText(QCoreApplication::translate("MainWindow", "Toggle maximize", nullptr));
|
||||||
actionZoomIn->setText(QCoreApplication::translate("MainWindow", "Zoom in", nullptr));
|
actionZoomIn->setText(QCoreApplication::translate("MainWindow", "Zoom in", nullptr));
|
||||||
actionZoomOut->setText(QCoreApplication::translate("MainWindow", "Zoom out", nullptr));
|
actionZoomOut->setText(QCoreApplication::translate("MainWindow", "Zoom out", nullptr));
|
||||||
actionToggleCheckerboard->setText(QCoreApplication::translate("MainWindow", "Toggle Checkerboard", nullptr));
|
actionToggleCheckerboard->setText(QCoreApplication::translate("MainWindow", "Toggle Checkerboard", nullptr));
|
||||||
actionRotateClockwise->setText(QCoreApplication::translate("MainWindow", "Rotate right", nullptr));
|
actionRotateClockwise->setText(QCoreApplication::translate("MainWindow", "Rotate right", nullptr));
|
||||||
actionRotateCounterClockwise->setText(QCoreApplication::translate("MainWindow", "Rotate left", nullptr));
|
actionRotateCounterClockwise->setText(QCoreApplication::translate("MainWindow", "Rotate left", nullptr));
|
||||||
|
|
||||||
actionPrevPicture->setText(QCoreApplication::translate("MainWindow", "Previous image", nullptr));
|
actionPrevPicture->setText(QCoreApplication::translate("MainWindow", "Previous image", nullptr));
|
||||||
actionNextPicture->setText(QCoreApplication::translate("MainWindow", "Next image", nullptr));
|
actionNextPicture->setText(QCoreApplication::translate("MainWindow", "Next image", nullptr));
|
||||||
|
|
||||||
actionTogglePauseAnimation->setText(QCoreApplication::translate("MainWindow", "Pause/Resume Animation", nullptr));
|
actionTogglePauseAnimation->setText(QCoreApplication::translate("MainWindow", "Pause/Resume Animation", nullptr));
|
||||||
actionAnimationNextFrame->setText(QCoreApplication::translate("MainWindow", "Animation Go to Next Frame", nullptr));
|
actionAnimationNextFrame->setText(QCoreApplication::translate("MainWindow", "Animation Go to Next Frame", nullptr));
|
||||||
|
|
||||||
actionHorizontalFlip->setText(QCoreApplication::translate("MainWindow", "Flip &Horizontally", nullptr));
|
actionHorizontalFlip->setText(QCoreApplication::translate("MainWindow", "Flip &Horizontally", nullptr));
|
||||||
actionFitInView->setText(QCoreApplication::translate("MainWindow", "Fit to view", nullptr));
|
actionFitInView->setText(QCoreApplication::translate("MainWindow", "Fit to view", nullptr));
|
||||||
actionFitByWidth->setText(QCoreApplication::translate("MainWindow", "Fit to width", nullptr));
|
actionFitByWidth->setText(QCoreApplication::translate("MainWindow", "Fit to width", nullptr));
|
||||||
actionCopyPixmap->setText(QCoreApplication::translate("MainWindow", "Copy P&ixmap", nullptr));
|
actionCopyPixmap->setText(QCoreApplication::translate("MainWindow", "Copy P&ixmap", nullptr));
|
||||||
actionCopyFilePath->setText(QCoreApplication::translate("MainWindow", "Copy &File Path", nullptr));
|
actionCopyFilePath->setText(QCoreApplication::translate("MainWindow", "Copy &File Path", nullptr));
|
||||||
actionPaste->setText(QCoreApplication::translate("MainWindow", "&Paste", nullptr));
|
actionPaste->setText(QCoreApplication::translate("MainWindow", "&Paste", nullptr));
|
||||||
actionTrash->setText(QCoreApplication::translate("MainWindow", "Move to Trash", nullptr));
|
actionTrash->setText(QCoreApplication::translate("MainWindow", "Move to Trash", nullptr));
|
||||||
actionToggleStayOnTop->setText(QCoreApplication::translate("MainWindow", "Stay on top", nullptr));
|
actionToggleStayOnTop->setText(QCoreApplication::translate("MainWindow", "Stay on top", nullptr));
|
||||||
actionToggleProtectMode->setText(QCoreApplication::translate("MainWindow", "Protected mode", nullptr));
|
actionToggleProtectMode->setText(QCoreApplication::translate("MainWindow", "Protected mode", nullptr));
|
||||||
actionToggleAvoidResetTransform->setText(QCoreApplication::translate("MainWindow", "Keep transformation", "The 'transformation' means the flip/rotation status that currently applied to the image view"));
|
actionToggleAvoidResetTransform->setText(QCoreApplication::translate("MainWindow", "Keep transformation", "The 'transformation' means the flip/rotation status that currently applied to the image view"));
|
||||||
actionSettings->setText(QCoreApplication::translate("MainWindow", "Configure...", nullptr));
|
actionSettings->setText(QCoreApplication::translate("MainWindow", "Configure...", nullptr));
|
||||||
actionHelp->setText(QCoreApplication::translate("MainWindow", "Help", nullptr));
|
actionHelp->setText(QCoreApplication::translate("MainWindow", "Help", nullptr));
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
actionLocateInFileManager->setText(
|
actionLocateInFileManager->setText(
|
||||||
QCoreApplication::translate(
|
QCoreApplication::translate(
|
||||||
"MainWindow", "Show in File Explorer",
|
"MainWindow", "Show in File Explorer",
|
||||||
"File Explorer is the name of explorer.exe under Windows"
|
"File Explorer is the name of explorer.exe under Windows"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
#else
|
#else
|
||||||
actionLocateInFileManager->setText(QCoreApplication::translate("MainWindow", "Show in directory", nullptr));
|
actionLocateInFileManager->setText(QCoreApplication::translate("MainWindow", "Show in directory", nullptr));
|
||||||
#endif // Q_OS_WIN
|
#endif // Q_OS_WIN
|
||||||
actionProperties->setText(QCoreApplication::translate("MainWindow", "Properties", nullptr));
|
actionProperties->setText(QCoreApplication::translate("MainWindow", "Properties", nullptr));
|
||||||
actionQuitApp->setText(QCoreApplication::translate("MainWindow", "Quit", nullptr));
|
actionQuitApp->setText(QCoreApplication::translate("MainWindow", "Quit", nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActionManager::setupShortcuts()
|
void ActionManager::setupShortcuts()
|
||||||
{
|
{
|
||||||
actionOpen->setShortcut(QKeySequence::Open);
|
actionOpen->setShortcut(QKeySequence::Open);
|
||||||
actionActualSize->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_0));
|
actionActualSize->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_0));
|
||||||
actionZoomIn->setShortcut(QKeySequence::ZoomIn);
|
actionZoomIn->setShortcut(QKeySequence::ZoomIn);
|
||||||
actionZoomOut->setShortcut(QKeySequence::ZoomOut);
|
actionZoomOut->setShortcut(QKeySequence::ZoomOut);
|
||||||
actionPrevPicture->setShortcuts({
|
actionPrevPicture->setShortcuts({
|
||||||
QKeySequence(Qt::Key_PageUp),
|
QKeySequence(Qt::Key_PageUp),
|
||||||
QKeySequence(Qt::Key_Left),
|
QKeySequence(Qt::Key_Left),
|
||||||
});
|
});
|
||||||
actionNextPicture->setShortcuts({
|
actionNextPicture->setShortcuts({
|
||||||
QKeySequence(Qt::Key_PageDown),
|
QKeySequence(Qt::Key_PageDown),
|
||||||
QKeySequence(Qt::Key_Right),
|
QKeySequence(Qt::Key_Right),
|
||||||
});
|
});
|
||||||
actionHorizontalFlip->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_R));
|
actionHorizontalFlip->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_R));
|
||||||
actionCopyPixmap->setShortcut(QKeySequence::Copy);
|
actionCopyPixmap->setShortcut(QKeySequence::Copy);
|
||||||
actionPaste->setShortcut(QKeySequence::Paste);
|
actionPaste->setShortcut(QKeySequence::Paste);
|
||||||
actionTrash->setShortcut(QKeySequence::Delete);
|
actionTrash->setShortcut(QKeySequence::Delete);
|
||||||
actionHelp->setShortcut(QKeySequence::HelpContents);
|
actionHelp->setShortcut(QKeySequence::HelpContents);
|
||||||
actionSettings->setShortcut(QKeySequence::Preferences);
|
actionSettings->setShortcut(QKeySequence::Preferences);
|
||||||
actionProperties->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_I));
|
actionProperties->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_I));
|
||||||
actionQuitApp->setShortcuts({
|
actionQuitApp->setShortcuts({
|
||||||
QKeySequence(Qt::Key_Space),
|
QKeySequence(Qt::Key_Space),
|
||||||
QKeySequence(Qt::Key_Escape)
|
QKeySequence(Qt::Key_Escape)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,58 +1,58 @@
|
|||||||
// SPDX-FileCopyrightText: 2025 Gary Wang <git@blumia.net>
|
// SPDX-FileCopyrightText: 2025 Gary Wang <git@blumia.net>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef ACTIONMANAGER_H
|
#ifndef ACTIONMANAGER_H
|
||||||
#define ACTIONMANAGER_H
|
#define ACTIONMANAGER_H
|
||||||
|
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
|
|
||||||
class MainWindow;
|
class MainWindow;
|
||||||
|
|
||||||
class ActionManager
|
class ActionManager
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit ActionManager() = default;
|
explicit ActionManager() = default;
|
||||||
~ActionManager() = default;
|
~ActionManager() = default;
|
||||||
|
|
||||||
void setupAction(MainWindow * mainWindow);
|
void setupAction(MainWindow * mainWindow);
|
||||||
void retranslateUi(MainWindow *MainWindow);
|
void retranslateUi(MainWindow *MainWindow);
|
||||||
void setupShortcuts();
|
void setupShortcuts();
|
||||||
|
|
||||||
static QIcon loadHidpiIcon(const QString &resp, QSize sz = QSize(32, 32));
|
static QIcon loadHidpiIcon(const QString &resp, QSize sz = QSize(32, 32));
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QAction *actionOpen;
|
QAction *actionOpen;
|
||||||
|
|
||||||
QAction *actionActualSize;
|
QAction *actionActualSize;
|
||||||
QAction *actionToggleMaximize;
|
QAction *actionToggleMaximize;
|
||||||
QAction *actionZoomIn;
|
QAction *actionZoomIn;
|
||||||
QAction *actionZoomOut;
|
QAction *actionZoomOut;
|
||||||
QAction *actionToggleCheckerboard;
|
QAction *actionToggleCheckerboard;
|
||||||
QAction *actionRotateClockwise;
|
QAction *actionRotateClockwise;
|
||||||
QAction *actionRotateCounterClockwise;
|
QAction *actionRotateCounterClockwise;
|
||||||
|
|
||||||
QAction *actionPrevPicture;
|
QAction *actionPrevPicture;
|
||||||
QAction *actionNextPicture;
|
QAction *actionNextPicture;
|
||||||
|
|
||||||
QAction *actionTogglePauseAnimation;
|
QAction *actionTogglePauseAnimation;
|
||||||
QAction *actionAnimationNextFrame;
|
QAction *actionAnimationNextFrame;
|
||||||
|
|
||||||
QAction *actionHorizontalFlip;
|
QAction *actionHorizontalFlip;
|
||||||
QAction *actionFitInView;
|
QAction *actionFitInView;
|
||||||
QAction *actionFitByWidth;
|
QAction *actionFitByWidth;
|
||||||
QAction *actionCopyPixmap;
|
QAction *actionCopyPixmap;
|
||||||
QAction *actionCopyFilePath;
|
QAction *actionCopyFilePath;
|
||||||
QAction *actionPaste;
|
QAction *actionPaste;
|
||||||
QAction *actionTrash;
|
QAction *actionTrash;
|
||||||
QAction *actionToggleStayOnTop;
|
QAction *actionToggleStayOnTop;
|
||||||
QAction *actionToggleProtectMode;
|
QAction *actionToggleProtectMode;
|
||||||
QAction *actionToggleAvoidResetTransform;
|
QAction *actionToggleAvoidResetTransform;
|
||||||
QAction *actionSettings;
|
QAction *actionSettings;
|
||||||
QAction *actionHelp;
|
QAction *actionHelp;
|
||||||
QAction *actionLocateInFileManager;
|
QAction *actionLocateInFileManager;
|
||||||
QAction *actionProperties;
|
QAction *actionProperties;
|
||||||
QAction *actionQuitApp;
|
QAction *actionQuitApp;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ACTIONMANAGER_H
|
#endif // ACTIONMANAGER_H
|
||||||
|
@ -1,57 +1,57 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "bottombuttongroup.h"
|
#include "bottombuttongroup.h"
|
||||||
|
|
||||||
#include "opacityhelper.h"
|
#include "opacityhelper.h"
|
||||||
|
|
||||||
#include <QToolButton>
|
#include <QToolButton>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
BottomButtonGroup::BottomButtonGroup(const std::vector<QAction *> &actionList, QWidget *parent)
|
BottomButtonGroup::BottomButtonGroup(const std::vector<QAction *> &actionList, QWidget *parent)
|
||||||
: QGroupBox (parent)
|
: QGroupBox (parent)
|
||||||
, m_opacityHelper(new OpacityHelper(this))
|
, m_opacityHelper(new OpacityHelper(this))
|
||||||
{
|
{
|
||||||
QHBoxLayout * mainLayout = new QHBoxLayout(this);
|
QHBoxLayout * mainLayout = new QHBoxLayout(this);
|
||||||
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||||
this->setLayout(mainLayout);
|
this->setLayout(mainLayout);
|
||||||
this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||||
this->setStyleSheet("BottomButtonGroup {"
|
this->setStyleSheet("BottomButtonGroup {"
|
||||||
"border: 1px solid gray;"
|
"border: 1px solid gray;"
|
||||||
"border-top-left-radius: 10px;"
|
"border-top-left-radius: 10px;"
|
||||||
"border-top-right-radius: 10px;"
|
"border-top-right-radius: 10px;"
|
||||||
"border-style: none;"
|
"border-style: none;"
|
||||||
"background-color:rgba(0,0,0,120)"
|
"background-color:rgba(0,0,0,120)"
|
||||||
"}"
|
"}"
|
||||||
"QToolButton {"
|
"QToolButton {"
|
||||||
"background:transparent;"
|
"background:transparent;"
|
||||||
"}"
|
"}"
|
||||||
"QToolButton:!focus {"
|
"QToolButton:!focus {"
|
||||||
"border-style: none;"
|
"border-style: none;"
|
||||||
"}");
|
"}");
|
||||||
|
|
||||||
auto newActionBtn = [this](QAction * action) -> QToolButton * {
|
auto newActionBtn = [this](QAction * action) -> QToolButton * {
|
||||||
QToolButton * btn = new QToolButton(this);
|
QToolButton * btn = new QToolButton(this);
|
||||||
btn->setDefaultAction(action);
|
btn->setDefaultAction(action);
|
||||||
btn->setIconSize(QSize(32, 32));
|
btn->setIconSize(QSize(32, 32));
|
||||||
btn->setFixedSize(40, 40);
|
btn->setFixedSize(40, 40);
|
||||||
return btn;
|
return btn;
|
||||||
};
|
};
|
||||||
|
|
||||||
for (QAction * action : actionList) {
|
for (QAction * action : actionList) {
|
||||||
addButton(newActionBtn(action));
|
addButton(newActionBtn(action));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BottomButtonGroup::setOpacity(qreal opacity, bool animated)
|
void BottomButtonGroup::setOpacity(qreal opacity, bool animated)
|
||||||
{
|
{
|
||||||
m_opacityHelper->setOpacity(opacity, animated);
|
m_opacityHelper->setOpacity(opacity, animated);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BottomButtonGroup::addButton(QAbstractButton *button)
|
void BottomButtonGroup::addButton(QAbstractButton *button)
|
||||||
{
|
{
|
||||||
layout()->addWidget(button);
|
layout()->addWidget(button);
|
||||||
updateGeometry();
|
updateGeometry();
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef BOTTOMBUTTONGROUP_H
|
#ifndef BOTTOMBUTTONGROUP_H
|
||||||
#define BOTTOMBUTTONGROUP_H
|
#define BOTTOMBUTTONGROUP_H
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <QAbstractButton>
|
#include <QAbstractButton>
|
||||||
#include <QGroupBox>
|
#include <QGroupBox>
|
||||||
|
|
||||||
class OpacityHelper;
|
class OpacityHelper;
|
||||||
class BottomButtonGroup : public QGroupBox
|
class BottomButtonGroup : public QGroupBox
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit BottomButtonGroup(const std::vector<QAction *> & actionList, QWidget *parent = nullptr);
|
explicit BottomButtonGroup(const std::vector<QAction *> & actionList, QWidget *parent = nullptr);
|
||||||
|
|
||||||
void setOpacity(qreal opacity, bool animated = true);
|
void setOpacity(qreal opacity, bool animated = true);
|
||||||
void addButton(QAbstractButton *button);
|
void addButton(QAbstractButton *button);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
OpacityHelper * m_opacityHelper;
|
OpacityHelper * m_opacityHelper;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BOTTOMBUTTONGROUP_H
|
#endif // BOTTOMBUTTONGROUP_H
|
||||||
|
@ -1,138 +1,138 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "exiv2wrapper.h"
|
#include "exiv2wrapper.h"
|
||||||
|
|
||||||
#ifdef HAVE_EXIV2_VERSION
|
#ifdef HAVE_EXIV2_VERSION
|
||||||
#include <exiv2/exiv2.hpp>
|
#include <exiv2/exiv2.hpp>
|
||||||
#else // HAVE_EXIV2_VERSION
|
#else // HAVE_EXIV2_VERSION
|
||||||
namespace Exiv2 {
|
namespace Exiv2 {
|
||||||
class Image {};
|
class Image {};
|
||||||
}
|
}
|
||||||
#endif // HAVE_EXIV2_VERSION
|
#endif // HAVE_EXIV2_VERSION
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
Exiv2Wrapper::Exiv2Wrapper()
|
Exiv2Wrapper::Exiv2Wrapper()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Exiv2Wrapper::~Exiv2Wrapper()
|
Exiv2Wrapper::~Exiv2Wrapper()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_EXIV2_VERSION // stupid AppleClang...
|
#ifdef HAVE_EXIV2_VERSION // stupid AppleClang...
|
||||||
template<typename Collection, typename Iterator>
|
template<typename Collection, typename Iterator>
|
||||||
void Exiv2Wrapper::cacheSection(Collection collection)
|
void Exiv2Wrapper::cacheSection(Collection collection)
|
||||||
{
|
{
|
||||||
const Collection& exifData = collection;
|
const Collection& exifData = collection;
|
||||||
Iterator it = exifData.begin(), end = exifData.end();
|
Iterator it = exifData.begin(), end = exifData.end();
|
||||||
for (; it != end; ++it) {
|
for (; it != end; ++it) {
|
||||||
QString key = QString::fromUtf8(it->key().c_str());
|
QString key = QString::fromUtf8(it->key().c_str());
|
||||||
if (it->tagName().substr(0, 2) == "0x") continue;
|
if (it->tagName().substr(0, 2) == "0x") continue;
|
||||||
// We might get exceptions like "No namespace info available for XMP prefix `Item'"
|
// We might get exceptions like "No namespace info available for XMP prefix `Item'"
|
||||||
// when trying to get tagLabel() data from a Xmpdatum if the tag is not common-used.
|
// when trying to get tagLabel() data from a Xmpdatum if the tag is not common-used.
|
||||||
// We don't care for those rare tags so let's just use a try-cache...
|
// We don't care for those rare tags so let's just use a try-cache...
|
||||||
try {
|
try {
|
||||||
QString label = QString::fromLocal8Bit(it->tagLabel().c_str());
|
QString label = QString::fromLocal8Bit(it->tagLabel().c_str());
|
||||||
std::ostringstream stream;
|
std::ostringstream stream;
|
||||||
stream << *it;
|
stream << *it;
|
||||||
QString value = QString::fromUtf8(stream.str().c_str());
|
QString value = QString::fromUtf8(stream.str().c_str());
|
||||||
m_metadataValue.insert(key, value);
|
m_metadataValue.insert(key, value);
|
||||||
m_metadataLabel.insert(key, label);
|
m_metadataLabel.insert(key, label);
|
||||||
qDebug() << key << label << value;
|
qDebug() << key << label << value;
|
||||||
#if EXIV2_TEST_VERSION(0, 28, 0)
|
#if EXIV2_TEST_VERSION(0, 28, 0)
|
||||||
} catch (Exiv2::Error & err) {
|
} catch (Exiv2::Error & err) {
|
||||||
#else // 0.27.x
|
#else // 0.27.x
|
||||||
} catch (Exiv2::AnyError & err) {
|
} catch (Exiv2::AnyError & err) {
|
||||||
#endif // EXIV2_TEST_VERSION(0, 28, 0)
|
#endif // EXIV2_TEST_VERSION(0, 28, 0)
|
||||||
qWarning() << "Error loading key" << key << ":" << err.what();
|
qWarning() << "Error loading key" << key << ":" << err.what();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif // HAVE_EXIV2_VERSION
|
#endif // HAVE_EXIV2_VERSION
|
||||||
|
|
||||||
bool Exiv2Wrapper::load(const QString &filePath)
|
bool Exiv2Wrapper::load(const QString &filePath)
|
||||||
{
|
{
|
||||||
#ifdef HAVE_EXIV2_VERSION
|
#ifdef HAVE_EXIV2_VERSION
|
||||||
QByteArray filePathByteArray = QFile::encodeName(filePath);
|
QByteArray filePathByteArray = QFile::encodeName(filePath);
|
||||||
try {
|
try {
|
||||||
m_exivImage.reset(Exiv2::ImageFactory::open(filePathByteArray.constData()).release());
|
m_exivImage.reset(Exiv2::ImageFactory::open(filePathByteArray.constData()).release());
|
||||||
m_exivImage->readMetadata();
|
m_exivImage->readMetadata();
|
||||||
} catch (const Exiv2::Error& error) {
|
} catch (const Exiv2::Error& error) {
|
||||||
m_errMsg = QString::fromUtf8(error.what());
|
m_errMsg = QString::fromUtf8(error.what());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
#else // HAVE_EXIV2_VERSION
|
#else // HAVE_EXIV2_VERSION
|
||||||
Q_UNUSED(filePath);
|
Q_UNUSED(filePath);
|
||||||
return false;
|
return false;
|
||||||
#endif // HAVE_EXIV2_VERSION
|
#endif // HAVE_EXIV2_VERSION
|
||||||
}
|
}
|
||||||
|
|
||||||
void Exiv2Wrapper::cacheSections()
|
void Exiv2Wrapper::cacheSections()
|
||||||
{
|
{
|
||||||
#ifdef HAVE_EXIV2_VERSION
|
#ifdef HAVE_EXIV2_VERSION
|
||||||
if (m_exivImage->checkMode(Exiv2::mdExif) & Exiv2::amRead) {
|
if (m_exivImage->checkMode(Exiv2::mdExif) & Exiv2::amRead) {
|
||||||
cacheSection<Exiv2::ExifData, Exiv2::ExifData::const_iterator>(m_exivImage->exifData());
|
cacheSection<Exiv2::ExifData, Exiv2::ExifData::const_iterator>(m_exivImage->exifData());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_exivImage->checkMode(Exiv2::mdIptc) & Exiv2::amRead) {
|
if (m_exivImage->checkMode(Exiv2::mdIptc) & Exiv2::amRead) {
|
||||||
cacheSection<Exiv2::IptcData, Exiv2::IptcData::const_iterator>(m_exivImage->iptcData());
|
cacheSection<Exiv2::IptcData, Exiv2::IptcData::const_iterator>(m_exivImage->iptcData());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_exivImage->checkMode(Exiv2::mdXmp) & Exiv2::amRead) {
|
if (m_exivImage->checkMode(Exiv2::mdXmp) & Exiv2::amRead) {
|
||||||
cacheSection<Exiv2::XmpData, Exiv2::XmpData::const_iterator>(m_exivImage->xmpData());
|
cacheSection<Exiv2::XmpData, Exiv2::XmpData::const_iterator>(m_exivImage->xmpData());
|
||||||
}
|
}
|
||||||
|
|
||||||
// qDebug() << m_metadataValue;
|
// qDebug() << m_metadataValue;
|
||||||
// qDebug() << m_metadataLabel;
|
// qDebug() << m_metadataLabel;
|
||||||
#endif // HAVE_EXIV2_VERSION
|
#endif // HAVE_EXIV2_VERSION
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Exiv2Wrapper::comment() const
|
QString Exiv2Wrapper::comment() const
|
||||||
{
|
{
|
||||||
#ifdef HAVE_EXIV2_VERSION
|
#ifdef HAVE_EXIV2_VERSION
|
||||||
return m_exivImage->comment().c_str();
|
return m_exivImage->comment().c_str();
|
||||||
#else // HAVE_EXIV2_VERSION
|
#else // HAVE_EXIV2_VERSION
|
||||||
return QString();
|
return QString();
|
||||||
#endif // HAVE_EXIV2_VERSION
|
#endif // HAVE_EXIV2_VERSION
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Exiv2Wrapper::label(const QString &key) const
|
QString Exiv2Wrapper::label(const QString &key) const
|
||||||
{
|
{
|
||||||
return m_metadataLabel.value(key);
|
return m_metadataLabel.value(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Exiv2Wrapper::value(const QString &key) const
|
QString Exiv2Wrapper::value(const QString &key) const
|
||||||
{
|
{
|
||||||
return m_metadataValue.value(key);
|
return m_metadataValue.value(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Exiv2Wrapper::XmpValue(const QString &rawValue)
|
QString Exiv2Wrapper::XmpValue(const QString &rawValue)
|
||||||
{
|
{
|
||||||
QString ignored;
|
QString ignored;
|
||||||
return Exiv2Wrapper::XmpValue(rawValue, ignored);
|
return Exiv2Wrapper::XmpValue(rawValue, ignored);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Exiv2Wrapper::XmpValue(const QString &rawValue, QString &language)
|
QString Exiv2Wrapper::XmpValue(const QString &rawValue, QString &language)
|
||||||
{
|
{
|
||||||
if (rawValue.size() > 6 && rawValue.startsWith(QLatin1String("lang=\""))) {
|
if (rawValue.size() > 6 && rawValue.startsWith(QLatin1String("lang=\""))) {
|
||||||
int pos = rawValue.indexOf('"', 6);
|
int pos = rawValue.indexOf('"', 6);
|
||||||
|
|
||||||
if (pos != -1) {
|
if (pos != -1) {
|
||||||
language = rawValue.mid(6, pos - 6);
|
language = rawValue.mid(6, pos - 6);
|
||||||
return (rawValue.mid(pos + 2));
|
return (rawValue.mid(pos + 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
language.clear();
|
language.clear();
|
||||||
return rawValue;
|
return rawValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,43 +1,43 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef EXIV2WRAPPER_H
|
#ifndef EXIV2WRAPPER_H
|
||||||
#define EXIV2WRAPPER_H
|
#define EXIV2WRAPPER_H
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
|
||||||
namespace Exiv2 {
|
namespace Exiv2 {
|
||||||
class Image;
|
class Image;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Exiv2Wrapper
|
class Exiv2Wrapper
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Exiv2Wrapper();
|
Exiv2Wrapper();
|
||||||
~Exiv2Wrapper();
|
~Exiv2Wrapper();
|
||||||
|
|
||||||
bool load(const QString& filePath);
|
bool load(const QString& filePath);
|
||||||
void cacheSections();
|
void cacheSections();
|
||||||
|
|
||||||
QString comment() const;
|
QString comment() const;
|
||||||
QString label(const QString & key) const;
|
QString label(const QString & key) const;
|
||||||
QString value(const QString & key) const;
|
QString value(const QString & key) const;
|
||||||
|
|
||||||
static QString XmpValue(const QString &rawValue);
|
static QString XmpValue(const QString &rawValue);
|
||||||
static QString XmpValue(const QString &rawValue, QString & language);
|
static QString XmpValue(const QString &rawValue, QString & language);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<Exiv2::Image> m_exivImage;
|
std::unique_ptr<Exiv2::Image> m_exivImage;
|
||||||
QMap<QString, QString> m_metadataValue;
|
QMap<QString, QString> m_metadataValue;
|
||||||
QMap<QString, QString> m_metadataLabel;
|
QMap<QString, QString> m_metadataLabel;
|
||||||
QString m_errMsg;
|
QString m_errMsg;
|
||||||
|
|
||||||
template<typename Collection, typename Iterator>
|
template<typename Collection, typename Iterator>
|
||||||
void cacheSection(Collection collection);
|
void cacheSection(Collection collection);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // EXIV2WRAPPER_H
|
#endif // EXIV2WRAPPER_H
|
||||||
|
@ -1,136 +1,136 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
// SPDX-FileCopyrightText: 2023 Tad Young <yyc12321@outlook.com>
|
// SPDX-FileCopyrightText: 2023 Tad Young <yyc12321@outlook.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "framelesswindow.h"
|
#include "framelesswindow.h"
|
||||||
|
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QHoverEvent>
|
#include <QHoverEvent>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QWindow>
|
#include <QWindow>
|
||||||
|
|
||||||
FramelessWindow::FramelessWindow(QWidget *parent)
|
FramelessWindow::FramelessWindow(QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, m_centralLayout(new QVBoxLayout(this))
|
, m_centralLayout(new QVBoxLayout(this))
|
||||||
, m_oldCursorShape(Qt::ArrowCursor)
|
, m_oldCursorShape(Qt::ArrowCursor)
|
||||||
, m_oldEdges()
|
, m_oldEdges()
|
||||||
{
|
{
|
||||||
this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
|
this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
|
||||||
this->setMouseTracking(true);
|
this->setMouseTracking(true);
|
||||||
this->setAttribute(Qt::WA_Hover, true);
|
this->setAttribute(Qt::WA_Hover, true);
|
||||||
this->installEventFilter(this);
|
this->installEventFilter(this);
|
||||||
|
|
||||||
m_centralLayout->setContentsMargins(QMargins());
|
m_centralLayout->setContentsMargins(QMargins());
|
||||||
}
|
}
|
||||||
|
|
||||||
void FramelessWindow::setCentralWidget(QWidget *widget)
|
void FramelessWindow::setCentralWidget(QWidget *widget)
|
||||||
{
|
{
|
||||||
if (m_centralWidget) {
|
if (m_centralWidget) {
|
||||||
m_centralLayout->removeWidget(m_centralWidget);
|
m_centralLayout->removeWidget(m_centralWidget);
|
||||||
m_centralWidget->deleteLater();
|
m_centralWidget->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_centralLayout->addWidget(widget);
|
m_centralLayout->addWidget(widget);
|
||||||
m_centralWidget = widget;
|
m_centralWidget = widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FramelessWindow::installResizeCapture(QObject* widget)
|
void FramelessWindow::installResizeCapture(QObject* widget)
|
||||||
{
|
{
|
||||||
widget->installEventFilter(this);
|
widget->installEventFilter(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FramelessWindow::eventFilter(QObject* o, QEvent* e)
|
bool FramelessWindow::eventFilter(QObject* o, QEvent* e)
|
||||||
{
|
{
|
||||||
switch (e->type()) {
|
switch (e->type()) {
|
||||||
case QEvent::HoverMove:
|
case QEvent::HoverMove:
|
||||||
{
|
{
|
||||||
QWidget* wg = qobject_cast<QWidget*>(o);
|
QWidget* wg = qobject_cast<QWidget*>(o);
|
||||||
if (wg != nullptr)
|
if (wg != nullptr)
|
||||||
return mouseHover(static_cast<QHoverEvent*>(e), wg);
|
return mouseHover(static_cast<QHoverEvent*>(e), wg);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case QEvent::MouseButtonPress:
|
case QEvent::MouseButtonPress:
|
||||||
return mousePress(static_cast<QMouseEvent*>(e));
|
return mousePress(static_cast<QMouseEvent*>(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
return QWidget::eventFilter(o, e);
|
return QWidget::eventFilter(o, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FramelessWindow::mouseHover(QHoverEvent* event, QWidget* wg)
|
bool FramelessWindow::mouseHover(QHoverEvent* event, QWidget* wg)
|
||||||
{
|
{
|
||||||
if (!isMaximized() && !isFullScreen()) {
|
if (!isMaximized() && !isFullScreen()) {
|
||||||
QWindow* win = window()->windowHandle();
|
QWindow* win = window()->windowHandle();
|
||||||
Qt::Edges edges = this->getEdgesByPos(wg->mapToGlobal(event->oldPos()), win->frameGeometry());
|
Qt::Edges edges = this->getEdgesByPos(wg->mapToGlobal(event->oldPos()), win->frameGeometry());
|
||||||
|
|
||||||
// backup & restore cursor shape
|
// backup & restore cursor shape
|
||||||
if (edges && !m_oldEdges)
|
if (edges && !m_oldEdges)
|
||||||
// entering the edge. backup cursor shape
|
// entering the edge. backup cursor shape
|
||||||
m_oldCursorShape = win->cursor().shape();
|
m_oldCursorShape = win->cursor().shape();
|
||||||
if (!edges && m_oldEdges)
|
if (!edges && m_oldEdges)
|
||||||
// leaving the edge. restore cursor shape
|
// leaving the edge. restore cursor shape
|
||||||
win->setCursor(m_oldCursorShape);
|
win->setCursor(m_oldCursorShape);
|
||||||
|
|
||||||
// save the latest edges status
|
// save the latest edges status
|
||||||
m_oldEdges = edges;
|
m_oldEdges = edges;
|
||||||
|
|
||||||
// show resize cursor shape if cursor is within border
|
// show resize cursor shape if cursor is within border
|
||||||
if (edges) {
|
if (edges) {
|
||||||
win->setCursor(this->getCursorByEdge(edges, Qt::ArrowCursor));
|
win->setCursor(this->getCursorByEdge(edges, Qt::ArrowCursor));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FramelessWindow::mousePress(QMouseEvent* event)
|
bool FramelessWindow::mousePress(QMouseEvent* event)
|
||||||
{
|
{
|
||||||
if (event->buttons() & Qt::LeftButton && !isMaximized() && !isFullScreen()) {
|
if (event->buttons() & Qt::LeftButton && !isMaximized() && !isFullScreen()) {
|
||||||
QWindow* win = window()->windowHandle();
|
QWindow* win = window()->windowHandle();
|
||||||
Qt::Edges edges = this->getEdgesByPos(event->globalPosition().toPoint(), win->frameGeometry());
|
Qt::Edges edges = this->getEdgesByPos(event->globalPosition().toPoint(), win->frameGeometry());
|
||||||
if (edges) {
|
if (edges) {
|
||||||
win->startSystemResize(edges);
|
win->startSystemResize(edges);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Qt::CursorShape FramelessWindow::getCursorByEdge(const Qt::Edges& edges, Qt::CursorShape default_cursor)
|
Qt::CursorShape FramelessWindow::getCursorByEdge(const Qt::Edges& edges, Qt::CursorShape default_cursor)
|
||||||
{
|
{
|
||||||
if ((edges == (Qt::TopEdge | Qt::LeftEdge)) || (edges == (Qt::RightEdge | Qt::BottomEdge)))
|
if ((edges == (Qt::TopEdge | Qt::LeftEdge)) || (edges == (Qt::RightEdge | Qt::BottomEdge)))
|
||||||
return Qt::SizeFDiagCursor;
|
return Qt::SizeFDiagCursor;
|
||||||
else if ((edges == (Qt::TopEdge | Qt::RightEdge)) || (edges == (Qt::LeftEdge | Qt::BottomEdge)))
|
else if ((edges == (Qt::TopEdge | Qt::RightEdge)) || (edges == (Qt::LeftEdge | Qt::BottomEdge)))
|
||||||
return Qt::SizeBDiagCursor;
|
return Qt::SizeBDiagCursor;
|
||||||
else if (edges & (Qt::TopEdge | Qt::BottomEdge))
|
else if (edges & (Qt::TopEdge | Qt::BottomEdge))
|
||||||
return Qt::SizeVerCursor;
|
return Qt::SizeVerCursor;
|
||||||
else if (edges & (Qt::LeftEdge | Qt::RightEdge))
|
else if (edges & (Qt::LeftEdge | Qt::RightEdge))
|
||||||
return Qt::SizeHorCursor;
|
return Qt::SizeHorCursor;
|
||||||
else
|
else
|
||||||
return default_cursor;
|
return default_cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
Qt::Edges FramelessWindow::getEdgesByPos(const QPoint gpos, const QRect& winrect)
|
Qt::Edges FramelessWindow::getEdgesByPos(const QPoint gpos, const QRect& winrect)
|
||||||
{
|
{
|
||||||
const int borderWidth = 8;
|
const int borderWidth = 8;
|
||||||
Qt::Edges edges;
|
Qt::Edges edges;
|
||||||
|
|
||||||
int x = gpos.x() - winrect.x();
|
int x = gpos.x() - winrect.x();
|
||||||
int y = gpos.y() - winrect.y();
|
int y = gpos.y() - winrect.y();
|
||||||
|
|
||||||
if (x < borderWidth)
|
if (x < borderWidth)
|
||||||
edges |= Qt::LeftEdge;
|
edges |= Qt::LeftEdge;
|
||||||
if (x > (winrect.width() - borderWidth))
|
if (x > (winrect.width() - borderWidth))
|
||||||
edges |= Qt::RightEdge;
|
edges |= Qt::RightEdge;
|
||||||
if (y < borderWidth)
|
if (y < borderWidth)
|
||||||
edges |= Qt::TopEdge;
|
edges |= Qt::TopEdge;
|
||||||
if (y > (winrect.height() - borderWidth))
|
if (y > (winrect.height() - borderWidth))
|
||||||
edges |= Qt::BottomEdge;
|
edges |= Qt::BottomEdge;
|
||||||
|
|
||||||
return edges;
|
return edges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,39 +1,39 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef FRAMELESSWINDOW_H
|
#ifndef FRAMELESSWINDOW_H
|
||||||
#define FRAMELESSWINDOW_H
|
#define FRAMELESSWINDOW_H
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
class QVBoxLayout;
|
class QVBoxLayout;
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
class FramelessWindow : public QWidget
|
class FramelessWindow : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit FramelessWindow(QWidget *parent = nullptr);
|
explicit FramelessWindow(QWidget *parent = nullptr);
|
||||||
|
|
||||||
void setCentralWidget(QWidget * widget);
|
void setCentralWidget(QWidget * widget);
|
||||||
void installResizeCapture(QObject* widget);
|
void installResizeCapture(QObject* widget);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool eventFilter(QObject *o, QEvent *e) override;
|
bool eventFilter(QObject *o, QEvent *e) override;
|
||||||
bool mouseHover(QHoverEvent* event, QWidget* wg);
|
bool mouseHover(QHoverEvent* event, QWidget* wg);
|
||||||
bool mousePress(QMouseEvent* event);
|
bool mousePress(QMouseEvent* event);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Qt::Edges m_oldEdges;
|
Qt::Edges m_oldEdges;
|
||||||
Qt::CursorShape m_oldCursorShape;
|
Qt::CursorShape m_oldCursorShape;
|
||||||
|
|
||||||
Qt::CursorShape getCursorByEdge(const Qt::Edges& edges, Qt::CursorShape default_cursor);
|
Qt::CursorShape getCursorByEdge(const Qt::Edges& edges, Qt::CursorShape default_cursor);
|
||||||
Qt::Edges getEdgesByPos(const QPoint pos, const QRect& winrect);
|
Qt::Edges getEdgesByPos(const QPoint pos, const QRect& winrect);
|
||||||
|
|
||||||
QVBoxLayout * m_centralLayout = nullptr;
|
QVBoxLayout * m_centralLayout = nullptr;
|
||||||
QWidget * m_centralWidget = nullptr; // just a pointer, doesn't take the ownership.
|
QWidget * m_centralWidget = nullptr; // just a pointer, doesn't take the ownership.
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // FRAMELESSWINDOW_H
|
#endif // FRAMELESSWINDOW_H
|
||||||
|
@ -1,371 +1,371 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "graphicsview.h"
|
#include "graphicsview.h"
|
||||||
|
|
||||||
#include "graphicsscene.h"
|
#include "graphicsscene.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QScrollBar>
|
#include <QScrollBar>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QImageReader>
|
#include <QImageReader>
|
||||||
#include <QStyleOptionGraphicsItem>
|
#include <QStyleOptionGraphicsItem>
|
||||||
|
|
||||||
GraphicsView::GraphicsView(QWidget *parent)
|
GraphicsView::GraphicsView(QWidget *parent)
|
||||||
: QGraphicsView (parent)
|
: QGraphicsView (parent)
|
||||||
{
|
{
|
||||||
setDragMode(QGraphicsView::ScrollHandDrag);
|
setDragMode(QGraphicsView::ScrollHandDrag);
|
||||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
setResizeAnchor(QGraphicsView::AnchorUnderMouse);
|
setResizeAnchor(QGraphicsView::AnchorUnderMouse);
|
||||||
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
|
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
|
||||||
setStyleSheet("background-color: rgba(0, 0, 0, 220);"
|
setStyleSheet("background-color: rgba(0, 0, 0, 220);"
|
||||||
"border-radius: 3px;");
|
"border-radius: 3px;");
|
||||||
setAcceptDrops(false);
|
setAcceptDrops(false);
|
||||||
setCheckerboardEnabled(false);
|
setCheckerboardEnabled(false);
|
||||||
|
|
||||||
connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, &GraphicsView::viewportRectChanged);
|
connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, &GraphicsView::viewportRectChanged);
|
||||||
connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &GraphicsView::viewportRectChanged);
|
connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &GraphicsView::viewportRectChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::showFileFromPath(const QString &filePath)
|
void GraphicsView::showFileFromPath(const QString &filePath)
|
||||||
{
|
{
|
||||||
emit navigatorViewRequired(false, transform());
|
emit navigatorViewRequired(false, transform());
|
||||||
|
|
||||||
if (filePath.endsWith(".svg")) {
|
if (filePath.endsWith(".svg")) {
|
||||||
showSvg(filePath);
|
showSvg(filePath);
|
||||||
} else {
|
} else {
|
||||||
QImageReader imageReader(filePath);
|
QImageReader imageReader(filePath);
|
||||||
imageReader.setAutoTransform(true);
|
imageReader.setAutoTransform(true);
|
||||||
imageReader.setDecideFormatFromContent(true);
|
imageReader.setDecideFormatFromContent(true);
|
||||||
imageReader.setAllocationLimit(0);
|
imageReader.setAllocationLimit(0);
|
||||||
|
|
||||||
// Since if the image format / plugin does not support this feature, imageFormat() will returns an invalid format.
|
// 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.
|
// 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();
|
// QImage::Format imageFormat = imageReader.imageFormat();
|
||||||
if (imageReader.format().isEmpty()) {
|
if (imageReader.format().isEmpty()) {
|
||||||
showText(tr("File is not a valid image"));
|
showText(tr("File is not a valid image"));
|
||||||
} else if (imageReader.supportsAnimation() && imageReader.imageCount() > 1) {
|
} else if (imageReader.supportsAnimation() && imageReader.imageCount() > 1) {
|
||||||
showAnimated(filePath);
|
showAnimated(filePath);
|
||||||
} else if (!imageReader.canRead()) {
|
} else if (!imageReader.canRead()) {
|
||||||
showText(tr("Image data is invalid or currently unsupported"));
|
showText(tr("Image data is invalid or currently unsupported"));
|
||||||
} else {
|
} else {
|
||||||
QPixmap && pixmap = QPixmap::fromImageReader(&imageReader);
|
QPixmap && pixmap = QPixmap::fromImageReader(&imageReader);
|
||||||
if (pixmap.isNull()) {
|
if (pixmap.isNull()) {
|
||||||
showText(tr("Image data is invalid or currently unsupported"));
|
showText(tr("Image data is invalid or currently unsupported"));
|
||||||
} else {
|
} else {
|
||||||
pixmap.setDevicePixelRatio(devicePixelRatioF());
|
pixmap.setDevicePixelRatio(devicePixelRatioF());
|
||||||
showImage(pixmap);
|
showImage(pixmap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::showImage(const QPixmap &pixmap)
|
void GraphicsView::showImage(const QPixmap &pixmap)
|
||||||
{
|
{
|
||||||
resetTransform();
|
resetTransform();
|
||||||
scene()->showImage(pixmap);
|
scene()->showImage(pixmap);
|
||||||
displayScene();
|
displayScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::showImage(const QImage &image)
|
void GraphicsView::showImage(const QImage &image)
|
||||||
{
|
{
|
||||||
resetTransform();
|
resetTransform();
|
||||||
scene()->showImage(QPixmap::fromImage(image));
|
scene()->showImage(QPixmap::fromImage(image));
|
||||||
displayScene();
|
displayScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::showText(const QString &text)
|
void GraphicsView::showText(const QString &text)
|
||||||
{
|
{
|
||||||
resetTransform();
|
resetTransform();
|
||||||
scene()->showText(text);
|
scene()->showText(text);
|
||||||
displayScene();
|
displayScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::showSvg(const QString &filepath)
|
void GraphicsView::showSvg(const QString &filepath)
|
||||||
{
|
{
|
||||||
resetTransform();
|
resetTransform();
|
||||||
scene()->showSvg(filepath);
|
scene()->showSvg(filepath);
|
||||||
displayScene();
|
displayScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::showAnimated(const QString &filepath)
|
void GraphicsView::showAnimated(const QString &filepath)
|
||||||
{
|
{
|
||||||
resetTransform();
|
resetTransform();
|
||||||
scene()->showAnimated(filepath);
|
scene()->showAnimated(filepath);
|
||||||
displayScene();
|
displayScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphicsScene *GraphicsView::scene() const
|
GraphicsScene *GraphicsView::scene() const
|
||||||
{
|
{
|
||||||
return qobject_cast<GraphicsScene*>(QGraphicsView::scene());
|
return qobject_cast<GraphicsScene*>(QGraphicsView::scene());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::setScene(GraphicsScene *scene)
|
void GraphicsView::setScene(GraphicsScene *scene)
|
||||||
{
|
{
|
||||||
return QGraphicsView::setScene(scene);
|
return QGraphicsView::setScene(scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal GraphicsView::scaleFactor() const
|
qreal GraphicsView::scaleFactor() const
|
||||||
{
|
{
|
||||||
return QStyleOptionGraphicsItem::levelOfDetailFromTransform(transform());
|
return QStyleOptionGraphicsItem::levelOfDetailFromTransform(transform());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::resetTransform()
|
void GraphicsView::resetTransform()
|
||||||
{
|
{
|
||||||
if (!shouldAvoidTransform()) {
|
if (!shouldAvoidTransform()) {
|
||||||
QGraphicsView::resetTransform();
|
QGraphicsView::resetTransform();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::zoomView(qreal scaleFactor)
|
void GraphicsView::zoomView(qreal scaleFactor)
|
||||||
{
|
{
|
||||||
m_enableFitInView = false;
|
m_enableFitInView = false;
|
||||||
scale(scaleFactor, scaleFactor);
|
scale(scaleFactor, scaleFactor);
|
||||||
applyTransformationModeByScaleFactor();
|
applyTransformationModeByScaleFactor();
|
||||||
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is always according to user's view.
|
// This is always according to user's view.
|
||||||
// the direction of the rotation will NOT be clockwise because the y-axis points downwards.
|
// the direction of the rotation will NOT be clockwise because the y-axis points downwards.
|
||||||
void GraphicsView::rotateView(bool clockwise)
|
void GraphicsView::rotateView(bool clockwise)
|
||||||
{
|
{
|
||||||
resetScale();
|
resetScale();
|
||||||
|
|
||||||
QTransform tf(0, clockwise ? 1 : -1, 0,
|
QTransform tf(0, clockwise ? 1 : -1, 0,
|
||||||
clockwise ? -1 : 1, 0, 0,
|
clockwise ? -1 : 1, 0, 0,
|
||||||
0, 0, 1);
|
0, 0, 1);
|
||||||
tf = transform() * tf;
|
tf = transform() * tf;
|
||||||
setTransform(tf);
|
setTransform(tf);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::flipView(bool horizontal)
|
void GraphicsView::flipView(bool horizontal)
|
||||||
{
|
{
|
||||||
QTransform tf(horizontal ? -1 : 1, 0, 0,
|
QTransform tf(horizontal ? -1 : 1, 0, 0,
|
||||||
0, horizontal ? 1 : -1, 0,
|
0, horizontal ? 1 : -1, 0,
|
||||||
0, 0, 1);
|
0, 0, 1);
|
||||||
tf = transform() * tf;
|
tf = transform() * tf;
|
||||||
setTransform(tf);
|
setTransform(tf);
|
||||||
|
|
||||||
// Ensure the navigation view is also flipped.
|
// Ensure the navigation view is also flipped.
|
||||||
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::resetScale()
|
void GraphicsView::resetScale()
|
||||||
{
|
{
|
||||||
setTransform(resetScale(transform()));
|
setTransform(resetScale(transform()));
|
||||||
applyTransformationModeByScaleFactor();
|
applyTransformationModeByScaleFactor();
|
||||||
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::fitInView(const QRectF &rect, Qt::AspectRatioMode aspectRadioMode)
|
void GraphicsView::fitInView(const QRectF &rect, Qt::AspectRatioMode aspectRadioMode)
|
||||||
{
|
{
|
||||||
QGraphicsView::fitInView(rect, aspectRadioMode);
|
QGraphicsView::fitInView(rect, aspectRadioMode);
|
||||||
applyTransformationModeByScaleFactor();
|
applyTransformationModeByScaleFactor();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::fitByOrientation(Qt::Orientation ori, bool scaleDownOnly)
|
void GraphicsView::fitByOrientation(Qt::Orientation ori, bool scaleDownOnly)
|
||||||
{
|
{
|
||||||
resetScale();
|
resetScale();
|
||||||
|
|
||||||
QRectF viewRect = this->viewport()->rect().adjusted(2, 2, -2, -2);
|
QRectF viewRect = this->viewport()->rect().adjusted(2, 2, -2, -2);
|
||||||
QRectF imageRect = transform().mapRect(sceneRect());
|
QRectF imageRect = transform().mapRect(sceneRect());
|
||||||
|
|
||||||
qreal ratio;
|
qreal ratio;
|
||||||
|
|
||||||
if (ori == Qt::Horizontal) {
|
if (ori == Qt::Horizontal) {
|
||||||
ratio = viewRect.width() / imageRect.width();
|
ratio = viewRect.width() / imageRect.width();
|
||||||
} else {
|
} else {
|
||||||
ratio = viewRect.height() / imageRect.height();
|
ratio = viewRect.height() / imageRect.height();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scaleDownOnly && ratio > 1) ratio = 1;
|
if (scaleDownOnly && ratio > 1) ratio = 1;
|
||||||
|
|
||||||
scale(ratio, ratio);
|
scale(ratio, ratio);
|
||||||
centerOn(imageRect.top(), 0);
|
centerOn(imageRect.top(), 0);
|
||||||
m_enableFitInView = false;
|
m_enableFitInView = false;
|
||||||
|
|
||||||
applyTransformationModeByScaleFactor();
|
applyTransformationModeByScaleFactor();
|
||||||
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::displayScene()
|
void GraphicsView::displayScene()
|
||||||
{
|
{
|
||||||
if (shouldAvoidTransform()) {
|
if (shouldAvoidTransform()) {
|
||||||
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSceneBiggerThanView()) {
|
if (isSceneBiggerThanView()) {
|
||||||
fitInView(sceneRect(), Qt::KeepAspectRatio);
|
fitInView(sceneRect(), Qt::KeepAspectRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_enableFitInView = true;
|
m_enableFitInView = true;
|
||||||
m_firstUserMediaLoaded = true;
|
m_firstUserMediaLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GraphicsView::isSceneBiggerThanView() const
|
bool GraphicsView::isSceneBiggerThanView() const
|
||||||
{
|
{
|
||||||
if (!isThingSmallerThanWindowWith(transform())) {
|
if (!isThingSmallerThanWindowWith(transform())) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automately do fit in view when viewport(window) smaller than image original size.
|
// Automately do fit in view when viewport(window) smaller than image original size.
|
||||||
void GraphicsView::setEnableAutoFitInView(bool enable)
|
void GraphicsView::setEnableAutoFitInView(bool enable)
|
||||||
{
|
{
|
||||||
m_enableFitInView = enable;
|
m_enableFitInView = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GraphicsView::avoidResetTransform() const
|
bool GraphicsView::avoidResetTransform() const
|
||||||
{
|
{
|
||||||
return m_avoidResetTransform;
|
return m_avoidResetTransform;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::setAvoidResetTransform(bool avoidReset)
|
void GraphicsView::setAvoidResetTransform(bool avoidReset)
|
||||||
{
|
{
|
||||||
m_avoidResetTransform = avoidReset;
|
m_avoidResetTransform = avoidReset;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline double zeroOrOne(double number)
|
inline double zeroOrOne(double number)
|
||||||
{
|
{
|
||||||
return qFuzzyIsNull(number) ? 0 : (number > 0 ? 1 : -1);
|
return qFuzzyIsNull(number) ? 0 : (number > 0 ? 1 : -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: this only works if we only have 90 degree based rotation
|
// Note: this only works if we only have 90 degree based rotation
|
||||||
// and no shear/translate.
|
// and no shear/translate.
|
||||||
QTransform GraphicsView::resetScale(const QTransform & orig)
|
QTransform GraphicsView::resetScale(const QTransform & orig)
|
||||||
{
|
{
|
||||||
return QTransform(zeroOrOne(orig.m11()), zeroOrOne(orig.m12()),
|
return QTransform(zeroOrOne(orig.m11()), zeroOrOne(orig.m12()),
|
||||||
zeroOrOne(orig.m21()), zeroOrOne(orig.m22()),
|
zeroOrOne(orig.m21()), zeroOrOne(orig.m22()),
|
||||||
orig.dx(), orig.dy());
|
orig.dx(), orig.dy());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::toggleCheckerboard(bool invertCheckerboardColor)
|
void GraphicsView::toggleCheckerboard(bool invertCheckerboardColor)
|
||||||
{
|
{
|
||||||
setCheckerboardEnabled(!m_checkerboardEnabled, invertCheckerboardColor);
|
setCheckerboardEnabled(!m_checkerboardEnabled, invertCheckerboardColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::mousePressEvent(QMouseEvent *event)
|
void GraphicsView::mousePressEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
if (shouldIgnoreMousePressMoveEvent(event)) {
|
if (shouldIgnoreMousePressMoveEvent(event)) {
|
||||||
event->ignore();
|
event->ignore();
|
||||||
// blumia: return here, or the QMouseEvent event transparency won't
|
// blumia: return here, or the QMouseEvent event transparency won't
|
||||||
// work if we set a QGraphicsView::ScrollHandDrag drag mode.
|
// work if we set a QGraphicsView::ScrollHandDrag drag mode.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return QGraphicsView::mousePressEvent(event);
|
return QGraphicsView::mousePressEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::mouseMoveEvent(QMouseEvent *event)
|
void GraphicsView::mouseMoveEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
if (shouldIgnoreMousePressMoveEvent(event)) {
|
if (shouldIgnoreMousePressMoveEvent(event)) {
|
||||||
event->ignore();
|
event->ignore();
|
||||||
}
|
}
|
||||||
|
|
||||||
return QGraphicsView::mouseMoveEvent(event);
|
return QGraphicsView::mouseMoveEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::mouseReleaseEvent(QMouseEvent *event)
|
void GraphicsView::mouseReleaseEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
if (event->button() == Qt::ForwardButton || event->button() == Qt::BackButton) {
|
if (event->button() == Qt::ForwardButton || event->button() == Qt::BackButton) {
|
||||||
event->ignore();
|
event->ignore();
|
||||||
} else {
|
} else {
|
||||||
QGraphicsItem *item = itemAt(event->pos());
|
QGraphicsItem *item = itemAt(event->pos());
|
||||||
if (!item) {
|
if (!item) {
|
||||||
event->ignore();
|
event->ignore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return QGraphicsView::mouseReleaseEvent(event);
|
return QGraphicsView::mouseReleaseEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::wheelEvent(QWheelEvent *event)
|
void GraphicsView::wheelEvent(QWheelEvent *event)
|
||||||
{
|
{
|
||||||
event->ignore();
|
event->ignore();
|
||||||
// blumia: no need for calling parent method.
|
// blumia: no need for calling parent method.
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::resizeEvent(QResizeEvent *event)
|
void GraphicsView::resizeEvent(QResizeEvent *event)
|
||||||
{
|
{
|
||||||
if (m_enableFitInView) {
|
if (m_enableFitInView) {
|
||||||
bool originalSizeSmallerThanWindow = isThingSmallerThanWindowWith(resetScale(transform()));
|
bool originalSizeSmallerThanWindow = isThingSmallerThanWindowWith(resetScale(transform()));
|
||||||
if (originalSizeSmallerThanWindow && scaleFactor() >= 1) {
|
if (originalSizeSmallerThanWindow && scaleFactor() >= 1) {
|
||||||
// no longer need to do fitInView()
|
// no longer need to do fitInView()
|
||||||
// but we leave the m_enableFitInView value unchanged in case
|
// but we leave the m_enableFitInView value unchanged in case
|
||||||
// user resize down the window again.
|
// user resize down the window again.
|
||||||
} else if (originalSizeSmallerThanWindow && scaleFactor() < 1) {
|
} else if (originalSizeSmallerThanWindow && scaleFactor() < 1) {
|
||||||
resetScale();
|
resetScale();
|
||||||
} else {
|
} else {
|
||||||
fitInView(sceneRect(), Qt::KeepAspectRatio);
|
fitInView(sceneRect(), Qt::KeepAspectRatio);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
||||||
}
|
}
|
||||||
return QGraphicsView::resizeEvent(event);
|
return QGraphicsView::resizeEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GraphicsView::isThingSmallerThanWindowWith(const QTransform &transform) const
|
bool GraphicsView::isThingSmallerThanWindowWith(const QTransform &transform) const
|
||||||
{
|
{
|
||||||
return rect().size().expandedTo(transform.mapRect(sceneRect()).size().toSize())
|
return rect().size().expandedTo(transform.mapRect(sceneRect()).size().toSize())
|
||||||
== rect().size();
|
== rect().size();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GraphicsView::shouldIgnoreMousePressMoveEvent(const QMouseEvent *event) const
|
bool GraphicsView::shouldIgnoreMousePressMoveEvent(const QMouseEvent *event) const
|
||||||
{
|
{
|
||||||
if (event->buttons() == Qt::NoButton) {
|
if (event->buttons() == Qt::NoButton) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QGraphicsItem *item = itemAt(event->pos());
|
QGraphicsItem *item = itemAt(event->pos());
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isThingSmallerThanWindowWith(transform())) {
|
if (isThingSmallerThanWindowWith(transform())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::setCheckerboardEnabled(bool enabled, bool invertColor)
|
void GraphicsView::setCheckerboardEnabled(bool enabled, bool invertColor)
|
||||||
{
|
{
|
||||||
m_checkerboardEnabled = enabled;
|
m_checkerboardEnabled = enabled;
|
||||||
bool isLightCheckerboard = Settings::instance()->useLightCheckerboard() ^ invertColor;
|
bool isLightCheckerboard = Settings::instance()->useLightCheckerboard() ^ invertColor;
|
||||||
if (m_checkerboardEnabled) {
|
if (m_checkerboardEnabled) {
|
||||||
// Prepare background check-board pattern
|
// Prepare background check-board pattern
|
||||||
QPixmap tilePixmap(0x20, 0x20);
|
QPixmap tilePixmap(0x20, 0x20);
|
||||||
tilePixmap.fill(isLightCheckerboard ? QColor(220, 220, 220, 170) : QColor(35, 35, 35, 170));
|
tilePixmap.fill(isLightCheckerboard ? QColor(220, 220, 220, 170) : QColor(35, 35, 35, 170));
|
||||||
QPainter tilePainter(&tilePixmap);
|
QPainter tilePainter(&tilePixmap);
|
||||||
constexpr QColor color(45, 45, 45, 170);
|
constexpr QColor color(45, 45, 45, 170);
|
||||||
constexpr QColor invertedColor(210, 210, 210, 170);
|
constexpr QColor invertedColor(210, 210, 210, 170);
|
||||||
tilePainter.fillRect(0, 0, 0x10, 0x10, isLightCheckerboard ? invertedColor : color);
|
tilePainter.fillRect(0, 0, 0x10, 0x10, isLightCheckerboard ? invertedColor : color);
|
||||||
tilePainter.fillRect(0x10, 0x10, 0x10, 0x10, isLightCheckerboard ? invertedColor : color);
|
tilePainter.fillRect(0x10, 0x10, 0x10, 0x10, isLightCheckerboard ? invertedColor : color);
|
||||||
tilePainter.end();
|
tilePainter.end();
|
||||||
|
|
||||||
setBackgroundBrush(tilePixmap);
|
setBackgroundBrush(tilePixmap);
|
||||||
} else {
|
} else {
|
||||||
setBackgroundBrush(Qt::transparent);
|
setBackgroundBrush(Qt::transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsView::applyTransformationModeByScaleFactor()
|
void GraphicsView::applyTransformationModeByScaleFactor()
|
||||||
{
|
{
|
||||||
if (this->scaleFactor() < 1) {
|
if (this->scaleFactor() < 1) {
|
||||||
scene()->trySetTransformationMode(Qt::SmoothTransformation, this->scaleFactor());
|
scene()->trySetTransformationMode(Qt::SmoothTransformation, this->scaleFactor());
|
||||||
} else {
|
} else {
|
||||||
scene()->trySetTransformationMode(Qt::FastTransformation, this->scaleFactor());
|
scene()->trySetTransformationMode(Qt::FastTransformation, this->scaleFactor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GraphicsView::shouldAvoidTransform() const
|
bool GraphicsView::shouldAvoidTransform() const
|
||||||
{
|
{
|
||||||
return m_firstUserMediaLoaded && m_avoidResetTransform;
|
return m_firstUserMediaLoaded && m_avoidResetTransform;
|
||||||
}
|
}
|
||||||
|
@ -1,79 +1,79 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef GRAPHICSVIEW_H
|
#ifndef GRAPHICSVIEW_H
|
||||||
#define GRAPHICSVIEW_H
|
#define GRAPHICSVIEW_H
|
||||||
|
|
||||||
#include <QGraphicsView>
|
#include <QGraphicsView>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
class GraphicsScene;
|
class GraphicsScene;
|
||||||
class GraphicsView : public QGraphicsView
|
class GraphicsView : public QGraphicsView
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
GraphicsView(QWidget *parent = nullptr);
|
GraphicsView(QWidget *parent = nullptr);
|
||||||
|
|
||||||
void showFileFromPath(const QString &filePath);
|
void showFileFromPath(const QString &filePath);
|
||||||
|
|
||||||
void showImage(const QPixmap &pixmap);
|
void showImage(const QPixmap &pixmap);
|
||||||
void showImage(const QImage &image);
|
void showImage(const QImage &image);
|
||||||
void showText(const QString &text);
|
void showText(const QString &text);
|
||||||
void showSvg(const QString &filepath);
|
void showSvg(const QString &filepath);
|
||||||
void showAnimated(const QString &filepath);
|
void showAnimated(const QString &filepath);
|
||||||
|
|
||||||
GraphicsScene * scene() const;
|
GraphicsScene * scene() const;
|
||||||
void setScene(GraphicsScene *scene);
|
void setScene(GraphicsScene *scene);
|
||||||
|
|
||||||
qreal scaleFactor() const;
|
qreal scaleFactor() const;
|
||||||
|
|
||||||
void resetTransform();
|
void resetTransform();
|
||||||
void zoomView(qreal scaleFactor);
|
void zoomView(qreal scaleFactor);
|
||||||
void rotateView(bool clockwise = true);
|
void rotateView(bool clockwise = true);
|
||||||
void flipView(bool horizontal = true);
|
void flipView(bool horizontal = true);
|
||||||
void resetScale();
|
void resetScale();
|
||||||
void fitInView(const QRectF &rect, Qt::AspectRatioMode aspectRadioMode = Qt::IgnoreAspectRatio);
|
void fitInView(const QRectF &rect, Qt::AspectRatioMode aspectRadioMode = Qt::IgnoreAspectRatio);
|
||||||
void fitByOrientation(Qt::Orientation ori = Qt::Horizontal, bool scaleDownOnly = false);
|
void fitByOrientation(Qt::Orientation ori = Qt::Horizontal, bool scaleDownOnly = false);
|
||||||
|
|
||||||
void displayScene();
|
void displayScene();
|
||||||
bool isSceneBiggerThanView() const;
|
bool isSceneBiggerThanView() const;
|
||||||
void setEnableAutoFitInView(bool enable = true);
|
void setEnableAutoFitInView(bool enable = true);
|
||||||
|
|
||||||
bool avoidResetTransform() const;
|
bool avoidResetTransform() const;
|
||||||
void setAvoidResetTransform(bool avoidReset);
|
void setAvoidResetTransform(bool avoidReset);
|
||||||
|
|
||||||
static QTransform resetScale(const QTransform & orig);
|
static QTransform resetScale(const QTransform & orig);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void navigatorViewRequired(bool required, QTransform transform);
|
void navigatorViewRequired(bool required, QTransform transform);
|
||||||
void viewportRectChanged();
|
void viewportRectChanged();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void toggleCheckerboard(bool invertCheckerboardColor = false);
|
void toggleCheckerboard(bool invertCheckerboardColor = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void mousePressEvent(QMouseEvent * event) override;
|
void mousePressEvent(QMouseEvent * event) override;
|
||||||
void mouseMoveEvent(QMouseEvent * event) override;
|
void mouseMoveEvent(QMouseEvent * event) override;
|
||||||
void mouseReleaseEvent(QMouseEvent * event) override;
|
void mouseReleaseEvent(QMouseEvent * event) override;
|
||||||
void wheelEvent(QWheelEvent *event) override;
|
void wheelEvent(QWheelEvent *event) override;
|
||||||
void resizeEvent(QResizeEvent *event) override;
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
|
|
||||||
bool isThingSmallerThanWindowWith(const QTransform &transform) const;
|
bool isThingSmallerThanWindowWith(const QTransform &transform) const;
|
||||||
bool shouldIgnoreMousePressMoveEvent(const QMouseEvent *event) const;
|
bool shouldIgnoreMousePressMoveEvent(const QMouseEvent *event) const;
|
||||||
void setCheckerboardEnabled(bool enabled, bool invertColor = false);
|
void setCheckerboardEnabled(bool enabled, bool invertColor = false);
|
||||||
void applyTransformationModeByScaleFactor();
|
void applyTransformationModeByScaleFactor();
|
||||||
|
|
||||||
inline bool shouldAvoidTransform() const;
|
inline bool shouldAvoidTransform() const;
|
||||||
|
|
||||||
// Consider switch to 3 state for "no fit", "always fit" and "fit when view is smaller"?
|
// Consider switch to 3 state for "no fit", "always fit" and "fit when view is smaller"?
|
||||||
// ... or even more? e.g. "fit/snap width" things...
|
// ... or even more? e.g. "fit/snap width" things...
|
||||||
// Currently it's "no fit" when it's false and "fit when view is smaller" when it's true.
|
// Currently it's "no fit" when it's false and "fit when view is smaller" when it's true.
|
||||||
bool m_enableFitInView = false;
|
bool m_enableFitInView = false;
|
||||||
bool m_avoidResetTransform = false;
|
bool m_avoidResetTransform = false;
|
||||||
bool m_checkerboardEnabled = false;
|
bool m_checkerboardEnabled = false;
|
||||||
bool m_useLightCheckerboard = false;
|
bool m_useLightCheckerboard = false;
|
||||||
bool m_firstUserMediaLoaded = false;
|
bool m_firstUserMediaLoaded = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // GRAPHICSVIEW_H
|
#endif // GRAPHICSVIEW_H
|
||||||
|
206
app/main.cpp
206
app/main.cpp
@ -1,103 +1,103 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
|
||||||
#include "playlistmanager.h"
|
#include "playlistmanager.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
#include "fileopeneventhandler.h"
|
#include "fileopeneventhandler.h"
|
||||||
#endif // Q_OS_MACOS
|
#endif // Q_OS_MACOS
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
QCoreApplication::setApplicationName(u"Pineapple Pictures"_s);
|
QCoreApplication::setApplicationName(u"Pineapple Pictures"_s);
|
||||||
QCoreApplication::setApplicationVersion(PPIC_VERSION_STRING);
|
QCoreApplication::setApplicationVersion(PPIC_VERSION_STRING);
|
||||||
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Settings::instance()->hiDpiScaleFactorBehavior());
|
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Settings::instance()->hiDpiScaleFactorBehavior());
|
||||||
|
|
||||||
QApplication a(argc, argv);
|
QApplication a(argc, argv);
|
||||||
|
|
||||||
QTranslator translator;
|
QTranslator translator;
|
||||||
#if defined(TRANSLATION_RESOURCE_EMBEDDING)
|
#if defined(TRANSLATION_RESOURCE_EMBEDDING)
|
||||||
const QString qmDir = u":/i18n/"_s;
|
const QString qmDir = u":/i18n/"_s;
|
||||||
#elif defined(QM_FILE_INSTALL_ABSOLUTE_DIR)
|
#elif defined(QM_FILE_INSTALL_ABSOLUTE_DIR)
|
||||||
const QString qmDir = QT_STRINGIFY(QM_FILE_INSTALL_ABSOLUTE_DIR);
|
const QString qmDir = QT_STRINGIFY(QM_FILE_INSTALL_ABSOLUTE_DIR);
|
||||||
#else
|
#else
|
||||||
const QString qmDir = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath("translations");
|
const QString qmDir = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath("translations");
|
||||||
#endif
|
#endif
|
||||||
if (translator.load(QLocale(), u"PineapplePictures"_s, u"_"_s, qmDir)) {
|
if (translator.load(QLocale(), u"PineapplePictures"_s, u"_"_s, qmDir)) {
|
||||||
QCoreApplication::installTranslator(&translator);
|
QCoreApplication::installTranslator(&translator);
|
||||||
}
|
}
|
||||||
|
|
||||||
QGuiApplication::setApplicationDisplayName(QCoreApplication::translate("main", "Pineapple Pictures"));
|
QGuiApplication::setApplicationDisplayName(QCoreApplication::translate("main", "Pineapple Pictures"));
|
||||||
|
|
||||||
// commandline options
|
// commandline options
|
||||||
QCommandLineOption supportedImageFormats(u"supported-image-formats"_s, QCoreApplication::translate("main", "List supported image format suffixes, and quit program."));
|
QCommandLineOption supportedImageFormats(u"supported-image-formats"_s, QCoreApplication::translate("main", "List supported image format suffixes, and quit program."));
|
||||||
// parse commandline arguments
|
// parse commandline arguments
|
||||||
QCommandLineParser parser;
|
QCommandLineParser parser;
|
||||||
parser.addOption(supportedImageFormats);
|
parser.addOption(supportedImageFormats);
|
||||||
parser.addPositionalArgument("File list", QCoreApplication::translate("main", "File list."));
|
parser.addPositionalArgument("File list", QCoreApplication::translate("main", "File list."));
|
||||||
parser.addHelpOption();
|
parser.addHelpOption();
|
||||||
parser.process(a);
|
parser.process(a);
|
||||||
|
|
||||||
if (parser.isSet(supportedImageFormats)) {
|
if (parser.isSet(supportedImageFormats)) {
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
|
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
|
||||||
fputs(qPrintable(MainWindow::supportedImageFormats().join(QChar('\n'))), stdout);
|
fputs(qPrintable(MainWindow::supportedImageFormats().join(QChar('\n'))), stdout);
|
||||||
::exit(EXIT_SUCCESS);
|
::exit(EXIT_SUCCESS);
|
||||||
#else
|
#else
|
||||||
QCommandLineParser::showMessageAndExit(QCommandLineParser::MessageType::Information,
|
QCommandLineParser::showMessageAndExit(QCommandLineParser::MessageType::Information,
|
||||||
MainWindow::supportedImageFormats().join(QChar('\n')));
|
MainWindow::supportedImageFormats().join(QChar('\n')));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow w;
|
MainWindow w;
|
||||||
w.show();
|
w.show();
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
FileOpenEventHandler * fileOpenEventHandler = new FileOpenEventHandler(&a);
|
FileOpenEventHandler * fileOpenEventHandler = new FileOpenEventHandler(&a);
|
||||||
a.installEventFilter(fileOpenEventHandler);
|
a.installEventFilter(fileOpenEventHandler);
|
||||||
a.connect(fileOpenEventHandler, &FileOpenEventHandler::fileOpen, [&w](const QUrl & url){
|
a.connect(fileOpenEventHandler, &FileOpenEventHandler::fileOpen, [&w](const QUrl & url){
|
||||||
if (w.isHidden()) {
|
if (w.isHidden()) {
|
||||||
w.setWindowOpacity(1);
|
w.setWindowOpacity(1);
|
||||||
w.showNormal();
|
w.showNormal();
|
||||||
} else {
|
} else {
|
||||||
w.activateWindow();
|
w.activateWindow();
|
||||||
}
|
}
|
||||||
w.showUrls({url});
|
w.showUrls({url});
|
||||||
w.initWindowSize();
|
w.initWindowSize();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle dock icon clicks to show hidden window
|
// Handle dock icon clicks to show hidden window
|
||||||
a.connect(&a, &QApplication::applicationStateChanged, [&w](Qt::ApplicationState state) {
|
a.connect(&a, &QApplication::applicationStateChanged, [&w](Qt::ApplicationState state) {
|
||||||
if (state == Qt::ApplicationActive && w.isHidden()) {
|
if (state == Qt::ApplicationActive && w.isHidden()) {
|
||||||
w.showUrls({});
|
w.showUrls({});
|
||||||
w.galleryCurrent(true, true);
|
w.galleryCurrent(true, true);
|
||||||
w.setWindowOpacity(1);
|
w.setWindowOpacity(1);
|
||||||
w.showNormal();
|
w.showNormal();
|
||||||
w.raise();
|
w.raise();
|
||||||
w.activateWindow();
|
w.activateWindow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
#endif // Q_OS_MACOS
|
#endif // Q_OS_MACOS
|
||||||
|
|
||||||
QStringList urlStrList = parser.positionalArguments();
|
QStringList urlStrList = parser.positionalArguments();
|
||||||
QList<QUrl> && urlList = PlaylistManager::convertToUrlList(urlStrList);
|
QList<QUrl> && urlList = PlaylistManager::convertToUrlList(urlStrList);
|
||||||
|
|
||||||
if (!urlList.isEmpty()) {
|
if (!urlList.isEmpty()) {
|
||||||
w.showUrls(urlList);
|
w.showUrls(urlList);
|
||||||
}
|
}
|
||||||
|
|
||||||
w.initWindowSize();
|
w.initWindowSize();
|
||||||
|
|
||||||
return QApplication::exec();
|
return QApplication::exec();
|
||||||
}
|
}
|
||||||
|
@ -313,7 +313,7 @@ QStringList MainWindow::supportedImageFormats()
|
|||||||
void MainWindow::showEvent(QShowEvent *event)
|
void MainWindow::showEvent(QShowEvent *event)
|
||||||
{
|
{
|
||||||
updateWidgetsPosition();
|
updateWidgetsPosition();
|
||||||
|
|
||||||
return FramelessWindow::showEvent(event);
|
return FramelessWindow::showEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
268
app/mainwindow.h
268
app/mainwindow.h
@ -1,134 +1,134 @@
|
|||||||
// SPDX-FileCopyrightText: 2025 Gary Wang <git@blumia.net>
|
// SPDX-FileCopyrightText: 2025 Gary Wang <git@blumia.net>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef MAINWINDOW_H
|
#ifndef MAINWINDOW_H
|
||||||
#define MAINWINDOW_H
|
#define MAINWINDOW_H
|
||||||
|
|
||||||
#include "framelesswindow.h"
|
#include "framelesswindow.h"
|
||||||
|
|
||||||
#include <QParallelAnimationGroup>
|
#include <QParallelAnimationGroup>
|
||||||
#include <QPropertyAnimation>
|
#include <QPropertyAnimation>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
class QGraphicsOpacityEffect;
|
class QGraphicsOpacityEffect;
|
||||||
class QGraphicsView;
|
class QGraphicsView;
|
||||||
class QFileSystemWatcher;
|
class QFileSystemWatcher;
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
class ActionManager;
|
class ActionManager;
|
||||||
class PlaylistManager;
|
class PlaylistManager;
|
||||||
class ToolButton;
|
class ToolButton;
|
||||||
class GraphicsView;
|
class GraphicsView;
|
||||||
class NavigatorView;
|
class NavigatorView;
|
||||||
class BottomButtonGroup;
|
class BottomButtonGroup;
|
||||||
class MainWindow : public FramelessWindow
|
class MainWindow : public FramelessWindow
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MainWindow(QWidget *parent = nullptr);
|
explicit MainWindow(QWidget *parent = nullptr);
|
||||||
~MainWindow() override;
|
~MainWindow() override;
|
||||||
|
|
||||||
void showUrls(const QList<QUrl> &urls);
|
void showUrls(const QList<QUrl> &urls);
|
||||||
void initWindowSize();
|
void initWindowSize();
|
||||||
void adjustWindowSizeBySceneRect();
|
void adjustWindowSizeBySceneRect();
|
||||||
QUrl currentImageFileUrl() const;
|
QUrl currentImageFileUrl() const;
|
||||||
|
|
||||||
void clearGallery();
|
void clearGallery();
|
||||||
void galleryPrev();
|
void galleryPrev();
|
||||||
void galleryNext();
|
void galleryNext();
|
||||||
void galleryCurrent(bool showLoadImageHintWhenEmpty, bool reloadImage);
|
void galleryCurrent(bool showLoadImageHintWhenEmpty, bool reloadImage);
|
||||||
|
|
||||||
static QStringList supportedImageFormats();
|
static QStringList supportedImageFormats();
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void showEvent(QShowEvent *event) override;
|
void showEvent(QShowEvent *event) override;
|
||||||
void enterEvent(QEnterEvent *event) override;
|
void enterEvent(QEnterEvent *event) override;
|
||||||
void leaveEvent(QEvent *event) override;
|
void leaveEvent(QEvent *event) override;
|
||||||
void mousePressEvent(QMouseEvent *event) override;
|
void mousePressEvent(QMouseEvent *event) override;
|
||||||
void mouseMoveEvent(QMouseEvent *event) override;
|
void mouseMoveEvent(QMouseEvent *event) override;
|
||||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||||
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||||
void wheelEvent(QWheelEvent *event) override;
|
void wheelEvent(QWheelEvent *event) override;
|
||||||
void resizeEvent(QResizeEvent *event) override;
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
void contextMenuEvent(QContextMenuEvent *event) override;
|
void contextMenuEvent(QContextMenuEvent *event) override;
|
||||||
void dragEnterEvent(QDragEnterEvent *event) override;
|
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||||
void dragMoveEvent(QDragMoveEvent *event) override;
|
void dragMoveEvent(QDragMoveEvent *event) override;
|
||||||
void dropEvent(QDropEvent *event) override;
|
void dropEvent(QDropEvent *event) override;
|
||||||
|
|
||||||
void centerWindow();
|
void centerWindow();
|
||||||
void closeWindow();
|
void closeWindow();
|
||||||
void updateWidgetsPosition();
|
void updateWidgetsPosition();
|
||||||
void toggleProtectedMode();
|
void toggleProtectedMode();
|
||||||
void toggleStayOnTop();
|
void toggleStayOnTop();
|
||||||
void toggleAvoidResetTransform();
|
void toggleAvoidResetTransform();
|
||||||
bool stayOnTop() const;
|
bool stayOnTop() const;
|
||||||
bool canPaste() const;
|
bool canPaste() const;
|
||||||
void quitAppAction(bool force = false);
|
void quitAppAction(bool force = false);
|
||||||
void toggleFullscreen();
|
void toggleFullscreen();
|
||||||
void toggleMaximize();
|
void toggleMaximize();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QSize sizeHint() const override;
|
QSize sizeHint() const override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void on_actionOpen_triggered();
|
void on_actionOpen_triggered();
|
||||||
|
|
||||||
void on_actionActualSize_triggered();
|
void on_actionActualSize_triggered();
|
||||||
void on_actionToggleMaximize_triggered();
|
void on_actionToggleMaximize_triggered();
|
||||||
void on_actionZoomIn_triggered();
|
void on_actionZoomIn_triggered();
|
||||||
void on_actionZoomOut_triggered();
|
void on_actionZoomOut_triggered();
|
||||||
void on_actionToggleCheckerboard_triggered();
|
void on_actionToggleCheckerboard_triggered();
|
||||||
void on_actionRotateClockwise_triggered();
|
void on_actionRotateClockwise_triggered();
|
||||||
void on_actionRotateCounterClockwise_triggered();
|
void on_actionRotateCounterClockwise_triggered();
|
||||||
|
|
||||||
void on_actionPrevPicture_triggered();
|
void on_actionPrevPicture_triggered();
|
||||||
void on_actionNextPicture_triggered();
|
void on_actionNextPicture_triggered();
|
||||||
|
|
||||||
void on_actionTogglePauseAnimation_triggered();
|
void on_actionTogglePauseAnimation_triggered();
|
||||||
void on_actionAnimationNextFrame_triggered();
|
void on_actionAnimationNextFrame_triggered();
|
||||||
|
|
||||||
void on_actionHorizontalFlip_triggered();
|
void on_actionHorizontalFlip_triggered();
|
||||||
void on_actionFitInView_triggered();
|
void on_actionFitInView_triggered();
|
||||||
void on_actionFitByWidth_triggered();
|
void on_actionFitByWidth_triggered();
|
||||||
void on_actionCopyPixmap_triggered();
|
void on_actionCopyPixmap_triggered();
|
||||||
void on_actionCopyFilePath_triggered();
|
void on_actionCopyFilePath_triggered();
|
||||||
void on_actionPaste_triggered();
|
void on_actionPaste_triggered();
|
||||||
void on_actionTrash_triggered();
|
void on_actionTrash_triggered();
|
||||||
void on_actionToggleStayOnTop_triggered();
|
void on_actionToggleStayOnTop_triggered();
|
||||||
void on_actionToggleProtectMode_triggered();
|
void on_actionToggleProtectMode_triggered();
|
||||||
void on_actionToggleAvoidResetTransform_triggered();
|
void on_actionToggleAvoidResetTransform_triggered();
|
||||||
void on_actionSettings_triggered();
|
void on_actionSettings_triggered();
|
||||||
void on_actionHelp_triggered();
|
void on_actionHelp_triggered();
|
||||||
void on_actionProperties_triggered();
|
void on_actionProperties_triggered();
|
||||||
void on_actionLocateInFileManager_triggered();
|
void on_actionLocateInFileManager_triggered();
|
||||||
void on_actionQuitApp_triggered();
|
void on_actionQuitApp_triggered();
|
||||||
|
|
||||||
void doCloseWindow();
|
void doCloseWindow();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool updateFileWatcher(const QString & basePath = QString());
|
bool updateFileWatcher(const QString & basePath = QString());
|
||||||
void updateGalleryButtonsVisibility();
|
void updateGalleryButtonsVisibility();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ActionManager *m_am;
|
ActionManager *m_am;
|
||||||
PlaylistManager *m_pm;
|
PlaylistManager *m_pm;
|
||||||
|
|
||||||
QPoint m_oldMousePos;
|
QPoint m_oldMousePos;
|
||||||
QPropertyAnimation *m_fadeOutAnimation;
|
QPropertyAnimation *m_fadeOutAnimation;
|
||||||
QPropertyAnimation *m_floatUpAnimation;
|
QPropertyAnimation *m_floatUpAnimation;
|
||||||
QParallelAnimationGroup *m_exitAnimationGroup;
|
QParallelAnimationGroup *m_exitAnimationGroup;
|
||||||
QFileSystemWatcher *m_fileSystemWatcher;
|
QFileSystemWatcher *m_fileSystemWatcher;
|
||||||
ToolButton *m_closeButton;
|
ToolButton *m_closeButton;
|
||||||
ToolButton *m_prevButton;
|
ToolButton *m_prevButton;
|
||||||
ToolButton *m_nextButton;
|
ToolButton *m_nextButton;
|
||||||
GraphicsView *m_graphicsView;
|
GraphicsView *m_graphicsView;
|
||||||
NavigatorView *m_gv;
|
NavigatorView *m_gv;
|
||||||
BottomButtonGroup *m_bottomButtonGroup;
|
BottomButtonGroup *m_bottomButtonGroup;
|
||||||
bool m_protectedMode = false;
|
bool m_protectedMode = false;
|
||||||
bool m_clickedOnWindow = false;
|
bool m_clickedOnWindow = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MAINWINDOW_H
|
#endif // MAINWINDOW_H
|
||||||
|
@ -1,110 +1,110 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "metadatadialog.h"
|
#include "metadatadialog.h"
|
||||||
|
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QStyledItemDelegate>
|
#include <QStyledItemDelegate>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
|
|
||||||
#include "metadatamodel.h"
|
#include "metadatamodel.h"
|
||||||
|
|
||||||
class PropertyTreeView : public QTreeView
|
class PropertyTreeView : public QTreeView
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit PropertyTreeView(QWidget* parent) : QTreeView(parent) {}
|
explicit PropertyTreeView(QWidget* parent) : QTreeView(parent) {}
|
||||||
~PropertyTreeView() {}
|
~PropertyTreeView() {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void rowsInserted(const QModelIndex& parent, int start, int end) override
|
void rowsInserted(const QModelIndex& parent, int start, int end) override
|
||||||
{
|
{
|
||||||
QTreeView::rowsInserted(parent, start, end);
|
QTreeView::rowsInserted(parent, start, end);
|
||||||
if (!parent.isValid()) {
|
if (!parent.isValid()) {
|
||||||
// we are inserting a section group
|
// we are inserting a section group
|
||||||
for (int row = start; row <= end; ++row) {
|
for (int row = start; row <= end; ++row) {
|
||||||
setupSection(row);
|
setupSection(row);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// we are inserting a property
|
// we are inserting a property
|
||||||
setRowHidden(parent.row(), QModelIndex(), false);
|
setRowHidden(parent.row(), QModelIndex(), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() override
|
void reset() override
|
||||||
{
|
{
|
||||||
QTreeView::reset();
|
QTreeView::reset();
|
||||||
if (model()) {
|
if (model()) {
|
||||||
for (int row = 0; row < model()->rowCount(); ++row) {
|
for (int row = 0; row < model()->rowCount(); ++row) {
|
||||||
setupSection(row);
|
setupSection(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupSection(int row)
|
void setupSection(int row)
|
||||||
{
|
{
|
||||||
expand(model()->index(row, 0));
|
expand(model()->index(row, 0));
|
||||||
setFirstColumnSpanned(row, QModelIndex(), true);
|
setFirstColumnSpanned(row, QModelIndex(), true);
|
||||||
setRowHidden(row, QModelIndex(), !model()->hasChildren(model()->index(row, 0)));
|
setRowHidden(row, QModelIndex(), !model()->hasChildren(model()->index(row, 0)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class PropertyTreeItemDelegate : public QStyledItemDelegate
|
class PropertyTreeItemDelegate : public QStyledItemDelegate
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PropertyTreeItemDelegate(QObject* parent)
|
PropertyTreeItemDelegate(QObject* parent)
|
||||||
: QStyledItemDelegate(parent)
|
: QStyledItemDelegate(parent)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override
|
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override
|
||||||
{
|
{
|
||||||
QStyleOptionViewItem opt = option;
|
QStyleOptionViewItem opt = option;
|
||||||
if (!index.parent().isValid()) {
|
if (!index.parent().isValid()) {
|
||||||
opt.font.setBold(true);
|
opt.font.setBold(true);
|
||||||
opt.features.setFlag(QStyleOptionViewItem::Alternate);
|
opt.features.setFlag(QStyleOptionViewItem::Alternate);
|
||||||
}
|
}
|
||||||
QStyledItemDelegate::paint(painter, opt, index);
|
QStyledItemDelegate::paint(painter, opt, index);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
MetadataDialog::MetadataDialog(QWidget *parent)
|
MetadataDialog::MetadataDialog(QWidget *parent)
|
||||||
: QDialog(parent)
|
: QDialog(parent)
|
||||||
, m_treeView(new PropertyTreeView(this))
|
, m_treeView(new PropertyTreeView(this))
|
||||||
{
|
{
|
||||||
m_treeView->setRootIsDecorated(false);
|
m_treeView->setRootIsDecorated(false);
|
||||||
m_treeView->setIndentation(0);
|
m_treeView->setIndentation(0);
|
||||||
m_treeView->setItemDelegate(new PropertyTreeItemDelegate(m_treeView));
|
m_treeView->setItemDelegate(new PropertyTreeItemDelegate(m_treeView));
|
||||||
m_treeView->header()->resizeSection(0, sizeHint().width() / 2);
|
m_treeView->header()->resizeSection(0, sizeHint().width() / 2);
|
||||||
|
|
||||||
setWindowTitle(tr("Image Metadata"));
|
setWindowTitle(tr("Image Metadata"));
|
||||||
|
|
||||||
QDialogButtonBox * buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
|
QDialogButtonBox * buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
|
||||||
|
|
||||||
setLayout(new QVBoxLayout);
|
setLayout(new QVBoxLayout);
|
||||||
layout()->addWidget(m_treeView);
|
layout()->addWidget(m_treeView);
|
||||||
layout()->addWidget(buttonBox);
|
layout()->addWidget(buttonBox);
|
||||||
|
|
||||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::close);
|
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::close);
|
||||||
|
|
||||||
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
|
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
MetadataDialog::~MetadataDialog()
|
MetadataDialog::~MetadataDialog()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MetadataDialog::setMetadataModel(MetadataModel * model)
|
void MetadataDialog::setMetadataModel(MetadataModel * model)
|
||||||
{
|
{
|
||||||
m_treeView->setModel(model);
|
m_treeView->setModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize MetadataDialog::sizeHint() const
|
QSize MetadataDialog::sizeHint() const
|
||||||
{
|
{
|
||||||
return QSize(520, 350);
|
return QSize(520, 350);
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef METADATADIALOG_H
|
#ifndef METADATADIALOG_H
|
||||||
#define METADATADIALOG_H
|
#define METADATADIALOG_H
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
class QTreeView;
|
class QTreeView;
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
class MetadataModel;
|
class MetadataModel;
|
||||||
class MetadataDialog : public QDialog
|
class MetadataDialog : public QDialog
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit MetadataDialog(QWidget * parent);
|
explicit MetadataDialog(QWidget * parent);
|
||||||
~MetadataDialog() override;
|
~MetadataDialog() override;
|
||||||
|
|
||||||
void setMetadataModel(MetadataModel * model);
|
void setMetadataModel(MetadataModel * model);
|
||||||
|
|
||||||
QSize sizeHint() const override;
|
QSize sizeHint() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QTreeView * m_treeView = nullptr;
|
QTreeView * m_treeView = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // METADATADIALOG_H
|
#endif // METADATADIALOG_H
|
||||||
|
@ -1,320 +1,320 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "metadatamodel.h"
|
#include "metadatamodel.h"
|
||||||
#include "exiv2wrapper.h"
|
#include "exiv2wrapper.h"
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QImageReader>
|
#include <QImageReader>
|
||||||
|
|
||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
|
|
||||||
MetadataModel::MetadataModel(QObject *parent)
|
MetadataModel::MetadataModel(QObject *parent)
|
||||||
: QAbstractItemModel(parent)
|
: QAbstractItemModel(parent)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MetadataModel::~MetadataModel()
|
MetadataModel::~MetadataModel()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MetadataModel::setFile(const QString &imageFilePath)
|
void MetadataModel::setFile(const QString &imageFilePath)
|
||||||
{
|
{
|
||||||
QFileInfo fileInfo(imageFilePath);
|
QFileInfo fileInfo(imageFilePath);
|
||||||
// It'll be fine if we don't re-use the image reader we pass to the graphics scene for now.
|
// It'll be fine if we don't re-use the image reader we pass to the graphics scene for now.
|
||||||
QImageReader imgReader(imageFilePath);
|
QImageReader imgReader(imageFilePath);
|
||||||
imgReader.setAutoTransform(true);
|
imgReader.setAutoTransform(true);
|
||||||
imgReader.setDecideFormatFromContent(true);
|
imgReader.setDecideFormatFromContent(true);
|
||||||
|
|
||||||
const QString & itemTypeString = tr("%1 File").arg(QString(imgReader.format().toUpper()));
|
const QString & itemTypeString = tr("%1 File").arg(QString(imgReader.format().toUpper()));
|
||||||
const QString & sizeString = QLocale().formattedDataSize(fileInfo.size());
|
const QString & sizeString = QLocale().formattedDataSize(fileInfo.size());
|
||||||
const QString & birthTimeString = QLocale().toString(fileInfo.birthTime(), QLocale::LongFormat);
|
const QString & birthTimeString = QLocale().toString(fileInfo.birthTime(), QLocale::LongFormat);
|
||||||
const QString & lastModifiedTimeString = QLocale().toString(fileInfo.lastModified(), QLocale::LongFormat);
|
const QString & lastModifiedTimeString = QLocale().toString(fileInfo.lastModified(), QLocale::LongFormat);
|
||||||
const QString & imageDimensionsString = imageSize(imgReader.size());
|
const QString & imageDimensionsString = imageSize(imgReader.size());
|
||||||
const QString & imageRatioString = imageSizeRatio(imgReader.size());
|
const QString & imageRatioString = imageSizeRatio(imgReader.size());
|
||||||
|
|
||||||
appendSection(u"Description"_s, tr("Description", "Section name."));
|
appendSection(u"Description"_s, tr("Description", "Section name."));
|
||||||
appendSection(u"Origin"_s, tr("Origin", "Section name."));
|
appendSection(u"Origin"_s, tr("Origin", "Section name."));
|
||||||
appendSection(u"Image"_s, tr("Image", "Section name."));
|
appendSection(u"Image"_s, tr("Image", "Section name."));
|
||||||
appendSection(u"Camera"_s, tr("Camera", "Section name."));
|
appendSection(u"Camera"_s, tr("Camera", "Section name."));
|
||||||
appendSection(u"AdvancedPhoto"_s, tr("Advanced photo", "Section name."));
|
appendSection(u"AdvancedPhoto"_s, tr("Advanced photo", "Section name."));
|
||||||
appendSection(u"GPS"_s, tr("GPS", "Section name."));
|
appendSection(u"GPS"_s, tr("GPS", "Section name."));
|
||||||
appendSection(u"File"_s, tr("File", "Section name."));
|
appendSection(u"File"_s, tr("File", "Section name."));
|
||||||
|
|
||||||
if (imgReader.supportsOption(QImageIOHandler::Size)) {
|
if (imgReader.supportsOption(QImageIOHandler::Size)) {
|
||||||
appendProperty(u"Image"_s, u"Image.Dimensions"_s,
|
appendProperty(u"Image"_s, u"Image.Dimensions"_s,
|
||||||
tr("Dimensions"), imageDimensionsString);
|
tr("Dimensions"), imageDimensionsString);
|
||||||
appendProperty(u"Image"_s, u"Image.SizeRatio"_s,
|
appendProperty(u"Image"_s, u"Image.SizeRatio"_s,
|
||||||
tr("Aspect ratio"), imageRatioString);
|
tr("Aspect ratio"), imageRatioString);
|
||||||
}
|
}
|
||||||
if (imgReader.supportsAnimation() && imgReader.imageCount() > 1) {
|
if (imgReader.supportsAnimation() && imgReader.imageCount() > 1) {
|
||||||
appendProperty(u"Image"_s, u"Image.FrameCount"_s,
|
appendProperty(u"Image"_s, u"Image.FrameCount"_s,
|
||||||
tr("Frame count"), QString::number(imgReader.imageCount()));
|
tr("Frame count"), QString::number(imgReader.imageCount()));
|
||||||
}
|
}
|
||||||
|
|
||||||
appendProperty(u"File"_s, u"File.Name"_s,
|
appendProperty(u"File"_s, u"File.Name"_s,
|
||||||
tr("Name"), fileInfo.fileName());
|
tr("Name"), fileInfo.fileName());
|
||||||
appendProperty(u"File"_s, u"File.ItemType"_s,
|
appendProperty(u"File"_s, u"File.ItemType"_s,
|
||||||
tr("Item type"), itemTypeString);
|
tr("Item type"), itemTypeString);
|
||||||
appendProperty(u"File"_s, u"File.Path"_s,
|
appendProperty(u"File"_s, u"File.Path"_s,
|
||||||
tr("Folder path"), QDir::toNativeSeparators(fileInfo.path()));
|
tr("Folder path"), QDir::toNativeSeparators(fileInfo.path()));
|
||||||
appendProperty(u"File"_s, u"File.Size"_s,
|
appendProperty(u"File"_s, u"File.Size"_s,
|
||||||
tr("Size"), sizeString);
|
tr("Size"), sizeString);
|
||||||
appendProperty(u"File"_s, u"File.CreatedTime"_s,
|
appendProperty(u"File"_s, u"File.CreatedTime"_s,
|
||||||
tr("Date created"), birthTimeString);
|
tr("Date created"), birthTimeString);
|
||||||
appendProperty(u"File"_s, u"File.LastModified"_s,
|
appendProperty(u"File"_s, u"File.LastModified"_s,
|
||||||
tr("Date modified"), lastModifiedTimeString);
|
tr("Date modified"), lastModifiedTimeString);
|
||||||
|
|
||||||
Exiv2Wrapper wrapper;
|
Exiv2Wrapper wrapper;
|
||||||
if (wrapper.load(imageFilePath)) {
|
if (wrapper.load(imageFilePath)) {
|
||||||
wrapper.cacheSections();
|
wrapper.cacheSections();
|
||||||
|
|
||||||
appendExivPropertyIfExist(wrapper, u"Description"_s,
|
appendExivPropertyIfExist(wrapper, u"Description"_s,
|
||||||
u"Xmp.dc.title"_s, tr("Title"), true);
|
u"Xmp.dc.title"_s, tr("Title"), true);
|
||||||
appendExivPropertyIfExist(wrapper, u"Description"_s,
|
appendExivPropertyIfExist(wrapper, u"Description"_s,
|
||||||
u"Exif.Image.ImageDescription"_s, tr("Subject"), true);
|
u"Exif.Image.ImageDescription"_s, tr("Subject"), true);
|
||||||
appendExivPropertyIfExist(wrapper, u"Description"_s,
|
appendExivPropertyIfExist(wrapper, u"Description"_s,
|
||||||
u"Exif.Image.Rating"_s, tr("Rating"));
|
u"Exif.Image.Rating"_s, tr("Rating"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Description"_s,
|
appendExivPropertyIfExist(wrapper, u"Description"_s,
|
||||||
u"Xmp.dc.subject"_s, tr("Tags"));
|
u"Xmp.dc.subject"_s, tr("Tags"));
|
||||||
appendPropertyIfNotEmpty(u"Description"_s, u"Description.Comments"_s,
|
appendPropertyIfNotEmpty(u"Description"_s, u"Description.Comments"_s,
|
||||||
tr("Comments"), wrapper.comment());
|
tr("Comments"), wrapper.comment());
|
||||||
|
|
||||||
appendExivPropertyIfExist(wrapper, u"Origin"_s,
|
appendExivPropertyIfExist(wrapper, u"Origin"_s,
|
||||||
u"Exif.Image.Artist"_s, tr("Authors"));
|
u"Exif.Image.Artist"_s, tr("Authors"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Origin"_s,
|
appendExivPropertyIfExist(wrapper, u"Origin"_s,
|
||||||
u"Exif.Photo.DateTimeOriginal"_s, tr("Date taken"));
|
u"Exif.Photo.DateTimeOriginal"_s, tr("Date taken"));
|
||||||
// FIXME: We may fetch the same type of metadata from different metadata collection.
|
// FIXME: We may fetch the same type of metadata from different metadata collection.
|
||||||
// Current implementation is not pretty and may need to do a rework...
|
// Current implementation is not pretty and may need to do a rework...
|
||||||
// appendExivPropertyIfExist(wrapper, QStringLiteral("Origin"),
|
// appendExivPropertyIfExist(wrapper, QStringLiteral("Origin"),
|
||||||
// QStringLiteral("Xmp.xmp.CreatorTool"), tr("Program name"));
|
// QStringLiteral("Xmp.xmp.CreatorTool"), tr("Program name"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Origin"_s,
|
appendExivPropertyIfExist(wrapper, u"Origin"_s,
|
||||||
u"Exif.Image.Software"_s, tr("Program name"));
|
u"Exif.Image.Software"_s, tr("Program name"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Origin"_s,
|
appendExivPropertyIfExist(wrapper, u"Origin"_s,
|
||||||
u"Exif.Image.Copyright"_s, tr("Copyright"));
|
u"Exif.Image.Copyright"_s, tr("Copyright"));
|
||||||
|
|
||||||
appendExivPropertyIfExist(wrapper, u"Image"_s,
|
appendExivPropertyIfExist(wrapper, u"Image"_s,
|
||||||
u"Exif.Image.XResolution"_s, tr("Horizontal resolution"));
|
u"Exif.Image.XResolution"_s, tr("Horizontal resolution"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Image"_s,
|
appendExivPropertyIfExist(wrapper, u"Image"_s,
|
||||||
u"Exif.Image.YResolution"_s, tr("Vertical resolution"));
|
u"Exif.Image.YResolution"_s, tr("Vertical resolution"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Image"_s,
|
appendExivPropertyIfExist(wrapper, u"Image"_s,
|
||||||
u"Exif.Image.ResolutionUnit"_s, tr("Resolution unit"));
|
u"Exif.Image.ResolutionUnit"_s, tr("Resolution unit"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Image"_s,
|
appendExivPropertyIfExist(wrapper, u"Image"_s,
|
||||||
u"Exif.Photo.ColorSpace"_s, tr("Colour representation"));
|
u"Exif.Photo.ColorSpace"_s, tr("Colour representation"));
|
||||||
|
|
||||||
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
||||||
u"Exif.Image.Make"_s, tr("Camera maker"));
|
u"Exif.Image.Make"_s, tr("Camera maker"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
||||||
u"Exif.Image.Model"_s, tr("Camera model"));
|
u"Exif.Image.Model"_s, tr("Camera model"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
||||||
u"Exif.Photo.FNumber"_s, tr("F-stop"));
|
u"Exif.Photo.FNumber"_s, tr("F-stop"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
||||||
u"Exif.Photo.ExposureTime"_s, tr("Exposure time"));
|
u"Exif.Photo.ExposureTime"_s, tr("Exposure time"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
||||||
u"Exif.Photo.ISOSpeedRatings"_s, tr("ISO speed"));
|
u"Exif.Photo.ISOSpeedRatings"_s, tr("ISO speed"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
||||||
u"Exif.Photo.ExposureBiasValue"_s, tr("Exposure bias"));
|
u"Exif.Photo.ExposureBiasValue"_s, tr("Exposure bias"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
||||||
u"Exif.Photo.FocalLength"_s, tr("Focal length"));
|
u"Exif.Photo.FocalLength"_s, tr("Focal length"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
||||||
u"Exif.Photo.MaxApertureValue"_s, tr("Max aperture"));
|
u"Exif.Photo.MaxApertureValue"_s, tr("Max aperture"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
||||||
u"Exif.Photo.MeteringMode"_s, tr("Metering mode"));
|
u"Exif.Photo.MeteringMode"_s, tr("Metering mode"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
||||||
u"Exif.Photo.SubjectDistance"_s, tr("Subject distance"));
|
u"Exif.Photo.SubjectDistance"_s, tr("Subject distance"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
||||||
u"Exif.Photo.Flash"_s, tr("Flash mode"));
|
u"Exif.Photo.Flash"_s, tr("Flash mode"));
|
||||||
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
appendExivPropertyIfExist(wrapper, u"Camera"_s,
|
||||||
u"Exif.Photo.FocalLengthIn35mmFilm"_s, tr("35mm focal length"));
|
u"Exif.Photo.FocalLengthIn35mmFilm"_s, tr("35mm focal length"));
|
||||||
|
|
||||||
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
||||||
u"Exif.Photo.LensModel"_s, tr("Lens model"));
|
u"Exif.Photo.LensModel"_s, tr("Lens model"));
|
||||||
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
||||||
u"Exif.Photo.Contrast"_s, tr("Contrast"));
|
u"Exif.Photo.Contrast"_s, tr("Contrast"));
|
||||||
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
||||||
u"Exif.Photo.BrightnessValue"_s, tr("Brightness"));
|
u"Exif.Photo.BrightnessValue"_s, tr("Brightness"));
|
||||||
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
||||||
u"Exif.Photo.ExposureProgram"_s, tr("Exposure program"));
|
u"Exif.Photo.ExposureProgram"_s, tr("Exposure program"));
|
||||||
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
||||||
u"Exif.Photo.Saturation"_s, tr("Saturation"));
|
u"Exif.Photo.Saturation"_s, tr("Saturation"));
|
||||||
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
||||||
u"Exif.Photo.Sharpness"_s, tr("Sharpness"));
|
u"Exif.Photo.Sharpness"_s, tr("Sharpness"));
|
||||||
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
||||||
u"Exif.Photo.WhiteBalance"_s, tr("White balance"));
|
u"Exif.Photo.WhiteBalance"_s, tr("White balance"));
|
||||||
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
||||||
u"Exif.Photo.DigitalZoomRatio"_s, tr("Digital zoom"));
|
u"Exif.Photo.DigitalZoomRatio"_s, tr("Digital zoom"));
|
||||||
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
appendExivPropertyIfExist(wrapper, u"AdvancedPhoto"_s,
|
||||||
u"Exif.Photo.ExifVersion"_s, tr("EXIF version"));
|
u"Exif.Photo.ExifVersion"_s, tr("EXIF version"));
|
||||||
|
|
||||||
appendExivPropertyIfExist(wrapper, u"GPS"_s,
|
appendExivPropertyIfExist(wrapper, u"GPS"_s,
|
||||||
u"Exif.GPSInfo.GPSLatitudeRef"_s, tr("Latitude reference"));
|
u"Exif.GPSInfo.GPSLatitudeRef"_s, tr("Latitude reference"));
|
||||||
appendExivPropertyIfExist(wrapper, u"GPS"_s,
|
appendExivPropertyIfExist(wrapper, u"GPS"_s,
|
||||||
u"Exif.GPSInfo.GPSLatitude"_s, tr("Latitude"));
|
u"Exif.GPSInfo.GPSLatitude"_s, tr("Latitude"));
|
||||||
appendExivPropertyIfExist(wrapper, u"GPS"_s,
|
appendExivPropertyIfExist(wrapper, u"GPS"_s,
|
||||||
u"Exif.GPSInfo.GPSLongitudeRef"_s, tr("Longitude reference"));
|
u"Exif.GPSInfo.GPSLongitudeRef"_s, tr("Longitude reference"));
|
||||||
appendExivPropertyIfExist(wrapper, u"GPS"_s,
|
appendExivPropertyIfExist(wrapper, u"GPS"_s,
|
||||||
u"Exif.GPSInfo.GPSLongitude"_s, tr("Longitude"));
|
u"Exif.GPSInfo.GPSLongitude"_s, tr("Longitude"));
|
||||||
appendExivPropertyIfExist(wrapper, u"GPS"_s,
|
appendExivPropertyIfExist(wrapper, u"GPS"_s,
|
||||||
u"Exif.GPSInfo.GPSAltitudeRef"_s, tr("Altitude reference"));
|
u"Exif.GPSInfo.GPSAltitudeRef"_s, tr("Altitude reference"));
|
||||||
appendExivPropertyIfExist(wrapper, u"GPS"_s,
|
appendExivPropertyIfExist(wrapper, u"GPS"_s,
|
||||||
u"Exif.GPSInfo.GPSAltitude"_s, tr("Altitude"));
|
u"Exif.GPSInfo.GPSAltitude"_s, tr("Altitude"));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MetadataModel::imageSize(const QSize &size)
|
QString MetadataModel::imageSize(const QSize &size)
|
||||||
{
|
{
|
||||||
QString imageSize;
|
QString imageSize;
|
||||||
|
|
||||||
if (size.isValid()) {
|
if (size.isValid()) {
|
||||||
imageSize = tr("%1 x %2").arg(QString::number(size.width()), QString::number(size.height()));
|
imageSize = tr("%1 x %2").arg(QString::number(size.width()), QString::number(size.height()));
|
||||||
} else {
|
} else {
|
||||||
imageSize = QLatin1Char('-');
|
imageSize = QLatin1Char('-');
|
||||||
}
|
}
|
||||||
|
|
||||||
return imageSize;
|
return imageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
int simplegcd(int a, int b) {
|
int simplegcd(int a, int b) {
|
||||||
return b == 0 ? a : simplegcd(b, a % b);
|
return b == 0 ? a : simplegcd(b, a % b);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MetadataModel::imageSizeRatio(const QSize &size)
|
QString MetadataModel::imageSizeRatio(const QSize &size)
|
||||||
{
|
{
|
||||||
if (!size.isValid()) {
|
if (!size.isValid()) {
|
||||||
return QStringLiteral("-");
|
return QStringLiteral("-");
|
||||||
}
|
}
|
||||||
int gcd = simplegcd(size.width(), size.height());
|
int gcd = simplegcd(size.width(), size.height());
|
||||||
return tr("%1 : %2").arg(QString::number(size.width() / gcd), QString::number(size.height() / gcd));
|
return tr("%1 : %2").arg(QString::number(size.width() / gcd), QString::number(size.height() / gcd));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MetadataModel::appendSection(const QString §ionKey, QStringView sectionDisplayName)
|
bool MetadataModel::appendSection(const QString §ionKey, QStringView sectionDisplayName)
|
||||||
{
|
{
|
||||||
if (m_sections.contains(sectionKey)) {
|
if (m_sections.contains(sectionKey)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_sections.append(sectionKey);
|
m_sections.append(sectionKey);
|
||||||
m_sectionProperties[sectionKey] = qMakePair<QString, QList<QString> >(sectionDisplayName.toString(), {});
|
m_sectionProperties[sectionKey] = qMakePair<QString, QList<QString> >(sectionDisplayName.toString(), {});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MetadataModel::appendPropertyIfNotEmpty(const QString §ionKey, const QString &propertyKey, const QString &propertyDisplayName, const QString &propertyValue)
|
bool MetadataModel::appendPropertyIfNotEmpty(const QString §ionKey, const QString &propertyKey, const QString &propertyDisplayName, const QString &propertyValue)
|
||||||
{
|
{
|
||||||
if (propertyValue.isEmpty()) return false;
|
if (propertyValue.isEmpty()) return false;
|
||||||
|
|
||||||
return appendProperty(sectionKey, propertyKey, propertyDisplayName, propertyValue);
|
return appendProperty(sectionKey, propertyKey, propertyDisplayName, propertyValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MetadataModel::appendProperty(const QString §ionKey, const QString &propertyKey, QStringView propertyDisplayName, QStringView propertyValue)
|
bool MetadataModel::appendProperty(const QString §ionKey, const QString &propertyKey, QStringView propertyDisplayName, QStringView propertyValue)
|
||||||
{
|
{
|
||||||
if (!m_sections.contains(sectionKey)) {
|
if (!m_sections.contains(sectionKey)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QString> & propertyList = m_sectionProperties[sectionKey].second;
|
QList<QString> & propertyList = m_sectionProperties[sectionKey].second;
|
||||||
if (!propertyList.contains(propertyKey)) {
|
if (!propertyList.contains(propertyKey)) {
|
||||||
propertyList.append(propertyKey);
|
propertyList.append(propertyKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_properties[propertyKey] = qMakePair<QString, QString>(propertyDisplayName.toString(), propertyValue.toString());
|
m_properties[propertyKey] = qMakePair<QString, QString>(propertyDisplayName.toString(), propertyValue.toString());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MetadataModel::appendExivPropertyIfExist(const Exiv2Wrapper &wrapper, const QString §ionKey, const QString &exiv2propertyKey, const QString &propertyDisplayName, bool isXmpString)
|
bool MetadataModel::appendExivPropertyIfExist(const Exiv2Wrapper &wrapper, const QString §ionKey, const QString &exiv2propertyKey, const QString &propertyDisplayName, bool isXmpString)
|
||||||
{
|
{
|
||||||
const QString & value = wrapper.value(exiv2propertyKey);
|
const QString & value = wrapper.value(exiv2propertyKey);
|
||||||
if (!value.isEmpty()) {
|
if (!value.isEmpty()) {
|
||||||
appendProperty(sectionKey, exiv2propertyKey,
|
appendProperty(sectionKey, exiv2propertyKey,
|
||||||
propertyDisplayName.isEmpty() ? wrapper.label(exiv2propertyKey) : propertyDisplayName,
|
propertyDisplayName.isEmpty() ? wrapper.label(exiv2propertyKey) : propertyDisplayName,
|
||||||
isXmpString ? Exiv2Wrapper::XmpValue(value) : value);
|
isXmpString ? Exiv2Wrapper::XmpValue(value) : value);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex MetadataModel::index(int row, int column, const QModelIndex &parent) const
|
QModelIndex MetadataModel::index(int row, int column, const QModelIndex &parent) const
|
||||||
{
|
{
|
||||||
if (!hasIndex(row, column, parent)) {
|
if (!hasIndex(row, column, parent)) {
|
||||||
return QModelIndex();
|
return QModelIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parent.isValid()) {
|
if (!parent.isValid()) {
|
||||||
return createIndex(row, column, RowType::SectionRow);
|
return createIndex(row, column, RowType::SectionRow);
|
||||||
} else {
|
} else {
|
||||||
// internalid param: row means nth section it belongs to.
|
// internalid param: row means nth section it belongs to.
|
||||||
return createIndex(row, column, RowType::PropertyRow + parent.row());
|
return createIndex(row, column, RowType::PropertyRow + parent.row());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex MetadataModel::parent(const QModelIndex &child) const
|
QModelIndex MetadataModel::parent(const QModelIndex &child) const
|
||||||
{
|
{
|
||||||
if (!child.isValid()) {
|
if (!child.isValid()) {
|
||||||
return QModelIndex();
|
return QModelIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (child.internalId() == RowType::SectionRow) {
|
if (child.internalId() == RowType::SectionRow) {
|
||||||
return QModelIndex();
|
return QModelIndex();
|
||||||
} else {
|
} else {
|
||||||
return createIndex(child.internalId() - RowType::PropertyRow, 0, SectionRow);
|
return createIndex(child.internalId() - RowType::PropertyRow, 0, SectionRow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int MetadataModel::rowCount(const QModelIndex &parent) const
|
int MetadataModel::rowCount(const QModelIndex &parent) const
|
||||||
{
|
{
|
||||||
if (!parent.isValid()) {
|
if (!parent.isValid()) {
|
||||||
return m_sections.count();
|
return m_sections.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parent.internalId() == RowType::SectionRow) {
|
if (parent.internalId() == RowType::SectionRow) {
|
||||||
const QString & sectionKey = m_sections[parent.row()];
|
const QString & sectionKey = m_sections[parent.row()];
|
||||||
return m_sectionProperties[sectionKey].second.count();
|
return m_sectionProperties[sectionKey].second.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int MetadataModel::columnCount(const QModelIndex &) const
|
int MetadataModel::columnCount(const QModelIndex &) const
|
||||||
{
|
{
|
||||||
// Always key(display name) and value.
|
// Always key(display name) and value.
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant MetadataModel::data(const QModelIndex &index, int role) const
|
QVariant MetadataModel::data(const QModelIndex &index, int role) const
|
||||||
{
|
{
|
||||||
if (!index.isValid()) {
|
if (!index.isValid()) {
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role != Qt::DisplayRole) {
|
if (role != Qt::DisplayRole) {
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index.internalId() == RowType::SectionRow) {
|
if (index.internalId() == RowType::SectionRow) {
|
||||||
return (index.column() == 0) ? m_sectionProperties[m_sections[index.row()]].first
|
return (index.column() == 0) ? m_sectionProperties[m_sections[index.row()]].first
|
||||||
: QVariant();
|
: QVariant();
|
||||||
} else {
|
} else {
|
||||||
int sectionIndex = index.internalId() - RowType::PropertyRow;
|
int sectionIndex = index.internalId() - RowType::PropertyRow;
|
||||||
const QString & sectionKey = m_sections[sectionIndex];
|
const QString & sectionKey = m_sections[sectionIndex];
|
||||||
const QList<QString> & propertyList = m_sectionProperties[sectionKey].second;
|
const QList<QString> & propertyList = m_sectionProperties[sectionKey].second;
|
||||||
return (index.column() == 0) ? m_properties[propertyList[index.row()]].first
|
return (index.column() == 0) ? m_properties[propertyList[index.row()]].first
|
||||||
: m_properties[propertyList[index.row()]].second;
|
: m_properties[propertyList[index.row()]].second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant MetadataModel::headerData(int section, Qt::Orientation orientation, int role) const
|
QVariant MetadataModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
{
|
{
|
||||||
if (orientation == Qt::Vertical || role != Qt::DisplayRole) {
|
if (orientation == Qt::Vertical || role != Qt::DisplayRole) {
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
return section == 0 ? tr("Property") : tr("Value");
|
return section == 0 ? tr("Property") : tr("Value");
|
||||||
}
|
}
|
||||||
|
@ -1,52 +1,52 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef METADATAMODEL_H
|
#ifndef METADATAMODEL_H
|
||||||
#define METADATAMODEL_H
|
#define METADATAMODEL_H
|
||||||
|
|
||||||
#include <QAbstractItemModel>
|
#include <QAbstractItemModel>
|
||||||
|
|
||||||
class Exiv2Wrapper;
|
class Exiv2Wrapper;
|
||||||
class MetadataModel : public QAbstractItemModel
|
class MetadataModel : public QAbstractItemModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MetadataModel(QObject *parent = nullptr);
|
explicit MetadataModel(QObject *parent = nullptr);
|
||||||
~MetadataModel();
|
~MetadataModel();
|
||||||
|
|
||||||
void setFile(const QString & imageFilePath);
|
void setFile(const QString & imageFilePath);
|
||||||
static QString imageSize(const QSize &size);
|
static QString imageSize(const QSize &size);
|
||||||
static QString imageSizeRatio(const QSize &size);
|
static QString imageSizeRatio(const QSize &size);
|
||||||
bool appendSection(const QString & sectionKey, QStringView sectionDisplayName);
|
bool appendSection(const QString & sectionKey, QStringView sectionDisplayName);
|
||||||
bool appendPropertyIfNotEmpty(const QString & sectionKey, const QString & propertyKey,
|
bool appendPropertyIfNotEmpty(const QString & sectionKey, const QString & propertyKey,
|
||||||
const QString & propertyDisplayName, const QString & propertyValue = QString());
|
const QString & propertyDisplayName, const QString & propertyValue = QString());
|
||||||
bool appendProperty(const QString & sectionKey, const QString & propertyKey,
|
bool appendProperty(const QString & sectionKey, const QString & propertyKey,
|
||||||
QStringView propertyDisplayName, QStringView propertyValue = QString());
|
QStringView propertyDisplayName, QStringView propertyValue = QString());
|
||||||
bool appendExivPropertyIfExist(const Exiv2Wrapper & wrapper, const QString & sectionKey,
|
bool appendExivPropertyIfExist(const Exiv2Wrapper & wrapper, const QString & sectionKey,
|
||||||
const QString & exiv2propertyKey, const QString & propertyDisplayName = QString(),
|
const QString & exiv2propertyKey, const QString & propertyDisplayName = QString(),
|
||||||
bool isXmpString = false);
|
bool isXmpString = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum RowType : quintptr {
|
enum RowType : quintptr {
|
||||||
SectionRow,
|
SectionRow,
|
||||||
PropertyRow,
|
PropertyRow,
|
||||||
};
|
};
|
||||||
|
|
||||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||||
QModelIndex parent(const QModelIndex &child) const override;
|
QModelIndex parent(const QModelIndex &child) const override;
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
int columnCount(const QModelIndex & = QModelIndex()) const override;
|
int columnCount(const QModelIndex & = QModelIndex()) const override;
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
// [SECTION_KEY]
|
// [SECTION_KEY]
|
||||||
QList<QString> m_sections;
|
QList<QString> m_sections;
|
||||||
// {SECTION_KEY: (SECTION_DISPLAY_NAME, [PROPERTY_KEY])}
|
// {SECTION_KEY: (SECTION_DISPLAY_NAME, [PROPERTY_KEY])}
|
||||||
QMap<QString, QPair<QString, QList<QString> > > m_sectionProperties;
|
QMap<QString, QPair<QString, QList<QString> > > m_sectionProperties;
|
||||||
// {PROPERTY_KEY: (PROPERTY_DISPLAY_NAME, PROPERTY_VALUE)}
|
// {PROPERTY_KEY: (PROPERTY_DISPLAY_NAME, PROPERTY_VALUE)}
|
||||||
QMap<QString, QPair<QString, QString> > m_properties;
|
QMap<QString, QPair<QString, QString> > m_properties;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // METADATAMODEL_H
|
#endif // METADATAMODEL_H
|
||||||
|
@ -1,86 +1,86 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "navigatorview.h"
|
#include "navigatorview.h"
|
||||||
|
|
||||||
#include "graphicsview.h"
|
#include "graphicsview.h"
|
||||||
#include "opacityhelper.h"
|
#include "opacityhelper.h"
|
||||||
|
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
NavigatorView::NavigatorView(QWidget *parent)
|
NavigatorView::NavigatorView(QWidget *parent)
|
||||||
: QGraphicsView (parent)
|
: QGraphicsView (parent)
|
||||||
, m_viewportRegion(this->rect())
|
, m_viewportRegion(this->rect())
|
||||||
, m_opacityHelper(new OpacityHelper(this))
|
, m_opacityHelper(new OpacityHelper(this))
|
||||||
{
|
{
|
||||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
setStyleSheet("background-color: rgba(0, 0, 0, 120);"
|
setStyleSheet("background-color: rgba(0, 0, 0, 120);"
|
||||||
"border-radius: 3px;");
|
"border-radius: 3px;");
|
||||||
}
|
}
|
||||||
|
|
||||||
// doesn't take or manage its ownership
|
// doesn't take or manage its ownership
|
||||||
void NavigatorView::setMainView(GraphicsView *mainView)
|
void NavigatorView::setMainView(GraphicsView *mainView)
|
||||||
{
|
{
|
||||||
m_mainView = mainView;
|
m_mainView = mainView;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavigatorView::setOpacity(qreal opacity, bool animated)
|
void NavigatorView::setOpacity(qreal opacity, bool animated)
|
||||||
{
|
{
|
||||||
m_opacityHelper->setOpacity(opacity, animated);
|
m_opacityHelper->setOpacity(opacity, animated);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavigatorView::updateMainViewportRegion()
|
void NavigatorView::updateMainViewportRegion()
|
||||||
{
|
{
|
||||||
if (m_mainView != nullptr) {
|
if (m_mainView != nullptr) {
|
||||||
m_viewportRegion = mapFromScene(m_mainView->mapToScene(m_mainView->rect()));
|
m_viewportRegion = mapFromScene(m_mainView->mapToScene(m_mainView->rect()));
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavigatorView::mousePressEvent(QMouseEvent *event)
|
void NavigatorView::mousePressEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
m_mouseDown = true;
|
m_mouseDown = true;
|
||||||
|
|
||||||
if (m_mainView) {
|
if (m_mainView) {
|
||||||
m_mainView->centerOn(mapToScene(event->pos()));
|
m_mainView->centerOn(mapToScene(event->pos()));
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
event->accept();
|
event->accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavigatorView::mouseMoveEvent(QMouseEvent *event)
|
void NavigatorView::mouseMoveEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
if (m_mouseDown && m_mainView) {
|
if (m_mouseDown && m_mainView) {
|
||||||
m_mainView->centerOn(mapToScene(event->pos()));
|
m_mainView->centerOn(mapToScene(event->pos()));
|
||||||
update();
|
update();
|
||||||
event->accept();
|
event->accept();
|
||||||
} else {
|
} else {
|
||||||
event->ignore();
|
event->ignore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavigatorView::mouseReleaseEvent(QMouseEvent *event)
|
void NavigatorView::mouseReleaseEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
m_mouseDown = false;
|
m_mouseDown = false;
|
||||||
|
|
||||||
event->accept();
|
event->accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavigatorView::wheelEvent(QWheelEvent *event)
|
void NavigatorView::wheelEvent(QWheelEvent *event)
|
||||||
{
|
{
|
||||||
event->ignore();
|
event->ignore();
|
||||||
return QGraphicsView::wheelEvent(event);
|
return QGraphicsView::wheelEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavigatorView::paintEvent(QPaintEvent *event)
|
void NavigatorView::paintEvent(QPaintEvent *event)
|
||||||
{
|
{
|
||||||
QGraphicsView::paintEvent(event);
|
QGraphicsView::paintEvent(event);
|
||||||
|
|
||||||
QPainter painter(viewport());
|
QPainter painter(viewport());
|
||||||
painter.setPen(QPen(Qt::gray, 2));
|
painter.setPen(QPen(Qt::gray, 2));
|
||||||
painter.drawRect(m_viewportRegion.boundingRect());
|
painter.drawRect(m_viewportRegion.boundingRect());
|
||||||
}
|
}
|
||||||
|
@ -1,38 +1,38 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef NAVIGATORVIEW_H
|
#ifndef NAVIGATORVIEW_H
|
||||||
#define NAVIGATORVIEW_H
|
#define NAVIGATORVIEW_H
|
||||||
|
|
||||||
#include <QGraphicsView>
|
#include <QGraphicsView>
|
||||||
|
|
||||||
class OpacityHelper;
|
class OpacityHelper;
|
||||||
class GraphicsView;
|
class GraphicsView;
|
||||||
class NavigatorView : public QGraphicsView
|
class NavigatorView : public QGraphicsView
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
NavigatorView(QWidget *parent = nullptr);
|
NavigatorView(QWidget *parent = nullptr);
|
||||||
|
|
||||||
void setMainView(GraphicsView *mainView);
|
void setMainView(GraphicsView *mainView);
|
||||||
void setOpacity(qreal opacity, bool animated = true);
|
void setOpacity(qreal opacity, bool animated = true);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void updateMainViewportRegion();
|
void updateMainViewportRegion();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void mousePressEvent(QMouseEvent * event) override;
|
void mousePressEvent(QMouseEvent * event) override;
|
||||||
void mouseMoveEvent(QMouseEvent * event) override;
|
void mouseMoveEvent(QMouseEvent * event) override;
|
||||||
void mouseReleaseEvent(QMouseEvent * event) override;
|
void mouseReleaseEvent(QMouseEvent * event) override;
|
||||||
|
|
||||||
void wheelEvent(QWheelEvent *event) override;
|
void wheelEvent(QWheelEvent *event) override;
|
||||||
void paintEvent(QPaintEvent *event) override;
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
|
||||||
bool m_mouseDown = false;
|
bool m_mouseDown = false;
|
||||||
QPolygon m_viewportRegion;
|
QPolygon m_viewportRegion;
|
||||||
QGraphicsView *m_mainView = nullptr;
|
QGraphicsView *m_mainView = nullptr;
|
||||||
OpacityHelper *m_opacityHelper = nullptr;
|
OpacityHelper *m_opacityHelper = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // NAVIGATORVIEW_H
|
#endif // NAVIGATORVIEW_H
|
||||||
|
@ -1,31 +1,31 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "opacityhelper.h"
|
#include "opacityhelper.h"
|
||||||
|
|
||||||
#include <QGraphicsOpacityEffect>
|
#include <QGraphicsOpacityEffect>
|
||||||
#include <QPropertyAnimation>
|
#include <QPropertyAnimation>
|
||||||
|
|
||||||
OpacityHelper::OpacityHelper(QWidget *parent)
|
OpacityHelper::OpacityHelper(QWidget *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_opacityFx(new QGraphicsOpacityEffect(parent))
|
, m_opacityFx(new QGraphicsOpacityEffect(parent))
|
||||||
, m_opacityAnimation(new QPropertyAnimation(m_opacityFx, "opacity"))
|
, m_opacityAnimation(new QPropertyAnimation(m_opacityFx, "opacity"))
|
||||||
{
|
{
|
||||||
parent->setGraphicsEffect(m_opacityFx);
|
parent->setGraphicsEffect(m_opacityFx);
|
||||||
|
|
||||||
m_opacityAnimation->setDuration(300);
|
m_opacityAnimation->setDuration(300);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpacityHelper::setOpacity(qreal opacity, bool animated)
|
void OpacityHelper::setOpacity(qreal opacity, bool animated)
|
||||||
{
|
{
|
||||||
if (!animated) {
|
if (!animated) {
|
||||||
m_opacityFx->setOpacity(opacity);
|
m_opacityFx->setOpacity(opacity);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_opacityAnimation->stop();
|
m_opacityAnimation->stop();
|
||||||
m_opacityAnimation->setStartValue(m_opacityFx->opacity());
|
m_opacityAnimation->setStartValue(m_opacityFx->opacity());
|
||||||
m_opacityAnimation->setEndValue(opacity);
|
m_opacityAnimation->setEndValue(opacity);
|
||||||
m_opacityAnimation->start();
|
m_opacityAnimation->start();
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef OPACITYHELPER_H
|
#ifndef OPACITYHELPER_H
|
||||||
#define OPACITYHELPER_H
|
#define OPACITYHELPER_H
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
class QGraphicsOpacityEffect;
|
class QGraphicsOpacityEffect;
|
||||||
class QPropertyAnimation;
|
class QPropertyAnimation;
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
class OpacityHelper : QObject
|
class OpacityHelper : QObject
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
OpacityHelper(QWidget * parent);
|
OpacityHelper(QWidget * parent);
|
||||||
|
|
||||||
void setOpacity(qreal opacity, bool animated = true);
|
void setOpacity(qreal opacity, bool animated = true);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QGraphicsOpacityEffect * m_opacityFx;
|
QGraphicsOpacityEffect * m_opacityFx;
|
||||||
QPropertyAnimation * m_opacityAnimation;
|
QPropertyAnimation * m_opacityAnimation;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // OPACITYHELPER_H
|
#endif // OPACITYHELPER_H
|
||||||
|
@ -1,283 +1,283 @@
|
|||||||
// SPDX-FileCopyrightText: 2025 Gary Wang <git@blumia.net>
|
// SPDX-FileCopyrightText: 2025 Gary Wang <git@blumia.net>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "playlistmanager.h"
|
#include "playlistmanager.h"
|
||||||
|
|
||||||
#include <QCollator>
|
#include <QCollator>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
PlaylistModel::PlaylistModel(QObject *parent)
|
PlaylistModel::PlaylistModel(QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistModel::~PlaylistModel()
|
PlaylistModel::~PlaylistModel()
|
||||||
= default;
|
= default;
|
||||||
|
|
||||||
void PlaylistModel::setPlaylist(const QList<QUrl> &urls)
|
void PlaylistModel::setPlaylist(const QList<QUrl> &urls)
|
||||||
{
|
{
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_playlist = urls;
|
m_playlist = urls;
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistModel::loadPlaylist(const QList<QUrl> & urls)
|
QModelIndex PlaylistModel::loadPlaylist(const QList<QUrl> & urls)
|
||||||
{
|
{
|
||||||
if (urls.isEmpty()) return {};
|
if (urls.isEmpty()) return {};
|
||||||
if (urls.count() == 1) {
|
if (urls.count() == 1) {
|
||||||
return loadPlaylist(urls.constFirst());
|
return loadPlaylist(urls.constFirst());
|
||||||
} else {
|
} else {
|
||||||
setPlaylist(urls);
|
setPlaylist(urls);
|
||||||
return index(0);
|
return index(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistModel::loadPlaylist(const QUrl &url)
|
QModelIndex PlaylistModel::loadPlaylist(const QUrl &url)
|
||||||
{
|
{
|
||||||
QFileInfo info(url.toLocalFile());
|
QFileInfo info(url.toLocalFile());
|
||||||
QDir dir(info.path());
|
QDir dir(info.path());
|
||||||
QString && currentFileName = info.fileName();
|
QString && currentFileName = info.fileName();
|
||||||
|
|
||||||
if (dir.path() == m_currentDir) {
|
if (dir.path() == m_currentDir) {
|
||||||
int idx = indexOf(url);
|
int idx = indexOf(url);
|
||||||
return idx == -1 ? appendToPlaylist(url) : index(idx);
|
return idx == -1 ? appendToPlaylist(url) : index(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList entryList = dir.entryList(
|
QStringList entryList = dir.entryList(
|
||||||
m_autoLoadSuffixes,
|
m_autoLoadSuffixes,
|
||||||
QDir::Files | QDir::NoSymLinks, QDir::NoSort);
|
QDir::Files | QDir::NoSymLinks, QDir::NoSort);
|
||||||
|
|
||||||
QCollator collator;
|
QCollator collator;
|
||||||
collator.setNumericMode(true);
|
collator.setNumericMode(true);
|
||||||
|
|
||||||
std::sort(entryList.begin(), entryList.end(), collator);
|
std::sort(entryList.begin(), entryList.end(), collator);
|
||||||
|
|
||||||
QList<QUrl> playlist;
|
QList<QUrl> playlist;
|
||||||
|
|
||||||
int idx = -1;
|
int idx = -1;
|
||||||
for (int i = 0; i < entryList.count(); i++) {
|
for (int i = 0; i < entryList.count(); i++) {
|
||||||
const QString & fileName = entryList.at(i);
|
const QString & fileName = entryList.at(i);
|
||||||
const QString & oneEntry = dir.absoluteFilePath(fileName);
|
const QString & oneEntry = dir.absoluteFilePath(fileName);
|
||||||
const QUrl & url = QUrl::fromLocalFile(oneEntry);
|
const QUrl & url = QUrl::fromLocalFile(oneEntry);
|
||||||
playlist.append(url);
|
playlist.append(url);
|
||||||
if (fileName == currentFileName) {
|
if (fileName == currentFileName) {
|
||||||
idx = i;
|
idx = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (idx == -1) {
|
if (idx == -1) {
|
||||||
idx = playlist.count();
|
idx = playlist.count();
|
||||||
playlist.append(url);
|
playlist.append(url);
|
||||||
}
|
}
|
||||||
m_currentDir = dir.path();
|
m_currentDir = dir.path();
|
||||||
|
|
||||||
setPlaylist(playlist);
|
setPlaylist(playlist);
|
||||||
|
|
||||||
return index(idx);
|
return index(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistModel::appendToPlaylist(const QUrl &url)
|
QModelIndex PlaylistModel::appendToPlaylist(const QUrl &url)
|
||||||
{
|
{
|
||||||
const int lastIndex = rowCount();
|
const int lastIndex = rowCount();
|
||||||
beginInsertRows(QModelIndex(), lastIndex, lastIndex);
|
beginInsertRows(QModelIndex(), lastIndex, lastIndex);
|
||||||
m_playlist.append(url);
|
m_playlist.append(url);
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
return index(lastIndex);
|
return index(lastIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PlaylistModel::removeAt(int index)
|
bool PlaylistModel::removeAt(int index)
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= rowCount()) return false;
|
if (index < 0 || index >= rowCount()) return false;
|
||||||
beginRemoveRows(QModelIndex(), index, index);
|
beginRemoveRows(QModelIndex(), index, index);
|
||||||
m_playlist.removeAt(index);
|
m_playlist.removeAt(index);
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PlaylistModel::indexOf(const QUrl &url) const
|
int PlaylistModel::indexOf(const QUrl &url) const
|
||||||
{
|
{
|
||||||
return m_playlist.indexOf(url);
|
return m_playlist.indexOf(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl PlaylistModel::urlByIndex(int index) const
|
QUrl PlaylistModel::urlByIndex(int index) const
|
||||||
{
|
{
|
||||||
return m_playlist.value(index);
|
return m_playlist.value(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList PlaylistModel::autoLoadFilterSuffixes() const
|
QStringList PlaylistModel::autoLoadFilterSuffixes() const
|
||||||
{
|
{
|
||||||
return m_autoLoadSuffixes;
|
return m_autoLoadSuffixes;
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<int, QByteArray> PlaylistModel::roleNames() const
|
QHash<int, QByteArray> PlaylistModel::roleNames() const
|
||||||
{
|
{
|
||||||
QHash<int, QByteArray> result = QAbstractListModel::roleNames();
|
QHash<int, QByteArray> result = QAbstractListModel::roleNames();
|
||||||
result.insert(UrlRole, "url");
|
result.insert(UrlRole, "url");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PlaylistModel::rowCount(const QModelIndex &parent) const
|
int PlaylistModel::rowCount(const QModelIndex &parent) const
|
||||||
{
|
{
|
||||||
return m_playlist.count();
|
return m_playlist.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant PlaylistModel::data(const QModelIndex &index, int role) const
|
QVariant PlaylistModel::data(const QModelIndex &index, int role) const
|
||||||
{
|
{
|
||||||
if (!index.isValid()) return {};
|
if (!index.isValid()) return {};
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case Qt::DisplayRole:
|
case Qt::DisplayRole:
|
||||||
return m_playlist.at(index.row()).fileName();
|
return m_playlist.at(index.row()).fileName();
|
||||||
case UrlRole:
|
case UrlRole:
|
||||||
return m_playlist.at(index.row());
|
return m_playlist.at(index.row());
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistManager::PlaylistManager(QObject *parent)
|
PlaylistManager::PlaylistManager(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
{
|
{
|
||||||
connect(&m_model, &PlaylistModel::rowsRemoved, this,
|
connect(&m_model, &PlaylistModel::rowsRemoved, this,
|
||||||
[this](const QModelIndex &, int, int) {
|
[this](const QModelIndex &, int, int) {
|
||||||
if (m_model.rowCount() <= m_currentIndex) {
|
if (m_model.rowCount() <= m_currentIndex) {
|
||||||
setProperty("currentIndex", m_currentIndex - 1);
|
setProperty("currentIndex", m_currentIndex - 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
auto onRowCountChanged = [this](){
|
auto onRowCountChanged = [this](){
|
||||||
emit totalCountChanged(m_model.rowCount());
|
emit totalCountChanged(m_model.rowCount());
|
||||||
};
|
};
|
||||||
|
|
||||||
connect(&m_model, &PlaylistModel::rowsInserted, this, onRowCountChanged);
|
connect(&m_model, &PlaylistModel::rowsInserted, this, onRowCountChanged);
|
||||||
connect(&m_model, &PlaylistModel::rowsRemoved, this, onRowCountChanged);
|
connect(&m_model, &PlaylistModel::rowsRemoved, this, onRowCountChanged);
|
||||||
connect(&m_model, &PlaylistModel::modelReset, this, onRowCountChanged);
|
connect(&m_model, &PlaylistModel::modelReset, this, onRowCountChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistManager::~PlaylistManager()
|
PlaylistManager::~PlaylistManager()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistModel *PlaylistManager::model()
|
PlaylistModel *PlaylistManager::model()
|
||||||
{
|
{
|
||||||
return &m_model;
|
return &m_model;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistManager::setPlaylist(const QList<QUrl> &urls)
|
void PlaylistManager::setPlaylist(const QList<QUrl> &urls)
|
||||||
{
|
{
|
||||||
m_model.setPlaylist(urls);
|
m_model.setPlaylist(urls);
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::loadPlaylist(const QList<QUrl> &urls)
|
QModelIndex PlaylistManager::loadPlaylist(const QList<QUrl> &urls)
|
||||||
{
|
{
|
||||||
QModelIndex idx = m_model.loadPlaylist(urls);
|
QModelIndex idx = m_model.loadPlaylist(urls);
|
||||||
setProperty("currentIndex", idx.row());
|
setProperty("currentIndex", idx.row());
|
||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::loadPlaylist(const QUrl &url)
|
QModelIndex PlaylistManager::loadPlaylist(const QUrl &url)
|
||||||
{
|
{
|
||||||
QModelIndex idx = m_model.loadPlaylist(url);
|
QModelIndex idx = m_model.loadPlaylist(url);
|
||||||
setProperty("currentIndex", idx.row());
|
setProperty("currentIndex", idx.row());
|
||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::loadM3U8Playlist(const QUrl &url)
|
QModelIndex PlaylistManager::loadM3U8Playlist(const QUrl &url)
|
||||||
{
|
{
|
||||||
QFile file(url.toLocalFile());
|
QFile file(url.toLocalFile());
|
||||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
QList<QUrl> urls;
|
QList<QUrl> urls;
|
||||||
while (!file.atEnd()) {
|
while (!file.atEnd()) {
|
||||||
QString line = file.readLine();
|
QString line = file.readLine();
|
||||||
if (line.startsWith('#')) {
|
if (line.startsWith('#')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
QFileInfo fileInfo(file);
|
QFileInfo fileInfo(file);
|
||||||
QUrl item = QUrl::fromUserInput(line, fileInfo.absolutePath());
|
QUrl item = QUrl::fromUserInput(line, fileInfo.absolutePath());
|
||||||
urls.append(item);
|
urls.append(item);
|
||||||
}
|
}
|
||||||
return loadPlaylist(urls);
|
return loadPlaylist(urls);
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int PlaylistManager::totalCount() const
|
int PlaylistManager::totalCount() const
|
||||||
{
|
{
|
||||||
return m_model.rowCount();
|
return m_model.rowCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::previousIndex() const
|
QModelIndex PlaylistManager::previousIndex() const
|
||||||
{
|
{
|
||||||
int count = totalCount();
|
int count = totalCount();
|
||||||
if (count == 0) return {};
|
if (count == 0) return {};
|
||||||
|
|
||||||
return m_model.index(isFirstIndex() ? count - 1 : m_currentIndex - 1);
|
return m_model.index(isFirstIndex() ? count - 1 : m_currentIndex - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::nextIndex() const
|
QModelIndex PlaylistManager::nextIndex() const
|
||||||
{
|
{
|
||||||
int count = totalCount();
|
int count = totalCount();
|
||||||
if (count == 0) return {};
|
if (count == 0) return {};
|
||||||
|
|
||||||
return m_model.index(isLastIndex() ? 0 : m_currentIndex + 1);
|
return m_model.index(isLastIndex() ? 0 : m_currentIndex + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex PlaylistManager::curIndex() const
|
QModelIndex PlaylistManager::curIndex() const
|
||||||
{
|
{
|
||||||
return m_model.index(m_currentIndex);
|
return m_model.index(m_currentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PlaylistManager::isFirstIndex() const
|
bool PlaylistManager::isFirstIndex() const
|
||||||
{
|
{
|
||||||
return m_currentIndex == 0;
|
return m_currentIndex == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PlaylistManager::isLastIndex() const
|
bool PlaylistManager::isLastIndex() const
|
||||||
{
|
{
|
||||||
return m_currentIndex + 1 == totalCount();
|
return m_currentIndex + 1 == totalCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistManager::setCurrentIndex(const QModelIndex &index)
|
void PlaylistManager::setCurrentIndex(const QModelIndex &index)
|
||||||
{
|
{
|
||||||
if (index.isValid() && index.row() >= 0 && index.row() < totalCount()) {
|
if (index.isValid() && index.row() >= 0 && index.row() < totalCount()) {
|
||||||
setProperty("currentIndex", index.row());
|
setProperty("currentIndex", index.row());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl PlaylistManager::urlByIndex(const QModelIndex &index)
|
QUrl PlaylistManager::urlByIndex(const QModelIndex &index)
|
||||||
{
|
{
|
||||||
return m_model.urlByIndex(index.row());
|
return m_model.urlByIndex(index.row());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString PlaylistManager::localFileByIndex(const QModelIndex &index)
|
QString PlaylistManager::localFileByIndex(const QModelIndex &index)
|
||||||
{
|
{
|
||||||
return urlByIndex(index).toLocalFile();
|
return urlByIndex(index).toLocalFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PlaylistManager::removeAt(const QModelIndex &index)
|
bool PlaylistManager::removeAt(const QModelIndex &index)
|
||||||
{
|
{
|
||||||
return m_model.removeAt(index.row());
|
return m_model.removeAt(index.row());
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistManager::setAutoLoadFilterSuffixes(const QStringList &nameFilters)
|
void PlaylistManager::setAutoLoadFilterSuffixes(const QStringList &nameFilters)
|
||||||
{
|
{
|
||||||
m_model.setProperty("autoLoadFilterSuffixes", nameFilters);
|
m_model.setProperty("autoLoadFilterSuffixes", nameFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QUrl> PlaylistManager::convertToUrlList(const QStringList &files)
|
QList<QUrl> PlaylistManager::convertToUrlList(const QStringList &files)
|
||||||
{
|
{
|
||||||
QList<QUrl> urlList;
|
QList<QUrl> urlList;
|
||||||
for (const QString & str : std::as_const(files)) {
|
for (const QString & str : std::as_const(files)) {
|
||||||
QUrl url = QUrl::fromLocalFile(str);
|
QUrl url = QUrl::fromLocalFile(str);
|
||||||
if (url.isValid()) {
|
if (url.isValid()) {
|
||||||
urlList.append(url);
|
urlList.append(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return urlList;
|
return urlList;
|
||||||
}
|
}
|
||||||
|
@ -1,88 +1,88 @@
|
|||||||
// SPDX-FileCopyrightText: 2025 Gary Wang <git@blumia.net>
|
// SPDX-FileCopyrightText: 2025 Gary Wang <git@blumia.net>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
|
|
||||||
class PlaylistModel : public QAbstractListModel
|
class PlaylistModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum PlaylistRole {
|
enum PlaylistRole {
|
||||||
UrlRole = Qt::UserRole
|
UrlRole = Qt::UserRole
|
||||||
};
|
};
|
||||||
Q_ENUM(PlaylistRole)
|
Q_ENUM(PlaylistRole)
|
||||||
Q_PROPERTY(QStringList autoLoadFilterSuffixes MEMBER m_autoLoadSuffixes NOTIFY autoLoadFilterSuffixesChanged)
|
Q_PROPERTY(QStringList autoLoadFilterSuffixes MEMBER m_autoLoadSuffixes NOTIFY autoLoadFilterSuffixesChanged)
|
||||||
|
|
||||||
explicit PlaylistModel(QObject *parent = nullptr);
|
explicit PlaylistModel(QObject *parent = nullptr);
|
||||||
~PlaylistModel() override;
|
~PlaylistModel() override;
|
||||||
|
|
||||||
void setPlaylist(const QList<QUrl> & urls);
|
void setPlaylist(const QList<QUrl> & urls);
|
||||||
QModelIndex loadPlaylist(const QList<QUrl> & urls);
|
QModelIndex loadPlaylist(const QList<QUrl> & urls);
|
||||||
QModelIndex loadPlaylist(const QUrl & url);
|
QModelIndex loadPlaylist(const QUrl & url);
|
||||||
QModelIndex appendToPlaylist(const QUrl & url);
|
QModelIndex appendToPlaylist(const QUrl & url);
|
||||||
bool removeAt(int index);
|
bool removeAt(int index);
|
||||||
int indexOf(const QUrl & url) const;
|
int indexOf(const QUrl & url) const;
|
||||||
QUrl urlByIndex(int index) const;
|
QUrl urlByIndex(int index) const;
|
||||||
QStringList autoLoadFilterSuffixes() const;
|
QStringList autoLoadFilterSuffixes() const;
|
||||||
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void autoLoadFilterSuffixesChanged(QStringList suffixes);
|
void autoLoadFilterSuffixesChanged(QStringList suffixes);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// model data
|
// model data
|
||||||
QList<QUrl> m_playlist;
|
QList<QUrl> m_playlist;
|
||||||
// properties
|
// properties
|
||||||
QStringList m_autoLoadSuffixes = {};
|
QStringList m_autoLoadSuffixes = {};
|
||||||
// internal
|
// internal
|
||||||
QString m_currentDir;
|
QString m_currentDir;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PlaylistManager : public QObject
|
class PlaylistManager : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
Q_PROPERTY(int currentIndex MEMBER m_currentIndex NOTIFY currentIndexChanged)
|
Q_PROPERTY(int currentIndex MEMBER m_currentIndex NOTIFY currentIndexChanged)
|
||||||
Q_PROPERTY(QStringList autoLoadFilterSuffixes WRITE setAutoLoadFilterSuffixes)
|
Q_PROPERTY(QStringList autoLoadFilterSuffixes WRITE setAutoLoadFilterSuffixes)
|
||||||
Q_PROPERTY(PlaylistModel * model READ model CONSTANT)
|
Q_PROPERTY(PlaylistModel * model READ model CONSTANT)
|
||||||
|
|
||||||
explicit PlaylistManager(QObject *parent = nullptr);
|
explicit PlaylistManager(QObject *parent = nullptr);
|
||||||
~PlaylistManager();
|
~PlaylistManager();
|
||||||
|
|
||||||
PlaylistModel * model();
|
PlaylistModel * model();
|
||||||
|
|
||||||
void setPlaylist(const QList<QUrl> & url);
|
void setPlaylist(const QList<QUrl> & url);
|
||||||
Q_INVOKABLE QModelIndex loadPlaylist(const QList<QUrl> & urls);
|
Q_INVOKABLE QModelIndex loadPlaylist(const QList<QUrl> & urls);
|
||||||
Q_INVOKABLE QModelIndex loadPlaylist(const QUrl & url);
|
Q_INVOKABLE QModelIndex loadPlaylist(const QUrl & url);
|
||||||
Q_INVOKABLE QModelIndex loadM3U8Playlist(const QUrl & url);
|
Q_INVOKABLE QModelIndex loadM3U8Playlist(const QUrl & url);
|
||||||
|
|
||||||
int totalCount() const;
|
int totalCount() const;
|
||||||
QModelIndex previousIndex() const;
|
QModelIndex previousIndex() const;
|
||||||
QModelIndex nextIndex() const;
|
QModelIndex nextIndex() const;
|
||||||
QModelIndex curIndex() const;
|
QModelIndex curIndex() const;
|
||||||
bool isFirstIndex() const;
|
bool isFirstIndex() const;
|
||||||
bool isLastIndex() const;
|
bool isLastIndex() const;
|
||||||
void setCurrentIndex(const QModelIndex & index);
|
void setCurrentIndex(const QModelIndex & index);
|
||||||
QUrl urlByIndex(const QModelIndex & index);
|
QUrl urlByIndex(const QModelIndex & index);
|
||||||
QString localFileByIndex(const QModelIndex & index);
|
QString localFileByIndex(const QModelIndex & index);
|
||||||
bool removeAt(const QModelIndex & index);
|
bool removeAt(const QModelIndex & index);
|
||||||
|
|
||||||
void setAutoLoadFilterSuffixes(const QStringList &nameFilters);
|
void setAutoLoadFilterSuffixes(const QStringList &nameFilters);
|
||||||
|
|
||||||
static QList<QUrl> convertToUrlList(const QStringList & files);
|
static QList<QUrl> convertToUrlList(const QStringList & files);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void currentIndexChanged(int index);
|
void currentIndexChanged(int index);
|
||||||
void totalCountChanged(int count);
|
void totalCountChanged(int count);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_currentIndex = -1;
|
int m_currentIndex = -1;
|
||||||
PlaylistModel m_model;
|
PlaylistModel m_model;
|
||||||
};
|
};
|
||||||
|
452
app/settings.cpp
452
app/settings.cpp
@ -1,226 +1,226 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QKeySequence>
|
#include <QKeySequence>
|
||||||
#include <QMetaEnum>
|
#include <QMetaEnum>
|
||||||
|
|
||||||
namespace QEnumHelper
|
namespace QEnumHelper
|
||||||
{
|
{
|
||||||
template <typename E>
|
template <typename E>
|
||||||
E fromString(const QString &text, const E defaultValue)
|
E fromString(const QString &text, const E defaultValue)
|
||||||
{
|
{
|
||||||
bool ok;
|
bool ok;
|
||||||
E result = static_cast<E>(QMetaEnum::fromType<E>().keyToValue(text.toUtf8(), &ok));
|
E result = static_cast<E>(QMetaEnum::fromType<E>().keyToValue(text.toUtf8(), &ok));
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename E>
|
template <typename E>
|
||||||
QString toString(E value)
|
QString toString(E value)
|
||||||
{
|
{
|
||||||
const int intValue = static_cast<int>(value);
|
const int intValue = static_cast<int>(value);
|
||||||
return QString::fromUtf8(QMetaEnum::fromType<E>().valueToKey(intValue));
|
return QString::fromUtf8(QMetaEnum::fromType<E>().valueToKey(intValue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings *Settings::m_settings_instance = nullptr;
|
Settings *Settings::m_settings_instance = nullptr;
|
||||||
|
|
||||||
Settings *Settings::instance()
|
Settings *Settings::instance()
|
||||||
{
|
{
|
||||||
if (!m_settings_instance) {
|
if (!m_settings_instance) {
|
||||||
m_settings_instance = new Settings;
|
m_settings_instance = new Settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_settings_instance;
|
return m_settings_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::stayOnTop() const
|
bool Settings::stayOnTop() const
|
||||||
{
|
{
|
||||||
return m_qsettings->value("stay_on_top", true).toBool();
|
return m_qsettings->value("stay_on_top", true).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::useBuiltInCloseAnimation() const
|
bool Settings::useBuiltInCloseAnimation() const
|
||||||
{
|
{
|
||||||
return m_qsettings->value("use_built_in_close_animation", true).toBool();
|
return m_qsettings->value("use_built_in_close_animation", true).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::useLightCheckerboard() const
|
bool Settings::useLightCheckerboard() const
|
||||||
{
|
{
|
||||||
return m_qsettings->value("use_light_checkerboard", false).toBool();
|
return m_qsettings->value("use_light_checkerboard", false).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::loopGallery() const
|
bool Settings::loopGallery() const
|
||||||
{
|
{
|
||||||
return m_qsettings->value("loop_gallery", true).toBool();
|
return m_qsettings->value("loop_gallery", true).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings::DoubleClickBehavior Settings::doubleClickBehavior() const
|
Settings::DoubleClickBehavior Settings::doubleClickBehavior() const
|
||||||
{
|
{
|
||||||
QString result = m_qsettings->value("double_click_behavior", "Close").toString();
|
QString result = m_qsettings->value("double_click_behavior", "Close").toString();
|
||||||
|
|
||||||
return QEnumHelper::fromString<DoubleClickBehavior>(result, DoubleClickBehavior::Close);
|
return QEnumHelper::fromString<DoubleClickBehavior>(result, DoubleClickBehavior::Close);
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings::MouseWheelBehavior Settings::mouseWheelBehavior() const
|
Settings::MouseWheelBehavior Settings::mouseWheelBehavior() const
|
||||||
{
|
{
|
||||||
QString result = m_qsettings->value("mouse_wheel_behavior", "Zoom").toString();
|
QString result = m_qsettings->value("mouse_wheel_behavior", "Zoom").toString();
|
||||||
|
|
||||||
return QEnumHelper::fromString<MouseWheelBehavior>(result, MouseWheelBehavior::Zoom);
|
return QEnumHelper::fromString<MouseWheelBehavior>(result, MouseWheelBehavior::Zoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings::WindowSizeBehavior Settings::initWindowSizeBehavior() const
|
Settings::WindowSizeBehavior Settings::initWindowSizeBehavior() const
|
||||||
{
|
{
|
||||||
QString result = m_qsettings->value("init_window_size_behavior", "Auto").toString();
|
QString result = m_qsettings->value("init_window_size_behavior", "Auto").toString();
|
||||||
|
|
||||||
return QEnumHelper::fromString<WindowSizeBehavior>(result, WindowSizeBehavior::Auto);
|
return QEnumHelper::fromString<WindowSizeBehavior>(result, WindowSizeBehavior::Auto);
|
||||||
}
|
}
|
||||||
|
|
||||||
Qt::HighDpiScaleFactorRoundingPolicy Settings::hiDpiScaleFactorBehavior() const
|
Qt::HighDpiScaleFactorRoundingPolicy Settings::hiDpiScaleFactorBehavior() const
|
||||||
{
|
{
|
||||||
QString result = m_qsettings->value("hidpi_scale_factor_behavior", "PassThrough").toString();
|
QString result = m_qsettings->value("hidpi_scale_factor_behavior", "PassThrough").toString();
|
||||||
|
|
||||||
return QEnumHelper::fromString<Qt::HighDpiScaleFactorRoundingPolicy>(result, Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
return QEnumHelper::fromString<Qt::HighDpiScaleFactorRoundingPolicy>(result, Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setStayOnTop(bool on)
|
void Settings::setStayOnTop(bool on)
|
||||||
{
|
{
|
||||||
m_qsettings->setValue("stay_on_top", on);
|
m_qsettings->setValue("stay_on_top", on);
|
||||||
m_qsettings->sync();
|
m_qsettings->sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setUseBuiltInCloseAnimation(bool on)
|
void Settings::setUseBuiltInCloseAnimation(bool on)
|
||||||
{
|
{
|
||||||
m_qsettings->setValue("use_built_in_close_animation", on);
|
m_qsettings->setValue("use_built_in_close_animation", on);
|
||||||
m_qsettings->sync();
|
m_qsettings->sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setUseLightCheckerboard(bool light)
|
void Settings::setUseLightCheckerboard(bool light)
|
||||||
{
|
{
|
||||||
m_qsettings->setValue("use_light_checkerboard", light);
|
m_qsettings->setValue("use_light_checkerboard", light);
|
||||||
m_qsettings->sync();
|
m_qsettings->sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setLoopGallery(bool on)
|
void Settings::setLoopGallery(bool on)
|
||||||
{
|
{
|
||||||
m_qsettings->setValue("loop_gallery", on);
|
m_qsettings->setValue("loop_gallery", on);
|
||||||
m_qsettings->sync();
|
m_qsettings->sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setDoubleClickBehavior(DoubleClickBehavior dcb)
|
void Settings::setDoubleClickBehavior(DoubleClickBehavior dcb)
|
||||||
{
|
{
|
||||||
m_qsettings->setValue("double_click_behavior", QEnumHelper::toString(dcb));
|
m_qsettings->setValue("double_click_behavior", QEnumHelper::toString(dcb));
|
||||||
m_qsettings->sync();
|
m_qsettings->sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setMouseWheelBehavior(MouseWheelBehavior mwb)
|
void Settings::setMouseWheelBehavior(MouseWheelBehavior mwb)
|
||||||
{
|
{
|
||||||
m_qsettings->setValue("mouse_wheel_behavior", QEnumHelper::toString(mwb));
|
m_qsettings->setValue("mouse_wheel_behavior", QEnumHelper::toString(mwb));
|
||||||
m_qsettings->sync();
|
m_qsettings->sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setInitWindowSizeBehavior(WindowSizeBehavior wsb)
|
void Settings::setInitWindowSizeBehavior(WindowSizeBehavior wsb)
|
||||||
{
|
{
|
||||||
m_qsettings->setValue("init_window_size_behavior", QEnumHelper::toString(wsb));
|
m_qsettings->setValue("init_window_size_behavior", QEnumHelper::toString(wsb));
|
||||||
m_qsettings->sync();
|
m_qsettings->sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setHiDpiScaleFactorBehavior(Qt::HighDpiScaleFactorRoundingPolicy hidpi)
|
void Settings::setHiDpiScaleFactorBehavior(Qt::HighDpiScaleFactorRoundingPolicy hidpi)
|
||||||
{
|
{
|
||||||
m_qsettings->setValue("hidpi_scale_factor_behavior", QEnumHelper::toString(hidpi));
|
m_qsettings->setValue("hidpi_scale_factor_behavior", QEnumHelper::toString(hidpi));
|
||||||
m_qsettings->sync();
|
m_qsettings->sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::applyUserShortcuts(QWidget *widget)
|
void Settings::applyUserShortcuts(QWidget *widget)
|
||||||
{
|
{
|
||||||
m_qsettings->beginGroup("shortcuts");
|
m_qsettings->beginGroup("shortcuts");
|
||||||
const QStringList shortcutNames = m_qsettings->allKeys();
|
const QStringList shortcutNames = m_qsettings->allKeys();
|
||||||
for (const QString & name : shortcutNames) {
|
for (const QString & name : shortcutNames) {
|
||||||
QList<QKeySequence> shortcuts = m_qsettings->value(name).value<QList<QKeySequence>>();
|
QList<QKeySequence> shortcuts = m_qsettings->value(name).value<QList<QKeySequence>>();
|
||||||
setShortcutsForAction(widget, name, shortcuts, false);
|
setShortcutsForAction(widget, name, shortcuts, false);
|
||||||
}
|
}
|
||||||
m_qsettings->endGroup();
|
m_qsettings->endGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::setShortcutsForAction(QWidget *widget, const QString &objectName,
|
bool Settings::setShortcutsForAction(QWidget *widget, const QString &objectName,
|
||||||
QList<QKeySequence> shortcuts, bool writeConfig)
|
QList<QKeySequence> shortcuts, bool writeConfig)
|
||||||
{
|
{
|
||||||
QAction * targetAction = nullptr;
|
QAction * targetAction = nullptr;
|
||||||
for (QAction * action : widget->actions()) {
|
for (QAction * action : widget->actions()) {
|
||||||
if (action->objectName() == objectName) {
|
if (action->objectName() == objectName) {
|
||||||
targetAction = action;
|
targetAction = action;
|
||||||
} else {
|
} else {
|
||||||
for (const QKeySequence & shortcut : std::as_const(shortcuts)) {
|
for (const QKeySequence & shortcut : std::as_const(shortcuts)) {
|
||||||
if (action->shortcuts().contains(shortcut)) {
|
if (action->shortcuts().contains(shortcut)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetAction) {
|
if (targetAction) {
|
||||||
targetAction->setShortcuts(shortcuts);
|
targetAction->setShortcuts(shortcuts);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetAction && writeConfig) {
|
if (targetAction && writeConfig) {
|
||||||
m_qsettings->beginGroup("shortcuts");
|
m_qsettings->beginGroup("shortcuts");
|
||||||
m_qsettings->setValue(objectName, QVariant::fromValue(shortcuts));
|
m_qsettings->setValue(objectName, QVariant::fromValue(shortcuts));
|
||||||
m_qsettings->endGroup();
|
m_qsettings->endGroup();
|
||||||
m_qsettings->sync();
|
m_qsettings->sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(FLAG_PORTABLE_MODE_SUPPORT) && defined(Q_OS_WIN)
|
#if defined(FLAG_PORTABLE_MODE_SUPPORT) && defined(Q_OS_WIN)
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
// QCoreApplication::applicationDirPath() parses the "applicationDirPath" from arg0, which...
|
// QCoreApplication::applicationDirPath() parses the "applicationDirPath" from arg0, which...
|
||||||
// 1. rely on a QApplication object instance
|
// 1. rely on a QApplication object instance
|
||||||
// but we need to call QGuiApplication::setHighDpiScaleFactorRoundingPolicy() before QApplication get created
|
// but we need to call QGuiApplication::setHighDpiScaleFactorRoundingPolicy() before QApplication get created
|
||||||
// 2. arg0 is NOT garanteed to be the path of execution
|
// 2. arg0 is NOT garanteed to be the path of execution
|
||||||
// see also: https://stackoverflow.com/questions/383973/is-args0-guaranteed-to-be-the-path-of-execution
|
// see also: https://stackoverflow.com/questions/383973/is-args0-guaranteed-to-be-the-path-of-execution
|
||||||
// This function is here mainly for #1.
|
// This function is here mainly for #1.
|
||||||
QString getApplicationDirPath()
|
QString getApplicationDirPath()
|
||||||
{
|
{
|
||||||
WCHAR buffer[MAX_PATH];
|
WCHAR buffer[MAX_PATH];
|
||||||
GetModuleFileNameW(NULL, buffer, MAX_PATH);
|
GetModuleFileNameW(NULL, buffer, MAX_PATH);
|
||||||
QString appPath = QString::fromWCharArray(buffer);
|
QString appPath = QString::fromWCharArray(buffer);
|
||||||
|
|
||||||
return appPath.left(appPath.lastIndexOf('\\'));
|
return appPath.left(appPath.lastIndexOf('\\'));
|
||||||
}
|
}
|
||||||
#endif // defined(FLAG_PORTABLE_MODE_SUPPORT) && defined(Q_OS_WIN)
|
#endif // defined(FLAG_PORTABLE_MODE_SUPPORT) && defined(Q_OS_WIN)
|
||||||
|
|
||||||
Settings::Settings()
|
Settings::Settings()
|
||||||
: QObject(qApp)
|
: QObject(qApp)
|
||||||
{
|
{
|
||||||
QString configPath;
|
QString configPath;
|
||||||
|
|
||||||
#if defined(FLAG_PORTABLE_MODE_SUPPORT) && defined(Q_OS_WIN)
|
#if defined(FLAG_PORTABLE_MODE_SUPPORT) && defined(Q_OS_WIN)
|
||||||
QString portableConfigDirPath = QDir(getApplicationDirPath()).absoluteFilePath("data");
|
QString portableConfigDirPath = QDir(getApplicationDirPath()).absoluteFilePath("data");
|
||||||
QFileInfo portableConfigDirInfo(portableConfigDirPath);
|
QFileInfo portableConfigDirInfo(portableConfigDirPath);
|
||||||
if (portableConfigDirInfo.exists() && portableConfigDirInfo.isDir() && portableConfigDirInfo.isWritable()) {
|
if (portableConfigDirInfo.exists() && portableConfigDirInfo.isDir() && portableConfigDirInfo.isWritable()) {
|
||||||
// we can use it.
|
// we can use it.
|
||||||
configPath = portableConfigDirPath;
|
configPath = portableConfigDirPath;
|
||||||
}
|
}
|
||||||
#endif // defined(FLAG_PORTABLE_MODE_SUPPORT) && defined(Q_OS_WIN)
|
#endif // defined(FLAG_PORTABLE_MODE_SUPPORT) && defined(Q_OS_WIN)
|
||||||
|
|
||||||
if (configPath.isEmpty()) {
|
if (configPath.isEmpty()) {
|
||||||
// Should be %LOCALAPPDATA%\<APPNAME> under Windows, ~/.config/<APPNAME> under Linux.
|
// Should be %LOCALAPPDATA%\<APPNAME> under Windows, ~/.config/<APPNAME> under Linux.
|
||||||
configPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
|
configPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_qsettings = new QSettings(QDir(configPath).absoluteFilePath("config.ini"), QSettings::IniFormat, this);
|
m_qsettings = new QSettings(QDir(configPath).absoluteFilePath("config.ini"), QSettings::IniFormat, this);
|
||||||
|
|
||||||
qRegisterMetaType<QList<QKeySequence>>();
|
qRegisterMetaType<QList<QKeySequence>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
138
app/settings.h
138
app/settings.h
@ -1,69 +1,69 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
class Settings : public QObject
|
class Settings : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum DoubleClickBehavior {
|
enum DoubleClickBehavior {
|
||||||
Ignore,
|
Ignore,
|
||||||
Close,
|
Close,
|
||||||
Maximize,
|
Maximize,
|
||||||
FullScreen,
|
FullScreen,
|
||||||
};
|
};
|
||||||
Q_ENUM(DoubleClickBehavior)
|
Q_ENUM(DoubleClickBehavior)
|
||||||
|
|
||||||
enum MouseWheelBehavior {
|
enum MouseWheelBehavior {
|
||||||
Zoom,
|
Zoom,
|
||||||
Switch,
|
Switch,
|
||||||
};
|
};
|
||||||
Q_ENUM(MouseWheelBehavior)
|
Q_ENUM(MouseWheelBehavior)
|
||||||
|
|
||||||
enum WindowSizeBehavior {
|
enum WindowSizeBehavior {
|
||||||
Auto,
|
Auto,
|
||||||
Maximized,
|
Maximized,
|
||||||
Windowed,
|
Windowed,
|
||||||
};
|
};
|
||||||
Q_ENUM(WindowSizeBehavior)
|
Q_ENUM(WindowSizeBehavior)
|
||||||
|
|
||||||
static Settings *instance();
|
static Settings *instance();
|
||||||
|
|
||||||
bool stayOnTop() const;
|
bool stayOnTop() const;
|
||||||
bool useBuiltInCloseAnimation() const;
|
bool useBuiltInCloseAnimation() const;
|
||||||
bool useLightCheckerboard() const;
|
bool useLightCheckerboard() const;
|
||||||
bool loopGallery() const;
|
bool loopGallery() const;
|
||||||
DoubleClickBehavior doubleClickBehavior() const;
|
DoubleClickBehavior doubleClickBehavior() const;
|
||||||
MouseWheelBehavior mouseWheelBehavior() const;
|
MouseWheelBehavior mouseWheelBehavior() const;
|
||||||
WindowSizeBehavior initWindowSizeBehavior() const;
|
WindowSizeBehavior initWindowSizeBehavior() const;
|
||||||
Qt::HighDpiScaleFactorRoundingPolicy hiDpiScaleFactorBehavior() const;
|
Qt::HighDpiScaleFactorRoundingPolicy hiDpiScaleFactorBehavior() const;
|
||||||
|
|
||||||
void setStayOnTop(bool on);
|
void setStayOnTop(bool on);
|
||||||
void setUseBuiltInCloseAnimation(bool on);
|
void setUseBuiltInCloseAnimation(bool on);
|
||||||
void setUseLightCheckerboard(bool light);
|
void setUseLightCheckerboard(bool light);
|
||||||
void setLoopGallery(bool on);
|
void setLoopGallery(bool on);
|
||||||
void setDoubleClickBehavior(DoubleClickBehavior dcb);
|
void setDoubleClickBehavior(DoubleClickBehavior dcb);
|
||||||
void setMouseWheelBehavior(MouseWheelBehavior mwb);
|
void setMouseWheelBehavior(MouseWheelBehavior mwb);
|
||||||
void setInitWindowSizeBehavior(WindowSizeBehavior wsb);
|
void setInitWindowSizeBehavior(WindowSizeBehavior wsb);
|
||||||
void setHiDpiScaleFactorBehavior(Qt::HighDpiScaleFactorRoundingPolicy hidpi);
|
void setHiDpiScaleFactorBehavior(Qt::HighDpiScaleFactorRoundingPolicy hidpi);
|
||||||
|
|
||||||
void applyUserShortcuts(QWidget * widget);
|
void applyUserShortcuts(QWidget * widget);
|
||||||
bool setShortcutsForAction(QWidget * widget, const QString & objectName,
|
bool setShortcutsForAction(QWidget * widget, const QString & objectName,
|
||||||
QList<QKeySequence> shortcuts, bool writeConfig = true);
|
QList<QKeySequence> shortcuts, bool writeConfig = true);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Settings();
|
Settings();
|
||||||
|
|
||||||
static Settings *m_settings_instance;
|
static Settings *m_settings_instance;
|
||||||
|
|
||||||
QSettings *m_qsettings;
|
QSettings *m_qsettings;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
};
|
};
|
||||||
|
@ -1,200 +1,200 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "settingsdialog.h"
|
#include "settingsdialog.h"
|
||||||
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "shortcutedit.h"
|
#include "shortcutedit.h"
|
||||||
|
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QFormLayout>
|
#include <QFormLayout>
|
||||||
#include <QKeySequenceEdit>
|
#include <QKeySequenceEdit>
|
||||||
#include <QScrollArea>
|
#include <QScrollArea>
|
||||||
#include <QSplitter>
|
#include <QSplitter>
|
||||||
#include <QStringListModel>
|
#include <QStringListModel>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
|
||||||
SettingsDialog::SettingsDialog(QWidget *parent)
|
SettingsDialog::SettingsDialog(QWidget *parent)
|
||||||
: QDialog(parent)
|
: QDialog(parent)
|
||||||
, m_stayOnTop(new QCheckBox)
|
, m_stayOnTop(new QCheckBox)
|
||||||
, m_useBuiltInCloseAnimation(new QCheckBox)
|
, m_useBuiltInCloseAnimation(new QCheckBox)
|
||||||
, m_useLightCheckerboard(new QCheckBox)
|
, m_useLightCheckerboard(new QCheckBox)
|
||||||
, m_loopGallery(new QCheckBox)
|
, m_loopGallery(new QCheckBox)
|
||||||
, m_doubleClickBehavior(new QComboBox)
|
, m_doubleClickBehavior(new QComboBox)
|
||||||
, m_mouseWheelBehavior(new QComboBox)
|
, m_mouseWheelBehavior(new QComboBox)
|
||||||
, m_initWindowSizeBehavior(new QComboBox)
|
, m_initWindowSizeBehavior(new QComboBox)
|
||||||
, m_hiDpiRoundingPolicyBehavior(new QComboBox)
|
, m_hiDpiRoundingPolicyBehavior(new QComboBox)
|
||||||
{
|
{
|
||||||
this->setWindowTitle(tr("Settings"));
|
this->setWindowTitle(tr("Settings"));
|
||||||
|
|
||||||
QHBoxLayout * mainLayout = new QHBoxLayout(this);
|
QHBoxLayout * mainLayout = new QHBoxLayout(this);
|
||||||
QTabWidget * settingsTabs = new QTabWidget(this);
|
QTabWidget * settingsTabs = new QTabWidget(this);
|
||||||
mainLayout->addWidget(settingsTabs);
|
mainLayout->addWidget(settingsTabs);
|
||||||
|
|
||||||
QWidget * settingsFormHolder = new QWidget;
|
QWidget * settingsFormHolder = new QWidget;
|
||||||
QFormLayout * settingsForm = new QFormLayout(settingsFormHolder);
|
QFormLayout * settingsForm = new QFormLayout(settingsFormHolder);
|
||||||
settingsTabs->addTab(settingsFormHolder, tr("Options"));
|
settingsTabs->addTab(settingsFormHolder, tr("Options"));
|
||||||
|
|
||||||
QSplitter * shortcutEditorSplitter = new QSplitter;
|
QSplitter * shortcutEditorSplitter = new QSplitter;
|
||||||
shortcutEditorSplitter->setOrientation(Qt::Vertical);
|
shortcutEditorSplitter->setOrientation(Qt::Vertical);
|
||||||
shortcutEditorSplitter->setChildrenCollapsible(false);
|
shortcutEditorSplitter->setChildrenCollapsible(false);
|
||||||
QScrollArea * shortcutScrollArea = new QScrollArea;
|
QScrollArea * shortcutScrollArea = new QScrollArea;
|
||||||
shortcutEditorSplitter->addWidget(shortcutScrollArea);
|
shortcutEditorSplitter->addWidget(shortcutScrollArea);
|
||||||
shortcutScrollArea->setWidgetResizable(true);
|
shortcutScrollArea->setWidgetResizable(true);
|
||||||
shortcutScrollArea->setMinimumHeight(200);
|
shortcutScrollArea->setMinimumHeight(200);
|
||||||
QWidget * shortcutsFormHolder = new QWidget;
|
QWidget * shortcutsFormHolder = new QWidget;
|
||||||
QFormLayout * shortcutsForm = new QFormLayout(shortcutsFormHolder);
|
QFormLayout * shortcutsForm = new QFormLayout(shortcutsFormHolder);
|
||||||
shortcutScrollArea->setWidget(shortcutsFormHolder);
|
shortcutScrollArea->setWidget(shortcutsFormHolder);
|
||||||
settingsTabs->addTab(shortcutEditorSplitter, tr("Shortcuts"));
|
settingsTabs->addTab(shortcutEditorSplitter, tr("Shortcuts"));
|
||||||
|
|
||||||
for (const QAction * action : parent->actions()) {
|
for (const QAction * action : parent->actions()) {
|
||||||
ShortcutEdit * shortcutEdit = new ShortcutEdit;
|
ShortcutEdit * shortcutEdit = new ShortcutEdit;
|
||||||
shortcutEdit->setObjectName(QLatin1String("shortcut_") + action->objectName());
|
shortcutEdit->setObjectName(QLatin1String("shortcut_") + action->objectName());
|
||||||
shortcutEdit->setShortcuts(action->shortcuts());
|
shortcutEdit->setShortcuts(action->shortcuts());
|
||||||
shortcutsForm->addRow(action->text(), shortcutEdit);
|
shortcutsForm->addRow(action->text(), shortcutEdit);
|
||||||
connect(shortcutEdit, &ShortcutEdit::editButtonClicked, this, [=](){
|
connect(shortcutEdit, &ShortcutEdit::editButtonClicked, this, [=](){
|
||||||
if (shortcutEditorSplitter->count() == 1) shortcutEditorSplitter->addWidget(new QWidget);
|
if (shortcutEditorSplitter->count() == 1) shortcutEditorSplitter->addWidget(new QWidget);
|
||||||
ShortcutEditor * shortcutEditor = new ShortcutEditor(shortcutEdit);
|
ShortcutEditor * shortcutEditor = new ShortcutEditor(shortcutEdit);
|
||||||
shortcutEditor->setDescription(tr("Editing shortcuts for action \"%1\":").arg(action->text()));
|
shortcutEditor->setDescription(tr("Editing shortcuts for action \"%1\":").arg(action->text()));
|
||||||
QWidget * oldEditor = shortcutEditorSplitter->replaceWidget(1, shortcutEditor);
|
QWidget * oldEditor = shortcutEditorSplitter->replaceWidget(1, shortcutEditor);
|
||||||
shortcutEditorSplitter->setSizes({shortcutEditorSplitter->height(), 1});
|
shortcutEditorSplitter->setSizes({shortcutEditorSplitter->height(), 1});
|
||||||
oldEditor->deleteLater();
|
oldEditor->deleteLater();
|
||||||
});
|
});
|
||||||
connect(shortcutEdit, &ShortcutEdit::applyShortcutsRequested, this, [=](QList<QKeySequence> newShortcuts){
|
connect(shortcutEdit, &ShortcutEdit::applyShortcutsRequested, this, [=](QList<QKeySequence> newShortcuts){
|
||||||
bool succ = Settings::instance()->setShortcutsForAction(parent, shortcutEdit->objectName().mid(9),
|
bool succ = Settings::instance()->setShortcutsForAction(parent, shortcutEdit->objectName().mid(9),
|
||||||
newShortcuts);
|
newShortcuts);
|
||||||
if (!succ) {
|
if (!succ) {
|
||||||
QMessageBox::warning(this, tr("Failed to set shortcuts"),
|
QMessageBox::warning(this, tr("Failed to set shortcuts"),
|
||||||
tr("Please check if shortcuts are duplicated with existing shortcuts."));
|
tr("Please check if shortcuts are duplicated with existing shortcuts."));
|
||||||
}
|
}
|
||||||
shortcutEdit->setShortcuts(action->shortcuts());
|
shortcutEdit->setShortcuts(action->shortcuts());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static QList< QPair<Settings::DoubleClickBehavior, QString> > _dc_options {
|
static QList< QPair<Settings::DoubleClickBehavior, QString> > _dc_options {
|
||||||
{ Settings::DoubleClickBehavior::Ignore, tr("Do nothing") },
|
{ Settings::DoubleClickBehavior::Ignore, tr("Do nothing") },
|
||||||
{ Settings::DoubleClickBehavior::Close, tr("Close the window") },
|
{ Settings::DoubleClickBehavior::Close, tr("Close the window") },
|
||||||
{ Settings::DoubleClickBehavior::Maximize, tr("Toggle maximize") },
|
{ Settings::DoubleClickBehavior::Maximize, tr("Toggle maximize") },
|
||||||
{ Settings::DoubleClickBehavior::FullScreen, tr("Toggle fullscreen") }
|
{ Settings::DoubleClickBehavior::FullScreen, tr("Toggle fullscreen") }
|
||||||
};
|
};
|
||||||
|
|
||||||
static QList< QPair<Settings::MouseWheelBehavior, QString> > _mw_options {
|
static QList< QPair<Settings::MouseWheelBehavior, QString> > _mw_options {
|
||||||
{ Settings::MouseWheelBehavior::Zoom, tr("Zoom in and out") },
|
{ Settings::MouseWheelBehavior::Zoom, tr("Zoom in and out") },
|
||||||
{ Settings::MouseWheelBehavior::Switch, tr("View next or previous item") }
|
{ Settings::MouseWheelBehavior::Switch, tr("View next or previous item") }
|
||||||
};
|
};
|
||||||
|
|
||||||
static QList< QPair<Settings::WindowSizeBehavior, QString> > _iws_options {
|
static QList< QPair<Settings::WindowSizeBehavior, QString> > _iws_options {
|
||||||
{ Settings::WindowSizeBehavior::Auto, tr("Auto size") },
|
{ Settings::WindowSizeBehavior::Auto, tr("Auto size") },
|
||||||
{ Settings::WindowSizeBehavior::Maximized, tr("Maximized") },
|
{ Settings::WindowSizeBehavior::Maximized, tr("Maximized") },
|
||||||
{ Settings::WindowSizeBehavior::Windowed, tr("Windowed") }
|
{ Settings::WindowSizeBehavior::Windowed, tr("Windowed") }
|
||||||
};
|
};
|
||||||
|
|
||||||
static QList< QPair<Qt::HighDpiScaleFactorRoundingPolicy, QString> > _hidpi_options {
|
static QList< QPair<Qt::HighDpiScaleFactorRoundingPolicy, QString> > _hidpi_options {
|
||||||
{ Qt::HighDpiScaleFactorRoundingPolicy::Round, tr("Round (Integer scaling)", "This option means round up for .5 and above") },
|
{ Qt::HighDpiScaleFactorRoundingPolicy::Round, tr("Round (Integer scaling)", "This option means round up for .5 and above") },
|
||||||
{ Qt::HighDpiScaleFactorRoundingPolicy::Ceil, tr("Ceil (Integer scaling)", "This option means always round up") },
|
{ Qt::HighDpiScaleFactorRoundingPolicy::Ceil, tr("Ceil (Integer scaling)", "This option means always round up") },
|
||||||
{ Qt::HighDpiScaleFactorRoundingPolicy::Floor, tr("Floor (Integer scaling)", "This option means always round down") },
|
{ Qt::HighDpiScaleFactorRoundingPolicy::Floor, tr("Floor (Integer scaling)", "This option means always round down") },
|
||||||
{ Qt::HighDpiScaleFactorRoundingPolicy::PassThrough, tr("Follow system (Fractional scaling)", "This option means don't round") }
|
{ Qt::HighDpiScaleFactorRoundingPolicy::PassThrough, tr("Follow system (Fractional scaling)", "This option means don't round") }
|
||||||
};
|
};
|
||||||
|
|
||||||
QStringList dcbDropDown;
|
QStringList dcbDropDown;
|
||||||
for (const QPair<Settings::DoubleClickBehavior, QString> & dcOption : _dc_options) {
|
for (const QPair<Settings::DoubleClickBehavior, QString> & dcOption : _dc_options) {
|
||||||
dcbDropDown.append(dcOption.second);
|
dcbDropDown.append(dcOption.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList mwbDropDown;
|
QStringList mwbDropDown;
|
||||||
for (const QPair<Settings::MouseWheelBehavior, QString> & mwOption : _mw_options) {
|
for (const QPair<Settings::MouseWheelBehavior, QString> & mwOption : _mw_options) {
|
||||||
mwbDropDown.append(mwOption.second);
|
mwbDropDown.append(mwOption.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList iwsbDropDown;
|
QStringList iwsbDropDown;
|
||||||
for (const QPair<Settings::WindowSizeBehavior, QString> & iwsOption : _iws_options) {
|
for (const QPair<Settings::WindowSizeBehavior, QString> & iwsOption : _iws_options) {
|
||||||
iwsbDropDown.append(iwsOption.second);
|
iwsbDropDown.append(iwsOption.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList hidpiDropDown;
|
QStringList hidpiDropDown;
|
||||||
for (const QPair<Qt::HighDpiScaleFactorRoundingPolicy, QString> & hidpiOption : _hidpi_options) {
|
for (const QPair<Qt::HighDpiScaleFactorRoundingPolicy, QString> & hidpiOption : _hidpi_options) {
|
||||||
hidpiDropDown.append(hidpiOption.second);
|
hidpiDropDown.append(hidpiOption.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsForm->addRow(tr("Stay on top when start-up"), m_stayOnTop);
|
settingsForm->addRow(tr("Stay on top when start-up"), m_stayOnTop);
|
||||||
settingsForm->addRow(tr("Use built-in close window animation"), m_useBuiltInCloseAnimation);
|
settingsForm->addRow(tr("Use built-in close window animation"), m_useBuiltInCloseAnimation);
|
||||||
settingsForm->addRow(tr("Use light-color checkerboard"), m_useLightCheckerboard);
|
settingsForm->addRow(tr("Use light-color checkerboard"), m_useLightCheckerboard);
|
||||||
settingsForm->addRow(tr("Loop the loaded gallery"), m_loopGallery);
|
settingsForm->addRow(tr("Loop the loaded gallery"), m_loopGallery);
|
||||||
settingsForm->addRow(tr("Double-click behavior"), m_doubleClickBehavior);
|
settingsForm->addRow(tr("Double-click behavior"), m_doubleClickBehavior);
|
||||||
settingsForm->addRow(tr("Mouse wheel behavior"), m_mouseWheelBehavior);
|
settingsForm->addRow(tr("Mouse wheel behavior"), m_mouseWheelBehavior);
|
||||||
settingsForm->addRow(tr("Default window size"), m_initWindowSizeBehavior);
|
settingsForm->addRow(tr("Default window size"), m_initWindowSizeBehavior);
|
||||||
settingsForm->addRow(tr("HiDPI scale factor rounding policy"), m_hiDpiRoundingPolicyBehavior);
|
settingsForm->addRow(tr("HiDPI scale factor rounding policy"), m_hiDpiRoundingPolicyBehavior);
|
||||||
|
|
||||||
m_stayOnTop->setChecked(Settings::instance()->stayOnTop());
|
m_stayOnTop->setChecked(Settings::instance()->stayOnTop());
|
||||||
m_useBuiltInCloseAnimation->setChecked(Settings::instance()->useBuiltInCloseAnimation());
|
m_useBuiltInCloseAnimation->setChecked(Settings::instance()->useBuiltInCloseAnimation());
|
||||||
m_useLightCheckerboard->setChecked(Settings::instance()->useLightCheckerboard());
|
m_useLightCheckerboard->setChecked(Settings::instance()->useLightCheckerboard());
|
||||||
m_loopGallery->setChecked(Settings::instance()->loopGallery());
|
m_loopGallery->setChecked(Settings::instance()->loopGallery());
|
||||||
m_doubleClickBehavior->setModel(new QStringListModel(dcbDropDown));
|
m_doubleClickBehavior->setModel(new QStringListModel(dcbDropDown));
|
||||||
Settings::DoubleClickBehavior dcb = Settings::instance()->doubleClickBehavior();
|
Settings::DoubleClickBehavior dcb = Settings::instance()->doubleClickBehavior();
|
||||||
m_doubleClickBehavior->setCurrentIndex(static_cast<int>(dcb));
|
m_doubleClickBehavior->setCurrentIndex(static_cast<int>(dcb));
|
||||||
m_mouseWheelBehavior->setModel(new QStringListModel(mwbDropDown));
|
m_mouseWheelBehavior->setModel(new QStringListModel(mwbDropDown));
|
||||||
Settings::MouseWheelBehavior mwb = Settings::instance()->mouseWheelBehavior();
|
Settings::MouseWheelBehavior mwb = Settings::instance()->mouseWheelBehavior();
|
||||||
m_mouseWheelBehavior->setCurrentIndex(static_cast<int>(mwb));
|
m_mouseWheelBehavior->setCurrentIndex(static_cast<int>(mwb));
|
||||||
m_initWindowSizeBehavior->setModel(new QStringListModel(iwsbDropDown));
|
m_initWindowSizeBehavior->setModel(new QStringListModel(iwsbDropDown));
|
||||||
Settings::WindowSizeBehavior iwsb = Settings::instance()->initWindowSizeBehavior();
|
Settings::WindowSizeBehavior iwsb = Settings::instance()->initWindowSizeBehavior();
|
||||||
m_initWindowSizeBehavior->setCurrentIndex(static_cast<int>(iwsb));
|
m_initWindowSizeBehavior->setCurrentIndex(static_cast<int>(iwsb));
|
||||||
m_hiDpiRoundingPolicyBehavior->setModel(new QStringListModel(hidpiDropDown));
|
m_hiDpiRoundingPolicyBehavior->setModel(new QStringListModel(hidpiDropDown));
|
||||||
Qt::HighDpiScaleFactorRoundingPolicy hidpi = Settings::instance()->hiDpiScaleFactorBehavior();
|
Qt::HighDpiScaleFactorRoundingPolicy hidpi = Settings::instance()->hiDpiScaleFactorBehavior();
|
||||||
for (int i = 0; i < _hidpi_options.count(); i++) {
|
for (int i = 0; i < _hidpi_options.count(); i++) {
|
||||||
if (_hidpi_options.at(i).first == hidpi) {
|
if (_hidpi_options.at(i).first == hidpi) {
|
||||||
m_hiDpiRoundingPolicyBehavior->setCurrentIndex(i);
|
m_hiDpiRoundingPolicyBehavior->setCurrentIndex(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||||
# define QCHECKBOX_CHECKSTATECHANGED QCheckBox::checkStateChanged
|
# define QCHECKBOX_CHECKSTATECHANGED QCheckBox::checkStateChanged
|
||||||
# define QT_CHECKSTATE Qt::CheckState
|
# define QT_CHECKSTATE Qt::CheckState
|
||||||
#else
|
#else
|
||||||
# define QCHECKBOX_CHECKSTATECHANGED QCheckBox::stateChanged
|
# define QCHECKBOX_CHECKSTATECHANGED QCheckBox::stateChanged
|
||||||
# define QT_CHECKSTATE int
|
# define QT_CHECKSTATE int
|
||||||
#endif // QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
#endif // QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||||
|
|
||||||
connect(m_stayOnTop, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
connect(m_stayOnTop, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
||||||
Settings::instance()->setStayOnTop(state == Qt::Checked);
|
Settings::instance()->setStayOnTop(state == Qt::Checked);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_useBuiltInCloseAnimation, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
connect(m_useBuiltInCloseAnimation, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
||||||
Settings::instance()->setUseBuiltInCloseAnimation(state == Qt::Checked);
|
Settings::instance()->setUseBuiltInCloseAnimation(state == Qt::Checked);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_useLightCheckerboard, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
connect(m_useLightCheckerboard, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
||||||
Settings::instance()->setUseLightCheckerboard(state == Qt::Checked);
|
Settings::instance()->setUseLightCheckerboard(state == Qt::Checked);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_loopGallery, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
connect(m_loopGallery, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
||||||
Settings::instance()->setLoopGallery(state == Qt::Checked);
|
Settings::instance()->setLoopGallery(state == Qt::Checked);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_doubleClickBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
|
connect(m_doubleClickBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
|
||||||
Settings::instance()->setDoubleClickBehavior(_dc_options.at(index).first);
|
Settings::instance()->setDoubleClickBehavior(_dc_options.at(index).first);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_mouseWheelBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
|
connect(m_mouseWheelBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
|
||||||
Settings::instance()->setMouseWheelBehavior(_mw_options.at(index).first);
|
Settings::instance()->setMouseWheelBehavior(_mw_options.at(index).first);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_initWindowSizeBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
|
connect(m_initWindowSizeBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
|
||||||
Settings::instance()->setInitWindowSizeBehavior(_iws_options.at(index).first);
|
Settings::instance()->setInitWindowSizeBehavior(_iws_options.at(index).first);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_hiDpiRoundingPolicyBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
|
connect(m_hiDpiRoundingPolicyBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
|
||||||
Settings::instance()->setHiDpiScaleFactorBehavior(_hidpi_options.at(index).first);
|
Settings::instance()->setHiDpiScaleFactorBehavior(_hidpi_options.at(index).first);
|
||||||
});
|
});
|
||||||
|
|
||||||
adjustSize();
|
adjustSize();
|
||||||
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
|
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsDialog::~SettingsDialog()
|
SettingsDialog::~SettingsDialog()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,35 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef SETTINGSDIALOG_H
|
#ifndef SETTINGSDIALOG_H
|
||||||
#define SETTINGSDIALOG_H
|
#define SETTINGSDIALOG_H
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
|
||||||
class QCheckBox;
|
class QCheckBox;
|
||||||
class QComboBox;
|
class QComboBox;
|
||||||
class SettingsDialog : public QDialog
|
class SettingsDialog : public QDialog
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit SettingsDialog(QWidget *parent = nullptr);
|
explicit SettingsDialog(QWidget *parent = nullptr);
|
||||||
~SettingsDialog();
|
~SettingsDialog();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QCheckBox * m_stayOnTop = nullptr;
|
QCheckBox * m_stayOnTop = nullptr;
|
||||||
QCheckBox * m_useBuiltInCloseAnimation = nullptr;
|
QCheckBox * m_useBuiltInCloseAnimation = nullptr;
|
||||||
QCheckBox * m_useLightCheckerboard = nullptr;
|
QCheckBox * m_useLightCheckerboard = nullptr;
|
||||||
QCheckBox * m_loopGallery = nullptr;
|
QCheckBox * m_loopGallery = nullptr;
|
||||||
QComboBox * m_doubleClickBehavior = nullptr;
|
QComboBox * m_doubleClickBehavior = nullptr;
|
||||||
QComboBox * m_mouseWheelBehavior = nullptr;
|
QComboBox * m_mouseWheelBehavior = nullptr;
|
||||||
QComboBox * m_initWindowSizeBehavior = nullptr;
|
QComboBox * m_initWindowSizeBehavior = nullptr;
|
||||||
QComboBox * m_hiDpiRoundingPolicyBehavior = nullptr;
|
QComboBox * m_hiDpiRoundingPolicyBehavior = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SETTINGSDIALOG_H
|
#endif // SETTINGSDIALOG_H
|
||||||
|
@ -1,38 +1,38 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "toolbutton.h"
|
#include "toolbutton.h"
|
||||||
|
|
||||||
#include "actionmanager.h"
|
#include "actionmanager.h"
|
||||||
#include "opacityhelper.h"
|
#include "opacityhelper.h"
|
||||||
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QGraphicsOpacityEffect>
|
#include <QGraphicsOpacityEffect>
|
||||||
#include <QPropertyAnimation>
|
#include <QPropertyAnimation>
|
||||||
|
|
||||||
ToolButton::ToolButton(bool hoverColor, QWidget *parent)
|
ToolButton::ToolButton(bool hoverColor, QWidget *parent)
|
||||||
: QPushButton(parent)
|
: QPushButton(parent)
|
||||||
, m_opacityHelper(new OpacityHelper(this))
|
, m_opacityHelper(new OpacityHelper(this))
|
||||||
{
|
{
|
||||||
setFlat(true);
|
setFlat(true);
|
||||||
QString qss = "QPushButton {"
|
QString qss = "QPushButton {"
|
||||||
"background: transparent;"
|
"background: transparent;"
|
||||||
"}";
|
"}";
|
||||||
if (hoverColor) {
|
if (hoverColor) {
|
||||||
qss += "QPushButton:hover {"
|
qss += "QPushButton:hover {"
|
||||||
"background: red;"
|
"background: red;"
|
||||||
"}";
|
"}";
|
||||||
}
|
}
|
||||||
setStyleSheet(qss);
|
setStyleSheet(qss);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ToolButton::setIconResourcePath(const QString &iconp)
|
void ToolButton::setIconResourcePath(const QString &iconp)
|
||||||
{
|
{
|
||||||
this->setIcon(ActionManager::loadHidpiIcon(iconp, this->iconSize()));
|
this->setIcon(ActionManager::loadHidpiIcon(iconp, this->iconSize()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ToolButton::setOpacity(qreal opacity, bool animated)
|
void ToolButton::setOpacity(qreal opacity, bool animated)
|
||||||
{
|
{
|
||||||
m_opacityHelper->setOpacity(opacity, animated);
|
m_opacityHelper->setOpacity(opacity, animated);
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#ifndef TOOLBUTTON_H
|
#ifndef TOOLBUTTON_H
|
||||||
#define TOOLBUTTON_H
|
#define TOOLBUTTON_H
|
||||||
|
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
|
||||||
class OpacityHelper;
|
class OpacityHelper;
|
||||||
class ToolButton : public QPushButton
|
class ToolButton : public QPushButton
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ToolButton(bool hoverColor = false, QWidget * parent = nullptr);
|
ToolButton(bool hoverColor = false, QWidget * parent = nullptr);
|
||||||
void setIconResourcePath(const QString &iconp);
|
void setIconResourcePath(const QString &iconp);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setOpacity(qreal opacity, bool animated = true);
|
void setOpacity(qreal opacity, bool animated = true);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
OpacityHelper * m_opacityHelper;
|
OpacityHelper * m_opacityHelper;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TOOLBUTTON_H
|
#endif // TOOLBUTTON_H
|
||||||
|
272
dist/MacOSXBundleInfo.plist.in
vendored
272
dist/MacOSXBundleInfo.plist.in
vendored
@ -1,136 +1,136 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>English</string>
|
<string>English</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||||
<key>CFBundleGetInfoString</key>
|
<key>CFBundleGetInfoString</key>
|
||||||
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
|
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleLongVersionString</key>
|
<key>CFBundleLongVersionString</key>
|
||||||
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
|
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||||
<key>CSResourcesFileMapped</key>
|
<key>CSResourcesFileMapped</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||||
<!-- FIXME: this list can't be automatically generated by Qt's CMake API, don't know why. -->
|
<!-- FIXME: this list can't be automatically generated by Qt's CMake API, don't know why. -->
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
<string>ca</string>
|
<string>ca</string>
|
||||||
<string>de</string>
|
<string>de</string>
|
||||||
<string>es</string>
|
<string>es</string>
|
||||||
<string>fr</string>
|
<string>fr</string>
|
||||||
<string>id</string>
|
<string>id</string>
|
||||||
<string>it</string>
|
<string>it</string>
|
||||||
<string>ja</string>
|
<string>ja</string>
|
||||||
<string>ko</string>
|
<string>ko</string>
|
||||||
<string>nb_NO</string>
|
<string>nb_NO</string>
|
||||||
<string>nl</string>
|
<string>nl</string>
|
||||||
<string>pa_PK</string>
|
<string>pa_PK</string>
|
||||||
<string>ru</string>
|
<string>ru</string>
|
||||||
<string>si</string>
|
<string>si</string>
|
||||||
<string>ta</string>
|
<string>ta</string>
|
||||||
<string>tr</string>
|
<string>tr</string>
|
||||||
<string>uk</string>
|
<string>uk</string>
|
||||||
<string>zh_CN</string>
|
<string>zh_CN</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleDocumentTypes</key>
|
<key>CFBundleDocumentTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<!-- JPEG -->
|
<!-- JPEG -->
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>JPEG Image</string>
|
<string>JPEG Image</string>
|
||||||
<key>CFBundleTypeExtensions</key>
|
<key>CFBundleTypeExtensions</key>
|
||||||
<array>
|
<array>
|
||||||
<string>jpg</string>
|
<string>jpg</string>
|
||||||
<string>jpeg</string>
|
<string>jpeg</string>
|
||||||
<string>jfif</string>
|
<string>jfif</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleTypeMIMETypes</key>
|
<key>CFBundleTypeMIMETypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>image/jpeg</string>
|
<string>image/jpeg</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
<string>Viewer</string>
|
<string>Viewer</string>
|
||||||
</dict>
|
</dict>
|
||||||
<!-- PNG -->
|
<!-- PNG -->
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>PNG Image</string>
|
<string>PNG Image</string>
|
||||||
<key>CFBundleTypeExtensions</key>
|
<key>CFBundleTypeExtensions</key>
|
||||||
<array>
|
<array>
|
||||||
<string>png</string>
|
<string>png</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleTypeMIMETypes</key>
|
<key>CFBundleTypeMIMETypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>image/png</string>
|
<string>image/png</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
<string>Viewer</string>
|
<string>Viewer</string>
|
||||||
</dict>
|
</dict>
|
||||||
<!-- WebP -->
|
<!-- WebP -->
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>WebP Image</string>
|
<string>WebP Image</string>
|
||||||
<key>CFBundleTypeExtensions</key>
|
<key>CFBundleTypeExtensions</key>
|
||||||
<array>
|
<array>
|
||||||
<string>webp</string>
|
<string>webp</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleTypeMIMETypes</key>
|
<key>CFBundleTypeMIMETypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>image/webp</string>
|
<string>image/webp</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
<string>Viewer</string>
|
<string>Viewer</string>
|
||||||
</dict>
|
</dict>
|
||||||
<!-- GIF -->
|
<!-- GIF -->
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>GIF Image</string>
|
<string>GIF Image</string>
|
||||||
<key>CFBundleTypeExtensions</key>
|
<key>CFBundleTypeExtensions</key>
|
||||||
<array>
|
<array>
|
||||||
<string>gif</string>
|
<string>gif</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleTypeMIMETypes</key>
|
<key>CFBundleTypeMIMETypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>image/gif</string>
|
<string>image/gif</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
<string>Viewer</string>
|
<string>Viewer</string>
|
||||||
</dict>
|
</dict>
|
||||||
<!-- SVG -->
|
<!-- SVG -->
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleTypeName</key>
|
<key>CFBundleTypeName</key>
|
||||||
<string>SVG Image</string>
|
<string>SVG Image</string>
|
||||||
<key>CFBundleTypeExtensions</key>
|
<key>CFBundleTypeExtensions</key>
|
||||||
<array>
|
<array>
|
||||||
<string>svg</string>
|
<string>svg</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleTypeMIMETypes</key>
|
<key>CFBundleTypeMIMETypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>image/svg+xml</string>
|
<string>image/svg+xml</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleTypeRole</key>
|
<key>CFBundleTypeRole</key>
|
||||||
<string>Viewer</string>
|
<string>Viewer</string>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -1,101 +1,101 @@
|
|||||||
# SPDX-FileCopyrightText: 2025 Gary Wang <git@blumia.net>
|
# SPDX-FileCopyrightText: 2025 Gary Wang <git@blumia.net>
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
QT += core widgets gui svg svgwidgets
|
QT += core widgets gui svg svgwidgets
|
||||||
|
|
||||||
TARGET = ppic
|
TARGET = ppic
|
||||||
TEMPLATE = app
|
TEMPLATE = app
|
||||||
DEFINES += PPIC_VERSION_STRING=\\\"x.y.z\\\"
|
DEFINES += PPIC_VERSION_STRING=\\\"x.y.z\\\"
|
||||||
|
|
||||||
win32 {
|
win32 {
|
||||||
DEFINES += FLAG_PORTABLE_MODE_SUPPORT=1
|
DEFINES += FLAG_PORTABLE_MODE_SUPPORT=1
|
||||||
}
|
}
|
||||||
|
|
||||||
# The following define makes your compiler emit warnings if you use
|
# The following define makes your compiler emit warnings if you use
|
||||||
# any feature of Qt which has been marked as deprecated (the exact warnings
|
# any feature of Qt which has been marked as deprecated (the exact warnings
|
||||||
# depend on your compiler). Please consult the documentation of the
|
# depend on your compiler). Please consult the documentation of the
|
||||||
# deprecated API in order to know how to port your code away from it.
|
# deprecated API in order to know how to port your code away from it.
|
||||||
DEFINES += QT_DEPRECATED_WARNINGS
|
DEFINES += QT_DEPRECATED_WARNINGS
|
||||||
|
|
||||||
# You can also make your code fail to compile if you use deprecated APIs.
|
# You can also make your code fail to compile if you use deprecated APIs.
|
||||||
# In order to do so, uncomment the following line.
|
# In order to do so, uncomment the following line.
|
||||||
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
||||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||||
|
|
||||||
CONFIG += c++17 lrelease embed_translations
|
CONFIG += c++17 lrelease embed_translations
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
app/aboutdialog.cpp \
|
app/aboutdialog.cpp \
|
||||||
app/main.cpp \
|
app/main.cpp \
|
||||||
app/framelesswindow.cpp \
|
app/framelesswindow.cpp \
|
||||||
app/mainwindow.cpp \
|
app/mainwindow.cpp \
|
||||||
app/graphicsview.cpp \
|
app/graphicsview.cpp \
|
||||||
app/bottombuttongroup.cpp \
|
app/bottombuttongroup.cpp \
|
||||||
app/graphicsscene.cpp \
|
app/graphicsscene.cpp \
|
||||||
app/navigatorview.cpp \
|
app/navigatorview.cpp \
|
||||||
app/opacityhelper.cpp \
|
app/opacityhelper.cpp \
|
||||||
app/toolbutton.cpp \
|
app/toolbutton.cpp \
|
||||||
app/settings.cpp \
|
app/settings.cpp \
|
||||||
app/settingsdialog.cpp \
|
app/settingsdialog.cpp \
|
||||||
app/metadatamodel.cpp \
|
app/metadatamodel.cpp \
|
||||||
app/metadatadialog.cpp \
|
app/metadatadialog.cpp \
|
||||||
app/exiv2wrapper.cpp \
|
app/exiv2wrapper.cpp \
|
||||||
app/actionmanager.cpp \
|
app/actionmanager.cpp \
|
||||||
app/playlistmanager.cpp \
|
app/playlistmanager.cpp \
|
||||||
app/shortcutedit.cpp \
|
app/shortcutedit.cpp \
|
||||||
app/fileopeneventhandler.cpp
|
app/fileopeneventhandler.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
app/aboutdialog.h \
|
app/aboutdialog.h \
|
||||||
app/framelesswindow.h \
|
app/framelesswindow.h \
|
||||||
app/mainwindow.h \
|
app/mainwindow.h \
|
||||||
app/graphicsview.h \
|
app/graphicsview.h \
|
||||||
app/bottombuttongroup.h \
|
app/bottombuttongroup.h \
|
||||||
app/graphicsscene.h \
|
app/graphicsscene.h \
|
||||||
app/navigatorview.h \
|
app/navigatorview.h \
|
||||||
app/opacityhelper.h \
|
app/opacityhelper.h \
|
||||||
app/toolbutton.h \
|
app/toolbutton.h \
|
||||||
app/settings.h \
|
app/settings.h \
|
||||||
app/settingsdialog.h \
|
app/settingsdialog.h \
|
||||||
app/metadatamodel.h \
|
app/metadatamodel.h \
|
||||||
app/metadatadialog.h \
|
app/metadatadialog.h \
|
||||||
app/exiv2wrapper.h \
|
app/exiv2wrapper.h \
|
||||||
app/actionmanager.h \
|
app/actionmanager.h \
|
||||||
app/playlistmanager.h \
|
app/playlistmanager.h \
|
||||||
app/shortcutedit.h \
|
app/shortcutedit.h \
|
||||||
app/fileopeneventhandler.h
|
app/fileopeneventhandler.h
|
||||||
|
|
||||||
TRANSLATIONS = \
|
TRANSLATIONS = \
|
||||||
app/translations/PineapplePictures_en.ts \
|
app/translations/PineapplePictures_en.ts \
|
||||||
app/translations/PineapplePictures_zh_CN.ts \
|
app/translations/PineapplePictures_zh_CN.ts \
|
||||||
app/translations/PineapplePictures_de.ts \
|
app/translations/PineapplePictures_de.ts \
|
||||||
app/translations/PineapplePictures_es.ts \
|
app/translations/PineapplePictures_es.ts \
|
||||||
app/translations/PineapplePictures_fr.ts \
|
app/translations/PineapplePictures_fr.ts \
|
||||||
app/translations/PineapplePictures_nb_NO.ts \
|
app/translations/PineapplePictures_nb_NO.ts \
|
||||||
app/translations/PineapplePictures_nl.ts \
|
app/translations/PineapplePictures_nl.ts \
|
||||||
app/translations/PineapplePictures_ru.ts \
|
app/translations/PineapplePictures_ru.ts \
|
||||||
app/translations/PineapplePictures_si.ts \
|
app/translations/PineapplePictures_si.ts \
|
||||||
app/translations/PineapplePictures_id.ts
|
app/translations/PineapplePictures_id.ts
|
||||||
|
|
||||||
# Default rules for deployment.
|
# Default rules for deployment.
|
||||||
qnx: target.path = /tmp/$${TARGET}/bin
|
qnx: target.path = /tmp/$${TARGET}/bin
|
||||||
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||||
!isEmpty(target.path): INSTALLS += target
|
!isEmpty(target.path): INSTALLS += target
|
||||||
|
|
||||||
RESOURCES += \
|
RESOURCES += \
|
||||||
assets/resources.qrc
|
assets/resources.qrc
|
||||||
|
|
||||||
# Generate from svg:
|
# Generate from svg:
|
||||||
# magick convert -density 512x512 -background none app-icon.svg -define icon:auto-resize app-icon.ico
|
# magick convert -density 512x512 -background none app-icon.svg -define icon:auto-resize app-icon.ico
|
||||||
RC_ICONS = assets/icons/app-icon.ico
|
RC_ICONS = assets/icons/app-icon.ico
|
||||||
|
|
||||||
# Windows only, for rc file (we're not going to use the .rc file in this repo)
|
# Windows only, for rc file (we're not going to use the .rc file in this repo)
|
||||||
QMAKE_TARGET_PRODUCT = Pineapple Pictures
|
QMAKE_TARGET_PRODUCT = Pineapple Pictures
|
||||||
QMAKE_TARGET_DESCRIPTION = Pineapple Pictures - Image Viewer
|
QMAKE_TARGET_DESCRIPTION = Pineapple Pictures - Image Viewer
|
||||||
QMAKE_TARGET_COPYRIGHT = MIT/Expat License - Copyright (C) 2024 Gary Wang
|
QMAKE_TARGET_COPYRIGHT = MIT/Expat License - Copyright (C) 2024 Gary Wang
|
||||||
|
|
||||||
# MSVC only, since QMake doesn't have a CMAKE_CXX_STANDARD_LIBRARIES or cpp_winlibs similar thing
|
# MSVC only, since QMake doesn't have a CMAKE_CXX_STANDARD_LIBRARIES or cpp_winlibs similar thing
|
||||||
win32-msvc* {
|
win32-msvc* {
|
||||||
LIBS += -luser32
|
LIBS += -luser32
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user