diff --git a/libs/flake/KoCanvasController.cpp b/libs/flake/KoCanvasController.cpp index f971fb633c..4ad3f5c26c 100644 --- a/libs/flake/KoCanvasController.cpp +++ b/libs/flake/KoCanvasController.cpp @@ -1,129 +1,129 @@ /* This file is part of the KDE project * * Copyright (C) 2010 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCanvasController.h" #include "KoToolManager.h" #include #include class Q_DECL_HIDDEN KoCanvasController::Private { public: Private() : margin(0) , preferredCenterFractionX(0.5) , preferredCenterFractionY(0.5) , actionCollection(0) { } int margin; - QSize documentSize; + QSizeF documentSize; QPoint documentOffset; qreal preferredCenterFractionX; qreal preferredCenterFractionY; KActionCollection* actionCollection; }; KoCanvasController::KoCanvasController(KActionCollection* actionCollection) : d(new Private()) { proxyObject = new KoCanvasControllerProxyObject(this); d->actionCollection = actionCollection; } KoCanvasController::~KoCanvasController() { KoToolManager::instance()->removeCanvasController(this); delete d; delete proxyObject; } void KoCanvasController::setMargin(int margin) { d->margin = margin; } int KoCanvasController::margin() const { return d->margin; } KoCanvasBase* KoCanvasController::canvas() const { return 0; } -void KoCanvasController::setDocumentSize(const QSize &sz) +void KoCanvasController::setDocumentSize(const QSizeF &sz) { d->documentSize = sz; } -QSize KoCanvasController::documentSize() const +QSizeF KoCanvasController::documentSize() const { return d->documentSize; } void KoCanvasController::setPreferredCenterFractionX(qreal x) { d->preferredCenterFractionX = x; } qreal KoCanvasController::preferredCenterFractionX() const { return d->preferredCenterFractionX; } void KoCanvasController::setPreferredCenterFractionY(qreal y) { d->preferredCenterFractionY = y; } qreal KoCanvasController::preferredCenterFractionY() const { return d->preferredCenterFractionY; } void KoCanvasController::setDocumentOffset(QPoint &offset) { d->documentOffset = offset; } QPoint KoCanvasController::documentOffset() const { return d->documentOffset; } KoCanvasControllerProxyObject::KoCanvasControllerProxyObject(KoCanvasController *controller, QObject *parent) : QObject(parent) , m_canvasController(controller) { } void KoCanvasControllerProxyObject::updateDocumentSize(const QSize &newSize, bool recalculateCenter) { m_canvasController->updateDocumentSize(newSize, recalculateCenter); } KActionCollection* KoCanvasController::actionCollection() const { return d->actionCollection; } diff --git a/libs/flake/KoCanvasController.h b/libs/flake/KoCanvasController.h index 78c40e1b71..471a68b36b 100644 --- a/libs/flake/KoCanvasController.h +++ b/libs/flake/KoCanvasController.h @@ -1,482 +1,482 @@ /* This file is part of the KDE project * Copyright (C) 2006, 2008 Thomas Zander * Copyright (C) 2007-2010 Boudewijn Rempt * Copyright (C) 2007-2008 C. Boemann * Copyright (C) 2006-2007 Jan Hambrecht * Copyright (C) 2009 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCANVASCONTROLLER_H #define KOCANVASCONTROLLER_H #include "kritaflake_export.h" #include #include #include #include #include class KActionCollection; class QRect; class QRectF; class KoShape; class KoCanvasBase; class KoCanvasControllerProxyObject; /** * KoCanvasController is the base class for wrappers around your canvas * that provides scrolling and zooming for your canvas. * * Flake does not provide a canvas, the application will have to * implement a canvas themselves. You canvas can be QWidget-based * or something we haven't invented yet -- as long the class that holds the canvas * implements KoCanvasController, tools, scrolling and zooming will work. * * A KoCanvasController implementation acts as a decorator around the canvas widget * and provides a way to scroll the canvas, allows the canvas to be centered * in the viewArea and manages tool activation. * *

The using application can instantiate this class and add its * canvas using the setCanvas() call. Which is designed so it can be * called multiple times if you need to exchange one canvas * widget for another, for instance, switching between a plain QWidget or a QOpenGLWidget. * *

There is _one_ KoCanvasController per canvas in your * application. * *

The canvas widget is at most as big as the viewport of the scroll * area, and when the view on the document is near its edges, smaller. * In your canvas widget code, you can find the right place in your * document in view coordinates (pixels) by adding the documentOffset */ class KRITAFLAKE_EXPORT KoCanvasController { public: // proxy QObject: use this to connect to slots and signals. QPointer proxyObject; /** * Constructor. * @param actionCollection the action collection for this canvas */ explicit KoCanvasController(KActionCollection* actionCollection); virtual ~KoCanvasController(); public: /** * Returns the current margin that is used to pad the canvas with. * This value is read from the KConfig property "canvasmargin" */ virtual int margin() const; /** * Set the new margin to pad the canvas with. */ virtual void setMargin(int margin); /** * compatibility with QAbstractScrollArea */ virtual void scrollContentsBy(int dx, int dy) = 0; /** * @return the size of the viewport */ - virtual QSize viewportSize() const = 0; + virtual QSizeF viewportSize() const = 0; /** * Set the new canvas to be shown as a child * Calling this will emit canvasRemoved() if there was a canvas before, and will emit * canvasSet() with the new canvas. * @param canvas the new canvas. The KoCanvasBase::canvas() will be called to retrieve the * actual widget which will then be added as child of this one. */ virtual void setCanvas(KoCanvasBase *canvas) = 0; /** * Return the currently set canvas. The default implementation will return Null * @return the currently set canvas */ virtual KoCanvasBase *canvas() const; /** * return the amount of pixels vertically visible of the child canvas. * @return the amount of pixels vertically visible of the child canvas. */ virtual int visibleHeight() const = 0; /** * return the amount of pixels horizontally visible of the child canvas. * @return the amount of pixels horizontally visible of the child canvas. */ virtual int visibleWidth() const = 0; /** * return the amount of pixels that are not visible on the left side of the canvas. * The leftmost pixel that is shown is returned. */ virtual int canvasOffsetX() const = 0; /** * return the amount of pixels that are not visible on the top side of the canvas. * The topmost pixel that is shown is returned. */ virtual int canvasOffsetY() const = 0; /** * @brief Scrolls the content of the canvas so that the given rect is visible. * * The rect is to be specified in view coordinates (pixels). The scrollbar positions * are changed so that the centerpoint of the rectangle is centered if possible. * * @param rect the rectangle to make visible * @param smooth if true the viewport translation will make be just enough to ensure visibility, no more. * @see KoViewConverter::documentToView() */ virtual void ensureVisible(const QRectF &rect, bool smooth = false) = 0; /** * @brief Scrolls the content of the canvas so that the given shape is visible. * * This is just a wrapper function of the above function. * * @param shape the shape to make visible */ virtual void ensureVisible(KoShape *shape) = 0; /** * @brief zooms in around the center. * * The center must be specified in view coordinates (pixels). The scrollbar positions * are changed so that the center becomes center if possible. * * @param center the position to zoom in on */ virtual void zoomIn(const QPoint ¢er) = 0; /** * @brief zooms out around the center. * * The center must be specified in view coordinates (pixels). The scrollbar positions * are changed so that the center becomes center if possible. * * @param center the position to zoom out around */ virtual void zoomOut(const QPoint ¢er) = 0; /** * @brief zooms around the center. * * The center must be specified in view coordinates (pixels). The scrollbar positions * are changed so that the center becomes center if possible. * * @param center the position to zoom around * @param zoom the zoom to apply */ virtual void zoomBy(const QPoint ¢er, qreal zoom) = 0; /** * @brief zoom so that rect is exactly visible (as close as possible) * * The rect must be specified in view coordinates (pixels). The scrollbar positions * are changed so that the center of the rect becomes center if possible. * * @param rect the rect in view coordinates (pixels) that should fit the view afterwards */ virtual void zoomTo(const QRect &rect) = 0; /** * @brief repositions the scrollbars so previous center is once again center * * The previous center is cached from when the user uses the scrollbars or zoomTo * are called. zoomTo is mostly used when a zoom tool of sorts have marked an area * to zoom in on * * The success of this method is limited by the size of thing. But we try our best. */ virtual void recenterPreferred() = 0; /** * Sets the preferred center point in view coordinates (pixels). * @param viewPoint the new preferred center */ virtual void setPreferredCenter(const QPointF &viewPoint) = 0; /// Returns the currently set preferred center point in view coordinates (pixels) virtual QPointF preferredCenter() const = 0; /** * Move the canvas over the x and y distance of the parameter distance * @param distance the distance in view coordinates (pixels). A positive distance means moving the canvas up/left. */ virtual void pan(const QPoint &distance) = 0; /** * Move the canvas up. This behaves the same as \sa pan() with a positive y coordinate. */ virtual void panUp() = 0; /** * Move the canvas down. This behaves the same as \sa pan() with a negative y coordinate. */ virtual void panDown() = 0; /** * Move the canvas to the left. This behaves the same as \sa pan() with a positive x coordinate. */ virtual void panLeft() = 0; /** * Move the canvas to the right. This behaves the same as \sa pan() with a negative x coordinate. */ virtual void panRight() = 0; /** * Get the position of the scrollbar */ virtual QPoint scrollBarValue() const = 0; /** * Set the position of the scrollbar * @param value the new values of the scroll bars */ virtual void setScrollBarValue(const QPoint &value) = 0; /** * Update the range of scroll bars */ virtual void resetScrollBars() = 0; /** * Called when the size of your document in view coordinates (pixels) changes, for instance when zooming. * * @param newSize the new size, in view coordinates (pixels), of the document. * @param recalculateCenter if true the offset in the document we center on after calling * recenterPreferred() will be recalculated for the new document size so the visual offset stays the same. */ - virtual void updateDocumentSize(const QSize &sz, bool recalculateCenter) = 0; + virtual void updateDocumentSize(const QSizeF &sz, bool recalculateCenter) = 0; /** * Set mouse wheel to zoom behaviour * @param zoom if true wheel will zoom instead of scroll, control modifier will scroll */ virtual void setZoomWithWheel(bool zoom) = 0; /** * Set scroll area to be bigger than actual document. * It allows the user to move the corner of the document * to e.g. the center of the screen * * @param factor the coefficient, defining how much we can scroll out, * measured in parts of the widget size. Null value means vast * scrolling is disabled. */ virtual void setVastScrolling(qreal factor) = 0; /** * Returns the action collection for the window * @returns action collection for this window, can be 0 */ KActionCollection* actionCollection() const; QPoint documentOffset() const; /** * @return the current position of the cursor fetched from QCursor::pos() and * converted into document coordinates */ virtual QPointF currentCursorPosition() const = 0; protected: - void setDocumentSize(const QSize &sz); - QSize documentSize() const; + void setDocumentSize(const QSizeF &sz); + QSizeF documentSize() const; void setPreferredCenterFractionX(qreal); qreal preferredCenterFractionX() const; void setPreferredCenterFractionY(qreal); qreal preferredCenterFractionY() const; void setDocumentOffset( QPoint &offset); private: class Private; Private * const d; }; /** * Workaround class for the problem that Qt does not allow two QObject base classes. * KoCanvasController can be implemented by for instance QWidgets, so it cannot be * a QObject directly. The interface of this class should be considered public interface * for KoCanvasController. */ class KRITAFLAKE_EXPORT KoCanvasControllerProxyObject : public QObject { Q_OBJECT Q_DISABLE_COPY(KoCanvasControllerProxyObject) public: explicit KoCanvasControllerProxyObject(KoCanvasController *canvasController, QObject *parent = 0); public: // Convenience methods to invoke the signals from subclasses void emitCanvasRemoved(KoCanvasController *canvasController) { emit canvasRemoved(canvasController); } void emitCanvasSet(KoCanvasController *canvasController) { emit canvasSet(canvasController); } void emitCanvasOffsetXChanged(int offset) { emit canvasOffsetXChanged(offset); } void emitCanvasOffsetYChanged(int offset) { emit canvasOffsetYChanged(offset); } void emitCanvasMousePositionChanged(const QPoint &position) { emit canvasMousePositionChanged(position); } void emitDocumentMousePositionChanged(const QPointF &position) { emit documentMousePositionChanged(position); } void emitSizeChanged(const QSize &size) { emit sizeChanged(size); } void emitMoveDocumentOffset(const QPoint &point) { emit moveDocumentOffset(point); } void emitZoomRelative(const qreal factor, const QPointF &stillPoint) { emit zoomRelative(factor, stillPoint); } // Convenience method to retrieve the canvas controller for who needs to use QPointer KoCanvasController *canvasController() const { return m_canvasController; } Q_SIGNALS: /** * Emitted when a previously added canvas is about to be removed. * @param canvasController this object */ void canvasRemoved(KoCanvasController *canvasController); /** * Emitted when a canvas is set on this widget * @param canvasController this object */ void canvasSet(KoCanvasController *canvasController); /** * Emitted when canvasOffsetX() changes * @param offset the new canvas offset */ void canvasOffsetXChanged(int offset); /** * Emitted when canvasOffsetY() changes * @param offset the new canvas offset */ void canvasOffsetYChanged(int offset); /** * Emitted when the cursor is moved over the canvas widget. * @param position the position in view coordinates (pixels). */ void canvasMousePositionChanged(const QPoint &position); /** * Emitted when the cursor is moved over the canvas widget. * @param position the position in document coordinates. * * Use \ref canvasMousePositionChanged to get the position * in view coordinates. */ void documentMousePositionChanged(const QPointF &position); /** * Emitted when the entire controller size changes * @param size the size in widget pixels. */ void sizeChanged(const QSize &size); /** * Emitted whenever the document is scrolled. * * @param point the new top-left point from which the document should * be drawn. */ void moveDocumentOffset(const QPoint &point); /** * Emitted when zoomRelativeToPoint have calculated a factor by which * the zoom should change and the point which should stand still * on screen. * Someone needs to connect to this and take action * * @param factor by how much the zoom needs to change. * @param stillPoint the point which will not change its position * in widget during the zooming. It is measured in * view coordinate system *before* zoom. */ void zoomRelative(const qreal factor, const QPointF &stillPoint); public Q_SLOTS: /** * Call this slot whenever the size of your document in view coordinates (pixels) * changes, for instance when zooming. * @param newSize the new size, in view coordinates (pixels), of the document. * @param recalculateCenter if true the offset in the document we center on after calling * recenterPreferred() will be recalculated for the new document size so the visual offset stays the same. */ void updateDocumentSize(const QSize &newSize, bool recalculateCenter = true); private: KoCanvasController *m_canvasController; }; class KRITAFLAKE_EXPORT KoDummyCanvasController : public KoCanvasController { public: explicit KoDummyCanvasController(KActionCollection* actionCollection) : KoCanvasController(actionCollection) {} ~KoDummyCanvasController() override {} void scrollContentsBy(int /*dx*/, int /*dy*/) override {} - QSize viewportSize() const override { return QSize(); } + QSizeF viewportSize() const override { return QSizeF(); } void setCanvas(KoCanvasBase *canvas) override {Q_UNUSED(canvas)} KoCanvasBase *canvas() const override {return 0;} int visibleHeight() const override {return 0;} int visibleWidth() const override {return 0;} int canvasOffsetX() const override {return 0;} int canvasOffsetY() const override {return 0;} void ensureVisible(const QRectF &/*rect*/, bool /*smooth */ = false) override {} void ensureVisible(KoShape *shape) override {Q_UNUSED(shape)} void zoomIn(const QPoint &/*center*/) override {} void zoomOut(const QPoint &/*center*/) override {} void zoomBy(const QPoint &/*center*/, qreal /*zoom*/) override {} void zoomTo(const QRect &/*rect*/) override {} void recenterPreferred() override {} void setPreferredCenter(const QPointF &/*viewPoint*/) override {} QPointF preferredCenter() const override {return QPointF();} void pan(const QPoint &/*distance*/) override {} void panUp() override {} void panDown() override {} void panLeft() override {} void panRight() override {} QPoint scrollBarValue() const override {return QPoint();} void setScrollBarValue(const QPoint &/*value*/) override {} void resetScrollBars() override {} - void updateDocumentSize(const QSize &/*sz*/, bool /*recalculateCenter*/) override {} + void updateDocumentSize(const QSizeF &/*sz*/, bool /*recalculateCenter*/) override {} void setZoomWithWheel(bool /*zoom*/) override {} void setVastScrolling(qreal /*factor*/) override {} QPointF currentCursorPosition() const override { return QPointF(); } }; #endif diff --git a/libs/flake/KoCanvasControllerWidget.cpp b/libs/flake/KoCanvasControllerWidget.cpp index 095058743b..3cda255127 100644 --- a/libs/flake/KoCanvasControllerWidget.cpp +++ b/libs/flake/KoCanvasControllerWidget.cpp @@ -1,622 +1,626 @@ /* This file is part of the KDE project * * Copyright (C) 2006, 2008-2009 Thomas Zander * Copyright (C) 2006 Peter Simonsson * Copyright (C) 2006, 2009 Thorsten Zachmann * Copyright (C) 2007-2010 Boudewijn Rempt * Copyright (C) 2007 C. Boemann * Copyright (C) 2006-2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCanvasControllerWidget.h" #include "KoCanvasControllerWidget_p.h" #include "KoCanvasControllerWidgetViewport_p.h" #include "KoShape.h" #include "KoViewConverter.h" #include "KoCanvasBase.h" #include "KoCanvasObserverBase.h" #include "KoCanvasSupervisor.h" #include "KoToolManager_p.h" #include #include #include #include #include #include #include #include #include #include void KoCanvasControllerWidget::Private::setDocumentOffset() { // The margins scroll the canvas widget inside the viewport, not // the document. The documentOffset is meant to be the value that // the canvas must add to the update rect in its paint event, to // compensate. QPoint pt(q->horizontalScrollBar()->value(), q->verticalScrollBar()->value()); q->proxyObject->emitMoveDocumentOffset(pt); QWidget *canvasWidget = canvas->canvasWidget(); if (canvasWidget) { // If it isn't an OpenGL canvas if (qobject_cast(canvasWidget) == 0) { QPoint diff = q->documentOffset() - pt; canvasWidget->scroll(diff.x(), diff.y(), canvasWidget->rect()); } } q->setDocumentOffset(pt); } void KoCanvasControllerWidget::Private::resetScrollBars() { // The scrollbar value always points at the top-left corner of the // bit of image we paint. - int docH = q->documentSize().height() + q->margin(); - int docW = q->documentSize().width() + q->margin(); + int docH = (int)q->documentSize().height() + q->margin(); + int docW = (int)q->documentSize().width() + q->margin(); int drawH = viewportWidget->height(); int drawW = viewportWidget->width(); QScrollBar *hScroll = q->horizontalScrollBar(); QScrollBar *vScroll = q->verticalScrollBar(); int horizontalReserve = vastScrollingFactor * drawW; int verticalReserve = vastScrollingFactor * drawH; int xMin = -horizontalReserve; int yMin = -verticalReserve; int xMax = docW - drawW + horizontalReserve; int yMax = docH - drawH + verticalReserve; hScroll->setRange(xMin, xMax); vScroll->setRange(yMin, yMax); int fontheight = QFontMetrics(q->font()).height(); vScroll->setPageStep(drawH); vScroll->setSingleStep(fontheight); hScroll->setPageStep(drawW); hScroll->setSingleStep(fontheight); } void KoCanvasControllerWidget::Private::emitPointerPositionChangedSignals(QEvent *event) { if (!canvas) return; if (!canvas->viewConverter()) return; QPoint pointerPos; QMouseEvent *mouseEvent = dynamic_cast(event); if (mouseEvent) { pointerPos = mouseEvent->pos(); } else { QTabletEvent *tabletEvent = dynamic_cast(event); if (tabletEvent) { pointerPos = tabletEvent->pos(); } } QPoint pixelPos = (pointerPos - canvas->documentOrigin()) + q->documentOffset(); QPointF documentPos = canvas->viewConverter()->viewToDocument(pixelPos); q->proxyObject->emitDocumentMousePositionChanged(documentPos); q->proxyObject->emitCanvasMousePositionChanged(pointerPos); } #include void KoCanvasControllerWidget::Private::activate() { QWidget *parent = q; while (parent->parentWidget()) { parent = parent->parentWidget(); } KoCanvasSupervisor *observerProvider = dynamic_cast(parent); if (!observerProvider) { return; } KoCanvasBase *canvas = q->canvas(); Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observer->setObservedCanvas(canvas); } } } void KoCanvasControllerWidget::Private::unsetCanvas() { QWidget *parent = q; while (parent->parentWidget()) { parent = parent->parentWidget(); } KoCanvasSupervisor *observerProvider = dynamic_cast(parent); if (!observerProvider) { return; } Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { if (observer->observedCanvas() == q->canvas()) { observer->unsetObservedCanvas(); } } } } //////////// KoCanvasControllerWidget::KoCanvasControllerWidget(KActionCollection * actionCollection, QWidget *parent) : QAbstractScrollArea(parent) , KoCanvasController(actionCollection) , d(new Private(this)) { // We need to set this as QDeclarativeView sets them a bit different from QAbstractScrollArea setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // And then our own Viewport d->viewportWidget = new Viewport(this); setViewport(d->viewportWidget); d->viewportWidget->setFocusPolicy(Qt::NoFocus); setFocusPolicy(Qt::NoFocus); setFrameStyle(0); //setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setAutoFillBackground(false); /* Fixes: apps starting at zero zoom. Details: Since the document is set on the mainwindow before loading commences the inial show/layout can choose to set the document to be very small, even to be zero pixels tall. Setting a sane minimum size on the widget means we no longer get rounding errors in zooming and we no longer end up with zero-zoom. Note: KoPage apps should probably startup with a sane document size; for Krita that's impossible */ setMinimumSize(QSize(50, 50)); setMouseTracking(true); connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetX())); connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetY())); connect(d->viewportWidget, SIGNAL(sizeChanged()), this, SLOT(updateCanvasOffsetX())); connect(proxyObject, SIGNAL(moveDocumentOffset(QPoint)), d->viewportWidget, SLOT(documentOffsetMoved(QPoint))); } KoCanvasControllerWidget::~KoCanvasControllerWidget() { delete d; } void KoCanvasControllerWidget::activate() { d->activate(); } void KoCanvasControllerWidget::scrollContentsBy(int dx, int dy) { Q_UNUSED(dx); Q_UNUSED(dy); d->setDocumentOffset(); } -QSize KoCanvasControllerWidget::viewportSize() const +QSizeF KoCanvasControllerWidget::viewportSize() const { - return viewport()->size(); + // Calculate viewport size aligned to device pixels to match KisOpenGLCanvas2. + qreal dpr = viewport()->devicePixelRatioF(); + int viewportWidth = static_cast(viewport()->width() * dpr); + int viewportHeight = static_cast(viewport()->height() * dpr); + return QSizeF(viewportWidth / dpr, viewportHeight / dpr); } void KoCanvasControllerWidget::resizeEvent(QResizeEvent *resizeEvent) { proxyObject->emitSizeChanged(resizeEvent->size()); // XXX: When resizing, keep the area we're looking at now in the // center of the resized view. resetScrollBars(); d->setDocumentOffset(); } void KoCanvasControllerWidget::setCanvas(KoCanvasBase *canvas) { if (d->canvas) { d->unsetCanvas(); proxyObject->emitCanvasRemoved(this); d->canvas->setCanvasController(0); d->canvas->canvasWidget()->removeEventFilter(this); } d->canvas = canvas; if (d->canvas) { d->canvas->setCanvasController(this); changeCanvasWidget(d->canvas->canvasWidget()); proxyObject->emitCanvasSet(this); QTimer::singleShot(0, this, SLOT(activate())); setPreferredCenterFractionX(0); setPreferredCenterFractionY(0); } } KoCanvasBase* KoCanvasControllerWidget::canvas() const { if (d->canvas.isNull()) return 0; return d->canvas; } void KoCanvasControllerWidget::changeCanvasWidget(QWidget *widget) { if (d->viewportWidget->canvas()) { widget->setCursor(d->viewportWidget->canvas()->cursor()); d->viewportWidget->canvas()->removeEventFilter(this); } d->viewportWidget->setCanvas(widget); setFocusProxy(d->canvas->canvasWidget()); } int KoCanvasControllerWidget::visibleHeight() const { if (d->canvas == 0) return 0; QWidget *canvasWidget = canvas()->canvasWidget(); int height1; if (canvasWidget == 0) height1 = viewport()->height(); else height1 = qMin(viewport()->height(), canvasWidget->height()); int height2 = height(); return qMin(height1, height2); } int KoCanvasControllerWidget::visibleWidth() const { if (d->canvas == 0) return 0; QWidget *canvasWidget = canvas()->canvasWidget(); int width1; if (canvasWidget == 0) width1 = viewport()->width(); else width1 = qMin(viewport()->width(), canvasWidget->width()); int width2 = width(); return qMin(width1, width2); } int KoCanvasControllerWidget::canvasOffsetX() const { int offset = -horizontalScrollBar()->value(); if (d->canvas) { offset += d->canvas->canvasWidget()->x() + frameWidth(); } return offset; } int KoCanvasControllerWidget::canvasOffsetY() const { int offset = -verticalScrollBar()->value(); if (d->canvas) { offset += d->canvas->canvasWidget()->y() + frameWidth(); } return offset; } void KoCanvasControllerWidget::updateCanvasOffsetX() { proxyObject->emitCanvasOffsetXChanged(canvasOffsetX()); if (d->ignoreScrollSignals) return; setPreferredCenterFractionX((horizontalScrollBar()->value() + viewport()->width() / 2.0) / documentSize().width()); } void KoCanvasControllerWidget::updateCanvasOffsetY() { proxyObject->emitCanvasOffsetYChanged(canvasOffsetY()); if (d->ignoreScrollSignals) return; setPreferredCenterFractionY((verticalScrollBar()->value() + verticalScrollBar()->pageStep() / 2.0) / documentSize().height()); } void KoCanvasControllerWidget::ensureVisible(KoShape *shape) { Q_ASSERT(shape); ensureVisible(d->canvas->viewConverter()->documentToView(shape->boundingRect())); } void KoCanvasControllerWidget::ensureVisible(const QRectF &rect, bool smooth) { QRect currentVisible(-canvasOffsetX(), -canvasOffsetY(), visibleWidth(), visibleHeight()); QRect viewRect = rect.toRect(); viewRect.translate(d->canvas->documentOrigin()); if (!viewRect.isValid() || currentVisible.contains(viewRect)) return; // its visible. Nothing to do. // if we move, we move a little more so the amount of times we have to move is less. int jumpWidth = smooth ? 0 : currentVisible.width() / 5; int jumpHeight = smooth ? 0 : currentVisible.height() / 5; if (!smooth && viewRect.width() + jumpWidth > currentVisible.width()) jumpWidth = 0; if (!smooth && viewRect.height() + jumpHeight > currentVisible.height()) jumpHeight = 0; int horizontalMove = 0; if (currentVisible.width() <= viewRect.width()) // center view horizontalMove = viewRect.center().x() - currentVisible.center().x(); else if (currentVisible.x() > viewRect.x()) // move left horizontalMove = viewRect.x() - currentVisible.x() - jumpWidth; else if (currentVisible.right() < viewRect.right()) // move right horizontalMove = viewRect.right() - qMax(0, currentVisible.right() - jumpWidth); int verticalMove = 0; if (currentVisible.height() <= viewRect.height()) // center view verticalMove = viewRect.center().y() - currentVisible.center().y(); if (currentVisible.y() > viewRect.y()) // move up verticalMove = viewRect.y() - currentVisible.y() - jumpHeight; else if (currentVisible.bottom() < viewRect.bottom()) // move down verticalMove = viewRect.bottom() - qMax(0, currentVisible.bottom() - jumpHeight); pan(QPoint(horizontalMove, verticalMove)); } void KoCanvasControllerWidget::recenterPreferred() { const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; QPointF center = preferredCenter(); // convert into a viewport based point center.rx() += d->canvas->canvasWidget()->x() + frameWidth(); center.ry() += d->canvas->canvasWidget()->y() + frameWidth(); // scroll to a new center point QPointF topLeft = center - 0.5 * QPointF(viewport()->width(), viewport()->height()); setScrollBarValue(topLeft.toPoint()); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::zoomIn(const QPoint ¢er) { zoomBy(center, sqrt(2.0)); } void KoCanvasControllerWidget::zoomOut(const QPoint ¢er) { zoomBy(center, sqrt(0.5)); } void KoCanvasControllerWidget::zoomBy(const QPoint ¢er, qreal zoom) { setPreferredCenterFractionX(1.0 * center.x() / documentSize().width()); setPreferredCenterFractionY(1.0 * center.y() / documentSize().height()); const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; proxyObject->emitZoomRelative(zoom, preferredCenter()); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::zoomTo(const QRect &viewRect) { qreal scale; if (1.0 * viewport()->width() / viewRect.width() > 1.0 * viewport()->height() / viewRect.height()) scale = 1.0 * viewport()->height() / viewRect.height(); else scale = 1.0 * viewport()->width() / viewRect.width(); zoomBy(viewRect.center(), scale); } -void KoCanvasControllerWidget::updateDocumentSize(const QSize &sz, bool recalculateCenter) +void KoCanvasControllerWidget::updateDocumentSize(const QSizeF &sz, bool recalculateCenter) { // Don't update if the document-size didn't changed to prevent infinite loops and unneeded updates. if (KoCanvasController::documentSize() == sz) return; if (!recalculateCenter) { // assume the distance from the top stays equal and recalculate the center. setPreferredCenterFractionX(documentSize().width() * preferredCenterFractionX() / sz.width()); setPreferredCenterFractionY(documentSize().height() * preferredCenterFractionY() / sz.height()); } const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; KoCanvasController::setDocumentSize(sz); d->viewportWidget->setDocumentSize(sz); resetScrollBars(); // Always emit the new offset. updateCanvasOffsetX(); updateCanvasOffsetY(); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::setZoomWithWheel(bool zoom) { d->zoomWithWheel = zoom; } void KoCanvasControllerWidget::setVastScrolling(qreal factor) { d->vastScrollingFactor = factor; } QPointF KoCanvasControllerWidget::currentCursorPosition() const { QWidget *canvasWidget = d->canvas->canvasWidget(); const KoViewConverter *converter = d->canvas->viewConverter(); return converter->viewToDocument(canvasWidget->mapFromGlobal(QCursor::pos()) + d->canvas->canvasController()->documentOffset() - canvasWidget->pos()); } void KoCanvasControllerWidget::pan(const QPoint &distance) { QPoint sourcePoint = scrollBarValue(); setScrollBarValue(sourcePoint + distance); } void KoCanvasControllerWidget::panUp() { pan(QPoint(0, verticalScrollBar()->singleStep())); } void KoCanvasControllerWidget::panDown() { pan(QPoint(0, -verticalScrollBar()->singleStep())); } void KoCanvasControllerWidget::panLeft() { pan(QPoint(horizontalScrollBar()->singleStep(), 0)); } void KoCanvasControllerWidget::panRight() { pan(QPoint(-horizontalScrollBar()->singleStep(), 0)); } void KoCanvasControllerWidget::setPreferredCenter(const QPointF &viewPoint) { setPreferredCenterFractionX(viewPoint.x() / documentSize().width()); setPreferredCenterFractionY(viewPoint.y() / documentSize().height()); recenterPreferred(); } QPointF KoCanvasControllerWidget::preferredCenter() const { QPointF center; center.setX(preferredCenterFractionX() * documentSize().width()); center.setY(preferredCenterFractionY() * documentSize().height()); return center; } void KoCanvasControllerWidget::paintEvent(QPaintEvent *event) { QPainter gc(viewport()); d->viewportWidget->handlePaintEvent(gc, event); } void KoCanvasControllerWidget::dragEnterEvent(QDragEnterEvent *event) { d->viewportWidget->handleDragEnterEvent(event); } void KoCanvasControllerWidget::dropEvent(QDropEvent *event) { d->viewportWidget->handleDropEvent(event); } void KoCanvasControllerWidget::dragMoveEvent(QDragMoveEvent *event) { d->viewportWidget->handleDragMoveEvent(event); } void KoCanvasControllerWidget::dragLeaveEvent(QDragLeaveEvent *event) { d->viewportWidget->handleDragLeaveEvent(event); } void KoCanvasControllerWidget::wheelEvent(QWheelEvent *event) { if (d->zoomWithWheel != ((event->modifiers() & Qt::ControlModifier) == Qt::ControlModifier)) { const qreal zoomCoeff = event->delta() > 0 ? sqrt(2.0) : sqrt(0.5); zoomRelativeToPoint(event->pos(), zoomCoeff); event->accept(); } else QAbstractScrollArea::wheelEvent(event); } void KoCanvasControllerWidget::zoomRelativeToPoint(const QPoint &widgetPoint, qreal zoomCoeff) { const QPoint offset = scrollBarValue(); const QPoint mousePos(widgetPoint + offset); const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; proxyObject->emitZoomRelative(zoomCoeff, mousePos); d->ignoreScrollSignals = oldIgnoreScrollSignals; } bool KoCanvasControllerWidget::focusNextPrevChild(bool) { // we always return false meaning the canvas takes keyboard focus, but never gives it away. return false; } void KoCanvasControllerWidget::setMargin(int margin) { KoCanvasController::setMargin(margin); Q_ASSERT(d->viewportWidget); d->viewportWidget->setMargin(margin); } QPoint KoCanvasControllerWidget::scrollBarValue() const { QScrollBar * hBar = horizontalScrollBar(); QScrollBar * vBar = verticalScrollBar(); return QPoint(hBar->value(), vBar->value()); } void KoCanvasControllerWidget::setScrollBarValue(const QPoint &value) { QScrollBar * hBar = horizontalScrollBar(); QScrollBar * vBar = verticalScrollBar(); hBar->setValue(value.x()); vBar->setValue(value.y()); } void KoCanvasControllerWidget::resetScrollBars() { d->resetScrollBars(); } qreal KoCanvasControllerWidget::vastScrollingFactor() const { return d->vastScrollingFactor; } KoCanvasControllerWidget::Private *KoCanvasControllerWidget::priv() { return d; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoCanvasControllerWidget.cpp" diff --git a/libs/flake/KoCanvasControllerWidget.h b/libs/flake/KoCanvasControllerWidget.h index f134fed94c..a74e5f109b 100644 --- a/libs/flake/KoCanvasControllerWidget.h +++ b/libs/flake/KoCanvasControllerWidget.h @@ -1,193 +1,193 @@ /* This file is part of the KDE project * Copyright (C) 2006, 2008 Thomas Zander * Copyright (C) 2007-2010 Boudewijn Rempt * Copyright (C) 2007-2008 C. Boemann * Copyright (C) 2006-2007 Jan Hambrecht * Copyright (C) 2009 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCANVASCONTROLLERWIDGET_H #define KOCANVASCONTROLLERWIDGET_H #include "kritaflake_export.h" #include #include #include "KoCanvasController.h" class KoShape; class KoCanvasBase; /** * KoCanvasController implementation for QWidget based canvases */ class KRITAFLAKE_EXPORT KoCanvasControllerWidget : public QAbstractScrollArea, public KoCanvasController { Q_OBJECT public: /** * Constructor. * @param actionCollection the action collection for this widget * @param parent the parent this widget will belong to */ explicit KoCanvasControllerWidget(KActionCollection * actionCollection, QWidget *parent = 0); ~KoCanvasControllerWidget() override; /** * Reimplemented from QAbstractScrollArea. */ void scrollContentsBy(int dx, int dy) override; - QSize viewportSize() const override; + QSizeF viewportSize() const override; /// Reimplemented from KoCanvasController /** * Activate this canvascontroller */ virtual void activate(); void setCanvas(KoCanvasBase *canvas) override; KoCanvasBase *canvas() const override; /** * Change the actual canvas widget used by the current canvas. This allows the canvas widget * to be changed while keeping the current KoCanvasBase canvas and its associated resources as * they are. This might be used, for example, to switch from a QWidget to a QOpenGLWidget canvas. * @param widget the new canvas widget. */ virtual void changeCanvasWidget(QWidget *widget); int visibleHeight() const override; int visibleWidth() const override; int canvasOffsetX() const override; int canvasOffsetY() const override; void ensureVisible(const QRectF &rect, bool smooth = false) override; void ensureVisible(KoShape *shape) override; /** * will cause the toolOptionWidgetsChanged to be emitted and all * listeners to be updated to the new widget. * * FIXME: This doesn't belong her and it does an * inherits("KoView") so it too much tied to komain * * @param widgets the map of widgets */ void setToolOptionWidgets(const QList > &widgets); void zoomIn(const QPoint ¢er) override; void zoomOut(const QPoint ¢er) override; void zoomBy(const QPoint ¢er, qreal zoom) override; void zoomTo(const QRect &rect) override; /** * Zoom document keeping point \p widgetPoint unchanged * \param widgetPoint sticky point in widget pixels * \param zoomCoeff the zoom */ virtual void zoomRelativeToPoint(const QPoint &widgetPoint, qreal zoomCoeff); void recenterPreferred() override; void setPreferredCenter(const QPointF &viewPoint) override; /// Returns the currently set preferred center point in view coordinates (pixels) QPointF preferredCenter() const override; void pan(const QPoint &distance) override; virtual void panUp() override; virtual void panDown() override; virtual void panLeft() override; virtual void panRight() override; void setMargin(int margin) override; QPoint scrollBarValue() const override; /** * Used by KisCanvasController to correct the scrollbars position * after the rotation. */ void setScrollBarValue(const QPoint &value) override; - void updateDocumentSize(const QSize &sz, bool recalculateCenter = true) override; + void updateDocumentSize(const QSizeF &sz, bool recalculateCenter = true) override; /** * Set mouse wheel to zoom behaviour * @param zoom if true wheel will zoom instead of scroll, control modifier will scroll */ void setZoomWithWheel(bool zoom) override; void setVastScrolling(qreal factor) override; QPointF currentCursorPosition() const override; void resetScrollBars() override; /** * \internal */ class Private; KoCanvasControllerWidget::Private *priv(); private Q_SLOTS: /// Called by the horizontal scrollbar when its value changes void updateCanvasOffsetX(); /// Called by the vertical scrollbar when its value changes void updateCanvasOffsetY(); protected: friend class KisZoomAndPanTest; qreal vastScrollingFactor() const; /// reimplemented from QWidget void paintEvent(QPaintEvent *event) override; /// reimplemented from QWidget void resizeEvent(QResizeEvent *resizeEvent) override; /// reimplemented from QWidget void dragEnterEvent(QDragEnterEvent *event) override; /// reimplemented from QWidget void dropEvent(QDropEvent *event) override; /// reimplemented from QWidget void dragMoveEvent(QDragMoveEvent *event) override; /// reimplemented from QWidget void dragLeaveEvent(QDragLeaveEvent *event) override; /// reimplemented from QWidget void wheelEvent(QWheelEvent *event) override; /// reimplemented from QWidget bool focusNextPrevChild(bool next) override; private: Q_PRIVATE_SLOT(d, void activate()) Private * const d; }; #endif diff --git a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp index e64484997f..ef532a43bb 100644 --- a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp +++ b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp @@ -1,332 +1,332 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007, 2009 Thomas Zander * Copyright (C) 2006 Thorsten Zachmann * Copyright (C) 2007-2010 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCanvasControllerWidgetViewport_p.h" #include #include #include #include #include #include #include #include "KoShape.h" #include "KoShape_p.h" #include "KoShapeFactoryBase.h" // for the SHAPE mimetypes #include "KoShapeRegistry.h" #include "KoShapeController.h" #include "KoShapeManager.h" #include "KoSelection.h" #include "KoCanvasBase.h" #include "KoShapeLayer.h" #include "KoShapePaintingContext.h" #include "KoToolProxy.h" #include "KoCanvasControllerWidget.h" #include "KoViewConverter.h" #include "KoSvgPaste.h" // ********** Viewport ********** Viewport::Viewport(KoCanvasControllerWidget *parent) : QWidget(parent) , m_draggedShape(0) , m_canvas(0) , m_documentOffset(QPoint(0, 0)) , m_margin(0) { setAutoFillBackground(true); setAcceptDrops(true); setMouseTracking(true); m_parent = parent; } void Viewport::setCanvas(QWidget *canvas) { if (m_canvas) { m_canvas->hide(); delete m_canvas; } m_canvas = canvas; if (!canvas) return; m_canvas->setParent(this); m_canvas->show(); if (!m_canvas->minimumSize().isNull()) { m_documentSize = m_canvas->minimumSize(); } resetLayout(); } -void Viewport::setDocumentSize(const QSize &size) +void Viewport::setDocumentSize(const QSizeF &size) { m_documentSize = size; resetLayout(); } void Viewport::documentOffsetMoved(const QPoint &pt) { m_documentOffset = pt; resetLayout(); } void Viewport::handleDragEnterEvent(QDragEnterEvent *event) { // if not a canvas set then ignore this, makes it possible to assume // we have a canvas in all the support methods. if (!(m_parent->canvas() && m_parent->canvas()->canvasWidget())) { event->ignore(); return; } delete m_draggedShape; m_draggedShape = 0; // only allow dropping when active layer is editable KoSelection *selection = m_parent->canvas()->shapeManager()->selection(); KoShapeLayer *activeLayer = selection->activeLayer(); if (activeLayer && (!activeLayer->isShapeEditable() || activeLayer->isGeometryProtected())) { event->ignore(); return; } const QMimeData *data = event->mimeData(); if (data->hasFormat(SHAPETEMPLATE_MIMETYPE) || data->hasFormat(SHAPEID_MIMETYPE) || data->hasFormat("image/svg+xml")) { if (data->hasFormat("image/svg+xml")) { KoCanvasBase *canvas = m_parent->canvas(); QSizeF fragmentSize; QList shapes = KoSvgPaste::fetchShapesFromData(data->data("image/svg+xml"), canvas->shapeController()->documentRectInPixels(), canvas->shapeController()->pixelsPerInch(), &fragmentSize); if (!shapes.isEmpty()) { m_draggedShape = shapes[0]; } } else { QByteArray itemData; bool isTemplate = true; if (data->hasFormat(SHAPETEMPLATE_MIMETYPE)) { itemData = data->data(SHAPETEMPLATE_MIMETYPE); } else if (data->hasFormat(SHAPEID_MIMETYPE)) { isTemplate = false; itemData = data->data(SHAPEID_MIMETYPE); } QDataStream dataStream(&itemData, QIODevice::ReadOnly); QString id; dataStream >> id; QString properties; if (isTemplate) dataStream >> properties; // and finally, there is a point. QPointF offset; dataStream >> offset; // The rest of this method is mostly a copy paste from the KoCreateShapeStrategy // So, lets remove this again when Zagge adds his new class that does this kind of thing. (KoLoadSave) KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(id); if (! factory) { warnFlake << "Application requested a shape that is not registered '" << id << "', Ignoring"; event->ignore(); return; } if (isTemplate) { KoProperties props; props.load(properties); m_draggedShape = factory->createShape(&props, m_parent->canvas()->shapeController()->resourceManager()); } else { m_draggedShape = factory->createDefaultShape(m_parent->canvas()->shapeController()->resourceManager()); } if (m_draggedShape->shapeId().isEmpty()) { m_draggedShape->setShapeId(factory->id()); } } event->setDropAction(Qt::CopyAction); event->accept(); Q_ASSERT(m_draggedShape); if (!m_draggedShape) return; // calculate maximum existing shape zIndex int pasteZIndex = 0; { QList allShapes = m_parent->canvas()->shapeManager()->topLevelShapes(); if (!allShapes.isEmpty()) { std::sort(allShapes.begin(), allShapes.end(), KoShape::compareShapeZIndex); pasteZIndex = qMin(int(KoShape::maxZIndex), allShapes.last()->zIndex() + 1); } } m_draggedShape->setZIndex(pasteZIndex); m_draggedShape->setAbsolutePosition(correctPosition(event->pos())); m_parent->canvas()->shapeManager()->addShape(m_draggedShape); } else { event->ignore(); } } void Viewport::handleDropEvent(QDropEvent *event) { if (!m_draggedShape) { m_parent->canvas()->toolProxy()->dropEvent(event, correctPosition(event->pos())); return; } repaint(m_draggedShape); m_parent->canvas()->shapeManager()->remove(m_draggedShape); // remove it to not interfere with z-index calc. m_draggedShape->setPosition(QPointF(0, 0)); // always save position. QPointF newPos = correctPosition(event->pos()); m_parent->canvas()->clipToDocument(m_draggedShape, newPos); // ensure the shape is dropped inside the document. m_draggedShape->setAbsolutePosition(newPos); KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape, 0); if (cmd) { m_parent->canvas()->addCommand(cmd); KoSelection *selection = m_parent->canvas()->shapeManager()->selection(); // repaint selection before selecting newly create shape Q_FOREACH (KoShape * shape, selection->selectedShapes()) { shape->update(); } selection->deselectAll(); selection->select(m_draggedShape); } else { delete m_draggedShape; } m_draggedShape = 0; } QPointF Viewport::correctPosition(const QPoint &point) const { QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. QPoint correctedPos(point.x() - canvasWidget->x(), point.y() - canvasWidget->y()); correctedPos += m_documentOffset; return m_parent->canvas()->viewToDocument(correctedPos); } void Viewport::handleDragMoveEvent(QDragMoveEvent *event) { if (!m_draggedShape) { m_parent->canvas()->toolProxy()->dragMoveEvent(event, correctPosition(event->pos())); return; } m_draggedShape->update(); repaint(m_draggedShape); m_draggedShape->setAbsolutePosition(correctPosition(event->pos())); m_draggedShape->update(); repaint(m_draggedShape); } void Viewport::repaint(KoShape *shape) { QRect rect = m_parent->canvas()->viewConverter()->documentToView(shape->boundingRect()).toRect(); QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. rect.moveLeft(rect.left() + canvasWidget->x() - m_documentOffset.x()); rect.moveTop(rect.top() + canvasWidget->y() - m_documentOffset.y()); rect.adjust(-2, -2, 2, 2); // adjust for antialias update(rect); } void Viewport::handleDragLeaveEvent(QDragLeaveEvent *event) { if (m_draggedShape) { repaint(m_draggedShape); m_parent->canvas()->shapeManager()->remove(m_draggedShape); delete m_draggedShape; m_draggedShape = 0; } else { m_parent->canvas()->toolProxy()->dragLeaveEvent(event); } } void Viewport::handlePaintEvent(QPainter &painter, QPaintEvent *event) { Q_UNUSED(event); if (m_draggedShape) { const KoViewConverter *vc = m_parent->canvas()->viewConverter(); painter.save(); QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. painter.translate(canvasWidget->x() - m_documentOffset.x(), canvasWidget->y() - m_documentOffset.y()); QPointF offset = vc->documentToView(m_draggedShape->position()); painter.setOpacity(0.6); painter.translate(offset.x(), offset.y()); painter.setRenderHint(QPainter::Antialiasing); KoShapePaintingContext paintContext; //FIXME m_draggedShape->paint(painter, *vc, paintContext); painter.restore(); } } void Viewport::resetLayout() { // Determine the area we have to show QRect viewRect(m_documentOffset, size()); const int viewH = viewRect.height(); const int viewW = viewRect.width(); if (m_canvas) { QRect geom = QRect(0, 0, viewW, viewH); if (m_canvas->geometry() != geom) { m_canvas->setGeometry(geom); m_canvas->update(); } } emit sizeChanged(); #if 0 debugFlake <<"View port geom:" << geometry(); if (m_canvas) debugFlake <<"Canvas widget geom:" << m_canvas->geometry(); #endif } diff --git a/libs/flake/KoCanvasControllerWidgetViewport_p.h b/libs/flake/KoCanvasControllerWidgetViewport_p.h index 5c56595b07..4ea532daaa 100644 --- a/libs/flake/KoCanvasControllerWidgetViewport_p.h +++ b/libs/flake/KoCanvasControllerWidgetViewport_p.h @@ -1,85 +1,85 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2007-2010 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCANVASCONTROLLERWIDGETVIEWPORT_P_H #define KOCANVASCONTROLLERWIDGETVIEWPORT_P_H #include #include #include class KoCanvasControllerWidget; class KoShape; class Viewport : public QWidget { Q_OBJECT public: explicit Viewport(KoCanvasControllerWidget *parent); ~Viewport() override {} void setCanvas(QWidget *canvas); QWidget *canvas() const { return m_canvas; } - void setDocumentSize(const QSize &size); + void setDocumentSize(const QSizeF &size); public Q_SLOTS: void documentOffsetMoved(const QPoint &); Q_SIGNALS: void sizeChanged(); public: void handleDragEnterEvent(QDragEnterEvent *event); void handleDropEvent(QDropEvent *event); void handleDragMoveEvent(QDragMoveEvent *event); void handleDragLeaveEvent(QDragLeaveEvent *event); void handlePaintEvent(QPainter &gc, QPaintEvent *event); void setMargin(int margin) { m_margin = margin; resetLayout(); } private: QPointF correctPosition(const QPoint &point) const; void repaint(KoShape *shape); /** Decides whether the containing canvas widget should be as big as the viewport (i.e., no margins are visible) or whether there are margins to be left blank, and then places the canvas widget accordingly. */ void resetLayout(); private: KoCanvasControllerWidget *m_parent; KoShape *m_draggedShape; QWidget *m_canvas; - QSize m_documentSize; // Size in pixels of the document + QSizeF m_documentSize; // Size in pixels of the document QPoint m_documentOffset; // Place where the canvas widget should int m_margin; // The viewport margin around the document }; #endif diff --git a/libs/ui/KisView.cpp b/libs/ui/KisView.cpp index 3d69a2244e..c5d99788d4 100644 --- a/libs/ui/KisView.cpp +++ b/libs/ui/KisView.cpp @@ -1,1062 +1,1062 @@ /* * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisView.h" #include "KisView_p.h" #include #include #include #include "KoPageLayout.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_config.h" #include "KisDocument.h" #include "kis_image_manager.h" #include "KisMainWindow.h" #include "kis_mimedata.h" #include "kis_mirror_axis.h" #include "kis_node_commands_adapter.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisPrintJob.h" #include "kis_shape_controller.h" #include "kis_tool_freehand.h" #include "KisViewManager.h" #include "kis_zoom_manager.h" #include "kis_statusbar.h" #include "kis_painting_assistants_decoration.h" #include "KisReferenceImagesDecoration.h" #include "kis_progress_widget.h" #include "kis_signal_compressor.h" #include "kis_filter_manager.h" #include "kis_file_layer.h" #include "krita_utils.h" #include "input/kis_input_manager.h" #include "KisRemoteFileFetcher.h" #include "kis_selection_manager.h" //static QString KisView::newObjectName() { static int s_viewIFNumber = 0; QString name; name.setNum(s_viewIFNumber++); name.prepend("view_"); return name; } bool KisView::s_firstView = true; class Q_DECL_HIDDEN KisView::Private { public: Private(KisView *_q, KisDocument *document, KoCanvasResourceProvider *resourceManager, KActionCollection *actionCollection) : actionCollection(actionCollection) , viewConverter() , canvasController(_q, actionCollection) , canvas(&viewConverter, resourceManager, _q, document->shapeController()) , zoomManager(_q, &this->viewConverter, &this->canvasController) , paintingAssistantsDecoration(new KisPaintingAssistantsDecoration(_q)) , referenceImagesDecoration(new KisReferenceImagesDecoration(_q, document)) , floatingMessageCompressor(100, KisSignalCompressor::POSTPONE) { } bool inOperation; //in the middle of an operation (no screen refreshing)? QPointer document; // our KisDocument QWidget *tempActiveWidget = 0; /** * Signals the document has been deleted. Can't use document==0 since this * only happens in ~QObject, and views get deleted by ~KisDocument. * XXX: either provide a better justification to do things this way, or * rework the mechanism. */ bool documentDeleted = false; KActionCollection* actionCollection; KisCoordinatesConverter viewConverter; KisCanvasController canvasController; KisCanvas2 canvas; KisZoomManager zoomManager; KisViewManager *viewManager = 0; KisNodeSP currentNode; KisPaintingAssistantsDecorationSP paintingAssistantsDecoration; KisReferenceImagesDecorationSP referenceImagesDecoration; bool isCurrent = false; bool showFloatingMessage = false; QPointer savedFloatingMessage; KisSignalCompressor floatingMessageCompressor; QMdiSubWindow *subWindow{nullptr}; bool softProofing = false; bool gamutCheck = false; // Hmm sorry for polluting the private class with such a big inner class. // At the beginning it was a little struct :) class StatusBarItem { public: StatusBarItem(QWidget * widget, int stretch, bool permanent) : m_widget(widget), m_stretch(stretch), m_permanent(permanent), m_connected(false), m_hidden(false) {} bool operator==(const StatusBarItem& rhs) { return m_widget == rhs.m_widget; } bool operator!=(const StatusBarItem& rhs) { return m_widget != rhs.m_widget; } QWidget * widget() const { return m_widget; } void ensureItemShown(QStatusBar * sb) { Q_ASSERT(m_widget); if (!m_connected) { if (m_permanent) sb->addPermanentWidget(m_widget, m_stretch); else sb->addWidget(m_widget, m_stretch); if(!m_hidden) m_widget->show(); m_connected = true; } } void ensureItemHidden(QStatusBar * sb) { if (m_connected) { m_hidden = m_widget->isHidden(); sb->removeWidget(m_widget); m_widget->hide(); m_connected = false; } } private: QWidget * m_widget = 0; int m_stretch; bool m_permanent; bool m_connected = false; bool m_hidden = false; }; }; KisView::KisView(KisDocument *document, KoCanvasResourceProvider *resourceManager, KActionCollection *actionCollection, QWidget *parent) : QWidget(parent) , d(new Private(this, document, resourceManager, actionCollection)) { Q_ASSERT(document); connect(document, SIGNAL(titleModified(QString,bool)), this, SIGNAL(titleModified(QString,bool))); setObjectName(newObjectName()); d->document = document; setFocusPolicy(Qt::StrongFocus); QStatusBar * sb = statusBar(); if (sb) { // No statusbar in e.g. konqueror connect(d->document, SIGNAL(statusBarMessage(QString,int)), this, SLOT(slotSavingStatusMessage(QString,int))); connect(d->document, SIGNAL(clearStatusBarMessage()), this, SLOT(slotClearStatusText())); } d->canvas.setup(); KisConfig cfg(false); d->canvasController.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->canvasController.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->canvasController.setVastScrolling(cfg.vastScrolling()); d->canvasController.setCanvas(&d->canvas); d->zoomManager.setup(d->actionCollection); connect(&d->canvasController, SIGNAL(documentSizeChanged()), &d->zoomManager, SLOT(slotScrollAreaSizeChanged())); setAcceptDrops(true); connect(d->document, SIGNAL(sigLoadingFinished()), this, SLOT(slotLoadingFinished())); connect(d->document, SIGNAL(sigSavingFinished()), this, SLOT(slotSavingFinished())); d->canvas.addDecoration(d->referenceImagesDecoration); d->referenceImagesDecoration->setVisible(true); d->canvas.addDecoration(d->paintingAssistantsDecoration); d->paintingAssistantsDecoration->setVisible(true); d->showFloatingMessage = cfg.showCanvasMessages(); d->zoomManager.updateScreenResolution(this); } KisView::~KisView() { if (d->viewManager) { if (d->viewManager->filterManager()->isStrokeRunning()) { d->viewManager->filterManager()->cancel(); } d->viewManager->mainWindow()->notifyChildViewDestroyed(this); } KoToolManager::instance()->removeCanvasController(&d->canvasController); d->canvasController.setCanvas(0); KisPart::instance()->removeView(this); delete d; } void KisView::notifyCurrentStateChanged(bool isCurrent) { d->isCurrent = isCurrent; if (!d->isCurrent && d->savedFloatingMessage) { d->savedFloatingMessage->removeMessage(); } KisInputManager *inputManager = globalInputManager(); if (d->isCurrent) { inputManager->attachPriorityEventFilter(&d->canvasController); } else { inputManager->detachPriorityEventFilter(&d->canvasController); } /** * When current view is changed, currently selected node is also changed, * therefore we should update selection overlay mask */ viewManager()->selectionManager()->selectionChanged(); } bool KisView::isCurrent() const { return d->isCurrent; } void KisView::setShowFloatingMessage(bool show) { d->showFloatingMessage = show; } void KisView::showFloatingMessage(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment) { if (!d->viewManager) return; if(d->isCurrent && d->showFloatingMessage && d->viewManager->qtMainWindow()) { if (d->savedFloatingMessage) { d->savedFloatingMessage->tryOverrideMessage(message, icon, timeout, priority, alignment); } else { d->savedFloatingMessage = new KisFloatingMessage(message, this->canvasBase()->canvasWidget(), false, timeout, priority, alignment); d->savedFloatingMessage->setShowOverParent(true); d->savedFloatingMessage->setIcon(icon); connect(&d->floatingMessageCompressor, SIGNAL(timeout()), d->savedFloatingMessage, SLOT(showMessage())); d->floatingMessageCompressor.start(); } } } bool KisView::canvasIsMirrored() const { return d->canvas.xAxisMirrored() || d->canvas.yAxisMirrored(); } void KisView::setViewManager(KisViewManager *view) { d->viewManager = view; KoToolManager::instance()->addController(&d->canvasController); KoToolManager::instance()->registerToolActions(d->actionCollection, &d->canvasController); dynamic_cast(d->document->shapeController())->setInitialShapeForCanvas(&d->canvas); if (resourceProvider()) { resourceProvider()->slotImageSizeChanged(); } if (d->viewManager && d->viewManager->nodeManager()) { d->viewManager->nodeManager()->nodesUpdated(); } connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(slotImageSizeChanged(QPointF,QPointF))); connect(image(), SIGNAL(sigResolutionChanged(double,double)), this, SLOT(slotImageResolutionChanged())); // executed in a context of an image thread connect(image(), SIGNAL(sigNodeAddedAsync(KisNodeSP)), SLOT(slotImageNodeAdded(KisNodeSP)), Qt::DirectConnection); // executed in a context of the gui thread connect(this, SIGNAL(sigContinueAddNode(KisNodeSP)), SLOT(slotContinueAddNode(KisNodeSP)), Qt::AutoConnection); // executed in a context of an image thread connect(image(), SIGNAL(sigRemoveNodeAsync(KisNodeSP)), SLOT(slotImageNodeRemoved(KisNodeSP)), Qt::DirectConnection); // executed in a context of the gui thread connect(this, SIGNAL(sigContinueRemoveNode(KisNodeSP)), SLOT(slotContinueRemoveNode(KisNodeSP)), Qt::AutoConnection); d->viewManager->updateGUI(); KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush"); } KisViewManager* KisView::viewManager() const { return d->viewManager; } void KisView::slotImageNodeAdded(KisNodeSP node) { emit sigContinueAddNode(node); } void KisView::slotContinueAddNode(KisNodeSP newActiveNode) { /** * When deleting the last layer, root node got selected. We should * fix it when the first layer is added back. * * Here we basically reimplement what Qt's view/model do. But * since they are not connected, we should do it manually. */ if (!d->isCurrent && (!d->currentNode || !d->currentNode->parent())) { d->currentNode = newActiveNode; } } void KisView::slotImageNodeRemoved(KisNodeSP node) { emit sigContinueRemoveNode(KritaUtils::nearestNodeAfterRemoval(node)); } void KisView::slotContinueRemoveNode(KisNodeSP newActiveNode) { if (!d->isCurrent) { d->currentNode = newActiveNode; } } KoZoomController *KisView::zoomController() const { return d->zoomManager.zoomController(); } KisZoomManager *KisView::zoomManager() const { return &d->zoomManager; } KisCanvasController *KisView::canvasController() const { return &d->canvasController; } KisCanvasResourceProvider *KisView::resourceProvider() const { if (d->viewManager) { return d->viewManager->canvasResourceProvider(); } return 0; } KisInputManager* KisView::globalInputManager() const { return d->viewManager ? d->viewManager->inputManager() : 0; } KisCanvas2 *KisView::canvasBase() const { return &d->canvas; } KisImageWSP KisView::image() const { if (d->document) { return d->document->image(); } return 0; } KisCoordinatesConverter *KisView::viewConverter() const { return &d->viewConverter; } void KisView::dragEnterEvent(QDragEnterEvent *event) { //qDebug() << "KisView::dragEnterEvent formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage(); if (event->mimeData()->hasImage() || event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node")) { event->accept(); // activate view if it should accept the drop this->setFocus(); } else { event->ignore(); } } void KisView::dropEvent(QDropEvent *event) { KisImageWSP kisimage = image(); Q_ASSERT(kisimage); QPoint cursorPos = canvasBase()->coordinatesConverter()->widgetToImage(event->pos()).toPoint(); QRect imageBounds = kisimage->bounds(); QPoint pasteCenter; bool forceRecenter; if (event->keyboardModifiers() & Qt::ShiftModifier && imageBounds.contains(cursorPos)) { pasteCenter = cursorPos; forceRecenter = true; } else { pasteCenter = imageBounds.center(); forceRecenter = false; } //qDebug() << "KisView::dropEvent() formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage(); if (event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasImage()) { KisShapeController *kritaShapeController = dynamic_cast(d->document->shapeController()); QList nodes = KisMimeData::loadNodes(event->mimeData(), imageBounds, pasteCenter, forceRecenter, kisimage, kritaShapeController); Q_FOREACH (KisNodeSP node, nodes) { if (node) { KisNodeCommandsAdapter adapter(viewManager()); if (!viewManager()->nodeManager()->activeLayer()) { adapter.addNode(node, kisimage->rootLayer() , 0); } else { adapter.addNode(node, viewManager()->nodeManager()->activeLayer()->parent(), viewManager()->nodeManager()->activeLayer()); } } } } else if (event->mimeData()->hasUrls()) { QList urls = event->mimeData()->urls(); if (urls.length() > 0) { QMenu popup; popup.setObjectName("drop_popup"); QAction *insertAsNewLayer = new QAction(i18n("Insert as New Layer"), &popup); QAction *insertManyLayers = new QAction(i18n("Insert Many Layers"), &popup); QAction *insertAsNewFileLayer = new QAction(i18n("Insert as New File Layer"), &popup); QAction *insertManyFileLayers = new QAction(i18n("Insert Many File Layers"), &popup); QAction *openInNewDocument = new QAction(i18n("Open in New Document"), &popup); QAction *openManyDocuments = new QAction(i18n("Open Many Documents"), &popup); QAction *insertAsReferenceImage = new QAction(i18n("Insert as Reference Image"), &popup); QAction *insertAsReferenceImages = new QAction(i18n("Insert as Reference Images"), &popup); QAction *cancel = new QAction(i18n("Cancel"), &popup); popup.addAction(insertAsNewLayer); popup.addAction(insertAsNewFileLayer); popup.addAction(openInNewDocument); popup.addAction(insertAsReferenceImage); popup.addAction(insertManyLayers); popup.addAction(insertManyFileLayers); popup.addAction(openManyDocuments); popup.addAction(insertAsReferenceImages); insertAsNewLayer->setEnabled(image() && urls.count() == 1); insertAsNewFileLayer->setEnabled(image() && urls.count() == 1); openInNewDocument->setEnabled(urls.count() == 1); insertAsReferenceImage->setEnabled(image() && urls.count() == 1); insertManyLayers->setEnabled(image() && urls.count() > 1); insertManyFileLayers->setEnabled(image() && urls.count() > 1); openManyDocuments->setEnabled(urls.count() > 1); insertAsReferenceImages->setEnabled(image() && urls.count() > 1); popup.addSeparator(); popup.addAction(cancel); QAction *action = popup.exec(QCursor::pos()); if (action != 0 && action != cancel) { QTemporaryFile *tmp = 0; for (QUrl url : urls) { if (!url.isLocalFile()) { // download the file and substitute the url KisRemoteFileFetcher fetcher; tmp = new QTemporaryFile(); tmp->setAutoRemove(true); if (!fetcher.fetchFile(url, tmp)) { qWarning() << "Fetching" << url << "failed"; continue; } url = url.fromLocalFile(tmp->fileName()); } if (url.isLocalFile()) { if (action == insertAsNewLayer || action == insertManyLayers) { d->viewManager->imageManager()->importImage(url); activateWindow(); } else if (action == insertAsNewFileLayer || action == insertManyFileLayers) { KisNodeCommandsAdapter adapter(viewManager()); KisFileLayer *fileLayer = new KisFileLayer(image(), "", url.toLocalFile(), KisFileLayer::None, image()->nextLayerName(), OPACITY_OPAQUE_U8); adapter.addNode(fileLayer, viewManager()->activeNode()->parent(), viewManager()->activeNode()); } else if (action == openInNewDocument || action == openManyDocuments) { if (mainWindow()) { mainWindow()->openDocument(url, KisMainWindow::None); } } else if (action == insertAsReferenceImage || action == insertAsReferenceImages) { auto *reference = KisReferenceImage::fromFile(url.toLocalFile(), d->viewConverter, this); if (reference) { reference->setPosition(d->viewConverter.imageToDocument(cursorPos)); d->referenceImagesDecoration->addReferenceImage(reference); KoToolManager::instance()->switchToolRequested("ToolReferenceImages"); } } } delete tmp; tmp = 0; } } } } } void KisView::dragMoveEvent(QDragMoveEvent *event) { //qDebug() << "KisView::dragMoveEvent"; if (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasFormat("application/x-qt-image")) { event->accept(); } } KisDocument *KisView::document() const { return d->document; } void KisView::setDocument(KisDocument *document) { d->document->disconnect(this); d->document = document; QStatusBar *sb = statusBar(); if (sb) { // No statusbar in e.g. konqueror connect(d->document, SIGNAL(statusBarMessage(QString,int)), this, SLOT(slotSavingStatusMessage(QString,int))); connect(d->document, SIGNAL(clearStatusBarMessage()), this, SLOT(slotClearStatusText())); } } void KisView::setDocumentDeleted() { d->documentDeleted = true; } QPrintDialog *KisView::createPrintDialog(KisPrintJob *printJob, QWidget *parent) { Q_UNUSED(parent); QPrintDialog *printDialog = new QPrintDialog(&printJob->printer(), this); printDialog->setMinMax(printJob->printer().fromPage(), printJob->printer().toPage()); printDialog->setEnabledOptions(printJob->printDialogOptions()); return printDialog; } KisMainWindow * KisView::mainWindow() const { return dynamic_cast(window()); } void KisView::setSubWindow(QMdiSubWindow *subWindow) { d->subWindow = subWindow; } QStatusBar * KisView::statusBar() const { KisMainWindow *mw = mainWindow(); return mw ? mw->statusBar() : 0; } void KisView::slotSavingStatusMessage(const QString &text, int timeout, bool isAutoSaving) { QStatusBar *sb = statusBar(); if (sb) { sb->showMessage(text, timeout); } KisConfig cfg(true); if (!sb || sb->isHidden() || (!isAutoSaving && cfg.forceShowSaveMessages()) || (cfg.forceShowAutosaveMessages() && isAutoSaving)) { viewManager()->showFloatingMessage(text, QIcon()); } } void KisView::slotClearStatusText() { QStatusBar *sb = statusBar(); if (sb) { sb->clearMessage(); } } QList KisView::createChangeUnitActions(bool addPixelUnit) { UnitActionGroup* unitActions = new UnitActionGroup(d->document, addPixelUnit, this); return unitActions->actions(); } void KisView::closeEvent(QCloseEvent *event) { // Check whether we're the last (user visible) view int viewCount = KisPart::instance()->viewCount(document()); if (viewCount > 1 || !isVisible()) { // there are others still, so don't bother the user event->accept(); return; } if (queryClose()) { event->accept(); return; } event->ignore(); } bool KisView::queryClose() { if (!document()) return true; document()->waitForSavingToComplete(); if (document()->isModified()) { QString name; if (document()->documentInfo()) { name = document()->documentInfo()->aboutInfo("title"); } if (name.isEmpty()) name = document()->url().fileName(); if (name.isEmpty()) name = i18n("Untitled"); int res = QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("

The document '%1' has been modified.

Do you want to save it?

", name), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : { bool isNative = (document()->mimeType() == document()->nativeFormatMimeType()); if (!viewManager()->mainWindow()->saveDocument(document(), !isNative, false)) return false; break; } case QMessageBox::No : { KisImageSP image = document()->image(); image->requestStrokeCancellation(); viewManager()->blockUntilOperationsFinishedForced(image); document()->removeAutoSaveFiles(document()->localFilePath(), document()->isRecovered()); document()->setModified(false); // Now when queryClose() is called by closeEvent it won't do anything. break; } default : // case QMessageBox::Cancel : return false; } } return true; } void KisView::slotScreenChanged() { d->zoomManager.updateScreenResolution(this); } void KisView::slotThemeChanged(QPalette pal) { this->setPalette(pal); for (int i=0; ichildren().size();i++) { QWidget *w = qobject_cast ( this->children().at(i)); if (w) { w->setPalette(pal); } } if (canvasBase()) { canvasBase()->canvasWidget()->setPalette(pal); } if (canvasController()) { canvasController()->setPalette(pal); } } void KisView::resetImageSizeAndScroll(bool changeCentering, const QPointF &oldImageStillPoint, const QPointF &newImageStillPoint) { const KisCoordinatesConverter *converter = d->canvas.coordinatesConverter(); QPointF oldPreferredCenter = d->canvasController.preferredCenter(); /** * Calculating the still point in old coordinates depending on the * parameters given */ QPointF oldStillPoint; if (changeCentering) { oldStillPoint = converter->imageToWidget(oldImageStillPoint) + converter->documentOffset(); } else { - QSize oldDocumentSize = d->canvasController.documentSize(); + QSizeF oldDocumentSize = d->canvasController.documentSize(); oldStillPoint = QPointF(0.5 * oldDocumentSize.width(), 0.5 * oldDocumentSize.height()); } /** * Updating the document size */ QSizeF size(image()->width() / image()->xRes(), image()->height() / image()->yRes()); KoZoomController *zc = d->zoomManager.zoomController(); zc->setZoom(KoZoomMode::ZOOM_CONSTANT, zc->zoomAction()->effectiveZoom()); zc->setPageSize(size); zc->setDocumentSize(size, true); /** * Calculating the still point in new coordinates depending on the * parameters given */ QPointF newStillPoint; if (changeCentering) { newStillPoint = converter->imageToWidget(newImageStillPoint) + converter->documentOffset(); } else { - QSize newDocumentSize = d->canvasController.documentSize(); + QSizeF newDocumentSize = d->canvasController.documentSize(); newStillPoint = QPointF(0.5 * newDocumentSize.width(), 0.5 * newDocumentSize.height()); } d->canvasController.setPreferredCenter(oldPreferredCenter - oldStillPoint + newStillPoint); } void KisView::syncLastActiveNodeToDocument() { KisDocument *doc = document(); if (doc) { doc->setPreActivatedNode(d->currentNode); } } void KisView::saveViewState(KisPropertiesConfiguration &config) const { config.setProperty("file", d->document->url()); config.setProperty("window", mainWindow()->windowStateConfig().name()); if (d->subWindow) { config.setProperty("geometry", d->subWindow->saveGeometry().toBase64()); } config.setProperty("zoomMode", (int)zoomController()->zoomMode()); config.setProperty("zoom", d->canvas.coordinatesConverter()->zoom()); d->canvasController.saveCanvasState(config); } void KisView::restoreViewState(const KisPropertiesConfiguration &config) { if (d->subWindow) { QByteArray geometry = QByteArray::fromBase64(config.getString("geometry", "").toLatin1()); d->subWindow->restoreGeometry(QByteArray::fromBase64(geometry)); } qreal zoom = config.getFloat("zoom", 1.0f); int zoomMode = config.getInt("zoomMode", (int)KoZoomMode::ZOOM_PAGE); d->zoomManager.zoomController()->setZoom((KoZoomMode::Mode)zoomMode, zoom); d->canvasController.restoreCanvasState(config); } void KisView::setCurrentNode(KisNodeSP node) { d->currentNode = node; d->canvas.slotTrySwitchShapeManager(); syncLastActiveNodeToDocument(); } KisNodeSP KisView::currentNode() const { return d->currentNode; } KisLayerSP KisView::currentLayer() const { KisNodeSP node; KisMaskSP mask = currentMask(); if (mask) { node = mask->parent(); } else { node = d->currentNode; } return qobject_cast(node.data()); } KisMaskSP KisView::currentMask() const { return dynamic_cast(d->currentNode.data()); } KisSelectionSP KisView::selection() { KisLayerSP layer = currentLayer(); if (layer) return layer->selection(); // falls through to the global // selection, or 0 in the end if (image()) { return image()->globalSelection(); } return 0; } void KisView::slotSoftProofing(bool softProofing) { d->softProofing = softProofing; QString message; if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F")) { message = i18n("Soft Proofing doesn't work in floating point."); viewManager()->showFloatingMessage(message,QIcon()); return; } if (softProofing){ message = i18n("Soft Proofing turned on."); } else { message = i18n("Soft Proofing turned off."); } viewManager()->showFloatingMessage(message,QIcon()); canvasBase()->slotSoftProofing(softProofing); } void KisView::slotGamutCheck(bool gamutCheck) { d->gamutCheck = gamutCheck; QString message; if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F")) { message = i18n("Gamut Warnings don't work in floating point."); viewManager()->showFloatingMessage(message,QIcon()); return; } if (gamutCheck){ message = i18n("Gamut Warnings turned on."); if (!d->softProofing){ message += "\n "+i18n("But Soft Proofing is still off."); } } else { message = i18n("Gamut Warnings turned off."); } viewManager()->showFloatingMessage(message,QIcon()); canvasBase()->slotGamutCheck(gamutCheck); } bool KisView::softProofing() { return d->softProofing; } bool KisView::gamutCheck() { return d->gamutCheck; } void KisView::slotLoadingFinished() { if (!document()) return; /** * Cold-start of image size/resolution signals */ slotImageResolutionChanged(); if (image()->locked()) { // If this is the first view on the image, the image will have been locked // so unlock it. image()->blockSignals(false); image()->unlock(); } canvasBase()->initializeImage(); /** * Dirty hack alert */ d->zoomManager.zoomController()->setAspectMode(true); if (viewConverter()) { viewConverter()->setZoomMode(KoZoomMode::ZOOM_PAGE); } connect(image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), this, SIGNAL(sigColorSpaceChanged(const KoColorSpace*))); connect(image(), SIGNAL(sigProfileChanged(const KoColorProfile*)), this, SIGNAL(sigProfileChanged(const KoColorProfile*))); connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SIGNAL(sigSizeChanged(QPointF,QPointF))); KisNodeSP activeNode = document()->preActivatedNode(); if (!activeNode) { activeNode = image()->rootLayer()->lastChild(); } while (activeNode && !activeNode->inherits("KisLayer")) { activeNode = activeNode->prevSibling(); } setCurrentNode(activeNode); connect(d->viewManager->mainWindow(), SIGNAL(screenChanged()), SLOT(slotScreenChanged())); zoomManager()->updateImageBoundsSnapping(); } void KisView::slotSavingFinished() { if (d->viewManager && d->viewManager->mainWindow()) { d->viewManager->mainWindow()->updateCaption(); } } KisPrintJob * KisView::createPrintJob() { return new KisPrintJob(image()); } void KisView::slotImageResolutionChanged() { resetImageSizeAndScroll(false); zoomManager()->updateImageBoundsSnapping(); zoomManager()->updateGUI(); // update KoUnit value for the document if (resourceProvider()) { resourceProvider()->resourceManager()-> setResource(KoCanvasResourceProvider::Unit, d->canvas.unit()); } } void KisView::slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint) { resetImageSizeAndScroll(true, oldStillPoint, newStillPoint); zoomManager()->updateImageBoundsSnapping(); zoomManager()->updateGUI(); } void KisView::closeView() { d->subWindow->close(); } diff --git a/libs/ui/canvas/kis_canvas_controller.cpp b/libs/ui/canvas/kis_canvas_controller.cpp index a7d5c36b5f..2ab25dec3b 100644 --- a/libs/ui/canvas/kis_canvas_controller.cpp +++ b/libs/ui/canvas/kis_canvas_controller.cpp @@ -1,398 +1,398 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_canvas_controller.h" #include #include #include #include #include #include "kis_canvas_decoration.h" #include "kis_paintop_transformation_connector.h" #include "kis_coordinates_converter.h" #include "kis_canvas2.h" #include "opengl/kis_opengl_canvas2.h" #include "KisDocument.h" #include "kis_image.h" #include "KisViewManager.h" #include "KisView.h" #include "krita_utils.h" #include "kis_config.h" #include "kis_signal_compressor_with_param.h" #include "kis_config_notifier.h" static const int gRulersUpdateDelay = 80 /* ms */; struct KisCanvasController::Private { Private(KisCanvasController *qq) : q(qq), paintOpTransformationConnector(0) { using namespace std::placeholders; std::function callback( std::bind(&KisCanvasController::Private::emitPointerPositionChangedSignals, this, _1)); mousePositionCompressor.reset( new KisSignalCompressorWithParam( gRulersUpdateDelay, callback, KisSignalCompressor::FIRST_ACTIVE)); } QPointer view; KisCoordinatesConverter *coordinatesConverter; KisCanvasController *q; KisPaintopTransformationConnector *paintOpTransformationConnector; QScopedPointer > mousePositionCompressor; void emitPointerPositionChangedSignals(QPoint pointerPos); void updateDocumentSizeAfterTransform(); void showRotationValueOnCanvas(); void showMirrorStateOnCanvas(); }; void KisCanvasController::Private::emitPointerPositionChangedSignals(QPoint pointerPos) { if (!coordinatesConverter) return; QPointF documentPos = coordinatesConverter->widgetToDocument(pointerPos); q->proxyObject->emitDocumentMousePositionChanged(documentPos); q->proxyObject->emitCanvasMousePositionChanged(pointerPos); } void KisCanvasController::Private::updateDocumentSizeAfterTransform() { // round the size of the area to the nearest integer instead of getting aligned rect QSize widgetSize = coordinatesConverter->imageRectInWidgetPixels().toRect().size(); q->updateDocumentSize(widgetSize, true); KisCanvas2 *kritaCanvas = dynamic_cast(q->canvas()); Q_ASSERT(kritaCanvas); kritaCanvas->notifyZoomChanged(); } KisCanvasController::KisCanvasController(QPointerparent, KActionCollection * actionCollection) : KoCanvasControllerWidget(actionCollection, parent), m_d(new Private(this)) { m_d->view = parent; } KisCanvasController::~KisCanvasController() { delete m_d; } void KisCanvasController::setCanvas(KoCanvasBase *canvas) { if (canvas) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas); KIS_SAFE_ASSERT_RECOVER_RETURN(kritaCanvas); m_d->coordinatesConverter = const_cast(kritaCanvas->coordinatesConverter()); m_d->paintOpTransformationConnector = new KisPaintopTransformationConnector(kritaCanvas, this); } else { m_d->coordinatesConverter = 0; delete m_d->paintOpTransformationConnector; m_d->paintOpTransformationConnector = 0; } KoCanvasControllerWidget::setCanvas(canvas); } void KisCanvasController::activate() { KoCanvasControllerWidget::activate(); } QPointF KisCanvasController::currentCursorPosition() const { KoCanvasBase *canvas = m_d->view->canvasBase(); QWidget *canvasWidget = canvas->canvasWidget(); const QPointF cursorPosWidget = canvasWidget->mapFromGlobal(QCursor::pos()); return m_d->coordinatesConverter->widgetToDocument(cursorPosWidget); } void KisCanvasController::keyPressEvent(QKeyEvent *event) { /** * Dirty Hack Alert: * Do not call the KoCanvasControllerWidget::keyPressEvent() * to avoid activation of Pan and Default tool activation shortcuts */ Q_UNUSED(event); } void KisCanvasController::wheelEvent(QWheelEvent *event) { /** * Dirty Hack Alert: * Do not call the KoCanvasControllerWidget::wheelEvent() * to disable the default behavior of KoCanvasControllerWidget and QAbstractScrollArea */ Q_UNUSED(event); } bool KisCanvasController::eventFilter(QObject *watched, QEvent *event) { KoCanvasBase *canvas = this->canvas(); if (!canvas || !canvas->canvasWidget() || canvas->canvasWidget() != watched) return false; if (event->type() == QEvent::MouseMove) { QMouseEvent *mevent = static_cast(event); m_d->mousePositionCompressor->start(mevent->pos()); } else if (event->type() == QEvent::TabletMove) { QTabletEvent *tevent = static_cast(event); m_d->mousePositionCompressor->start(tevent->pos()); } else if (event->type() == QEvent::FocusIn) { m_d->view->syncLastActiveNodeToDocument(); } return false; } -void KisCanvasController::updateDocumentSize(const QSize &sz, bool recalculateCenter) +void KisCanvasController::updateDocumentSize(const QSizeF &sz, bool recalculateCenter) { KoCanvasControllerWidget::updateDocumentSize(sz, recalculateCenter); emit documentSizeChanged(); } void KisCanvasController::Private::showMirrorStateOnCanvas() { bool isXMirrored = coordinatesConverter->xAxisMirrored(); view->viewManager()-> showFloatingMessage( i18nc("floating message about mirroring", "Horizontal mirroring: %1 ", isXMirrored ? i18n("ON") : i18n("OFF")), QIcon(), 500, KisFloatingMessage::Low); } void KisCanvasController::mirrorCanvas(bool enable) { QPoint newOffset = m_d->coordinatesConverter->mirror(m_d->coordinatesConverter->widgetCenterPoint(), enable, false); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->paintOpTransformationConnector->notifyTransformationChanged(); m_d->showMirrorStateOnCanvas(); } void KisCanvasController::Private::showRotationValueOnCanvas() { qreal rotationAngle = coordinatesConverter->rotationAngle(); view->viewManager()-> showFloatingMessage( i18nc("floating message about rotation", "Rotation: %1° ", KritaUtils::prettyFormatReal(rotationAngle)), QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter); } void KisCanvasController::rotateCanvas(qreal angle, const QPointF ¢er) { QPoint newOffset = m_d->coordinatesConverter->rotate(center, angle); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->paintOpTransformationConnector->notifyTransformationChanged(); m_d->showRotationValueOnCanvas(); } void KisCanvasController::rotateCanvas(qreal angle) { rotateCanvas(angle, m_d->coordinatesConverter->widgetCenterPoint()); } void KisCanvasController::rotateCanvasRight15() { rotateCanvas(15.0); } void KisCanvasController::rotateCanvasLeft15() { rotateCanvas(-15.0); } qreal KisCanvasController::rotation() const { return m_d->coordinatesConverter->rotationAngle(); } void KisCanvasController::resetCanvasRotation() { QPoint newOffset = m_d->coordinatesConverter->resetRotation(m_d->coordinatesConverter->widgetCenterPoint()); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->paintOpTransformationConnector->notifyTransformationChanged(); m_d->showRotationValueOnCanvas(); } void KisCanvasController::slotToggleWrapAroundMode(bool value) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); if (!canvas()->canvasIsOpenGL() && value) { m_d->view->viewManager()->showFloatingMessage(i18n("You are activating wrap-around mode, but have not enabled OpenGL.\n" "To visualize wrap-around mode, enable OpenGL."), QIcon()); } kritaCanvas->setWrapAroundViewingMode(value); kritaCanvas->image()->setWrapAroundModePermitted(value); } bool KisCanvasController::wrapAroundMode() const { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->wrapAroundViewingMode(); } void KisCanvasController::slotTogglePixelGrid(bool value) { KisConfig cfg(false); cfg.enablePixelGrid(value); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); } void KisCanvasController::slotToggleLevelOfDetailMode(bool value) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); kritaCanvas->setLodAllowedInCanvas(value); bool result = levelOfDetailMode(); if (!value || result) { m_d->view->viewManager()->showFloatingMessage( i18n("Instant Preview Mode: %1", result ? i18n("ON") : i18n("OFF")), QIcon(), 500, KisFloatingMessage::Low); } else { QString reason; if (!kritaCanvas->canvasIsOpenGL()) { reason = i18n("Instant Preview is only supported with OpenGL activated"); } else if (kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode || kritaCanvas->openGLFilterMode() == KisOpenGL::NearestFilterMode) { QString filteringMode = kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode ? i18n("Bilinear") : i18n("Nearest Neighbour"); reason = i18n("Instant Preview is supported\n in Trilinear or High Quality filtering modes.\nCurrent mode is %1", filteringMode); } m_d->view->viewManager()->showFloatingMessage( i18n("Failed activating Instant Preview mode!\n\n%1", reason), QIcon(), 5000, KisFloatingMessage::Low); } } bool KisCanvasController::levelOfDetailMode() const { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->lodAllowedInCanvas(); } void KisCanvasController::saveCanvasState(KisPropertiesConfiguration &config) const { const QPointF ¢er = preferredCenter(); config.setProperty("panX", center.x()); config.setProperty("panY", center.y()); config.setProperty("rotation", rotation()); config.setProperty("mirror", m_d->coordinatesConverter->xAxisMirrored()); config.setProperty("wrapAround", wrapAroundMode()); config.setProperty("enableInstantPreview", levelOfDetailMode()); } void KisCanvasController::restoreCanvasState(const KisPropertiesConfiguration &config) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); mirrorCanvas(config.getBool("mirror", false)); rotateCanvas(config.getFloat("rotation", 0.0f)); const QPointF ¢er = preferredCenter(); float panX = config.getFloat("panX", center.x()); float panY = config.getFloat("panY", center.y()); setPreferredCenter(QPointF(panX, panY)); slotToggleWrapAroundMode(config.getBool("wrapAround", false)); kritaCanvas->setLodAllowedInCanvas(config.getBool("enableInstantPreview", false)); } void KisCanvasController::resetScrollBars() { // The scrollbar value always points at the top-left corner of the // bit of image we paint. KisDocument *doc = m_d->view->document(); if (!doc) return; QRectF documentBounds = doc->documentBounds(); QRectF viewRect = m_d->coordinatesConverter->imageToWidget(documentBounds); // Cancel out any existing pan const QRectF imageBounds = m_d->view->image()->bounds(); const QRectF imageBB = m_d->coordinatesConverter->imageToWidget(imageBounds); QPointF pan = imageBB.topLeft(); viewRect.translate(-pan); int drawH = viewport()->height(); int drawW = viewport()->width(); qreal horizontalReserve = vastScrollingFactor() * drawW; qreal verticalReserve = vastScrollingFactor() * drawH; qreal xMin = viewRect.left() - horizontalReserve; qreal yMin = viewRect.top() - verticalReserve; qreal xMax = viewRect.right() - drawW + horizontalReserve; qreal yMax = viewRect.bottom() - drawH + verticalReserve; QScrollBar *hScroll = horizontalScrollBar(); QScrollBar *vScroll = verticalScrollBar(); hScroll->setRange(static_cast(xMin), static_cast(xMax)); vScroll->setRange(static_cast(yMin), static_cast(yMax)); int fontHeight = QFontMetrics(font()).height(); vScroll->setPageStep(drawH); vScroll->setSingleStep(fontHeight); hScroll->setPageStep(drawW); hScroll->setSingleStep(fontHeight); } diff --git a/libs/ui/canvas/kis_canvas_controller.h b/libs/ui/canvas/kis_canvas_controller.h index 9ac0e26f69..c1226313be 100644 --- a/libs/ui/canvas/kis_canvas_controller.h +++ b/libs/ui/canvas/kis_canvas_controller.h @@ -1,77 +1,77 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_CANVAS_CONTROLLER_H #define KIS_CANVAS_CONTROLLER_H #include #include "kritaui_export.h" #include "kis_types.h" class KConfigGroup; class KisView; class KRITAUI_EXPORT KisCanvasController : public KoCanvasControllerWidget { Q_OBJECT public: KisCanvasController(QPointerparent, KActionCollection * actionCollection); ~KisCanvasController() override; void setCanvas(KoCanvasBase *canvas) override; void keyPressEvent(QKeyEvent *event) override; void wheelEvent(QWheelEvent *event) override; bool eventFilter(QObject *watched, QEvent *event) override; - void updateDocumentSize(const QSize &sz, bool recalculateCenter) override; + void updateDocumentSize(const QSizeF &sz, bool recalculateCenter) override; void activate() override; QPointF currentCursorPosition() const override; public: using KoCanvasController::documentSize; bool wrapAroundMode() const; bool levelOfDetailMode() const; void saveCanvasState(KisPropertiesConfiguration &config) const; void restoreCanvasState(const KisPropertiesConfiguration &config); void resetScrollBars() override; public Q_SLOTS: void mirrorCanvas(bool enable); void rotateCanvas(qreal angle, const QPointF ¢er); void rotateCanvas(qreal angle); void rotateCanvasRight15(); void rotateCanvasLeft15(); qreal rotation() const; void resetCanvasRotation(); void slotToggleWrapAroundMode(bool value); void slotTogglePixelGrid(bool value); void slotToggleLevelOfDetailMode(bool value); Q_SIGNALS: void documentSizeChanged(); private: struct Private; Private * const m_d; }; #endif /* KIS_CANVAS_CONTROLLER_H */ diff --git a/libs/ui/canvas/kis_coordinates_converter.cpp b/libs/ui/canvas/kis_coordinates_converter.cpp index 401d850187..9470fd866b 100644 --- a/libs/ui/canvas/kis_coordinates_converter.cpp +++ b/libs/ui/canvas/kis_coordinates_converter.cpp @@ -1,486 +1,486 @@ /* * Copyright (c) 2010 Dmitry Kazakov * Copyright (c) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "kis_coordinates_converter.h" #include #include #include #include #include struct KisCoordinatesConverter::Private { Private(): isXAxisMirrored(false), isYAxisMirrored(false), rotationAngle(0.0), devicePixelRatio(1.0) { } KisImageWSP image; bool isXAxisMirrored; bool isYAxisMirrored; qreal rotationAngle; QSizeF canvasWidgetSize; qreal devicePixelRatio; QPointF documentOffset; QTransform flakeToWidget; QTransform imageToDocument; QTransform documentToFlake; QTransform widgetToViewport; }; /** * When vastScrolling value is less than 0.5 it is possible * that the whole scrolling area (viewport) will be smaller than * the size of the widget. In such cases the image should be * centered in the widget. Previously we used a special parameter * documentOrigin for this purpose, now the value for this * centering is calculated dynamically, helping the offset to * center the image inside the widget * * Note that the correction is null when the size of the document * plus vast scrolling reserve is larger than the widget. This * is always true for vastScrolling parameter > 0.5. */ QPointF KisCoordinatesConverter::centeringCorrection() const { KisConfig cfg(true); QSize documentSize = imageRectInWidgetPixels().toAlignedRect().size(); QPointF dPoint(documentSize.width(), documentSize.height()); QPointF wPoint(m_d->canvasWidgetSize.width(), m_d->canvasWidgetSize.height()); QPointF minOffset = -cfg.vastScrolling() * wPoint; QPointF maxOffset = dPoint - wPoint + cfg.vastScrolling() * wPoint; QPointF range = maxOffset - minOffset; range.rx() = qMin(range.x(), (qreal)0.0); range.ry() = qMin(range.y(), (qreal)0.0); range /= 2; return -range; } /** * The document offset and the position of the top left corner of the * image must always coincide, that is why we need to correct them to * and fro. * * When we change zoom level, the calculation of the new offset is * done by KoCanvasControllerWidget, that is why we just passively fix * the flakeToWidget transform to conform the offset and wait until * the canvas controller will recenter us. * * But when we do our own transformations of the canvas, like rotation * and mirroring, we cannot rely on the centering of the canvas * controller and we do it ourselves. Then we just set new offset and * return its value to be set in the canvas controller explicitly. */ void KisCoordinatesConverter::correctOffsetToTransformation() { - m_d->documentOffset = -(imageRectInWidgetPixels().topLeft() - - centeringCorrection()).toPoint(); + m_d->documentOffset = snapToDevicePixel(-(imageRectInWidgetPixels().topLeft() - + centeringCorrection())); } void KisCoordinatesConverter::correctTransformationToOffset() { QPointF topLeft = imageRectInWidgetPixels().topLeft(); QPointF diff = (-topLeft) - m_d->documentOffset; diff += centeringCorrection(); m_d->flakeToWidget *= QTransform::fromTranslate(diff.x(), diff.y()); } void KisCoordinatesConverter::recalculateTransformations() { if(!m_d->image) return; m_d->imageToDocument = QTransform::fromScale(1 / m_d->image->xRes(), 1 / m_d->image->yRes()); qreal zoomX, zoomY; KoZoomHandler::zoom(&zoomX, &zoomY); m_d->documentToFlake = QTransform::fromScale(zoomX, zoomY); correctTransformationToOffset(); QRectF irect = imageRectInWidgetPixels(); QRectF wrect = QRectF(QPoint(0,0), m_d->canvasWidgetSize); QRectF rrect = irect & wrect; QTransform reversedTransform = flakeToWidgetTransform().inverted(); QRectF canvasBounds = reversedTransform.mapRect(rrect); QPointF offset = canvasBounds.topLeft(); m_d->widgetToViewport = reversedTransform * QTransform::fromTranslate(-offset.x(), -offset.y()); } KisCoordinatesConverter::KisCoordinatesConverter() : m_d(new Private) { } KisCoordinatesConverter::~KisCoordinatesConverter() { delete m_d; } void KisCoordinatesConverter::setCanvasWidgetSize(QSizeF size) { m_d->canvasWidgetSize = size; recalculateTransformations(); } void KisCoordinatesConverter::setDevicePixelRatio(qreal value) { m_d->devicePixelRatio = value; } void KisCoordinatesConverter::setImage(KisImageWSP image) { m_d->image = image; recalculateTransformations(); } void KisCoordinatesConverter::setDocumentOffset(const QPointF& offset) { QPointF diff = m_d->documentOffset - offset; m_d->documentOffset = offset; m_d->flakeToWidget *= QTransform::fromTranslate(diff.x(), diff.y()); recalculateTransformations(); } qreal KisCoordinatesConverter::devicePixelRatio() const { return m_d->devicePixelRatio; } QPoint KisCoordinatesConverter::documentOffset() const { return QPoint(int(m_d->documentOffset.x()), int(m_d->documentOffset.y())); } qreal KisCoordinatesConverter::rotationAngle() const { return m_d->rotationAngle; } void KisCoordinatesConverter::setZoom(qreal zoom) { KoZoomHandler::setZoom(zoom); recalculateTransformations(); } qreal KisCoordinatesConverter::effectiveZoom() const { qreal scaleX, scaleY; this->imageScale(&scaleX, &scaleY); if (scaleX != scaleY) { qWarning() << "WARNING: Zoom is not isotropic!" << ppVar(scaleX) << ppVar(scaleY) << ppVar(qFuzzyCompare(scaleX, scaleY)); } // zoom by average of x and y return 0.5 * (scaleX + scaleY); } QPoint KisCoordinatesConverter::rotate(QPointF center, qreal angle) { QTransform rot; rot.rotate(angle); m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(),-center.y()); m_d->flakeToWidget *= rot; m_d->flakeToWidget *= QTransform::fromTranslate(center.x(), center.y()); m_d->rotationAngle = std::fmod(m_d->rotationAngle + angle, 360.0); correctOffsetToTransformation(); recalculateTransformations(); return m_d->documentOffset.toPoint(); } QPoint KisCoordinatesConverter::mirror(QPointF center, bool mirrorXAxis, bool mirrorYAxis) { bool keepOrientation = false; // XXX: Keep here for now, maybe some day we can restore the parameter again. bool doXMirroring = m_d->isXAxisMirrored ^ mirrorXAxis; bool doYMirroring = m_d->isYAxisMirrored ^ mirrorYAxis; qreal scaleX = doXMirroring ? -1.0 : 1.0; qreal scaleY = doYMirroring ? -1.0 : 1.0; QTransform mirror = QTransform::fromScale(scaleX, scaleY); QTransform rot; rot.rotate(m_d->rotationAngle); m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(),-center.y()); if (keepOrientation) { m_d->flakeToWidget *= rot.inverted(); } m_d->flakeToWidget *= mirror; if (keepOrientation) { m_d->flakeToWidget *= rot; } m_d->flakeToWidget *= QTransform::fromTranslate(center.x(),center.y()); if (!keepOrientation && (doXMirroring ^ doYMirroring)) { m_d->rotationAngle = -m_d->rotationAngle; } m_d->isXAxisMirrored = mirrorXAxis; m_d->isYAxisMirrored = mirrorYAxis; correctOffsetToTransformation(); recalculateTransformations(); return m_d->documentOffset.toPoint(); } bool KisCoordinatesConverter::xAxisMirrored() const { return m_d->isXAxisMirrored; } bool KisCoordinatesConverter::yAxisMirrored() const { return m_d->isYAxisMirrored; } QPoint KisCoordinatesConverter::resetRotation(QPointF center) { QTransform rot; rot.rotate(-m_d->rotationAngle); m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(), -center.y()); m_d->flakeToWidget *= rot; m_d->flakeToWidget *= QTransform::fromTranslate(center.x(), center.y()); m_d->rotationAngle = 0.0; correctOffsetToTransformation(); recalculateTransformations(); return m_d->documentOffset.toPoint(); } QTransform KisCoordinatesConverter::imageToWidgetTransform() const{ return m_d->imageToDocument * m_d->documentToFlake * m_d->flakeToWidget; } QTransform KisCoordinatesConverter::imageToDocumentTransform() const { return m_d->imageToDocument; } QTransform KisCoordinatesConverter::documentToFlakeTransform() const { return m_d->documentToFlake; } QTransform KisCoordinatesConverter::flakeToWidgetTransform() const { return m_d->flakeToWidget; } QTransform KisCoordinatesConverter::documentToWidgetTransform() const { return m_d->documentToFlake * m_d->flakeToWidget; } QTransform KisCoordinatesConverter::viewportToWidgetTransform() const { return m_d->widgetToViewport.inverted(); } QTransform KisCoordinatesConverter::imageToViewportTransform() const { return m_d->imageToDocument * m_d->documentToFlake * m_d->flakeToWidget * m_d->widgetToViewport; } void KisCoordinatesConverter::getQPainterCheckersInfo(QTransform *transform, QPointF *brushOrigin, QPolygonF *polygon, const bool scrollCheckers) const { /** * Qt has different rounding for QPainter::drawRect/drawImage. * The image is rounded mathematically, while rect in aligned * to the next integer. That causes transparent line appear on * the canvas. * * See: https://bugreports.qt.nokia.com/browse/QTBUG-22827 */ QRectF imageRect = imageRectInViewportPixels(); imageRect.adjust(0,0,-0.5,-0.5); if (scrollCheckers) { *transform = viewportToWidgetTransform(); *polygon = imageRect; *brushOrigin = imageToViewport(QPointF(0,0)); } else { *transform = QTransform(); *polygon = viewportToWidgetTransform().map(imageRect); *brushOrigin = QPoint(0,0); } } void KisCoordinatesConverter::getOpenGLCheckersInfo(const QRectF &viewportRect, QTransform *textureTransform, QTransform *modelTransform, QRectF *textureRect, QRectF *modelRect, const bool scrollCheckers) const { if(scrollCheckers) { *textureTransform = QTransform(); *textureRect = QRectF(0, 0, viewportRect.width(),viewportRect.height()); } else { *textureTransform = viewportToWidgetTransform(); *textureRect = viewportRect; } *modelTransform = viewportToWidgetTransform(); *modelRect = viewportRect; } QPointF KisCoordinatesConverter::imageCenterInWidgetPixel() const { if(!m_d->image) return QPointF(); QPolygonF poly = imageToWidget(QPolygon(m_d->image->bounds())); return (poly[0] + poly[1] + poly[2] + poly[3]) / 4.0; } // these functions return a bounding rect if the canvas is rotated QRectF KisCoordinatesConverter::imageRectInWidgetPixels() const { if(!m_d->image) return QRectF(); return imageToWidget(m_d->image->bounds()); } QRectF KisCoordinatesConverter::imageRectInViewportPixels() const { if(!m_d->image) return QRectF(); return imageToViewport(m_d->image->bounds()); } QRect KisCoordinatesConverter::imageRectInImagePixels() const { if(!m_d->image) return QRect(); return m_d->image->bounds(); } QRectF KisCoordinatesConverter::imageRectInDocumentPixels() const { if(!m_d->image) return QRectF(); return imageToDocument(m_d->image->bounds()); } QSizeF KisCoordinatesConverter::imageSizeInFlakePixels() const { if(!m_d->image) return QSizeF(); qreal scaleX, scaleY; imageScale(&scaleX, &scaleY); QSize imageSize = m_d->image->size(); return QSizeF(imageSize.width() * scaleX, imageSize.height() * scaleY); } QRectF KisCoordinatesConverter::widgetRectInFlakePixels() const { return widgetToFlake(QRectF(QPoint(0,0), m_d->canvasWidgetSize)); } QRectF KisCoordinatesConverter::widgetRectInImagePixels() const { return widgetToImage(QRectF(QPoint(0,0), m_d->canvasWidgetSize)); } QPointF KisCoordinatesConverter::flakeCenterPoint() const { QRectF widgetRect = widgetRectInFlakePixels(); return QPointF(widgetRect.left() + widgetRect.width() / 2, widgetRect.top() + widgetRect.height() / 2); } QPointF KisCoordinatesConverter::widgetCenterPoint() const { return QPointF(m_d->canvasWidgetSize.width() / 2.0, m_d->canvasWidgetSize.height() / 2.0); } void KisCoordinatesConverter::imageScale(qreal *scaleX, qreal *scaleY) const { if(!m_d->image) { *scaleX = 1.0; *scaleY = 1.0; return; } // get the x and y zoom level of the canvas qreal zoomX, zoomY; KoZoomHandler::zoom(&zoomX, &zoomY); // Get the KisImage resolution qreal resX = m_d->image->xRes(); qreal resY = m_d->image->yRes(); // Compute the scale factors *scaleX = zoomX / resX; *scaleY = zoomY / resY; } void KisCoordinatesConverter::imagePhysicalScale(qreal *scaleX, qreal *scaleY) const { imageScale(scaleX, scaleY); *scaleX *= m_d->devicePixelRatio; *scaleY *= m_d->devicePixelRatio; } /** * @brief Adjust a given pair of coordinates to the nearest device pixel * according to the value of `devicePixelRatio`. * @param point a point in logical pixel space * @return The point in logical pixel space but adjusted to the nearest device * pixel */ QPointF KisCoordinatesConverter::snapToDevicePixel(const QPointF &point) const { QPoint devicePixel = (point * m_d->devicePixelRatio).toPoint(); // These adjusted coords will be in logical pixel but is aligned in device // pixel space for pixel-perfect rendering. return QPointF(devicePixel) / m_d->devicePixelRatio; } diff --git a/libs/ui/kis_zoom_manager.cc b/libs/ui/kis_zoom_manager.cc index f54d8a3859..9e5b9c8bfd 100644 --- a/libs/ui/kis_zoom_manager.cc +++ b/libs/ui/kis_zoom_manager.cc @@ -1,393 +1,395 @@ /* * Copyright (C) 2006, 2010 Boudewijn Rempt * Copyright (C) 2009 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_zoom_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KisDocument.h" #include "KisViewManager.h" #include "canvas/kis_canvas2.h" #include "kis_coordinates_converter.h" #include "kis_image.h" #include "kis_statusbar.h" #include "kis_config.h" #include "krita_utils.h" #include "kis_canvas_resource_provider.h" #include "kis_lod_transform.h" #include "kis_snap_line_strategy.h" #include "kis_guides_config.h" #include "kis_guides_manager.h" class KisZoomController : public KoZoomController { public: KisZoomController(KoCanvasController *co, KisCoordinatesConverter *zh, KActionCollection *actionCollection, QObject *parent) : KoZoomController(co, zh, actionCollection, parent), m_converter(zh) { } protected: - QSize documentToViewport(const QSizeF &size) override { + QSizeF documentToViewport(const QSizeF &size) override { QRectF docRect(QPointF(), size); - return m_converter->documentToWidget(docRect).toRect().size(); + QSizeF viewport = m_converter->documentToWidget(docRect).size(); + QPointF adjustedViewport = m_converter->snapToDevicePixel(QPointF(viewport.width(), viewport.height())); + return QSizeF(adjustedViewport.x(), adjustedViewport.y()); } private: KisCoordinatesConverter *m_converter; }; KisZoomManager::KisZoomManager(QPointer view, KoZoomHandler * zoomHandler, KoCanvasController * canvasController) : m_view(view) , m_zoomHandler(zoomHandler) , m_canvasController(canvasController) , m_horizontalRuler(0) , m_verticalRuler(0) , m_zoomAction(0) , m_zoomActionWidget(0) , m_physicalDpiX(72.0) , m_physicalDpiY(72.0) , m_devicePixelRatio(1.0) { } KisZoomManager::~KisZoomManager() { if (m_zoomActionWidget && !m_zoomActionWidget->parent()) { delete m_zoomActionWidget; } } void KisZoomManager::updateScreenResolution(QWidget *parentWidget) { if (qFuzzyCompare(parentWidget->physicalDpiX(), m_physicalDpiX) && qFuzzyCompare(parentWidget->physicalDpiY(), m_physicalDpiY) && qFuzzyCompare(parentWidget->devicePixelRatioF(), m_devicePixelRatio)) { return; } m_physicalDpiX = parentWidget->physicalDpiX(); m_physicalDpiY = parentWidget->physicalDpiY(); m_devicePixelRatio = parentWidget->devicePixelRatioF(); KisCoordinatesConverter *converter = dynamic_cast(m_zoomHandler); converter->setDevicePixelRatio(m_devicePixelRatio); changeAspectMode(m_aspectMode); } void KisZoomManager::setup(KActionCollection * actionCollection) { KisImageWSP image = m_view->image(); if (!image) return; connect(image, SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(setMinMaxZoom())); KisCoordinatesConverter *converter = dynamic_cast(m_zoomHandler); m_zoomController = new KisZoomController(m_canvasController, converter, actionCollection, this); m_zoomHandler->setZoomMode(KoZoomMode::ZOOM_PIXELS); m_zoomHandler->setZoom(1.0); m_zoomController->setPageSize(QSizeF(image->width() / image->xRes(), image->height() / image->yRes())); m_zoomController->setDocumentSize(QSizeF(image->width() / image->xRes(), image->height() / image->yRes()), true); m_zoomAction = m_zoomController->zoomAction(); setMinMaxZoom(); m_zoomActionWidget = m_zoomAction->createWidget(0); // Put the canvascontroller in a layout so it resizes with us QGridLayout * layout = new QGridLayout(m_view); layout->setSpacing(0); layout->setMargin(0); m_view->setLayout(layout); m_view->document()->setUnit(KoUnit(KoUnit::Pixel)); m_horizontalRuler = new KoRuler(m_view, Qt::Horizontal, m_zoomHandler); m_horizontalRuler->setShowMousePosition(true); m_horizontalRuler->createGuideToolConnection(m_view->canvasBase()); m_horizontalRuler->setVisible(false); // this prevents the rulers from flashing on to off when a new document is created m_verticalRuler = new KoRuler(m_view, Qt::Vertical, m_zoomHandler); m_verticalRuler->setShowMousePosition(true); m_verticalRuler->createGuideToolConnection(m_view->canvasBase()); m_verticalRuler->setVisible(false); QAction *rulerAction = actionCollection->action("ruler_pixel_multiple2"); if (m_view->document()->guidesConfig().rulersMultiple2()) { m_horizontalRuler->setUnitPixelMultiple2(true); m_verticalRuler->setUnitPixelMultiple2(true); } QList unitActions = m_view->createChangeUnitActions(true); unitActions.append(rulerAction); m_horizontalRuler->setPopupActionList(unitActions); m_verticalRuler->setPopupActionList(unitActions); connect(m_view->document(), SIGNAL(unitChanged(KoUnit)), SLOT(applyRulersUnit(KoUnit))); connect(rulerAction, SIGNAL(toggled(bool)), SLOT(setRulersPixelMultiple2(bool))); layout->addWidget(m_horizontalRuler, 0, 1); layout->addWidget(m_verticalRuler, 1, 0); layout->addWidget(static_cast(m_canvasController), 1, 1); connect(m_canvasController->proxyObject, SIGNAL(canvasOffsetXChanged(int)), this, SLOT(pageOffsetChanged())); connect(m_canvasController->proxyObject, SIGNAL(canvasOffsetYChanged(int)), this, SLOT(pageOffsetChanged())); connect(m_zoomController, SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)), this, SLOT(slotZoomChanged(KoZoomMode::Mode,qreal))); connect(m_zoomController, SIGNAL(aspectModeChanged(bool)), this, SLOT(changeAspectMode(bool))); applyRulersUnit(m_view->document()->unit()); } void KisZoomManager::updateImageBoundsSnapping() { const QRectF docRect = m_view->canvasBase()->coordinatesConverter()->imageRectInDocumentPixels(); const QPointF docCenter = docRect.center(); KoSnapGuide *snapGuide = m_view->canvasBase()->snapGuide(); { KisSnapLineStrategy *boundsSnap = new KisSnapLineStrategy(KoSnapGuide::DocumentBoundsSnapping); boundsSnap->addLine(Qt::Horizontal, docRect.y()); boundsSnap->addLine(Qt::Horizontal, docRect.bottom()); boundsSnap->addLine(Qt::Vertical, docRect.x()); boundsSnap->addLine(Qt::Vertical, docRect.right()); snapGuide->overrideSnapStrategy(KoSnapGuide::DocumentBoundsSnapping, boundsSnap); } { KisSnapLineStrategy *centerSnap = new KisSnapLineStrategy(KoSnapGuide::DocumentCenterSnapping); centerSnap->addLine(Qt::Horizontal, docCenter.y()); centerSnap->addLine(Qt::Vertical, docCenter.x()); snapGuide->overrideSnapStrategy(KoSnapGuide::DocumentCenterSnapping, centerSnap); } } void KisZoomManager::updateMouseTrackingConnections() { bool value = m_horizontalRuler->isVisible() && m_verticalRuler->isVisible() && m_horizontalRuler->showMousePosition() && m_verticalRuler->showMousePosition(); m_mouseTrackingConnections.clear(); if (value) { m_mouseTrackingConnections.addConnection(m_canvasController->proxyObject, SIGNAL(canvasMousePositionChanged(QPoint)), this, SLOT(mousePositionChanged(QPoint))); } } KoRuler* KisZoomManager::horizontalRuler() const { return m_horizontalRuler; } KoRuler* KisZoomManager::verticalRuler() const { return m_verticalRuler; } qreal KisZoomManager::zoom() const { qreal zoomX; qreal zoomY; m_zoomHandler->zoom(&zoomX, &zoomY); return zoomX; } void KisZoomManager::mousePositionChanged(const QPoint &viewPos) { QPoint pt = viewPos - m_rulersOffset; m_horizontalRuler->updateMouseCoordinate(pt.x()); m_verticalRuler->updateMouseCoordinate(pt.y()); } void KisZoomManager::setShowRulers(bool show) { m_horizontalRuler->setVisible(show); m_verticalRuler->setVisible(show); updateMouseTrackingConnections(); } void KisZoomManager::setRulersTrackMouse(bool value) { m_horizontalRuler->setShowMousePosition(value); m_verticalRuler->setShowMousePosition(value); updateMouseTrackingConnections(); } void KisZoomManager::applyRulersUnit(const KoUnit &baseUnit) { if (m_view && m_view->image()) { m_horizontalRuler->setUnit(KoUnit(baseUnit.type(), m_view->image()->xRes())); m_verticalRuler->setUnit(KoUnit(baseUnit.type(), m_view->image()->yRes())); } if (m_view->viewManager()) { m_view->viewManager()->guidesManager()->setUnitType(baseUnit.type()); } } void KisZoomManager::setRulersPixelMultiple2(bool enabled) { m_horizontalRuler->setUnitPixelMultiple2(enabled); m_verticalRuler->setUnitPixelMultiple2(enabled); if (m_view->viewManager()) { m_view->viewManager()->guidesManager()->setRulersMultiple2(enabled); } } void KisZoomManager::setMinMaxZoom() { KisImageWSP image = m_view->image(); if (!image) return; QSize imageSize = image->size(); qreal minDimension = qMin(imageSize.width(), imageSize.height()); qreal minZoom = qMin(100.0 / minDimension, 0.1); m_zoomAction->setMinimumZoom(minZoom); m_zoomAction->setMaximumZoom(90.0); } void KisZoomManager::updateGUI() { QRectF widgetRect = m_view->canvasBase()->coordinatesConverter()->imageRectInWidgetPixels(); QSize documentSize = m_view->canvasBase()->viewConverter()->viewToDocument(widgetRect).toAlignedRect().size(); m_horizontalRuler->setRulerLength(documentSize.width()); m_verticalRuler->setRulerLength(documentSize.height()); applyRulersUnit(m_horizontalRuler->unit()); } QWidget *KisZoomManager::zoomActionWidget() const { return m_zoomActionWidget; } void KisZoomManager::slotZoomChanged(KoZoomMode::Mode mode, qreal zoom) { Q_UNUSED(mode); Q_UNUSED(zoom); m_view->canvasBase()->notifyZoomChanged(); qreal humanZoom = zoom * 100.0; // XXX: KOMVC -- this is very irritating in MDI mode if (m_view->viewManager()) { m_view->viewManager()-> showFloatingMessage( i18nc("floating message about zoom", "Zoom: %1 %", KritaUtils::prettyFormatReal(humanZoom)), QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter); } const qreal effectiveZoom = m_view->canvasBase()->coordinatesConverter()->effectiveZoom(); m_view->canvasBase()->resourceManager()->setResource(KisCanvasResourceProvider::EffectiveZoom, effectiveZoom); } void KisZoomManager::slotScrollAreaSizeChanged() { pageOffsetChanged(); updateGUI(); } void KisZoomManager::changeAspectMode(bool aspectMode) { KisImageWSP image = m_view->image(); // changeAspectMode is called with the same aspectMode when the window is // moved across screens. Preserve the old zoomMode if this is the case. const KoZoomMode::Mode newMode = aspectMode == m_aspectMode ? m_zoomHandler->zoomMode() : KoZoomMode::ZOOM_CONSTANT; const qreal newZoom = m_zoomHandler->zoom(); const qreal resolutionX = aspectMode ? image->xRes() / m_devicePixelRatio : POINT_TO_INCH(m_physicalDpiX); const qreal resolutionY = aspectMode ? image->yRes() / m_devicePixelRatio : POINT_TO_INCH(m_physicalDpiY); m_aspectMode = aspectMode; m_zoomController->setZoom(newMode, newZoom, resolutionX, resolutionY); m_view->canvasBase()->notifyZoomChanged(); } void KisZoomManager::pageOffsetChanged() { QRectF widgetRect = m_view->canvasBase()->coordinatesConverter()->imageRectInWidgetPixels(); m_rulersOffset = widgetRect.topLeft().toPoint(); m_horizontalRuler->setOffset(m_rulersOffset.x()); m_verticalRuler->setOffset(m_rulersOffset.y()); } void KisZoomManager::zoomTo100() { m_zoomController->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0); m_view->canvasBase()->notifyZoomChanged(); } diff --git a/libs/ui/tests/kis_zoom_and_pan_test.cpp b/libs/ui/tests/kis_zoom_and_pan_test.cpp index 6be75ad1dd..856051562b 100644 --- a/libs/ui/tests/kis_zoom_and_pan_test.cpp +++ b/libs/ui/tests/kis_zoom_and_pan_test.cpp @@ -1,766 +1,766 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_zoom_and_pan_test.h" #include #include #include #include "testutil.h" #include "qimage_based_test.h" #include #include "kis_config.h" #include "KisMainWindow.h" #include "KoZoomController.h" #include "KisDocument.h" #include "KisPart.h" #include "KisViewManager.h" #include "KisView.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_coordinates_converter.h" #include "kis_filter_strategy.h" #include "kistest.h" class ZoomAndPanTester : public TestUtil::QImageBasedTest { public: ZoomAndPanTester() // we are not going to use our own QImage sets,so // just exploit the set of the selection manager test : QImageBasedTest("selection_manager_test") { m_undoStore = new KisSurrogateUndoStore(); m_image = createImage(m_undoStore); m_image->initialRefreshGraph(); QVERIFY(checkLayersInitial(m_image)); m_doc = KisPart::instance()->createDocument(); m_doc->setCurrentImage(m_image); m_mainWindow = KisPart::instance()->createMainWindow(); m_view = new KisView(m_doc, m_mainWindow->resourceManager(), m_mainWindow->actionCollection(), m_mainWindow); m_image->refreshGraph(); m_mainWindow->show(); } ~ZoomAndPanTester() { m_image->waitForDone(); QApplication::processEvents(); delete m_mainWindow; delete m_doc; /** * The event queue may have up to 200k events * by the time all the tests are finished. Removing * all of them may last forever, so clear them after * every single test is finished */ QApplication::removePostedEvents(0); } QPointer view() { return m_view; } KisMainWindow* mainWindow() { return m_mainWindow; } KisImageWSP image() { return m_image; } KisCanvas2* canvas() { return m_view->canvasBase(); } QWidget* canvasWidget() { return m_view->canvasBase()->canvasWidget(); } KoZoomController* zoomController() { return m_view->zoomController(); } KisCanvasController* canvasController() { return dynamic_cast(m_view->canvasController()); } const KisCoordinatesConverter* coordinatesConverter() { return m_view->canvasBase()->coordinatesConverter(); } private: KisSurrogateUndoStore *m_undoStore; KisImageSP m_image; KisDocument *m_doc; QPointerm_view; KisMainWindow *m_mainWindow; }; template inline bool compareWithRounding(const P &pt0, const P &pt1, T tolerance) { return qAbs(pt0.x() - pt1.x()) <= tolerance && qAbs(pt0.y() - pt1.y()) <= tolerance; } bool verifyOffset(ZoomAndPanTester &t, const QPoint &offset) { if (t.coordinatesConverter()->documentOffset() != offset) { dbgKrita << "########################"; dbgKrita << "Expected Offset:" << offset; dbgKrita << "Actual values:"; dbgKrita << "Offset:" << t.coordinatesConverter()->documentOffset(); dbgKrita << "wsize:" << t.canvasWidget()->size(); dbgKrita << "vport:" << t.canvasController()->viewportSize(); dbgKrita << "pref:" << t.canvasController()->preferredCenter(); dbgKrita << "########################"; } return t.coordinatesConverter()->documentOffset() == offset; } bool KisZoomAndPanTest::checkPan(ZoomAndPanTester &t, QPoint shift) { QPoint oldOffset = t.coordinatesConverter()->documentOffset(); QPointF oldPrefCenter = t.canvasController()->preferredCenter(); t.canvasController()->pan(shift); QPoint newOffset = t.coordinatesConverter()->documentOffset(); QPointF newPrefCenter = t.canvasController()->preferredCenter(); QPointF newTopLeft = t.coordinatesConverter()->imageRectInWidgetPixels().topLeft(); QPoint expectedOffset = oldOffset + shift; QPointF expectedPrefCenter = oldPrefCenter + shift; // no tolerance accepted for pan bool offsetAsExpected = newOffset == expectedOffset; // rounding can happen due to the scroll bars being the main // source of the offset bool preferredCenterAsExpected = compareWithRounding(expectedPrefCenter, newPrefCenter, 1.0); bool topLeftAsExpected = newTopLeft.toPoint() == -newOffset; if (!offsetAsExpected || !preferredCenterAsExpected || !topLeftAsExpected) { dbgKrita << "***** PAN *****************"; if(!offsetAsExpected) { dbgKrita << " ### Offset invariant broken"; } if(!preferredCenterAsExpected) { dbgKrita << " ### Preferred center invariant broken"; } if(!topLeftAsExpected) { dbgKrita << " ### TopLeft invariant broken"; } dbgKrita << ppVar(expectedOffset); dbgKrita << ppVar(expectedPrefCenter); dbgKrita << ppVar(oldOffset) << ppVar(newOffset); dbgKrita << ppVar(oldPrefCenter) << ppVar(newPrefCenter); dbgKrita << ppVar(newTopLeft); dbgKrita << "***************************"; } return offsetAsExpected && preferredCenterAsExpected && topLeftAsExpected; } bool KisZoomAndPanTest::checkInvariants(const QPointF &baseFlakePoint, const QPoint &oldOffset, const QPointF &oldPreferredCenter, qreal oldZoom, const QPoint &newOffset, const QPointF &newPreferredCenter, qreal newZoom, const QPointF &newTopLeft, const QSize &oldDocumentSize) { qreal k = newZoom / oldZoom; QPointF expectedOffset = oldOffset + (k - 1) * baseFlakePoint; QPointF expectedPreferredCenter = oldPreferredCenter + (k - 1) * baseFlakePoint; qreal oldPreferredCenterFractionX = 1.0 * oldPreferredCenter.x() / oldDocumentSize.width(); qreal oldPreferredCenterFractionY = 1.0 * oldPreferredCenter.y() / oldDocumentSize.height(); qreal roundingTolerance = qMax(qreal(1.0), qMax(oldPreferredCenterFractionX, oldPreferredCenterFractionY) / k); /** * In the computation of the offset two roundings happen: * first for the computation of oldOffset and the second * for the computation of newOffset. So the maximum tolerance * should equal 2. */ bool offsetAsExpected = compareWithRounding(expectedOffset, QPointF(newOffset), 2 * roundingTolerance); /** * Rounding for the preferred center happens due to the rounding * of the document size while zooming. The wider the step of the * zooming, the bigger tolerance should be */ bool preferredCenterAsExpected = compareWithRounding(expectedPreferredCenter, newPreferredCenter, roundingTolerance); bool topLeftAsExpected = newTopLeft.toPoint() == -newOffset; if (!offsetAsExpected || !preferredCenterAsExpected || !topLeftAsExpected) { dbgKrita << "***** ZOOM ****************"; if(!offsetAsExpected) { dbgKrita << " ### Offset invariant broken"; } if(!preferredCenterAsExpected) { dbgKrita << " ### Preferred center invariant broken"; } if(!topLeftAsExpected) { dbgKrita << " ### TopLeft invariant broken"; } dbgKrita << ppVar(expectedOffset); dbgKrita << ppVar(expectedPreferredCenter); dbgKrita << ppVar(oldOffset) << ppVar(newOffset); dbgKrita << ppVar(oldPreferredCenter) << ppVar(newPreferredCenter); dbgKrita << ppVar(oldPreferredCenterFractionX); dbgKrita << ppVar(oldPreferredCenterFractionY); dbgKrita << ppVar(oldZoom) << ppVar(newZoom); dbgKrita << ppVar(baseFlakePoint); dbgKrita << ppVar(newTopLeft); dbgKrita << ppVar(roundingTolerance); dbgKrita << "***************************"; } return offsetAsExpected && preferredCenterAsExpected && topLeftAsExpected; } bool KisZoomAndPanTest::checkZoomWithAction(ZoomAndPanTester &t, qreal newZoom, bool limitedZoom) { QPoint oldOffset = t.coordinatesConverter()->documentOffset(); QPointF oldPrefCenter = t.canvasController()->preferredCenter(); qreal oldZoom = t.zoomController()->zoomAction()->effectiveZoom(); - QSize oldDocumentSize = t.canvasController()->documentSize(); + QSize oldDocumentSize = t.canvasController()->documentSize().toSize(); t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom); QPointF newTopLeft = t.coordinatesConverter()->imageRectInWidgetPixels().topLeft(); return checkInvariants(oldPrefCenter, oldOffset, oldPrefCenter, oldZoom, t.coordinatesConverter()->documentOffset(), t.canvasController()->preferredCenter(), limitedZoom ? oldZoom : newZoom, newTopLeft, oldDocumentSize); } bool KisZoomAndPanTest::checkZoomWithWheel(ZoomAndPanTester &t, const QPoint &widgetPoint, qreal zoomCoeff, bool limitedZoom) { QPoint oldOffset = t.coordinatesConverter()->documentOffset(); QPointF oldPrefCenter = t.canvasController()->preferredCenter(); qreal oldZoom = t.zoomController()->zoomAction()->effectiveZoom(); - QSize oldDocumentSize = t.canvasController()->documentSize(); + QSize oldDocumentSize = t.canvasController()->documentSize().toSize(); t.canvasController()->zoomRelativeToPoint(widgetPoint, zoomCoeff); QPointF newTopLeft = t.coordinatesConverter()->imageRectInWidgetPixels().topLeft(); return checkInvariants(oldOffset + widgetPoint, oldOffset, oldPrefCenter, oldZoom, t.coordinatesConverter()->documentOffset(), t.canvasController()->preferredCenter(), limitedZoom ? oldZoom : zoomCoeff * oldZoom, newTopLeft, oldDocumentSize); } void KisZoomAndPanTest::testZoom100ChangingWidgetSize() { ZoomAndPanTester t; QCOMPARE(t.image()->size(), QSize(640,441)); QCOMPARE(t.image()->xRes(), 1.0); QCOMPARE(t.image()->yRes(), 1.0); t.canvasController()->resize(QSize(1000,1000)); t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0); t.canvasController()->setPreferredCenter(QPoint(320,220)); QCOMPARE(t.canvasWidget()->size(), QSize(983,983)); - QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize()); + QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize()); QVERIFY(verifyOffset(t, QPoint(-171,-271))); t.canvasController()->resize(QSize(700,700)); QCOMPARE(t.canvasWidget()->size(), QSize(683,683)); - QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize()); + QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize()); QVERIFY(verifyOffset(t, QPoint(-171,-271))); t.canvasController()->setPreferredCenter(QPoint(320,220)); QVERIFY(verifyOffset(t, QPoint(-21,-121))); t.canvasController()->resize(QSize(400,400)); QCOMPARE(t.canvasWidget()->size(), QSize(383,383)); - QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize()); + QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize()); QVERIFY(verifyOffset(t, QPoint(-21,-121))); t.canvasController()->setPreferredCenter(QPoint(320,220)); QVERIFY(verifyOffset(t, QPoint(129,29))); t.canvasController()->pan(QPoint(100,100)); QVERIFY(verifyOffset(t, QPoint(229,129))); } void KisZoomAndPanTest::initializeViewport(ZoomAndPanTester &t, bool fullscreenMode, bool rotate, bool mirror) { QCOMPARE(t.image()->size(), QSize(640,441)); QCOMPARE(t.image()->xRes(), 1.0); QCOMPARE(t.image()->yRes(), 1.0); t.canvasController()->resize(QSize(500,500)); t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0); t.canvasController()->setPreferredCenter(QPoint(320,220)); QCOMPARE(t.canvasWidget()->size(), QSize(483,483)); - QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize()); + QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize()); QVERIFY(verifyOffset(t, QPoint(79,-21))); if (fullscreenMode) { QCOMPARE(t.canvasController()->preferredCenter(), QPointF(320,220)); QAction *action = t.view()->viewManager()->actionCollection()->action("view_show_canvas_only"); action->setChecked(true); QVERIFY(verifyOffset(t, QPoint(79,-21))); QCOMPARE(t.canvasController()->preferredCenter(), QPointF(329,220)); t.canvasController()->resize(QSize(483,483)); QCOMPARE(t.canvasWidget()->size(), QSize(483,483)); - QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize()); + QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize()); QVERIFY(verifyOffset(t, QPoint(79,-21))); /** * FIXME: here is a small flaw in KoCanvasControllerWidget * We cannot set the center point explicitly, because it'll be rounded * up by recenterPreferred function, so real center point will be * different. Make the preferredCenter() return real center of the * image instead of the set value */ QCOMPARE(t.canvasController()->preferredCenter(), QPointF(320.5,220)); } if (rotate) { t.canvasController()->rotateCanvas(90); QVERIFY(verifyOffset(t, QPoint(-21,79))); QVERIFY(compareWithRounding(QPointF(220,320), t.canvasController()->preferredCenter(), 2)); QCOMPARE(t.coordinatesConverter()->imageRectInWidgetPixels().topLeft().toPoint(), -t.coordinatesConverter()->documentOffset()); } if (mirror) { t.canvasController()->mirrorCanvas(true); QVERIFY(verifyOffset(t, QPoint(78, -21))); QVERIFY(compareWithRounding(QPointF(320,220), t.canvasController()->preferredCenter(), 2)); QCOMPARE(t.coordinatesConverter()->imageRectInWidgetPixels().topLeft().toPoint(), -t.coordinatesConverter()->documentOffset()); } } void KisZoomAndPanTest::testSequentialActionZoomAndPan(bool fullscreenMode, bool rotate, bool mirror) { ZoomAndPanTester t; initializeViewport(t, fullscreenMode, rotate, mirror); QVERIFY(checkZoomWithAction(t, 0.5)); QVERIFY(checkPan(t, QPoint(100,100))); QVERIFY(checkZoomWithAction(t, 0.25)); QVERIFY(checkPan(t, QPoint(-100,-100))); QVERIFY(checkZoomWithAction(t, 0.35)); QVERIFY(checkPan(t, QPoint(100,100))); QVERIFY(checkZoomWithAction(t, 0.45)); QVERIFY(checkPan(t, QPoint(100,100))); QVERIFY(checkZoomWithAction(t, 0.85)); QVERIFY(checkPan(t, QPoint(-100,-100))); QVERIFY(checkZoomWithAction(t, 2.35)); QVERIFY(checkPan(t, QPoint(100,100))); } void KisZoomAndPanTest::testSequentialWheelZoomAndPan(bool fullscreenMode, bool rotate, bool mirror) { ZoomAndPanTester t; initializeViewport(t, fullscreenMode, rotate, mirror); QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5)); QVERIFY(checkPan(t, QPoint(100,100))); QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5)); QVERIFY(checkPan(t, QPoint(-100,-100))); QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 1.25)); QVERIFY(checkPan(t, QPoint(100,100))); QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 1.5)); QVERIFY(checkPan(t, QPoint(100,100))); QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 2.5)); QVERIFY(checkPan(t, QPoint(-100,-100))); // check one point which is outside the widget QVERIFY(checkZoomWithWheel(t, QPoint(-100,100), 2.5)); QVERIFY(checkPan(t, QPoint(-100,-100))); QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5)); QVERIFY(checkPan(t, QPoint(-100,-100))); } void KisZoomAndPanTest::testSequentialActionZoomAndPan() { testSequentialActionZoomAndPan(false, false, false); } void KisZoomAndPanTest::testSequentialActionZoomAndPanFullscreen() { testSequentialActionZoomAndPan(true, false, false); } void KisZoomAndPanTest::testSequentialActionZoomAndPanRotate() { testSequentialActionZoomAndPan(false, true, false); } void KisZoomAndPanTest::testSequentialActionZoomAndPanRotateFullscreen() { testSequentialActionZoomAndPan(true, true, false); } void KisZoomAndPanTest::testSequentialActionZoomAndPanMirror() { testSequentialActionZoomAndPan(false, false, true); } void KisZoomAndPanTest::testSequentialWheelZoomAndPan() { testSequentialWheelZoomAndPan(false, false, false); } void KisZoomAndPanTest::testSequentialWheelZoomAndPanFullscreen() { testSequentialWheelZoomAndPan(true, false, false); } void KisZoomAndPanTest::testSequentialWheelZoomAndPanRotate() { testSequentialWheelZoomAndPan(false, true, false); } void KisZoomAndPanTest::testSequentialWheelZoomAndPanRotateFullscreen() { testSequentialWheelZoomAndPan(true, true, false); } void KisZoomAndPanTest::testSequentialWheelZoomAndPanMirror() { testSequentialWheelZoomAndPan(false, false, true); } void KisZoomAndPanTest::testZoomOnBorderZoomLevels() { ZoomAndPanTester t; initializeViewport(t, false, false, false); QPoint widgetPoint(100,100); warnKrita << "WARNING: testZoomOnBorderZoomLevels() is disabled due to some changes in KoZoomMode::minimum/maximumZoom()"; return; // test min zoom level t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, KoZoomMode::minimumZoom()); QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5, true)); QVERIFY(checkZoomWithAction(t, KoZoomMode::minimumZoom() * 0.5, true)); // test max zoom level t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, KoZoomMode::maximumZoom()); QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 2.0, true)); QVERIFY(checkZoomWithAction(t, KoZoomMode::maximumZoom() * 2.0, true)); } inline QTransform correctionMatrix(qreal angle) { return QTransform(0,0,0,sin(M_PI * angle / 180),0,0,0,0,1); } bool KisZoomAndPanTest::checkRotation(ZoomAndPanTester &t, qreal angle) { // save old values QPoint oldOffset = t.coordinatesConverter()->documentOffset(); QPointF oldCenteringCorrection = t.coordinatesConverter()->centeringCorrection(); QPointF oldPreferredCenter = t.canvasController()->preferredCenter(); QPointF oldRealCenterPoint = t.coordinatesConverter()->widgetToImage(t.coordinatesConverter()->widgetCenterPoint()); - QSize oldDocumentSize = t.canvasController()->documentSize(); + QSize oldDocumentSize = t.canvasController()->documentSize().toSize(); qreal baseAngle = t.coordinatesConverter()->rotationAngle(); t.canvasController()->rotateCanvas(angle); // save result values QPoint newOffset = t.coordinatesConverter()->documentOffset(); QPointF newCenteringCorrection = t.coordinatesConverter()->centeringCorrection(); QPointF newPreferredCenter = t.canvasController()->preferredCenter(); QPointF newRealCenterPoint = t.coordinatesConverter()->widgetToImage(t.coordinatesConverter()->widgetCenterPoint()); - QSize newDocumentSize = t.canvasController()->documentSize(); + QSize newDocumentSize = t.canvasController()->documentSize().toSize(); // calculate theoretical preferred center QTransform rot; rot.rotate(angle); QSizeF dSize = t.coordinatesConverter()->imageSizeInFlakePixels(); QPointF dPoint(dSize.width(), dSize.height()); QPointF expectedPreferredCenter = (oldPreferredCenter - dPoint * correctionMatrix(baseAngle)) * rot + dPoint * correctionMatrix(baseAngle + angle); // calculate theoretical offset based on the real preferred center QPointF wPoint(t.canvasWidget()->size().width(), t.canvasWidget()->size().height()); QPointF expectedOldOffset = oldPreferredCenter - 0.5 * wPoint; QPointF expectedNewOffset = newPreferredCenter - 0.5 * wPoint; bool preferredCenterAsExpected = compareWithRounding(expectedPreferredCenter, newPreferredCenter, 2); bool oldOffsetAsExpected = compareWithRounding(expectedOldOffset + oldCenteringCorrection, QPointF(oldOffset), 2); bool newOffsetAsExpected = compareWithRounding(expectedNewOffset + newCenteringCorrection, QPointF(newOffset), 3); qreal zoom = t.zoomController()->zoomAction()->effectiveZoom(); bool realCenterPointAsExpected = compareWithRounding(oldRealCenterPoint, newRealCenterPoint, 2/zoom); if (!oldOffsetAsExpected || !newOffsetAsExpected || !preferredCenterAsExpected || !realCenterPointAsExpected) { dbgKrita << "***** ROTATE **************"; if(!oldOffsetAsExpected) { dbgKrita << " ### Old offset invariant broken"; } if(!newOffsetAsExpected) { dbgKrita << " ### New offset invariant broken"; } if(!preferredCenterAsExpected) { dbgKrita << " ### Preferred center invariant broken"; } if(!realCenterPointAsExpected) { dbgKrita << " ### *Real* center invariant broken"; } dbgKrita << ppVar(expectedOldOffset); dbgKrita << ppVar(expectedNewOffset); dbgKrita << ppVar(expectedPreferredCenter); dbgKrita << ppVar(oldOffset) << ppVar(newOffset); dbgKrita << ppVar(oldCenteringCorrection) << ppVar(newCenteringCorrection); dbgKrita << ppVar(oldPreferredCenter) << ppVar(newPreferredCenter); dbgKrita << ppVar(oldRealCenterPoint) << ppVar(newRealCenterPoint); dbgKrita << ppVar(oldDocumentSize) << ppVar(newDocumentSize); dbgKrita << ppVar(baseAngle) << "deg"; dbgKrita << ppVar(angle) << "deg"; dbgKrita << "***************************"; } return preferredCenterAsExpected && oldOffsetAsExpected && newOffsetAsExpected && realCenterPointAsExpected; } void KisZoomAndPanTest::testRotation(qreal vastScrolling, qreal zoom) { KisConfig cfg(false); cfg.setVastScrolling(vastScrolling); ZoomAndPanTester t; QCOMPARE(t.image()->size(), QSize(640,441)); QCOMPARE(t.image()->xRes(), 1.0); QCOMPARE(t.image()->yRes(), 1.0); QPointF preferredCenter = zoom * t.image()->bounds().center(); t.canvasController()->resize(QSize(500,500)); t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, zoom); t.canvasController()->setPreferredCenter(preferredCenter.toPoint()); QCOMPARE(t.canvasWidget()->size(), QSize(483,483)); - QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize()); + QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize().toSize()); QPointF realCenterPoint = t.coordinatesConverter()->widgetToImage(t.coordinatesConverter()->widgetCenterPoint()); QPointF expectedCenterPoint = QPointF(t.image()->bounds().center()); if(!compareWithRounding(realCenterPoint, expectedCenterPoint, 2/zoom)) { dbgKrita << "Failed to set initial center point"; dbgKrita << ppVar(expectedCenterPoint) << ppVar(realCenterPoint); QFAIL("FAIL: Failed to set initial center point"); } QVERIFY(checkRotation(t, 30)); QVERIFY(checkRotation(t, 20)); QVERIFY(checkRotation(t, 10)); QVERIFY(checkRotation(t, 5)); QVERIFY(checkRotation(t, 5)); QVERIFY(checkRotation(t, 5)); if(vastScrolling < 0.5 && zoom < 1) { warnKrita << "Disabling a few tests for vast scrolling =" << vastScrolling << ". See comment for more"; /** * We have to disable a couple of tests here for the case when * vastScrolling value is 0.2. The problem is that the centering * correction applied to the offset in * KisCanvasController::rotateCanvas pollutes the preferredCenter * value, because KoCnvasControllerWidget has no access to this * correction and cannot calculate the real value of the center of * the image. To fix this bug the calculation of correction * (aka "origin") should be moved to the KoCanvasControllerWidget * itself which would cause quite huge changes (including the change * of the external interface of it). Namely, we would have to * *calculate* offset from the value of the scroll bars, but not * use their values directly: * * offset = scrollBarValue - origin * * So now we just disable these unittests and allow a couple * of "jumping" bugs appear in vastScrolling < 0.5 modes, which * is, actually, not the default case. */ } else { QVERIFY(checkRotation(t, 5)); QVERIFY(checkRotation(t, 5)); QVERIFY(checkRotation(t, 5)); } } void KisZoomAndPanTest::testRotation_VastScrolling_1_0() { testRotation(0.9, 1.0); } void KisZoomAndPanTest::testRotation_VastScrolling_0_5() { testRotation(0.9, 0.5); } void KisZoomAndPanTest::testRotation_NoVastScrolling_1_0() { testRotation(0.2, 1.0); } void KisZoomAndPanTest::testRotation_NoVastScrolling_0_5() { testRotation(0.2, 0.5); } void KisZoomAndPanTest::testImageRescaled_0_5() { ZoomAndPanTester t; QApplication::processEvents(); initializeViewport(t, false, false, false); QApplication::processEvents(); QVERIFY(checkPan(t, QPoint(200,200))); QApplication::processEvents(); QPointF oldStillPoint = t.coordinatesConverter()->imageRectInWidgetPixels().center(); KisFilterStrategy *strategy = new KisBilinearFilterStrategy(); t.image()->scaleImage(QSize(320, 220), t.image()->xRes(), t.image()->yRes(), strategy); t.image()->waitForDone(); QApplication::processEvents(); delete strategy; QPointF newStillPoint = t.coordinatesConverter()->imageRectInWidgetPixels().center(); QVERIFY(compareWithRounding(oldStillPoint, newStillPoint, 1.0)); } void KisZoomAndPanTest::testImageCropped() { ZoomAndPanTester t; QApplication::processEvents(); initializeViewport(t, false, false, false); QApplication::processEvents(); QVERIFY(checkPan(t, QPoint(-150,-150))); QApplication::processEvents(); QPointF oldStillPoint = t.coordinatesConverter()->imageToWidget(QPointF(150,150)); t.image()->cropImage(QRect(100,100,100,100)); t.image()->waitForDone(); QApplication::processEvents(); QPointF newStillPoint = t.coordinatesConverter()->imageToWidget(QPointF(50,50)); QVERIFY(compareWithRounding(oldStillPoint, newStillPoint, 1.0)); } KISTEST_MAIN(KisZoomAndPanTest) diff --git a/libs/widgets/KoZoomController.cpp b/libs/widgets/KoZoomController.cpp index 7f7f99c631..dd25f05922 100644 --- a/libs/widgets/KoZoomController.cpp +++ b/libs/widgets/KoZoomController.cpp @@ -1,215 +1,223 @@ /* This file is part of the KDE project * Copyright (C) 2007 C. Boemann * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 2010 Boudewijn Rempt * Copyright (C) 2011 Arjen Hiemstra * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include +#include + void KoZoomController::Private::init(KoCanvasController *co, KoZoomHandler *zh, KActionCollection *actionCollection) { canvasController = co; fitMargin = co->margin(); zoomHandler = zh; connect(action, SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)), parent, SLOT(setZoom(KoZoomMode::Mode,qreal))); connect(action, SIGNAL(aspectModeChanged(bool)), parent, SIGNAL(aspectModeChanged(bool))); connect(action, SIGNAL(zoomedToSelection()), parent, SIGNAL(zoomedToSelection())); connect(action, SIGNAL(zoomedToAll()), parent, SIGNAL(zoomedToAll())); actionCollection->addAction("view_zoom", action); connect(canvasController->proxyObject, SIGNAL(sizeChanged(QSize)), parent, SLOT(setAvailableSize()) ); connect(canvasController->proxyObject, SIGNAL(zoomRelative(qreal,QPointF)), parent, SLOT(requestZoomRelative(qreal,QPointF)) ); } KoZoomController::KoZoomController(KoCanvasController *co, KoZoomHandler *zh, KActionCollection *actionCollection, QObject *parent) : QObject(parent), d(new Private(this)) { d->init(co, zh, actionCollection); } KoZoomController::~KoZoomController() { delete d; } KoZoomAction *KoZoomController::zoomAction() const { return d->action; } void KoZoomController::setZoomMode(KoZoomMode::Mode mode) { setZoom(mode, 1.0); } KoZoomMode::Mode KoZoomController::zoomMode() const { return d->zoomHandler->zoomMode(); } void KoZoomController::setPageSize(const QSizeF &pageSize) { if(d->pageSize == pageSize) return; d->pageSize = pageSize; if(d->zoomHandler->zoomMode() == KoZoomMode::ZOOM_WIDTH) setZoom(KoZoomMode::ZOOM_WIDTH, 0); if(d->zoomHandler->zoomMode() == KoZoomMode::ZOOM_PAGE) setZoom(KoZoomMode::ZOOM_PAGE, 0); } QSizeF KoZoomController::pageSize() const { return d->pageSize; } void KoZoomController::setDocumentSize(const QSizeF &documentSize, bool recalculateCenter) { d->documentSize = documentSize; d->canvasController->updateDocumentSize(documentToViewport(d->documentSize), recalculateCenter); // Finally ask the canvasController to recenter d->canvasController->recenterPreferred(); } QSizeF KoZoomController::documentSize() const { return d->documentSize; } void KoZoomController::setZoom(KoZoomMode::Mode mode, qreal zoom) { setZoom(mode, zoom, d->canvasController->preferredCenter()); } void KoZoomController::setZoom(KoZoomMode::Mode mode, qreal zoom, const QPointF &stillPoint) { setZoom(mode, zoom, d->zoomHandler->resolutionX(), d->zoomHandler->resolutionY(), stillPoint); } void KoZoomController::setZoom(KoZoomMode::Mode mode, qreal zoom, qreal resolutionX, qreal resolutionY) { setZoom(mode, zoom, resolutionX, resolutionY, d->canvasController->preferredCenter()); } void KoZoomController::setZoom(KoZoomMode::Mode mode, qreal zoom, qreal resolutionX, qreal resolutionY, const QPointF &stillPoint) { if (d->zoomHandler->zoomMode() == mode && qFuzzyCompare(d->zoomHandler->zoom(), zoom) && qFuzzyCompare(d->zoomHandler->resolutionX(), resolutionX) && qFuzzyCompare(d->zoomHandler->resolutionY(), resolutionY)) { return; // no change } qreal oldEffectiveZoom = d->action->effectiveZoom(); - QSize oldPageViewportSize = documentToViewport(d->pageSize); + QSizeF oldPageViewportSize = documentToViewport(d->pageSize); if(!qFuzzyCompare(d->zoomHandler->resolutionX(), resolutionX) || !qFuzzyCompare(d->zoomHandler->resolutionY(), resolutionY)) { d->zoomHandler->setResolution(resolutionX, resolutionY); } if(mode == KoZoomMode::ZOOM_CONSTANT) { if(zoom == 0.0) return; d->action->setZoom(zoom); } else if(mode == KoZoomMode::ZOOM_WIDTH) { zoom = (d->canvasController->viewportSize().width() - 2 * d->fitMargin) / (oldPageViewportSize.width() / d->zoomHandler->zoom()); d->action->setSelectedZoomMode(mode); d->action->setEffectiveZoom(zoom); } else if(mode == KoZoomMode::ZOOM_PAGE) { zoom = (d->canvasController->viewportSize().width() - 2 * d->fitMargin) / (oldPageViewportSize.width() / d->zoomHandler->zoom()); zoom = qMin(zoom, (d->canvasController->viewportSize().height() - 2 * d->fitMargin) / (oldPageViewportSize.height() / d->zoomHandler->zoom())); d->action->setSelectedZoomMode(mode); d->action->setEffectiveZoom(zoom); } d->zoomHandler->setZoomMode(mode); d->zoomHandler->setZoom(d->action->effectiveZoom()); #ifdef DEBUG if(! d->documentSize.isValid()) warnWidgets << "Setting zoom while there is no document size set, this will fail"; else if (d->pageSize.width() > d->documentSize.width() || d->pageSize.height() > d->documentSize.height()) warnWidgets << "ZoomController; Your page size is larger than your document size (" << d->pageSize << " > " << d->documentSize << ")\n"; #endif - QSize documentViewportSize = documentToViewport(d->documentSize); + QSizeF documentViewportSize = documentToViewport(d->documentSize); // Tell the canvasController that the zoom has changed // Actually canvasController doesn't know about zoom, but the document in pixels // has changed as a result of the zoom change // To allow listeners of offset changes to react on the real new offset and not on the // intermediate offsets, we block the signals here, and emit by ourselves later. d->canvasController->proxyObject->blockSignals(true); d->canvasController->updateDocumentSize(documentViewportSize, true); d->canvasController->proxyObject->blockSignals(false); // Finally ask the canvasController to recenter QPointF documentCenter; if (mode == KoZoomMode::ZOOM_WIDTH || mode == KoZoomMode::ZOOM_PAGE) { documentCenter = QRectF(QPointF(), documentViewportSize).center(); } else { qreal zoomCoeff = d->action->effectiveZoom() / oldEffectiveZoom; QPointF oldCenter = d->canvasController->preferredCenter(); documentCenter = stillPoint * zoomCoeff - (stillPoint - 1.0 / zoomCoeff * oldCenter); } d->canvasController->setPreferredCenter(documentCenter); emit zoomChanged(mode, d->action->effectiveZoom()); } -QSize KoZoomController::documentToViewport(const QSizeF &size) +QSizeF KoZoomController::documentToViewport(const QSizeF &size) { return d->zoomHandler->documentToView(size).toSize(); } +QSize KoZoomController::documentToViewportCeil(const QSizeF &size) +{ + QSizeF viewport = documentToViewport(size); + return QSize(qCeil(viewport.width()), qCeil(viewport.height())); +} + void KoZoomController::setAspectMode(bool status) { if (d->action) { d->action->setAspectMode(status); } } //have to include this because of Q_PRIVATE_SLOT #include diff --git a/libs/widgets/KoZoomController.h b/libs/widgets/KoZoomController.h index f1b7b652e8..b6abc0ab4c 100644 --- a/libs/widgets/KoZoomController.h +++ b/libs/widgets/KoZoomController.h @@ -1,203 +1,204 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007,2012 C. Boemann * Copyright (C) 2007 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOZOOMCONTROLLER_H #define KOZOOMCONTROLLER_H #include "KoZoomAction.h" #include "kritawidgets_export.h" #include #include #include class KoCanvasController; class KoZoomAction; class KoZoomHandler; class KActionCollection; class QSize; /** * This controller class handles zoom levels for any canvas. * * For each KoCanvasController you should have one instance of this * class to go with it. This class then creates a KoZoomAction and * basically handles all zooming for you. * * All you need to do is connect to the setDocumentSize() slot and * keep the controller up-to-date if your on-screen document ever * changes (note that this is in document units, so this is a zoom * independent size). * * If you choose to have zoom modes of 'page' and 'width' you are * required to set the page size using the setPageSize() method. * * Additionally you can connect to the zoomChanged() signal if you * want to store the latest zoom level and mode, for example to * restore the last used one at next restart. * * The specialAspectMode toggle is only a UI element. It does nothing * except emit the aspectModeChanged signal. * */ class KRITAWIDGETS_EXPORT KoZoomController : public QObject { Q_OBJECT public: /** * Constructor. Create one per canvasController. The zoomAction is created in the constructor and will * be available to the passed actionCollection for usage by XMLGui. * @param controller the canvasController * @param zoomHandler the zoom handler (viewconverter with setter methods) * @param actionCollection the action collection where the KoZoomAction is added to * @param parent the parent QObject */ KoZoomController(KoCanvasController *controller, KoZoomHandler *zoomHandler, KActionCollection *actionCollection, QObject *parent = 0); /// destructor ~KoZoomController() override; /// returns the zoomAction that is maintained by this controller KoZoomAction *zoomAction() const; /** * Alter the current zoom mode which updates the Gui. * @param mode the new mode that will be used to auto-calculate a new zoom-level if needed. */ void setZoomMode(KoZoomMode::Mode mode); /** * @return the current zoom mode. */ KoZoomMode::Mode zoomMode() const; /** * Set the resolution, zoom, the zoom mode for this zoom Controller. * Typically for use just after construction to restore the * persistent data. * * @param mode new zoom mode for the canvas * @param zoom (for ZOOM_CONSTANT zoom mode only) new zoom value for * the canvas * @param resolutionX new X resolution for the document * @param resolutionY new Y resolution for the document * @param stillPoint (for ZOOM_CONSTANT zoom mode only) the point * which will not change its position in widget * during the zooming. It is measured in view * coordinate system *before* zoom. */ void setZoom(KoZoomMode::Mode mode, qreal zoom, qreal resolutionX, qreal resolutionY, const QPointF &stillPoint); /** * Convenience function that changes resolution with * keeping the centering unchanged */ void setZoom(KoZoomMode::Mode mode, qreal zoom, qreal resolutionX, qreal resolutionY); /** * Convenience function that does not touch the resolution of the * document */ void setZoom(KoZoomMode::Mode mode, qreal zoom, const QPointF &stillPoint); /** * Convenience function with @p center always set to the current * center point of the canvas */ void setZoom(KoZoomMode::Mode mode, qreal zoom); /** * Set Aspect Mode button status and begin a chain of signals */ void setAspectMode(bool status); public Q_SLOTS: /** * Set the size of the current page in document coordinates which allows zoom modes that use the pageSize * to update. * @param pageSize the new page size in points */ void setPageSize(const QSizeF &pageSize); /** * Returns the size of the current page in document coordinates * @returns the page size in points */ QSizeF pageSize() const; /** * Set the size of the whole document currently being shown on the canvas. * The document size will be used together with the current zoom level to calculate the size of the * canvas in the canvasController. * @param documentSize the new document size in points * @param recalculateCenter tells canvas controller not to touch * preferredCenterFraction */ void setDocumentSize(const QSizeF &documentSize, bool recalculateCenter = false); /** * Returns the size of the whole document currently being shown on the canvas. * @returns the document size in points */ QSizeF documentSize() const; Q_SIGNALS: /** * This signal is emitted whenever either the zoommode or the zoom level is changed by the user. * the application can use the emitted data for persistency purposes. */ void zoomChanged (KoZoomMode::Mode mode, qreal zoom); /** * emitted when the special aspect mode toggle changes. * @see KoZoomAction::aspectModeChanged() */ void aspectModeChanged (bool aspectModeActivated); /** * Signal is triggered when the user clicks the zoom to selection button. * Nothing else happens except that this signal is emitted. */ void zoomedToSelection(); /** * Signal is triggered when the user clicks the zoom to all button. * Nothing else happens except that this signal is emitted. */ void zoomedToAll(); protected: - virtual QSize documentToViewport(const QSizeF &size); + virtual QSizeF documentToViewport(const QSizeF &size); + QSize documentToViewportCeil(const QSizeF &size); private: Q_PRIVATE_SLOT(d, void setAvailableSize()) Q_PRIVATE_SLOT(d, void requestZoomRelative(const qreal, const QPointF&)) Q_PRIVATE_SLOT(d, void setZoom(KoZoomMode::Mode, qreal)) Q_DISABLE_COPY( KoZoomController ) class Private; Private * const d; }; #endif