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