pineapple-pictures/app/graphicsview.cpp

368 lines
11 KiB
C++
Raw Normal View History

// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
//
// SPDX-License-Identifier: MIT
2019-09-28 01:18:08 +08:00
#include "graphicsview.h"
2019-09-29 15:52:35 +08:00
#include "graphicsscene.h"
#include "settings.h"
2019-09-29 15:52:35 +08:00
2019-09-28 01:18:08 +08:00
#include <QDebug>
#include <QMouseEvent>
#include <QScrollBar>
2019-09-29 15:52:35 +08:00
#include <QMimeData>
#include <QImageReader>
#include <QStyleOptionGraphicsItem>
2019-09-28 01:18:08 +08:00
GraphicsView::GraphicsView(QWidget *parent)
: QGraphicsView (parent)
{
setDragMode(QGraphicsView::ScrollHandDrag);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2019-09-29 21:22:20 +08:00
setResizeAnchor(QGraphicsView::AnchorUnderMouse);
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
2019-09-29 22:18:38 +08:00
setStyleSheet("background-color: rgba(0, 0, 0, 220);"
2019-09-28 01:18:08 +08:00
"border-radius: 3px;");
setAcceptDrops(false);
2019-09-30 23:02:44 +08:00
setCheckerboardEnabled(false);
connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, &GraphicsView::viewportRectChanged);
connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &GraphicsView::viewportRectChanged);
2019-09-29 15:52:35 +08:00
}
void GraphicsView::showFileFromPath(const QString &filePath)
{
emit navigatorViewRequired(false, transform());
if (filePath.endsWith(".svg")) {
showSvg(filePath);
} else {
QImageReader imageReader(filePath);
2019-10-13 23:29:41 +08:00
imageReader.setAutoTransform(true);
2019-10-13 22:12:58 +08:00
imageReader.setDecideFormatFromContent(true);
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
imageReader.setAllocationLimit(0);
#endif //QT_VERSION > QT_VERSION_CHECK(6, 0, 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 {
2022-09-30 00:24:04 +08:00
QPixmap && pixmap = QPixmap::fromImageReader(&imageReader);
if (pixmap.isNull()) {
showText(tr("Image data is invalid or currently unsupported"));
} else {
2022-09-30 00:24:04 +08:00
pixmap.setDevicePixelRatio(devicePixelRatioF());
showImage(pixmap);
}
}
}
}
2019-09-29 15:52:35 +08:00
void GraphicsView::showImage(const QPixmap &pixmap)
{
2019-09-29 21:22:20 +08:00
resetTransform();
2019-09-29 15:52:35 +08:00
scene()->showImage(pixmap);
displayScene();
2019-09-29 15:52:35 +08:00
}
void GraphicsView::showImage(const QImage &image)
{
resetTransform();
scene()->showImage(QPixmap::fromImage(image));
displayScene();
}
2019-09-29 15:52:35 +08:00
void GraphicsView::showText(const QString &text)
{
2019-09-30 23:02:44 +08:00
resetTransform();
2019-09-29 15:52:35 +08:00
scene()->showText(text);
displayScene();
2019-09-29 15:52:35 +08:00
}
2019-09-29 23:53:29 +08:00
void GraphicsView::showSvg(const QString &filepath)
{
2019-09-30 23:02:44 +08:00
resetTransform();
2019-09-29 23:53:29 +08:00
scene()->showSvg(filepath);
displayScene();
2019-09-29 23:53:29 +08:00
}
void GraphicsView::showAnimated(const QString &filepath)
2019-09-30 13:11:43 +08:00
{
2019-09-30 23:02:44 +08:00
resetTransform();
scene()->showAnimated(filepath);
displayScene();
2019-09-30 13:11:43 +08:00
}
2019-09-29 15:52:35 +08:00
GraphicsScene *GraphicsView::scene() const
{
return qobject_cast<GraphicsScene*>(QGraphicsView::scene());
}
void GraphicsView::setScene(GraphicsScene *scene)
{
return QGraphicsView::setScene(scene);
2019-09-28 01:18:08 +08:00
}
2019-10-02 16:04:50 +08:00
qreal GraphicsView::scaleFactor() const
{
return QStyleOptionGraphicsItem::levelOfDetailFromTransform(transform());
2019-10-02 16:04:50 +08:00
}
void GraphicsView::resetTransform()
{
if (!m_avoidResetTransform) {
QGraphicsView::resetTransform();
}
2019-10-02 16:04:50 +08:00
}
void GraphicsView::zoomView(qreal scaleFactor)
{
2019-10-04 09:54:13 +08:00
m_enableFitInView = false;
2019-11-02 13:32:13 +08:00
scale(scaleFactor, scaleFactor);
applyTransformationModeByScaleFactor();
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
2019-10-02 16:04:50 +08:00
}
// 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);
}
2021-03-19 23:11:06 +08:00
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());
2021-03-19 23:11:06 +08:00
}
2019-10-02 16:04:50 +08:00
void GraphicsView::resetScale()
{
setTransform(resetScale(transform()));
applyTransformationModeByScaleFactor();
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
2019-10-02 16:04:50 +08:00
}
void GraphicsView::fitInView(const QRectF &rect, Qt::AspectRatioMode aspectRadioMode)
{
QGraphicsView::fitInView(rect, aspectRadioMode);
applyTransformationModeByScaleFactor();
2019-10-02 16:04:50 +08:00
}
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()
2019-10-02 14:31:24 +08:00
{
if (m_avoidResetTransform) {
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
return;
}
if (isSceneBiggerThanView()) {
2019-10-02 14:31:24 +08:00
fitInView(sceneRect(), Qt::KeepAspectRatio);
}
2020-07-27 20:47:54 +08:00
m_enableFitInView = true;
}
bool GraphicsView::isSceneBiggerThanView() const
{
if (!isThingSmallerThanWindowWith(transform())) {
return true;
} else {
return false;
2020-07-27 20:47:54 +08:00
}
2019-10-02 14:31:24 +08:00
}
2021-06-04 13:53:47 +08:00
// 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)
2019-09-30 23:02:44 +08:00
{
setCheckerboardEnabled(!m_checkerboardEnabled, invertCheckerboardColor);
2019-09-30 23:02:44 +08:00
}
2019-09-28 01:18:08 +08:00
void GraphicsView::mousePressEvent(QMouseEvent *event)
{
2019-09-29 21:22:20 +08:00
if (shouldIgnoreMousePressMoveEvent(event)) {
2019-09-28 01:18:08 +08:00
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)
{
2019-09-29 21:22:20 +08:00
if (shouldIgnoreMousePressMoveEvent(event)) {
2019-09-28 01:18:08 +08:00
event->ignore();
}
return QGraphicsView::mouseMoveEvent(event);
}
void GraphicsView::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::ForwardButton || event->button() == Qt::BackButton) {
2019-09-28 01:18:08 +08:00
event->ignore();
} else {
QGraphicsItem *item = itemAt(event->pos());
if (!item) {
event->ignore();
}
2019-09-28 01:18:08 +08:00
}
return QGraphicsView::mouseReleaseEvent(event);
}
void GraphicsView::wheelEvent(QWheelEvent *event)
{
2019-10-04 09:54:13 +08:00
event->ignore();
// blumia: no need for calling parent method.
2019-09-28 01:18:08 +08:00
}
2019-09-29 15:52:35 +08:00
2019-09-29 21:22:20 +08:00
void GraphicsView::resizeEvent(QResizeEvent *event)
{
if (m_enableFitInView) {
bool originalSizeSmallerThanWindow = isThingSmallerThanWindowWith(resetScale(transform()));
if (originalSizeSmallerThanWindow && scaleFactor() >= 1) {
2019-09-29 21:22:20 +08:00
// 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();
2019-09-29 21:22:20 +08:00
} else {
fitInView(sceneRect(), Qt::KeepAspectRatio);
}
2019-10-04 21:34:20 +08:00
} else {
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
2019-09-29 21:22:20 +08:00
}
return QGraphicsView::resizeEvent(event);
}
bool GraphicsView::isThingSmallerThanWindowWith(const QTransform &transform) const
2019-10-02 16:04:50 +08:00
{
2019-09-29 21:22:20 +08:00
return rect().size().expandedTo(transform.mapRect(sceneRect()).size().toSize())
== rect().size();
}
bool GraphicsView::shouldIgnoreMousePressMoveEvent(const QMouseEvent *event) const
{
2019-10-02 14:31:24 +08:00
if (event->buttons() == Qt::NoButton) {
2019-09-29 21:22:20 +08:00
return true;
}
QGraphicsItem *item = itemAt(event->pos());
if (!item) {
return true;
}
2019-10-02 14:31:24 +08:00
if (isThingSmallerThanWindowWith(transform())) {
return true;
2019-09-30 23:02:44 +08:00
}
2019-10-02 14:31:24 +08:00
return false;
2019-09-30 23:02:44 +08:00
}
void GraphicsView::setCheckerboardEnabled(bool enabled, bool invertColor)
2019-09-30 23:02:44 +08:00
{
m_checkerboardEnabled = enabled;
bool isLightCheckerboard = Settings::instance()->useLightCheckerboard() ^ invertColor;
2019-09-30 23:02:44 +08:00
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));
2019-09-30 23:02:44 +08:00
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);
2019-09-30 23:02:44 +08:00
tilePainter.end();
setBackgroundBrush(tilePixmap);
} else {
setBackgroundBrush(Qt::transparent);
}
}
2019-10-02 16:04:50 +08:00
void GraphicsView::applyTransformationModeByScaleFactor()
{
if (this->scaleFactor() < 1) {
scene()->trySetTransformationMode(Qt::SmoothTransformation, this->scaleFactor());
} else {
scene()->trySetTransformationMode(Qt::FastTransformation, this->scaleFactor());
}
}