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 }