File indexing completed on 2024-05-19 04:29:08

0001 /*
0002  *  SPDX-FileCopyrightText: 2006, 2010 Boudewijn Rempt <boud@valdyas.org>
0003  *  SPDX-FileCopyrightText: 2009 Lukáš Tvrdý <lukast.dev@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "kis_zoom_manager.h"
0009 
0010 
0011 #include <QGridLayout>
0012 
0013 #include <kactioncollection.h>
0014 #include <ktoggleaction.h>
0015 #include <kis_debug.h>
0016 
0017 #include <KisView.h>
0018 #include <KoZoomAction.h>
0019 #include <KoRuler.h>
0020 #include <KoZoomHandler.h>
0021 #include <KoZoomController.h>
0022 #include <KoCanvasControllerWidget.h>
0023 #include <KoUnit.h>
0024 
0025 #include "KisDocument.h"
0026 #include "KisViewManager.h"
0027 #include "canvas/kis_canvas2.h"
0028 #include "kis_coordinates_converter.h"
0029 #include "kis_image.h"
0030 #include "kis_statusbar.h"
0031 #include "kis_config.h"
0032 #include "krita_utils.h"
0033 #include "kis_canvas_resource_provider.h"
0034 #include "kis_lod_transform.h"
0035 #include "kis_snap_line_strategy.h"
0036 #include "kis_guides_config.h"
0037 #include "kis_guides_manager.h"
0038 
0039 
0040 class KisZoomController : public KoZoomController
0041 {
0042 public:
0043     KisZoomController(KoCanvasController *co, KisCoordinatesConverter *zh, KisKActionCollection *actionCollection, QObject *parent)
0044         : KoZoomController(co, zh, actionCollection, parent),
0045           m_converter(zh)
0046     {
0047     }
0048 
0049 protected:
0050     QSizeF documentToViewport(const QSizeF &size) override {
0051         QRectF docRect(QPointF(), size);
0052         QSizeF viewport = m_converter->documentToWidget(docRect).size();
0053         QPointF adjustedViewport = m_converter->snapToDevicePixel(QPointF(viewport.width(), viewport.height()));
0054         return QSizeF(adjustedViewport.x(), adjustedViewport.y());
0055     }
0056 
0057 private:
0058     KisCoordinatesConverter *m_converter;
0059 };
0060 
0061 
0062 KisZoomManager::KisZoomManager(QPointer<KisView> view, KoZoomHandler * zoomHandler,
0063                                KoCanvasController * canvasController)
0064         : m_view(view)
0065         , m_zoomHandler(zoomHandler)
0066         , m_canvasController(canvasController)
0067         , m_guiUpdateCompressor(80, KisSignalCompressor::FIRST_ACTIVE)
0068         , m_previousZoomMode(KoZoomMode::ZOOM_PAGE)
0069         , m_previousZoomPoint(QPointF(0.0, 0.0))
0070 {
0071 }
0072 
0073 KisZoomManager::~KisZoomManager()
0074 {
0075     if (m_zoomActionWidget && !m_zoomActionWidget->parent()) {
0076         delete m_zoomActionWidget;
0077     }
0078 }
0079 
0080 void KisZoomManager::updateScreenResolution(QWidget *parentWidget)
0081 {
0082     if (qFuzzyCompare(parentWidget->physicalDpiX(), m_physicalDpiX) &&
0083         qFuzzyCompare(parentWidget->physicalDpiY(), m_physicalDpiY) &&
0084         qFuzzyCompare(parentWidget->devicePixelRatioF(), m_devicePixelRatio)) {
0085 
0086         return;
0087     }
0088 
0089     m_physicalDpiX = parentWidget->physicalDpiX();
0090     m_physicalDpiY = parentWidget->physicalDpiY();
0091     m_devicePixelRatio = parentWidget->devicePixelRatioF();
0092 
0093     KisCoordinatesConverter *converter =
0094         dynamic_cast<KisCoordinatesConverter*>(m_zoomHandler);
0095     KIS_ASSERT_RECOVER_RETURN(converter);
0096 
0097     converter->setDevicePixelRatio(m_devicePixelRatio);
0098 
0099     changeCanvasMappingMode(m_canvasMappingMode);
0100 }
0101 
0102 void KisZoomManager::setup(KisKActionCollection * actionCollection)
0103 {
0104 
0105     KisImageWSP image = m_view->image();
0106     if (!image) return;
0107 
0108     connect(image, SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(setMinMaxZoom()));
0109 
0110     KisCoordinatesConverter *converter =
0111         dynamic_cast<KisCoordinatesConverter*>(m_zoomHandler);
0112 
0113     m_zoomController = new KisZoomController(m_canvasController, converter, actionCollection, this);
0114     m_zoomHandler->setZoomMode(KoZoomMode::ZOOM_PIXELS);
0115     m_zoomHandler->setZoom(1.0);
0116 
0117     m_zoomController->setPageSize(QSizeF(image->width() / image->xRes(), image->height() / image->yRes()));
0118     m_zoomController->setDocumentSize(QSizeF(image->width() / image->xRes(), image->height() / image->yRes()), true);
0119 
0120     m_zoomAction = m_zoomController->zoomAction();
0121 
0122     setMinMaxZoom();
0123 
0124     m_zoomActionWidget = m_zoomAction->createWidget(0);
0125 
0126 
0127     // Put the canvascontroller in a layout so it resizes with us
0128     QGridLayout * layout = new QGridLayout(m_view);
0129     layout->setSpacing(0);
0130     layout->setMargin(0);
0131 
0132     m_view->document()->setUnit(KoUnit(KoUnit::Pixel));
0133 
0134     m_horizontalRuler = new KoRuler(m_view, Qt::Horizontal, m_zoomHandler);
0135     m_horizontalRuler->setShowMousePosition(true);
0136     m_horizontalRuler->createGuideToolConnection(m_view->canvasBase());
0137     m_horizontalRuler->setVisible(false); // this prevents the rulers from flashing on to off when a new document is created
0138 
0139     m_verticalRuler = new KoRuler(m_view, Qt::Vertical, m_zoomHandler);
0140     m_verticalRuler->setShowMousePosition(true);
0141     m_verticalRuler->createGuideToolConnection(m_view->canvasBase());
0142     m_verticalRuler->setVisible(false);
0143 
0144 
0145     QAction *rulerAction = actionCollection->action("ruler_pixel_multiple2");
0146     if (m_view->document()->guidesConfig().rulersMultiple2()) {
0147         m_horizontalRuler->setUnitPixelMultiple2(true);
0148         m_verticalRuler->setUnitPixelMultiple2(true);
0149     }
0150     QList<QAction*> unitActions = m_view->createChangeUnitActions(true);
0151     unitActions.append(rulerAction);
0152     m_horizontalRuler->setPopupActionList(unitActions);
0153     m_verticalRuler->setPopupActionList(unitActions);
0154 
0155     connect(m_view->document(), SIGNAL(unitChanged(KoUnit)), SLOT(applyRulersUnit(KoUnit)));
0156     connect(rulerAction, SIGNAL(toggled(bool)), SLOT(setRulersPixelMultiple2(bool)));
0157 
0158     layout->addWidget(m_horizontalRuler, 0, 1);
0159     layout->addWidget(m_verticalRuler, 1, 0);
0160     layout->addWidget(static_cast<KoCanvasControllerWidget*>(m_canvasController), 1, 1);
0161 
0162     connect(m_canvasController->proxyObject, SIGNAL(canvasOffsetXChanged(int)),
0163             this, SLOT(pageOffsetChanged()));
0164 
0165     connect(m_canvasController->proxyObject, SIGNAL(canvasOffsetYChanged(int)),
0166             this, SLOT(pageOffsetChanged()));
0167 
0168     connect(m_zoomController, SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)),
0169             this, SLOT(slotZoomChanged(KoZoomMode::Mode,qreal)));
0170 
0171     connect(m_zoomController, SIGNAL(canvasMappingModeChanged(bool)),
0172             this, SLOT(changeCanvasMappingMode(bool)));
0173 
0174     applyRulersUnit(m_view->document()->unit());
0175 
0176     connect(&m_guiUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateGuiAfterZoomChange()));
0177 }
0178 
0179 void KisZoomManager::updateImageBoundsSnapping()
0180 {
0181     const QRectF docRect = m_view->canvasBase()->coordinatesConverter()->imageRectInDocumentPixels();
0182     const QPointF docCenter = docRect.center();
0183 
0184     KoSnapGuide *snapGuide = m_view->canvasBase()->snapGuide();
0185 
0186     {
0187         KisSnapLineStrategy *boundsSnap =
0188             new KisSnapLineStrategy(KoSnapGuide::DocumentBoundsSnapping);
0189 
0190         boundsSnap->addLine(Qt::Horizontal, docRect.y());
0191         boundsSnap->addLine(Qt::Horizontal, docRect.bottom());
0192         boundsSnap->addLine(Qt::Vertical, docRect.x());
0193         boundsSnap->addLine(Qt::Vertical, docRect.right());
0194 
0195         snapGuide->overrideSnapStrategy(KoSnapGuide::DocumentBoundsSnapping, boundsSnap);
0196     }
0197 
0198     {
0199         KisSnapLineStrategy *centerSnap =
0200             new KisSnapLineStrategy(KoSnapGuide::DocumentCenterSnapping);
0201 
0202         centerSnap->addLine(Qt::Horizontal, docCenter.y());
0203         centerSnap->addLine(Qt::Vertical, docCenter.x());
0204 
0205         snapGuide->overrideSnapStrategy(KoSnapGuide::DocumentCenterSnapping, centerSnap);
0206     }
0207 }
0208 
0209 void KisZoomManager::updateCurrentZoomResource()
0210 {
0211     const qreal effectiveZoom =
0212         m_view->canvasBase()->coordinatesConverter()->effectiveZoom();
0213     const qreal effectivePhysicalZoom =
0214         m_view->canvasBase()->coordinatesConverter()->effectivePhysicalZoom();
0215 
0216     m_view->canvasBase()->resourceManager()->setResource(KoCanvasResource::EffectiveZoom, effectiveZoom);
0217     m_view->canvasBase()->resourceManager()->setResource(KoCanvasResource::EffectivePhysicalZoom, effectivePhysicalZoom);
0218 }
0219 
0220 void KisZoomManager::updateMouseTrackingConnections()
0221 {
0222     bool value = m_horizontalRuler->isVisible() &&
0223         m_verticalRuler->isVisible() &&
0224         m_horizontalRuler->showMousePosition() &&
0225         m_verticalRuler->showMousePosition();
0226 
0227     m_mouseTrackingConnections.clear();
0228 
0229     if (value) {
0230         m_mouseTrackingConnections.addConnection(m_canvasController->proxyObject,
0231                 SIGNAL(canvasMousePositionChanged(QPoint)),
0232                 this,
0233                 SLOT(mousePositionChanged(QPoint)));
0234     }
0235 }
0236 
0237 KoRuler* KisZoomManager::horizontalRuler() const
0238 {
0239     return m_horizontalRuler;
0240 }
0241 
0242 KoRuler* KisZoomManager::verticalRuler() const
0243 {
0244     return m_verticalRuler;
0245 }
0246 
0247 qreal KisZoomManager::zoom() const
0248 {
0249     qreal zoomX;
0250     qreal zoomY;
0251     m_zoomHandler->zoom(&zoomX, &zoomY);
0252     return zoomX;
0253 }
0254 
0255 qreal KisZoomManager::resolutionX() const
0256 {
0257     KisImageSP image = m_view->image();
0258     return m_canvasMappingMode ? POINT_TO_INCH(m_physicalDpiX) : image->xRes() / m_devicePixelRatio;
0259 }
0260 
0261 qreal KisZoomManager::resolutionY() const
0262 {
0263     KisImageSP image = m_view->image();
0264     return m_canvasMappingMode ? POINT_TO_INCH(m_physicalDpiY) : image->yRes() / m_devicePixelRatio;
0265 }
0266 
0267 void KisZoomManager::mousePositionChanged(const QPoint &viewPos)
0268 {
0269     QPoint pt = viewPos - m_rulersOffset;
0270 
0271     m_horizontalRuler->updateMouseCoordinate(pt.x());
0272     m_verticalRuler->updateMouseCoordinate(pt.y());
0273 }
0274 
0275 void KisZoomManager::setShowRulers(bool show)
0276 {
0277     m_horizontalRuler->setVisible(show);
0278     m_verticalRuler->setVisible(show);
0279     updateMouseTrackingConnections();
0280 }
0281 
0282 void KisZoomManager::setRulersTrackMouse(bool value)
0283 {
0284     m_horizontalRuler->setShowMousePosition(value);
0285     m_verticalRuler->setShowMousePosition(value);
0286     updateMouseTrackingConnections();
0287 }
0288 
0289 void KisZoomManager::applyRulersUnit(const KoUnit &baseUnit)
0290 {
0291     if (m_view && m_view->image()) {
0292         m_horizontalRuler->setUnit(KoUnit(baseUnit.type(), m_view->image()->xRes()));
0293         m_verticalRuler->setUnit(KoUnit(baseUnit.type(), m_view->image()->yRes()));
0294     }
0295     if (m_view->viewManager()) {
0296         m_view->viewManager()->guidesManager()->setUnitType(baseUnit.type());
0297     }
0298 }
0299 
0300 void KisZoomManager::setRulersPixelMultiple2(bool enabled)
0301 {
0302     m_horizontalRuler->setUnitPixelMultiple2(enabled);
0303     m_verticalRuler->setUnitPixelMultiple2(enabled);
0304     if (m_view->viewManager()) {
0305         m_view->viewManager()->guidesManager()->setRulersMultiple2(enabled);
0306     }
0307 }
0308 
0309 void KisZoomManager::slotUpdateGuiAfterZoomChange()
0310 {
0311     const qreal zoomValue = m_view->canvasBase()->coordinatesConverter()->zoom();
0312     const qreal humanZoom = zoomValue * 100.0;
0313 
0314     // XXX: KOMVC -- this is very irritating in MDI mode
0315 
0316     if (m_view->viewManager()) {
0317         m_view->viewManager()->
0318                 showFloatingMessage(
0319                     i18nc("floating message about zoom", "Zoom: %1 %",
0320                           KritaUtils::prettyFormatReal(humanZoom)),
0321                     QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter);
0322     }
0323 
0324     updateCurrentZoomResource();
0325 }
0326 
0327 void KisZoomManager::setMinMaxZoom()
0328 {
0329     KisImageWSP image = m_view->image();
0330     if (!image) return;
0331 
0332     QSize imageSize = image->size();
0333     qreal minDimension = qMin(imageSize.width(), imageSize.height());
0334     qreal minZoom = qMin(100.0 / minDimension, 0.1);
0335 
0336     m_zoomAction->setMinMaxZoom(minZoom, 90.0);
0337 }
0338 
0339 void KisZoomManager::updateGuiAfterDocumentSize()
0340 {
0341     QRectF widgetRect = m_view->canvasBase()->coordinatesConverter()->imageRectInWidgetPixels();
0342     QSize documentSize = m_view->canvasBase()->viewConverter()->viewToDocument(widgetRect).toAlignedRect().size();
0343 
0344     m_horizontalRuler->setRulerLength(documentSize.width());
0345     m_verticalRuler->setRulerLength(documentSize.height());
0346 
0347     applyRulersUnit(m_horizontalRuler->unit());
0348 
0349     updateZoomMarginSize();
0350 }
0351 
0352 QWidget *KisZoomManager::zoomActionWidget() const
0353 {
0354     return m_zoomActionWidget;
0355 }
0356 
0357 void KisZoomManager::slotZoomChanged(KoZoomMode::Mode mode, qreal zoom)
0358 {
0359     Q_UNUSED(mode);
0360     Q_UNUSED(zoom);
0361     m_view->canvasBase()->notifyZoomChanged();
0362     m_guiUpdateCompressor.start();
0363 }
0364 
0365 void KisZoomManager::slotZoomLevelsChanged()
0366 {
0367     m_zoomAction->slotUpdateZoomLevels();
0368 }
0369 
0370 void KisZoomManager::slotScrollAreaSizeChanged()
0371 {
0372     pageOffsetChanged();
0373     updateGuiAfterDocumentSize();
0374 }
0375 
0376 void KisZoomManager::changeCanvasMappingMode(bool canvasMappingMode)
0377 {
0378     KisImageSP image = m_view->image();
0379 
0380     // changeCanvasMappingMode is called with the same canvasMappingMode when the window is
0381     // moved across screens. Preserve the old zoomMode if this is the case.
0382     const KoZoomMode::Mode newMode =
0383             canvasMappingMode == m_canvasMappingMode ? m_zoomHandler->zoomMode() : KoZoomMode::ZOOM_CONSTANT;
0384     const qreal newZoom = m_zoomHandler->zoom();
0385 
0386     m_canvasMappingMode = canvasMappingMode;
0387     m_zoomController->setZoom(newMode, newZoom, resolutionX(), resolutionY());
0388     m_view->canvasBase()->notifyZoomChanged();
0389 
0390     m_view->viewManager()->updatePrintSizeAction(canvasMappingMode);
0391 }
0392 
0393 void KisZoomManager::pageOffsetChanged()
0394 {
0395     QRectF widgetRect = m_view->canvasBase()->coordinatesConverter()->imageRectInWidgetPixels();
0396     m_rulersOffset = widgetRect.topLeft().toPoint();
0397 
0398     m_horizontalRuler->setOffset(m_rulersOffset.x());
0399     m_verticalRuler->setOffset(m_rulersOffset.y());
0400 }
0401 
0402 void KisZoomManager::zoomTo100()
0403 {
0404     m_zoomController->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0);
0405     m_view->canvasBase()->notifyZoomChanged();
0406 }
0407 
0408 void KisZoomManager::slotZoomToFit()
0409 {
0410     m_zoomController->setZoom(KoZoomMode::ZOOM_PAGE, 0);
0411     m_view->canvasBase()->notifyZoomChanged();
0412 }
0413 
0414 void KisZoomManager::slotZoomToFitWidth()
0415 {
0416     m_zoomController->setZoom(KoZoomMode::ZOOM_WIDTH, 0);
0417     m_view->canvasBase()->notifyZoomChanged();
0418 }
0419 void KisZoomManager::slotZoomToFitHeight()
0420 {
0421     m_zoomController->setZoom(KoZoomMode::ZOOM_HEIGHT, 0);
0422     m_view->canvasBase()->notifyZoomChanged();
0423 }
0424 
0425 void KisZoomManager::slotToggleZoomToFit()
0426 {
0427     KoZoomMode::Mode currentZoomMode = m_zoomController->zoomMode();
0428     if (currentZoomMode == KoZoomMode::ZOOM_CONSTANT) {
0429         m_previousZoomLevel = m_zoomController->zoomAction()->effectiveZoom();
0430         m_previousZoomPoint = m_canvasController->preferredCenter();
0431         m_zoomController->setZoom(m_previousZoomMode, 0);
0432     }
0433     else {
0434         m_previousZoomMode = currentZoomMode;
0435         m_zoomController->setZoom(KoZoomMode::ZOOM_CONSTANT, m_previousZoomLevel);
0436         m_canvasController->setPreferredCenter(m_previousZoomPoint);
0437     }
0438     m_view->canvasBase()->notifyZoomChanged();
0439 }
0440 
0441 void KisZoomManager::updateZoomMarginSize()
0442 {
0443     KisConfig cfg(true);
0444     m_zoomController->setZoomMarginSize(cfg.zoomMarginSize());
0445 }