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
* imlements 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:
/// An enum to alter the positioning and size of the canvas inside the canvas controller
enum CanvasMode {
AlignTop, ///< canvas is top aligned if smaller than the viewport
Centered, ///< canvas is centered if smaller than the viewport
Infinite, ///< canvas is never smaller than the viewport
Spreadsheet ///< same as Infinite, but supports right-to-left layouts
};
// proxy QObject: use this to connect to slots and signals.
KoCanvasControllerProxyObject *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);
/**
* Sets the how the canvas behaves if the zoomed document becomes smaller than the viewport.
* @param mode the new canvas mode, CanvasMode::Centered is the default value
*/
virtual void setCanvasMode(KoCanvasController::CanvasMode mode);
/// Returns the current canvas mode
virtual KoCanvasController::CanvasMode canvasMode() const;
/**
* compatibility with QAbstractScrollArea
*/
virtual void scrollContentsBy(int dx, int dy) = 0;
/**
* @return the size of the viewport
*/
virtual QSize viewportSize() const = 0;
/**
* Set the shadow option -- by default the canvas controller draws
* a black shadow around the canvas widget, which you may or may
* not want.
*
* @param drawShadow if true, the shadow is drawn, if false, not
*/
virtual void setDrawShadow(bool drawShadow) = 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;
/**
* 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;
/**
* 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;
/**
* 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 canvas
* @returns action collection for this canvas, can be 0
*/
virtual 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 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)
{}
virtual ~KoDummyCanvasController()
{}
virtual void scrollContentsBy(int /*dx*/, int /*dy*/) {}
virtual QSize viewportSize() const { return QSize(); }
virtual void setDrawShadow(bool /*drawShadow*/) {}
virtual void setCanvas(KoCanvasBase *canvas) {Q_UNUSED(canvas)}
virtual KoCanvasBase *canvas() const {return 0;}
virtual int visibleHeight() const {return 0;}
virtual int visibleWidth() const {return 0;}
virtual int canvasOffsetX() const {return 0;}
virtual int canvasOffsetY() const {return 0;}
virtual void ensureVisible(const QRectF &/*rect*/, bool /*smooth */ = false) {}
virtual void ensureVisible(KoShape *shape) {Q_UNUSED(shape)}
virtual void zoomIn(const QPoint &/*center*/) {}
virtual void zoomOut(const QPoint &/*center*/) {}
virtual void zoomBy(const QPoint &/*center*/, qreal /*zoom*/) {}
virtual void zoomTo(const QRect &/*rect*/) {}
virtual void recenterPreferred() {}
virtual void setPreferredCenter(const QPointF &/*viewPoint*/) {}
virtual QPointF preferredCenter() const {return QPointF();}
virtual void pan(const QPoint &/*distance*/) {}
virtual QPoint scrollBarValue() const {return QPoint();}
virtual void setScrollBarValue(const QPoint &/*value*/) {}
virtual void updateDocumentSize(const QSize &/*sz*/, bool /*recalculateCenter*/) {}
virtual void setZoomWithWheel(bool /*zoom*/) {}
virtual void setVastScrolling(qreal /*factor*/) {}
+ QPointF currentCursorPosition() const override { return QPointF(); }
};
#endif
diff --git a/libs/flake/KoCanvasControllerWidget.cpp b/libs/flake/KoCanvasControllerWidget.cpp
index 91695c757c..820559bd72 100644
--- a/libs/flake/KoCanvasControllerWidget.cpp
+++ b/libs/flake/KoCanvasControllerWidget.cpp
@@ -1,588 +1,595 @@
/* 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;
if (q->canvasMode() == Spreadsheet && canvasWidget->layoutDirection() == Qt::RightToLeft) {
canvasWidget->scroll(-diff.x(), diff.y());
} else {
canvasWidget->scroll(diff.x(), diff.y());
}
}
}
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 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;
}
Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) {
KoCanvasObserverBase *observer = dynamic_cast(docker);
if (observer) {
observer->setObservedCanvas(q->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 && 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 differnt 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 loger 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(const QPoint&)), d->viewportWidget, SLOT(documentOffsetMoved(const QPoint&)));
}
KoCanvasControllerWidget::~KoCanvasControllerWidget()
{
d->unsetCanvas();
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
{
return viewport()->size();
}
void KoCanvasControllerWidget::setDrawShadow(bool drawShadow)
{
d->viewportWidget->setDrawShadow(drawShadow);
}
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.
d->resetScrollBars();
d->setDocumentOffset();
}
void KoCanvasControllerWidget::setCanvas(KoCanvasBase *canvas)
{
Q_ASSERT(canvas); // param is not null
if (d->canvas) {
d->unsetCanvas();
proxyObject->emitCanvasRemoved(this);
canvas->setCanvasController(0);
d->canvas->canvasWidget()->removeEventFilter(this);
}
canvas->setCanvasController(this);
d->canvas = canvas;
changeCanvasWidget(canvas->canvasWidget());
proxyObject->emitCanvasSet(this);
QTimer::singleShot(0, this, SLOT(activate()));
setPreferredCenterFractionX(0);
setPreferredCenterFractionY(0);
}
KoCanvasBase* KoCanvasControllerWidget::canvas() const
{
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)
{
// 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);
d->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::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());
}
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 f8f3bf1da7..68d9d80f2f 100644
--- a/libs/flake/KoCanvasControllerWidget.h
+++ b/libs/flake/KoCanvasControllerWidget.h
@@ -1,182 +1,184 @@
/* 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 parent the parent this widget will belong to
*/
explicit KoCanvasControllerWidget(KActionCollection * actionCollection, QWidget *parent = 0);
virtual ~KoCanvasControllerWidget();
/**
* Reimplemented from QAbstractScrollArea.
*/
void scrollContentsBy(int dx, int dy);
virtual QSize viewportSize() const;
/// Reimplemented from KoCanvasController
/**
* Activate this canvascontroller
*/
virtual void activate();
virtual void setDrawShadow(bool drawShadow);
virtual void setCanvas(KoCanvasBase *canvas);
virtual KoCanvasBase *canvas() const;
/**
* 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);
virtual int visibleHeight() const;
virtual int visibleWidth() const;
virtual int canvasOffsetX() const;
virtual int canvasOffsetY() const;
virtual void ensureVisible(const QRectF &rect, bool smooth = false);
virtual void ensureVisible(KoShape *shape);
/**
* 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);
virtual void zoomIn(const QPoint ¢er);
virtual void zoomOut(const QPoint ¢er);
virtual void zoomBy(const QPoint ¢er, qreal zoom);
virtual void zoomTo(const QRect &rect);
/**
* Zoom document keeping point \p widgetPoint unchanged
* \param widgetPoint sticky point in widget pixels
*/
virtual void zoomRelativeToPoint(const QPoint &widgetPoint, qreal zoomCoeff);
virtual void recenterPreferred();
virtual void setPreferredCenter(const QPointF &viewPoint);
/// Returns the currently set preferred center point in view coordinates (pixels)
virtual QPointF preferredCenter() const;
virtual void pan(const QPoint &distance);
virtual void setMargin(int margin);
virtual QPoint scrollBarValue() const;
/**
* Used by KisCanvasController to correct the scrollbars position
* after the rotation.
*/
virtual void setScrollBarValue(const QPoint &value);
virtual void updateDocumentSize(const QSize &sz, bool recalculateCenter = true);
/**
* Set mouse wheel to zoom behaviour
* @param zoom if true wheel will zoom instead of scroll, control modifier will scroll
*/
void setZoomWithWheel(bool zoom);
virtual void setVastScrolling(qreal factor);
+ QPointF currentCursorPosition() const 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;
/// reimplemented from QWidget
virtual void paintEvent(QPaintEvent *event);
/// reimplemented from QWidget
virtual void resizeEvent(QResizeEvent *resizeEvent);
/// reimplemented from QWidget
virtual void dragEnterEvent(QDragEnterEvent *event);
/// reimplemented from QWidget
virtual void dropEvent(QDropEvent *event);
/// reimplemented from QWidget
virtual void dragMoveEvent(QDragMoveEvent *event);
/// reimplemented from QWidget
virtual void dragLeaveEvent(QDragLeaveEvent *event);
/// reimplemented from QWidget
virtual void wheelEvent(QWheelEvent *event);
/// reimplemented from QWidget
virtual bool focusNextPrevChild(bool next);
private:
Q_PRIVATE_SLOT(d, void activate())
Private * const d;
};
#endif
diff --git a/libs/flake/KoDrag.cpp b/libs/flake/KoDrag.cpp
index 2f82798c27..51b9f1bd07 100644
--- a/libs/flake/KoDrag.cpp
+++ b/libs/flake/KoDrag.cpp
@@ -1,149 +1,117 @@
/* This file is part of the KDE project
* Copyright (C) 2007-2008 Thorsten Zachmann
* Copyright (C) 2009 Thomas Zander
*
* 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 "KoDrag.h"
#include "KoDragOdfSaveHelper.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KoShapeSavingContext.h"
+#include
+#include
+#include
+
+
class KoDragPrivate {
public:
KoDragPrivate() : mimeData(0) { }
~KoDragPrivate() { delete mimeData; }
QMimeData *mimeData;
};
KoDrag::KoDrag()
: d(new KoDragPrivate())
{
}
KoDrag::~KoDrag()
{
delete d;
}
-bool KoDrag::setOdf(const char *mimeType, KoDragOdfSaveHelper &helper)
+bool KoDrag::setOdf(const char *, KoDragOdfSaveHelper &)
{
- struct Finally {
- Finally(KoStore *s) : store(s) { }
- ~Finally() {
- delete store;
- }
- KoStore *store;
- };
-
- QBuffer buffer;
- KoStore *store = KoStore::createStore(&buffer, KoStore::Write, mimeType);
- Finally finally(store); // delete store when we exit this scope
- Q_ASSERT(store);
- Q_ASSERT(!store->bad());
-
- KoOdfWriteStore odfStore(store);
- KoEmbeddedDocumentSaver embeddedSaver;
-
- KoXmlWriter *manifestWriter = odfStore.manifestWriter(mimeType);
- KoXmlWriter *contentWriter = odfStore.contentWriter();
-
- if (!contentWriter) {
- return false;
- }
+ return false;
+}
- KoGenStyles mainStyles;
- KoXmlWriter *bodyWriter = odfStore.bodyWriter();
- KoShapeSavingContext *context = helper.context(bodyWriter, mainStyles, embeddedSaver);
+bool KoDrag::setSvg(const QList originalShapes)
+{
+ QRectF boundingRect;
+ QList shapes;
- if (!helper.writeBody()) {
- return false;
+ Q_FOREACH (KoShape *shape, originalShapes) {
+ boundingRect |= shape->boundingRect();
+ shapes.append(shape->cloneShape());
}
- mainStyles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, contentWriter);
+ qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
- odfStore.closeContentWriter();
-
- //add manifest line for content.xml
- manifestWriter->addManifestEntry("content.xml", "text/xml");
-
-
- if (!mainStyles.saveOdfStylesDotXml(store, manifestWriter)) {
- return false;
- }
-
- if (!context->saveDataCenter(store, manifestWriter)) {
- debugFlake << "save data centers failed";
- return false;
- }
+ QBuffer buffer;
+ QLatin1String mimeType("image/svg+xml");
- // Save embedded objects
- KoDocumentBase::SavingContext documentContext(odfStore, embeddedSaver);
- if (!embeddedSaver.saveEmbeddedDocuments(documentContext)) {
- debugFlake << "save embedded documents failed";
- return false;
- }
+ buffer.open(QIODevice::WriteOnly);
- // Write out manifest file
- if (!odfStore.closeManifestWriter()) {
- return false;
- }
+ const QSizeF pageSize(boundingRect.right(), boundingRect.bottom());
+ SvgWriter writer(shapes, pageSize);
+ writer.save(buffer);
- delete store; // make sure the buffer if fully flushed.
- finally.store = 0;
- setData(mimeType, buffer.buffer());
+ buffer.close();
+ qDeleteAll(shapes);
+ setData(mimeType, buffer.data());
return true;
}
void KoDrag::setData(const QString &mimeType, const QByteArray &data)
{
if (d->mimeData == 0) {
d->mimeData = new QMimeData();
}
d->mimeData->setData(mimeType, data);
}
void KoDrag::addToClipboard()
{
if (d->mimeData) {
QApplication::clipboard()->setMimeData(d->mimeData);
d->mimeData = 0;
}
}
QMimeData * KoDrag::mimeData()
{
QMimeData *mimeData = d->mimeData;
d->mimeData = 0;
return mimeData;
}
diff --git a/libs/flake/KoDrag.h b/libs/flake/KoDrag.h
index 8ef1c26b0f..db1938fe0f 100644
--- a/libs/flake/KoDrag.h
+++ b/libs/flake/KoDrag.h
@@ -1,78 +1,83 @@
/* This file is part of the KDE project
* Copyright (C) 2007 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 KODRAG_H
#define KODRAG_H
#include "kritaflake_export.h"
+#include
+
class QMimeData;
class QString;
class QByteArray;
class KoDragOdfSaveHelper;
class KoDragPrivate;
+class KoShape;
/**
* Class for simplifying adding a odf to the clip board
*
* For saving the odf a KoDragOdfSaveHelper class is used.
* It implements the writing of the body of the document. The
* setOdf takes care of saving styles and all the other
* common stuff.
*/
class KRITAFLAKE_EXPORT KoDrag
{
public:
KoDrag();
~KoDrag();
/**
* Set odf mime type
*
* This calls helper.writeBody();
*
* @param mimeType used for creating the odf document
* @param helper helper for saving the body of the odf document
*/
- bool setOdf(const char *mimeType, KoDragOdfSaveHelper &helper);
+ bool setOdf(const char *, KoDragOdfSaveHelper &);
+
+ bool setSvg(const QList shapes);
/**
* Add additional mimeTypes
*/
void setData(const QString &mimeType, const QByteArray &data);
/**
* Add the mimeData to the clipboard
*/
void addToClipboard();
/**
* Get the mime data
*
* This transfers the ownership of the mimeData to the caller
*
* This function is for use in automated tests
*/
QMimeData *mimeData();
private:
KoDragPrivate * const d;
};
#endif /* KODRAG_H */
diff --git a/libs/flake/KoShape.cpp b/libs/flake/KoShape.cpp
index d1bb8c4226..bdaacdeec6 100644
--- a/libs/flake/KoShape.cpp
+++ b/libs/flake/KoShape.cpp
@@ -1,2398 +1,2407 @@
/* This file is part of the KDE project
Copyright (C) 2006 C. Boemann Rasmussen
Copyright (C) 2006-2010 Thomas Zander
Copyright (C) 2006-2010 Thorsten Zachmann
Copyright (C) 2007-2009,2011 Jan Hambrecht
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 "KoShape.h"
#include "KoShape_p.h"
#include "KoShapeContainer.h"
#include "KoShapeLayer.h"
#include "KoShapeContainerModel.h"
#include "KoSelection.h"
#include "KoPointerEvent.h"
#include "KoInsets.h"
#include "KoShapeStrokeModel.h"
#include "KoShapeBackground.h"
#include "KoColorBackground.h"
#include "KoHatchBackground.h"
#include "KoGradientBackground.h"
#include "KoPatternBackground.h"
#include "KoShapeManager.h"
#include "KoShapeUserData.h"
#include "KoShapeApplicationData.h"
#include "KoShapeSavingContext.h"
#include "KoShapeLoadingContext.h"
#include "KoViewConverter.h"
#include "KoShapeStroke.h"
#include "KoShapeShadow.h"
#include "KoClipPath.h"
#include "KoPathShape.h"
#include "KoOdfWorkaround.h"
#include "KoFilterEffectStack.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_assert.h"
#include
#include "KoOdfGradientBackground.h"
#include
// KoShapePrivate
KoShapePrivate::KoShapePrivate(KoShape *shape)
: q_ptr(shape),
size(50, 50),
parent(0),
shadow(0),
border(0),
filterEffectStack(0),
transparency(0.0),
zIndex(0),
runThrough(0),
visible(true),
printable(true),
geometryProtected(false),
keepAspect(false),
selectable(true),
detectCollision(false),
protectContent(false),
textRunAroundSide(KoShape::BiggestRunAroundSide),
textRunAroundDistanceLeft(0.0),
textRunAroundDistanceTop(0.0),
textRunAroundDistanceRight(0.0),
textRunAroundDistanceBottom(0.0),
textRunAroundThreshold(0.0),
textRunAroundContour(KoShape::ContourFull)
{
connectors[KoConnectionPoint::TopConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::TopConnectionPoint);
connectors[KoConnectionPoint::RightConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::RightConnectionPoint);
connectors[KoConnectionPoint::BottomConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::BottomConnectionPoint);
connectors[KoConnectionPoint::LeftConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::LeftConnectionPoint);
connectors[KoConnectionPoint::FirstCustomConnectionPoint] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter);
}
KoShapePrivate::KoShapePrivate(const KoShapePrivate &rhs, KoShape *q)
: q_ptr(q),
size(rhs.size),
shapeId(rhs.shapeId),
name(rhs.name),
localMatrix(rhs.localMatrix),
connectors(rhs.connectors),
parent(0), // to be initialized later
shapeManagers(), // to be initialized later
toolDelegates(), // FIXME: how to initialize them?
userData(rhs.userData ? rhs.userData->clone() : 0),
stroke(rhs.stroke),
fill(rhs.fill),
dependees(), // FIXME: how to initialize them?
shadow(0), // WARNING: not implemented in Krita
border(0), // WARNING: not implemented in Krita
clipPath(rhs.clipPath ? rhs.clipPath->clone() : 0),
clipMask(rhs.clipMask ? rhs.clipMask->clone() : 0),
additionalAttributes(rhs.additionalAttributes),
additionalStyleAttributes(rhs.additionalStyleAttributes),
filterEffectStack(0), // WARNING: not implemented in Krita
transparency(rhs.transparency),
hyperLink(rhs.hyperLink),
zIndex(rhs.zIndex),
runThrough(rhs.runThrough),
visible(rhs.visible),
printable(rhs.visible),
geometryProtected(rhs.geometryProtected),
keepAspect(rhs.keepAspect),
selectable(rhs.selectable),
detectCollision(rhs.detectCollision),
protectContent(rhs.protectContent),
textRunAroundSide(rhs.textRunAroundSide),
textRunAroundDistanceLeft(rhs.textRunAroundDistanceLeft),
textRunAroundDistanceTop(rhs.textRunAroundDistanceTop),
textRunAroundDistanceRight(rhs.textRunAroundDistanceRight),
textRunAroundDistanceBottom(rhs.textRunAroundDistanceBottom),
textRunAroundThreshold(rhs.textRunAroundThreshold),
textRunAroundContour(rhs.textRunAroundContour)
{
}
KoShapePrivate::~KoShapePrivate()
{
Q_Q(KoShape);
if (parent)
parent->removeShape(q);
Q_FOREACH (KoShapeManager *manager, shapeManagers) {
manager->remove(q);
}
if (shadow && !shadow->deref())
delete shadow;
if (filterEffectStack && !filterEffectStack->deref())
delete filterEffectStack;
}
void KoShapePrivate::shapeChanged(KoShape::ChangeType type)
{
Q_Q(KoShape);
if (parent)
parent->model()->childChanged(q, type);
q->shapeChanged(type);
Q_FOREACH (KoShape * shape, dependees) {
shape->shapeChanged(type, q);
}
Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners) {
listener->notifyShapeChangedImpl(type, q);
}
}
void KoShapePrivate::updateStroke()
{
Q_Q(KoShape);
if (!stroke) return;
KoInsets insets;
stroke->strokeInsets(q, insets);
QSizeF inner = q->size();
// update left
q->update(QRectF(-insets.left, -insets.top, insets.left,
inner.height() + insets.top + insets.bottom));
// update top
q->update(QRectF(-insets.left, -insets.top,
inner.width() + insets.left + insets.right, insets.top));
// update right
q->update(QRectF(inner.width(), -insets.top, insets.right,
inner.height() + insets.top + insets.bottom));
// update bottom
q->update(QRectF(-insets.left, inner.height(),
inner.width() + insets.left + insets.right, insets.bottom));
}
void KoShapePrivate::addShapeManager(KoShapeManager *manager)
{
shapeManagers.insert(manager);
}
void KoShapePrivate::removeShapeManager(KoShapeManager *manager)
{
shapeManagers.remove(manager);
}
void KoShapePrivate::convertFromShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const
{
switch(point.alignment) {
case KoConnectionPoint::AlignNone:
point.position = KoFlake::toRelative(point.position, shapeSize);
point.position.rx() = qBound(0.0, point.position.x(), 1.0);
point.position.ry() = qBound(0.0, point.position.y(), 1.0);
break;
case KoConnectionPoint::AlignRight:
point.position.rx() -= shapeSize.width();
case KoConnectionPoint::AlignLeft:
point.position.ry() = 0.5*shapeSize.height();
break;
case KoConnectionPoint::AlignBottom:
point.position.ry() -= shapeSize.height();
case KoConnectionPoint::AlignTop:
point.position.rx() = 0.5*shapeSize.width();
break;
case KoConnectionPoint::AlignTopLeft:
// nothing to do here
break;
case KoConnectionPoint::AlignTopRight:
point.position.rx() -= shapeSize.width();
break;
case KoConnectionPoint::AlignBottomLeft:
point.position.ry() -= shapeSize.height();
break;
case KoConnectionPoint::AlignBottomRight:
point.position.rx() -= shapeSize.width();
point.position.ry() -= shapeSize.height();
break;
case KoConnectionPoint::AlignCenter:
point.position.rx() -= 0.5 * shapeSize.width();
point.position.ry() -= 0.5 * shapeSize.height();
break;
}
}
void KoShapePrivate::convertToShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const
{
switch(point.alignment) {
case KoConnectionPoint::AlignNone:
point.position = KoFlake::toAbsolute(point.position, shapeSize);
break;
case KoConnectionPoint::AlignRight:
point.position.rx() += shapeSize.width();
case KoConnectionPoint::AlignLeft:
point.position.ry() = 0.5*shapeSize.height();
break;
case KoConnectionPoint::AlignBottom:
point.position.ry() += shapeSize.height();
case KoConnectionPoint::AlignTop:
point.position.rx() = 0.5*shapeSize.width();
break;
case KoConnectionPoint::AlignTopLeft:
// nothing to do here
break;
case KoConnectionPoint::AlignTopRight:
point.position.rx() += shapeSize.width();
break;
case KoConnectionPoint::AlignBottomLeft:
point.position.ry() += shapeSize.height();
break;
case KoConnectionPoint::AlignBottomRight:
point.position.rx() += shapeSize.width();
point.position.ry() += shapeSize.height();
break;
case KoConnectionPoint::AlignCenter:
point.position.rx() += 0.5 * shapeSize.width();
point.position.ry() += 0.5 * shapeSize.height();
break;
}
}
// static
QString KoShapePrivate::getStyleProperty(const char *property, KoShapeLoadingContext &context)
{
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
QString value;
if (styleStack.hasProperty(KoXmlNS::draw, property)) {
value = styleStack.property(KoXmlNS::draw, property);
}
return value;
}
// ======== KoShape
KoShape::KoShape()
: d_ptr(new KoShapePrivate(this))
{
notifyChanged();
}
KoShape::KoShape(KoShapePrivate *dd)
: d_ptr(dd)
{
}
KoShape::~KoShape()
{
Q_D(KoShape);
d->shapeChanged(Deleted);
delete d_ptr;
}
KoShape *KoShape::cloneShape() const
{
return 0;
}
void KoShape::scale(qreal sx, qreal sy)
{
Q_D(KoShape);
QPointF pos = position();
QTransform scaleMatrix;
scaleMatrix.translate(pos.x(), pos.y());
scaleMatrix.scale(sx, sy);
scaleMatrix.translate(-pos.x(), -pos.y());
d->localMatrix = d->localMatrix * scaleMatrix;
notifyChanged();
d->shapeChanged(ScaleChanged);
}
void KoShape::rotate(qreal angle)
{
Q_D(KoShape);
QPointF center = d->localMatrix.map(QPointF(0.5 * size().width(), 0.5 * size().height()));
QTransform rotateMatrix;
rotateMatrix.translate(center.x(), center.y());
rotateMatrix.rotate(angle);
rotateMatrix.translate(-center.x(), -center.y());
d->localMatrix = d->localMatrix * rotateMatrix;
notifyChanged();
d->shapeChanged(RotationChanged);
}
void KoShape::shear(qreal sx, qreal sy)
{
Q_D(KoShape);
QPointF pos = position();
QTransform shearMatrix;
shearMatrix.translate(pos.x(), pos.y());
shearMatrix.shear(sx, sy);
shearMatrix.translate(-pos.x(), -pos.y());
d->localMatrix = d->localMatrix * shearMatrix;
notifyChanged();
d->shapeChanged(ShearChanged);
}
void KoShape::setSize(const QSizeF &newSize)
{
Q_D(KoShape);
QSizeF oldSize(size());
// always set size, as d->size and size() may vary
d->size = newSize;
if (oldSize == newSize)
return;
notifyChanged();
d->shapeChanged(SizeChanged);
}
void KoShape::setPosition(const QPointF &newPosition)
{
Q_D(KoShape);
QPointF currentPos = position();
if (newPosition == currentPos)
return;
QTransform translateMatrix;
translateMatrix.translate(newPosition.x() - currentPos.x(), newPosition.y() - currentPos.y());
d->localMatrix = d->localMatrix * translateMatrix;
notifyChanged();
d->shapeChanged(PositionChanged);
}
bool KoShape::hitTest(const QPointF &position) const
{
Q_D(const KoShape);
if (d->parent && d->parent->isClipped(this) && !d->parent->hitTest(position))
return false;
QPointF point = absoluteTransformation(0).inverted().map(position);
QRectF bb(QPointF(), size());
if (d->stroke) {
KoInsets insets;
d->stroke->strokeInsets(this, insets);
bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
}
if (bb.contains(point))
return true;
// if there is no shadow we can as well just leave
if (! d->shadow)
return false;
// the shadow has an offset to the shape, so we simply
// check if the position minus the shadow offset hits the shape
point = absoluteTransformation(0).inverted().map(position - d->shadow->offset());
return bb.contains(point);
}
QRectF KoShape::boundingRect() const
{
Q_D(const KoShape);
QTransform transform = absoluteTransformation(0);
QRectF bb = outlineRect();
if (d->stroke) {
KoInsets insets;
d->stroke->strokeInsets(this, insets);
bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
}
bb = transform.mapRect(bb);
if (d->shadow) {
KoInsets insets;
d->shadow->insets(insets);
bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
}
if (d->filterEffectStack) {
QRectF clipRect = d->filterEffectStack->clipRectForBoundingRect(outlineRect());
bb |= transform.mapRect(clipRect);
}
return bb;
}
+QRectF KoShape::boundingRect(const QList &shapes)
+{
+ QRectF boundingRect;
+ Q_FOREACH (KoShape *shape, shapes) {
+ boundingRect |= shape->boundingRect();
+ }
+ return boundingRect;
+}
+
QTransform KoShape::absoluteTransformation(const KoViewConverter *converter) const
{
Q_D(const KoShape);
QTransform matrix;
// apply parents matrix to inherit any transformations done there.
KoShapeContainer * container = d->parent;
if (container) {
if (container->inheritsTransform(this)) {
// We do need to pass the converter here, otherwise the parent's
// translation is not inherited.
matrix = container->absoluteTransformation(converter);
} else {
QSizeF containerSize = container->size();
QPointF containerPos = container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height());
if (converter)
containerPos = converter->documentToView(containerPos);
matrix.translate(containerPos.x(), containerPos.y());
}
}
if (converter) {
QPointF pos = d->localMatrix.map(QPointF());
QPointF trans = converter->documentToView(pos) - pos;
matrix.translate(trans.x(), trans.y());
}
return d->localMatrix * matrix;
}
void KoShape::applyAbsoluteTransformation(const QTransform &matrix)
{
QTransform globalMatrix = absoluteTransformation(0);
// the transformation is relative to the global coordinate system
// but we want to change the local matrix, so convert the matrix
// to be relative to the local coordinate system
QTransform transformMatrix = globalMatrix * matrix * globalMatrix.inverted();
applyTransformation(transformMatrix);
}
void KoShape::applyTransformation(const QTransform &matrix)
{
Q_D(KoShape);
d->localMatrix = matrix * d->localMatrix;
notifyChanged();
d->shapeChanged(GenericMatrixChange);
}
void KoShape::setTransformation(const QTransform &matrix)
{
Q_D(KoShape);
d->localMatrix = matrix;
notifyChanged();
d->shapeChanged(GenericMatrixChange);
}
QTransform KoShape::transformation() const
{
Q_D(const KoShape);
return d->localMatrix;
}
KoShape::ChildZOrderPolicy KoShape::childZOrderPolicy()
{
return ChildZDefault;
}
bool KoShape::compareShapeZIndex(KoShape *s1, KoShape *s2)
{
// First sort according to runThrough which is sort of a master level
KoShape *parentShapeS1 = s1->parent();
KoShape *parentShapeS2 = s2->parent();
int runThrough1 = s1->runThrough();
int runThrough2 = s2->runThrough();
while (parentShapeS1) {
if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) {
runThrough1 = parentShapeS1->runThrough();
} else {
runThrough1 = runThrough1 + parentShapeS1->runThrough();
}
parentShapeS1 = parentShapeS1->parent();
}
while (parentShapeS2) {
if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) {
runThrough2 = parentShapeS2->runThrough();
} else {
runThrough2 = runThrough2 + parentShapeS2->runThrough();
}
parentShapeS2 = parentShapeS2->parent();
}
if (runThrough1 > runThrough2) {
return false;
}
if (runThrough1 < runThrough2) {
return true;
}
// If on the same runThrough level then the zIndex is all that matters.
//
// We basically walk up through the parents until we find a common base parent
// To do that we need two loops where the inner loop walks up through the parents
// of s2 every time we step up one parent level on s1
//
// We don't update the index value until after we have seen that it's not a common base
// That way we ensure that two children of a common base are sorted according to their respective
// z value
bool foundCommonParent = false;
int index1 = s1->zIndex();
int index2 = s2->zIndex();
parentShapeS1 = s1;
parentShapeS2 = s2;
while (parentShapeS1 && !foundCommonParent) {
parentShapeS2 = s2;
index2 = parentShapeS2->zIndex();
while (parentShapeS2) {
if (parentShapeS2 == parentShapeS1) {
foundCommonParent = true;
break;
}
if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) {
index2 = parentShapeS2->zIndex();
}
parentShapeS2 = parentShapeS2->parent();
}
if (!foundCommonParent) {
if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) {
index1 = parentShapeS1->zIndex();
}
parentShapeS1 = parentShapeS1->parent();
}
}
// If the one shape is a parent/child of the other then sort so.
if (s1 == parentShapeS2) {
return true;
}
if (s2 == parentShapeS1) {
return false;
}
// If we went that far then the z-Index is used for sorting.
return index1 < index2;
}
void KoShape::setParent(KoShapeContainer *parent)
{
Q_D(KoShape);
if (d->parent == parent) {
return;
}
KoShapeContainer *oldParent = d->parent;
d->parent = 0; // avoids recursive removing
if (oldParent) {
oldParent->shapeInterface()->removeShape(this);
}
KIS_SAFE_ASSERT_RECOVER_NOOP(parent != this);
if (parent && parent != this) {
d->parent = parent;
parent->shapeInterface()->addShape(this);
}
notifyChanged();
d->shapeChanged(ParentChanged);
}
int KoShape::zIndex() const
{
Q_D(const KoShape);
return d->zIndex;
}
void KoShape::update() const
{
Q_D(const KoShape);
if (!d->shapeManagers.empty()) {
QRectF rect(boundingRect());
Q_FOREACH (KoShapeManager * manager, d->shapeManagers) {
manager->update(rect, this, true);
}
}
}
void KoShape::update(const QRectF &rect) const
{
if (rect.isEmpty() && !rect.isNull()) {
return;
}
Q_D(const KoShape);
if (!d->shapeManagers.empty() && isVisible()) {
QRectF rc(absoluteTransformation(0).mapRect(rect));
Q_FOREACH (KoShapeManager * manager, d->shapeManagers) {
manager->update(rc);
}
}
}
QPainterPath KoShape::outline() const
{
QPainterPath path;
path.addRect(outlineRect());
return path;
}
QRectF KoShape::outlineRect() const
{
const QSizeF s = size();
return QRectF(QPointF(0, 0), QSizeF(qMax(s.width(), qreal(0.0001)),
qMax(s.height(), qreal(0.0001))));
}
QPainterPath KoShape::shadowOutline() const
{
Q_D(const KoShape);
if (d->fill) {
return outline();
}
return QPainterPath();
}
QPointF KoShape::absolutePosition(KoFlake::AnchorPosition anchor) const
{
const QRectF rc = outlineRect();
QPointF point = rc.topLeft();
bool valid = false;
QPointF anchoredPoint = KoFlake::anchorToPoint(anchor, rc, &valid);
if (valid) {
point = anchoredPoint;
}
return absoluteTransformation(0).map(point);
}
void KoShape::setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor)
{
Q_D(KoShape);
QPointF currentAbsPosition = absolutePosition(anchor);
QPointF translate = newPosition - currentAbsPosition;
QTransform translateMatrix;
translateMatrix.translate(translate.x(), translate.y());
applyAbsoluteTransformation(translateMatrix);
notifyChanged();
d->shapeChanged(PositionChanged);
}
void KoShape::copySettings(const KoShape *shape)
{
Q_D(KoShape);
d->size = shape->size();
d->connectors.clear();
Q_FOREACH (const KoConnectionPoint &point, shape->connectionPoints())
addConnectionPoint(point);
d->zIndex = shape->zIndex();
d->visible = shape->isVisible();
// Ensure printable is true by default
if (!d->visible)
d->printable = true;
else
d->printable = shape->isPrintable();
d->geometryProtected = shape->isGeometryProtected();
d->protectContent = shape->isContentProtected();
d->selectable = shape->isSelectable();
d->keepAspect = shape->keepAspectRatio();
d->localMatrix = shape->d_ptr->localMatrix;
}
void KoShape::notifyChanged()
{
Q_D(KoShape);
Q_FOREACH (KoShapeManager * manager, d->shapeManagers) {
manager->notifyShapeChanged(this);
}
}
void KoShape::setUserData(KoShapeUserData *userData)
{
Q_D(KoShape);
d->userData.reset(userData);
}
KoShapeUserData *KoShape::userData() const
{
Q_D(const KoShape);
return d->userData.data();
}
bool KoShape::hasTransparency() const
{
Q_D(const KoShape);
if (! d->fill)
return true;
else
return d->fill->hasTransparency() || d->transparency > 0.0;
}
void KoShape::setTransparency(qreal transparency)
{
Q_D(KoShape);
d->transparency = qBound(0.0, transparency, 1.0);
d->shapeChanged(TransparencyChanged);
notifyChanged();
}
qreal KoShape::transparency(bool recursive) const
{
Q_D(const KoShape);
if (!recursive || !parent()) {
return d->transparency;
} else {
const qreal parentOpacity = 1.0-parent()->transparency(recursive);
const qreal childOpacity = 1.0-d->transparency;
return 1.0-(parentOpacity*childOpacity);
}
}
KoInsets KoShape::strokeInsets() const
{
Q_D(const KoShape);
KoInsets answer;
if (d->stroke)
d->stroke->strokeInsets(this, answer);
return answer;
}
qreal KoShape::rotation() const
{
Q_D(const KoShape);
// try to extract the rotation angle out of the local matrix
// if it is a pure rotation matrix
// check if the matrix has shearing mixed in
if (fabs(fabs(d->localMatrix.m12()) - fabs(d->localMatrix.m21())) > 1e-10)
return std::numeric_limits::quiet_NaN();
// check if the matrix has scaling mixed in
if (fabs(d->localMatrix.m11() - d->localMatrix.m22()) > 1e-10)
return std::numeric_limits::quiet_NaN();
// calculate the angle from the matrix elements
qreal angle = atan2(-d->localMatrix.m21(), d->localMatrix.m11()) * 180.0 / M_PI;
if (angle < 0.0)
angle += 360.0;
return angle;
}
QSizeF KoShape::size() const
{
Q_D(const KoShape);
return d->size;
}
QPointF KoShape::position() const
{
Q_D(const KoShape);
QPointF center = outlineRect().center();
return d->localMatrix.map(center) - center;
}
int KoShape::addConnectionPoint(const KoConnectionPoint &point)
{
Q_D(KoShape);
// get next glue point id
int nextConnectionPointId = KoConnectionPoint::FirstCustomConnectionPoint;
if (d->connectors.size())
nextConnectionPointId = qMax(nextConnectionPointId, (--d->connectors.end()).key()+1);
KoConnectionPoint p = point;
d->convertFromShapeCoordinates(p, size());
d->connectors[nextConnectionPointId] = p;
return nextConnectionPointId;
}
bool KoShape::setConnectionPoint(int connectionPointId, const KoConnectionPoint &point)
{
Q_D(KoShape);
if (connectionPointId < 0)
return false;
const bool insertPoint = !hasConnectionPoint(connectionPointId);
switch(connectionPointId) {
case KoConnectionPoint::TopConnectionPoint:
case KoConnectionPoint::RightConnectionPoint:
case KoConnectionPoint::BottomConnectionPoint:
case KoConnectionPoint::LeftConnectionPoint:
{
KoConnectionPoint::PointId id = static_cast(connectionPointId);
d->connectors[id] = KoConnectionPoint::defaultConnectionPoint(id);
break;
}
default:
{
KoConnectionPoint p = point;
d->convertFromShapeCoordinates(p, size());
d->connectors[connectionPointId] = p;
break;
}
}
if(!insertPoint)
d->shapeChanged(ConnectionPointChanged);
return true;
}
bool KoShape::hasConnectionPoint(int connectionPointId) const
{
Q_D(const KoShape);
return d->connectors.contains(connectionPointId);
}
KoConnectionPoint KoShape::connectionPoint(int connectionPointId) const
{
Q_D(const KoShape);
KoConnectionPoint p = d->connectors.value(connectionPointId, KoConnectionPoint());
// convert glue point to shape coordinates
d->convertToShapeCoordinates(p, size());
return p;
}
KoConnectionPoints KoShape::connectionPoints() const
{
Q_D(const KoShape);
QSizeF s = size();
KoConnectionPoints points = d->connectors;
KoConnectionPoints::iterator point = points.begin();
KoConnectionPoints::iterator lastPoint = points.end();
// convert glue points to shape coordinates
for(; point != lastPoint; ++point) {
d->convertToShapeCoordinates(point.value(), s);
}
return points;
}
void KoShape::removeConnectionPoint(int connectionPointId)
{
Q_D(KoShape);
d->connectors.remove(connectionPointId);
d->shapeChanged(ConnectionPointChanged);
}
void KoShape::clearConnectionPoints()
{
Q_D(KoShape);
d->connectors.clear();
}
KoShape::TextRunAroundSide KoShape::textRunAroundSide() const
{
Q_D(const KoShape);
return d->textRunAroundSide;
}
void KoShape::setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrought)
{
Q_D(KoShape);
if (side == RunThrough) {
if (runThrought == Background) {
setRunThrough(-1);
} else {
setRunThrough(1);
}
} else {
setRunThrough(0);
}
if ( d->textRunAroundSide == side) {
return;
}
d->textRunAroundSide = side;
notifyChanged();
d->shapeChanged(TextRunAroundChanged);
}
qreal KoShape::textRunAroundDistanceTop() const
{
Q_D(const KoShape);
return d->textRunAroundDistanceTop;
}
void KoShape::setTextRunAroundDistanceTop(qreal distance)
{
Q_D(KoShape);
d->textRunAroundDistanceTop = distance;
}
qreal KoShape::textRunAroundDistanceLeft() const
{
Q_D(const KoShape);
return d->textRunAroundDistanceLeft;
}
void KoShape::setTextRunAroundDistanceLeft(qreal distance)
{
Q_D(KoShape);
d->textRunAroundDistanceLeft = distance;
}
qreal KoShape::textRunAroundDistanceRight() const
{
Q_D(const KoShape);
return d->textRunAroundDistanceRight;
}
void KoShape::setTextRunAroundDistanceRight(qreal distance)
{
Q_D(KoShape);
d->textRunAroundDistanceRight = distance;
}
qreal KoShape::textRunAroundDistanceBottom() const
{
Q_D(const KoShape);
return d->textRunAroundDistanceBottom;
}
void KoShape::setTextRunAroundDistanceBottom(qreal distance)
{
Q_D(KoShape);
d->textRunAroundDistanceBottom = distance;
}
qreal KoShape::textRunAroundThreshold() const
{
Q_D(const KoShape);
return d->textRunAroundThreshold;
}
void KoShape::setTextRunAroundThreshold(qreal threshold)
{
Q_D(KoShape);
d->textRunAroundThreshold = threshold;
}
KoShape::TextRunAroundContour KoShape::textRunAroundContour() const
{
Q_D(const KoShape);
return d->textRunAroundContour;
}
void KoShape::setTextRunAroundContour(KoShape::TextRunAroundContour contour)
{
Q_D(KoShape);
d->textRunAroundContour = contour;
}
void KoShape::setBackground(QSharedPointer fill)
{
Q_D(KoShape);
d->fill = fill;
d->shapeChanged(BackgroundChanged);
notifyChanged();
}
QSharedPointer KoShape::background() const
{
Q_D(const KoShape);
return d->fill;
}
void KoShape::setZIndex(int zIndex)
{
Q_D(KoShape);
if (d->zIndex == zIndex)
return;
d->zIndex = zIndex;
notifyChanged();
}
int KoShape::runThrough()
{
Q_D(const KoShape);
return d->runThrough;
}
void KoShape::setRunThrough(short int runThrough)
{
Q_D(KoShape);
d->runThrough = runThrough;
}
void KoShape::setVisible(bool on)
{
Q_D(KoShape);
int _on = (on ? 1 : 0);
if (d->visible == _on) return;
d->visible = _on;
}
bool KoShape::isVisible(bool recursive) const
{
Q_D(const KoShape);
if (! recursive)
return d->visible;
if (recursive && ! d->visible)
return false;
KoShapeContainer * parentShape = parent();
while (parentShape) {
if (! parentShape->isVisible())
return false;
parentShape = parentShape->parent();
}
return true;
}
void KoShape::setPrintable(bool on)
{
Q_D(KoShape);
d->printable = on;
}
bool KoShape::isPrintable() const
{
Q_D(const KoShape);
if (d->visible)
return d->printable;
else
return false;
}
void KoShape::setSelectable(bool selectable)
{
Q_D(KoShape);
d->selectable = selectable;
}
bool KoShape::isSelectable() const
{
Q_D(const KoShape);
return d->selectable;
}
void KoShape::setGeometryProtected(bool on)
{
Q_D(KoShape);
d->geometryProtected = on;
}
bool KoShape::isGeometryProtected() const
{
Q_D(const KoShape);
return d->geometryProtected;
}
void KoShape::setContentProtected(bool protect)
{
Q_D(KoShape);
d->protectContent = protect;
}
bool KoShape::isContentProtected() const
{
Q_D(const KoShape);
return d->protectContent;
}
KoShapeContainer *KoShape::parent() const
{
Q_D(const KoShape);
return d->parent;
}
void KoShape::setKeepAspectRatio(bool keepAspect)
{
Q_D(KoShape);
d->keepAspect = keepAspect;
d->shapeChanged(KeepAspectRatioChange);
notifyChanged();
}
bool KoShape::keepAspectRatio() const
{
Q_D(const KoShape);
return d->keepAspect;
}
QString KoShape::shapeId() const
{
Q_D(const KoShape);
return d->shapeId;
}
void KoShape::setShapeId(const QString &id)
{
Q_D(KoShape);
d->shapeId = id;
}
void KoShape::setCollisionDetection(bool detect)
{
Q_D(KoShape);
d->detectCollision = detect;
}
bool KoShape::collisionDetection()
{
Q_D(KoShape);
return d->detectCollision;
}
KoShapeStrokeModelSP KoShape::stroke() const
{
Q_D(const KoShape);
return d->stroke;
}
void KoShape::setStroke(KoShapeStrokeModelSP stroke)
{
Q_D(KoShape);
// TODO: check if it really updates stuff
d->updateStroke();
d->stroke = stroke;
d->updateStroke();
d->shapeChanged(StrokeChanged);
notifyChanged();
}
void KoShape::setShadow(KoShapeShadow *shadow)
{
Q_D(KoShape);
if (d->shadow)
d->shadow->deref();
d->shadow = shadow;
if (d->shadow) {
d->shadow->ref();
// TODO update changed area
}
d->shapeChanged(ShadowChanged);
notifyChanged();
}
KoShapeShadow *KoShape::shadow() const
{
Q_D(const KoShape);
return d->shadow;
}
void KoShape::setBorder(KoBorder *border)
{
Q_D(KoShape);
if (d->border) {
// The shape owns the border.
delete d->border;
}
d->border = border;
d->shapeChanged(BorderChanged);
notifyChanged();
}
KoBorder *KoShape::border() const
{
Q_D(const KoShape);
return d->border;
}
void KoShape::setClipPath(KoClipPath *clipPath)
{
Q_D(KoShape);
d->clipPath.reset(clipPath);
d->shapeChanged(ClipPathChanged);
notifyChanged();
}
KoClipPath * KoShape::clipPath() const
{
Q_D(const KoShape);
return d->clipPath.data();
}
void KoShape::setClipMask(KoClipMask *clipMask)
{
Q_D(KoShape);
d->clipMask.reset(clipMask);
}
KoClipMask* KoShape::clipMask() const
{
Q_D(const KoShape);
return d->clipMask.data();
}
QTransform KoShape::transform() const
{
Q_D(const KoShape);
return d->localMatrix;
}
QString KoShape::name() const
{
Q_D(const KoShape);
return d->name;
}
void KoShape::setName(const QString &name)
{
Q_D(KoShape);
d->name = name;
}
void KoShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const
{
Q_UNUSED(converter);
Q_UNUSED(asynchronous);
}
bool KoShape::isEditable() const
{
Q_D(const KoShape);
if (!d->visible || d->geometryProtected)
return false;
if (d->parent && d->parent->isChildLocked(this))
return false;
return true;
}
// painting
void KoShape::paintBorder(QPainter &painter, const KoViewConverter &converter)
{
Q_UNUSED(converter);
KoBorder *bd = border();
if (!bd) {
return;
}
QRectF borderRect = QRectF(QPointF(0, 0), size());
// Paint the border.
bd->paint(painter, borderRect, KoBorder::PaintInsideLine);
}
// loading & saving methods
QString KoShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const
{
Q_D(const KoShape);
// and fill the style
KoShapeStrokeModelSP sm = stroke();
if (sm) {
sm->fillStyle(style, context);
}
else {
style.addProperty("draw:stroke", "none", KoGenStyle::GraphicType);
}
KoShapeShadow *s = shadow();
if (s)
s->fillStyle(style, context);
QSharedPointer bg = background();
if (bg) {
bg->fillStyle(style, context);
}
else {
style.addProperty("draw:fill", "none", KoGenStyle::GraphicType);
}
KoBorder *b = border();
if (b) {
b->saveOdf(style);
}
if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) {
style.setAutoStyleInStylesDotXml(true);
}
QString value;
if (isGeometryProtected()) {
value = "position size";
}
if (isContentProtected()) {
if (! value.isEmpty())
value += ' ';
value += "content";
}
if (!value.isEmpty()) {
style.addProperty("style:protect", value, KoGenStyle::GraphicType);
}
QMap::const_iterator it(d->additionalStyleAttributes.constBegin());
for (; it != d->additionalStyleAttributes.constEnd(); ++it) {
style.addProperty(it.key(), it.value());
}
if (parent() && parent()->isClipped(this)) {
/*
* In Calligra clipping is done using a parent shape which can be rotated, sheared etc
* and even non-square. So the ODF interoperability version we write here is really
* just a very simple version of that...
*/
qreal top = -position().y();
qreal left = -position().x();
qreal right = parent()->size().width() - size().width() - left;
qreal bottom = parent()->size().height() - size().height() - top;
style.addProperty("fo:clip", QString("rect(%1pt, %2pt, %3pt, %4pt)")
.arg(top, 10, 'f').arg(right, 10, 'f')
.arg(bottom, 10, 'f').arg(left, 10, 'f'), KoGenStyle::GraphicType);
}
QString wrap;
switch (textRunAroundSide()) {
case BiggestRunAroundSide:
wrap = "biggest";
break;
case LeftRunAroundSide:
wrap = "left";
break;
case RightRunAroundSide:
wrap = "right";
break;
case EnoughRunAroundSide:
wrap = "dynamic";
break;
case BothRunAroundSide:
wrap = "parallel";
break;
case NoRunAround:
wrap = "none";
break;
case RunThrough:
wrap = "run-through";
break;
}
style.addProperty("style:wrap", wrap, KoGenStyle::GraphicType);
switch (textRunAroundContour()) {
case ContourBox:
style.addProperty("style:wrap-contour", "false", KoGenStyle::GraphicType);
break;
case ContourFull:
style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType);
style.addProperty("style:wrap-contour-mode", "full", KoGenStyle::GraphicType);
break;
case ContourOutside:
style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType);
style.addProperty("style:wrap-contour-mode", "outside", KoGenStyle::GraphicType);
break;
}
style.addPropertyPt("style:wrap-dynamic-threshold", textRunAroundThreshold(), KoGenStyle::GraphicType);
if ((textRunAroundDistanceLeft() == textRunAroundDistanceRight())
&& (textRunAroundDistanceTop() == textRunAroundDistanceBottom())
&& (textRunAroundDistanceLeft() == textRunAroundDistanceTop())) {
style.addPropertyPt("fo:margin", textRunAroundDistanceLeft(), KoGenStyle::GraphicType);
} else {
style.addPropertyPt("fo:margin-left", textRunAroundDistanceLeft(), KoGenStyle::GraphicType);
style.addPropertyPt("fo:margin-top", textRunAroundDistanceTop(), KoGenStyle::GraphicType);
style.addPropertyPt("fo:margin-right", textRunAroundDistanceRight(), KoGenStyle::GraphicType);
style.addPropertyPt("fo:margin-bottom", textRunAroundDistanceBottom(), KoGenStyle::GraphicType);
}
return context.mainStyles().insert(style, context.isSet(KoShapeSavingContext::PresentationShape) ? "pr" : "gr");
}
void KoShape::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context)
{
Q_D(KoShape);
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
styleStack.setTypeProperties("graphic");
d->fill.clear();
d->stroke.clear();
if (d->shadow && !d->shadow->deref()) {
delete d->shadow;
d->shadow = 0;
}
setBackground(loadOdfFill(context));
setStroke(loadOdfStroke(element, context));
setShadow(d->loadOdfShadow(context));
setBorder(d->loadOdfBorder(context));
QString protect(styleStack.property(KoXmlNS::style, "protect"));
setGeometryProtected(protect.contains("position") || protect.contains("size"));
setContentProtected(protect.contains("content"));
QString margin = styleStack.property(KoXmlNS::fo, "margin");
if (!margin.isEmpty()) {
setTextRunAroundDistanceLeft(KoUnit::parseValue(margin));
setTextRunAroundDistanceTop(KoUnit::parseValue(margin));
setTextRunAroundDistanceRight(KoUnit::parseValue(margin));
setTextRunAroundDistanceBottom(KoUnit::parseValue(margin));
}
margin = styleStack.property(KoXmlNS::fo, "margin-left");
if (!margin.isEmpty()) {
setTextRunAroundDistanceLeft(KoUnit::parseValue(margin));
}
margin = styleStack.property(KoXmlNS::fo, "margin-top");
if (!margin.isEmpty()) {
setTextRunAroundDistanceTop(KoUnit::parseValue(margin));
}
margin = styleStack.property(KoXmlNS::fo, "margin-right");
if (!margin.isEmpty()) {
setTextRunAroundDistanceRight(KoUnit::parseValue(margin));
}
margin = styleStack.property(KoXmlNS::fo, "margin-bottom");
if (!margin.isEmpty()) {
setTextRunAroundDistanceBottom(KoUnit::parseValue(margin));
}
QString wrap;
if (styleStack.hasProperty(KoXmlNS::style, "wrap")) {
wrap = styleStack.property(KoXmlNS::style, "wrap");
} else {
// no value given in the file, but guess biggest
wrap = "biggest";
}
if (wrap == "none") {
setTextRunAroundSide(KoShape::NoRunAround);
} else if (wrap == "run-through") {
QString runTrought = styleStack.property(KoXmlNS::style, "run-through", "background");
if (runTrought == "background") {
setTextRunAroundSide(KoShape::RunThrough, KoShape::Background);
} else {
setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground);
}
} else {
if (wrap == "biggest")
setTextRunAroundSide(KoShape::BiggestRunAroundSide);
else if (wrap == "left")
setTextRunAroundSide(KoShape::LeftRunAroundSide);
else if (wrap == "right")
setTextRunAroundSide(KoShape::RightRunAroundSide);
else if (wrap == "dynamic")
setTextRunAroundSide(KoShape::EnoughRunAroundSide);
else if (wrap == "parallel")
setTextRunAroundSide(KoShape::BothRunAroundSide);
}
if (styleStack.hasProperty(KoXmlNS::style, "wrap-dynamic-threshold")) {
QString wrapThreshold = styleStack.property(KoXmlNS::style, "wrap-dynamic-threshold");
if (!wrapThreshold.isEmpty()) {
setTextRunAroundThreshold(KoUnit::parseValue(wrapThreshold));
}
}
if (styleStack.property(KoXmlNS::style, "wrap-contour", "false") == "true") {
if (styleStack.property(KoXmlNS::style, "wrap-contour-mode", "full") == "full") {
setTextRunAroundContour(KoShape::ContourFull);
} else {
setTextRunAroundContour(KoShape::ContourOutside);
}
} else {
setTextRunAroundContour(KoShape::ContourBox);
}
}
bool KoShape::loadOdfAttributes(const KoXmlElement &element, KoShapeLoadingContext &context, int attributes)
{
if (attributes & OdfPosition) {
QPointF pos(position());
if (element.hasAttributeNS(KoXmlNS::svg, "x"))
pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString())));
if (element.hasAttributeNS(KoXmlNS::svg, "y"))
pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString())));
setPosition(pos);
}
if (attributes & OdfSize) {
QSizeF s(size());
if (element.hasAttributeNS(KoXmlNS::svg, "width"))
s.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString())));
if (element.hasAttributeNS(KoXmlNS::svg, "height"))
s.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString())));
setSize(s);
}
if (attributes & OdfLayer) {
if (element.hasAttributeNS(KoXmlNS::draw, "layer")) {
KoShapeLayer *layer = context.layer(element.attributeNS(KoXmlNS::draw, "layer"));
if (layer) {
setParent(layer);
}
}
}
if (attributes & OdfId) {
KoElementReference ref;
ref.loadOdf(element);
if (ref.isValid()) {
context.addShapeId(this, ref.toString());
}
}
if (attributes & OdfZIndex) {
if (element.hasAttributeNS(KoXmlNS::draw, "z-index")) {
setZIndex(element.attributeNS(KoXmlNS::draw, "z-index").toInt());
} else {
setZIndex(context.zIndex());
}
}
if (attributes & OdfName) {
if (element.hasAttributeNS(KoXmlNS::draw, "name")) {
setName(element.attributeNS(KoXmlNS::draw, "name"));
}
}
if (attributes & OdfStyle) {
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
styleStack.save();
if (element.hasAttributeNS(KoXmlNS::draw, "style-name")) {
context.odfLoadingContext().fillStyleStack(element, KoXmlNS::draw, "style-name", "graphic");
}
if (element.hasAttributeNS(KoXmlNS::presentation, "style-name")) {
context.odfLoadingContext().fillStyleStack(element, KoXmlNS::presentation, "style-name", "presentation");
}
loadStyle(element, context);
styleStack.restore();
}
if (attributes & OdfTransformation) {
QString transform = element.attributeNS(KoXmlNS::draw, "transform", QString());
if (! transform.isEmpty())
applyAbsoluteTransformation(parseOdfTransform(transform));
}
if (attributes & OdfAdditionalAttributes) {
QSet additionalAttributeData = KoShapeLoadingContext::additionalAttributeData();
Q_FOREACH (const KoShapeLoadingContext::AdditionalAttributeData &attributeData, additionalAttributeData) {
if (element.hasAttributeNS(attributeData.ns, attributeData.tag)) {
QString value = element.attributeNS(attributeData.ns, attributeData.tag);
//debugFlake << "load additional attribute" << attributeData.tag << value;
setAdditionalAttribute(attributeData.name, value);
}
}
}
if (attributes & OdfCommonChildElements) {
// load glue points (connection points)
loadOdfGluePoints(element, context);
}
return true;
}
QSharedPointer KoShape::loadOdfFill(KoShapeLoadingContext &context) const
{
QString fill = KoShapePrivate::getStyleProperty("fill", context);
QSharedPointer bg;
if (fill == "solid") {
bg = QSharedPointer(new KoColorBackground());
}
else if (fill == "hatch") {
bg = QSharedPointer(new KoHatchBackground());
}
else if (fill == "gradient") {
QString styleName = KoShapePrivate::getStyleProperty("fill-gradient-name", context);
KoXmlElement *e = context.odfLoadingContext().stylesReader().drawStyles("gradient")[styleName];
QString style;
if (e) {
style = e->attributeNS(KoXmlNS::draw, "style", QString());
}
if ((style == "rectangular") || (style == "square")) {
bg = QSharedPointer(new KoOdfGradientBackground());
} else {
QGradient *gradient = new QLinearGradient();
gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
bg = QSharedPointer(new KoGradientBackground(gradient));
}
} else if (fill == "bitmap") {
bg = QSharedPointer(new KoPatternBackground(context.imageCollection()));
#ifndef NWORKAROUND_ODF_BUGS
} else if (fill.isEmpty()) {
bg = QSharedPointer(KoOdfWorkaround::fixBackgroundColor(this, context));
return bg;
#endif
} else {
return QSharedPointer(0);
}
if (!bg->loadStyle(context.odfLoadingContext(), size())) {
return QSharedPointer(0);
}
return bg;
}
KoShapeStrokeModelSP KoShape::loadOdfStroke(const KoXmlElement &element, KoShapeLoadingContext &context) const
{
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader();
QString stroke = KoShapePrivate::getStyleProperty("stroke", context);
if (stroke == "solid" || stroke == "dash") {
QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, stroke, stylesReader);
QSharedPointer stroke(new KoShapeStroke());
if (styleStack.hasProperty(KoXmlNS::calligra, "stroke-gradient")) {
QString gradientName = styleStack.property(KoXmlNS::calligra, "stroke-gradient");
QBrush brush = KoOdfGraphicStyles::loadOdfGradientStyleByName(stylesReader, gradientName, size());
stroke->setLineBrush(brush);
} else {
stroke->setColor(pen.color());
}
#ifndef NWORKAROUND_ODF_BUGS
KoOdfWorkaround::fixPenWidth(pen, context);
#endif
stroke->setLineWidth(pen.widthF());
stroke->setJoinStyle(pen.joinStyle());
stroke->setLineStyle(pen.style(), pen.dashPattern());
stroke->setCapStyle(pen.capStyle());
return stroke;
#ifndef NWORKAROUND_ODF_BUGS
} else if (stroke.isEmpty()) {
QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, "solid", stylesReader);
if (KoOdfWorkaround::fixMissingStroke(pen, element, context, this)) {
QSharedPointer stroke(new KoShapeStroke());
#ifndef NWORKAROUND_ODF_BUGS
KoOdfWorkaround::fixPenWidth(pen, context);
#endif
stroke->setLineWidth(pen.widthF());
stroke->setJoinStyle(pen.joinStyle());
stroke->setLineStyle(pen.style(), pen.dashPattern());
stroke->setCapStyle(pen.capStyle());
stroke->setColor(pen.color());
return stroke;
}
#endif
}
return KoShapeStrokeModelSP();
}
KoShapeShadow *KoShapePrivate::loadOdfShadow(KoShapeLoadingContext &context) const
{
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
QString shadowStyle = KoShapePrivate::getStyleProperty("shadow", context);
if (shadowStyle == "visible" || shadowStyle == "hidden") {
KoShapeShadow *shadow = new KoShapeShadow();
QColor shadowColor(styleStack.property(KoXmlNS::draw, "shadow-color"));
qreal offsetX = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-x"));
qreal offsetY = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-y"));
shadow->setOffset(QPointF(offsetX, offsetY));
qreal blur = KoUnit::parseValue(styleStack.property(KoXmlNS::calligra, "shadow-blur-radius"));
shadow->setBlur(blur);
QString opacity = styleStack.property(KoXmlNS::draw, "shadow-opacity");
if (! opacity.isEmpty() && opacity.right(1) == "%")
shadowColor.setAlphaF(opacity.left(opacity.length() - 1).toFloat() / 100.0);
shadow->setColor(shadowColor);
shadow->setVisible(shadowStyle == "visible");
return shadow;
}
return 0;
}
KoBorder *KoShapePrivate::loadOdfBorder(KoShapeLoadingContext &context) const
{
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
KoBorder *border = new KoBorder();
if (border->loadOdf(styleStack)) {
return border;
}
delete border;
return 0;
}
void KoShape::loadOdfGluePoints(const KoXmlElement &element, KoShapeLoadingContext &context)
{
Q_D(KoShape);
KoXmlElement child;
bool hasCenterGluePoint = false;
forEachElement(child, element) {
if (child.namespaceURI() != KoXmlNS::draw)
continue;
if (child.localName() != "glue-point")
continue;
// NOTE: this uses draw:id, but apparently while ODF 1.2 has deprecated
// all use of draw:id for xml:id, it didn't specify that here, so it
// doesn't support xml:id (and so, maybe, shouldn't use KoElementReference.
const QString id = child.attributeNS(KoXmlNS::draw, "id", QString());
const int index = id.toInt();
// connection point in center should be default but odf doesn't support,
// in new shape, first custom point is in center, it's okay to replace that point
// with point from xml now, we'll add it back later
if(id.isEmpty() || index < KoConnectionPoint::FirstCustomConnectionPoint ||
(index != KoConnectionPoint::FirstCustomConnectionPoint && d->connectors.contains(index))) {
warnFlake << "glue-point with no or invalid id";
continue;
}
QString xStr = child.attributeNS(KoXmlNS::svg, "x", QString()).simplified();
QString yStr = child.attributeNS(KoXmlNS::svg, "y", QString()).simplified();
if(xStr.isEmpty() || yStr.isEmpty()) {
warnFlake << "glue-point with invald position";
continue;
}
KoConnectionPoint connector;
const QString align = child.attributeNS(KoXmlNS::draw, "align", QString());
if (align.isEmpty()) {
#ifndef NWORKAROUND_ODF_BUGS
KoOdfWorkaround::fixGluePointPosition(xStr, context);
KoOdfWorkaround::fixGluePointPosition(yStr, context);
#endif
if(!xStr.endsWith('%') || !yStr.endsWith('%')) {
warnFlake << "glue-point with invald position";
continue;
}
// x and y are relative to drawing object center
connector.position.setX(xStr.remove('%').toDouble()/100.0);
connector.position.setY(yStr.remove('%').toDouble()/100.0);
// convert position to be relative to top-left corner
connector.position += QPointF(0.5, 0.5);
connector.position.rx() = qBound(0.0, connector.position.x(), 1.0);
connector.position.ry() = qBound(0.0, connector.position.y(), 1.0);
} else {
// absolute distances to the edge specified by align
connector.position.setX(KoUnit::parseValue(xStr));
connector.position.setY(KoUnit::parseValue(yStr));
if (align == "top-left") {
connector.alignment = KoConnectionPoint::AlignTopLeft;
} else if (align == "top") {
connector.alignment = KoConnectionPoint::AlignTop;
} else if (align == "top-right") {
connector.alignment = KoConnectionPoint::AlignTopRight;
} else if (align == "left") {
connector.alignment = KoConnectionPoint::AlignLeft;
} else if (align == "center") {
connector.alignment = KoConnectionPoint::AlignCenter;
} else if (align == "right") {
connector.alignment = KoConnectionPoint::AlignRight;
} else if (align == "bottom-left") {
connector.alignment = KoConnectionPoint::AlignBottomLeft;
} else if (align == "bottom") {
connector.alignment = KoConnectionPoint::AlignBottom;
} else if (align == "bottom-right") {
connector.alignment = KoConnectionPoint::AlignBottomRight;
}
debugFlake << "using alignment" << align;
}
const QString escape = child.attributeNS(KoXmlNS::draw, "escape-direction", QString());
if (!escape.isEmpty()) {
if (escape == "horizontal") {
connector.escapeDirection = KoConnectionPoint::HorizontalDirections;
} else if (escape == "vertical") {
connector.escapeDirection = KoConnectionPoint::VerticalDirections;
} else if (escape == "left") {
connector.escapeDirection = KoConnectionPoint::LeftDirection;
} else if (escape == "right") {
connector.escapeDirection = KoConnectionPoint::RightDirection;
} else if (escape == "up") {
connector.escapeDirection = KoConnectionPoint::UpDirection;
} else if (escape == "down") {
connector.escapeDirection = KoConnectionPoint::DownDirection;
}
debugFlake << "using escape direction" << escape;
}
d->connectors[index] = connector;
debugFlake << "loaded glue-point" << index << "at position" << connector.position;
if (d->connectors[index].position == QPointF(0.5, 0.5)) {
hasCenterGluePoint = true;
debugFlake << "center glue-point found at id " << index;
}
}
if (!hasCenterGluePoint) {
d->connectors[d->connectors.count()] = KoConnectionPoint(QPointF(0.5, 0.5),
KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter);
}
debugFlake << "shape has now" << d->connectors.count() << "glue-points";
}
void KoShape::loadOdfClipContour(const KoXmlElement &element, KoShapeLoadingContext &context, const QSizeF &scaleFactor)
{
Q_D(KoShape);
KoXmlElement child;
forEachElement(child, element) {
if (child.namespaceURI() != KoXmlNS::draw)
continue;
if (child.localName() != "contour-polygon")
continue;
debugFlake << "shape loads contour-polygon";
KoPathShape *ps = new KoPathShape();
ps->loadContourOdf(child, context, scaleFactor);
ps->setTransformation(transformation());
KoClipPath *clipPath = new KoClipPath({ps}, KoFlake::UserSpaceOnUse);
d->clipPath.reset(clipPath);
}
}
QTransform KoShape::parseOdfTransform(const QString &transform)
{
QTransform matrix;
// Split string for handling 1 transform statement at a time
QStringList subtransforms = transform.split(')', QString::SkipEmptyParts);
QStringList::ConstIterator it = subtransforms.constBegin();
QStringList::ConstIterator end = subtransforms.constEnd();
for (; it != end; ++it) {
QStringList subtransform = (*it).split('(', QString::SkipEmptyParts);
subtransform[0] = subtransform[0].trimmed().toLower();
subtransform[1] = subtransform[1].simplified();
QRegExp reg("[,( ]");
QStringList params = subtransform[1].split(reg, QString::SkipEmptyParts);
if (subtransform[0].startsWith(';') || subtransform[0].startsWith(','))
subtransform[0] = subtransform[0].right(subtransform[0].length() - 1);
QString cmd = subtransform[0].toLower();
if (cmd == "rotate") {
QTransform rotMatrix;
if (params.count() == 3) {
qreal x = KoUnit::parseValue(params[1]);
qreal y = KoUnit::parseValue(params[2]);
rotMatrix.translate(x, y);
// oo2 rotates by radians
rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI);
rotMatrix.translate(-x, -y);
} else {
// oo2 rotates by radians
rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI);
}
matrix = matrix * rotMatrix;
} else if (cmd == "translate") {
QTransform moveMatrix;
if (params.count() == 2) {
qreal x = KoUnit::parseValue(params[0]);
qreal y = KoUnit::parseValue(params[1]);
moveMatrix.translate(x, y);
} else // Spec : if only one param given, assume 2nd param to be 0
moveMatrix.translate(KoUnit::parseValue(params[0]) , 0);
matrix = matrix * moveMatrix;
} else if (cmd == "scale") {
QTransform scaleMatrix;
if (params.count() == 2)
scaleMatrix.scale(params[0].toDouble(), params[1].toDouble());
else // Spec : if only one param given, assume uniform scaling
scaleMatrix.scale(params[0].toDouble(), params[0].toDouble());
matrix = matrix * scaleMatrix;
} else if (cmd == "skewx") {
QPointF p = absolutePosition(KoFlake::TopLeft);
QTransform shearMatrix;
shearMatrix.translate(p.x(), p.y());
shearMatrix.shear(tan(-params[0].toDouble()), 0.0F);
shearMatrix.translate(-p.x(), -p.y());
matrix = matrix * shearMatrix;
} else if (cmd == "skewy") {
QPointF p = absolutePosition(KoFlake::TopLeft);
QTransform shearMatrix;
shearMatrix.translate(p.x(), p.y());
shearMatrix.shear(0.0F, tan(-params[0].toDouble()));
shearMatrix.translate(-p.x(), -p.y());
matrix = matrix * shearMatrix;
} else if (cmd == "matrix") {
QTransform m;
if (params.count() >= 6) {
m.setMatrix(params[0].toDouble(), params[1].toDouble(), 0,
params[2].toDouble(), params[3].toDouble(), 0,
KoUnit::parseValue(params[4]), KoUnit::parseValue(params[5]), 1);
}
matrix = matrix * m;
}
}
return matrix;
}
void KoShape::saveOdfAttributes(KoShapeSavingContext &context, int attributes) const
{
Q_D(const KoShape);
if (attributes & OdfStyle) {
KoGenStyle style;
// all items that should be written to 'draw:frame' and any other 'draw:' object that inherits this shape
if (context.isSet(KoShapeSavingContext::PresentationShape)) {
style = KoGenStyle(KoGenStyle::PresentationAutoStyle, "presentation");
context.xmlWriter().addAttribute("presentation:style-name", saveStyle(style, context));
} else {
style = KoGenStyle(KoGenStyle::GraphicAutoStyle, "graphic");
context.xmlWriter().addAttribute("draw:style-name", saveStyle(style, context));
}
}
if (attributes & OdfId) {
if (context.isSet(KoShapeSavingContext::DrawId)) {
KoElementReference ref = context.xmlid(this, "shape", KoElementReference::Counter);
ref.saveOdf(&context.xmlWriter(), KoElementReference::DrawId);
}
}
if (attributes & OdfName) {
if (! name().isEmpty())
context.xmlWriter().addAttribute("draw:name", name());
}
if (attributes & OdfLayer) {
KoShape *parent = d->parent;
while (parent) {
if (dynamic_cast(parent)) {
context.xmlWriter().addAttribute("draw:layer", parent->name());
break;
}
parent = parent->parent();
}
}
if (attributes & OdfZIndex && context.isSet(KoShapeSavingContext::ZIndex)) {
context.xmlWriter().addAttribute("draw:z-index", zIndex());
}
if (attributes & OdfSize) {
QSizeF s(size());
if (parent() && parent()->isClipped(this)) { // being clipped shrinks our visible size
// clipping in ODF is done using a combination of visual size and content cliprect.
// A picture of 10cm x 10cm displayed in a box of 2cm x 4cm will be scaled (out
// of proportion in this case). If we then add a fo:clip like;
// fo:clip="rect(2cm, 3cm, 4cm, 5cm)" (top, right, bottom, left)
// our original 10x10 is clipped to 2cm x 4cm and *then* fitted in that box.
// TODO do this properly by subtracting rects
s = parent()->size();
}
context.xmlWriter().addAttributePt("svg:width", s.width());
context.xmlWriter().addAttributePt("svg:height", s.height());
}
// The position is implicitly stored in the transformation matrix
// if the transformation is saved as well
if ((attributes & OdfPosition) && !(attributes & OdfTransformation)) {
const QPointF p(position() * context.shapeOffset(this));
context.xmlWriter().addAttributePt("svg:x", p.x());
context.xmlWriter().addAttributePt("svg:y", p.y());
}
if (attributes & OdfTransformation) {
QTransform matrix = absoluteTransformation(0) * context.shapeOffset(this);
if (! matrix.isIdentity()) {
if (qAbs(matrix.m11() - 1) < 1E-5 // 1
&& qAbs(matrix.m12()) < 1E-5 // 0
&& qAbs(matrix.m21()) < 1E-5 // 0
&& qAbs(matrix.m22() - 1) < 1E-5) { // 1
context.xmlWriter().addAttributePt("svg:x", matrix.dx());
context.xmlWriter().addAttributePt("svg:y", matrix.dy());
} else {
QString m = QString("matrix(%1 %2 %3 %4 %5pt %6pt)")
.arg(matrix.m11(), 0, 'f', 11)
.arg(matrix.m12(), 0, 'f', 11)
.arg(matrix.m21(), 0, 'f', 11)
.arg(matrix.m22(), 0, 'f', 11)
.arg(matrix.dx(), 0, 'f', 11)
.arg(matrix.dy(), 0, 'f', 11);
context.xmlWriter().addAttribute("draw:transform", m);
}
}
}
if (attributes & OdfViewbox) {
const QSizeF s(size());
QString viewBox = QString("0 0 %1 %2").arg(qRound(s.width())).arg(qRound(s.height()));
context.xmlWriter().addAttribute("svg:viewBox", viewBox);
}
if (attributes & OdfAdditionalAttributes) {
QMap::const_iterator it(d->additionalAttributes.constBegin());
for (; it != d->additionalAttributes.constEnd(); ++it) {
context.xmlWriter().addAttribute(it.key().toUtf8(), it.value());
}
}
}
void KoShape::saveOdfCommonChildElements(KoShapeSavingContext &context) const
{
Q_D(const KoShape);
// save glue points see ODF 9.2.19 Glue Points
if(d->connectors.count()) {
KoConnectionPoints::const_iterator cp = d->connectors.constBegin();
KoConnectionPoints::const_iterator lastCp = d->connectors.constEnd();
for(; cp != lastCp; ++cp) {
// do not save default glue points
if(cp.key() < 4)
continue;
context.xmlWriter().startElement("draw:glue-point");
context.xmlWriter().addAttribute("draw:id", QString("%1").arg(cp.key()));
if (cp.value().alignment == KoConnectionPoint::AlignNone) {
// convert to percent from center
const qreal x = cp.value().position.x() * 100.0 - 50.0;
const qreal y = cp.value().position.y() * 100.0 - 50.0;
context.xmlWriter().addAttribute("svg:x", QString("%1%").arg(x));
context.xmlWriter().addAttribute("svg:y", QString("%1%").arg(y));
} else {
context.xmlWriter().addAttributePt("svg:x", cp.value().position.x());
context.xmlWriter().addAttributePt("svg:y", cp.value().position.y());
}
QString escapeDirection;
switch(cp.value().escapeDirection) {
case KoConnectionPoint::HorizontalDirections:
escapeDirection = "horizontal";
break;
case KoConnectionPoint::VerticalDirections:
escapeDirection = "vertical";
break;
case KoConnectionPoint::LeftDirection:
escapeDirection = "left";
break;
case KoConnectionPoint::RightDirection:
escapeDirection = "right";
break;
case KoConnectionPoint::UpDirection:
escapeDirection = "up";
break;
case KoConnectionPoint::DownDirection:
escapeDirection = "down";
break;
default:
// fall through
break;
}
if(!escapeDirection.isEmpty()) {
context.xmlWriter().addAttribute("draw:escape-direction", escapeDirection);
}
QString alignment;
switch(cp.value().alignment) {
case KoConnectionPoint::AlignTopLeft:
alignment = "top-left";
break;
case KoConnectionPoint::AlignTop:
alignment = "top";
break;
case KoConnectionPoint::AlignTopRight:
alignment = "top-right";
break;
case KoConnectionPoint::AlignLeft:
alignment = "left";
break;
case KoConnectionPoint::AlignCenter:
alignment = "center";
break;
case KoConnectionPoint::AlignRight:
alignment = "right";
break;
case KoConnectionPoint::AlignBottomLeft:
alignment = "bottom-left";
break;
case KoConnectionPoint::AlignBottom:
alignment = "bottom";
break;
case KoConnectionPoint::AlignBottomRight:
alignment = "bottom-right";
break;
default:
// fall through
break;
}
if(!alignment.isEmpty()) {
context.xmlWriter().addAttribute("draw:align", alignment);
}
context.xmlWriter().endElement();
}
}
}
void KoShape::saveOdfClipContour(KoShapeSavingContext &context, const QSizeF &originalSize) const
{
Q_D(const KoShape);
debugFlake << "shape saves contour-polygon";
if (d->clipPath && !d->clipPath->clipPathShapes().isEmpty()) {
// This will loose data as odf can only save one set of contour wheras
// svg loading and at least karbon editing can produce more than one
// TODO, FIXME see if we can save more than one clipshape to odf
d->clipPath->clipPathShapes().first()->saveContourOdf(context, originalSize);
}
}
// end loading & saving methods
// static
void KoShape::applyConversion(QPainter &painter, const KoViewConverter &converter)
{
qreal zoomX, zoomY;
converter.zoom(&zoomX, &zoomY);
painter.scale(zoomX, zoomY);
}
KisHandlePainterHelper KoShape::createHandlePainterHelper(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius)
{
const QTransform originalPainterTransform = painter->transform();
painter->setTransform(shape->absoluteTransformation(&converter) * painter->transform());
KoShape::applyConversion(*painter, converter);
// move c-tor
return KisHandlePainterHelper(painter, originalPainterTransform, handleRadius);
}
QPointF KoShape::shapeToDocument(const QPointF &point) const
{
return absoluteTransformation(0).map(point);
}
QRectF KoShape::shapeToDocument(const QRectF &rect) const
{
return absoluteTransformation(0).mapRect(rect);
}
QPointF KoShape::documentToShape(const QPointF &point) const
{
return absoluteTransformation(0).inverted().map(point);
}
QRectF KoShape::documentToShape(const QRectF &rect) const
{
return absoluteTransformation(0).inverted().mapRect(rect);
}
bool KoShape::addDependee(KoShape *shape)
{
Q_D(KoShape);
if (! shape)
return false;
// refuse to establish a circular dependency
if (shape->hasDependee(this))
return false;
if (! d->dependees.contains(shape))
d->dependees.append(shape);
return true;
}
void KoShape::removeDependee(KoShape *shape)
{
Q_D(KoShape);
int index = d->dependees.indexOf(shape);
if (index >= 0)
d->dependees.removeAt(index);
}
bool KoShape::hasDependee(KoShape *shape) const
{
Q_D(const KoShape);
return d->dependees.contains(shape);
}
QList KoShape::dependees() const
{
Q_D(const KoShape);
return d->dependees;
}
void KoShape::shapeChanged(ChangeType type, KoShape *shape)
{
Q_UNUSED(type);
Q_UNUSED(shape);
}
KoSnapData KoShape::snapData() const
{
return KoSnapData();
}
void KoShape::setAdditionalAttribute(const QString &name, const QString &value)
{
Q_D(KoShape);
d->additionalAttributes.insert(name, value);
}
void KoShape::removeAdditionalAttribute(const QString &name)
{
Q_D(KoShape);
d->additionalAttributes.remove(name);
}
bool KoShape::hasAdditionalAttribute(const QString &name) const
{
Q_D(const KoShape);
return d->additionalAttributes.contains(name);
}
QString KoShape::additionalAttribute(const QString &name) const
{
Q_D(const KoShape);
return d->additionalAttributes.value(name);
}
void KoShape::setAdditionalStyleAttribute(const char *name, const QString &value)
{
Q_D(KoShape);
d->additionalStyleAttributes.insert(name, value);
}
void KoShape::removeAdditionalStyleAttribute(const char *name)
{
Q_D(KoShape);
d->additionalStyleAttributes.remove(name);
}
KoFilterEffectStack *KoShape::filterEffectStack() const
{
Q_D(const KoShape);
return d->filterEffectStack;
}
void KoShape::setFilterEffectStack(KoFilterEffectStack *filterEffectStack)
{
Q_D(KoShape);
if (d->filterEffectStack)
d->filterEffectStack->deref();
d->filterEffectStack = filterEffectStack;
if (d->filterEffectStack) {
d->filterEffectStack->ref();
}
notifyChanged();
}
QSet KoShape::toolDelegates() const
{
Q_D(const KoShape);
return d->toolDelegates;
}
void KoShape::setToolDelegates(const QSet &delegates)
{
Q_D(KoShape);
d->toolDelegates = delegates;
}
QString KoShape::hyperLink () const
{
Q_D(const KoShape);
return d->hyperLink;
}
void KoShape::setHyperLink(const QString &hyperLink)
{
Q_D(KoShape);
d->hyperLink = hyperLink;
}
KoShapePrivate *KoShape::priv()
{
Q_D(KoShape);
return d;
}
KoShape::ShapeChangeListener::~ShapeChangeListener()
{
Q_FOREACH(KoShape *shape, m_registeredShapes) {
shape->removeShapeChangeListener(this);
}
}
void KoShape::ShapeChangeListener::registerShape(KoShape *shape)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!m_registeredShapes.contains(shape));
m_registeredShapes.append(shape);
}
void KoShape::ShapeChangeListener::unregisterShape(KoShape *shape)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape));
m_registeredShapes.removeAll(shape);
}
void KoShape::ShapeChangeListener::notifyShapeChangedImpl(KoShape::ChangeType type, KoShape *shape)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape));
notifyShapeChanged(type, shape);
if (type == KoShape::Deleted) {
unregisterShape(shape);
}
}
void KoShape::addShapeChangeListener(KoShape::ShapeChangeListener *listener)
{
Q_D(KoShape);
KIS_SAFE_ASSERT_RECOVER_RETURN(!d->listeners.contains(listener));
listener->registerShape(this);
d->listeners.append(listener);
}
void KoShape::removeShapeChangeListener(KoShape::ShapeChangeListener *listener)
{
Q_D(KoShape);
KIS_SAFE_ASSERT_RECOVER_RETURN(d->listeners.contains(listener));
d->listeners.removeAll(listener);
listener->unregisterShape(this);
}
QList KoShape::linearizeSubtree(const QList &shapes)
{
QList result;
Q_FOREACH (KoShape *shape, shapes) {
result << shape;
KoShapeContainer *container = dynamic_cast(shape);
if (container) {
result << linearizeSubtree(container->shapes());
}
}
return result;
}
diff --git a/libs/flake/KoShape.h b/libs/flake/KoShape.h
index 4e2699fbcb..bee698062c 100644
--- a/libs/flake/KoShape.h
+++ b/libs/flake/KoShape.h
@@ -1,1222 +1,1228 @@
/* This file is part of the KDE project
Copyright (C) 2006-2008 Thorsten Zachmann
Copyright (C) 2006, 2008 C. Boemann
Copyright (C) 2006-2010 Thomas Zander
Copyright (C) 2007-2009,2011 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 KOSHAPE_H
#define KOSHAPE_H
#include "KoFlake.h"
#include "KoFlakeTypes.h"
#include "KoConnectionPoint.h"
#include
#include
#include
#include
#include "kritaflake_export.h"
class QPainter;
class QRectF;
class QPainterPath;
class QTransform;
class KoShapeContainer;
class KoShapeStrokeModel;
class KoShapeUserData;
class KoViewConverter;
class KoShapeApplicationData;
class KoShapeSavingContext;
class KoShapeLoadingContext;
class KoGenStyle;
class KoShapeShadow;
class KoShapePrivate;
class KoFilterEffectStack;
class KoSnapData;
class KoClipPath;
class KoClipMask;
class KoShapePaintingContext;
class KoShapeAnchor;
class KoBorder;
struct KoInsets;
class KoShapeBackground;
class KisHandlePainterHelper;
/**
*
* Base class for all flake shapes. Shapes extend this class
* to allow themselves to be manipulated. This class just represents
* a graphical shape in the document and can be manipulated by some default
* tools in this library.
*
* Due to the limited responsibility of this class, the extending object
* can have any data backend and is responsible for painting itself.
*
* We strongly suggest that any extending class will use a Model View
* Controller (MVC) design where the View part is all in this class, as well
* as the one that inherits from this one. This allows the data that rests
* in the model to be reused in different parts of the document. For example
* by having two flake objects that show that same data. Or each showing a section of it.
*
* The KoShape data is completely in postscript-points (pt) (see KoUnit
* for conversion methods to and from points).
* This image will explain the real-world use of the shape and its options.
*
* The Rotation center can be returned with absolutePosition()
*
* Flake objects can be created in three ways:
*
* - a simple new KoDerivedFlake(),
*
- through an associated tool,
*
- through a factory
*
*
* Shape interaction notifications
* We had several notification methods that allow your shape to be notified of changes in other
* shapes positions or rotation etc.
* - The most general is KoShape::shapeChanged().
* a virtual method that you can use to check various changed to your shape made by tools or otherwise.
* - for shape hierarchies the parent may receive a notification when a child was modified.
* This is done though KoShapeContainerModel::childChanged()
* - any shape that is at a similar position as another shape there is collision detection.
* You can register your shape to be sensitive to any changes like moving or whatever to
* other shapes that intersect yours.
* Such changes will then be notified to your shape using the method from (1) You should call
* KoShape::setCollisionDetection(bool) to enable this.
*
*/
class KRITAFLAKE_EXPORT KoShape
{
public:
/// Used by shapeChanged() to select which change was made
enum ChangeType {
PositionChanged, ///< used after a setPosition()
RotationChanged, ///< used after a setRotation()
ScaleChanged, ///< used after a scale()
ShearChanged, ///< used after a shear()
SizeChanged, ///< used after a setSize()
GenericMatrixChange, ///< used after the matrix was changed without knowing which property explicitly changed
KeepAspectRatioChange, ///< used after setKeepAspectRatio()
ParentChanged, ///< used after a setParent()
CollisionDetected, ///< used when another shape moved in our boundingrect
Deleted, ///< the shape was deleted
StrokeChanged, ///< the shapes stroke has changed
BackgroundChanged, ///< the shapes background has changed
ShadowChanged, ///< the shapes shadow has changed
BorderChanged, ///< the shapes border has changed
ParameterChanged, ///< the shapes parameter has changed (KoParameterShape only)
ContentChanged, ///< the content of the shape changed e.g. a new image inside a pixmap/text change inside a textshape
TextRunAroundChanged, ///< used after a setTextRunAroundSide()
ChildChanged, ///< a child of a container was changed/removed. This is propagated to all parents
ConnectionPointChanged, ///< a connection point has changed
ClipPathChanged, ///< the shapes clip path has changed
TransparencyChanged ///< the shapetransparency value has changed
};
/// The behavior text should do when intersecting this shape.
enum TextRunAroundSide {
BiggestRunAroundSide, ///< Run other text around the side that has the most space
LeftRunAroundSide, ///< Run other text around the left side of the frame
RightRunAroundSide, ///< Run other text around the right side of the frame
EnoughRunAroundSide, ///< Run other text dynamically around both sides of the shape, provided there is sufficient space left
BothRunAroundSide, ///< Run other text around both sides of the shape
NoRunAround, ///< The text will be completely avoiding the frame by keeping the horizontal space that this frame occupies blank.
RunThrough ///< The text will completely ignore the frame and layout as if it was not there
};
/// The behavior text should do when intersecting this shape.
enum TextRunAroundContour {
ContourBox, /// Run other text around a bounding rect of the outline
ContourFull, ///< Run other text around also on the inside
ContourOutside ///< Run other text around only on the outside
};
/**
* TODO
*/
enum RunThroughLevel {
Background,
Foreground
};
/**
* @brief Constructor
*/
KoShape();
/**
* @brief Destructor
*/
virtual ~KoShape();
/**
* @brief creates a deep copy of thie shape or shapes subtree
* @return a cloned shape
*/
virtual KoShape* cloneShape() const;
/**
* @brief Paint the shape
* The class extending this one is responsible for painting itself. Since we do not
* assume the shape is square the paint must also clear its background if it will draw
* something transparent on top.
* This can be done with a method like:
*
painter.fillRect(converter.normalToView(QRectF(QPointF(0.0,0.0), size())), background());
* Or equavalent for non-square objects.
* Do note that a shape's top-left is always at coordinate 0,0. Even if the shape itself is rotated
* or translated.
* @param painter used for painting the shape
* @param converter to convert between internal and view coordinates.
* @see applyConversion()
* @param paintcontext the painting context.
*/
virtual void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) = 0;
/**
* @brief Paint the shape's border
* This is a helper function that could be called from the paint() method of all shapes.
* @param painter used for painting the shape
* @param converter to convert between internal and view coordinates.
* @see applyConversion()
*/
virtual void paintBorder(QPainter &painter, const KoViewConverter &converter);
/**
* Load a shape from odf
*
* @param context the KoShapeLoadingContext used for loading
* @param element element which represents the shape in odf
*
* @return false if loading failed
*/
virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) = 0;
/**
* @brief store the shape data as ODF XML.
* This is the method that will be called when saving a shape as a described in
* OpenDocument 9.2 Drawing Shapes.
* @see saveOdfAttributes
*/
virtual void saveOdf(KoShapeSavingContext &context) const = 0;
/**
* This method can be used while saving the shape as ODF to add the data
* stored on this shape to the current element.
*
* @param context the context for the current save.
* @param attributes a number of OdfAttribute items to state which attributes to save.
* @see saveOdf
*/
void saveOdfAttributes(KoShapeSavingContext &context, int attributes) const;
/**
* This method can be used while saving the shape as Odf to add common child elements
*
* The office:event-listeners and draw:glue-point are saved.
* @param context the context for the current save.
*/
void saveOdfCommonChildElements(KoShapeSavingContext &context) const;
/**
* This method can be used to save contour data from the clipPath()
*
* The draw:contour-polygon or draw:contour-path elements are saved.
* @param context the context for the current save.
* @param originalSize the original size of the unscaled image.
*/
void saveOdfClipContour(KoShapeSavingContext &context, const QSizeF &originalSize) const;
/**
* @brief Scale the shape using the zero-point which is the top-left corner.
* @see position()
*
* @param sx scale in x direction
* @param sy scale in y direction
*/
void scale(qreal sx, qreal sy);
/**
* @brief Rotate the shape (relative)
*
* The shape will be rotated from the current rotation using the center of the shape using the size()
*
* @param angle change the angle of rotation increasing it with 'angle' degrees
*/
void rotate(qreal angle);
/**
* Return the current rotation in degrees.
* It returns NaN if the shape has a shearing or scaling transformation applied.
*/
qreal rotation() const;
/**
* @brief Shear the shape
* The shape will be sheared using the zero-point which is the top-left corner.
* @see position()
*
* @param sx shear in x direction
* @param sy shear in y direction
*/
void shear(qreal sx, qreal sy);
/**
* @brief Resize the shape
*
* @param size the new size of the shape. This is different from scaling as
* scaling is a so called secondary operation which is comparable to zooming in
* instead of changing the size of the basic shape.
* Easiest example of this difference is that using this method will not distort the
* size of pattern-fills and strokes.
*/
virtual void setSize(const QSizeF &size);
/**
* @brief Get the size of the shape in pt.
*
* The size is in shape coordinates.
*
* @return the size of the shape as set by setSize()
*/
virtual QSizeF size() const;
/**
* @brief Set the position of the shape in pt
*
* @param position the new position of the shape
*/
virtual void setPosition(const QPointF &position);
/**
* @brief Get the position of the shape in pt
*
* @return the position of the shape
*/
QPointF position() const;
/**
* @brief Check if the shape is hit on position
* @param position the position where the user clicked.
* @return true when it hits.
*/
virtual bool hitTest(const QPointF &position) const;
/**
* @brief Get the bounding box of the shape
*
* This includes the line width and the shadow of the shape
*
* @return the bounding box of the shape
*/
virtual QRectF boundingRect() const;
+ /**
+ * Get the united bounding box of a group of shapes. This is a utility
+ * function used in many places in Krita.
+ */
+ static QRectF boundingRect(const QList &shapes);
+
/**
* @brief Add a connector point to the shape
*
* A connector is a place on the shape that allows a graphical connection to be made
* using a line, for example.
*
* @param point the connection point to add
* @return the id of the new connection point
*/
int addConnectionPoint(const KoConnectionPoint &point);
/**
* Sets data of connection point with specified id.
*
* The position of the connector is restricted to the bounding rectangle of the shape.
* When setting a default connection point, the new position is ignored, as these
* are fixed at their default position.
* The function will insert a new connection point if the specified id was not used
* before.
*
* @param connectionPointId the id of the connection point to set
* @param point the connection point data
* @return false if specified connection point id is invalid, else true
*/
bool setConnectionPoint(int connectionPointId, const KoConnectionPoint &point);
/// Checks if a connection point with the specified id exists
bool hasConnectionPoint(int connectionPointId) const;
/// Returns connection point with specified connection point id
KoConnectionPoint connectionPoint(int connectionPointId) const;
/**
* Return a list of the connection points that have been added to this shape.
* All the points are relative to the shape position, see absolutePosition().
* @return a list of the connectors that have been added to this shape.
*/
KoConnectionPoints connectionPoints() const;
/// Removes connection point with specified id
void removeConnectionPoint(int connectionPointId);
/// Removes all connection points
void clearConnectionPoints();
/**
* Return the side text should flow around this shape. This implements the ODF style:wrap
* attribute that specifies how text is displayed around a frame or graphic object.
*/
TextRunAroundSide textRunAroundSide() const;
/**
* Set the side text should flow around this shape.
* @param side the requested side
* @param runThrought run through the foreground or background or...
*/
void setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrough = Background);
/**
* The space between this shape's left edge and text that runs around this shape.
* @return the space around this shape to keep free from text
*/
qreal textRunAroundDistanceLeft() const;
/**
* Set the space between this shape's left edge and the text that run around this shape.
* @param distance the space around this shape to keep free from text
*/
void setTextRunAroundDistanceLeft(qreal distance);
/**
* The space between this shape's top edge and text that runs around this shape.
* @return the space around this shape to keep free from text
*/
qreal textRunAroundDistanceTop() const;
/**
* Set the space between this shape's top edge and the text that run around this shape.
* @param distance the space around this shape to keep free from text
*/
void setTextRunAroundDistanceTop(qreal distance);
/**
* The space between this shape's right edge and text that runs around this shape.
* @return the space around this shape to keep free from text
*/
qreal textRunAroundDistanceRight() const;
/**
* Set the space between this shape's right edge and the text that run around this shape.
* @param distance the space around this shape to keep free from text
*/
void setTextRunAroundDistanceRight(qreal distance);
/**
* The space between this shape's bottom edge and text that runs around this shape.
* @return the space around this shape to keep free from text
*/
qreal textRunAroundDistanceBottom() const;
/**
* Set the space between this shape's bottom edge and the text that run around this shape.
* @param distance the space around this shape to keep free from text
*/
void setTextRunAroundDistanceBottom(qreal distance);
/**
* Return the threshold above which text should flow around this shape.
* The text will not flow around the shape on a side unless the space available on that side
* is above this threshold. Only used when the text run around side is EnoughRunAroundSide.
* @return threshold the threshold
*/
qreal textRunAroundThreshold() const;
/**
* Set the threshold above which text should flow around this shape.
* The text will not flow around the shape on a side unless the space available on that side
* is above this threshold. Only used when the text run around side is EnoughRunAroundSide.
* @param threshold the new threshold
*/
void setTextRunAroundThreshold(qreal threshold);
/**
* Return the how tight text run around is done around this shape.
* @return the contour
*/
TextRunAroundContour textRunAroundContour() const;
/**
* Set how tight text run around is done around this shape.
* @param contour the new contour
*/
void setTextRunAroundContour(TextRunAroundContour contour);
/**
* Set the KoShapeAnchor
*/
void setAnchor(KoShapeAnchor *anchor);
/**
* Return the KoShapeAnchor, or 0
*/
KoShapeAnchor *anchor() const;
/**
* Set the minimum height of the shape.
* Currently it's not respected but only for informational purpose
* @param minimumShapeHeight the minimum height of the frame.
*/
void setMinimumHeight(qreal height);
/**
* Return the minimum height of the shape.
* @return the minimum height of the shape. Default is 0.0.
*/
qreal minimumHeight() const;
/**
* Set the background of the shape.
* A shape background can be a plain color, a gradient, a pattern, be fully transparent
* or have a complex fill.
* Setting such a background will allow the shape to be filled and will be able to tell
* if it is transparent or not.
* @param background the new shape background.
*/
void setBackground(QSharedPointer background);
/**
* return the brush used to paint te background of this shape with.
* A QBrush can have a plain color, be fully transparent or have a complex fill.
* setting such a brush will allow the shape to fill itself using that brush and
* will be able to tell if its transparent or not.
* @return the background-brush
*/
QSharedPointer background() const;
/**
* Returns true if there is some transparency, false if the shape is fully opaque.
* The default implementation will just return if the background has some transparency,
* you should override it and always return true if your shape is not square.
* @return if the shape is (partly) transparent.
*/
virtual bool hasTransparency() const;
/**
* Sets shape level transparency.
* @param transparency the new shape level transparency
*/
void setTransparency(qreal transparency);
/**
* Returns the shape level transparency.
* @param recursive when true takes the parents transparency into account
*/
qreal transparency(bool recursive=false) const;
/**
* Retrieve the z-coordinate of this shape.
* The zIndex property is used to determine which shape lies on top of other objects.
* An shape with a higher z-order is on top, and can obscure another shape.
* @return the z-index of this shape.
* @see setZIndex()
*/
int zIndex() const;
/**
* Set the z-coordinate of this shape.
* The zIndex property is used to determine which shape lies on top of other objects.
* An shape with a higher z-order is on top, and can obscure, another shape.
* Just like two objects having the same x or y coordinate will make them 'touch',
* so will two objects with the same z-index touch on the z plane. In layering the
* shape this, however, can cause a little confusion as one always has to be on top.
* The layering if two overlapping objects have the same index is implementation dependent
* and probably depends on the order in which they are added to the shape manager.
* @param zIndex the new z-index;
*/
void setZIndex(int zIndex);
/**
* Retrieve the run through property of this shape.
* The run through property is used to determine if the shape is behind, inside or before text.
* @return the run through of this shape.
*/
int runThrough();
/**
* Set the run through property of this shape.
* The run through property is used to determine if the shape is behind, inside or before text.
* @param runThrough the new run through;
*/
virtual void setRunThrough(short int runThrough);
/**
* Changes the Shape to be visible or invisible.
* Being visible means being painted, as well as being used for
* things like guidelines or searches.
* @param on when true; set the shape to be visible.
* @see setGeometryProtected(), setContentProtected(), setSelectable()
*/
void setVisible(bool on);
/**
* Returns current visibility state of this shape.
* Being visible means being painted, as well as being used for
* things like guidelines or searches.
* @param recursive when true, checks visibility recursively
* @return current visibility state of this shape.
* @see isGeometryProtected(), isContentProtected(), isSelectable()
*/
bool isVisible(bool recursive = false) const;
/**
* Changes the shape to be printable or not. The default is true.
*
* If a Shape's print flag is true, the shape will be printed. If
* false, the shape will not be printed. If a shape is not visible (@see isVisible),
* it isPrinted will return false, too.
*/
void setPrintable(bool on);
/**
* Returns the current printable state of this shape.
*
* A shape can be visible but not printable, not printable and not visible
* or visible and printable, but not invisible and still printable.
*
* @return current printable state of this shape.
*/
bool isPrintable() const;
/**
* Makes it possible for the user to select this shape.
* This parameter defaults to true.
* @param selectable when true; set the shape to be selectable by the user.
* @see setGeometryProtected(), setContentProtected(), setVisible()
*/
void setSelectable(bool selectable);
/**
* Returns if this shape can be selected by the user.
* @return true only when the object is selectable.
* @see isGeometryProtected(), isContentProtected(), isVisible()
*/
bool isSelectable() const;
/**
* Tells the shape to have its position/rotation and size protected from user-changes.
* The geometry being protected means the user can not change shape or position of the
* shape. This includes any matrix operation such as rotation.
* @param on when true; set the shape to have its geometry protected.
* @see setContentProtected(), setSelectable(), setVisible()
*/
void setGeometryProtected(bool on);
/**
* Returns current geometry protection state of this shape.
* The geometry being protected means the user can not change shape or position of the
* shape. This includes any matrix operation such as rotation.
* @return current geometry protection state of this shape.
* @see isContentProtected(), isSelectable(), isVisible()
*/
bool isGeometryProtected() const;
/**
* Marks the shape to have its content protected against editing.
* Content protection is a hint for tools to disallow the user editing the content.
* @param protect when true set the shapes content to be protected from user modification.
* @see setGeometryProtected(), setSelectable(), setVisible()
*/
void setContentProtected(bool protect);
/**
* Returns current content protection state of this shape.
* Content protection is a hint for tools to disallow the user editing the content.
* @return current content protection state of this shape.
* @see isGeometryProtected(), isSelectable(), isVisible()
*/
bool isContentProtected() const;
/**
* Returns the parent, or 0 if there is no parent.
* @return the parent, or 0 if there is no parent.
*/
KoShapeContainer *parent() const;
/**
* Set the parent of this shape.
* @param parent the new parent of this shape. Can be 0 if the shape has no parent anymore.
*/
void setParent(KoShapeContainer *parent);
/**
* Request a repaint to be queued.
* The repaint will be of the entire Shape, including its selection handles should this
* shape be selected.
*
This method will return immediately and only request a repaint. Successive calls
* will be merged into an appropriate repaint action.
*/
virtual void update() const;
/**
* Request a repaint to be queued.
* The repaint will be restricted to the parameters rectangle, which is expected to be
* in points (the internal coordinates system of KoShape) and it is expected to be
* normalized.
*
This method will return immediately and only request a repaint. Successive calls
* will be merged into an appropriate repaint action.
* @param rect the rectangle (in pt) to queue for repaint.
*/
virtual void update(const QRectF &rect) const;
/// Used by compareShapeZIndex() to order shapes
enum ChildZOrderPolicy {
ChildZDefault,
ChildZParentChild = ChildZDefault, ///< normal parent/child ordering
ChildZPassThrough ///< children are considered equal to this shape
};
/**
* Returns if during compareShapeZIndex() how this shape portrays the values
* of its children. The default behaviour is to let this shape's z values take
* the place of its childrens values, so you get a parent/child relationship.
* The children are naturally still ordered relatively to their z values
*
* But for special cases (like Calligra's TextShape) it can be overloaded to return
* ChildZPassThrough which means the children keep their own z values
* @returns the z order policy of this shape
*/
virtual ChildZOrderPolicy childZOrderPolicy();
/**
* This is a method used to sort a list using the STL sorting methods.
* @param s1 the first shape
* @param s2 the second shape
*/
static bool compareShapeZIndex(KoShape *s1, KoShape *s2);
/**
* returns the outline of the shape in the form of a path.
* The outline returned will always be relative to the position() of the shape, so
* moving the shape will not alter the result. The outline is used to draw the stroke
* on, for example.
* @returns the outline of the shape in the form of a path.
*/
virtual QPainterPath outline() const;
/**
* returns the outline of the shape in the form of a rect.
* The outlineRect returned will always be relative to the position() of the shape, so
* moving the shape will not alter the result. The outline is used to calculate
* the boundingRect.
* @returns the outline of the shape in the form of a rect.
*/
virtual QRectF outlineRect() const;
/**
* returns the outline of the shape in the form of a path for the use of painting a shadow.
*
* Normally this would be the same as outline() if there is a fill (background) set on the
* shape and empty if not. However, a shape could reimplement this to return an outline
* even if no fill is defined. A typical example of this would be the picture shape
* which has a picture but almost never a background.
*
* @returns the outline of the shape in the form of a path.
*/
virtual QPainterPath shadowOutline() const;
/**
* Returns the currently set stroke, or 0 if there is no stroke.
* @return the currently set stroke, or 0 if there is no stroke.
*/
KoShapeStrokeModelSP stroke() const;
/**
* Set a new stroke, removing the old one.
* @param stroke the new stroke, or 0 if there should be no stroke.
*/
void setStroke(KoShapeStrokeModelSP stroke);
/**
* Return the insets of the stroke.
* Convenience method for KoShapeStrokeModel::strokeInsets()
*/
KoInsets strokeInsets() const;
/// Sets the new shadow, removing the old one
void setShadow(KoShapeShadow *shadow);
/// Returns the currently set shadow or 0 if there is no shadow set
KoShapeShadow *shadow() const;
/// Sets the new border, removing the old one.
void setBorder(KoBorder *border);
/// Returns the currently set border or 0 if there is no border set
KoBorder *border() const;
/// Sets a new clip path, removing the old one
void setClipPath(KoClipPath *clipPath);
/// Returns the currently set clip path or 0 if there is no clip path set
KoClipPath * clipPath() const;
/// Sets a new clip mask, removing the old one. The mask is owned by the shape.
void setClipMask(KoClipMask *clipMask);
/// Returns the currently set clip mask or 0 if there is no clip mask set
KoClipMask* clipMask() const;
/**
* Setting the shape to keep its aspect-ratio has the effect that user-scaling will
* keep the width/hight ratio intact so as not to distort shapes that rely on that
* ratio.
* @param keepAspect the new value
*/
void setKeepAspectRatio(bool keepAspect);
/**
* Setting the shape to keep its aspect-ratio has the effect that user-scaling will
* keep the width/hight ratio intact so as not to distort shapes that rely on that
* ratio.
* @return whether to keep aspect ratio of this shape
*/
bool keepAspectRatio() const;
/**
* Return the position of this shape regardless of rotation/skew/scaling and regardless of
* this shape having a parent (being in a group) or not.
* @param anchor The place on the (unaltered) shape that you want the position of.
* @return the point that is the absolute, centered position of this shape.
*/
QPointF absolutePosition(KoFlake::AnchorPosition anchor = KoFlake::Center) const;
/**
* Move this shape to an absolute position where the end location will be the same
* regardless of the shape's rotation/skew/scaling and regardless of this shape having
* a parent (being in a group) or not.
* The newPosition is going to be the center of the shape.
* This has the convenient effect that:
shape->setAbsolutePosition(QPointF(0,0));
shape->rotate(45);
Will result in the same visual position of the shape as the opposite:
shape->rotate(45);
shape->setAbsolutePosition(QPointF(0,0));
* @param newPosition the new absolute center of the shape.
* @param anchor The place on the (unaltered) shape that you set the position of.
*/
void setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor = KoFlake::Center);
/**
* Set a data object on the shape to be used by an application.
* This is specifically useful when a shape is created in a plugin and that data from that
* shape should be accessible outside the plugin.
* @param userData the new user data, or 0 to delete the current one.
*/
void setUserData(KoShapeUserData *userData);
/**
* Return the current userData.
*/
KoShapeUserData *userData() const;
/**
* Return the Id of this shape, identifying the type of shape by the id of the factory.
* @see KoShapeFactoryBase::shapeId()
* @return the id of the shape-type
*/
QString shapeId() const;
/**
* Set the Id of this shape. A shapeFactory is expected to set the Id at creation
* so applications can find out what kind of shape this is.
* @see KoShapeFactoryBase::shapeId()
* @param id the ID from the factory that created this shape
*/
void setShapeId(const QString &id);
/**
* Create a matrix that describes all the transformations done on this shape.
*
* The absolute transformation is the combined transformation of this shape
* and all its parents and grandparents.
*
* @param converter if not null, this method uses the converter to mark the right
* offsets in the current view.
*/
QTransform absoluteTransformation(const KoViewConverter *converter) const;
/**
* Applies a transformation to this shape.
*
* The transformation given is relative to the global coordinate system, i.e. the document.
* This is a convenience function to apply a global transformation to this shape.
* @see applyTransformation
*
* @param matrix the transformation matrix to apply
*/
void applyAbsoluteTransformation(const QTransform &matrix);
/**
* Sets a new transformation matrix describing the local transformations on this shape.
* @param matrix the new transformation matrix
*/
void setTransformation(const QTransform &matrix);
/// Returns the shapes local transformation matrix
QTransform transformation() const;
/**
* Applies a transformation to this shape.
*
* The transformation given is relative to the shape coordinate system.
*
* @param matrix the transformation matrix to apply
*/
void applyTransformation(const QTransform &matrix);
/**
* Copy all the settings from the parameter shape and apply them to this shape.
* Settings like the position and rotation to visible and locked. The parent
* is a notable exclusion.
* @param shape the shape to use as original
*/
void copySettings(const KoShape *shape);
/**
* Convenience method that allows people implementing paint() to use the shape
* internal coordinate system directly to paint itself instead of considering the
* views zoom.
* @param painter the painter to alter the zoom level of.
* @param converter the converter for the current views zoom.
*/
static void applyConversion(QPainter &painter, const KoViewConverter &converter);
/**
* A convenience method that creates a handles helper with applying transformations at
* the same time. Please note that you shouldn't save/restore additionally. All the work
* on restoring original painter's transformations is done by the helper.
*/
static KisHandlePainterHelper createHandlePainterHelper(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius = 0.0);
/**
* @brief Transforms point from shape coordinates to document coordinates
* @param point in shape coordinates
* @return point in document coordinates
*/
QPointF shapeToDocument(const QPointF &point) const;
/**
* @brief Transforms rect from shape coordinates to document coordinates
* @param rect in shape coordinates
* @return rect in document coordinates
*/
QRectF shapeToDocument(const QRectF &rect) const;
/**
* @brief Transforms point from document coordinates to shape coordinates
* @param point in document coordinates
* @return point in shape coordinates
*/
QPointF documentToShape(const QPointF &point) const;
/**
* @brief Transform rect from document coordinates to shape coordinates
* @param rect in document coordinates
* @return rect in shape coordinates
*/
QRectF documentToShape(const QRectF &rect) const;
/**
* Returns the name of the shape.
* @return the shapes name
*/
QString name() const;
/**
* Sets the name of the shape.
* @param name the new shape name
*/
void setName(const QString &name);
/**
* Update the position of the shape in the tree of the KoShapeManager.
*/
void notifyChanged();
/**
* A shape can be in a state that it is doing processing data like loading or text layout.
* In this case it can be shown on screen probably partially but it should really not be printed
* until it is fully done processing.
* Warning! This method can be blocking for a long time
* @param asynchronous If set to true the processing will can take place in a different thread and the
* function will not block until the shape is finised.
* In case of printing Flake will call this method from a non-main thread and only
* start printing it when the in case of printing method returned.
* If set to false the processing needs to be done synchronously and will
* block until the result is finished.
*/
virtual void waitUntilReady(const KoViewConverter &converter, bool asynchronous = true) const;
/// checks recursively if the shape or one of its parents is not visible or locked
bool isEditable() const;
/**
* Adds a shape which depends on this shape.
* Making a shape dependent on this one means it will get shapeChanged() called
* on each update of this shape.
*
* If this shape already depends on the given shape, establishing the
* dependency is refused to prevent circular dependencies.
*
* @param shape the shape which depends on this shape
* @return true if dependency could be established, otherwise false
* @see removeDependee(), hasDependee()
*/
bool addDependee(KoShape *shape);
/**
* Removes as shape depending on this shape.
* @see addDependee(), hasDependee()
*/
void removeDependee(KoShape *shape);
/// Returns if the given shape is dependent on this shape
bool hasDependee(KoShape *shape) const;
/// Returns list of shapes depending on this shape
QList dependees() const;
/// Returns additional snap data the shape wants to have snapping to
virtual KoSnapData snapData() const;
/**
* Set additional attribute
*
* This can be used to attach additional attributes to a shape for attributes
* that are application specific like presentation:placeholder
*
* @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder
* @param value The value of the attribute
*/
void setAdditionalAttribute(const QString &name, const QString &value);
/**
* Remove additional attribute
*
* @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder
*/
void removeAdditionalAttribute(const QString &name);
/**
* Check if additional attribute is set
*
* @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder
*
* @return true if there is a attribute with prefix:tag set, false otherwise
*/
bool hasAdditionalAttribute(const QString &name) const;
/**
* Get additional attribute
*
* @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder
*
* @return The value of the attribute if it exists or a null string if not found.
*/
QString additionalAttribute(const QString &name) const;
void setAdditionalStyleAttribute(const char *name, const QString &value);
void removeAdditionalStyleAttribute(const char *name);
/**
* Returns the filter effect stack of the shape
*
* @return the list of filter effects applied on the shape when rendering.
*/
KoFilterEffectStack *filterEffectStack() const;
/// Sets the new filter effect stack, removing the old one
void setFilterEffectStack(KoFilterEffectStack *filterEffectStack);
/**
* Set the property collision detection.
* Setting this to true will result in calls to shapeChanged() with the CollisionDetected
* parameter whenever either this or another shape is moved/rotated etc and intersects this shape.
* @param detect if true detect collisions.
*/
void setCollisionDetection(bool detect);
/**
* get the property collision detection.
* @returns true if collision detection is on.
*/
bool collisionDetection();
/**
* Return the tool delegates for this shape.
* In Flake a shape being selected will cause the tool manager to make available all tools that
* can edit the selected shapes. In some cases selecting one shape should allow the tool to
* edit a related shape be available too. The tool delegates allows this to happen by taking
* all the shapes in the set into account on tool selection.
* Notice that if the set is non-empty 'this' shape is no longer looked at. You can choose
* to add itself to the set too.
*/
QSet toolDelegates() const;
/**
* Set the tool delegates.
* @param delegates the new delegates.
* @see toolDelegates()
*/
void setToolDelegates(const QSet &delegates);
/**
* Return the hyperlink for this shape.
*/
QString hyperLink () const;
/**
* Set hyperlink for this shape.
* @param hyperLink name.
*/
void setHyperLink(const QString &hyperLink);
/**
* \internal
* Returns the private object for use within the flake lib
*/
KoShapePrivate *priv();
public:
struct ShapeChangeListener {
virtual ~ShapeChangeListener();
virtual void notifyShapeChanged(ChangeType type, KoShape *shape) = 0;
private:
friend class KoShape;
friend class KoShapePrivate;
void registerShape(KoShape *shape);
void unregisterShape(KoShape *shape);
void notifyShapeChangedImpl(ChangeType type, KoShape *shape);
QList m_registeredShapes;
};
void addShapeChangeListener(ShapeChangeListener *listener);
void removeShapeChangeListener(ShapeChangeListener *listener);
public:
static QList linearizeSubtree(const QList &shapes);
protected:
/// constructor
KoShape(KoShapePrivate *);
/* ** loading saving helper methods */
/// attributes from ODF 1.1 chapter 9.2.15 Common Drawing Shape Attributes
enum OdfAttribute {
OdfTransformation = 1, ///< Store transformation information
OdfSize = 2, ///< Store size information
OdfPosition = 8, ///< Store position
OdfAdditionalAttributes = 4, ///< Store additional attributes of the shape
OdfCommonChildElements = 16, ///< Event actions and connection points
OdfLayer = 64, ///< Store layer name
OdfStyle = 128, ///< Store the style
OdfId = 256, ///< Store the unique ID
OdfName = 512, ///< Store the name of the shape
OdfZIndex = 1024, ///< Store the z-index
OdfViewbox = 2048, ///< Store the viewbox
/// A mask for all mandatory attributes
OdfMandatories = OdfLayer | OdfStyle | OdfId | OdfName | OdfZIndex,
/// A mask for geometry attributes
OdfGeometry = OdfPosition | OdfSize,
/// A mask for all the attributes
OdfAllAttributes = OdfTransformation | OdfGeometry | OdfAdditionalAttributes | OdfMandatories | OdfCommonChildElements
};
/**
* This method is used during loading of the shape to load common attributes
*
* @param context the KoShapeLoadingContext used for loading
* @param element element which represents the shape in odf
* @param attributes a number of OdfAttribute items to state which attributes to load.
*/
bool loadOdfAttributes(const KoXmlElement &element, KoShapeLoadingContext &context, int attributes);
/**
* Parses the transformation attribute from the given string
* @param transform the transform attribute string
* @return the resulting transformation matrix
*/
QTransform parseOdfTransform(const QString &transform);
/**
* @brief Saves the style used for the shape
*
* This method fills the given style object with the stroke and
* background properties and then adds the style to the context.
*
* @param style the style object to fill
* @param context used for saving
* @return the name of the style
* @see saveOdf
*/
virtual QString saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const;
/**
* Loads the stroke and fill style from the given element.
*
* @param element the xml element to load the style from
* @param context the loading context used for loading
*/
virtual void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context);
/// Loads the stroke style
KoShapeStrokeModelSP loadOdfStroke(const KoXmlElement &element, KoShapeLoadingContext &context) const;
/// Loads the fill style
QSharedPointer loadOdfFill(KoShapeLoadingContext &context) const;
/// Loads the connection points
void loadOdfGluePoints(const KoXmlElement &element, KoShapeLoadingContext &context);
/// Loads the clip contour
void loadOdfClipContour(const KoXmlElement &element, KoShapeLoadingContext &context, const QSizeF &scaleFactor);
/* ** end loading saving */
/**
* A hook that allows inheriting classes to do something after a KoShape property changed
* This is called whenever the shape, position rotation or scale properties were altered.
* @param type an indicator which type was changed.
*/
virtual void shapeChanged(ChangeType type, KoShape *shape = 0);
/// return the current matrix that contains the rotation/scale/position of this shape
QTransform transform() const;
KoShapePrivate *d_ptr;
private:
Q_DECLARE_PRIVATE(KoShape)
};
Q_DECLARE_METATYPE(KoShape*)
#endif
diff --git a/libs/flake/KoShapeBasedDocumentBase.cpp b/libs/flake/KoShapeBasedDocumentBase.cpp
index fc13c9e9c0..b8dfc0725e 100644
--- a/libs/flake/KoShapeBasedDocumentBase.cpp
+++ b/libs/flake/KoShapeBasedDocumentBase.cpp
@@ -1,82 +1,89 @@
/* This file is part of the KDE project
Copyright (C) 2006, 2010 Thomas Zander
Copyright (C) 2011 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
#include "KoShapeBasedDocumentBase.h"
#include "KoDocumentResourceManager.h"
#include "KoShapeRegistry.h"
#include "KoShapeFactoryBase.h"
#include
#include
#include
class KoShapeBasedDocumentBasePrivate
{
public:
KoShapeBasedDocumentBasePrivate()
: resourceManager(new KoDocumentResourceManager())
{
KoShapeRegistry *registry = KoShapeRegistry::instance();
foreach (const QString &id, registry->keys()) {
KoShapeFactoryBase *shapeFactory = registry->value(id);
shapeFactory->newDocumentResourceManager(resourceManager);
}
// read persistent application wide resources
KSharedConfigPtr config = KSharedConfig::openConfig();
- if (config->hasGroup("Misc")) {
- KConfigGroup miscGroup = config->group("Misc");
- const qreal pasteOffset = miscGroup.readEntry("CopyOffset", 10.0);
- resourceManager->setPasteOffset(pasteOffset);
- const bool pasteAtCursor = miscGroup.readEntry("PasteAtCursor", true);
- resourceManager->enablePasteAtCursor(pasteAtCursor);
- const uint grabSensitivity = miscGroup.readEntry("GrabSensitivity", 3);
- resourceManager->setGrabSensitivity(grabSensitivity);
- const uint handleRadius = miscGroup.readEntry("HandleRadius", 3);
- resourceManager->setHandleRadius(handleRadius);
- }
+ KConfigGroup miscGroup = config->group("Misc");
+ const qreal pasteOffset = miscGroup.readEntry("CopyOffset", 10.0);
+ resourceManager->setPasteOffset(pasteOffset);
+ const bool pasteAtCursor = miscGroup.readEntry("PasteAtCursor", true);
+ resourceManager->enablePasteAtCursor(pasteAtCursor);
+ const uint grabSensitivity = miscGroup.readEntry("GrabSensitivity", 3);
+ resourceManager->setGrabSensitivity(grabSensitivity);
+ const uint handleRadius = miscGroup.readEntry("HandleRadius", 3);
+ resourceManager->setHandleRadius(handleRadius);
}
~KoShapeBasedDocumentBasePrivate()
{
delete resourceManager;
}
KoDocumentResourceManager *resourceManager;
};
KoShapeBasedDocumentBase::KoShapeBasedDocumentBase()
: d(new KoShapeBasedDocumentBasePrivate())
{
}
KoShapeBasedDocumentBase::~KoShapeBasedDocumentBase()
{
delete d;
}
void KoShapeBasedDocumentBase::shapesRemoved(const QList & /*shapes*/, KUndo2Command * /*command*/)
{
}
KoDocumentResourceManager *KoShapeBasedDocumentBase::resourceManager() const
{
return d->resourceManager;
}
+
+QRectF KoShapeBasedDocumentBase::documentRect() const
+{
+ const qreal pxToPt = 72.0 / pixelsPerInch();
+
+ QTransform t = QTransform::fromScale(pxToPt, pxToPt);
+ return t.mapRect(documentRectInPixels());
+}
diff --git a/libs/flake/KoShapeBasedDocumentBase.h b/libs/flake/KoShapeBasedDocumentBase.h
index b2f3565b03..95e6327d6d 100644
--- a/libs/flake/KoShapeBasedDocumentBase.h
+++ b/libs/flake/KoShapeBasedDocumentBase.h
@@ -1,85 +1,102 @@
/* This file is part of the KDE project
Copyright (C) 2006 Jan Hambrecht
Copyright (C) 2006, 2010 Thomas Zander
Copyright (C) 2008 C. Boemann
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 KOSHAPEBASEDDOCUMENTBASE_H
#define KOSHAPEBASEDDOCUMENTBASE_H
#include "kritaflake_export.h"
#include
+class QRectF;
class KoShape;
class KoShapeBasedDocumentBasePrivate;
class KoDocumentResourceManager;
class KUndo2Command;
/**
* The KoShapeBasedDocumentBase is an abstract interface that the application's class
* that owns the shapes should implement. This tends to be the document.
* @see KoShapeDeleteCommand, KoShapeCreateCommand
*/
class KRITAFLAKE_EXPORT KoShapeBasedDocumentBase
{
public:
KoShapeBasedDocumentBase();
virtual ~KoShapeBasedDocumentBase();
/**
* Add a shape to the shape controller, allowing it to be seen and saved.
* The controller should add the shape to the ShapeManager instance(s) manually
* if the shape is one that should be currently shown on screen.
* @param shape the new shape
*/
virtual void addShape(KoShape *shape) = 0;
/**
* Remove a shape from the shape controllers control, allowing it to be deleted shortly after
* The controller should remove the shape from all the ShapeManager instance(s) manually
* @param shape the shape to remove
*/
virtual void removeShape(KoShape *shape) = 0;
/**
* This method gets called after the KoShapeDeleteCommand is executed
*
* This passes the KoShapeDeleteCommand as the command parameter. This makes it possible
* for applications that need to do something after the KoShapeDeleteCommand is done, e.g.
* adding one commands that need to be executed when a shape was deleted.
* The default implementation is empty.
* @param shapes The list of shapes that got removed.
* @param command The command that was used to remove the shapes from the document.
*/
virtual void shapesRemoved(const QList &shapes, KUndo2Command *command);
/**
* Return a pointer to the resource manager associated with the
* shape-set (typically a document). The resource manager contains
* document wide resources * such as variable managers, the image
* collection and others.
*/
virtual KoDocumentResourceManager *resourceManager() const;
+ /**
+ * The size of the document measured in rasterized pixels. This information is needed for loading
+ * SVG documents that use 'px' as the default unit.
+ */
+ virtual QRectF documentRectInPixels() const = 0;
+
+ /**
+ * The size of the document measured in 'pt'
+ */
+ QRectF documentRect() const;
+
+ /**
+ * Resolution of the rasterized representaiton of the document. Used to load SVG documents correctly.
+ */
+ virtual qreal pixelsPerInch() const = 0;
+
private:
KoShapeBasedDocumentBasePrivate * const d;
};
#endif
diff --git a/libs/flake/KoShapeController.cpp b/libs/flake/KoShapeController.cpp
index 7d4559247a..d2f692fd34 100644
--- a/libs/flake/KoShapeController.cpp
+++ b/libs/flake/KoShapeController.cpp
@@ -1,185 +1,200 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006-2007, 2010 Thomas Zander
* Copyright (C) 2006-2008 Thorsten Zachmann
* Copyright (C) 2011 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 "KoShapeController.h"
#include "KoShapeBasedDocumentBase.h"
#include "KoShapeRegistry.h"
#include "KoDocumentResourceManager.h"
#include "KoShapeManager.h"
#include "KoShapeLayer.h"
#include "KoSelection.h"
#include "commands/KoShapeCreateCommand.h"
#include "commands/KoShapeDeleteCommand.h"
#include "commands/KoShapeConnectionChangeCommand.h"
#include "KoCanvasBase.h"
#include "KoShapeConfigWidgetBase.h"
#include "KoShapeFactoryBase.h"
#include "KoShape.h"
#include "KoConnectionShape.h"
#include
#include
#include
#include
class KoShapeController::Private
{
public:
Private()
: canvas(0),
shapeBasedDocument(0)
{
}
KoCanvasBase *canvas;
KoShapeBasedDocumentBase *shapeBasedDocument;
KUndo2Command* addShape(KoShape *shape, bool showDialog, KUndo2Command *parent) {
if (canvas) {
if (showDialog) {
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shape->shapeId());
Q_ASSERT(factory);
int z = 0;
Q_FOREACH (KoShape *sh, canvas->shapeManager()->shapes())
z = qMax(z, sh->zIndex());
shape->setZIndex(z + 1);
// show config dialog.
KPageDialog *dialog = new KPageDialog(canvas->canvasWidget());
dialog->setWindowTitle(i18n("%1 Options", factory->name()));
int pageCount = 0;
QList widgets;
Q_FOREACH (KoShapeConfigWidgetBase* panel, factory->createShapeOptionPanels()) {
if (! panel->showOnShapeCreate())
continue;
panel->open(shape);
panel->connect(panel, SIGNAL(accept()), dialog, SLOT(accept()));
widgets.append(panel);
panel->setResourceManager(canvas->resourceManager());
panel->setUnit(canvas->unit());
QString title = panel->windowTitle().isEmpty() ? panel->objectName() : panel->windowTitle();
dialog->addPage(panel, title);
pageCount ++;
}
if (pageCount > 0) {
if (pageCount > 1)
dialog->setFaceType(KPageDialog::Tabbed);
if (dialog->exec() != KPageDialog::Accepted) {
delete dialog;
return 0;
}
Q_FOREACH (KoShapeConfigWidgetBase *widget, widgets)
widget->save();
}
delete dialog;
}
// set the active layer as parent if there is not yet a parent.
if (!shape->parent()) {
shape->setParent(canvas->shapeManager()->selection()->activeLayer());
}
}
return new KoShapeCreateCommand(shapeBasedDocument, shape, parent);
}
void handleAttachedConnections(KoShape *shape, KUndo2Command *parentCmd) {
foreach (KoShape *dependee, shape->dependees()) {
KoConnectionShape *connection = dynamic_cast(dependee);
if (connection) {
if (shape == connection->firstShape()) {
new KoShapeConnectionChangeCommand(connection, KoConnectionShape::StartHandle,
shape, connection->firstConnectionId(), 0, -1, parentCmd);
} else if (shape == connection->secondShape()) {
new KoShapeConnectionChangeCommand(connection, KoConnectionShape::EndHandle,
shape, connection->secondConnectionId(), 0, -1, parentCmd);
}
}
}
}
};
KoShapeController::KoShapeController(KoCanvasBase *canvas, KoShapeBasedDocumentBase *shapeBasedDocument)
: d(new Private())
{
d->canvas = canvas;
d->shapeBasedDocument = shapeBasedDocument;
if (shapeBasedDocument) {
shapeBasedDocument->resourceManager()->setShapeController(this);
}
}
KoShapeController::~KoShapeController()
{
delete d;
}
KUndo2Command* KoShapeController::addShape(KoShape *shape, KUndo2Command *parent)
{
return d->addShape(shape, true, parent);
}
KUndo2Command* KoShapeController::addShapeDirect(KoShape *shape, KUndo2Command *parent)
{
return d->addShape(shape, false, parent);
}
KUndo2Command* KoShapeController::removeShape(KoShape *shape, KUndo2Command *parent)
{
KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeBasedDocument, shape, parent);
QList shapes;
shapes.append(shape);
d->shapeBasedDocument->shapesRemoved(shapes, cmd);
// detach shape from any attached connection shapes
d->handleAttachedConnections(shape, cmd);
return cmd;
}
KUndo2Command* KoShapeController::removeShapes(const QList &shapes, KUndo2Command *parent)
{
KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeBasedDocument, shapes, parent);
d->shapeBasedDocument->shapesRemoved(shapes, cmd);
foreach (KoShape *shape, shapes) {
d->handleAttachedConnections(shape, cmd);
}
return cmd;
}
void KoShapeController::setShapeControllerBase(KoShapeBasedDocumentBase *shapeBasedDocument)
{
d->shapeBasedDocument = shapeBasedDocument;
}
+QRectF KoShapeController::documentRectInPixels() const
+{
+ return d->shapeBasedDocument ? d->shapeBasedDocument->documentRectInPixels() : QRectF(0,0,1920,1080);
+}
+
+qreal KoShapeController::pixelsPerInch() const
+{
+ return d->shapeBasedDocument ? d->shapeBasedDocument->pixelsPerInch() : 72.0;
+}
+
+QRectF KoShapeController::documentRect() const
+{
+ return d->shapeBasedDocument ? d->shapeBasedDocument->documentRect() : documentRectInPixels();
+}
+
KoDocumentResourceManager *KoShapeController::resourceManager() const
{
if (!d->shapeBasedDocument)
return 0;
return d->shapeBasedDocument->resourceManager();
}
KoShapeBasedDocumentBase *KoShapeController::documentBase() const
{
return d->shapeBasedDocument;
}
diff --git a/libs/flake/KoShapeController.h b/libs/flake/KoShapeController.h
index 42f7d45068..377cae0dbe 100644
--- a/libs/flake/KoShapeController.h
+++ b/libs/flake/KoShapeController.h
@@ -1,135 +1,151 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006-2007, 2010 Thomas Zander
* Copyright (C) 2006-2008 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 KOSHAPECONTROLLER_H
#define KOSHAPECONTROLLER_H
#include "kritaflake_export.h"
#include
#include
class KoCanvasBase;
class KoShape;
class KoShapeBasedDocumentBase;
class KUndo2Command;
class KoDocumentResourceManager;
/**
* Class used by tools to maintain the list of shapes.
* All applications have some sort of list of all shapes that belong to the document.
* The applications implement the KoShapeBasedDocumentBase interface (all pure virtuals)
* to add and remove shapes from the document. To ensure that an application can expect
* a certain protocol to be adhered to when adding/removing shapes, all tools use the API
* from this class for maintaining the list of shapes in the document. So no tool gets
* to access the application directly.
*/
class KRITAFLAKE_EXPORT KoShapeController
{
public:
/**
* Create a new Controller; typically not called by applications, only
* by the KonCanvasBase constructor.
* @param canvas the canvas this controller works for. The canvas can be 0
* @param shapeBasedDocument the application provided shapeBasedDocument that we can call.
*/
KoShapeController(KoCanvasBase *canvas, KoShapeBasedDocumentBase *shapeBasedDocument);
/// destructor
~KoShapeController();
/**
* @brief Add a shape to the document.
* If the shape has no parent, the active layer will become its parent.
*
* @param shape to add to the document
* @param parent the parent command if the resulting command is a compound undo command.
*
* @return command which will insert the shape into the document or 0 if the
* insertion was cancelled. The command is not yet executed.
*/
KUndo2Command* addShape(KoShape *shape, KUndo2Command *parent = 0);
/**
* @brief Add a shape to the document, skipping any dialogs or other user interaction.
*
* @param shape to add to the document
* @param parent the parent command if the resulting command is a compound undo command.
*
* @return command which will insert the shape into the document. The command is not yet executed.
*/
KUndo2Command* addShapeDirect(KoShape *shape, KUndo2Command *parent = 0);
/**
* @brief Remove a shape from the document.
*
* @param shape to remove from the document
* @param parent the parent command if the resulting command is a compound undo command.
*
* @return command which will remove the shape from the document.
* The command is not yet executed.
*/
KUndo2Command* removeShape(KoShape *shape, KUndo2Command *parent = 0);
/**
* Remove a shape from the document.
*
* @param shapes the set of shapes to remove from the document
* @param parent the parent command if the resulting command is a compound undo command.
*
* @return command which will remove the shape from the document.
* The command is not yet executed.
*/
KUndo2Command* removeShapes(const QList &shapes, KUndo2Command *parent = 0);
/**
* @brief Set the KoShapeBasedDocumentBase used to add/remove shapes.
*
* NOTE: only Sheets uses this method. Do not use it in your application. Sheets
* has to also call:
* KoToolManager::instance()->updateShapeControllerBase(shapeBasedDocument, canvas->canvasController());
*
* @param shapeBasedDocument the new shapeBasedDocument.
*/
void setShapeControllerBase(KoShapeBasedDocumentBase *shapeBasedDocument);
+ /**
+ * The size of the document measured in rasterized pixels. This information is needed for loading
+ * SVG documents that use 'px' as the default unit.
+ */
+ QRectF documentRectInPixels() const;
+
+ /**
+ * Resolution of the rasterized representaiton of the document. Used to load SVG documents correctly.
+ */
+ qreal pixelsPerInch() const;
+
+ /**
+ * Document rect measured in 'pt'
+ */
+ QRectF documentRect() const;
+
/**
* Return a pointer to the resource manager associated with the
* shape-set (typically a document). The resource manager contains
* document wide resources * such as variable managers, the image
* collection and others.
*/
KoDocumentResourceManager *resourceManager() const;
/**
* @brief Returns the KoShapeBasedDocumentBase used to add/remove shapes.
*
* @return the KoShapeBasedDocumentBase
*/
KoShapeBasedDocumentBase *documentBase() const;
private:
class Private;
Private * const d;
};
Q_DECLARE_METATYPE(KoShapeController *)
#endif
diff --git a/libs/flake/KoSvgPaste.cpp b/libs/flake/KoSvgPaste.cpp
new file mode 100644
index 0000000000..205cca0285
--- /dev/null
+++ b/libs/flake/KoSvgPaste.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2017 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 "KoSvgPaste.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+KoSvgPaste::KoSvgPaste()
+{
+}
+
+bool KoSvgPaste::hasShapes() const
+{
+ const QMimeData *mimeData = QApplication::clipboard()->mimeData();
+ return mimeData && mimeData->hasFormat("image/svg+xml");
+}
+
+QList KoSvgPaste::fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize) const
+{
+ QList shapes;
+
+ const QMimeData *mimeData = QApplication::clipboard()->mimeData();
+ if (!mimeData) return shapes;
+
+ QByteArray data = mimeData->data("image/svg+xml");
+ if (data.isEmpty()) return shapes;
+
+ KoXmlDocument doc;
+
+ QString errorMsg;
+ int errorLine = 0;
+ int errorColumn = 0;
+
+ const bool documentValid = doc.setContent(data, false, &errorMsg, &errorLine, &errorColumn);
+
+ if (!documentValid) {
+ errorFlake << "Failed to process an SVG file at"
+ << errorLine << ":" << errorColumn << "->" << errorMsg;
+ return shapes;
+ }
+
+ KoDocumentResourceManager resourceManager;
+ SvgParser parser(&resourceManager);
+ parser.setResolution(viewportInPx, resolutionPPI);
+
+ shapes = parser.parseSvg(doc.documentElement(), fragmentSize);
+
+ return shapes;
+}
diff --git a/libs/flake/KoSvgPaste.h b/libs/flake/KoSvgPaste.h
new file mode 100644
index 0000000000..98400cf6e1
--- /dev/null
+++ b/libs/flake/KoSvgPaste.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2017 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 KOSVGPASTE_H
+#define KOSVGPASTE_H
+
+#include "kritaflake_export.h"
+#include
+
+class KoShape;
+class QRectF;
+class QSizeF;
+
+class KRITAFLAKE_EXPORT KoSvgPaste
+{
+public:
+ KoSvgPaste();
+
+ bool hasShapes() const;
+ QList fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize = 0) const;
+};
+
+#endif // KOSVGPASTE_H
diff --git a/libs/flake/KoToolManager.cpp b/libs/flake/KoToolManager.cpp
index 7fc1ca04a2..b2571512a8 100644
--- a/libs/flake/KoToolManager.cpp
+++ b/libs/flake/KoToolManager.cpp
@@ -1,1053 +1,1059 @@
/* This file is part of the KDE project
*
* Copyright (c) 2005-2010 Boudewijn Rempt
* Copyright (C) 2006-2008 Thomas Zander
* Copyright (C) 2006 Thorsten Zachmann
* Copyright (C) 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.
*/
// flake
#include "KoToolManager.h"
#include "KoToolManager_p.h"
#include "KoToolRegistry.h"
#include "KoToolProxy.h"
#include "KoToolProxy_p.h"
#include "KoSelection.h"
#include "KoCanvasController.h"
#include "KoCanvasControllerWidget.h"
#include "KoShape.h"
#include "KoShapeLayer.h"
#include "KoShapeRegistry.h"
#include "KoShapeManager.h"
#include "KoSelectedShapesProxy.h"
#include "KoCanvasBase.h"
#include "KoInputDeviceHandlerRegistry.h"
#include "KoInputDeviceHandlerEvent.h"
#include "KoPointerEvent.h"
#include "tools/KoCreateShapesTool.h"
#include "tools/KoZoomTool.h"
#include "tools/KoPanTool.h"
#include "kis_action_registry.h"
#include "KoToolFactoryBase.h"
+#include
+
// Qt + kde
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
Q_GLOBAL_STATIC(KoToolManager, s_instance)
class CanvasData
{
public:
CanvasData(KoCanvasController *cc, const KoInputDevice &id)
: activeTool(0),
canvas(cc),
inputDevice(id),
dummyToolWidget(0),
dummyToolLabel(0)
{
}
~CanvasData()
{
// the dummy tool widget does not necessarily have a parent and we create it, so we delete it.
delete dummyToolWidget;
}
void activateToolActions()
{
disabledDisabledActions.clear();
disabledActions.clear();
disabledCanvasShortcuts.clear();
// we do several things here
// 1. enable the actions of the active tool
// 2. disable conflicting actions
// 3. replace conflicting actions in the action collection
KActionCollection *canvasActionCollection = canvas->actionCollection();
QHash toolActions = activeTool->actions();
QHash::const_iterator it(toolActions.constBegin());
for (; it != toolActions.constEnd(); ++it) {
if (canvasActionCollection) {
QString toolActionID = it.key();
QAction *toolAction = it.value();
QAction * action = qobject_cast(canvasActionCollection->action(it.key()));
if (action) {
canvasActionCollection->takeAction(action);
if (action != it.value()) {
if (action->isEnabled()) {
action->setEnabled(false);
disabledActions.append(action);
} else {
disabledDisabledActions.append(action);
}
}
}
Q_FOREACH (QAction *a, canvasActionCollection->actions()) {
QAction *canvasAction = dynamic_cast(a);
if (canvasAction && canvasAction->shortcut().toString() != "" && canvasAction->shortcut() == toolAction->shortcut()) {
warnFlake << activeToolId << ": action" << toolActionID << "conflicts with canvas action" << canvasAction->objectName() << "shortcut:" << canvasAction->shortcut().toString();
disabledCanvasShortcuts[canvasAction] = canvasAction->shortcut().toString();
canvasAction->setShortcut(QKeySequence());
}
}
canvasActionCollection->addAction(toolActionID, toolAction);
}
it.value()->setEnabled(true);
}
canvasActionCollection->readSettings(); // The shortcuts might have been configured in the meantime.
}
void deactivateToolActions()
{
if (!activeTool)
return;
// disable actions of active tool
Q_FOREACH (QAction *action, activeTool->actions()) {
action->setEnabled(false);
}
// enable actions which where disabled on activating the active tool
// and re-add them to the action collection
KActionCollection *ac = canvas->actionCollection();
Q_FOREACH (QPointer action, disabledDisabledActions) {
if (action) {
if (ac) {
ac->addAction(action->objectName(), action);
}
}
}
disabledDisabledActions.clear();
Q_FOREACH (QPointer action, disabledActions) {
if (action) {
action->setEnabled(true);
if(ac) {
ac->addAction(action->objectName(), action);
}
}
}
disabledActions.clear();
QMap, QString>::const_iterator it(disabledCanvasShortcuts.constBegin());
for (; it != disabledCanvasShortcuts.constEnd(); ++it) {
QAction *action = it.key();
QString shortcut = it.value();
action->setShortcut(shortcut);
}
disabledCanvasShortcuts.clear();
}
KoToolBase *activeTool; // active Tool
QString activeToolId; // the id of the active Tool
QString activationShapeId; // the shape-type (KoShape::shapeId()) the activeTool 'belongs' to.
QHash allTools; // all the tools that are created for this canvas.
QStack stack; // stack of temporary tools
KoCanvasController *const canvas;
const KoInputDevice inputDevice;
QWidget *dummyToolWidget; // the widget shown in the toolDocker.
QLabel *dummyToolLabel;
QList > disabledActions; ///< disabled conflicting actions
QList > disabledDisabledActions; ///< disabled conflicting actions that were already disabled
QMap, QString> disabledCanvasShortcuts; ///< Shortcuts that were temporarily removed from canvas actions because the tool overrides
};
// ******** KoToolManager **********
KoToolManager::KoToolManager()
: QObject(),
d(new Private(this))
{
connect(QApplication::instance(), SIGNAL(focusChanged(QWidget*, QWidget*)),
this, SLOT(movedFocus(QWidget*, QWidget*)));
}
KoToolManager::~KoToolManager()
{
delete d;
}
QList KoToolManager::toolActionList() const
{
QList answer;
answer.reserve(d->tools.count());
Q_FOREACH (ToolHelper *tool, d->tools) {
if (tool->id() == KoCreateShapesTool_ID)
continue; // don't show this one.
answer.append(tool->toolAction());
}
return answer;
}
void KoToolManager::requestToolActivation(KoCanvasController * controller)
{
if (d->canvasses.contains(controller)) {
QString activeToolId = d->canvasses.value(controller).first()->activeToolId;
Q_FOREACH (ToolHelper * th, d->tools) {
if (th->id() == activeToolId) {
d->toolActivated(th);
break;
}
}
}
}
KoInputDevice KoToolManager::currentInputDevice() const
{
return d->inputDevice;
}
void KoToolManager::registerToolActions(KActionCollection *ac, KoCanvasController *controller)
{
Q_ASSERT(controller);
Q_ASSERT(ac);
d->setup();
if (!d->canvasses.contains(controller)) {
return;
}
// Actions available during the use of individual tools
CanvasData *cd = d->canvasses.value(controller).first();
Q_FOREACH (KoToolBase *tool, cd->allTools) {
QHash actions = tool->actions();
QHash::const_iterator action(actions.constBegin());
for (; action != actions.constEnd(); ++action) {
if (!ac->action(action.key()))
ac->addAction(action.key(), action.value());
}
}
// Actions used to switch tools via shortcuts
Q_FOREACH (ToolHelper * th, d->tools) {
if (ac->action(th->id())) {
continue;
}
ShortcutToolAction* action = th->createShortcutToolAction(ac);
ac->addCategorizedAction(th->id(), action, "tool-shortcuts");
}
}
void KoToolManager::addController(KoCanvasController *controller)
{
Q_ASSERT(controller);
if (d->canvasses.contains(controller))
return;
d->setup();
d->attachCanvas(controller);
connect(controller->proxyObject, SIGNAL(destroyed(QObject*)), this, SLOT(attemptCanvasControllerRemoval(QObject*)));
connect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*)));
connect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*)));
}
void KoToolManager::removeCanvasController(KoCanvasController *controller)
{
Q_ASSERT(controller);
disconnect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*)));
disconnect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*)));
d->detachCanvas(controller);
}
void KoToolManager::attemptCanvasControllerRemoval(QObject* controller)
{
KoCanvasControllerProxyObject* controllerActual = qobject_cast(controller);
if (controllerActual) {
removeCanvasController(controllerActual->canvasController());
}
}
void KoToolManager::updateShapeControllerBase(KoShapeBasedDocumentBase *shapeController, KoCanvasController *canvasController)
{
if (!d->canvasses.contains(canvasController))
return;
QList canvasses = d->canvasses[canvasController];
Q_FOREACH (CanvasData *canvas, canvasses) {
Q_FOREACH (KoToolBase *tool, canvas->allTools.values()) {
tool->updateShapeController(shapeController);
}
}
}
void KoToolManager::switchToolRequested(const QString & id)
{
Q_ASSERT(d->canvasData);
if (!d->canvasData) return;
while (!d->canvasData->stack.isEmpty()) // switching means to flush the stack
d->canvasData->stack.pop();
d->switchTool(id, false);
}
void KoToolManager::switchInputDeviceRequested(const KoInputDevice &id)
{
if (!d->canvasData) return;
d->switchInputDevice(id);
}
void KoToolManager::switchToolTemporaryRequested(const QString &id)
{
d->switchTool(id, true);
}
void KoToolManager::switchBackRequested()
{
if (!d->canvasData) return;
if (d->canvasData->stack.isEmpty()) {
// default to changing to the interactionTool
d->switchTool(KoInteractionTool_ID, false);
return;
}
d->switchTool(d->canvasData->stack.pop(), false);
}
KoCreateShapesTool * KoToolManager::shapeCreatorTool(KoCanvasBase *canvas) const
{
Q_ASSERT(canvas);
Q_FOREACH (KoCanvasController *controller, d->canvasses.keys()) {
if (controller->canvas() == canvas) {
KoCreateShapesTool *createTool = dynamic_cast
(d->canvasData->allTools.value(KoCreateShapesTool_ID));
Q_ASSERT(createTool /* ID changed? */);
return createTool;
}
}
Q_ASSERT(0); // this should not happen
return 0;
}
KoToolBase *KoToolManager::toolById(KoCanvasBase *canvas, const QString &id) const
{
Q_ASSERT(canvas);
Q_FOREACH (KoCanvasController *controller, d->canvasses.keys()) {
if (controller->canvas() == canvas)
return d->canvasData->allTools.value(id);
}
return 0;
}
KoCanvasController *KoToolManager::activeCanvasController() const
{
if (! d->canvasData) return 0;
return d->canvasData->canvas;
}
QString KoToolManager::preferredToolForSelection(const QList &shapes)
{
QList types;
- Q_FOREACH (KoShape *shape, shapes)
- if (! types.contains(shape->shapeId()))
- types.append(shape->shapeId());
+ Q_FOREACH (KoShape *shape, shapes) {
+ types << shape->shapeId();
+ }
+
+ KritaUtils::makeContainerUnique(types);
QString toolType = KoInteractionTool_ID;
int prio = INT_MAX;
Q_FOREACH (ToolHelper *helper, d->tools) {
+ if (helper->id() == KoCreateShapesTool_ID) continue;
+
if (helper->priority() >= prio)
continue;
bool toolWillWork = false;
foreach (const QString &type, types) {
if (helper->activationShapeId().split(',').contains(type)) {
toolWillWork = true;
break;
}
}
if (toolWillWork) {
toolType = helper->id();
prio = helper->priority();
}
}
return toolType;
}
void KoToolManager::addDeferredToolFactory(KoToolFactoryBase *toolFactory)
{
ToolHelper *tool = new ToolHelper(toolFactory);
// make sure all plugins are loaded as otherwise we will not load them
d->setup();
d->tools.append(tool);
// connect to all tools so we can hear their button-clicks
connect(tool, SIGNAL(toolActivated(ToolHelper*)), this, SLOT(toolActivated(ToolHelper*)));
// now create tools for all existing canvases
Q_FOREACH (KoCanvasController *controller, d->canvasses.keys()) {
// this canvascontroller is unknown, which is weird
if (!d->canvasses.contains(controller)) {
continue;
}
// create a tool for all canvasdata objects (i.e., all input devices on this canvas)
foreach (CanvasData *cd, d->canvasses[controller]) {
QPair toolPair = createTools(controller, tool);
if (toolPair.second) {
cd->allTools.insert(toolPair.first, toolPair.second);
}
}
// Then create a button for the toolbox for this canvas
if (tool->id() == KoCreateShapesTool_ID) {
continue;
}
emit addedTool(tool->toolAction(), controller);
}
}
QPair KoToolManager::createTools(KoCanvasController *controller, ToolHelper *tool)
{
// XXX: maybe this method should go into the private class?
QHash origHash;
if (d->canvasses.contains(controller)) {
origHash = d->canvasses.value(controller).first()->allTools;
}
if (origHash.contains(tool->id())) {
return QPair(tool->id(), origHash.value(tool->id()));
}
debugFlake << "Creating tool" << tool->id() << ". Activated on:" << tool->activationShapeId() << ", prio:" << tool->priority();
KoToolBase *tl = tool->createTool(controller->canvas());
if (tl) {
d->uniqueToolIds.insert(tl, tool->uniqueId());
tl->setObjectName(tool->id());
Q_FOREACH (QAction *action, tl->actions()) {
action->setEnabled(false);
}
}
KoZoomTool *zoomTool = dynamic_cast(tl);
if (zoomTool) {
zoomTool->setCanvasController(controller);
}
KoPanTool *panTool = dynamic_cast(tl);
if (panTool) {
panTool->setCanvasController(controller);
}
return QPair(tool->id(), tl);
}
// NOT IMPLEMENTED
void KoToolManager::updateToolShortcuts()
{
// auto actionRegistry = KisActionRegistry::instance();
// foreach (KoToolBase *t, allTools) {
// for (auto it = t->actions().constBegin();
// it != t->actions().constEnd();
// ++it;) {
// actionRegistry->updateShortcut(it.key(), it.value());
// }
// }
}
void KoToolManager::initializeCurrentToolForCanvas()
{
d->postSwitchTool(false);
}
KoToolManager* KoToolManager::instance()
{
return s_instance;
}
QString KoToolManager::activeToolId() const
{
if (!d->canvasData) return QString();
return d->canvasData->activeToolId;
}
KoToolManager::Private *KoToolManager::priv()
{
return d;
}
/**** KoToolManager::Private ****/
KoToolManager::Private::Private(KoToolManager *qq)
: q(qq),
canvasData(0),
layerExplicitlyDisabled(false)
{
}
KoToolManager::Private::~Private()
{
qDeleteAll(tools);
}
// helper method.
CanvasData *KoToolManager::Private::createCanvasData(KoCanvasController *controller, const KoInputDevice &device)
{
QHash toolsHash;
Q_FOREACH (ToolHelper *tool, tools) {
QPair toolPair = q->createTools(controller, tool);
if (toolPair.second) { // only if a real tool was created
toolsHash.insert(toolPair.first, toolPair.second);
}
}
KoCreateShapesTool *createShapesTool = dynamic_cast(toolsHash.value(KoCreateShapesTool_ID));
Q_ASSERT(createShapesTool);
QString id = KoShapeRegistry::instance()->keys()[0];
createShapesTool->setShapeId(id);
CanvasData *cd = new CanvasData(controller, device);
cd->allTools = toolsHash;
return cd;
}
void KoToolManager::Private::setup()
{
if (tools.size() > 0)
return;
KoShapeRegistry::instance();
KoToolRegistry *registry = KoToolRegistry::instance();
Q_FOREACH (const QString & id, registry->keys()) {
ToolHelper *t = new ToolHelper(registry->value(id));
tools.append(t);
}
// connect to all tools so we can hear their button-clicks
Q_FOREACH (ToolHelper *tool, tools)
connect(tool, SIGNAL(toolActivated(ToolHelper*)), q, SLOT(toolActivated(ToolHelper*)));
// load pluggable input devices
KoInputDeviceHandlerRegistry::instance();
}
void KoToolManager::Private::connectActiveTool()
{
if (canvasData->activeTool) {
connect(canvasData->activeTool, SIGNAL(cursorChanged(const QCursor &)),
q, SLOT(updateCursor(const QCursor &)));
connect(canvasData->activeTool, SIGNAL(activateTool(const QString &)),
q, SLOT(switchToolRequested(const QString &)));
connect(canvasData->activeTool, SIGNAL(activateTemporary(const QString &)),
q, SLOT(switchToolTemporaryRequested(const QString &)));
connect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested()));
connect(canvasData->activeTool, SIGNAL(statusTextChanged(const QString &)),
q, SIGNAL(changedStatusText(const QString &)));
}
// we expect the tool to emit a cursor on activation.
updateCursor(Qt::ForbiddenCursor);
}
void KoToolManager::Private::disconnectActiveTool()
{
if (canvasData->activeTool) {
canvasData->deactivateToolActions();
// repaint the decorations before we deactivate the tool as it might deleted
// data needed for the repaint
canvasData->activeTool->deactivate();
disconnect(canvasData->activeTool, SIGNAL(cursorChanged(const QCursor&)),
q, SLOT(updateCursor(const QCursor&)));
disconnect(canvasData->activeTool, SIGNAL(activateTool(const QString &)),
q, SLOT(switchToolRequested(const QString &)));
disconnect(canvasData->activeTool, SIGNAL(activateTemporary(const QString &)),
q, SLOT(switchToolTemporaryRequested(const QString &)));
disconnect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested()));
disconnect(canvasData->activeTool, SIGNAL(statusTextChanged(const QString &)),
q, SIGNAL(changedStatusText(const QString &)));
}
// emit a empty status text to clear status text from last active tool
emit q->changedStatusText(QString());
}
void KoToolManager::Private::switchTool(KoToolBase *tool, bool temporary)
{
Q_ASSERT(tool);
if (canvasData == 0)
return;
if (canvasData->activeTool == tool && tool->toolId() != KoInteractionTool_ID)
return;
disconnectActiveTool();
canvasData->activeTool = tool;
connectActiveTool();
postSwitchTool(temporary);
}
void KoToolManager::Private::switchTool(const QString &id, bool temporary)
{
Q_ASSERT(canvasData);
if (!canvasData) return;
if (canvasData->activeTool && temporary)
canvasData->stack.push(canvasData->activeToolId);
canvasData->activeToolId = id;
KoToolBase *tool = canvasData->allTools.value(id);
if (! tool) {
return;
}
Q_FOREACH (ToolHelper *th, tools) {
if (th->id() == id) {
canvasData->activationShapeId = th->activationShapeId();
break;
}
}
switchTool(tool, temporary);
}
void KoToolManager::Private::postSwitchTool(bool temporary)
{
#ifndef NDEBUG
int canvasCount = 1;
Q_FOREACH (QList list, canvasses) {
bool first = true;
Q_FOREACH (CanvasData *data, list) {
if (first) {
debugFlake << "Canvas" << canvasCount++;
}
debugFlake << " +- Tool:" << data->activeToolId << (data == canvasData ? " *" : "");
first = false;
}
}
#endif
Q_ASSERT(canvasData);
if (!canvasData) return;
KoToolBase::ToolActivation toolActivation;
if (temporary)
toolActivation = KoToolBase::TemporaryActivation;
else
toolActivation = KoToolBase::DefaultActivation;
QSet shapesToOperateOn;
if (canvasData->activeTool
&& canvasData->activeTool->canvas()
&& canvasData->activeTool->canvas()->shapeManager()) {
KoSelection *selection = canvasData->activeTool->canvas()->shapeManager()->selection();
Q_ASSERT(selection);
shapesToOperateOn = QSet::fromList(selection->selectedEditableShapesAndDelegates());
}
if (canvasData->canvas->canvas()) {
// Caller of postSwitchTool expect this to be called to update the selected tool
updateToolForProxy();
canvasData->activeTool->activate(toolActivation, shapesToOperateOn);
KoCanvasBase *canvas = canvasData->canvas->canvas();
canvas->updateInputMethodInfo();
} else {
canvasData->activeTool->activate(toolActivation, shapesToOperateOn);
}
QList > optionWidgetList = canvasData->activeTool->optionWidgets();
if (optionWidgetList.empty()) { // no option widget.
QWidget *toolWidget;
QString title;
Q_FOREACH (ToolHelper *tool, tools) {
if (tool->id() == canvasData->activeTool->toolId()) {
title = tool->toolTip();
break;
}
}
toolWidget = canvasData->dummyToolWidget;
if (toolWidget == 0) {
toolWidget = new QWidget();
toolWidget->setObjectName("DummyToolWidget");
QVBoxLayout *layout = new QVBoxLayout(toolWidget);
layout->setMargin(3);
canvasData->dummyToolLabel = new QLabel(toolWidget);
layout->addWidget(canvasData->dummyToolLabel);
layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding));
toolWidget->setLayout(layout);
canvasData->dummyToolWidget = toolWidget;
}
canvasData->dummyToolLabel->setText(i18n("Active tool: %1", title));
optionWidgetList.append(toolWidget);
}
// Activate the actions for the currently active tool
canvasData->activateToolActions();
emit q->changedTool(canvasData->canvas, uniqueToolIds.value(canvasData->activeTool));
emit q->toolOptionWidgetsChanged(canvasData->canvas, optionWidgetList);
}
void KoToolManager::Private::switchCanvasData(CanvasData *cd)
{
Q_ASSERT(cd);
KoCanvasBase *oldCanvas = 0;
KoInputDevice oldInputDevice;
if (canvasData) {
oldCanvas = canvasData->canvas->canvas();
oldInputDevice = canvasData->inputDevice;
if (canvasData->activeTool) {
disconnectActiveTool();
}
KoToolProxy *proxy = proxies.value(oldCanvas);
Q_ASSERT(proxy);
proxy->setActiveTool(0);
}
canvasData = cd;
inputDevice = canvasData->inputDevice;
if (canvasData->activeTool) {
connectActiveTool();
postSwitchTool(false);
}
if (oldInputDevice != canvasData->inputDevice) {
emit q->inputDeviceChanged(canvasData->inputDevice);
}
if (oldCanvas != canvasData->canvas->canvas()) {
emit q->changedCanvas(canvasData->canvas->canvas());
}
}
void KoToolManager::Private::toolActivated(ToolHelper *tool)
{
Q_ASSERT(tool);
Q_ASSERT(canvasData);
if (!canvasData) return;
KoToolBase *t = canvasData->allTools.value(tool->id());
Q_ASSERT(t);
canvasData->activeToolId = tool->id();
canvasData->activationShapeId = tool->activationShapeId();
switchTool(t, false);
}
void KoToolManager::Private::detachCanvas(KoCanvasController *controller)
{
Q_ASSERT(controller);
// check if we are removing the active canvas controller
if (canvasData && canvasData->canvas == controller) {
KoCanvasController *newCanvas = 0;
// try to find another canvas controller beside the one we are removing
Q_FOREACH (KoCanvasController* canvas, canvasses.keys()) {
if (canvas != controller) {
// yay found one
newCanvas = canvas;
break;
}
}
if (newCanvas) {
switchCanvasData(canvasses.value(newCanvas).first());
} else {
emit q->toolOptionWidgetsChanged(controller, QList >());
// as a last resort just set a blank one
canvasData = 0;
}
}
KoToolProxy *proxy = proxies.value(controller->canvas());
if (proxy)
proxy->setActiveTool(0);
QList tools;
Q_FOREACH (CanvasData *canvasData, canvasses.value(controller)) {
Q_FOREACH (KoToolBase *tool, canvasData->allTools) {
if (! tools.contains(tool)) {
tools.append(tool);
}
}
delete canvasData;
}
Q_FOREACH (KoToolBase *tool, tools) {
uniqueToolIds.remove(tool);
delete tool;
}
canvasses.remove(controller);
emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0);
}
void KoToolManager::Private::attachCanvas(KoCanvasController *controller)
{
Q_ASSERT(controller);
CanvasData *cd = createCanvasData(controller, KoInputDevice::mouse());
// switch to new canvas as the active one.
switchCanvasData(cd);
inputDevice = cd->inputDevice;
QList canvasses_;
canvasses_.append(cd);
canvasses[controller] = canvasses_;
KoToolProxy *tp = proxies[controller->canvas()];
if (tp)
tp->priv()->setCanvasController(controller);
if (cd->activeTool == 0) {
// no active tool, so we activate the highest priority main tool
int highestPriority = INT_MAX;
ToolHelper * helper = 0;
Q_FOREACH (ToolHelper * th, tools) {
if (th->section() == KoToolFactoryBase::mainToolType()) {
if (th->priority() < highestPriority) {
highestPriority = qMin(highestPriority, th->priority());
helper = th;
}
}
}
if (helper)
toolActivated(helper);
}
Connector *connector = new Connector(controller->canvas()->shapeManager());
connect(connector, SIGNAL(selectionChanged(QList)), q,
SLOT(selectionChanged(QList)));
connect(controller->canvas()->selectedShapesProxy(),
SIGNAL(currentLayerChanged(const KoShapeLayer*)),
q, SLOT(currentLayerChanged(const KoShapeLayer*)));
emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0);
}
void KoToolManager::Private::movedFocus(QWidget *from, QWidget *to)
{
Q_UNUSED(from);
// no canvas anyway or no focus set anyway?
if (!canvasData || to == 0) {
return;
}
// Check if this app is about QWidget-based KoCanvasControllerWidget canvasses
// XXX: Focus handling for non-qwidget based canvases!
KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast