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