chore: adjusted the project directory structure
This commit is contained in:
157
app/aboutdialog.cpp
Normal file
157
app/aboutdialog.cpp
Normal file
@ -0,0 +1,157 @@
|
||||
#include "aboutdialog.h"
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QTextBrowser>
|
||||
#include <QTabWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QApplication>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
|
||||
AboutDialog::AboutDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, m_tabWidget(new QTabWidget)
|
||||
, m_buttonBox(new QDialogButtonBox)
|
||||
, m_helpTextEdit(new QTextBrowser)
|
||||
, m_aboutTextEdit(new QTextBrowser)
|
||||
, m_specialThanksTextEdit(new QTextBrowser)
|
||||
, m_licenseTextEdit(new QTextBrowser)
|
||||
, m_3rdPartyLibsTextEdit(new QTextBrowser)
|
||||
{
|
||||
this->setWindowTitle(tr("About"));
|
||||
|
||||
QStringList helpStr {
|
||||
QStringLiteral("<p>%1</p>").arg(tr("Launch application with image file path as argument to load the file.")),
|
||||
QStringLiteral("<p>%1</p>").arg(tr("Drag and drop image file onto the window is also supported.")),
|
||||
QStringLiteral("<p>%1</p>").arg(tr("Context menu option explanation:")),
|
||||
QStringLiteral("<ul>"),
|
||||
QStringLiteral("<li><b>%1</b>:<br/>%2</li>").arg(
|
||||
QCoreApplication::translate("MainWindow", "Stay on top"),
|
||||
this->tr("Make window stay on top of all other windows.")
|
||||
),
|
||||
QStringLiteral("<li><b>%1</b>:<br/>%2</li>").arg(
|
||||
QCoreApplication::translate("MainWindow", "Protected mode"),
|
||||
this->tr("Avoid close window accidentally. (eg. by double clicking the window)")
|
||||
),
|
||||
QStringLiteral("</ul>")
|
||||
};
|
||||
|
||||
QStringList aboutStr {
|
||||
QStringLiteral("<center><img width='128' height='128' src=':/icons/app-icon.svg'/><br/>"),
|
||||
qApp->applicationDisplayName(),
|
||||
#ifdef GIT_DESCRIBE_VERSION_STRING
|
||||
(QStringLiteral("<br/>") + tr("Version: %1").arg(GIT_DESCRIBE_VERSION_STRING)),
|
||||
#endif // GIT_DESCRIBE_VERSION_STRING
|
||||
QStringLiteral("<hr/>"),
|
||||
tr("Copyright (c) 2020 %1").arg(QStringLiteral("<a href='https://github.com/BLumia'>@BLumia</a>")),
|
||||
QStringLiteral("<br/>"),
|
||||
tr("Logo designed by %1").arg(QStringLiteral("<a href='https://github.com/Lovelyblack'>@Lovelyblack</a>")),
|
||||
QStringLiteral("<hr/>"),
|
||||
tr("Built with Qt %1 (%2)").arg(QT_VERSION_STR, QSysInfo::buildCpuArchitecture()),
|
||||
QStringLiteral("<br/><a href='%1'>%2</a>").arg("https://github.com/BLumia/pineapple-pictures", tr("Source code")),
|
||||
QStringLiteral("</center>")
|
||||
};
|
||||
|
||||
QStringList specialThanksStr {
|
||||
QStringLiteral("<h1 align='center'>%1</h1><a href='%2'>%3</a><p>%4</p>").arg(
|
||||
tr("Contributors"),
|
||||
QStringLiteral("https://github.com/BLumia/pineapple-pictures/graphs/contributors"),
|
||||
tr("List of contributors on GitHub"),
|
||||
tr("Thanks to all people who contributed to this project.")
|
||||
),
|
||||
#if 0
|
||||
QStringLiteral("<h1 align='center'>%1</h1><p>%2</p>").arg(
|
||||
tr("Translators"),
|
||||
tr("I would like to thank the following people who volunteered to translate this application.")
|
||||
),
|
||||
#endif
|
||||
};
|
||||
|
||||
QStringList licenseStr {
|
||||
QStringLiteral("<h1 align='center'><b>%1</b></h1>").arg(tr("Your Rights")),
|
||||
QStringLiteral("<p>%1</p><p>%2</p><ul><li>%3</li><li>%4</li><li>%5</li><li>%6</li></ul>").arg(
|
||||
tr("%1 is released under the MIT License."), // %1
|
||||
tr("This license grants people a number of freedoms:"), // %2
|
||||
tr("You are free to use %1, for any purpose"), // %3
|
||||
tr("You are free to distribute %1"), // %4
|
||||
tr("You can study how %1 works and change it"), // %5
|
||||
tr("You can distribute changed versions of %1") // %6
|
||||
).arg(QStringLiteral("<i>%1</i>")),
|
||||
QStringLiteral("<p>%1</p>").arg(tr("The MIT license guarantees you this freedom. Nobody is ever permitted to take it away.")),
|
||||
QStringLiteral("<hr/><pre>%2</pre>")
|
||||
};
|
||||
|
||||
QString mitLicense(QStringLiteral(R"(Expat/MIT License
|
||||
|
||||
Copyright (c) 2020 BLumia
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
)"));
|
||||
|
||||
QStringList thirdPartyLibsStr {
|
||||
QStringLiteral("<h1 align='center'><b>%1</b></h1>").arg(tr("Third-party Libraries used by %1")),
|
||||
tr("%1 is built on the following free software libraries:"),
|
||||
QStringLiteral("<ul>"),
|
||||
QStringLiteral("<li><a href='%1'>%2</a>: %3</li>").arg("https://www.qt.io/", "Qt", "GPLv2 + GPLv3 + LGPLv2.1 + LGPLv3"),
|
||||
QStringLiteral("</ul>")
|
||||
};
|
||||
|
||||
m_helpTextEdit->setText(helpStr.join('\n'));
|
||||
|
||||
m_aboutTextEdit->setText(aboutStr.join('\n'));
|
||||
m_aboutTextEdit->setOpenExternalLinks(true);
|
||||
|
||||
m_specialThanksTextEdit->setText(specialThanksStr.join('\n'));
|
||||
m_specialThanksTextEdit->setOpenExternalLinks(true);
|
||||
|
||||
m_licenseTextEdit->setText(licenseStr.join('\n').arg(qApp->applicationDisplayName(), mitLicense));
|
||||
|
||||
m_3rdPartyLibsTextEdit->setText(thirdPartyLibsStr.join('\n').arg(QStringLiteral("<i>%1</i>")).arg(qApp->applicationDisplayName()));
|
||||
m_3rdPartyLibsTextEdit->setOpenExternalLinks(true);
|
||||
|
||||
m_tabWidget->addTab(m_helpTextEdit, tr("&Help"));
|
||||
m_tabWidget->addTab(m_aboutTextEdit, tr("&About"));
|
||||
m_tabWidget->addTab(m_specialThanksTextEdit, tr("&Special Thanks"));
|
||||
m_tabWidget->addTab(m_licenseTextEdit, tr("&License"));
|
||||
m_tabWidget->addTab(m_3rdPartyLibsTextEdit, tr("&Third-party Libraries"));
|
||||
|
||||
m_buttonBox->setStandardButtons(QDialogButtonBox::Close);
|
||||
connect(m_buttonBox, QOverload<QAbstractButton *>::of(&QDialogButtonBox::clicked), this, [this](){
|
||||
this->close();
|
||||
});
|
||||
|
||||
setLayout(new QVBoxLayout);
|
||||
|
||||
layout()->addWidget(m_tabWidget);
|
||||
layout()->addWidget(m_buttonBox);
|
||||
|
||||
setMinimumSize(361, 161); // not sure why it complain "Unable to set geometry"
|
||||
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
|
||||
}
|
||||
|
||||
AboutDialog::~AboutDialog()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QSize AboutDialog::sizeHint() const
|
||||
{
|
||||
return QSize(520, 350);
|
||||
}
|
32
app/aboutdialog.h
Normal file
32
app/aboutdialog.h
Normal file
@ -0,0 +1,32 @@
|
||||
#ifndef ABOUTDIALOG_H
|
||||
#define ABOUTDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QTextBrowser;
|
||||
class QTabWidget;
|
||||
class QDialogButtonBox;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class AboutDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AboutDialog(QWidget *parent = nullptr);
|
||||
~AboutDialog() override;
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
private:
|
||||
QTabWidget * m_tabWidget = nullptr;
|
||||
QDialogButtonBox * m_buttonBox = nullptr;
|
||||
|
||||
QTextBrowser * m_helpTextEdit = nullptr;
|
||||
QTextBrowser * m_aboutTextEdit = nullptr;
|
||||
QTextBrowser * m_specialThanksTextEdit = nullptr;
|
||||
QTextBrowser * m_licenseTextEdit = nullptr;
|
||||
QTextBrowser * m_3rdPartyLibsTextEdit = nullptr;
|
||||
};
|
||||
|
||||
#endif // ABOUTDIALOG_H
|
68
app/bottombuttongroup.cpp
Normal file
68
app/bottombuttongroup.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
#include "bottombuttongroup.h"
|
||||
|
||||
#include "opacityhelper.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QDebug>
|
||||
|
||||
BottomButtonGroup::BottomButtonGroup(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)"
|
||||
"}"
|
||||
"QPushButton {"
|
||||
"background-color:rgba(225,255,255,0);"
|
||||
"color: white;"
|
||||
"border-style: none;"
|
||||
"}");
|
||||
|
||||
auto newBtn = [](QString text, std::function<void()> func) -> QPushButton * {
|
||||
QPushButton * btn = new QPushButton(QIcon(QStringLiteral(":/icons/") + text), "");
|
||||
btn->setIconSize(QSize(40, 40));
|
||||
btn->setFixedSize(40, 40);
|
||||
QObject::connect(btn, &QAbstractButton::clicked, btn, func);
|
||||
return btn;
|
||||
};
|
||||
addButton(newBtn("zoom-original", [this]() {
|
||||
emit resetToOriginalBtnClicked();
|
||||
}));
|
||||
addButton(newBtn("view-fullscreen", [this]() {
|
||||
emit toggleWindowMaximum();
|
||||
}));
|
||||
addButton(newBtn("zoom-in", [this]() {
|
||||
emit zoomInBtnClicked();
|
||||
}));
|
||||
addButton(newBtn("zoom-out", [this]() {
|
||||
emit zoomOutBtnClicked();
|
||||
}));
|
||||
addButton(newBtn("view-background-checkerboard", [this]() {
|
||||
emit toggleCheckerboardBtnClicked();
|
||||
}));
|
||||
addButton(newBtn("object-rotate-right", [this]() {
|
||||
emit rotateRightBtnClicked();
|
||||
}));
|
||||
}
|
||||
|
||||
void BottomButtonGroup::setOpacity(qreal opacity, bool animated)
|
||||
{
|
||||
m_opacityHelper->setOpacity(opacity, animated);
|
||||
}
|
||||
|
||||
void BottomButtonGroup::addButton(QAbstractButton *button)
|
||||
{
|
||||
layout()->addWidget(button);
|
||||
updateGeometry();
|
||||
}
|
29
app/bottombuttongroup.h
Normal file
29
app/bottombuttongroup.h
Normal file
@ -0,0 +1,29 @@
|
||||
#ifndef BOTTOMBUTTONGROUP_H
|
||||
#define BOTTOMBUTTONGROUP_H
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QGroupBox>
|
||||
|
||||
class OpacityHelper;
|
||||
class BottomButtonGroup : public QGroupBox
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit BottomButtonGroup(QWidget *parent = nullptr);
|
||||
|
||||
void setOpacity(qreal opacity, bool animated = true);
|
||||
void addButton(QAbstractButton *button);
|
||||
|
||||
signals:
|
||||
void resetToOriginalBtnClicked();
|
||||
void toggleWindowMaximum();
|
||||
void zoomInBtnClicked();
|
||||
void zoomOutBtnClicked();
|
||||
void toggleCheckerboardBtnClicked();
|
||||
void rotateRightBtnClicked();
|
||||
|
||||
private:
|
||||
OpacityHelper * m_opacityHelper;
|
||||
};
|
||||
|
||||
#endif // BOTTOMBUTTONGROUP_H
|
84
app/graphicsscene.cpp
Normal file
84
app/graphicsscene.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
#include "graphicsscene.h"
|
||||
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QMimeData>
|
||||
#include <QDebug>
|
||||
#include <QGraphicsItem>
|
||||
#include <QUrl>
|
||||
#include <QGraphicsSvgItem>
|
||||
#include <QMovie>
|
||||
#include <QLabel>
|
||||
#include <QPainter>
|
||||
|
||||
GraphicsScene::GraphicsScene(QObject *parent)
|
||||
: QGraphicsScene(parent)
|
||||
{
|
||||
showText(tr("Drag image here"));
|
||||
}
|
||||
|
||||
GraphicsScene::~GraphicsScene()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GraphicsScene::showImage(const QPixmap &pixmap)
|
||||
{
|
||||
this->clear();
|
||||
QGraphicsPixmapItem * pixmapItem = this->addPixmap(pixmap);
|
||||
pixmapItem->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
|
||||
m_theThing = pixmapItem;
|
||||
this->setSceneRect(m_theThing->boundingRect());
|
||||
}
|
||||
|
||||
void GraphicsScene::showText(const QString &text)
|
||||
{
|
||||
this->clear();
|
||||
QGraphicsTextItem * textItem = this->addText(text);
|
||||
textItem->setDefaultTextColor(QColor("White"));
|
||||
m_theThing = textItem;
|
||||
this->setSceneRect(m_theThing->boundingRect());
|
||||
}
|
||||
|
||||
void GraphicsScene::showSvg(const QString &filepath)
|
||||
{
|
||||
this->clear();
|
||||
QGraphicsSvgItem * svgItem = new QGraphicsSvgItem(filepath);
|
||||
this->addItem(svgItem);
|
||||
m_theThing = svgItem;
|
||||
this->setSceneRect(m_theThing->boundingRect());
|
||||
}
|
||||
|
||||
void GraphicsScene::showGif(const QString &filepath)
|
||||
{
|
||||
this->clear();
|
||||
QMovie * movie = new QMovie(filepath);
|
||||
QLabel * label = new QLabel;
|
||||
label->setStyleSheet("background-color:rgba(225,255,255,0);");
|
||||
label->setMovie(movie);
|
||||
this->addWidget(label);
|
||||
movie->start();
|
||||
m_theThing = this->addRect(QRect(QPoint(0, 0), label->sizeHint()),
|
||||
QPen(Qt::transparent));
|
||||
this->setSceneRect(m_theThing->boundingRect());
|
||||
}
|
||||
|
||||
bool GraphicsScene::trySetTransformationMode(Qt::TransformationMode mode)
|
||||
{
|
||||
QGraphicsPixmapItem * pixmapItem = qgraphicsitem_cast<QGraphicsPixmapItem *>(m_theThing);
|
||||
if (pixmapItem) {
|
||||
pixmapItem->setTransformationMode(mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QPixmap GraphicsScene::renderToPixmap()
|
||||
{
|
||||
QPixmap pixmap(sceneRect().toRect().size());
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter p(&pixmap);
|
||||
render(&p, sceneRect());
|
||||
|
||||
return pixmap;
|
||||
}
|
26
app/graphicsscene.h
Normal file
26
app/graphicsscene.h
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef GRAPHICSSCENE_H
|
||||
#define GRAPHICSSCENE_H
|
||||
|
||||
#include <QGraphicsScene>
|
||||
|
||||
class GraphicsScene : public QGraphicsScene
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
GraphicsScene(QObject *parent = nullptr);
|
||||
~GraphicsScene();
|
||||
|
||||
void showImage(const QPixmap &pixmap);
|
||||
void showText(const QString &text);
|
||||
void showSvg(const QString &filepath);
|
||||
void showGif(const QString &filepath);
|
||||
|
||||
bool trySetTransformationMode(Qt::TransformationMode mode);
|
||||
|
||||
QPixmap renderToPixmap();
|
||||
|
||||
private:
|
||||
QGraphicsItem * m_theThing;
|
||||
};
|
||||
|
||||
#endif // GRAPHICSSCENE_H
|
337
app/graphicsview.cpp
Normal file
337
app/graphicsview.cpp
Normal file
@ -0,0 +1,337 @@
|
||||
#include "graphicsview.h"
|
||||
|
||||
#include "graphicsscene.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMouseEvent>
|
||||
#include <QScrollBar>
|
||||
#include <QMimeData>
|
||||
#include <QImageReader>
|
||||
|
||||
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(true);
|
||||
setCheckerboardEnabled(false);
|
||||
|
||||
connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, &GraphicsView::viewportRectChanged);
|
||||
connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &GraphicsView::viewportRectChanged);
|
||||
}
|
||||
|
||||
void GraphicsView::showFileFromUrl(const QUrl &url, bool doRequestGallery)
|
||||
{
|
||||
emit navigatorViewRequired(false, 0);
|
||||
|
||||
QString filePath(url.toLocalFile());
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// TODO: remove this workaround when M$ change the "wsl$" hostname.
|
||||
if (Q_UNLIKELY(url.scheme() == QStringLiteral("qtbug-86277"))) {
|
||||
filePath = url.path();
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
|
||||
if (filePath.endsWith(".svg")) {
|
||||
showSvg(filePath);
|
||||
} else if (filePath.endsWith(".gif")) {
|
||||
showGif(filePath);
|
||||
} else {
|
||||
QImageReader imageReader(filePath);
|
||||
imageReader.setAutoTransform(true);
|
||||
imageReader.setDecideFormatFromContent(true);
|
||||
// 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.canRead()) {
|
||||
showText(tr("Image data is invalid or currently unsupported"));
|
||||
} else {
|
||||
const QPixmap & pixmap = QPixmap::fromImageReader(&imageReader);
|
||||
if (pixmap.isNull()) {
|
||||
showText(tr("Image data is invalid or currently unsupported"));
|
||||
} else {
|
||||
showImage(pixmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (doRequestGallery) {
|
||||
emit requestGallery(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsView::showImage(const QPixmap &pixmap)
|
||||
{
|
||||
resetTransform();
|
||||
scene()->showImage(pixmap);
|
||||
checkAndDoFitInView();
|
||||
}
|
||||
|
||||
void GraphicsView::showImage(const QImage &image)
|
||||
{
|
||||
resetTransform();
|
||||
scene()->showImage(QPixmap::fromImage(image));
|
||||
checkAndDoFitInView();
|
||||
}
|
||||
|
||||
void GraphicsView::showText(const QString &text)
|
||||
{
|
||||
resetTransform();
|
||||
scene()->showText(text);
|
||||
checkAndDoFitInView();
|
||||
}
|
||||
|
||||
void GraphicsView::showSvg(const QString &filepath)
|
||||
{
|
||||
resetTransform();
|
||||
scene()->showSvg(filepath);
|
||||
checkAndDoFitInView();
|
||||
}
|
||||
|
||||
void GraphicsView::showGif(const QString &filepath)
|
||||
{
|
||||
resetTransform();
|
||||
scene()->showGif(filepath);
|
||||
checkAndDoFitInView();
|
||||
}
|
||||
|
||||
GraphicsScene *GraphicsView::scene() const
|
||||
{
|
||||
return qobject_cast<GraphicsScene*>(QGraphicsView::scene());
|
||||
}
|
||||
|
||||
void GraphicsView::setScene(GraphicsScene *scene)
|
||||
{
|
||||
return QGraphicsView::setScene(scene);
|
||||
}
|
||||
|
||||
qreal GraphicsView::scaleFactor() const
|
||||
{
|
||||
int angle = static_cast<int>(m_rotateAngle);
|
||||
if (angle == 0 || angle == 180) {
|
||||
return qAbs(transform().m11());
|
||||
} else {
|
||||
return qAbs(transform().m12());
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsView::resetTransform()
|
||||
{
|
||||
m_rotateAngle = 0;
|
||||
QGraphicsView::resetTransform();
|
||||
}
|
||||
|
||||
void GraphicsView::zoomView(qreal scaleFactor)
|
||||
{
|
||||
m_enableFitInView = false;
|
||||
scale(scaleFactor, scaleFactor);
|
||||
applyTransformationModeByScaleFactor();
|
||||
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), m_rotateAngle);
|
||||
}
|
||||
|
||||
void GraphicsView::resetScale()
|
||||
{
|
||||
resetWithScaleAndRotate(1, m_rotateAngle);
|
||||
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), m_rotateAngle);
|
||||
}
|
||||
|
||||
void GraphicsView::rotateView(qreal rotateAngel)
|
||||
{
|
||||
m_rotateAngle += rotateAngel;
|
||||
m_rotateAngle = static_cast<int>(m_rotateAngle) % 360;
|
||||
resetWithScaleAndRotate(1, m_rotateAngle);
|
||||
}
|
||||
|
||||
void GraphicsView::fitInView(const QRectF &rect, Qt::AspectRatioMode aspectRadioMode)
|
||||
{
|
||||
QGraphicsView::fitInView(rect, aspectRadioMode);
|
||||
applyTransformationModeByScaleFactor();
|
||||
}
|
||||
|
||||
void GraphicsView::checkAndDoFitInView(bool markItOnAnyway)
|
||||
{
|
||||
if (!isThingSmallerThanWindowWith(transform())) {
|
||||
m_enableFitInView = true;
|
||||
fitInView(sceneRect(), Qt::KeepAspectRatio);
|
||||
}
|
||||
|
||||
if (markItOnAnyway) {
|
||||
m_enableFitInView = true;
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsView::toggleCheckerboard()
|
||||
{
|
||||
setCheckerboardEnabled(!m_checkerboardEnabled);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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) {
|
||||
QTransform tf;
|
||||
tf.rotate(m_rotateAngle);
|
||||
if (isThingSmallerThanWindowWith(tf) && 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 {
|
||||
fitInView(sceneRect(), Qt::KeepAspectRatio);
|
||||
}
|
||||
} else {
|
||||
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), m_rotateAngle);
|
||||
}
|
||||
return QGraphicsView::resizeEvent(event);
|
||||
}
|
||||
|
||||
void GraphicsView::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
if (event->mimeData()->hasUrls() || event->mimeData()->hasImage() || event->mimeData()->hasText()) {
|
||||
event->acceptProposedAction();
|
||||
} else {
|
||||
event->ignore();
|
||||
}
|
||||
// qDebug() << event->mimeData() << "Drag Enter Event"
|
||||
// << event->mimeData()->hasUrls() << event->mimeData()->hasImage()
|
||||
// << event->mimeData()->formats() << event->mimeData()->hasFormat("text/uri-list");
|
||||
|
||||
return QGraphicsView::dragEnterEvent(event);
|
||||
}
|
||||
|
||||
void GraphicsView::dragMoveEvent(QDragMoveEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
// by default, QGraphicsView/Scene will ignore the action if there are no QGraphicsItem under cursor.
|
||||
// We actually doesn't care and would like to keep the drag event as-is, so just do nothing here.
|
||||
}
|
||||
|
||||
void GraphicsView::dropEvent(QDropEvent *event)
|
||||
{
|
||||
event->acceptProposedAction();
|
||||
|
||||
const QMimeData * mimeData = event->mimeData();
|
||||
|
||||
if (mimeData->hasUrls()) {
|
||||
const QList<QUrl> &urls = mimeData->urls();
|
||||
if (urls.isEmpty()) {
|
||||
showText(tr("File url list is empty"));
|
||||
} else {
|
||||
showFileFromUrl(urls.first(), true);
|
||||
}
|
||||
} else if (mimeData->hasImage()) {
|
||||
QImage img = qvariant_cast<QImage>(mimeData->imageData());
|
||||
QPixmap pixmap = QPixmap::fromImage(img);
|
||||
if (pixmap.isNull()) {
|
||||
showText(tr("Image data is invalid"));
|
||||
} else {
|
||||
showImage(pixmap);
|
||||
}
|
||||
} else if (mimeData->hasText()) {
|
||||
showText(mimeData->text());
|
||||
} else {
|
||||
showText(tr("Not supported mimedata: %1").arg(mimeData->formats().first()));
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
m_checkerboardEnabled = enabled;
|
||||
if (m_checkerboardEnabled) {
|
||||
// Prepare background check-board pattern
|
||||
QPixmap tilePixmap(0x20, 0x20);
|
||||
tilePixmap.fill(QColor(35, 35, 35, 170));
|
||||
QPainter tilePainter(&tilePixmap);
|
||||
QColor color(45, 45, 45, 170);
|
||||
tilePainter.fillRect(0, 0, 0x10, 0x10, color);
|
||||
tilePainter.fillRect(0x10, 0x10, 0x10, 0x10, color);
|
||||
tilePainter.end();
|
||||
|
||||
setBackgroundBrush(tilePixmap);
|
||||
} else {
|
||||
setBackgroundBrush(Qt::transparent);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsView::applyTransformationModeByScaleFactor()
|
||||
{
|
||||
if (this->scaleFactor() < 1) {
|
||||
scene()->trySetTransformationMode(Qt::SmoothTransformation);
|
||||
} else {
|
||||
scene()->trySetTransformationMode(Qt::FastTransformation);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsView::resetWithScaleAndRotate(qreal scaleFactor, qreal rotateAngle)
|
||||
{
|
||||
QGraphicsView::resetTransform();
|
||||
scale(scaleFactor, scaleFactor);
|
||||
rotate(rotateAngle);
|
||||
}
|
66
app/graphicsview.h
Normal file
66
app/graphicsview.h
Normal file
@ -0,0 +1,66 @@
|
||||
#ifndef GRAPHICSVIEW_H
|
||||
#define GRAPHICSVIEW_H
|
||||
|
||||
#include <QGraphicsView>
|
||||
#include <QUrl>
|
||||
|
||||
class GraphicsScene;
|
||||
class GraphicsView : public QGraphicsView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
GraphicsView(QWidget *parent = nullptr);
|
||||
|
||||
void showFileFromUrl(const QUrl &url, bool requestGallery = false);
|
||||
|
||||
void showImage(const QPixmap &pixmap);
|
||||
void showImage(const QImage &image);
|
||||
void showText(const QString &text);
|
||||
void showSvg(const QString &filepath);
|
||||
void showGif(const QString &filepath);
|
||||
|
||||
GraphicsScene * scene() const;
|
||||
void setScene(GraphicsScene *scene);
|
||||
|
||||
qreal scaleFactor() const;
|
||||
|
||||
void resetTransform();
|
||||
void zoomView(qreal scaleFactor);
|
||||
void resetScale();
|
||||
void rotateView(qreal rotateAngel);
|
||||
void fitInView(const QRectF &rect, Qt::AspectRatioMode aspectRadioMode = Qt::IgnoreAspectRatio);
|
||||
|
||||
void checkAndDoFitInView(bool markItOnAnyway = true);
|
||||
|
||||
signals:
|
||||
void navigatorViewRequired(bool required, qreal angle);
|
||||
void viewportRectChanged();
|
||||
void requestGallery(const QString &filePath);
|
||||
|
||||
public slots:
|
||||
void toggleCheckerboard();
|
||||
|
||||
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;
|
||||
|
||||
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||
void dragMoveEvent(QDragMoveEvent *event) override;
|
||||
void dropEvent(QDropEvent *event) override;
|
||||
|
||||
bool isThingSmallerThanWindowWith(const QTransform &transform) const;
|
||||
bool shouldIgnoreMousePressMoveEvent(const QMouseEvent *event) const;
|
||||
void setCheckerboardEnabled(bool enabled);
|
||||
void applyTransformationModeByScaleFactor();
|
||||
|
||||
void resetWithScaleAndRotate(qreal scaleFactor, qreal rotateAngle);
|
||||
|
||||
bool m_enableFitInView = false;
|
||||
bool m_checkerboardEnabled = false;
|
||||
qreal m_rotateAngle = 0;
|
||||
};
|
||||
|
||||
#endif // GRAPHICSVIEW_H
|
62
app/main.cpp
Normal file
62
app/main.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
#include "mainwindow.h"
|
||||
#include <QApplication>
|
||||
#include <QCommandLineParser>
|
||||
#include <QDir>
|
||||
#include <QTranslator>
|
||||
#include <QUrl>
|
||||
|
||||
// QM_FILE_INSTALL_DIR should be defined from the CMakeLists file.
|
||||
#ifndef QM_FILE_INSTALL_DIR
|
||||
#define QM_FILE_INSTALL_DIR ":/i18n/"
|
||||
#endif // QM_FILE_INSTALL_DIR
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
|
||||
QTranslator translator;
|
||||
QString qmDir;
|
||||
#ifdef _WIN32
|
||||
qmDir = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath("translations");
|
||||
#else
|
||||
qmDir = QT_STRINGIFY(QM_FILE_INSTALL_DIR);
|
||||
#endif
|
||||
translator.load(QString("PineapplePictures_%1").arg(QLocale::system().name()), qmDir);
|
||||
a.installTranslator(&translator);
|
||||
a.setApplicationName("Pineapple Pictures");
|
||||
a.setApplicationDisplayName(QCoreApplication::translate("main", "Pineapple Pictures"));
|
||||
|
||||
// parse commandline arguments
|
||||
QCommandLineParser parser;
|
||||
parser.addPositionalArgument("File list", QCoreApplication::translate("main", "File list."));
|
||||
parser.addHelpOption();
|
||||
|
||||
parser.process(a);
|
||||
|
||||
QStringList urlStrList = parser.positionalArguments();
|
||||
QList<QUrl> urlList;
|
||||
for (const QString & str : urlStrList) {
|
||||
QUrl url = QUrl::fromLocalFile(str);
|
||||
#ifdef Q_OS_WIN
|
||||
// TODO: remove this workaround when M$ change the "wsl$" hostname.
|
||||
if (Q_UNLIKELY(str.startsWith(R"(\\wsl$\)"))) {
|
||||
url.clear();
|
||||
url.setScheme(QStringLiteral("qtbug-86277"));
|
||||
url.setPath(str);
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
if (url.isValid()) {
|
||||
urlList.append(url);
|
||||
}
|
||||
}
|
||||
|
||||
MainWindow w;
|
||||
w.show();
|
||||
|
||||
if (!urlList.isEmpty()) {
|
||||
w.showUrls(urlList);
|
||||
w.adjustWindowSizeBySceneRect();
|
||||
}
|
||||
|
||||
return a.exec();
|
||||
}
|
696
app/mainwindow.cpp
Normal file
696
app/mainwindow.cpp
Normal file
@ -0,0 +1,696 @@
|
||||
#include "mainwindow.h"
|
||||
|
||||
#include "settings.h"
|
||||
#include "toolbutton.h"
|
||||
#include "bottombuttongroup.h"
|
||||
#include "graphicsview.h"
|
||||
#include "navigatorview.h"
|
||||
#include "graphicsscene.h"
|
||||
#include "settingsdialog.h"
|
||||
#include "aboutdialog.h"
|
||||
#include "metadatamodel.h"
|
||||
#include "metadatadialog.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QMovie>
|
||||
#include <QDebug>
|
||||
#include <QGraphicsTextItem>
|
||||
#include <QApplication>
|
||||
#include <QStyle>
|
||||
#include <QScreen>
|
||||
#include <QMenu>
|
||||
#include <QShortcut>
|
||||
#include <QDir>
|
||||
#include <QCollator>
|
||||
#include <QClipboard>
|
||||
#include <QMimeData>
|
||||
#include <QWindow>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif // _WIN32
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QMainWindow(parent)
|
||||
{
|
||||
if (Settings::instance()->stayOnTop()) {
|
||||
this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinimizeButtonHint | Qt::WindowStaysOnTopHint);
|
||||
} else {
|
||||
this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinimizeButtonHint);
|
||||
}
|
||||
|
||||
this->setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
this->setMinimumSize(350, 330);
|
||||
this->setWindowIcon(QIcon(":/icons/app-icon.svg"));
|
||||
this->setMouseTracking(true);
|
||||
|
||||
m_fadeOutAnimation = new QPropertyAnimation(this, "windowOpacity");
|
||||
m_fadeOutAnimation->setDuration(300);
|
||||
m_fadeOutAnimation->setStartValue(1);
|
||||
m_fadeOutAnimation->setEndValue(0);
|
||||
m_floatUpAnimation = new QPropertyAnimation(this, "geometry");
|
||||
m_floatUpAnimation->setDuration(300);
|
||||
m_floatUpAnimation->setEasingCurve(QEasingCurve::OutCirc);
|
||||
m_exitAnimationGroup = new QParallelAnimationGroup;
|
||||
m_exitAnimationGroup->addAnimation(m_fadeOutAnimation);
|
||||
m_exitAnimationGroup->addAnimation(m_floatUpAnimation);
|
||||
connect(m_exitAnimationGroup, &QParallelAnimationGroup::finished,
|
||||
this, &QMainWindow::close);
|
||||
|
||||
GraphicsScene * scene = new GraphicsScene(this);
|
||||
|
||||
m_graphicsView = new GraphicsView(this);
|
||||
m_graphicsView->setScene(scene);
|
||||
this->setCentralWidget(m_graphicsView);
|
||||
|
||||
m_gv = new NavigatorView(this);
|
||||
m_gv->setFixedSize(220, 160);
|
||||
m_gv->setScene(scene);
|
||||
m_gv->setMainView(m_graphicsView);
|
||||
m_gv->fitInView(m_gv->sceneRect(), Qt::KeepAspectRatio);
|
||||
|
||||
connect(m_graphicsView, &GraphicsView::navigatorViewRequired,
|
||||
this, [ = ](bool required, qreal angle){
|
||||
m_gv->resetTransform();
|
||||
m_gv->rotate(angle);
|
||||
m_gv->fitInView(m_gv->sceneRect(), Qt::KeepAspectRatio);
|
||||
m_gv->setVisible(required);
|
||||
m_gv->updateMainViewportRegion();
|
||||
});
|
||||
|
||||
connect(m_graphicsView, &GraphicsView::viewportRectChanged,
|
||||
m_gv, &NavigatorView::updateMainViewportRegion);
|
||||
|
||||
connect(m_graphicsView, &GraphicsView::requestGallery,
|
||||
this, &MainWindow::loadGalleryBySingleLocalFile);
|
||||
|
||||
m_closeButton = new ToolButton(true, m_graphicsView);
|
||||
m_closeButton->setIcon(QIcon(":/icons/window-close"));
|
||||
m_closeButton->setIconSize(QSize(50, 50));
|
||||
|
||||
connect(m_closeButton, &QAbstractButton::clicked,
|
||||
this, &MainWindow::closeWindow);
|
||||
|
||||
m_prevButton = new ToolButton(false, m_graphicsView);
|
||||
m_prevButton->setIcon(QIcon(":/icons/go-previous"));
|
||||
m_prevButton->setIconSize(QSize(75, 75));
|
||||
m_prevButton->setVisible(false);
|
||||
m_prevButton->setOpacity(0, false);
|
||||
m_nextButton = new ToolButton(false, m_graphicsView);
|
||||
m_nextButton->setIcon(QIcon(":/icons/go-next"));
|
||||
m_nextButton->setIconSize(QSize(75, 75));
|
||||
m_nextButton->setVisible(false);
|
||||
m_nextButton->setOpacity(0, false);
|
||||
|
||||
connect(m_prevButton, &QAbstractButton::clicked,
|
||||
this, &MainWindow::galleryPrev);
|
||||
connect(m_nextButton, &QAbstractButton::clicked,
|
||||
this, &MainWindow::galleryNext);
|
||||
|
||||
m_bottomButtonGroup = new BottomButtonGroup(this);
|
||||
|
||||
connect(m_bottomButtonGroup, &BottomButtonGroup::resetToOriginalBtnClicked,
|
||||
this, [ = ](){ m_graphicsView->resetScale(); });
|
||||
connect(m_bottomButtonGroup, &BottomButtonGroup::toggleWindowMaximum,
|
||||
this, &MainWindow::toggleMaximize);
|
||||
connect(m_bottomButtonGroup, &BottomButtonGroup::zoomInBtnClicked,
|
||||
this, [ = ](){ m_graphicsView->zoomView(1.25); });
|
||||
connect(m_bottomButtonGroup, &BottomButtonGroup::zoomOutBtnClicked,
|
||||
this, [ = ](){ m_graphicsView->zoomView(0.75); });
|
||||
connect(m_bottomButtonGroup, &BottomButtonGroup::toggleCheckerboardBtnClicked,
|
||||
this, [ = ](){ m_graphicsView->toggleCheckerboard(); });
|
||||
connect(m_bottomButtonGroup, &BottomButtonGroup::rotateRightBtnClicked,
|
||||
this, [ = ](){
|
||||
m_graphicsView->resetScale();
|
||||
m_graphicsView->rotateView(90);
|
||||
m_graphicsView->checkAndDoFitInView();
|
||||
m_gv->setVisible(false);
|
||||
});
|
||||
|
||||
m_bottomButtonGroup->setOpacity(0, false);
|
||||
m_gv->setOpacity(0, false);
|
||||
m_closeButton->setOpacity(0, false);
|
||||
|
||||
connect(this, &MainWindow::galleryLoaded, this, [this]() {
|
||||
m_prevButton->setVisible(isGalleryAvailable());
|
||||
m_nextButton->setVisible(isGalleryAvailable());
|
||||
});
|
||||
|
||||
QShortcut * quitAppShorucut = new QShortcut(QKeySequence(Qt::Key_Space), this);
|
||||
connect(quitAppShorucut, &QShortcut::activated,
|
||||
std::bind(&MainWindow::quitAppAction, this, false));
|
||||
|
||||
QShortcut * quitAppShorucut2 = new QShortcut(QKeySequence(Qt::Key_Escape), this);
|
||||
connect(quitAppShorucut2, &QShortcut::activated,
|
||||
std::bind(&MainWindow::quitAppAction, this, false));
|
||||
|
||||
QShortcut * prevPictureShorucut = new QShortcut(QKeySequence(Qt::Key_PageUp), this);
|
||||
connect(prevPictureShorucut, &QShortcut::activated,
|
||||
this, &MainWindow::galleryPrev);
|
||||
|
||||
QShortcut * nextPictureShorucut = new QShortcut(QKeySequence(Qt::Key_PageDown), this);
|
||||
connect(nextPictureShorucut, &QShortcut::activated,
|
||||
this, &MainWindow::galleryNext);
|
||||
|
||||
QShortcut * fullscreenShorucut = new QShortcut(QKeySequence(QKeySequence::FullScreen), this);
|
||||
connect(fullscreenShorucut, &QShortcut::activated,
|
||||
this, &MainWindow::toggleFullscreen);
|
||||
|
||||
centerWindow();
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::showUrls(const QList<QUrl> &urls)
|
||||
{
|
||||
if (!urls.isEmpty()) {
|
||||
if (urls.count() == 1) {
|
||||
m_graphicsView->showFileFromUrl(urls.first(), true);
|
||||
} else {
|
||||
m_graphicsView->showFileFromUrl(urls.first(), false);
|
||||
m_files = urls;
|
||||
m_currentFileIndex = 0;
|
||||
}
|
||||
} else {
|
||||
m_graphicsView->showText(tr("File url list is empty"));
|
||||
return;
|
||||
}
|
||||
|
||||
m_gv->fitInView(m_gv->sceneRect(), Qt::KeepAspectRatio);
|
||||
}
|
||||
|
||||
void MainWindow::adjustWindowSizeBySceneRect()
|
||||
{
|
||||
QSize sceneSize = m_graphicsView->sceneRect().toRect().size();
|
||||
QSize sceneSizeWithMargins = sceneSize + QSize(130, 125);
|
||||
|
||||
if (m_graphicsView->scaleFactor() < 1 || size().expandedTo(sceneSizeWithMargins) != size()) {
|
||||
// if it scaled down by the resize policy:
|
||||
QSize screenSize = qApp->screenAt(QCursor::pos())->availableSize();
|
||||
if (screenSize.expandedTo(sceneSize) == screenSize) {
|
||||
// we can show the picture by increase the window size.
|
||||
QSize finalSize = (screenSize.expandedTo(sceneSizeWithMargins) == screenSize) ?
|
||||
sceneSizeWithMargins : screenSize;
|
||||
// We have a very reasonable sizeHint() value ;P
|
||||
this->resize(finalSize.expandedTo(this->sizeHint()));
|
||||
|
||||
// We're sure the window can display the whole thing with 1:1 scale.
|
||||
// The old window size may cause fitInView call from resize() and the
|
||||
// above resize() call won't reset the scale back to 1:1, so we
|
||||
// just call resetScale() here to ensure the thing is no longer scaled.
|
||||
m_graphicsView->resetScale();
|
||||
centerWindow();
|
||||
} else {
|
||||
// toggle maximum
|
||||
showMaximized();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// can be empty if it is NOT from a local file.
|
||||
QUrl MainWindow::currentImageFileUrl() const
|
||||
{
|
||||
if (m_currentFileIndex != -1) {
|
||||
return m_files.value(m_currentFileIndex);
|
||||
}
|
||||
|
||||
return QUrl();
|
||||
}
|
||||
|
||||
void MainWindow::clearGallery()
|
||||
{
|
||||
m_currentFileIndex = -1;
|
||||
m_files.clear();
|
||||
}
|
||||
|
||||
void MainWindow::loadGalleryBySingleLocalFile(const QString &path)
|
||||
{
|
||||
QFileInfo info(path);
|
||||
QDir dir(info.path());
|
||||
QString currentFileName = info.fileName();
|
||||
QStringList entryList = dir.entryList({"*.jpg", "*.jpeg", "*.jfif", "*.png", "*.gif", "*.svg", "*.bmp"},
|
||||
QDir::Files | QDir::NoSymLinks, QDir::NoSort);
|
||||
|
||||
QCollator collator;
|
||||
collator.setNumericMode(true);
|
||||
|
||||
std::sort(entryList.begin(), entryList.end(), collator);
|
||||
|
||||
clearGallery();
|
||||
|
||||
for (int i = 0; i < entryList.count(); i++) {
|
||||
const QString & fileName = entryList.at(i);
|
||||
const QString & oneEntry = dir.absoluteFilePath(fileName);
|
||||
QUrl url = QUrl::fromLocalFile(oneEntry);
|
||||
#ifdef Q_OS_WIN
|
||||
// TODO: remove this workaround when M$ change the "wsl$" hostname.
|
||||
// Qt will convert path "\\wsl$\" to "//wsl$/"...
|
||||
if (Q_UNLIKELY(oneEntry.startsWith(R"(//wsl$/)"))) {
|
||||
url.clear();
|
||||
url.setScheme(QStringLiteral("qtbug-86277"));
|
||||
url.setPath(oneEntry);
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
m_files.append(url);
|
||||
if (fileName == currentFileName) {
|
||||
m_currentFileIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
emit galleryLoaded();
|
||||
}
|
||||
|
||||
void MainWindow::galleryPrev()
|
||||
{
|
||||
int count = m_files.count();
|
||||
if (!isGalleryAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_currentFileIndex = m_currentFileIndex - 1 < 0 ? count - 1 : m_currentFileIndex - 1;
|
||||
|
||||
m_graphicsView->showFileFromUrl(m_files.at(m_currentFileIndex), false);
|
||||
}
|
||||
|
||||
void MainWindow::galleryNext()
|
||||
{
|
||||
int count = m_files.count();
|
||||
if (!isGalleryAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_currentFileIndex = m_currentFileIndex + 1 == count ? 0 : m_currentFileIndex + 1;
|
||||
|
||||
m_graphicsView->showFileFromUrl(m_files.at(m_currentFileIndex), false);
|
||||
}
|
||||
|
||||
bool MainWindow::isGalleryAvailable()
|
||||
{
|
||||
if (m_currentFileIndex < 0 || m_files.isEmpty() || m_currentFileIndex >= m_files.count()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWindow::showEvent(QShowEvent *event)
|
||||
{
|
||||
updateWidgetsPosition();
|
||||
|
||||
return QMainWindow::showEvent(event);
|
||||
}
|
||||
|
||||
void MainWindow::enterEvent(QEvent *event)
|
||||
{
|
||||
m_bottomButtonGroup->setOpacity(1);
|
||||
m_gv->setOpacity(1);
|
||||
|
||||
m_closeButton->setOpacity(1);
|
||||
m_prevButton->setOpacity(1);
|
||||
m_nextButton->setOpacity(1);
|
||||
|
||||
return QMainWindow::enterEvent(event);
|
||||
}
|
||||
|
||||
void MainWindow::leaveEvent(QEvent *event)
|
||||
{
|
||||
m_bottomButtonGroup->setOpacity(0);
|
||||
m_gv->setOpacity(0);
|
||||
|
||||
m_closeButton->setOpacity(0);
|
||||
m_prevButton->setOpacity(0);
|
||||
m_nextButton->setOpacity(0);
|
||||
|
||||
return QMainWindow::leaveEvent(event);
|
||||
}
|
||||
|
||||
void MainWindow::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->buttons() & Qt::LeftButton && !isMaximized()) {
|
||||
m_clickedOnWindow = true;
|
||||
m_oldMousePos = event->pos();
|
||||
// qDebug() << m_oldMousePos << m_graphicsView->transform().m11()
|
||||
// << m_graphicsView->transform().m22() << m_graphicsView->matrix().m12();
|
||||
event->accept();
|
||||
}
|
||||
|
||||
return QMainWindow::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void MainWindow::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->buttons() & Qt::LeftButton && m_clickedOnWindow && !isMaximized()) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
if (!window()->windowHandle()->startSystemMove()) {
|
||||
move(event->globalPos() - m_oldMousePos);
|
||||
}
|
||||
#else
|
||||
move(event->globalPos() - m_oldMousePos);
|
||||
#endif // QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
event->accept();
|
||||
}
|
||||
|
||||
return QMainWindow::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void MainWindow::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
m_clickedOnWindow = false;
|
||||
|
||||
return QMainWindow::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
void MainWindow::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
switch (Settings::instance()->doubleClickBehavior()) {
|
||||
case ActionCloseWindow:
|
||||
quitAppAction();
|
||||
event->accept();
|
||||
break;
|
||||
case ActionMaximizeWindow:
|
||||
toggleMaximize();
|
||||
event->accept();
|
||||
break;
|
||||
case ActionDoNothing:
|
||||
break;
|
||||
}
|
||||
|
||||
// blumia: don't call parent constructor here, seems it will cause mouse move
|
||||
// event get called even if we set event->accept();
|
||||
// return QMainWindow::mouseDoubleClickEvent(event);
|
||||
}
|
||||
|
||||
void MainWindow::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
QPoint numDegrees = event->angleDelta() / 8;
|
||||
bool needZoom = false, zoomIn = false;
|
||||
|
||||
// NOTE: Only checking angleDelta since the QWheelEvent::pixelDelta() doc says
|
||||
// pixelDelta() value is driver specific and unreliable on X11...
|
||||
// We are not scrolling the canvas, just zoom in or out, so it probably
|
||||
// doesn't matter here.
|
||||
if (!numDegrees.isNull() && numDegrees.y() != 0) {
|
||||
needZoom = true;
|
||||
zoomIn = numDegrees.y() > 0;
|
||||
}
|
||||
|
||||
if (needZoom) {
|
||||
m_graphicsView->zoomView(zoomIn ? 1.25 : 0.8);
|
||||
event->accept();
|
||||
} else {
|
||||
QMainWindow::wheelEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
updateWidgetsPosition();
|
||||
|
||||
return QMainWindow::resizeEvent(event);
|
||||
}
|
||||
|
||||
void MainWindow::contextMenuEvent(QContextMenuEvent *event)
|
||||
{
|
||||
QMenu * menu = new QMenu;
|
||||
QMenu * copyMenu = new QMenu(tr("&Copy"));
|
||||
QUrl currentFileUrl = currentImageFileUrl();
|
||||
QImage clipboardImage;
|
||||
QUrl clipboardFileUrl;
|
||||
|
||||
const QMimeData * clipboardData = QApplication::clipboard()->mimeData();
|
||||
if (clipboardData->hasImage()) {
|
||||
QVariant imageVariant(clipboardData->imageData());
|
||||
if (imageVariant.isValid()) {
|
||||
clipboardImage = qvariant_cast<QImage>(imageVariant);
|
||||
}
|
||||
} else if (clipboardData->hasText()) {
|
||||
QString clipboardText(clipboardData->text());
|
||||
if (clipboardText.startsWith("PICTURE:")) {
|
||||
QString maybeFilename(clipboardText.mid(8));
|
||||
if (QFile::exists(maybeFilename)) {
|
||||
clipboardFileUrl = QUrl::fromLocalFile(maybeFilename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QAction * copyPixmap = new QAction(tr("Copy P&ixmap"));
|
||||
connect(copyPixmap, &QAction::triggered, this, [ = ](){
|
||||
QClipboard *cb = QApplication::clipboard();
|
||||
cb->setPixmap(m_graphicsView->scene()->renderToPixmap());
|
||||
});
|
||||
QAction * copyFilePath = new QAction(tr("Copy &File Path"));
|
||||
connect(copyFilePath, &QAction::triggered, this, [ = ](){
|
||||
QClipboard *cb = QApplication::clipboard();
|
||||
cb->setText(currentFileUrl.toLocalFile());
|
||||
});
|
||||
copyMenu->addAction(copyPixmap);
|
||||
if (currentFileUrl.isValid()) {
|
||||
copyMenu->addAction(copyFilePath);
|
||||
}
|
||||
|
||||
QAction * pasteImage = new QAction(tr("&Paste Image"));
|
||||
connect(pasteImage, &QAction::triggered, this, [ = ](){
|
||||
clearGallery();
|
||||
m_graphicsView->showImage(clipboardImage);
|
||||
});
|
||||
|
||||
QAction * pasteImageFile = new QAction(tr("&Paste Image File"));
|
||||
connect(pasteImageFile, &QAction::triggered, this, [ = ](){
|
||||
m_graphicsView->showFileFromUrl(clipboardFileUrl, true);
|
||||
});
|
||||
|
||||
QAction * stayOnTopMode = new QAction(tr("Stay on top"));
|
||||
connect(stayOnTopMode, &QAction::triggered, this, [ = ](){
|
||||
toggleStayOnTop();
|
||||
});
|
||||
stayOnTopMode->setCheckable(true);
|
||||
stayOnTopMode->setChecked(stayOnTop());
|
||||
|
||||
QAction * protectedMode = new QAction(tr("Protected mode"));
|
||||
connect(protectedMode, &QAction::triggered, this, [ = ](){
|
||||
toggleProtectedMode();
|
||||
});
|
||||
protectedMode->setCheckable(true);
|
||||
protectedMode->setChecked(m_protectedMode);
|
||||
|
||||
QAction * toggleSettings = new QAction(tr("Configure..."));
|
||||
connect(toggleSettings, &QAction::triggered, this, [ = ](){
|
||||
SettingsDialog * sd = new SettingsDialog(this);
|
||||
sd->exec();
|
||||
sd->deleteLater();
|
||||
});
|
||||
|
||||
QAction * helpAction = new QAction(tr("Help"));
|
||||
connect(helpAction, &QAction::triggered, this, [ = ](){
|
||||
AboutDialog * ad = new AboutDialog(this);
|
||||
ad->exec();
|
||||
ad->deleteLater();
|
||||
});
|
||||
|
||||
QAction * propertiesAction = new QAction(tr("Properties"));
|
||||
connect(propertiesAction, &QAction::triggered, this, [ = ](){
|
||||
MetadataModel * md = new MetadataModel();
|
||||
md->setFile(currentFileUrl.toLocalFile());
|
||||
|
||||
MetadataDialog * ad = new MetadataDialog(this);
|
||||
ad->setMetadataModel(md);
|
||||
ad->exec();
|
||||
ad->deleteLater();
|
||||
});
|
||||
|
||||
if (copyMenu->actions().count() == 1) {
|
||||
menu->addActions(copyMenu->actions());
|
||||
} else {
|
||||
menu->addMenu(copyMenu);
|
||||
}
|
||||
if (!clipboardImage.isNull()) {
|
||||
menu->addAction(pasteImage);
|
||||
} else if (clipboardFileUrl.isValid()) {
|
||||
menu->addAction(pasteImageFile);
|
||||
}
|
||||
menu->addSeparator();
|
||||
menu->addAction(stayOnTopMode);
|
||||
menu->addAction(protectedMode);
|
||||
menu->addSeparator();
|
||||
menu->addAction(toggleSettings);
|
||||
menu->addAction(helpAction);
|
||||
if (currentFileUrl.isValid()) {
|
||||
menu->addSeparator();
|
||||
menu->addAction(propertiesAction);
|
||||
}
|
||||
menu->exec(mapToGlobal(event->pos()));
|
||||
menu->deleteLater();
|
||||
|
||||
return QMainWindow::contextMenuEvent(event);
|
||||
}
|
||||
|
||||
bool MainWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// https://stackoverflow.com/questions/43505580/qt-windows-resizable-frameless-window
|
||||
// Too lazy to do this now.. just stackoverflow it and did a copy and paste..
|
||||
Q_UNUSED(eventType)
|
||||
MSG* msg = static_cast<MSG*>(message);
|
||||
|
||||
if (msg->message == WM_NCHITTEST) {
|
||||
if (isMaximized()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = 0;
|
||||
const LONG borderWidth = 8;
|
||||
RECT winrect;
|
||||
GetWindowRect(reinterpret_cast<HWND>(winId()), &winrect);
|
||||
|
||||
// must be short to correctly work with multiple monitors (negative coordinates)
|
||||
short x = msg->lParam & 0x0000FFFF;
|
||||
short y = (msg->lParam & 0xFFFF0000) >> 16;
|
||||
|
||||
bool resizeWidth = minimumWidth() != maximumWidth();
|
||||
bool resizeHeight = minimumHeight() != maximumHeight();
|
||||
if (resizeWidth) {
|
||||
//left border
|
||||
if (x >= winrect.left && x < winrect.left + borderWidth) {
|
||||
*result = HTLEFT;
|
||||
}
|
||||
//right border
|
||||
if (x < winrect.right && x >= winrect.right - borderWidth) {
|
||||
*result = HTRIGHT;
|
||||
}
|
||||
}
|
||||
if (resizeHeight) {
|
||||
//bottom border
|
||||
if (y < winrect.bottom && y >= winrect.bottom - borderWidth) {
|
||||
*result = HTBOTTOM;
|
||||
}
|
||||
//top border
|
||||
if (y >= winrect.top && y < winrect.top + borderWidth) {
|
||||
*result = HTTOP;
|
||||
}
|
||||
}
|
||||
if (resizeWidth && resizeHeight) {
|
||||
//bottom left corner
|
||||
if (x >= winrect.left && x < winrect.left + borderWidth &&
|
||||
y < winrect.bottom && y >= winrect.bottom - borderWidth)
|
||||
{
|
||||
*result = HTBOTTOMLEFT;
|
||||
}
|
||||
//bottom right corner
|
||||
if (x < winrect.right && x >= winrect.right - borderWidth &&
|
||||
y < winrect.bottom && y >= winrect.bottom - borderWidth)
|
||||
{
|
||||
*result = HTBOTTOMRIGHT;
|
||||
}
|
||||
//top left corner
|
||||
if (x >= winrect.left && x < winrect.left + borderWidth &&
|
||||
y >= winrect.top && y < winrect.top + borderWidth)
|
||||
{
|
||||
*result = HTTOPLEFT;
|
||||
}
|
||||
//top right corner
|
||||
if (x < winrect.right && x >= winrect.right - borderWidth &&
|
||||
y >= winrect.top && y < winrect.top + borderWidth)
|
||||
{
|
||||
*result = HTTOPRIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
if (*result != 0)
|
||||
return true;
|
||||
|
||||
QWidget *action = QApplication::widgetAt(QCursor::pos());
|
||||
if (action == this) {
|
||||
*result = HTCAPTION;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
#else
|
||||
return QMainWindow::nativeEvent(eventType, message, result);
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
QSize MainWindow::sizeHint() const
|
||||
{
|
||||
return QSize(710, 530);
|
||||
}
|
||||
|
||||
void MainWindow::centerWindow()
|
||||
{
|
||||
this->setGeometry(
|
||||
QStyle::alignedRect(
|
||||
Qt::LeftToRight,
|
||||
Qt::AlignCenter,
|
||||
this->size(),
|
||||
qApp->screenAt(QCursor::pos())->geometry()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void MainWindow::closeWindow()
|
||||
{
|
||||
QRect windowRect(this->geometry());
|
||||
m_floatUpAnimation->setStartValue(windowRect);
|
||||
m_floatUpAnimation->setEndValue(windowRect.adjusted(0, -80, 0, 0));
|
||||
m_floatUpAnimation->setStartValue(QRect(this->geometry().x(), this->geometry().y(), this->geometry().width(), this->geometry().height()));
|
||||
m_floatUpAnimation->setEndValue(QRect(this->geometry().x(), this->geometry().y()-80, this->geometry().width(), this->geometry().height()));
|
||||
m_exitAnimationGroup->start();
|
||||
}
|
||||
|
||||
void MainWindow::updateWidgetsPosition()
|
||||
{
|
||||
m_closeButton->move(width() - m_closeButton->width(), 0);
|
||||
m_prevButton->move(25, (height() - m_prevButton->height()) / 2);
|
||||
m_nextButton->move(width() - m_nextButton->width() - 25,
|
||||
(height() - m_prevButton->height()) / 2);
|
||||
m_bottomButtonGroup->move((width() - m_bottomButtonGroup->width()) / 2,
|
||||
height() - m_bottomButtonGroup->height());
|
||||
m_gv->move(width() - m_gv->width(), height() - m_gv->height());
|
||||
}
|
||||
|
||||
void MainWindow::toggleProtectedMode()
|
||||
{
|
||||
m_protectedMode = !m_protectedMode;
|
||||
m_closeButton->setVisible(!m_protectedMode);
|
||||
m_prevButton->setVisible(!m_protectedMode);
|
||||
m_nextButton->setVisible(!m_protectedMode);
|
||||
}
|
||||
|
||||
void MainWindow::toggleStayOnTop()
|
||||
{
|
||||
setWindowFlag(Qt::WindowStaysOnTopHint, !stayOnTop());
|
||||
show();
|
||||
}
|
||||
|
||||
bool MainWindow::stayOnTop()
|
||||
{
|
||||
return windowFlags().testFlag(Qt::WindowStaysOnTopHint);
|
||||
}
|
||||
|
||||
void MainWindow::quitAppAction(bool force)
|
||||
{
|
||||
if (!m_protectedMode || force) {
|
||||
closeWindow();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::toggleFullscreen()
|
||||
{
|
||||
if (isFullScreen()) {
|
||||
showNormal();
|
||||
} else {
|
||||
showFullScreen();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::toggleMaximize()
|
||||
{
|
||||
if (isMaximized()) {
|
||||
showNormal();
|
||||
} else {
|
||||
showMaximized();
|
||||
}
|
||||
}
|
83
app/mainwindow.h
Normal file
83
app/mainwindow.h
Normal file
@ -0,0 +1,83 @@
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QParallelAnimationGroup>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QPushButton>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QGraphicsOpacityEffect;
|
||||
class QGraphicsView;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class ToolButton;
|
||||
class GraphicsView;
|
||||
class NavigatorView;
|
||||
class BottomButtonGroup;
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow() override;
|
||||
|
||||
void showUrls(const QList<QUrl> &urls);
|
||||
void adjustWindowSizeBySceneRect();
|
||||
QUrl currentImageFileUrl() const;
|
||||
|
||||
void clearGallery();
|
||||
void loadGalleryBySingleLocalFile(const QString &path);
|
||||
void galleryPrev();
|
||||
void galleryNext();
|
||||
bool isGalleryAvailable();
|
||||
|
||||
signals:
|
||||
void galleryLoaded();
|
||||
|
||||
protected slots:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void enterEvent(QEvent *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;
|
||||
|
||||
bool nativeEvent(const QByteArray& eventType, void* message, long* result) override;
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
void centerWindow();
|
||||
void closeWindow();
|
||||
void updateWidgetsPosition();
|
||||
void toggleProtectedMode();
|
||||
void toggleStayOnTop();
|
||||
bool stayOnTop();
|
||||
void quitAppAction(bool force = false);
|
||||
void toggleFullscreen();
|
||||
void toggleMaximize();
|
||||
|
||||
private:
|
||||
QPoint m_oldMousePos;
|
||||
QPropertyAnimation *m_fadeOutAnimation;
|
||||
QPropertyAnimation *m_floatUpAnimation;
|
||||
QParallelAnimationGroup *m_exitAnimationGroup;
|
||||
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;
|
||||
|
||||
QList<QUrl> m_files;
|
||||
int m_currentFileIndex = -1;
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
101
app/metadatadialog.cpp
Normal file
101
app/metadatadialog.cpp
Normal file
@ -0,0 +1,101 @@
|
||||
#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()) {
|
||||
for (int row = start; row <= end; ++row) {
|
||||
setupSection(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
26
app/metadatadialog.h
Normal file
26
app/metadatadialog.h
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef METADATADIALOG_H
|
||||
#define METADATADIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QTreeView;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class MetadataModel;
|
||||
class MetadataDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MetadataDialog(QWidget * parent);
|
||||
~MetadataDialog() override;
|
||||
|
||||
void setMetadataModel(MetadataModel * model);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
private:
|
||||
QTreeView * m_treeView = nullptr;
|
||||
};
|
||||
|
||||
#endif // METADATADIALOG_H
|
205
app/metadatamodel.cpp
Normal file
205
app/metadatamodel.cpp
Normal file
@ -0,0 +1,205 @@
|
||||
#include "metadatamodel.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDateTime>
|
||||
#include <QFileInfo>
|
||||
#include <QImageReader>
|
||||
|
||||
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());
|
||||
|
||||
#if 0
|
||||
appendSection(QStringLiteral("Description"), tr("Description", "Section name."));
|
||||
appendSection(QStringLiteral("Origin"), tr("Origin", "Section name."));
|
||||
#endif // 0
|
||||
appendSection(QStringLiteral("Image"), tr("Image", "Section name."));
|
||||
#if 0
|
||||
appendSection(QStringLiteral("Camera"), tr("Camera", "Section name."));
|
||||
appendSection(QStringLiteral("AdvancedPhoto"), tr("Advanced photo", "Section name."));
|
||||
appendSection(QStringLiteral("GPS"), tr("GPS", "Section name."));
|
||||
#endif // 0
|
||||
appendSection(QStringLiteral("File"), tr("File", "Section name."));
|
||||
|
||||
appendProperty(QStringLiteral("Image"), QStringLiteral("Image.Dimensions"),
|
||||
tr("Dimensions"), imageDimensionsString);
|
||||
appendProperty(QStringLiteral("Image"), QStringLiteral("Image.SizeRatio"),
|
||||
tr("Aspect Ratio"), imageRatioString);
|
||||
|
||||
appendProperty(QStringLiteral("File"), QStringLiteral("File.Name"),
|
||||
tr("Name"), fileInfo.fileName());
|
||||
appendProperty(QStringLiteral("File"), QStringLiteral("File.ItemType"),
|
||||
tr("Item type"), itemTypeString);
|
||||
appendProperty(QStringLiteral("File"), QStringLiteral("File.Path"),
|
||||
tr("Folder path"), fileInfo.path());
|
||||
appendProperty(QStringLiteral("File"), QStringLiteral("File.Size"),
|
||||
tr("Size"), sizeString);
|
||||
appendProperty(QStringLiteral("File"), QStringLiteral("File.CreatedTime"),
|
||||
tr("Date Created"), birthTimeString);
|
||||
appendProperty(QStringLiteral("File"), QStringLiteral("File.LastModified"),
|
||||
tr("Date Modified"), lastModifiedTimeString);
|
||||
}
|
||||
|
||||
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, const QString §ionDisplayName)
|
||||
{
|
||||
if (m_sections.contains(sectionKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_sections.append(sectionKey);
|
||||
m_sectionProperties[sectionKey] = qMakePair<QString, QList<QString> >(sectionDisplayName, {});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetadataModel::appendProperty(const QString §ionKey, const QString &propertyKey, const QString &propertyDisplayName, const QString &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, propertyValue);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetadataModel::updateProperty(const QString &propertyKey, const QString &propertyValue)
|
||||
{
|
||||
if (m_properties.contains(propertyKey)) {
|
||||
m_properties[propertyKey].second = propertyValue;
|
||||
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");
|
||||
}
|
43
app/metadatamodel.h
Normal file
43
app/metadatamodel.h
Normal file
@ -0,0 +1,43 @@
|
||||
#ifndef METADATAMODEL_H
|
||||
#define METADATAMODEL_H
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
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, const QString & sectionDisplayName);
|
||||
bool appendProperty(const QString & sectionKey, const QString & propertyKey,
|
||||
const QString & propertyDisplayName, const QString & propertyValue = QString());
|
||||
bool updateProperty(const QString & propertyKey, const QString & propertyValue);
|
||||
|
||||
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
|
80
app/navigatorview.cpp
Normal file
80
app/navigatorview.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
#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()));
|
||||
}
|
||||
}
|
||||
|
||||
void NavigatorView::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
m_mouseDown = true;
|
||||
|
||||
if (m_mainView) {
|
||||
m_mainView->centerOn(mapToScene(event->pos()));
|
||||
update();
|
||||
}
|
||||
|
||||
return QGraphicsView::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void NavigatorView::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (m_mouseDown && m_mainView) {
|
||||
m_mainView->centerOn(mapToScene(event->pos()));
|
||||
update();
|
||||
}
|
||||
|
||||
return QGraphicsView::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void NavigatorView::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
m_mouseDown = false;
|
||||
|
||||
return QGraphicsView::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
34
app/navigatorview.h
Normal file
34
app/navigatorview.h
Normal file
@ -0,0 +1,34 @@
|
||||
#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
|
27
app/opacityhelper.cpp
Normal file
27
app/opacityhelper.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
#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();
|
||||
}
|
23
app/opacityhelper.h
Normal file
23
app/opacityhelper.h
Normal file
@ -0,0 +1,23 @@
|
||||
#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
|
86
app/settings.cpp
Normal file
86
app/settings.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
#include "settings.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QStandardPaths>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
|
||||
Settings *Settings::m_settings_instance = nullptr;
|
||||
|
||||
Settings *Settings::instance()
|
||||
{
|
||||
if (!m_settings_instance) {
|
||||
m_settings_instance = new Settings;
|
||||
}
|
||||
|
||||
return m_settings_instance;
|
||||
}
|
||||
|
||||
bool Settings::stayOnTop()
|
||||
{
|
||||
return m_qsettings->value("stay_on_top", true).toBool();
|
||||
}
|
||||
|
||||
DoubleClickBehavior Settings::doubleClickBehavior()
|
||||
{
|
||||
QString result = m_qsettings->value("double_click_behavior", "close").toString().toLower();
|
||||
|
||||
return stringToDoubleClickBehavior(result);
|
||||
}
|
||||
|
||||
void Settings::setStayOnTop(bool on)
|
||||
{
|
||||
m_qsettings->setValue("stay_on_top", on);
|
||||
m_qsettings->sync();
|
||||
}
|
||||
|
||||
void Settings::setDoubleClickBehavior(DoubleClickBehavior dcb)
|
||||
{
|
||||
m_qsettings->setValue("double_click_behavior", doubleClickBehaviorToString(dcb));
|
||||
m_qsettings->sync();
|
||||
}
|
||||
|
||||
QString Settings::doubleClickBehaviorToString(DoubleClickBehavior dcb)
|
||||
{
|
||||
static QMap<DoubleClickBehavior, QString> _map {
|
||||
{ActionCloseWindow, "close"},
|
||||
{ActionMaximizeWindow, "maximize"},
|
||||
{ActionDoNothing, "ignore"}
|
||||
};
|
||||
|
||||
return _map.value(dcb, "close");
|
||||
}
|
||||
|
||||
DoubleClickBehavior Settings::stringToDoubleClickBehavior(QString str)
|
||||
{
|
||||
static QMap<QString, DoubleClickBehavior> _map {
|
||||
{"close", ActionCloseWindow},
|
||||
{"maximize", ActionMaximizeWindow},
|
||||
{"ignore", ActionDoNothing}
|
||||
};
|
||||
|
||||
return _map.value(str, ActionCloseWindow);
|
||||
}
|
||||
|
||||
Settings::Settings()
|
||||
: QObject(qApp)
|
||||
{
|
||||
QString configPath;
|
||||
|
||||
#ifdef FLAG_PORTABLE_MODE_SUPPORT
|
||||
QString portableConfigDirPath = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath("data");
|
||||
QFileInfo portableConfigDirInfo(portableConfigDirPath);
|
||||
if (portableConfigDirInfo.exists() && portableConfigDirInfo.isDir() && portableConfigDirInfo.isWritable()) {
|
||||
// we can use it.
|
||||
configPath = portableConfigDirPath;
|
||||
}
|
||||
#endif // FLAG_PORTABLE_MODE_SUPPORT
|
||||
|
||||
// %LOCALAPPDATA% under Windows.
|
||||
if (configPath.isEmpty()) {
|
||||
configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
|
||||
}
|
||||
|
||||
m_qsettings = new QSettings(QDir(configPath).absoluteFilePath("config.ini"), QSettings::IniFormat, this);
|
||||
}
|
||||
|
41
app/settings.h
Normal file
41
app/settings.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QSettings>
|
||||
|
||||
enum DoubleClickBehavior {
|
||||
ActionDoNothing,
|
||||
ActionCloseWindow,
|
||||
ActionMaximizeWindow,
|
||||
|
||||
ActionStart = ActionDoNothing,
|
||||
ActionEnd = ActionMaximizeWindow
|
||||
};
|
||||
|
||||
class Settings : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static Settings *instance();
|
||||
|
||||
bool stayOnTop();
|
||||
DoubleClickBehavior doubleClickBehavior();
|
||||
|
||||
void setStayOnTop(bool on);
|
||||
void setDoubleClickBehavior(DoubleClickBehavior dcb);
|
||||
|
||||
static QString doubleClickBehaviorToString(DoubleClickBehavior dcb);
|
||||
static DoubleClickBehavior stringToDoubleClickBehavior(QString str);
|
||||
|
||||
private:
|
||||
Settings();
|
||||
|
||||
static Settings *m_settings_instance;
|
||||
|
||||
QSettings *m_qsettings;
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
};
|
||||
|
53
app/settingsdialog.cpp
Normal file
53
app/settingsdialog.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include "settingsdialog.h"
|
||||
|
||||
#include "settings.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QFormLayout>
|
||||
#include <QStringListModel>
|
||||
|
||||
SettingsDialog::SettingsDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, m_stayOnTop(new QCheckBox)
|
||||
, m_doubleClickBehavior(new QComboBox)
|
||||
{
|
||||
this->setWindowTitle(tr("Settings"));
|
||||
|
||||
QFormLayout * settingsForm = new QFormLayout(this);
|
||||
|
||||
static QMap<DoubleClickBehavior, QString> _map {
|
||||
{ ActionDoNothing, tr("Do nothing") },
|
||||
{ ActionCloseWindow, tr("Close the window") },
|
||||
{ ActionMaximizeWindow, tr("Toggle maximize") }
|
||||
};
|
||||
|
||||
QStringList dropDown;
|
||||
for (int dcb = ActionStart; dcb <= ActionEnd; dcb++) {
|
||||
dropDown.append(_map.value(static_cast<DoubleClickBehavior>(dcb)));
|
||||
}
|
||||
|
||||
settingsForm->addRow(tr("Stay on top when start-up"), m_stayOnTop);
|
||||
settingsForm->addRow(tr("Double-click behavior"), m_doubleClickBehavior);
|
||||
|
||||
m_stayOnTop->setChecked(Settings::instance()->stayOnTop());
|
||||
m_doubleClickBehavior->setModel(new QStringListModel(dropDown));
|
||||
DoubleClickBehavior dcb = Settings::instance()->doubleClickBehavior();
|
||||
m_doubleClickBehavior->setCurrentIndex(static_cast<int>(dcb));
|
||||
|
||||
connect(m_stayOnTop, &QCheckBox::stateChanged, this, [ = ](int state){
|
||||
Settings::instance()->setStayOnTop(state == Qt::Checked);
|
||||
});
|
||||
|
||||
connect(m_doubleClickBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
|
||||
Settings::instance()->setDoubleClickBehavior(static_cast<DoubleClickBehavior>(index));
|
||||
});
|
||||
|
||||
this->setMinimumSize(300, 61); // not sure why it complain "Unable to set geometry"
|
||||
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
|
||||
}
|
||||
|
||||
SettingsDialog::~SettingsDialog()
|
||||
{
|
||||
|
||||
}
|
25
app/settingsdialog.h
Normal file
25
app/settingsdialog.h
Normal file
@ -0,0 +1,25 @@
|
||||
#ifndef SETTINGSDIALOG_H
|
||||
#define SETTINGSDIALOG_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDialog>
|
||||
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class SettingsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SettingsDialog(QWidget *parent = nullptr);
|
||||
~SettingsDialog();
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
|
||||
private:
|
||||
QCheckBox * m_stayOnTop = nullptr;
|
||||
QComboBox * m_doubleClickBehavior = nullptr;
|
||||
};
|
||||
|
||||
#endif // SETTINGSDIALOG_H
|
28
app/toolbutton.cpp
Normal file
28
app/toolbutton.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
#include "toolbutton.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::setOpacity(qreal opacity, bool animated)
|
||||
{
|
||||
m_opacityHelper->setOpacity(opacity, animated);
|
||||
}
|
20
app/toolbutton.h
Normal file
20
app/toolbutton.h
Normal file
@ -0,0 +1,20 @@
|
||||
#ifndef TOOLBUTTON_H
|
||||
#define TOOLBUTTON_H
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
class OpacityHelper;
|
||||
class ToolButton : public QPushButton
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ToolButton(bool hoverColor = false, QWidget * parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void setOpacity(qreal opacity, bool animated = true);
|
||||
|
||||
private:
|
||||
OpacityHelper * m_opacityHelper;
|
||||
};
|
||||
|
||||
#endif // TOOLBUTTON_H
|
Reference in New Issue
Block a user