2019-09-28 01:18:08 +08:00
|
|
|
#include "graphicsview.h"
|
|
|
|
|
2019-09-29 15:52:35 +08:00
|
|
|
#include "graphicsscene.h"
|
|
|
|
|
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>
|
2021-02-18 20:03:17 +08:00
|
|
|
#include <QStyleOptionGraphicsItem>
|
2019-09-28 01:18:08 +08:00
|
|
|
|
2021-03-01 13:11:17 +08:00
|
|
|
// TODO: remove this once we drop older Qt support.
|
2021-03-01 13:24:03 +08:00
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
|
2021-03-01 13:11:17 +08:00
|
|
|
#define COMPAT_CONSTCOLOR constexpr
|
|
|
|
#else
|
|
|
|
#define COMPAT_CONSTCOLOR const
|
|
|
|
#endif
|
|
|
|
|
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);
|
2019-10-07 14:46:22 +08:00
|
|
|
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;");
|
2019-09-29 15:52:35 +08:00
|
|
|
setAcceptDrops(true);
|
2019-09-30 23:02:44 +08:00
|
|
|
setCheckerboardEnabled(false);
|
2019-10-06 13:02:08 +08:00
|
|
|
|
|
|
|
connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, &GraphicsView::viewportRectChanged);
|
|
|
|
connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &GraphicsView::viewportRectChanged);
|
2019-09-29 15:52:35 +08:00
|
|
|
}
|
|
|
|
|
2021-02-09 14:19:09 +08:00
|
|
|
void GraphicsView::showFileFromPath(const QString &filePath, bool doRequestGallery)
|
2019-10-01 10:37:14 +08:00
|
|
|
{
|
2021-03-25 23:05:50 +08:00
|
|
|
emit navigatorViewRequired(false, transform());
|
2019-11-16 15:53:24 +08:00
|
|
|
|
2019-10-01 10:37:14 +08:00
|
|
|
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);
|
2021-04-15 00:00:18 +08:00
|
|
|
|
2020-03-03 09:53:23 +08:00
|
|
|
// 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()) {
|
2021-02-09 14:19:09 +08:00
|
|
|
doRequestGallery = false;
|
2019-10-06 17:31:27 +08:00
|
|
|
showText(tr("File is not a valid image"));
|
2021-04-15 00:00:18 +08:00
|
|
|
} else if (imageReader.supportsAnimation() && imageReader.imageCount() > 1) {
|
|
|
|
showAnimated(filePath);
|
|
|
|
} else if (!imageReader.canRead()) {
|
2021-02-09 14:19:09 +08:00
|
|
|
doRequestGallery = false;
|
2020-11-01 13:01:36 +08:00
|
|
|
showText(tr("Image data is invalid or currently unsupported"));
|
2019-10-01 10:37:14 +08:00
|
|
|
} else {
|
2020-11-01 13:01:36 +08:00
|
|
|
const QPixmap & pixmap = QPixmap::fromImageReader(&imageReader);
|
|
|
|
if (pixmap.isNull()) {
|
2021-02-09 14:19:09 +08:00
|
|
|
doRequestGallery = false;
|
2020-11-01 13:01:36 +08:00
|
|
|
showText(tr("Image data is invalid or currently unsupported"));
|
|
|
|
} else {
|
|
|
|
showImage(pixmap);
|
|
|
|
}
|
2019-10-01 10:37:14 +08:00
|
|
|
}
|
|
|
|
}
|
2019-11-16 15:53:24 +08:00
|
|
|
|
|
|
|
if (doRequestGallery) {
|
|
|
|
emit requestGallery(filePath);
|
|
|
|
}
|
2019-10-01 10:37:14 +08:00
|
|
|
}
|
|
|
|
|
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);
|
2019-09-30 23:02:44 +08:00
|
|
|
checkAndDoFitInView();
|
2019-09-29 15:52:35 +08:00
|
|
|
}
|
|
|
|
|
2020-01-01 14:51:46 +08:00
|
|
|
void GraphicsView::showImage(const QImage &image)
|
|
|
|
{
|
|
|
|
resetTransform();
|
|
|
|
scene()->showImage(QPixmap::fromImage(image));
|
|
|
|
checkAndDoFitInView();
|
|
|
|
}
|
|
|
|
|
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);
|
2019-09-30 23:02:44 +08:00
|
|
|
checkAndDoFitInView();
|
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);
|
2019-09-30 23:02:44 +08:00
|
|
|
checkAndDoFitInView();
|
2019-09-29 23:53:29 +08:00
|
|
|
}
|
|
|
|
|
2021-04-15 00:00:18 +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();
|
2021-04-15 00:00:18 +08:00
|
|
|
scene()->showAnimated(filepath);
|
2019-09-30 23:02:44 +08:00
|
|
|
checkAndDoFitInView();
|
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
|
|
|
|
{
|
2021-02-18 20:03:17 +08:00
|
|
|
return QStyleOptionGraphicsItem::levelOfDetailFromTransform(transform());
|
2019-10-02 16:04:50 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void GraphicsView::resetTransform()
|
|
|
|
{
|
|
|
|
QGraphicsView::resetTransform();
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2020-02-13 19:45:14 +08:00
|
|
|
applyTransformationModeByScaleFactor();
|
2021-03-25 23:05:50 +08:00
|
|
|
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
2019-10-02 16:04:50 +08:00
|
|
|
}
|
|
|
|
|
2021-03-29 22:43:37 +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)
|
|
|
|
{
|
2021-03-27 22:22:04 +08:00
|
|
|
QTransform tf(horizontal ? -1 : 1, 0, 0,
|
|
|
|
0, horizontal ? 1 : -1, 0,
|
|
|
|
0, 0, 1);
|
|
|
|
tf = transform() * tf;
|
|
|
|
setTransform(tf);
|
|
|
|
|
2021-03-25 23:05:50 +08:00
|
|
|
// 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()
|
|
|
|
{
|
2021-03-25 23:05:50 +08:00
|
|
|
setTransform(resetScale(transform()));
|
|
|
|
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);
|
2020-02-13 19:45:14 +08:00
|
|
|
applyTransformationModeByScaleFactor();
|
2019-10-02 16:04:50 +08:00
|
|
|
}
|
|
|
|
|
2020-07-27 20:47:54 +08:00
|
|
|
void GraphicsView::checkAndDoFitInView(bool markItOnAnyway)
|
2019-10-02 14:31:24 +08:00
|
|
|
{
|
|
|
|
if (!isThingSmallerThanWindowWith(transform())) {
|
|
|
|
m_enableFitInView = true;
|
|
|
|
fitInView(sceneRect(), Qt::KeepAspectRatio);
|
|
|
|
}
|
2020-07-27 20:47:54 +08:00
|
|
|
|
|
|
|
if (markItOnAnyway) {
|
|
|
|
m_enableFitInView = true;
|
|
|
|
}
|
2019-10-02 14:31:24 +08:00
|
|
|
}
|
|
|
|
|
2021-03-25 23:05:50 +08:00
|
|
|
inline double zeroOrOne(double number)
|
|
|
|
{
|
|
|
|
return qFuzzyIsNull(number) ? 0 : (number > 0 ? 1 : -1);
|
|
|
|
}
|
|
|
|
|
2021-03-27 22:22:04 +08:00
|
|
|
// Note: this only works if we only have 90 degree based rotation
|
|
|
|
// and no shear/translate.
|
2021-03-25 23:05:50 +08:00
|
|
|
QTransform GraphicsView::resetScale(const QTransform & orig)
|
|
|
|
{
|
|
|
|
return QTransform(zeroOrOne(orig.m11()), zeroOrOne(orig.m12()),
|
|
|
|
zeroOrOne(orig.m21()), zeroOrOne(orig.m22()),
|
|
|
|
orig.dx(), orig.dy());
|
|
|
|
}
|
|
|
|
|
2021-03-01 00:44:50 +08:00
|
|
|
void GraphicsView::toggleCheckerboard(bool invertCheckerboardColor)
|
2019-09-30 23:02:44 +08:00
|
|
|
{
|
2021-03-01 00:44:50 +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)
|
|
|
|
{
|
|
|
|
QGraphicsItem *item = itemAt(event->pos());
|
|
|
|
if (!item) {
|
|
|
|
event->ignore();
|
|
|
|
}
|
|
|
|
|
|
|
|
return QGraphicsView::mouseReleaseEvent(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
void GraphicsView::wheelEvent(QWheelEvent *event)
|
|
|
|
{
|
2019-10-04 09:54:13 +08:00
|
|
|
event->ignore();
|
2019-10-07 14:46:22 +08:00
|
|
|
// 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) {
|
2021-03-25 23:05:50 +08:00
|
|
|
bool originalSizeSmallerThanWindow = isThingSmallerThanWindowWith(resetScale(transform()));
|
2021-02-18 20:03:17 +08:00
|
|
|
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.
|
2021-02-18 20:03:17 +08:00
|
|
|
} 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 {
|
2021-03-25 23:05:50 +08:00
|
|
|
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
|
2019-09-29 21:22:20 +08:00
|
|
|
}
|
|
|
|
return QGraphicsView::resizeEvent(event);
|
|
|
|
}
|
|
|
|
|
2019-09-29 15:52:35 +08:00
|
|
|
void GraphicsView::dragEnterEvent(QDragEnterEvent *event)
|
|
|
|
{
|
|
|
|
if (event->mimeData()->hasUrls() || event->mimeData()->hasImage() || event->mimeData()->hasText()) {
|
|
|
|
event->acceptProposedAction();
|
|
|
|
} else {
|
|
|
|
event->ignore();
|
|
|
|
}
|
2020-01-27 17:11:24 +08:00
|
|
|
// qDebug() << event->mimeData() << "Drag Enter Event"
|
|
|
|
// << event->mimeData()->hasUrls() << event->mimeData()->hasImage()
|
|
|
|
// << event->mimeData()->formats() << event->mimeData()->hasFormat("text/uri-list");
|
2019-09-29 15:52:35 +08:00
|
|
|
|
|
|
|
return QGraphicsView::dragEnterEvent(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
void GraphicsView::dragMoveEvent(QDragMoveEvent *event)
|
|
|
|
{
|
2019-10-06 17:31:27 +08:00
|
|
|
Q_UNUSED(event)
|
2019-09-29 15:52:35 +08:00
|
|
|
// 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()) {
|
2019-11-16 15:53:24 +08:00
|
|
|
const QList<QUrl> &urls = mimeData->urls();
|
|
|
|
if (urls.isEmpty()) {
|
|
|
|
showText(tr("File url list is empty"));
|
|
|
|
} else {
|
2021-02-09 14:19:09 +08:00
|
|
|
showFileFromPath(urls.first().toLocalFile(), true);
|
2019-11-16 15:53:24 +08:00
|
|
|
}
|
2019-09-29 15:52:35 +08:00
|
|
|
} else if (mimeData->hasImage()) {
|
|
|
|
QImage img = qvariant_cast<QImage>(mimeData->imageData());
|
|
|
|
QPixmap pixmap = QPixmap::fromImage(img);
|
|
|
|
if (pixmap.isNull()) {
|
2019-10-06 17:31:27 +08:00
|
|
|
showText(tr("Image data is invalid"));
|
2019-09-29 15:52:35 +08:00
|
|
|
} else {
|
|
|
|
showImage(pixmap);
|
|
|
|
}
|
|
|
|
} else if (mimeData->hasText()) {
|
|
|
|
showText(mimeData->text());
|
2019-09-29 21:22:20 +08:00
|
|
|
} else {
|
2019-10-06 17:31:27 +08:00
|
|
|
showText(tr("Not supported mimedata: %1").arg(mimeData->formats().first()));
|
2019-09-29 15:52:35 +08:00
|
|
|
}
|
|
|
|
}
|
2019-09-29 21:22:20 +08:00
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2021-03-01 00:44:50 +08:00
|
|
|
void GraphicsView::setCheckerboardEnabled(bool enabled, bool invertColor)
|
2019-09-30 23:02:44 +08:00
|
|
|
{
|
|
|
|
m_checkerboardEnabled = enabled;
|
2021-03-01 00:44:50 +08:00
|
|
|
m_isLastCheckerboardColorInverted = invertColor;
|
2019-09-30 23:02:44 +08:00
|
|
|
if (m_checkerboardEnabled) {
|
|
|
|
// Prepare background check-board pattern
|
|
|
|
QPixmap tilePixmap(0x20, 0x20);
|
2021-03-01 00:44:50 +08:00
|
|
|
tilePixmap.fill(invertColor ? QColor(220, 220, 220, 170) : QColor(35, 35, 35, 170));
|
2019-09-30 23:02:44 +08:00
|
|
|
QPainter tilePainter(&tilePixmap);
|
2021-03-01 13:11:17 +08:00
|
|
|
COMPAT_CONSTCOLOR QColor color(45, 45, 45, 170);
|
|
|
|
COMPAT_CONSTCOLOR QColor invertedColor(210, 210, 210, 170);
|
2021-03-01 00:44:50 +08:00
|
|
|
tilePainter.fillRect(0, 0, 0x10, 0x10, invertColor ? invertedColor : color);
|
|
|
|
tilePainter.fillRect(0x10, 0x10, 0x10, 0x10, invertColor ? 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
|
|
|
|
2020-02-13 19:45:14 +08:00
|
|
|
void GraphicsView::applyTransformationModeByScaleFactor()
|
|
|
|
{
|
|
|
|
if (this->scaleFactor() < 1) {
|
|
|
|
scene()->trySetTransformationMode(Qt::SmoothTransformation);
|
|
|
|
} else {
|
|
|
|
scene()->trySetTransformationMode(Qt::FastTransformation);
|
|
|
|
}
|
|
|
|
}
|