File indexing completed on 2024-06-16 04:16:15

0001 /*
0002  *  SPDX-FileCopyrightText: 2009 Cyrille Berger <cberger@cberger.net>
0003  *  SPDX-FileCopyrightText: 2014 Sven Langkamp <sven.langkamp@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 
0008 
0009 #include "overviewwidget.h"
0010 
0011 #include <QMouseEvent>
0012 #include <QPainter>
0013 #include <QCursor>
0014 
0015 #include <KoCanvasController.h>
0016 #include <KoZoomController.h>
0017 
0018 #include <kis_canvas2.h>
0019 #include <KisViewManager.h>
0020 #include <kis_image.h>
0021 #include <kis_signal_compressor.h>
0022 #include <kis_config.h>
0023 #include <QApplication>
0024 #include "KisImageThumbnailStrokeStrategy.h"
0025 #include <kis_display_color_converter.h>
0026 #include <KisMainWindow.h>
0027 #include "KisIdleTasksManager.h"
0028 
0029 OverviewWidget::OverviewWidget(QWidget * parent)
0030     : KisWidgetWithIdleTask<QWidget>(parent)
0031     , m_dragging(false)
0032 {
0033     setMouseTracking(true);
0034     KisConfig cfg(true);
0035     slotThemeChanged();
0036     recalculatePreviewDimensions();
0037 }
0038 
0039 OverviewWidget::~OverviewWidget()
0040 {
0041 }
0042 
0043 void OverviewWidget::setCanvas(KisCanvas2 *canvas)
0044 {
0045     if (m_canvas) {
0046         m_canvas->image()->disconnect(this);
0047         m_canvas->displayColorConverter()->disconnect(this);
0048     }
0049 
0050     KisWidgetWithIdleTask<QWidget>::setCanvas(canvas);
0051 
0052     if (m_canvas) {
0053         connect(m_canvas->displayColorConverter(), SIGNAL(displayConfigurationChanged()), SLOT(startUpdateCanvasProjection()));
0054         connect(m_canvas->canvasController()->proxyObject, SIGNAL(canvasOffsetXChanged(int)), this, SLOT(update()), Qt::UniqueConnection);
0055         connect(m_canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotThemeChanged()), Qt::UniqueConnection);
0056     }
0057 }
0058 
0059 void OverviewWidget::recalculatePreviewDimensions()
0060 {
0061     if (!m_canvas || !m_canvas->image()) {
0062         return;
0063     }
0064 
0065     QSize imageSize(m_canvas->image()->bounds().size());
0066 
0067     const qreal hScale = 1.0 * this->width() / imageSize.width();
0068     const qreal vScale = 1.0 * this->height() / imageSize.height();
0069 
0070     m_previewScale = qMin(hScale, vScale);
0071     m_previewSize = imageSize * m_previewScale;
0072     m_previewOrigin = calculatePreviewOrigin(m_previewSize);
0073 
0074 }
0075 
0076 KisIdleTasksManager::TaskGuard OverviewWidget::registerIdleTask(KisCanvas2 *canvas)
0077 {
0078     return
0079         canvas->viewManager()->idleTasksManager()->
0080         addIdleTaskWithGuard([this](KisImageSP image) {
0081             const KoColorProfile *profile =
0082                 m_canvas->displayColorConverter()->monitorProfile();
0083             KoColorConversionTransformation::ConversionFlags conversionFlags =
0084                 m_canvas->displayColorConverter()->conversionFlags();
0085             KoColorConversionTransformation::Intent renderingIntent =
0086                 m_canvas->displayColorConverter()->renderingIntent();
0087 
0088             // If the widget is presented on a device with a pixel ratio > 1.0, we must compensate for it
0089             // by increasing the thumbnail's resolution. Otherwise it will appear blurry.
0090             QSize thumbnailSize = m_previewSize * devicePixelRatioF();
0091 
0092             if ((thumbnailSize.width() > image->width()) || (thumbnailSize.height() > image->height())) {
0093                 thumbnailSize.scale(image->size(), Qt::KeepAspectRatio);
0094             }
0095 
0096             KisImageThumbnailStrokeStrategy *strategy =
0097                 new KisImageThumbnailStrokeStrategy(image->projection(), image->bounds(), thumbnailSize, isPixelArt(), profile, renderingIntent, conversionFlags);
0098 
0099             connect(strategy, SIGNAL(thumbnailUpdated(QImage)), this, SLOT(updateThumbnail(QImage)));
0100 
0101             return strategy;
0102         });
0103 }
0104 
0105 void OverviewWidget::clearCachedState()
0106 {
0107     m_pixmap = QPixmap();
0108     m_oldPixmap = QPixmap();
0109 }
0110 
0111 bool OverviewWidget::isPixelArt()
0112 {
0113     return m_previewScale > 1;
0114 }
0115 
0116 QPointF OverviewWidget::calculatePreviewOrigin(QSize previewSize)
0117 {
0118     return QPointF((width() - previewSize.width()) / 2.0f, (height() - previewSize.height()) / 2.0f);
0119 }
0120 
0121 QPolygonF OverviewWidget::previewPolygon()
0122 {
0123     if (m_canvas) {
0124         const QRectF &canvasRect = QRectF(m_canvas->canvasWidget()->rect());
0125         return canvasToPreviewTransform().map(canvasRect);
0126     }
0127     return QPolygonF();
0128 }
0129 
0130 QTransform OverviewWidget::previewToCanvasTransform()
0131 {
0132     QTransform previewToImage =
0133             QTransform::fromTranslate(-this->width() / 2.0, -this->height() / 2.0) *
0134             QTransform::fromScale(1.0 / m_previewScale, 1.0 / m_previewScale) *
0135             QTransform::fromTranslate(m_canvas->image()->width() / 2.0, m_canvas->image()->height() / 2.0);
0136 
0137     return previewToImage * m_canvas->coordinatesConverter()->imageToWidgetTransform();
0138 }
0139 
0140 QTransform OverviewWidget::canvasToPreviewTransform()
0141 {
0142     return previewToCanvasTransform().inverted();
0143 }
0144 
0145 void OverviewWidget::startUpdateCanvasProjection()
0146 {
0147     triggerCacheUpdate();
0148 }
0149 
0150 void OverviewWidget::resizeEvent(QResizeEvent *event)
0151 {
0152     Q_UNUSED(event);
0153     if (m_canvas) {
0154         if (!m_oldPixmap.isNull()) {
0155             recalculatePreviewDimensions();
0156             m_pixmap = m_oldPixmap.scaled(m_previewSize, Qt::KeepAspectRatio, Qt::FastTransformation);
0157         }
0158         triggerCacheUpdate();
0159     }
0160 }
0161 
0162 void OverviewWidget::mousePressEvent(QMouseEvent* event)
0163 {
0164     if (m_canvas) {
0165         QPointF previewPos = event->pos();
0166 
0167         if (!previewPolygon().containsPoint(previewPos, Qt::WindingFill)) {
0168             const QRect& canvasRect = m_canvas->canvasWidget()->rect();
0169             const QPointF newCanvasPos = previewToCanvasTransform().map(previewPos) -
0170                     QPointF(canvasRect.width() / 2.0f, canvasRect.height() / 2.0f);
0171             m_canvas->canvasController()->pan(newCanvasPos.toPoint());
0172         }
0173         m_lastPos = previewPos;
0174         m_dragging = true;
0175         emit signalDraggingStarted();
0176     }
0177     event->accept();
0178     update();
0179 }
0180 
0181 void OverviewWidget::mouseMoveEvent(QMouseEvent* event)
0182 {
0183     if (m_dragging) {
0184         QPointF previewPos = event->pos();
0185         const QPointF lastCanvasPos = previewToCanvasTransform().map(m_lastPos);
0186         const QPointF newCanvasPos = previewToCanvasTransform().map(event->pos());
0187 
0188         QPointF diff = newCanvasPos - lastCanvasPos;
0189         m_canvas->canvasController()->pan(diff.toPoint());
0190         m_lastPos = previewPos;
0191     }
0192     event->accept();
0193 }
0194 
0195 void OverviewWidget::mouseReleaseEvent(QMouseEvent* event)
0196 {
0197     if (m_dragging) {
0198         m_dragging = false;
0199         emit signalDraggingFinished();
0200     }
0201     event->accept();
0202     update();
0203 }
0204 
0205 void OverviewWidget::wheelEvent(QWheelEvent* event)
0206 {
0207     if (m_canvas) {
0208         float delta = event->delta();
0209 
0210         if (delta > 0) {
0211             m_canvas->viewManager()->zoomController()->zoomAction()->zoomIn();
0212         } else {
0213             m_canvas->viewManager()->zoomController()->zoomAction()->zoomOut();
0214         }
0215     }
0216 }
0217 
0218 void OverviewWidget::updateThumbnail(QImage pixmap)
0219 {
0220     m_pixmap = QPixmap::fromImage(pixmap);
0221     m_oldPixmap = m_pixmap.copy();
0222     update();
0223 }
0224 
0225 void OverviewWidget::slotThemeChanged()
0226 {
0227     m_outlineColor = qApp->palette().color(QPalette::Highlight);
0228 }
0229 
0230 
0231 void OverviewWidget::paintEvent(QPaintEvent* event)
0232 {
0233     QWidget::paintEvent(event);
0234 
0235     if (m_canvas) {
0236         recalculatePreviewDimensions();
0237         QPainter p(this);
0238 
0239         const QRectF previewRect = QRectF(m_previewOrigin, m_previewSize);
0240         p.drawPixmap(previewRect.toRect(), m_pixmap);
0241 
0242         QRect r = rect();
0243         QPolygonF outline;
0244         outline << r.topLeft() << r.topRight() << r.bottomRight() << r.bottomLeft();
0245 
0246         QPen pen;
0247         pen.setColor(m_outlineColor);
0248         pen.setStyle(Qt::DashLine);
0249 
0250         p.setPen(pen);
0251         p.drawPolygon(outline.intersected(previewPolygon()));
0252 
0253         pen.setStyle(Qt::SolidLine);
0254         p.setPen(pen);
0255         p.drawPolygon(previewPolygon());
0256 
0257     }
0258 }
0259 
0260