diff --git a/libs/flake/CMakeLists.txt b/libs/flake/CMakeLists.txt index cd175e1f6f..d4b56a63cb 100644 --- a/libs/flake/CMakeLists.txt +++ b/libs/flake/CMakeLists.txt @@ -1,237 +1,235 @@ project(kritaflake) include_directories( ${CMAKE_SOURCE_DIR}/libs/flake/commands ${CMAKE_SOURCE_DIR}/libs/flake/tools ${CMAKE_SOURCE_DIR}/libs/flake/svg ${CMAKE_SOURCE_DIR}/libs/flake/text ${CMAKE_BINARY_DIR}/libs/flake ) add_subdirectory(styles) add_subdirectory(tests) add_subdirectory(resources/tests) set(kritaflake_SRCS KoGradientHelper.cpp KoFlake.cpp KoCanvasBase.cpp KoResourceManager_p.cpp KoDerivedResourceConverter.cpp KoResourceUpdateMediator.cpp KoCanvasResourceProvider.cpp KoDocumentResourceManager.cpp KoCanvasObserverBase.cpp KoCanvasSupervisor.cpp KoDockFactoryBase.cpp KoDockRegistry.cpp KoDataCenterBase.cpp KoInsets.cpp KoPathShape.cpp KoPathPoint.cpp KoPathSegment.cpp KoSelection.cpp KoSelectedShapesProxy.cpp KoSelectedShapesProxySimple.cpp KoShape.cpp KoShapeAnchor.cpp KoShapeControllerBase.cpp KoShapeApplicationData.cpp KoShapeContainer.cpp KoShapeContainerModel.cpp KoShapeGroup.cpp KoShapeManager.cpp KoShapePaintingContext.cpp KoFrameShape.cpp KoMarker.cpp KoMarkerCollection.cpp KoToolBase.cpp KoCanvasController.cpp KoCanvasControllerWidget.cpp KoCanvasControllerWidgetViewport_p.cpp KoShapeRegistry.cpp KoDeferredShapeFactoryBase.cpp KoToolFactoryBase.cpp KoPathShapeFactory.cpp KoShapeFactoryBase.cpp KoShapeUserData.cpp KoParameterShape.cpp KoPointerEvent.cpp KoShapeController.cpp KoToolSelection.cpp KoShapeLayer.cpp KoPostscriptPaintDevice.cpp KoInputDevice.cpp KoToolManager_p.cpp KoToolManager.cpp KoToolRegistry.cpp KoToolProxy.cpp KoShapeSavingContext.cpp KoShapeLoadingContext.cpp KoLoadingShapeUpdater.cpp KoPathShapeLoader.cpp KoShapeStrokeModel.cpp KoShapeStroke.cpp KoShapeBackground.cpp KoColorBackground.cpp KoGradientBackground.cpp - KoOdfGradientBackground.cpp KoHatchBackground.cpp KoPatternBackground.cpp KoVectorPatternBackground.cpp KoShapeFillWrapper.cpp KoShapeFillResourceConnector.cpp KoShapeConfigWidgetBase.cpp KoDrag.cpp KoSvgPaste.cpp KoSnapGuide.cpp KoSnapProxy.cpp KoSnapStrategy.cpp KoSnapData.cpp KoShapeShadow.cpp KoSharedLoadingData.cpp KoSharedSavingData.cpp KoViewConverter.cpp KoInputDeviceHandler.cpp KoInputDeviceHandlerEvent.cpp KoInputDeviceHandlerRegistry.cpp KoImageData.cpp KoImageData_p.cpp KoImageCollection.cpp - KoOdfWorkaround.cpp KoFilterEffect.cpp KoFilterEffectStack.cpp KoFilterEffectFactoryBase.cpp KoFilterEffectRegistry.cpp KoFilterEffectConfigWidgetBase.cpp KoFilterEffectRenderContext.cpp KoFilterEffectLoadingContext.cpp KoTextShapeDataBase.cpp KoTosContainer.cpp KoTosContainerModel.cpp KoClipPath.cpp KoClipMask.cpp KoClipMaskPainter.cpp KoCurveFit.cpp commands/KoShapeGroupCommand.cpp commands/KoShapeAlignCommand.cpp commands/KoShapeBackgroundCommand.cpp commands/KoShapeCreateCommand.cpp commands/KoShapeDeleteCommand.cpp commands/KoShapeDistributeCommand.cpp commands/KoShapeLockCommand.cpp commands/KoShapeMoveCommand.cpp commands/KoShapeResizeCommand.cpp commands/KoShapeShearCommand.cpp commands/KoShapeSizeCommand.cpp commands/KoShapeStrokeCommand.cpp commands/KoShapeUngroupCommand.cpp commands/KoShapeReorderCommand.cpp commands/KoShapeKeepAspectRatioCommand.cpp commands/KoPathBaseCommand.cpp commands/KoPathPointMoveCommand.cpp commands/KoPathControlPointMoveCommand.cpp commands/KoPathPointTypeCommand.cpp commands/KoPathPointRemoveCommand.cpp commands/KoPathPointInsertCommand.cpp commands/KoPathSegmentBreakCommand.cpp commands/KoPathBreakAtPointCommand.cpp commands/KoPathSegmentTypeCommand.cpp commands/KoPathCombineCommand.cpp commands/KoSubpathRemoveCommand.cpp commands/KoSubpathJoinCommand.cpp commands/KoParameterHandleMoveCommand.cpp commands/KoParameterToPathCommand.cpp commands/KoShapeTransformCommand.cpp commands/KoPathFillRuleCommand.cpp commands/KoShapeShadowCommand.cpp commands/KoPathReverseCommand.cpp commands/KoShapeRenameCommand.cpp commands/KoShapeRunAroundCommand.cpp commands/KoPathPointMergeCommand.cpp commands/KoShapeTransparencyCommand.cpp commands/KoShapeClipCommand.cpp commands/KoShapeUnclipCommand.cpp commands/KoPathShapeMarkerCommand.cpp commands/KoMultiPathPointMergeCommand.cpp commands/KoMultiPathPointJoinCommand.cpp commands/KoKeepShapesSelectedCommand.cpp commands/KoPathMergeUtils.cpp html/HtmlSavingContext.cpp html/HtmlWriter.cpp tools/KoPathToolFactory.cpp tools/KoPathTool.cpp tools/KoPathToolSelection.cpp tools/KoPathToolHandle.cpp tools/PathToolOptionWidget.cpp tools/KoPathPointRubberSelectStrategy.cpp tools/KoPathPointMoveStrategy.cpp tools/KoPathControlPointMoveStrategy.cpp tools/KoParameterChangeStrategy.cpp tools/KoZoomTool.cpp tools/KoZoomToolFactory.cpp tools/KoZoomToolWidget.cpp tools/KoZoomStrategy.cpp tools/KoInteractionTool.cpp tools/KoInteractionStrategy.cpp tools/KoInteractionStrategyFactory.cpp tools/KoShapeRubberSelectStrategy.cpp tools/KoPathSegmentChangeStrategy.cpp svg/KoShapePainter.cpp svg/SvgUtil.cpp svg/SvgGraphicContext.cpp svg/SvgSavingContext.cpp svg/SvgWriter.cpp svg/SvgStyleWriter.cpp svg/SvgShape.cpp svg/SvgParser.cpp svg/SvgStyleParser.cpp svg/SvgGradientHelper.cpp svg/SvgFilterHelper.cpp svg/SvgCssHelper.cpp svg/SvgClipPathHelper.cpp svg/SvgLoadingContext.cpp svg/SvgShapeFactory.cpp svg/parsers/SvgTransformParser.cpp text/KoSvgText.cpp text/KoSvgTextProperties.cpp text/KoSvgTextChunkShape.cpp text/KoSvgTextShape.cpp text/KoSvgTextShapeMarkupConverter.cpp resources/KoSvgSymbolCollectionResource.cpp resources/KoGamutMask.cpp FlakeDebug.cpp tests/MockShapes.cpp ) ki18n_wrap_ui(kritaflake_SRCS tools/PathToolOptionWidgetBase.ui tools/KoZoomToolWidget.ui ) add_library(kritaflake SHARED ${kritaflake_SRCS}) generate_export_header(kritaflake BASE_NAME kritaflake) target_include_directories(kritaflake PUBLIC $ $ $ $ ) target_link_libraries(kritaflake kritapigment kritawidgetutils kritaodf kritacommand KF5::WidgetsAddons Qt5::Svg) set_target_properties(kritaflake PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaflake ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/flake/KoColorBackground.cpp b/libs/flake/KoColorBackground.cpp index 2eacb4790a..f8de5b0f26 100644 --- a/libs/flake/KoColorBackground.cpp +++ b/libs/flake/KoColorBackground.cpp @@ -1,117 +1,96 @@ /* This file is part of the KDE project * 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. */ #include "KoColorBackground.h" #include "KoShapeSavingContext.h" #include #include #include #include #include #include class KoColorBackground::Private : public QSharedData { public: Private() : QSharedData() , color(Qt::black) , style(Qt::SolidPattern) {} QColor color; Qt::BrushStyle style; }; KoColorBackground::KoColorBackground() : KoShapeBackground() , d(new Private) { } KoColorBackground::KoColorBackground(const QColor &color, Qt::BrushStyle style) : KoShapeBackground() , d(new Private) { if (style < Qt::SolidPattern || style >= Qt::LinearGradientPattern) { style = Qt::SolidPattern; } d->style = style; d->color = color; } KoColorBackground::~KoColorBackground() { } bool KoColorBackground::compareTo(const KoShapeBackground *other) const { const KoColorBackground *bg = dynamic_cast(other); return bg && bg->color() == d->color; } QColor KoColorBackground::color() const { return d->color; } void KoColorBackground::setColor(const QColor &color) { d->color = color; } Qt::BrushStyle KoColorBackground::style() const { return d->style; } QBrush KoColorBackground::brush() const { return QBrush(d->color, d->style); } void KoColorBackground::paint(QPainter &painter, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const { painter.setBrush(brush()); painter.drawPath(fillPath); } -void KoColorBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) -{ - KoOdfGraphicStyles::saveOdfFillStyle(style, context.mainStyles(), QBrush(d->color, d->style)); -} - -bool KoColorBackground::loadStyle(KoOdfLoadingContext & context, const QSizeF &) -{ - KoStyleStack &styleStack = context.styleStack(); - if (! styleStack.hasProperty(KoXmlNS::draw, "fill")) - return false; - - QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); - if (fillStyle == "solid" || fillStyle == "hatch") { - QBrush brush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, fillStyle, context.stylesReader()); - d->color = brush.color(); - d->style = brush.style(); - return true; - } - - return false; -} diff --git a/libs/flake/KoColorBackground.h b/libs/flake/KoColorBackground.h index 474db8efa0..0732ae0a5e 100644 --- a/libs/flake/KoColorBackground.h +++ b/libs/flake/KoColorBackground.h @@ -1,67 +1,64 @@ /* This file is part of the KDE project * 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. */ #ifndef KOCOLORBACKGROUND_H #define KOCOLORBACKGROUND_H #include "KoShapeBackground.h" #include "kritaflake_export.h" #include #include class KoColorBackgroundPrivate; class QColor; class QBrush; /// A simple solid color shape background class KRITAFLAKE_EXPORT KoColorBackground : public KoShapeBackground { public: KoColorBackground(); /// Creates background from given color and style explicit KoColorBackground(const QColor &color, Qt::BrushStyle style = Qt::SolidPattern); ~KoColorBackground() override; bool compareTo(const KoShapeBackground *other) const override; /// Returns the background color QColor color() const; /// Sets the background color void setColor(const QColor &color); /// Returns the background style Qt::BrushStyle style() const; QBrush brush() const; // reimplemented from KoShapeBackground void paint(QPainter &painter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override; - // reimplemented from KoShapeBackground - void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override; - // reimplemented from KoShapeBackground - bool loadStyle(KoOdfLoadingContext & context, const QSizeF &shapeSize) override; + private: class Private; QSharedDataPointer d; }; #endif // KOCOLORBACKGROUND_H diff --git a/libs/flake/KoFrameShape.h b/libs/flake/KoFrameShape.h index e067e2b689..1437576fb6 100644 --- a/libs/flake/KoFrameShape.h +++ b/libs/flake/KoFrameShape.h @@ -1,99 +1,87 @@ /* This file is part of the KDE project Copyright (C) 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 KOFRAMESHAPE_H #define KOFRAMESHAPE_H #include "kritaflake_export.h" class KoShapeLoadingContext; #include class QString; /** * @brief Base class for shapes that are saved as a part of a draw:frame. * * Shapes like the TextShape or the PictureShape are implementing this * class to deal with frames and their attributes. * * What follows is a sample taken out of an ODT-file that shows how this works * together; * @code * * * * @endcode - * - * The loading code of the shape gets passed the draw:frame element. Out of this element the - * odf attributes can be loaded. Then it calls loadOdfFrame which loads the correct frame element - * the object supports. The loading of the frame element is done in the loadOdfFrameElement. - * - * @code - * bool PictureShape::loadOdf( const KoXmlElement & element, KoShapeLoadingContext &context ) - * { - * loadOdfAttributes( element, context, OdfAllAttributes ); - * return loadOdfFrame( element, context ); - * } - * @endcode */ class KRITAFLAKE_EXPORT KoFrameShape { public: /** * Constructor. * * \param ns The namespace. E.g. KoXmlNS::draw * \param element The tag-name. E.g. "image" */ KoFrameShape(const QString &ns, const QString &tag); /** * Copy contrustor */ KoFrameShape(const KoFrameShape &rhs); /** * Destructor. */ virtual ~KoFrameShape(); /** * Loads the content of the draw:frame element and it's children. This * method calls the abstract loadOdfFrameElement() method. * * @return false if loading failed */ virtual bool loadOdfFrame(const KoXmlElement &element, KoShapeLoadingContext &context); protected: /** * Abstract method to handle loading of the defined inner element like * e.g. the draw:image element. * @return false if loading failed */ virtual bool loadOdfFrameElement(const KoXmlElement &element, KoShapeLoadingContext &context) = 0; private: class Private; Private * const d; }; #endif /* KOFRAMESHAPE_H */ diff --git a/libs/flake/KoGradientBackground.cpp b/libs/flake/KoGradientBackground.cpp index fc9fbf8109..8efa578f02 100644 --- a/libs/flake/KoGradientBackground.cpp +++ b/libs/flake/KoGradientBackground.cpp @@ -1,184 +1,141 @@ /* This file is part of the KDE project * 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. */ #include "KoGradientBackground.h" #include "KoFlake.h" #include #include #include #include #include #include #include #include #include #include #include class KoGradientBackground::Private : public QSharedData { public: Private() : QSharedData() , gradient(0) {} QGradient *gradient; QTransform matrix; }; KoGradientBackground::KoGradientBackground(QGradient * gradient, const QTransform &matrix) : KoShapeBackground() , d(new Private) { d->gradient = gradient; d->matrix = matrix; Q_ASSERT(d->gradient); } KoGradientBackground::KoGradientBackground(const QGradient & gradient, const QTransform &matrix) : KoShapeBackground() , d(new Private) { d->gradient = KoFlake::cloneGradient(&gradient); d->matrix = matrix; Q_ASSERT(d->gradient); } KoGradientBackground::~KoGradientBackground() { delete d->gradient; } bool KoGradientBackground::compareTo(const KoShapeBackground *other) const { const KoGradientBackground *otherGradient = dynamic_cast(other); return otherGradient && d->matrix == otherGradient->d->matrix && *d->gradient == *otherGradient->d->gradient; } void KoGradientBackground::setTransform(const QTransform &matrix) { d->matrix = matrix; } QTransform KoGradientBackground::transform() const { return d->matrix; } void KoGradientBackground::setGradient(const QGradient &gradient) { delete d->gradient; d->gradient = KoFlake::cloneGradient(&gradient); Q_ASSERT(d->gradient); } const QGradient * KoGradientBackground::gradient() const { return d->gradient; } void KoGradientBackground::paint(QPainter &painter, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const { if (!d->gradient) return; if (d->gradient->coordinateMode() == QGradient::ObjectBoundingMode) { /** * NOTE: important hack! * * Qt has different notation of QBrush::setTransform() in comparison * to what SVG defines. SVG defines gradientToUser matrix to be postmultiplied * by QBrush::transform(), but Qt does exactly reverse! * * That most probably has beed caused by the fact that Qt uses transposed * matrices and someone just mistyped the stuff long ago :( * * So here we basically emulate this feature by converting the gradient into * QGradient::LogicalMode and doing transformations manually. */ const QRectF boundingRect = fillPath.boundingRect(); QTransform gradientToUser(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); // TODO: how about slicing the object? QGradient g = *d->gradient; g.setCoordinateMode(QGradient::LogicalMode); QBrush b(g); b.setTransform(d->matrix * gradientToUser); painter.setBrush(b); } else { QBrush b(*d->gradient); b.setTransform(d->matrix); painter.setBrush(b); } painter.drawPath(fillPath); } - -void KoGradientBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) -{ - if (!d->gradient) return; - QBrush brush(*d->gradient); - brush.setTransform(d->matrix); - KoOdfGraphicStyles::saveOdfFillStyle(style, context.mainStyles(), brush); -} - -bool KoGradientBackground::loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) -{ - KoStyleStack &styleStack = context.styleStack(); - if (! styleStack.hasProperty(KoXmlNS::draw, "fill")) - return false; - - QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); - if (fillStyle == "gradient") { - QBrush brush = KoOdfGraphicStyles::loadOdfGradientStyle(styleStack, context.stylesReader(), shapeSize); - const QGradient * gradient = brush.gradient(); - if (gradient) { - d->gradient = KoFlake::cloneGradient(gradient); - d->matrix = brush.transform(); - - //Gopalakrishna Bhat: If the brush has transparency then we ignore the draw:opacity property and use the brush transparency. - // Brush will have transparency if the svg:linearGradient stop point has stop-opacity property otherwise it is opaque - if (brush.isOpaque() && styleStack.hasProperty(KoXmlNS::draw, "opacity")) { - QString opacityPercent = styleStack.property(KoXmlNS::draw, "opacity"); - if (! opacityPercent.isEmpty() && opacityPercent.right(1) == "%") { - float opacity = qMin(opacityPercent.left(opacityPercent.length() - 1).toDouble(), 100.0) / 100; - QGradientStops stops; - Q_FOREACH (QGradientStop stop, d->gradient->stops()) { - stop.second.setAlphaF(opacity); - stops << stop; - } - d->gradient->setStops(stops); - } - } - - return true; - } - } - return false; -} diff --git a/libs/flake/KoGradientBackground.h b/libs/flake/KoGradientBackground.h index 71955abafe..452866e1f4 100644 --- a/libs/flake/KoGradientBackground.h +++ b/libs/flake/KoGradientBackground.h @@ -1,78 +1,74 @@ /* This file is part of the KDE project * 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. */ #ifndef KOGRADIENTBACKGROUND_H #define KOGRADIENTBACKGROUND_H #include "KoShapeBackground.h" #include "kritaflake_export.h" #include #include class QGradient; /// A gradient shape background class KRITAFLAKE_EXPORT KoGradientBackground : public KoShapeBackground { public: /** * Creates new gradient background from given gradient. * The background takes ownership of the given gradient. */ explicit KoGradientBackground(QGradient *gradient, const QTransform &matrix = QTransform()); /** * Create new gradient background from the given gradient. * A clone of the given gradient is used. */ explicit KoGradientBackground(const QGradient &gradient, const QTransform &matrix = QTransform()); /// Destroys the background ~KoGradientBackground() override; bool compareTo(const KoShapeBackground *other) const override; /// Sets the transform matrix void setTransform(const QTransform &matrix); /// Returns the transform matrix QTransform transform() const; /** * Sets a new gradient. * A clone of the given gradient is used. */ void setGradient(const QGradient &gradient); /// Returns the gradient const QGradient *gradient() const; /// reimplemented from KoShapeBackground void paint(QPainter &painter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override; - /// reimplemented from KoShapeBackground - void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override; - /// reimplemented from KoShapeBackground - bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) override; private: class Private; QSharedDataPointer d; }; #endif // KOGRADIENTBACKGROUND_H diff --git a/libs/flake/KoHatchBackground.cpp b/libs/flake/KoHatchBackground.cpp index 3275a590d2..55cd18f5bc 100644 --- a/libs/flake/KoHatchBackground.cpp +++ b/libs/flake/KoHatchBackground.cpp @@ -1,238 +1,131 @@ /* This file is part of the KDE project * * Copyright (C) 2012 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. */ #include "KoHatchBackground.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KoHatchBackground::Private : public QSharedData { public: Private() : QSharedData() , angle(0.0) , distance(1.0) , style(KoHatchBackground::Single) {} QColor lineColor; int angle; qreal distance; KoHatchBackground::HatchStyle style; QString name; }; KoHatchBackground::KoHatchBackground() : KoColorBackground() , d(new Private) { } KoHatchBackground::~KoHatchBackground() { } void KoHatchBackground::paint(QPainter &painter, KoShapePaintingContext &context, const QPainterPath &fillPath) const { if (color().isValid()) { // paint background color if set by using the color background KoColorBackground::paint(painter, context, fillPath); } const QRectF targetRect = fillPath.boundingRect(); painter.save(); painter.setClipPath(fillPath); QPen pen(d->lineColor); // we set the pen width to 0.5 pt for the hatch. This is not defined in the spec. pen.setWidthF(0.5); painter.setPen(pen); QVector lines; // The different styles are handled by painting the lines multiple times with a different // angel offset as basically it just means we paint the lines also at a different angle. // This are the angle offsets we need to apply to the different lines of a style. // -90 is for single, 0 for the 2nd line in double and -45 for the 3th line in triple. const int angleOffset[] = {-90, 0, -45 }; // The number of loops is defined by the style. int loops = (d->style == Single) ? 1 : (d->style == Double) ? 2 : 3; for (int i = 0; i < loops; ++i) { int angle = d->angle - angleOffset[i]; qreal cosAngle = ::cos(angle/180.0*M_PI); // if cos is nearly 0 the lines are horizontal. Use a special case for that if (qAbs(cosAngle) > 0.00001) { qreal xDiff = tan(angle/180.0*M_PI) * targetRect.height(); // calculate the distance we need to increase x when creating the lines so that the // distance between the lines is also correct for rotated lines. qreal xOffset = qAbs(d->distance / cosAngle); // if the lines go to the right we need to start more to the left. Get the correct start. qreal xStart = 0; while (-xDiff < xStart) { xStart -= xOffset; } // if the lines go to the left we need to stop more at the right. Get the correct end offset qreal xEndOffset = 0; if (xDiff < 0) { while (xDiff < -xEndOffset) { xEndOffset += xOffset; } } // create line objects. lines.reserve(lines.size() + int((targetRect.width() + xEndOffset - xStart) / xOffset) + 1); for (qreal x = xStart; x < targetRect.width() + xEndOffset; x += xOffset) { lines.append(QLineF(x, 0, x + xDiff, targetRect.height())); } } else { // horizontal lines lines.reserve(lines.size() + int(targetRect.height()/d->distance) + 1); for (qreal y = 0; y < targetRect.height(); y += d->distance) { lines.append(QLineF(0, y, targetRect.width(), y)); } } } painter.drawLines(lines); painter.restore(); } - -void KoHatchBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) -{ - KoGenStyle::Type type = style.type(); - KoGenStyle::PropertyType propertyType = (type == KoGenStyle::GraphicStyle || type == KoGenStyle::GraphicAutoStyle || - type == KoGenStyle::DrawingPageStyle || type == KoGenStyle::DrawingPageAutoStyle ) - ? KoGenStyle::DefaultType : KoGenStyle::GraphicType; - - style.addProperty("draw:fill", "hatch", propertyType); - style.addProperty("draw:fill-hatch-name", saveHatchStyle(context), propertyType); - bool fillHatchSolid = color().isValid(); - style.addProperty("draw:fill-hatch-solid", fillHatchSolid, propertyType); - if (fillHatchSolid) { - style.addProperty("draw:fill-color", color().name(), propertyType); - } -} - -QString KoHatchBackground::saveHatchStyle(KoShapeSavingContext &context) const -{ - KoGenStyle hatchStyle(KoGenStyle::HatchStyle /*no family name*/); - hatchStyle.addAttribute("draw:display-name", d->name); - hatchStyle.addAttribute("draw:color", d->lineColor.name()); - - hatchStyle.addAttribute("draw:distance", d->distance); - - hatchStyle.addAttribute("draw:rotation", QString("%1").arg(d->angle * 10)); - - switch (d->style) { - case Single: - hatchStyle.addAttribute("draw:style", "single"); - break; - case Double: - hatchStyle.addAttribute("draw:style", "double"); - break; - case Triple: - hatchStyle.addAttribute("draw:style", "triple"); - break; - } - - return context.mainStyles().insert(hatchStyle, "hatch"); -} - -bool KoHatchBackground::loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) -{ - // - Q_UNUSED(shapeSize); - - KoStyleStack &styleStack = context.styleStack(); - QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); - if (fillStyle == "hatch") { - QString style = styleStack.property(KoXmlNS::draw, "fill-hatch-name"); - debugFlake << " hatch style is :" << style; - - KoXmlElement* draw = context.stylesReader().drawStyles("hatch")[style]; - if (draw) { - debugFlake << "Hatch style found for:" << style; - - QString angle = draw->attributeNS(KoXmlNS::draw, "rotation", QString("0")); - if (angle.at(angle.size()-1).isLetter()) { - d->angle = KoUnit::parseAngle(angle); - } - else { - // OO saves the angle value without unit and multiplied by a factor of 10 - d->angle = int(angle.toInt() / 10); - } - - debugFlake << "angle :" << d->angle; - - d->name = draw->attributeNS(KoXmlNS::draw, "display-name"); - - // use 2mm as default, just in case it is not given in a document so we show something sensible. - d->distance = KoUnit::parseValue(draw->attributeNS(KoXmlNS::draw, "distance", "2mm")); - - bool fillHatchSolid = styleStack.property(KoXmlNS::draw, "fill-hatch-solid") == QLatin1String("true"); - if (fillHatchSolid) { - QString fillColor = styleStack.property(KoXmlNS::draw, "fill-color"); - if (!fillColor.isEmpty()) { - QColor c = color(); - c.setNamedColor(fillColor); - setColor(c); - } - else { - setColor(QColor()); - } - } - else { - setColor(QColor()); - } - d->lineColor.setNamedColor(draw->attributeNS(KoXmlNS::draw, "color", QString("#000000"))); - - QString style = draw->attributeNS(KoXmlNS::draw, "style", QString()); - if (style == "double") { - d->style = Double; - } - else if (style == "triple") { - d->style = Triple; - } - else { - d->style = Single; - } - } - - return true; - } - - return false; -} diff --git a/libs/flake/KoHatchBackground.h b/libs/flake/KoHatchBackground.h index 3f50301c17..2252b36aa0 100644 --- a/libs/flake/KoHatchBackground.h +++ b/libs/flake/KoHatchBackground.h @@ -1,58 +1,49 @@ /* This file is part of the KDE project * * Copyright (C) 2012 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 KOHATCHBACKGROUND_H #define KOHATCHBACKGROUND_H #include "KoColorBackground.h" /** * A hatch shape background */ class KoHatchBackground : public KoColorBackground { public: enum HatchStyle { Single, Double, Triple }; KoHatchBackground(); ~KoHatchBackground() override; // reimplemented void paint(QPainter &painter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override; - // reimplemented - void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override; - - // reimplemented - bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) override; - -private: - QString saveHatchStyle(KoShapeSavingContext &context) const; - private: class Private; QSharedDataPointer d; }; #endif /* KOHATCHBACKGROUND_H */ diff --git a/libs/flake/KoMarker.cpp b/libs/flake/KoMarker.cpp index a27b20a915..b460b4d61b 100644 --- a/libs/flake/KoMarker.cpp +++ b/libs/flake/KoMarker.cpp @@ -1,416 +1,415 @@ /* This file is part of the KDE project Copyright (C) 2011 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. */ #include "KoMarker.h" #include #include #include #include #include "KoPathShape.h" #include "KoPathShapeLoader.h" #include "KoShapeLoadingContext.h" #include "KoShapeSavingContext.h" -#include "KoOdfWorkaround.h" #include "KoShapePainter.h" #include #include #include #include #include #include #include #include "kis_global.h" #include "kis_algebra_2d.h" class Q_DECL_HIDDEN KoMarker::Private { public: Private() : coordinateSystem(StrokeWidth), referenceSize(3,3), hasAutoOrientation(false), explicitOrientation(0) {} ~Private() { // shape manager that is stored in the painter should be destroyed // before the shapes themselves shapePainter.reset(); qDeleteAll(shapes); } bool operator==(const KoMarker::Private &other) const { // WARNING: comparison of shapes is extremely fuzzy! Don't // trust it in life-critical cases! return name == other.name && coordinateSystem == other.coordinateSystem && referencePoint == other.referencePoint && referenceSize == other.referenceSize && hasAutoOrientation == other.hasAutoOrientation && explicitOrientation == other.explicitOrientation && compareShapesTo(other.shapes); } Private(const Private &rhs) : name(rhs.name), coordinateSystem(rhs.coordinateSystem), referencePoint(rhs.referencePoint), referenceSize(rhs.referenceSize), hasAutoOrientation(rhs.hasAutoOrientation), explicitOrientation(rhs.explicitOrientation) { Q_FOREACH (KoShape *shape, rhs.shapes) { shapes << shape->cloneShape(); } } QString name; MarkerCoordinateSystem coordinateSystem; QPointF referencePoint; QSizeF referenceSize; bool hasAutoOrientation; qreal explicitOrientation; QList shapes; QScopedPointer shapePainter; bool compareShapesTo(const QList other) const { if (shapes.size() != other.size()) return false; for (int i = 0; i < shapes.size(); i++) { if (shapes[i]->outline() != other[i]->outline() || shapes[i]->absoluteTransformation() != other[i]->absoluteTransformation()) { return false; } } return true; } QTransform markerTransform(qreal strokeWidth, qreal nodeAngle, const QPointF &pos = QPointF()) { const QTransform translate = QTransform::fromTranslate(-referencePoint.x(), -referencePoint.y()); QTransform t = translate; if (coordinateSystem == StrokeWidth) { t *= QTransform::fromScale(strokeWidth, strokeWidth); } const qreal angle = hasAutoOrientation ? nodeAngle : explicitOrientation; if (angle != 0.0) { QTransform r; r.rotateRadians(angle); t *= r; } t *= QTransform::fromTranslate(pos.x(), pos.y()); return t; } }; KoMarker::KoMarker() : d(new Private()) { } KoMarker::~KoMarker() { delete d; } QString KoMarker::name() const { return d->name; } KoMarker::KoMarker(const KoMarker &rhs) : QSharedData(rhs), d(new Private(*rhs.d)) { } bool KoMarker::operator==(const KoMarker &other) const { return *d == *other.d; } void KoMarker::setCoordinateSystem(KoMarker::MarkerCoordinateSystem value) { d->coordinateSystem = value; } KoMarker::MarkerCoordinateSystem KoMarker::coordinateSystem() const { return d->coordinateSystem; } KoMarker::MarkerCoordinateSystem KoMarker::coordinateSystemFromString(const QString &value) { MarkerCoordinateSystem result = StrokeWidth; if (value == "userSpaceOnUse") { result = UserSpaceOnUse; } return result; } QString KoMarker::coordinateSystemToString(KoMarker::MarkerCoordinateSystem value) { return value == StrokeWidth ? "strokeWidth" : "userSpaceOnUse"; } void KoMarker::setReferencePoint(const QPointF &value) { d->referencePoint = value; } QPointF KoMarker::referencePoint() const { return d->referencePoint; } void KoMarker::setReferenceSize(const QSizeF &size) { d->referenceSize = size; } QSizeF KoMarker::referenceSize() const { return d->referenceSize; } bool KoMarker::hasAutoOtientation() const { return d->hasAutoOrientation; } void KoMarker::setAutoOrientation(bool value) { d->hasAutoOrientation = value; } qreal KoMarker::explicitOrientation() const { return d->explicitOrientation; } void KoMarker::setExplicitOrientation(qreal value) { d->explicitOrientation = value; } void KoMarker::setShapes(const QList &shapes) { d->shapes = shapes; if (d->shapePainter) { d->shapePainter->setShapes(shapes); } } QList KoMarker::shapes() const { return d->shapes; } void KoMarker::paintAtPosition(QPainter *painter, const QPointF &pos, qreal strokeWidth, qreal nodeAngle) { QTransform oldTransform = painter->transform(); if (!d->shapePainter) { d->shapePainter.reset(new KoShapePainter()); d->shapePainter->setShapes(d->shapes); } painter->setTransform(d->markerTransform(strokeWidth, nodeAngle, pos), true); d->shapePainter->paint(*painter); painter->setTransform(oldTransform); } qreal KoMarker::maxInset(qreal strokeWidth) const { QRectF shapesBounds = boundingRect(strokeWidth, 0.0); // normalized to 0,0 qreal result = 0.0; result = qMax(KisAlgebra2D::norm(shapesBounds.topLeft()), result); result = qMax(KisAlgebra2D::norm(shapesBounds.topRight()), result); result = qMax(KisAlgebra2D::norm(shapesBounds.bottomLeft()), result); result = qMax(KisAlgebra2D::norm(shapesBounds.bottomRight()), result); if (d->coordinateSystem == StrokeWidth) { result *= strokeWidth; } return result; } QRectF KoMarker::boundingRect(qreal strokeWidth, qreal nodeAngle) const { QRectF shapesBounds = KoShape::boundingRect(d->shapes); const QTransform t = d->markerTransform(strokeWidth, nodeAngle); if (!t.isIdentity()) { shapesBounds = t.mapRect(shapesBounds); } return shapesBounds; } QPainterPath KoMarker::outline(qreal strokeWidth, qreal nodeAngle) const { QPainterPath outline; Q_FOREACH (KoShape *shape, d->shapes) { outline |= shape->absoluteTransformation().map(shape->outline()); } const QTransform t = d->markerTransform(strokeWidth, nodeAngle); if (!t.isIdentity()) { outline = t.map(outline); } return outline; } void KoMarker::drawPreview(QPainter *painter, const QRectF &previewRect, const QPen &pen, KoFlake::MarkerPosition position) { const QRectF outlineRect = outline(pen.widthF(), 0).boundingRect(); // normalized to 0,0 QPointF marker; QPointF start; QPointF end; if (position == KoFlake::StartMarker) { marker = QPointF(-outlineRect.left() + previewRect.left(), previewRect.center().y()); start = marker; end = QPointF(previewRect.right(), start.y()); } else if (position == KoFlake::MidMarker) { start = QPointF(previewRect.left(), previewRect.center().y()); marker = QPointF(-outlineRect.center().x() + previewRect.center().x(), start.y()); end = QPointF(previewRect.right(), start.y()); } else if (position == KoFlake::EndMarker) { start = QPointF(previewRect.left(), previewRect.center().y()); marker = QPointF(-outlineRect.right() + previewRect.right(), start.y()); end = marker; } painter->save(); painter->setPen(pen); painter->setClipRect(previewRect); painter->drawLine(start, end); paintAtPosition(painter, marker, pen.widthF(), 0); painter->restore(); } void KoMarker::applyShapeStroke(const KoShape *parentShape, KoShapeStroke *stroke, const QPointF &pos, qreal strokeWidth, qreal nodeAngle) { const QGradient *originalGradient = stroke->lineBrush().gradient(); if (!originalGradient) { QList linearizedShapes = KoShape::linearizeSubtree(d->shapes); Q_FOREACH(KoShape *shape, linearizedShapes) { // update the stroke KoShapeStrokeSP shapeStroke = shape->stroke() ? qSharedPointerDynamicCast(shape->stroke()) : KoShapeStrokeSP(); if (shapeStroke) { shapeStroke = toQShared(new KoShapeStroke(*shapeStroke)); shapeStroke->setLineBrush(QBrush()); shapeStroke->setColor(stroke->color()); shape->setStroke(shapeStroke); } // update the background if (shape->background()) { QSharedPointer bg(new KoColorBackground(stroke->color())); shape->setBackground(bg); } } } else { QScopedPointer g(KoFlake::cloneGradient(originalGradient)); KIS_ASSERT_RECOVER_RETURN(g); const QTransform markerTransformInverted = d->markerTransform(strokeWidth, nodeAngle, pos).inverted(); QTransform gradientToUser; // Unwrap the gradient to work in global mode if (g->coordinateMode() == QGradient::ObjectBoundingMode) { QRectF boundingRect = parentShape ? parentShape->outline().boundingRect() : this->boundingRect(strokeWidth, nodeAngle); boundingRect = KisAlgebra2D::ensureRectNotSmaller(boundingRect, QSizeF(1.0, 1.0)); gradientToUser = QTransform(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); g->setCoordinateMode(QGradient::LogicalMode); } QList linearizedShapes = KoShape::linearizeSubtree(d->shapes); Q_FOREACH(KoShape *shape, linearizedShapes) { // shape-unwinding transform QTransform t = gradientToUser * markerTransformInverted * shape->absoluteTransformation().inverted(); // update the stroke KoShapeStrokeSP shapeStroke = shape->stroke() ? qSharedPointerDynamicCast(shape->stroke()) : KoShapeStrokeSP(); if (shapeStroke) { shapeStroke = toQShared(new KoShapeStroke(*shapeStroke)); QBrush brush(*g); brush.setTransform(t); shapeStroke->setLineBrush(brush); shapeStroke->setColor(Qt::transparent); shape->setStroke(shapeStroke); } // update the background if (shape->background()) { QSharedPointer bg(new KoGradientBackground(KoFlake::cloneGradient(g.data()), t)); shape->setBackground(bg); } } } } diff --git a/libs/flake/KoOdfGradientBackground.cpp b/libs/flake/KoOdfGradientBackground.cpp deleted file mode 100644 index fe0f561bc4..0000000000 --- a/libs/flake/KoOdfGradientBackground.cpp +++ /dev/null @@ -1,380 +0,0 @@ -/* This file is part of the KDE project - * - * Copyright (C) 2011 Lukáš Tvrdý - * - * 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 "KoOdfGradientBackground.h" - -#include "KoShapeSavingContext.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "FlakeDebug.h" - -class KoOdfGradientBackground::Private : public QSharedData -{ -public: - Private() - : QSharedData() - , style() - , cx(0) - , cy(0) - , startColor() - , endColor() - , angle(0) - , border(0) - , opacity(1.0) - {} - ~Private() = default; - //data - QString style; - int cx; - int cy; - QColor startColor; - QColor endColor; - qreal angle; - qreal border; - qreal opacity; -}; - - -KoOdfGradientBackground::KoOdfGradientBackground() - : KoShapeBackground() - , d(new Private) -{ - -} - -KoOdfGradientBackground::~KoOdfGradientBackground() -{ - -} - -bool KoOdfGradientBackground::compareTo(const KoShapeBackground *other) const -{ - Q_UNUSED(other); - return false; -} - - -bool KoOdfGradientBackground::loadOdf(const KoXmlElement& e) -{ - d->style = e.attributeNS(KoXmlNS::draw, "style", QString()); - //TODO: support ellipsoid here too - if ((d->style != "rectangular") && (d->style != "square")) { - return false; - } - - d->cx = KoUnit::parseValue(e.attributeNS(KoXmlNS::draw, "cx", QString()).remove('%')); - d->cy = KoUnit::parseValue(e.attributeNS(KoXmlNS::draw, "cy", QString()).remove('%')); - - d->border = qBound(0.0,0.01 * e.attributeNS(KoXmlNS::draw, "border", "0").remove('%').toDouble(),1.0); - - d->startColor = QColor(e.attributeNS(KoXmlNS::draw, "start-color", QString())); - d->startColor.setAlphaF((0.01 * e.attributeNS(KoXmlNS::draw, "start-intensity", "100").remove('%').toDouble())); - - d->endColor = QColor(e.attributeNS(KoXmlNS::draw, "end-color", QString())); - d->endColor.setAlphaF(0.01 * e.attributeNS(KoXmlNS::draw, "end-intensity", "100").remove('%').toDouble()); - d->angle = e.attributeNS(KoXmlNS::draw, "angle", "0").toDouble() / 10; - - return true; -} - - -void KoOdfGradientBackground::saveOdf(KoGenStyle& styleFill, KoGenStyles& mainStyles) const -{ - KoGenStyle::Type type = styleFill.type(); - KoGenStyle::PropertyType propertyType = (type == KoGenStyle::GraphicStyle || type == KoGenStyle::GraphicAutoStyle || - type == KoGenStyle::DrawingPageStyle || type == KoGenStyle::DrawingPageAutoStyle ) - ? KoGenStyle::DefaultType : KoGenStyle::GraphicType; - - KoGenStyle gradientStyle(KoGenStyle::GradientStyle); - - gradientStyle.addAttribute("draw:style", d->style); // draw:style="square" - gradientStyle.addAttribute("draw:cx", QString("%1%").arg(d->cx)); - gradientStyle.addAttribute("draw:cy", QString("%1%").arg(d->cy)); - gradientStyle.addAttribute("draw:start-color", d->startColor.name()); - gradientStyle.addAttribute("draw:end-color", d->endColor.name()); - gradientStyle.addAttribute("draw:start-intensity", QString("%1%").arg(qRound(d->startColor.alphaF() * 100)) ); - gradientStyle.addAttribute("draw:end-intensity", QString("%1%").arg(qRound(d->endColor.alphaF() * 100)) ); - gradientStyle.addAttribute("draw:angle", QString("%1").arg(d->angle * 10)); - gradientStyle.addAttribute("draw:border", QString("%1%").arg(qRound(d->border * 100.0))); - - QString gradientStyleName = mainStyles.insert(gradientStyle, "gradient"); - - styleFill.addProperty("draw:fill", "gradient", propertyType); - styleFill.addProperty("draw:fill-gradient-name", gradientStyleName, propertyType); - if (d->opacity <= 1.0) { - styleFill.addProperty("draw:opacity", QString("%1%").arg(d->opacity * 100.0), propertyType); - } -} - -void KoOdfGradientBackground::paint(QPainter& painter, KoShapePaintingContext &/*context*/, const QPainterPath& fillPath) const -{ - QImage buffer; - - QRectF targetRect = fillPath.boundingRect(); - QRectF pixels = painter.transform().mapRect(QRectF(0,0,targetRect.width(), targetRect.height())); - QSize currentSize( qCeil(pixels.size().width()), qCeil(pixels.size().height()) ); - if (buffer.isNull() || buffer.size() != currentSize){ - buffer = QImage(currentSize, QImage::Format_ARGB32_Premultiplied); - if (d->style == "square") { - renderSquareGradient(buffer); - } else { - renderRectangleGradient(buffer); - } - } - - painter.setClipPath(fillPath); - - painter.setOpacity(d->opacity); - painter.drawImage(targetRect, buffer, QRectF(QPointF(0,0), buffer.size())); -} - -void KoOdfGradientBackground::fillStyle(KoGenStyle& style, KoShapeSavingContext& context) -{ - saveOdf(style, context.mainStyles()); -} - -bool KoOdfGradientBackground::loadStyle(KoOdfLoadingContext& context, const QSizeF& shapeSize) -{ - Q_UNUSED(shapeSize); - - KoStyleStack &styleStack = context.styleStack(); - if (!styleStack.hasProperty(KoXmlNS::draw, "fill")) { - return false; - } - - QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); - if (fillStyle == "gradient") { - - if (styleStack.hasProperty(KoXmlNS::draw, "opacity")) { - QString opacity = styleStack.property(KoXmlNS::draw, "opacity"); - if (! opacity.isEmpty() && opacity.right(1) == "%") { - d->opacity = qMin(opacity.left(opacity.length() - 1).toDouble(), 100.0) / 100; - } - } - - QString styleName = styleStack.property(KoXmlNS::draw, "fill-gradient-name"); - KoXmlElement * e = context.stylesReader().drawStyles("gradient")[styleName]; - return loadOdf(*e); - } - - return false; -} - - -void KoOdfGradientBackground::renderSquareGradient(QImage& buffer) const -{ - buffer.fill(d->startColor.rgba()); - - QPainter painter(&buffer); - painter.setPen(Qt::NoPen); - painter.setRenderHint(QPainter::Antialiasing, false); - - int width = buffer.width(); - int height = buffer.height(); - - qreal gradientCenterX = qRound(width * d->cx * 0.01); - qreal gradientCenterY = qRound(height * d->cy * 0.01); - qreal centerX = width * 0.5; - qreal centerY = height * 0.5; - - qreal areaCenterX = qRound(centerX); - qreal areaCenterY = qRound(centerY); - - QTransform m; - m.translate(gradientCenterX, gradientCenterY); - m.rotate(-d->angle); - m.scale(1.0 - d->border, 1.0 - d->border); - m.translate(-gradientCenterX, -gradientCenterY); - m.translate(gradientCenterX - areaCenterX,gradientCenterY - areaCenterY); - painter.setTransform(m); - - QLinearGradient linearGradient; - linearGradient.setColorAt(1, d->startColor); - linearGradient.setColorAt(0, d->endColor); - - // from center going North - linearGradient.setStart(centerX, centerY); - linearGradient.setFinalStop(centerX, 0); - painter.setBrush(linearGradient); - painter.drawRect(0, 0, width, centerY); - - // from center going South - linearGradient.setFinalStop(centerX, height); - painter.setBrush(linearGradient); - painter.drawRect(0, centerY, width, centerY); - - // clip the East and West portion - QPainterPath clip; - clip.moveTo(width, 0); - clip.lineTo(width, height); - clip.lineTo(0, 0); - clip.lineTo(0, height); - clip.closeSubpath(); - painter.setClipPath(clip); - - // from center going East - linearGradient.setFinalStop(width, centerY); - painter.setBrush(linearGradient); - painter.drawRect(centerX, 0, width, height); - - // from center going West - linearGradient.setFinalStop( 0, centerY); - painter.setBrush(linearGradient); - painter.drawRect(0, 0, centerX, height); -} - - -void KoOdfGradientBackground::renderRectangleGradient(QImage& buffer) const -{ - buffer.fill(d->startColor.rgba()); - - QPainter painter(&buffer); - painter.setPen(Qt::NoPen); - painter.setRenderHint(QPainter::Antialiasing, false); - - int width = buffer.width(); - int height = buffer.height(); - - qreal gradientCenterX = qRound(width * d->cx * 0.01); - qreal gradientCenterY = qRound(height * d->cy * 0.01); - qreal centerX = width * 0.5; - qreal centerY = height * 0.5; - - qreal areaCenterY = qRound(centerY); - qreal areaCenterX = qRound(centerX); - - QTransform m; - m.translate(gradientCenterX, gradientCenterY); - // m.rotate(-d->angle); // OOo rotates the gradient differently - m.scale(1.0 - d->border, 1.0 - d->border); - m.translate(-gradientCenterX, -gradientCenterY); - m.translate(gradientCenterX - areaCenterX,gradientCenterY - areaCenterY); - painter.setTransform(m); - - QLinearGradient linearGradient; - linearGradient.setColorAt(1, d->startColor); - linearGradient.setColorAt(0, d->endColor); - - // render background - QPainterPath clipPath; - if (width < height) { - QRectF west(0,0,centerX, height); - QRectF east(centerX, 0, centerX, height); - - linearGradient.setStart(centerX, centerY); - linearGradient.setFinalStop(0, centerY); - painter.setBrush(linearGradient); - painter.drawRect(west); - - linearGradient.setFinalStop(width, centerY); - painter.setBrush(linearGradient); - painter.drawRect(east); - - QRectF north(0,0,width, centerX); - QRectF south(0,height - centerX, width, centerX); - - clipPath.moveTo(0,0); - clipPath.lineTo(width, 0); - clipPath.lineTo(centerX, centerX); - clipPath.closeSubpath(); - - clipPath.moveTo(width, height); - clipPath.lineTo(0, height); - clipPath.lineTo(centerX, south.y()); - clipPath.closeSubpath(); - - linearGradient.setStart(centerX, centerX); - linearGradient.setFinalStop(centerX, 0); - - painter.setClipPath(clipPath); - painter.setBrush(linearGradient); - painter.drawRect(north); - - linearGradient.setStart(centerX, south.y()); - linearGradient.setFinalStop(centerX, height); - - painter.setBrush(linearGradient); - painter.drawRect(south); - } else { - QRectF north(0,0,width, centerY); - QRectF south(0, centerY, width, centerY); - - linearGradient.setStart(centerX, centerY); - linearGradient.setFinalStop(centerX, 0); - - painter.setBrush(linearGradient); - painter.drawRect(north); - - linearGradient.setFinalStop(centerX, height); - painter.setBrush(linearGradient); - painter.drawRect(south); - - - QRectF west(0,0,centerY, height); - QRectF east(width - centerY, 0, centerY, height); - - clipPath.moveTo(0,0); - clipPath.lineTo(centerY, centerY); - clipPath.lineTo(0,height); - clipPath.closeSubpath(); - - clipPath.moveTo(width, height); - clipPath.lineTo(east.x(), centerY); - clipPath.lineTo(width,0); - clipPath.closeSubpath(); - - linearGradient.setStart(centerY, centerY); - linearGradient.setFinalStop(0, centerY); - - painter.setClipPath(clipPath); - painter.setBrush(linearGradient); - painter.drawRect(west); - - linearGradient.setStart(east.x(), centerY); - linearGradient.setFinalStop(width, centerY); - - painter.setBrush(linearGradient); - painter.drawRect(east); - } -} - - -void KoOdfGradientBackground::debug() const -{ - debugFlake << "cx,cy: "<< d->cx << d->cy; - debugFlake << "style" << d->style; - debugFlake << "colors" << d->startColor << d->endColor; - debugFlake << "angle:" << d->angle; - debugFlake << "border" << d->border; -} diff --git a/libs/flake/KoOdfGradientBackground.h b/libs/flake/KoOdfGradientBackground.h deleted file mode 100644 index 19a94f6832..0000000000 --- a/libs/flake/KoOdfGradientBackground.h +++ /dev/null @@ -1,66 +0,0 @@ -/* This file is part of the KDE project - * - * Copyright (C) 2011 Lukáš Tvrdý - * - * 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 KOODFGRADIENTBACKGROUND_H -#define KOODFGRADIENTBACKGROUND_H - -#include "KoShapeBackground.h" -#include "kritaflake_export.h" - -class QImage; - -#include - -#include -class KoGenStyles; -class KoGenStyle; - -/// Gradients from odf that are not native to Qt -class KoOdfGradientBackground : public KoShapeBackground { -public: - // constructor - KoOdfGradientBackground(); - // destructor - ~KoOdfGradientBackground() override; - - bool compareTo(const KoShapeBackground *other) const override; - - /// reimplemented from KoShapeBackground - void fillStyle(KoGenStyle& style, KoShapeSavingContext& context) override; - /// reimplemented from KoShapeBackground - bool loadStyle(KoOdfLoadingContext& context, const QSizeF& shapeSize) override; - /// reimplemented from KoShapeBackground - void paint(QPainter& painter, KoShapePaintingContext &context, const QPainterPath& fillPath) const override; - -private: - bool loadOdf(const KoXmlElement &element); - void saveOdf(KoGenStyle& styleFill, KoGenStyles& mainStyles) const; - - void renderSquareGradient(QImage &buffer) const; - void renderRectangleGradient(QImage &buffer) const; - -private: - void debug() const; -private: - class Private; - QSharedDataPointer d; -}; - -#endif diff --git a/libs/flake/KoOdfWorkaround.cpp b/libs/flake/KoOdfWorkaround.cpp deleted file mode 100644 index b726dad385..0000000000 --- a/libs/flake/KoOdfWorkaround.cpp +++ /dev/null @@ -1,380 +0,0 @@ -/* This file is part of the KDE project - Copyright (C) 2009 Thorsten Zachmann - Copyright (C) 2009 Johannes Simon - Copyright (C) 2010,2011 Jan Hambrecht - Copyright 2012 Friedrich W. H. Kossebau - - 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 "KoOdfWorkaround.h" - -#include "KoShapeLoadingContext.h" -#include "KoShape.h" -#include "KoPathShape.h" -#include "KoColorBackground.h" -#include -#include -#include -#include -#include - -#include -#include - -#include - -static bool s_workaroundPresentationPlaceholderBug = false; - -void KoOdfWorkaround::fixPenWidth(QPen & pen, KoShapeLoadingContext &context) -{ - if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice && pen.widthF() == 0.0) { - pen.setWidthF(0.5); - debugFlake << "Work around OO bug with pen width 0"; - } -} - -void KoOdfWorkaround::fixEnhancedPath(QString & path, const KoXmlElement &element, KoShapeLoadingContext &context) -{ - if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice) { - if (path.isEmpty() && element.attributeNS(KoXmlNS::draw, "type", "") == "ellipse") { - path = "U 10800 10800 10800 10800 0 360 Z N"; - } - } -} - -void KoOdfWorkaround::fixEnhancedPathPolarHandlePosition(QString &position, const KoXmlElement &element, KoShapeLoadingContext &context) -{ - if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice) { - if (element.hasAttributeNS(KoXmlNS::draw, "handle-polar")) { - QStringList tokens = position.simplified().split(' '); - if (tokens.count() == 2) { - position = tokens[1] + ' ' + tokens[0]; - } - } - } -} - -QColor KoOdfWorkaround::fixMissingFillColor(const KoXmlElement &element, KoShapeLoadingContext &context) -{ - // Default to an invalid color - QColor color; - - if (element.prefix() == "chart") { - KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); - styleStack.save(); - - bool hasStyle = element.hasAttributeNS(KoXmlNS::chart, "style-name"); - if (hasStyle) { - context.odfLoadingContext().fillStyleStack(element, KoXmlNS::chart, "style-name", "chart"); - styleStack.setTypeProperties("graphic"); - } - - if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice) { - if (hasStyle && !styleStack.hasProperty(KoXmlNS::draw, "fill") && - styleStack.hasProperty(KoXmlNS::draw, "fill-color")) { - color = QColor(styleStack.property(KoXmlNS::draw, "fill-color")); - } else if (!hasStyle || (!styleStack.hasProperty(KoXmlNS::draw, "fill") - && !styleStack.hasProperty(KoXmlNS::draw, "fill-color"))) { - KoXmlElement plotAreaElement = element.parentNode().toElement(); - KoXmlElement chartElement = plotAreaElement.parentNode().toElement(); - - if (element.tagName() == "wall") { - if (chartElement.hasAttributeNS(KoXmlNS::chart, "class")) { - QString chartType = chartElement.attributeNS(KoXmlNS::chart, "class"); - // TODO: Check what default backgrounds for surface, stock and gantt charts are - if (chartType == "chart:line" || - chartType == "chart:area" || - chartType == "chart:bar" || - chartType == "chart:scatter") - color = QColor(0xe0e0e0); - } - } else if (element.tagName() == "series") { - if (chartElement.hasAttributeNS(KoXmlNS::chart, "class")) { - QString chartType = chartElement.attributeNS(KoXmlNS::chart, "class"); - // TODO: Check what default backgrounds for surface, stock and gantt charts are - if (chartType == "chart:area" || - chartType == "chart:bar") - color = QColor(0x99ccff); - } - } - else if (element.tagName() == "chart") - color = QColor(0xffffff); - } - } - - styleStack.restore(); - } - - return color; -} - -bool KoOdfWorkaround::fixMissingStroke(QPen &pen, const KoXmlElement &element, KoShapeLoadingContext &context, const KoShape *shape) -{ - bool fixed = false; - - if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice) { - KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); - if (element.prefix() == "chart") { - styleStack.save(); - - bool hasStyle = element.hasAttributeNS(KoXmlNS::chart, "style-name"); - if (hasStyle) { - context.odfLoadingContext().fillStyleStack(element, KoXmlNS::chart, "style-name", "chart"); - styleStack.setTypeProperties("graphic"); - } - - if (hasStyle && styleStack.hasProperty(KoXmlNS::draw, "stroke") && - !styleStack.hasProperty(KoXmlNS::svg, "stroke-color")) { - fixed = true; - pen.setColor(Qt::black); - } else if (!hasStyle) { - KoXmlElement plotAreaElement = element.parentNode().toElement(); - KoXmlElement chartElement = plotAreaElement.parentNode().toElement(); - - if (element.tagName() == "series") { - QString chartType = chartElement.attributeNS(KoXmlNS::chart, "class"); - if (!chartType.isEmpty()) { - // TODO: Check what default backgrounds for surface, stock and gantt charts are - if (chartType == "chart:line" || - chartType == "chart:scatter") { - fixed = true; - pen = QPen(0x99ccff); - } - } - } else if (element.tagName() == "legend") { - fixed = true; - pen = QPen(Qt::black); - } - } - styleStack.restore(); - } - else { - const KoPathShape *pathShape = dynamic_cast(shape); - if (pathShape) { - const QString strokeColor(styleStack.property(KoXmlNS::svg, "stroke-color")); - if (strokeColor.isEmpty()) { - pen.setColor(Qt::black); - } else { - pen.setColor(strokeColor); - } - fixed = true; - } - } - } - - return fixed; -} - -bool KoOdfWorkaround::fixMissingStyle_DisplayLabel(const KoXmlElement &element, KoShapeLoadingContext &context) -{ - Q_UNUSED(element); - // If no axis style is specified, OpenOffice.org hides the axis' data labels - if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice) - return false; - - // In all other cases, they're visible - return true; -} - -void KoOdfWorkaround::setFixPresentationPlaceholder(bool fix, KoShapeLoadingContext &context) -{ - KoOdfLoadingContext::GeneratorType type(context.odfLoadingContext().generatorType()); - if (type == KoOdfLoadingContext::OpenOffice || type == KoOdfLoadingContext::MicrosoftOffice) { - s_workaroundPresentationPlaceholderBug = fix; - } -} - -bool KoOdfWorkaround::fixPresentationPlaceholder() -{ - return s_workaroundPresentationPlaceholderBug; -} - -void KoOdfWorkaround::fixPresentationPlaceholder(KoShape *shape) -{ - if (s_workaroundPresentationPlaceholderBug && !shape->hasAdditionalAttribute("presentation:placeholder")) { - shape->setAdditionalAttribute("presentation:placeholder", "true"); - } -} - -QSharedPointer KoOdfWorkaround::fixBackgroundColor(const KoShape *shape, KoShapeLoadingContext &context) -{ - QSharedPointer colorBackground; - KoOdfLoadingContext &odfContext = context.odfLoadingContext(); - if (odfContext.generatorType() == KoOdfLoadingContext::OpenOffice) { - const KoPathShape *pathShape = dynamic_cast(shape); - //check shape type - if (pathShape) { - KoStyleStack &styleStack = odfContext.styleStack(); - const QString color(styleStack.property(KoXmlNS::draw, "fill-color")); - if (color.isEmpty()) { - colorBackground = QSharedPointer(new KoColorBackground(QColor(153, 204, 255))); - } else { - colorBackground = QSharedPointer(new KoColorBackground(color)); - } - } - } - return colorBackground; -} - -void KoOdfWorkaround::fixGluePointPosition(QString &positionString, KoShapeLoadingContext &context) -{ - KoOdfLoadingContext::GeneratorType type(context.odfLoadingContext().generatorType()); - if (type == KoOdfLoadingContext::OpenOffice && !positionString.endsWith('%')) { - const qreal pos = KoUnit::parseValue(positionString); - positionString = QString("%1%%").arg(KoUnit(KoUnit::Millimeter).toUserValue(pos)); - } -} - -void KoOdfWorkaround::fixMissingFillRule(Qt::FillRule& fillRule, KoShapeLoadingContext& context) -{ - if ((context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice)) { - fillRule = Qt::OddEvenFill; - } -} - -bool KoOdfWorkaround::fixAutoGrow(KoTextShapeDataBase::ResizeMethod method, KoShapeLoadingContext &context) -{ - bool fix = false; - if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice) { - if (method == KoTextShapeDataBase::AutoGrowWidth || method == KoTextShapeDataBase::AutoGrowHeight || method == KoTextShapeDataBase::AutoGrowWidthAndHeight) { - fix = true; - } - } - return fix; -} - -bool KoOdfWorkaround::fixEllipse(const QString &kind, KoShapeLoadingContext &context) -{ - bool radiusGiven = false; - if (context.odfLoadingContext().generatorType() == KoOdfLoadingContext::OpenOffice) { - if (kind == "section" || kind == "arc") { - radiusGiven = true; - } - } - return radiusGiven; -} - -void KoOdfWorkaround::fixBadFormulaHiddenForStyleCellProtect(QString& value) -{ - if (value.endsWith(QLatin1String("Formula.hidden"))) { - const int length = value.length(); - value[length-14] = QLatin1Char('f'); - value[length-7] = QLatin1Char('-'); - } -} - -void KoOdfWorkaround::fixBadDateForTextTime(QString &value) -{ - if (value.startsWith(QLatin1String("0-00-00T"))) { - value.remove(0, 8); - } -} - -void KoOdfWorkaround::fixClipRectOffsetValuesString(QString &offsetValuesString) -{ - if (! offsetValuesString.contains(QLatin1Char(','))) { - // assumes no spaces existing between values and units - offsetValuesString = offsetValuesString.simplified().replace(QLatin1Char(' '), QLatin1Char(',')); - } -} - -QString KoOdfWorkaround::fixTableTemplateName(const KoXmlElement &e) -{ - return e.attributeNS(KoXmlNS::text, "style-name", QString()); -} - -QString KoOdfWorkaround::fixTableTemplateCellStyleName(const KoXmlElement &e) -{ - return e.attributeNS(KoXmlNS::text, "style-name", QString()); -} - -static const struct { - const char* oldPath; - const char* newPath; -} markerPathMapping[] = { - // Arrow - {"m10 0-10 30h20z", - "M10 0l-10 30h20z"}, - // Square - {"m0 0h10v10h-10", - "M0 0h10v10h-10z"}, - // Small Arrow - {"m1321 3493h-1321l702-3493z", - "M1321 3493h-1321l702-3493z"}, - // Dimension Lines - {"M0 0h278 278 280v36 36 38h-278-278-280v-36-36z", - "m0 0h278 278 280v36 36 38h-278-278-280v-36-36z"}, - // Double Arrow - {"m737 1131h394l-564-1131-567 1131h398l-398 787h1131z", - "M737 1131h394l-564-1131-567 1131h398l-398 787h1131z"}, - // Rounded short Arrow - {"m1009 1050-449-1008-22-30-29-12-34 12-21 26-449 1012-5 13v8l5 21 12 21 17 13 21 4h903l21-4 21-13 9-21 4-21v-8z", - "M1009 1050l-449-1008-22-30-29-12-34 12-21 26-449 1012-5 13v8l5 21 12 21 17 13 21 4h903l21-4 21-13 9-21 4-21v-8z"}, - // Symmetric Arrow - {"m564 0-564 902h1131z", - "M564 0l-564 902h1131z"}, - // Line Arrow - {"m0 2108v17 17l12 42 30 34 38 21 43 4 29-8 30-21 25-26 13-34 343-1532 339 1520 13 42 29 34 39 21 42 4 42-12 34-30 21-42v-39-12l-4 4-440-1998-9-42-25-39-38-25-43-8-42 8-38 25-26 39-8 42z", - "M0 2108v17 17l12 42 30 34 38 21 43 4 29-8 30-21 25-26 13-34 343-1532 339 1520 13 42 29 34 39 21 42 4 42-12 34-30 21-42v-39-12l-4 4-440-1998-9-42-25-39-38-25-43-8-42 8-38 25-26 39-8 42z"}, - // Rounded large Arrow - {"m1127 2120-449-2006-9-42-25-39-38-25-38-8-43 8-38 25-25 39-9 42-449 2006v13l-4 9 9 42 25 38 38 25 42 9h903l42-9 38-25 26-38 8-42v-9z", - "M1127 2120l-449-2006-9-42-25-39-38-25-38-8-43 8-38 25-25 39-9 42-449 2006v13l-4 9 9 42 25 38 38 25 42 9h903l42-9 38-25 26-38 8-42v-9z"}, - // Circle - {"m462 1118-102-29-102-51-93-72-72-93-51-102-29-102-13-105 13-102 29-106 51-102 72-89 93-72 102-50 102-34 106-9 101 9 106 34 98 50 93 72 72 89 51 102 29 106 13 102-13 105-29 102-51 102-72 93-93 72-98 51-106 29-101 13z", - "M462 1118l-102-29-102-51-93-72-72-93-51-102-29-102-13-105 13-102 29-106 51-102 72-89 93-72 102-50 102-34 106-9 101 9 106 34 98 50 93 72 72 89 51 102 29 106 13 102-13 105-29 102-51 102-72 93-93 72-98 51-106 29-101 13z"}, - // Square 45 - {"m0 564 564 567 567-567-567-564z", - "M0 564l564 567 567-567-567-564z"}, - // Arrow concave - {"m1013 1491 118 89-567-1580-564 1580 114-85 136-68 148-46 161-17 161 13 153 46z", - "M1013 1491l118 89-567-1580-564 1580 114-85 136-68 148-46 161-17 161 13 153 46z"}, - // Short line Arrow - {"m1500 0 1500 2789v211h-114l-1286-2392v2392h-200v-2392l-1286 2392h-114v-211z", - "M1500 0l1500 2789v211h-114l-1286-2392v2392h-200v-2392l-1286 2392h-114v-211z"}, - // Triangle unfilled - {"m1500 0 1500 3000h-3000zm1500-2553-1176 2353h2353z", - "M1500 0l1500 3000h-3000zM1500 447l-1176 2353h2353z"}, - // Diamond unfilled - {"m1500 0 1500 3000-1500 3000-1500-3000zm1500-2553-1276 2553 1276 2553 1276-2553z", - "M1500 0l1500 3000-1500 3000-1500-3000zM1500 447l-1276 2553 1276 2553 1276-2553z"}, - // Diamond - {"m1500 0 1500 3000-1500 3000-1500-3000z", - "M1500 0l1500 3000-1500 3000-1500-3000z"}, - // Circle unfilled - {"m1500 3000c-276 0-511-63-750-201s-411-310-549-549-201-474-201-750 63-511 201-750 310-411 549-549 474-201 750-201 511 63 750 201 411 310 549 549 201 474 201 750-63 511-201 750-310 411-549 549-474 201-750 201zm0-200c-239 0-443-55-650-174s-356-269-476-476-174-411-174-650 55-443 174-650 269-356 476-476c207-119 411-174 650-174s443 55 650 174c207 120 356 269 476 476s174 411 174 650-55 443-174 650-269 356-476 476c-207 119-411 174-650 174z", - "M1500 3000c-276 0-511-63-750-201s-411-310-549-549-201-474-201-750 63-511 201-750 310-411 549-549 474-201 750-201 511 63 750 201 411 310 549 549 201 474 201 750-63 511-201 750-310 411-549 549-474 201-750 201zM1500 2800c-239 0-443-55-650-174s-356-269-476-476-174-411-174-650 55-443 174-650 269-356 476-476c207-119 411-174 650-174s443 55 650 174c207 120 356 269 476 476s174 411 174 650-55 443-174 650-269 356-476 476c-207 119-411 174-650 174z"}, - // Square 45 unfilled - {"m1500 3000-1500-1500 1500-1500 1500 1500zm-1500 1215-1215-1215 1215-1215 1215 1215z", - "M1500 3000l-1500-1500 1500-1500 1500 1500zM1500 2715l-1215-1215 1215-1215 1215 1215z"}, - // Square unfilled - {"m0 0h300v300h-300zm20-280h260v260h-260z", - "M0 0h300v300h-300zM20 20h260v260h-260z"}, - // Half Circle unfilled - {"m14971 0c21 229 29 423 29 653 0 690-79 1328-244 1943-165 614-416 1206-761 1804-345 597-733 1110-1183 1560-451 450-964 837-1562 1182-598 345-1190 596-1806 760-600 161-1223 240-1894 244v600h-100v-600c-671-4-1294-83-1894-244-616-164-1208-415-1806-760-598-345-1111-732-1562-1182-450-450-838-963-1183-1560-345-598-596-1190-761-1804-165-615-244-1253-244-1943 0-230 8-424 29-653l298 26 299 26c-18 211-26 390-26 601 0 635 72 1222 224 1787 151 566 383 1110 700 1659 318 550 674 1022 1088 1437 415 414 888 769 1438 1087 550 317 1095 548 1661 700 566 151 1154 223 1789 223s1223-72 1789-223c566-152 1111-383 1661-700 550-318 1023-673 1438-1087 414-415 770-887 1088-1437 317-549 549-1093 700-1659 152-565 224-1152 224-1787 0-211-8-390-26-601l299-26z", - "M14971 0c21 229 29 423 29 653 0 690-79 1328-244 1943-165 614-416 1206-761 1804-345 597-733 1110-1183 1560-451 450-964 837-1562 1182s-1190 596-1806 760c-600 161-1223 240-1894 244v600h-100v-600c-671-4-1294-83-1894-244-616-164-1208-415-1806-760s-1111-732-1562-1182c-450-450-838-963-1183-1560-345-598-596-1190-761-1804-165-615-244-1253-244-1943 0-230 8-424 29-653l298 26 299 26c-18 211-26 390-26 601 0 635 72 1222 224 1787 151 566 383 1110 700 1659 318 550 674 1022 1088 1437 415 414 888 769 1438 1087 550 317 1095 548 1661 700 566 151 1154 223 1789 223s1223-72 1789-223c566-152 1111-383 1661-700 550-318 1023-673 1438-1087 414-415 770-887 1088-1437 317-549 549-1093 700-1659 152-565 224-1152 224-1787 0-211-8-390-26-601l299-26z"} -}; -static const int markerPathMappingSize = sizeof(markerPathMapping)/sizeof(markerPathMapping[0]); - -void KoOdfWorkaround::fixMarkerPath(QString& path) -{ - for (int i = 0; i < markerPathMappingSize; ++i) { - if (path == QLatin1String(markerPathMapping[i].oldPath)) { - path = QLatin1String(markerPathMapping[i].newPath); - break; - } - } -} diff --git a/libs/flake/KoOdfWorkaround.h b/libs/flake/KoOdfWorkaround.h deleted file mode 100644 index 9bf9e91d95..0000000000 --- a/libs/flake/KoOdfWorkaround.h +++ /dev/null @@ -1,162 +0,0 @@ -/* This file is part of the KDE project - Copyright (C) 2009 Thorsten Zachmann - Copyright (C) 2011 Jan Hambrecht - Copyright (C) 2011 Lukáš Tvrdý - - 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 KOODFWORKAROUND_H -#define KOODFWORKAROUND_H - -#include "kritaflake_export.h" -#include "KoTextShapeDataBase.h" -#include - -#include - -#include -class KoShape; -class KoShapeLoadingContext; -class QPen; -class QColor; -class QString; -class KoColorBackground; - -/** - * This class should contain all workarounds to correct problems with different ODF - * implementations. If you need to access application specific things please create a - * new namespace in the application you need it in - * All calls to methods of this class should be wrapped into ifndefs like e.g. - * - * @code - * #ifndef NWORKAROUND_ODF_BUGS - * KoOdfWorkaround::fixPenWidth(pen, context); - * #endif - * @endcode - */ -namespace KoOdfWorkaround -{ - /** - * OpenOffice handles a line with the width of 0 as a cosmetic line but in svg it makes the line invisible. - * To show it in calligra use a very small line width. However this is not a cosmetic line. - */ - KRITAFLAKE_EXPORT void fixPenWidth(QPen &pen, KoShapeLoadingContext &context); - - /** - * OpenOffice < 3.0 does not store the draw:enhanced-path for draw:type="ellipse" - * Add the path needed for the ellipse - */ - KRITAFLAKE_EXPORT void fixEnhancedPath(QString &path, const KoXmlElement &element, KoShapeLoadingContext &context); - - /** - * OpenOffice interchanges the position coordinates for polar handles. - * According to the specification the first coordinate is the angle, the - * second coordinates is the radius. OpenOffice does it the other way around. - */ - KRITAFLAKE_EXPORT void fixEnhancedPathPolarHandlePosition(QString &position, const KoXmlElement &element, KoShapeLoadingContext &context); - - KRITAFLAKE_EXPORT bool fixMissingStroke(QPen &pen, const KoXmlElement &element, KoShapeLoadingContext &context, const KoShape *shape = 0); - KRITAFLAKE_EXPORT QColor fixMissingFillColor(const KoXmlElement &element, KoShapeLoadingContext &context); - KRITAFLAKE_EXPORT bool fixMissingStyle_DisplayLabel(const KoXmlElement &element, KoShapeLoadingContext &context); - - KRITAFLAKE_EXPORT QSharedPointer fixBackgroundColor(const KoShape *shape, KoShapeLoadingContext &context); - - /** - * Old versions of ooimpress does not set the placeholder for shapes that should have it set - * See open office issue https://bz.apache.org/ooo/show_bug.cgi?id=96406 - * And kde bug https://bugs.kde.org/show_bug.cgi?id=185354 - */ - KRITAFLAKE_EXPORT void setFixPresentationPlaceholder(bool fix, KoShapeLoadingContext &context); - KRITAFLAKE_EXPORT bool fixPresentationPlaceholder(); - KRITAFLAKE_EXPORT void fixPresentationPlaceholder(KoShape *shape); - - /** - * OpenOffice and LibreOffice save gluepoint positions wrong when no align is specified. - * According to the specification for the above situation, the position should be saved - * as percent values relative to the shapes center point. OpenOffice seems to write - * these percent values converted to length units, where the millimeter value corresponds - * to the correct percent value (i.e. -5cm = -50mm = -50%). - */ - KRITAFLAKE_EXPORT void fixGluePointPosition(QString &positionString, KoShapeLoadingContext &context); - - /** - * OpenOffice and LibreOffice does not conform to the specification about default value - * of the svg:fill-rule. If this attribute is missing, according the spec, the initial - * value is nonzero, but OOo uses evenodd. Because we are conform to the spec, we need - * to set what OOo display. - * See http://www.w3.org/TR/SVG/painting.html#FillRuleProperty - */ - KRITAFLAKE_EXPORT void fixMissingFillRule(Qt::FillRule &fillRule, KoShapeLoadingContext &context); - - /** - * OpenOffice resizes text shapes with autogrow in both directions. If the text box is saved to - * small the text will not fit and it needs to be adjusted during the first layout. - * This methods returns true if we need to adjust the layout. The adjusting is handled at a different place. - */ - KRITAFLAKE_EXPORT bool fixAutoGrow(KoTextShapeDataBase::ResizeMethod method, KoShapeLoadingContext &context); - - /** - * OpenOffice and LibreOffice do not set the svg:width, svg:height, svg:x and svg:y correctly when saving - * parts of draw:ellipses or draw:circle - * This method returns true when the width, height, x and y is given for the full circle - */ - KRITAFLAKE_EXPORT bool fixEllipse(const QString &kind, KoShapeLoadingContext &context); - - /** - * Calligra did use the bad strings "Formula.hidden" and "protected Formula.hidden" as values - * for style:cell-protect, instead of "formula-hidden" and "protected formula-hidden". - * This method fixes the bad strings to the correct ones. - */ - KRITAFLAKE_EXPORT void fixBadFormulaHiddenForStyleCellProtect(QString &value); - - /** - * Calligra used to store text:time-value with a "0-00-00T" prefix - * This method removes that prefix. - */ - KRITAFLAKE_EXPORT void fixBadDateForTextTime(QString &value); - - /** - * OpenOffice.org used to write the "rect(...)" value for fo:clip without - * separating the 4 offset values by commas. - * This method changes the string with the offset values to have commas as separators. - */ - KRITAFLAKE_EXPORT void fixClipRectOffsetValuesString(QString &offsetValuesString); - - /** - * LibreOffice used to write text:style-name attribute for table:table-template element, - * which is not a valid attribute for the element. - */ - KRITAFLAKE_EXPORT QString fixTableTemplateName(const KoXmlElement &e); - - /** - * LibreOffice used to write text:style-name attribute for - * table:first-row, table:last-row, table:first-column, - * table:last-column, table:odd-rows, table:odd-columns, - * table:body elements, which is not a valid attribute for the element. - */ - KRITAFLAKE_EXPORT QString fixTableTemplateCellStyleName(const KoXmlElement &e); - - /** - * LibreOffice used to have a bug with handling of z command in svg path. - * This resulted in broken marker path used (and copied also to Calligra). - * This methods substitutes known old marker paths with the latest (fixed) - * path variant. - */ - KRITAFLAKE_EXPORT void fixMarkerPath(QString &path); -} - -#endif /* KOODFWORKAROUND_H */ diff --git a/libs/flake/KoPathShape.cpp b/libs/flake/KoPathShape.cpp index af2f26d7e3..fa2f71c4ca 100644 --- a/libs/flake/KoPathShape.cpp +++ b/libs/flake/KoPathShape.cpp @@ -1,1666 +1,1404 @@ /* This file is part of the KDE project Copyright (C) 2006-2008, 2010-2011 Thorsten Zachmann Copyright (C) 2006-2011 Jan Hambrecht Copyright (C) 2007-2009 Thomas Zander Copyright (C) 2011 Jean-Nicolas Artaud 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 "KoPathShape.h" #include "KoPathShape_p.h" #include "KoPathSegment.h" -#include "KoOdfWorkaround.h" #include "KoPathPoint.h" #include "KoShapeStrokeModel.h" #include "KoPathShapeLoader.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoShapeShadow.h" #include "KoShapeBackground.h" #include "KoShapeContainer.h" #include "KoFilterEffectStack.h" #include "KoMarker.h" #include "KoShapeStroke.h" #include "KoInsets.h" #include #include #include #include #include #include #include #include "KisQPainterStateSaver.h" #include #include #include #include "kis_global.h" #include // for qIsNaN static bool qIsNaNPoint(const QPointF &p) { return qIsNaN(p.x()) || qIsNaN(p.y()); } KoPathShape::Private::Private() : fillRule(Qt::OddEvenFill) , autoFillMarkers(false) { } KoPathShape::Private::Private(const Private &rhs) : fillRule(rhs.fillRule) , markersNew(rhs.markersNew) , autoFillMarkers(rhs.autoFillMarkers) { } QRectF KoPathShape::Private::handleRect(const QPointF &p, qreal radius) const { return QRectF(p.x() - radius, p.y() - radius, 2*radius, 2*radius); } -void KoPathShape::Private::applyViewboxTransformation(const KoXmlElement &element) -{ - // apply viewbox transformation - const QRect viewBox = KoPathShape::loadOdfViewbox(element); - if (! viewBox.isEmpty()) { - // load the desired size - QSizeF size; - size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); - size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); - - // load the desired position - QPointF pos; - pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); - pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); - - // create matrix to transform original path data into desired size and position - QTransform viewMatrix; - viewMatrix.translate(-viewBox.left(), -viewBox.top()); - viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height()); - viewMatrix.translate(pos.x(), pos.y()); - - // transform the path data - map(viewMatrix); - } -} KoPathShape::KoPathShape() : KoTosContainer() , d(new Private) { } KoPathShape::KoPathShape(const KoPathShape &rhs) : KoTosContainer(rhs) , d(new Private(*rhs.d)) { // local data cannot be shared via QSharedData because // every path point holds a pointer to the parent shape KoSubpathList subpaths; Q_FOREACH (KoSubpath *subPath, rhs.d->subpaths) { KoSubpath *clonedSubPath = new KoSubpath(); Q_FOREACH (KoPathPoint *point, *subPath) { *clonedSubPath << new KoPathPoint(*point, this); } subpaths << clonedSubPath; } d->subpaths = subpaths; } KoPathShape::~KoPathShape() { clear(); } KoShape *KoPathShape::cloneShape() const { return new KoPathShape(*this); } -void KoPathShape::saveContourOdf(KoShapeSavingContext &context, const QSizeF &scaleFactor) const -{ - if (d->subpaths.length() <= 1) { - QTransform matrix; - matrix.scale(scaleFactor.width(), scaleFactor.height()); - QString points; - KoSubpath *subPath = d->subpaths.first(); - KoSubpath::const_iterator pointIt(subPath->constBegin()); - - KoPathPoint *currPoint= 0; - // iterate over all points - for (; pointIt != subPath->constEnd(); ++pointIt) { - currPoint = *pointIt; - - if (currPoint->activeControlPoint1() || currPoint->activeControlPoint2()) { - break; - } - const QPointF p = matrix.map(currPoint->point()); - points += QString("%1,%2 ").arg(qRound(1000*p.x())).arg(qRound(1000*p.y())); - } - - if (currPoint && !(currPoint->activeControlPoint1() || currPoint->activeControlPoint2())) { - context.xmlWriter().startElement("draw:contour-polygon"); - context.xmlWriter().addAttribute("svg:width", size().width()); - context.xmlWriter().addAttribute("svg:height", size().height()); - - const QSizeF s(size()); - QString viewBox = QString("0 0 %1 %2").arg(qRound(1000*s.width())).arg(qRound(1000*s.height())); - context.xmlWriter().addAttribute("svg:viewBox", viewBox); - - context.xmlWriter().addAttribute("draw:points", points); - - context.xmlWriter().addAttribute("draw:recreate-on-edit", "true"); - context.xmlWriter().endElement(); - - return; - } - } - - // if we get here we couldn't save as polygon - let-s try contour-path - context.xmlWriter().startElement("draw:contour-path"); - saveOdfAttributes(context, OdfViewbox); - - context.xmlWriter().addAttribute("svg:d", toString()); - context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes()); - context.xmlWriter().addAttribute("draw:recreate-on-edit", "true"); - context.xmlWriter().endElement(); -} - -void KoPathShape::saveOdf(KoShapeSavingContext & context) const -{ - context.xmlWriter().startElement("draw:path"); - saveOdfAttributes(context, OdfAllAttributes | OdfViewbox); - - context.xmlWriter().addAttribute("svg:d", toString()); - context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes()); - saveText(context); - context.xmlWriter().endElement(); -} - -bool KoPathShape::loadContourOdf(const KoXmlElement &element, KoShapeLoadingContext &, const QSizeF &scaleFactor) -{ - // first clear the path data from the default path - clear(); - - if (element.localName() == "contour-polygon") { - QString points = element.attributeNS(KoXmlNS::draw, "points").simplified(); - points.replace(',', ' '); - points.remove('\r'); - points.remove('\n'); - bool firstPoint = true; - const QStringList coordinateList = points.split(' '); - for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) { - QPointF point; - point.setX((*it).toDouble()); - ++it; - point.setY((*it).toDouble()); - if (firstPoint) { - moveTo(point); - firstPoint = false; - } else - lineTo(point); - } - close(); - } else if (element.localName() == "contour-path") { - KoPathShapeLoader loader(this); - loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); - d->loadNodeTypes(element); - } - - // apply viewbox transformation - const QRect viewBox = KoPathShape::loadOdfViewbox(element); - if (! viewBox.isEmpty()) { - QSizeF size; - size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); - size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); - - // create matrix to transform original path data into desired size and position - QTransform viewMatrix; - viewMatrix.translate(-viewBox.left(), -viewBox.top()); - viewMatrix.scale(scaleFactor.width(), scaleFactor.height()); - viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height()); - - // transform the path data - d->map(viewMatrix); - } - setTransformation(QTransform()); - - return true; -} - -bool KoPathShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) -{ - loadOdfAttributes(element, context, OdfMandatories | OdfAdditionalAttributes ); - - // first clear the path data from the default path - clear(); - - if (element.localName() == "line") { - QPointF start; - start.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", ""))); - start.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", ""))); - QPointF end; - end.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", ""))); - end.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", ""))); - moveTo(start); - lineTo(end); - } else if (element.localName() == "polyline" || element.localName() == "polygon") { - QString points = element.attributeNS(KoXmlNS::draw, "points").simplified(); - points.replace(',', ' '); - points.remove('\r'); - points.remove('\n'); - bool firstPoint = true; - const QStringList coordinateList = points.split(' '); - for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) { - QPointF point; - point.setX((*it).toDouble()); - ++it; - point.setY((*it).toDouble()); - if (firstPoint) { - moveTo(point); - firstPoint = false; - } else - lineTo(point); - } - if (element.localName() == "polygon") - close(); - } else { // path loading - KoPathShapeLoader loader(this); - loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); - d->loadNodeTypes(element); - } - - d->applyViewboxTransformation(element); - QPointF pos = normalize(); - setTransformation(QTransform()); - - if (element.hasAttributeNS(KoXmlNS::svg, "x") || element.hasAttributeNS(KoXmlNS::svg, "y")) { - pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); - pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); - } - - setPosition(pos); - - loadOdfAttributes(element, context, OdfTransformation); - - // now that the correct transformation is set up - // apply that matrix to the path geometry so that - // we don't transform the stroke - d->map(transformation()); - setTransformation(QTransform()); - normalize(); - - loadText(element, context); - - return true; -} - -QString KoPathShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const -{ - style.addProperty("svg:fill-rule", d->fillRule == Qt::OddEvenFill ? "evenodd" : "nonzero"); - - QSharedPointer lineBorder = qSharedPointerDynamicCast(stroke()); - qreal lineWidth = 0; - if (lineBorder) { - lineWidth = lineBorder->lineWidth(); - } - - Q_UNUSED(lineWidth) - - return KoTosContainer::saveStyle(style, context); -} - -void KoPathShape::loadStyle(const KoXmlElement & element, KoShapeLoadingContext &context) -{ - KoTosContainer::loadStyle(element, context); - - KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); - styleStack.setTypeProperties("graphic"); - - if (styleStack.hasProperty(KoXmlNS::svg, "fill-rule")) { - QString rule = styleStack.property(KoXmlNS::svg, "fill-rule"); - d->fillRule = (rule == "nonzero") ? Qt::WindingFill : Qt::OddEvenFill; - } else { - d->fillRule = Qt::WindingFill; -#ifndef NWORKAROUND_ODF_BUGS - KoOdfWorkaround::fixMissingFillRule(d->fillRule, context); -#endif - } - - QSharedPointer lineBorder = qSharedPointerDynamicCast(stroke()); - qreal lineWidth = 0; - if (lineBorder) { - lineWidth = lineBorder->lineWidth(); - } - - Q_UNUSED(lineWidth); -} - -QRect KoPathShape::loadOdfViewbox(const KoXmlElement & element) -{ - QRect viewbox; - - QString data = element.attributeNS(KoXmlNS::svg, QLatin1String("viewBox")); - if (! data.isEmpty()) { - data.replace(QLatin1Char(','), QLatin1Char(' ')); - const QStringList coordinates = data.simplified().split(QLatin1Char(' '), QString::SkipEmptyParts); - if (coordinates.count() == 4) { - viewbox.setRect(coordinates.at(0).toInt(), coordinates.at(1).toInt(), - coordinates.at(2).toInt(), coordinates.at(3).toInt()); - } - } - - return viewbox; -} - void KoPathShape::clear() { Q_FOREACH (KoSubpath *subpath, d->subpaths) { Q_FOREACH (KoPathPoint *point, *subpath) delete point; delete subpath; } d->subpaths.clear(); notifyPointsChanged(); } void KoPathShape::paint(QPainter &painter, KoShapePaintingContext &paintContext) const { KisQPainterStateSaver saver(&painter); QPainterPath path(outline()); path.setFillRule(d->fillRule); if (background()) { background()->paint(painter, paintContext, path); } //d->paintDebug(painter); } #ifndef NDEBUG void KoPathShape::Private::paintDebug(QPainter &painter) { KoSubpathList::const_iterator pathIt(subpaths.constBegin()); int i = 0; QPen pen(Qt::black, 0); painter.save(); painter.setPen(pen); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { ++i; KoPathPoint *point = (*it); QRectF r(point->point(), QSizeF(5, 5)); r.translate(-2.5, -2.5); QPen pen(Qt::black, 0); painter.setPen(pen); if (point->activeControlPoint1() && point->activeControlPoint2()) { QBrush b(Qt::red); painter.setBrush(b); } else if (point->activeControlPoint1()) { QBrush b(Qt::yellow); painter.setBrush(b); } else if (point->activeControlPoint2()) { QBrush b(Qt::darkYellow); painter.setBrush(b); } painter.drawEllipse(r); } } painter.restore(); debugFlake << "nop =" << i; } void KoPathShape::Private::debugPath() const { KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { debugFlake << "p:" << (*pathIt) << "," << *it << "," << (*it)->point() << "," << (*it)->properties(); } } } #endif void KoPathShape::paintPoints(KisHandlePainterHelper &handlesHelper) { KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) (*it)->paint(handlesHelper, KoPathPoint::Node); } } QRectF KoPathShape::outlineRect() const { return outline().boundingRect(); } QPainterPath KoPathShape::outline() const { QPainterPath path; for (auto subpathIt = d->subpaths.constBegin(); subpathIt != d->subpaths.constEnd(); ++subpathIt) { const KoSubpath * subpath = *subpathIt; const KoPathPoint * lastPoint = subpath->constFirst(); bool activeCP = false; for (auto pointIt = subpath->constBegin(); pointIt != subpath->constEnd(); ++pointIt) { const KoPathPoint * currPoint = *pointIt; KoPathPoint::PointProperties currProperties = currPoint->properties(); if (currPoint == subpath->constFirst()) { if (currProperties & KoPathPoint::StartSubpath) { Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.moveTo(currPoint->point()); } } else if (activeCP && currPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.cubicTo( lastPoint->controlPoint2(), currPoint->controlPoint1(), currPoint->point()); } else if (activeCP || currPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); path.quadTo( activeCP ? lastPoint->controlPoint2() : currPoint->controlPoint1(), currPoint->point()); } else { Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.lineTo(currPoint->point()); } if (currProperties & KoPathPoint::CloseSubpath && currProperties & KoPathPoint::StopSubpath) { // add curve when there is a curve on the way to the first point KoPathPoint * firstPoint = subpath->first(); Q_ASSERT(!qIsNaNPoint(firstPoint->point())); if (currPoint->activeControlPoint2() && firstPoint->activeControlPoint1()) { path.cubicTo( currPoint->controlPoint2(), firstPoint->controlPoint1(), firstPoint->point()); } else if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(currPoint->point())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); path.quadTo( currPoint->activeControlPoint2() ? currPoint->controlPoint2() : firstPoint->controlPoint1(), firstPoint->point()); } path.closeSubpath(); } if (currPoint->activeControlPoint2()) { activeCP = true; } else { activeCP = false; } lastPoint = currPoint; } } return path; } QRectF KoPathShape::boundingRect() const { const QTransform transform = absoluteTransformation(); /** * First we approximate the insets of the stroke by rendering a fat bezier curve * with width set to the maximum inset of miters and markers. The are swept by this * curve will be a good approximation of the real curve bounding rect. */ qreal outlineSweepWidth = 0; const QSharedPointer lineBorder = qSharedPointerDynamicCast(stroke()); if (lineBorder) { outlineSweepWidth = lineBorder->lineWidth(); } if (stroke()) { KoInsets inset; stroke()->strokeInsets(this, inset); const qreal maxInset = std::max({inset.left, inset.top, inset.right, inset.bottom}); // insets extend outside the shape, but width extends both inside and outside, // so we should multiply insets by 2.0 outlineSweepWidth = std::max({outlineSweepWidth, 2.0 * maxInset, 2.0 * stroke()->strokeMaxMarkersInset(this)}); } /// NOTE: stroking the entire shape might be too expensive, so try to /// estimate the bounds using insets only... #if 0 QPen pen(Qt::black, outlineSweepWidth); // select round joins and caps to ensure it sweeps exactly // 'outlineSweepWidth' pixels in every possible pen.setJoinStyle(Qt::RoundJoin); pen.setCapStyle(Qt::RoundCap); QRectF bb = transform.map(pathStroke(pen)).boundingRect(); #endif QRectF bb = transform.mapRect(kisGrowRect(outline().boundingRect(), outlineSweepWidth)); if (shadow()) { KoInsets insets; shadow()->insets(insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (filterEffectStack()) { QRectF clipRect = filterEffectStack()->clipRectForBoundingRect(QRectF(QPointF(), size())); bb |= transform.mapRect(clipRect); } return bb; } QSizeF KoPathShape::size() const { // don't call boundingRect here as it uses absoluteTransformation // which itself uses size() -> leads to infinite recursion return outlineRect().size(); } void KoPathShape::setSize(const QSizeF &newSize) { QTransform matrix(resizeMatrix(newSize)); KoShape::setSize(newSize); d->map(matrix); } QTransform KoPathShape::resizeMatrix(const QSizeF & newSize) const { QSizeF oldSize = size(); if (oldSize.width() == 0.0) { oldSize.setWidth(0.000001); } if (oldSize.height() == 0.0) { oldSize.setHeight(0.000001); } QSizeF sizeNew(newSize); if (sizeNew.width() == 0.0) { sizeNew.setWidth(0.000001); } if (sizeNew.height() == 0.0) { sizeNew.setHeight(0.000001); } return QTransform(sizeNew.width() / oldSize.width(), 0, 0, sizeNew.height() / oldSize.height(), 0, 0); } KoPathPoint * KoPathShape::moveTo(const QPointF &p) { KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StartSubpath | KoPathPoint::StopSubpath); KoSubpath * path = new KoSubpath; path->push_back(point); d->subpaths.push_back(path); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::lineTo(const QPointF &p) { if (d->subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); KoPathPoint * lastPoint = d->subpaths.last()->last(); updateLastPriv(&lastPoint); d->subpaths.last()->push_back(point); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::curveTo(const QPointF &c1, const QPointF &c2, const QPointF &p) { if (d->subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * lastPoint = d->subpaths.last()->last(); updateLastPriv(&lastPoint); lastPoint->setControlPoint2(c1); KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); point->setControlPoint1(c2); d->subpaths.last()->push_back(point); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::curveTo(const QPointF &c, const QPointF &p) { if (d->subpaths.empty()) moveTo(QPointF(0, 0)); KoPathPoint * lastPoint = d->subpaths.last()->last(); updateLastPriv(&lastPoint); lastPoint->setControlPoint2(c); KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); d->subpaths.last()->push_back(point); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::arcTo(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle) { if (d->subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * lastPoint = d->subpaths.last()->last(); if (lastPoint->properties() & KoPathPoint::CloseSubpath) { lastPoint = d->subpaths.last()->first(); } QPointF startpoint(lastPoint->point()); KoPathPoint * newEndPoint = lastPoint; QPointF curvePoints[12]; int pointCnt = arcToCurve(rx, ry, startAngle, sweepAngle, startpoint, curvePoints); for (int i = 0; i < pointCnt; i += 3) { newEndPoint = curveTo(curvePoints[i], curvePoints[i+1], curvePoints[i+2]); } return newEndPoint; } int KoPathShape::arcToCurve(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle, const QPointF & offset, QPointF * curvePoints) const { int pointCnt = 0; // check Parameters if (sweepAngle == 0.0) return pointCnt; sweepAngle = qBound(-360.0, sweepAngle, 360.0); if (rx == 0 || ry == 0) { //TODO } // split angles bigger than 90° so that it gives a good approximation to the circle qreal parts = ceil(qAbs(sweepAngle / 90.0)); qreal sa_rad = startAngle * M_PI / 180.0; qreal partangle = sweepAngle / parts; qreal endangle = startAngle + partangle; qreal se_rad = endangle * M_PI / 180.0; qreal sinsa = sin(sa_rad); qreal cossa = cos(sa_rad); qreal kappa = 4.0 / 3.0 * tan((se_rad - sa_rad) / 4); // startpoint is at the last point is the path but when it is closed // it is at the first point QPointF startpoint(offset); //center berechnen QPointF center(startpoint - QPointF(cossa * rx, -sinsa * ry)); //debugFlake <<"kappa" << kappa <<"parts" << parts; for (int part = 0; part < parts; ++part) { // start tangent curvePoints[pointCnt++] = QPointF(startpoint - QPointF(sinsa * rx * kappa, cossa * ry * kappa)); qreal sinse = sin(se_rad); qreal cosse = cos(se_rad); // end point QPointF endpoint(center + QPointF(cosse * rx, -sinse * ry)); // end tangent curvePoints[pointCnt++] = QPointF(endpoint - QPointF(-sinse * rx * kappa, -cosse * ry * kappa)); curvePoints[pointCnt++] = endpoint; // set the endpoint as next start point startpoint = endpoint; sinsa = sinse; cossa = cosse; endangle += partangle; se_rad = endangle * M_PI / 180.0; } return pointCnt; } void KoPathShape::close() { if (d->subpaths.empty()) { return; } closeSubpathPriv(d->subpaths.last()); } void KoPathShape::closeMerge() { if (d->subpaths.empty()) { return; } closeMergeSubpathPriv(d->subpaths.last()); } QPointF KoPathShape::normalize() { QPointF tl(outline().boundingRect().topLeft()); QTransform matrix; matrix.translate(-tl.x(), -tl.y()); d->map(matrix); // keep the top left point of the object applyTransformation(matrix.inverted()); shapeChangedPriv(ContentChanged); return tl; } void KoPathShape::Private::map(const QTransform &matrix) { KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { // It's possible there are null points in the map... if (*it) { (*it)->map(matrix); } } } } void KoPathShape::updateLastPriv(KoPathPoint **lastPoint) { // check if we are about to add a new point to a closed subpath if ((*lastPoint)->properties() & KoPathPoint::StopSubpath && (*lastPoint)->properties() & KoPathPoint::CloseSubpath) { // get the first point of the subpath KoPathPoint *subpathStart = d->subpaths.last()->first(); // clone the first point of the subpath... KoPathPoint * newLastPoint = new KoPathPoint(*subpathStart, this); // ... and make it a normal point newLastPoint->setProperties(KoPathPoint::Normal); // now start a new subpath with the cloned start point KoSubpath *path = new KoSubpath; path->push_back(newLastPoint); d->subpaths.push_back(path); *lastPoint = newLastPoint; } else { // the subpath was not closed so the formerly last point // of the subpath is no end point anymore (*lastPoint)->unsetProperty(KoPathPoint::StopSubpath); } (*lastPoint)->unsetProperty(KoPathPoint::CloseSubpath); } QList KoPathShape::pointsAt(const QRectF &r) const { QList result; KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { if (r.contains((*it)->point())) result.append(*it); else if ((*it)->activeControlPoint1() && r.contains((*it)->controlPoint1())) result.append(*it); else if ((*it)->activeControlPoint2() && r.contains((*it)->controlPoint2())) result.append(*it); } } return result; } QList KoPathShape::segmentsAt(const QRectF &r) const { QList segments; int subpathCount = d->subpaths.count(); for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) { KoSubpath * subpath = d->subpaths[subpathIndex]; int pointCount = subpath->count(); bool subpathClosed = isClosedSubpath(subpathIndex); for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex) { if (pointIndex == (pointCount - 1) && ! subpathClosed) break; KoPathSegment s(subpath->at(pointIndex), subpath->at((pointIndex + 1) % pointCount)); QRectF controlRect = s.controlPointRect(); if (! r.intersects(controlRect) && ! controlRect.contains(r)) continue; QRectF bound = s.boundingRect(); if (! r.intersects(bound) && ! bound.contains(r)) continue; segments.append(s); } } return segments; } KoPathPointIndex KoPathShape::pathPointIndex(const KoPathPoint *point) const { for (int subpathIndex = 0; subpathIndex < d->subpaths.size(); ++subpathIndex) { KoSubpath * subpath = d->subpaths.at(subpathIndex); for (int pointPos = 0; pointPos < subpath->size(); ++pointPos) { if (subpath->at(pointPos) == point) { return KoPathPointIndex(subpathIndex, pointPos); } } } return KoPathPointIndex(-1, -1); } KoPathPoint * KoPathShape::pointByIndex(const KoPathPointIndex &pointIndex) const { KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) return 0; return subpath->at(pointIndex.second); } KoPathSegment KoPathShape::segmentByIndex(const KoPathPointIndex &pointIndex) const { KoPathSegment segment(0, 0); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath != 0 && pointIndex.second >= 0 && pointIndex.second < subpath->size()) { KoPathPoint * point = subpath->at(pointIndex.second); int index = pointIndex.second; // check if we have a (closing) segment starting from the last point if ((index == subpath->size() - 1) && point->properties() & KoPathPoint::CloseSubpath) index = 0; else ++index; if (index < subpath->size()) { segment = KoPathSegment(point, subpath->at(index)); } } return segment; } int KoPathShape::pointCount() const { int i = 0; KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { i += (*pathIt)->size(); } return i; } int KoPathShape::subpathCount() const { return d->subpaths.count(); } int KoPathShape::subpathPointCount(int subpathIndex) const { KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return -1; return subpath->size(); } bool KoPathShape::isClosedSubpath(int subpathIndex) const { KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return false; const bool firstClosed = subpath->first()->properties() & KoPathPoint::CloseSubpath; const bool lastClosed = subpath->last()->properties() & KoPathPoint::CloseSubpath; return firstClosed && lastClosed; } bool KoPathShape::insertPoint(KoPathPoint* point, const KoPathPointIndex &pointIndex) { KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second > subpath->size()) return false; KoPathPoint::PointProperties properties = point->properties(); properties &= ~KoPathPoint::StartSubpath; properties &= ~KoPathPoint::StopSubpath; properties &= ~KoPathPoint::CloseSubpath; // check if new point starts subpath if (pointIndex.second == 0) { properties |= KoPathPoint::StartSubpath; // subpath was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep the path closed properties |= KoPathPoint::CloseSubpath; } // old first point does not start the subpath anymore subpath->first()->unsetProperty(KoPathPoint::StartSubpath); } // check if new point stops subpath else if (pointIndex.second == subpath->size()) { properties |= KoPathPoint::StopSubpath; // subpath was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep the path closed properties = properties | KoPathPoint::CloseSubpath; } // old last point does not end subpath anymore subpath->last()->unsetProperty(KoPathPoint::StopSubpath); } point->setProperties(properties); point->setParent(this); subpath->insert(pointIndex.second , point); notifyPointsChanged(); return true; } KoPathPoint * KoPathShape::removePoint(const KoPathPointIndex &pointIndex) { KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) return 0; KoPathPoint * point = subpath->takeAt(pointIndex.second); point->setParent(0); //don't do anything (not even crash), if there was only one point if (pointCount()==0) { return point; } // check if we removed the first point else if (pointIndex.second == 0) { // first point removed, set new StartSubpath subpath->first()->setProperty(KoPathPoint::StartSubpath); // check if path was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep path closed subpath->first()->setProperty(KoPathPoint::CloseSubpath); } } // check if we removed the last point else if (pointIndex.second == subpath->size()) { // use size as point is already removed // last point removed, set new StopSubpath subpath->last()->setProperty(KoPathPoint::StopSubpath); // check if path was closed if (point->properties() & KoPathPoint::CloseSubpath) { // keep path closed subpath->last()->setProperty(KoPathPoint::CloseSubpath); } } notifyPointsChanged(); return point; } bool KoPathShape::breakAfter(const KoPathPointIndex &pointIndex) { KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second > subpath->size() - 2 || isClosedSubpath(pointIndex.first)) return false; KoSubpath * newSubpath = new KoSubpath; int size = subpath->size(); for (int i = pointIndex.second + 1; i < size; ++i) { newSubpath->append(subpath->takeAt(pointIndex.second + 1)); } // now make the first point of the new subpath a starting node newSubpath->first()->setProperty(KoPathPoint::StartSubpath); // the last point of the old subpath is now an ending node subpath->last()->setProperty(KoPathPoint::StopSubpath); // insert the new subpath after the broken one d->subpaths.insert(pointIndex.first + 1, newSubpath); notifyPointsChanged(); return true; } bool KoPathShape::join(int subpathIndex) { KoSubpath *subpath = d->subPath(subpathIndex); KoSubpath *nextSubpath = d->subPath(subpathIndex + 1); if (!subpath || !nextSubpath || isClosedSubpath(subpathIndex) || isClosedSubpath(subpathIndex+1)) return false; // the last point of the subpath does not end the subpath anymore subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // the first point of the next subpath does not start a subpath anymore nextSubpath->first()->unsetProperty(KoPathPoint::StartSubpath); // append the second subpath to the first Q_FOREACH (KoPathPoint * p, *nextSubpath) subpath->append(p); // remove the nextSubpath from path d->subpaths.removeAt(subpathIndex + 1); // delete it as it is no longer possible to use it delete nextSubpath; notifyPointsChanged(); return true; } bool KoPathShape::moveSubpath(int oldSubpathIndex, int newSubpathIndex) { KoSubpath *subpath = d->subPath(oldSubpathIndex); if (subpath == 0 || newSubpathIndex >= d->subpaths.size()) return false; if (oldSubpathIndex == newSubpathIndex) return true; d->subpaths.removeAt(oldSubpathIndex); d->subpaths.insert(newSubpathIndex, subpath); notifyPointsChanged(); return true; } KoPathPointIndex KoPathShape::openSubpath(const KoPathPointIndex &pointIndex) { KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() || !isClosedSubpath(pointIndex.first)) return KoPathPointIndex(-1, -1); KoPathPoint * oldStartPoint = subpath->first(); // the old starting node no longer starts the subpath oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); // the old end node no longer closes the subpath subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // reorder the subpath for (int i = 0; i < pointIndex.second; ++i) { subpath->append(subpath->takeFirst()); } // make the first point a start node subpath->first()->setProperty(KoPathPoint::StartSubpath); // make the last point an end node subpath->last()->setProperty(KoPathPoint::StopSubpath); notifyPointsChanged(); return pathPointIndex(oldStartPoint); } KoPathPointIndex KoPathShape::closeSubpath(const KoPathPointIndex &pointIndex) { KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() || isClosedSubpath(pointIndex.first)) return KoPathPointIndex(-1, -1); KoPathPoint * oldStartPoint = subpath->first(); // the old starting node no longer starts the subpath oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); // the old end node no longer ends the subpath subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // reorder the subpath for (int i = 0; i < pointIndex.second; ++i) { subpath->append(subpath->takeFirst()); } subpath->first()->setProperty(KoPathPoint::StartSubpath); subpath->last()->setProperty(KoPathPoint::StopSubpath); closeSubpathPriv(subpath); notifyPointsChanged(); return pathPointIndex(oldStartPoint); } bool KoPathShape::reverseSubpath(int subpathIndex) { KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return false; int size = subpath->size(); for (int i = 0; i < size; ++i) { KoPathPoint *p = subpath->takeAt(i); p->reverse(); subpath->prepend(p); } // adjust the position dependent properties KoPathPoint *first = subpath->first(); KoPathPoint *last = subpath->last(); KoPathPoint::PointProperties firstProps = first->properties(); KoPathPoint::PointProperties lastProps = last->properties(); firstProps |= KoPathPoint::StartSubpath; firstProps &= ~KoPathPoint::StopSubpath; lastProps |= KoPathPoint::StopSubpath; lastProps &= ~KoPathPoint::StartSubpath; if (firstProps & KoPathPoint::CloseSubpath) { firstProps |= KoPathPoint::CloseSubpath; lastProps |= KoPathPoint::CloseSubpath; } first->setProperties(firstProps); last->setProperties(lastProps); notifyPointsChanged(); return true; } KoSubpath * KoPathShape::removeSubpath(int subpathIndex) { KoSubpath *subpath = d->subPath(subpathIndex); if (subpath != 0) { Q_FOREACH (KoPathPoint* point, *subpath) { point->setParent(this); } d->subpaths.removeAt(subpathIndex); } notifyPointsChanged(); return subpath; } bool KoPathShape::addSubpath(KoSubpath * subpath, int subpathIndex) { if (subpathIndex < 0 || subpathIndex > d->subpaths.size()) return false; Q_FOREACH (KoPathPoint* point, *subpath) { point->setParent(this); } d->subpaths.insert(subpathIndex, subpath); notifyPointsChanged(); return true; } int KoPathShape::combine(KoPathShape *path) { int insertSegmentPosition = -1; if (!path) return insertSegmentPosition; QTransform pathMatrix = path->absoluteTransformation(); QTransform myMatrix = absoluteTransformation().inverted(); Q_FOREACH (KoSubpath* subpath, path->d->subpaths) { KoSubpath *newSubpath = new KoSubpath(); Q_FOREACH (KoPathPoint* point, *subpath) { KoPathPoint *newPoint = new KoPathPoint(*point, this); newPoint->map(pathMatrix); newPoint->map(myMatrix); newSubpath->append(newPoint); } d->subpaths.append(newSubpath); if (insertSegmentPosition < 0) { insertSegmentPosition = d->subpaths.size() - 1; } } normalize(); notifyPointsChanged(); return insertSegmentPosition; } bool KoPathShape::separate(QList & separatedPaths) { if (! d->subpaths.size()) return false; QTransform myMatrix = absoluteTransformation(); Q_FOREACH (KoSubpath* subpath, d->subpaths) { KoPathShape *shape = new KoPathShape(); shape->setStroke(stroke()); shape->setBackground(background()); shape->setShapeId(shapeId()); shape->setZIndex(zIndex()); KoSubpath *newSubpath = new KoSubpath(); Q_FOREACH (KoPathPoint* point, *subpath) { KoPathPoint *newPoint = new KoPathPoint(*point, shape); newPoint->map(myMatrix); newSubpath->append(newPoint); } shape->d->subpaths.append(newSubpath); shape->normalize(); // NOTE: shape cannot have any listeners yet, so no notification about // points modification is needed separatedPaths.append(shape); } return true; } void KoPathShape::closeSubpathPriv(KoSubpath *subpath) { if (! subpath) return; subpath->last()->setProperty(KoPathPoint::CloseSubpath); subpath->first()->setProperty(KoPathPoint::CloseSubpath); notifyPointsChanged(); } void KoPathShape::closeMergeSubpathPriv(KoSubpath *subpath) { if (! subpath || subpath->size() < 2) return; KoPathPoint * lastPoint = subpath->last(); KoPathPoint * firstPoint = subpath->first(); // check if first and last points are coincident if (lastPoint->point() == firstPoint->point()) { // we are removing the current last point and // reuse its first control point if active firstPoint->setProperty(KoPathPoint::StartSubpath); firstPoint->setProperty(KoPathPoint::CloseSubpath); if (lastPoint->activeControlPoint1()) firstPoint->setControlPoint1(lastPoint->controlPoint1()); // remove last point delete subpath->takeLast(); // the new last point closes the subpath now lastPoint = subpath->last(); lastPoint->setProperty(KoPathPoint::StopSubpath); lastPoint->setProperty(KoPathPoint::CloseSubpath); notifyPointsChanged(); } else { closeSubpathPriv(subpath); } } const KoSubpathList &KoPathShape::subpaths() const { return d->subpaths; } KoSubpathList &KoPathShape::subpaths() { return d->subpaths; } void KoPathShape::map(const QTransform &matrix) { return d->map(matrix); } KoSubpath *KoPathShape::Private::subPath(int subpathIndex) const { if (subpathIndex < 0 || subpathIndex >= subpaths.size()) return 0; return subpaths.at(subpathIndex); } QString KoPathShape::pathShapeId() const { return KoPathShapeId; } QString KoPathShape::toString(const QTransform &matrix) const { QString pathString; // iterate over all subpaths KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator pointIt((*pathIt)->constBegin()); // keep a pointer to the first point of the subpath KoPathPoint *firstPoint(*pointIt); // keep a pointer to the previous point of the subpath KoPathPoint *lastPoint = firstPoint; // keep track if the previous point has an active control point 2 bool activeControlPoint2 = false; // iterate over all points of the current subpath for (; pointIt != (*pathIt)->constEnd(); ++pointIt) { KoPathPoint *currPoint(*pointIt); if (!currPoint) { qWarning() << "Found a zero point in the shape's path!"; continue; } // first point of subpath ? if (currPoint == firstPoint) { // are we starting a subpath ? if (currPoint->properties() & KoPathPoint::StartSubpath) { const QPointF p = matrix.map(currPoint->point()); pathString += QString("M%1 %2").arg(p.x()).arg(p.y()); } } // end point of curve segment ? else if (activeControlPoint2 || currPoint->activeControlPoint1()) { // check if we have a cubic or quadratic curve const bool isCubic = activeControlPoint2 && currPoint->activeControlPoint1(); KoPathSegment cubicSeg = isCubic ? KoPathSegment(lastPoint, currPoint) : KoPathSegment(lastPoint, currPoint).toCubic(); if (cubicSeg.first() && cubicSeg.second()) { const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); const QPointF p = matrix.map(cubicSeg.second()->point()); pathString += QString("C%1 %2 %3 %4 %5 %6") .arg(cp1.x()).arg(cp1.y()) .arg(cp2.x()).arg(cp2.y()) .arg(p.x()).arg(p.y()); } } // end point of line segment! else { const QPointF p = matrix.map(currPoint->point()); pathString += QString("L%1 %2").arg(p.x()).arg(p.y()); } // last point closes subpath ? if (currPoint->properties() & KoPathPoint::StopSubpath && currPoint->properties() & KoPathPoint::CloseSubpath) { // add curve when there is a curve on the way to the first point if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { // check if we have a cubic or quadratic curve const bool isCubic = currPoint->activeControlPoint2() && firstPoint->activeControlPoint1(); KoPathSegment cubicSeg = isCubic ? KoPathSegment(currPoint, firstPoint) : KoPathSegment(currPoint, firstPoint).toCubic(); if (cubicSeg.first() && cubicSeg.second()) { const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); const QPointF p = matrix.map(cubicSeg.second()->point()); pathString += QString("C%1 %2 %3 %4 %5 %6") .arg(cp1.x()).arg(cp1.y()) .arg(cp2.x()).arg(cp2.y()) .arg(p.x()).arg(p.y()); } } pathString += QString("Z"); } activeControlPoint2 = currPoint->activeControlPoint2(); lastPoint = currPoint; } } return pathString; } char nodeType(const KoPathPoint * point) { if (point->properties() & KoPathPoint::IsSmooth) { return 's'; } else if (point->properties() & KoPathPoint::IsSymmetric) { return 'z'; } else { return 'c'; } } QString KoPathShape::Private::nodeTypes() const { QString types; KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { if (it == (*pathIt)->constBegin()) { types.append('c'); } else { types.append(nodeType(*it)); } if ((*it)->properties() & KoPathPoint::StopSubpath && (*it)->properties() & KoPathPoint::CloseSubpath) { KoPathPoint * firstPoint = (*pathIt)->first(); types.append(nodeType(firstPoint)); } } } return types; } void updateNodeType(KoPathPoint * point, const QChar & nodeType) { if (nodeType == 's') { point->setProperty(KoPathPoint::IsSmooth); } else if (nodeType == 'z') { point->setProperty(KoPathPoint::IsSymmetric); } } void KoPathShape::Private::loadNodeTypes(const KoXmlElement &element) { if (element.hasAttributeNS(KoXmlNS::calligra, "nodeTypes")) { QString nodeTypes = element.attributeNS(KoXmlNS::calligra, "nodeTypes"); QString::const_iterator nIt(nodeTypes.constBegin()); KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it, nIt++) { // be sure not to crash if there are not enough nodes in nodeTypes if (nIt == nodeTypes.constEnd()) { warnFlake << "not enough nodes in calligra:nodeTypes"; return; } // the first node is always of type 'c' if (it != (*pathIt)->constBegin()) { updateNodeType(*it, *nIt); } if ((*it)->properties() & KoPathPoint::StopSubpath && (*it)->properties() & KoPathPoint::CloseSubpath) { ++nIt; updateNodeType((*pathIt)->first(), *nIt); } } } } } Qt::FillRule KoPathShape::fillRule() const { return d->fillRule; } void KoPathShape::setFillRule(Qt::FillRule fillRule) { d->fillRule = fillRule; } KoPathShape * KoPathShape::createShapeFromPainterPath(const QPainterPath &path) { KoPathShape * shape = new KoPathShape(); int elementCount = path.elementCount(); for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = path.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: shape->moveTo(QPointF(element.x, element.y)); break; case QPainterPath::LineToElement: shape->lineTo(QPointF(element.x, element.y)); break; case QPainterPath::CurveToElement: shape->curveTo(QPointF(element.x, element.y), QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y), QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y)); break; default: continue; } } shape->setShapeId(KoPathShapeId); //shape->normalize(); return shape; } bool KoPathShape::hitTest(const QPointF &position) const { if (parent() && parent()->isClipped(this) && ! parent()->hitTest(position)) return false; QPointF point = absoluteTransformation().inverted().map(position); const QPainterPath outlinePath = outline(); if (stroke()) { KoInsets insets; stroke()->strokeInsets(this, insets); QRectF roi(QPointF(-insets.left, -insets.top), QPointF(insets.right, insets.bottom)); roi.moveCenter(point); if (outlinePath.intersects(roi) || outlinePath.contains(roi)) return true; } else { if (outlinePath.contains(point)) return true; } // if there is no shadow we can as well just leave if (! 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().inverted().map(position - shadow()->offset()); return outlinePath.contains(point); } void KoPathShape::setMarker(KoMarker *marker, KoFlake::MarkerPosition pos) { if (!marker && d->markersNew.contains(pos)) { d->markersNew.remove(pos); } else { d->markersNew[pos] = marker; } } KoMarker *KoPathShape::marker(KoFlake::MarkerPosition pos) const { return d->markersNew[pos].data(); } bool KoPathShape::hasMarkers() const { return !d->markersNew.isEmpty(); } bool KoPathShape::autoFillMarkers() const { return d->autoFillMarkers; } void KoPathShape::setAutoFillMarkers(bool value) { d->autoFillMarkers = value; } void KoPathShape::recommendPointSelectionChange(const QList &newSelection) { Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners()) { PointSelectionChangeListener *pointListener = dynamic_cast(listener); if (pointListener) { pointListener->recommendPointSelectionChange(this, newSelection); } } } void KoPathShape::notifyPointsChanged() { Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners()) { PointSelectionChangeListener *pointListener = dynamic_cast(listener); if (pointListener) { pointListener->notifyPathPointsChanged(this); } } } QPainterPath KoPathShape::pathStroke(const QPen &pen) const { if (d->subpaths.isEmpty()) { return QPainterPath(); } QPainterPath pathOutline; QPainterPathStroker stroker; stroker.setWidth(0); stroker.setJoinStyle(Qt::MiterJoin); stroker.setWidth(pen.widthF()); stroker.setJoinStyle(pen.joinStyle()); stroker.setMiterLimit(pen.miterLimit()); stroker.setCapStyle(pen.capStyle()); stroker.setDashOffset(pen.dashOffset()); stroker.setDashPattern(pen.dashPattern()); QPainterPath path = stroker.createStroke(outline()); pathOutline.addPath(path); pathOutline.setFillRule(Qt::WindingFill); return pathOutline; } void KoPathShape::PointSelectionChangeListener::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape) { Q_UNUSED(type); Q_UNUSED(shape); } diff --git a/libs/flake/KoPathShape.h b/libs/flake/KoPathShape.h index 44b7fb994b..fee23212a8 100644 --- a/libs/flake/KoPathShape.h +++ b/libs/flake/KoPathShape.h @@ -1,534 +1,513 @@ /* This file is part of the KDE project Copyright (C) 2006, 2011 Thorsten Zachmann Copyright (C) 2007,2009 Thomas Zander Copyright (C) 2006-2008 Jan Hambrecht Copyright (C) 2011 Jean-Nicolas Artaud 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 KOPATHSHAPE_H #define KOPATHSHAPE_H #include "kritaflake_export.h" #include #include #include "KoTosContainer.h" #define KoPathShapeId "KoPathShape" class KoPathSegment; class KoPathPoint; class KoPathShapePrivate; class KoMarker; class KisHandlePainterHelper; typedef QPair KoPathPointIndex; /// a KoSubpath contains a path from a moveTo until a close or a new moveTo typedef QList KoSubpath; typedef QList KoSubpathList; /// The position of a path point within a path shape /** * @brief This is the base for all graphical objects. * * All graphical objects are based on this object e.g. lines, rectangulars, pies * and so on. * * The KoPathShape uses KoPathPoint's to describe the path of the shape. * * Here a short example: * 3 points connected by a curveTo's described by the following svg: * M 100,200 C 100,100 250,100 250,200 C 250,200 400,300 400,200. * * This will be stored in 3 KoPathPoint's as * The first point contains in * point 100,200 * controlPoint2 100,100 * The second point contains in * point 250,200 * controlPoint1 250,100 * controlPoint2 250,300 * The third point contains in * point 400,300 * controlPoint1 400,200 * * Not the segments are stored but the points. Out of the points the segments are * generated. See the outline method. The reason for storing it like that is that * it is the points that are modified by the user and not the segments. */ class KRITAFLAKE_EXPORT KoPathShape : public KoTosContainer { public: /** * @brief constructor */ KoPathShape(); /** * @brief */ ~KoPathShape() override; KoShape *cloneShape() const override; /// reimplemented void paint(QPainter &painter, KoShapePaintingContext &paintContext) const override; virtual void paintPoints(KisHandlePainterHelper &handlesHelper); /// reimplemented QRectF outlineRect() const override; /// reimplemented QPainterPath outline() const override; /// reimplemented QRectF boundingRect() const override; /// reimplemented QSizeF size() const override; QPainterPath pathStroke(const QPen &pen) const; /** * Resize the shape * * This makes sure that the pathshape will not be resized to 0 if the new size * is null as that makes it impossible to undo the change. * * All functions that overwrite this function should also use the resizeMatrix * function to get and use the same data in resizing. * * @see resizeMatrix() */ void setSize(const QSizeF &size) override; + /// reimplemented bool hitTest(const QPointF &position) const override; - // reimplemented - void saveOdf(KoShapeSavingContext &context) const override; - // reimplemented - bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override; - - // basically the same as loadOdf but adapted to the contour cases - // tag needs to be either contour-polygon or contour-path from another shape - bool loadContourOdf(const KoXmlElement & element, KoShapeLoadingContext &context, const QSizeF &scaleFactor); - - /** basically the equivalent saveOdf but adapted to the contour cases - * @param context the saving context - * @param originalSize the original size of the unscaled image. - */ - void saveContourOdf(KoShapeSavingContext &context, const QSizeF &originalSize) const; - /// Removes all subpaths and their points from the path void clear(); /** * @brief Starts a new Subpath * * Moves the pen to p and starts a new subpath. * * @return the newly created point */ KoPathPoint *moveTo(const QPointF &p); /** * @brief Adds a new line segment * * Adds a straight line between the last point and the given point p. * * @return the newly created point */ KoPathPoint *lineTo(const QPointF &p); /** * @brief Adds a new cubic Bezier curve segment. * * Adds a cubic Bezier curve between the last point and the given point p, * using the control points specified by c1 and c2. * * @param c1 control point1 * @param c2 control point2 * @param p the endpoint of this curve segment * * @return The newly created point */ KoPathPoint *curveTo(const QPointF &c1, const QPointF &c2, const QPointF &p); /** * @brief Adds a new quadratic Bezier curve segment. * * Adds a quadratic Bezier curve between the last point and the given point p, * using the control point specified by c. * * @param c control point * @param p the endpoint of this curve segment * * @return The newly created point */ KoPathPoint *curveTo(const QPointF &c, const QPointF &p); /** * @brief Add an arc. * * Adds an arc starting at the current point. The arc will be converted to bezier curves. * * @param rx x radius of the ellipse * @param ry y radius of the ellipse * @param startAngle the angle where the arc will be started * @param sweepAngle the length of the angle * TODO add param to have angle of the ellipse * * @return The newly created point */ KoPathPoint *arcTo(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle); /** * @brief Closes the current subpath */ void close(); /** * @brief Closes the current subpath * * It tries to merge the last and first point of the subpath * to one point and then closes the subpath. If merging is not * possible as the two point are to far from each other a close * will be done. * TODO define a maximum distance between the two points until this is working */ void closeMerge(); /** * @brief Normalizes the path data. * * The path points are transformed so that the top-left corner * of the bounding rect is at (0,0). * This should be called after adding points to the path or changing * positions of path points. * @return the offset by which the points are moved in shape coordinates. */ virtual QPointF normalize(); /** * @brief Returns the path points within the given rectangle. * @param rect the rectangle the requested points are in * @return list of points within the rectangle */ QList pointsAt(const QRectF &rect) const; /** * @brief Returns the list of path segments within the given rectangle. * @param rect the rectangle the requested segments are in * @return list of segments within the rectangle */ QList segmentsAt(const QRectF &rect) const; /** * @brief Returns the path point index of a given path point * * @param point the point for which you want to get the index * @return path point index of the point if it exists * otherwise KoPathPointIndex( -1, -1 ) */ KoPathPointIndex pathPointIndex(const KoPathPoint *point) const; /** * @brief Returns the path point specified by a path point index * * @param pointIndex index of the point to get * * @return KoPathPoint on success, 0 otherwise e.g. out of bounds */ KoPathPoint *pointByIndex(const KoPathPointIndex &pointIndex) const; /** * @brief Returns the segment specified by a path point index * * A segment is defined by the point index of the first point in the segment. * A segment contains the defined point and its following point. If the subpath is * closed and the and the pointIndex point to the last point in the subpath, the * following point is the first point in the subpath. * * @param pointIndex index of the first point of the segment * * @return Segment containing both points of the segment or KoPathSegment( 0, 0 ) on error e.g. out of bounds */ KoPathSegment segmentByIndex(const KoPathPointIndex &pointIndex) const; /** * @brief Returns the number of points in the path * * @return The number of points in the path */ int pointCount() const; /** * @brief Returns the number of subpaths in the path * * @return The number of subpaths in the path */ int subpathCount() const; /** * @brief Returns the number of points in a subpath * * @return The number of points in the subpath or -1 if subpath out of bounds */ int subpathPointCount(int subpathIndex) const; /** * @brief Checks if a subpath is closed * * @param subpathIndex index of the subpath to check * * @return true when the subpath is closed, false otherwise */ bool isClosedSubpath(int subpathIndex) const; /** * @brief Inserts a new point into the given subpath at the specified position * * This method keeps the subpath closed if it is closed, and open when it was * open. So it can change the properties of the point inserted. * You might need to update the point before/after to get the desired result * e.g. when you insert the point into a curve. * * @param point to insert * @param pointIndex index at which the point should be inserted * * @return true on success, * false when pointIndex is out of bounds */ bool insertPoint(KoPathPoint *point, const KoPathPointIndex &pointIndex); /** * @brief Removes a point from the path. * * Note that the ownership of the point will pass to the caller. * * @param pointIndex index of the point which should be removed * * @return The removed point on success, * otherwise 0 */ KoPathPoint *removePoint(const KoPathPointIndex &pointIndex); /** * @brief Breaks the path after the point index * * The new subpath will be behind the one that was broken. The segment between * the given point and the one behind will be removed. If you want to split at * one point insert first a copy of the point behind it. * This does not work when the subpath is closed. Use openSubpath for this. * It does not break at the last position of a subpath or if there is only one * point in the subpath. * * @param pointIndex index of the point after which the path should be broken * * @return true if the subpath was broken, otherwise false */ bool breakAfter(const KoPathPointIndex &pointIndex); /** * @brief Joins the given subpath with the following one * * Joins the given subpath with the following one by inserting a segment between * the two subpaths. * This does nothing if the specified subpath is the last subpath * or one of both subpaths is closed. * * @param subpathIndex index of the subpath being joined with the following subpath * * @return true if the subpath was joined, otherwise false */ bool join(int subpathIndex); /** * @brief Moves the position of a subpath within a path * * @param oldSubpathIndex old index of the subpath * @param newSubpathIndex new index of the subpath * * @return true if the subpath was moved, otherwise false e.g. if an index is out of bounds */ bool moveSubpath(int oldSubpathIndex, int newSubpathIndex); /** * @brief Opens a closed subpath * * The subpath is opened by removing the segment before the given point, making * the given point the new start point of the subpath. * * @param pointIndex the index of the point at which to open the closed subpath * @return the new position of the old first point in the subpath * otherwise KoPathPointIndex( -1, -1 ) */ KoPathPointIndex openSubpath(const KoPathPointIndex &pointIndex); /** * @brief Close a open subpath * * The subpath is closed be inserting a segment between the start and end point, making * the given point the new start point of the subpath. * * @return the new position of the old first point in the subpath * otherwise KoPathPointIndex( -1, -1 ) */ KoPathPointIndex closeSubpath(const KoPathPointIndex &pointIndex); /** * @brief Reverse subpath * * The last point becomes the first point and the first one becomes the last one. * * @param subpathIndex the index of the subpath to reverse */ bool reverseSubpath(int subpathIndex); /** * @brief Removes subpath from the path * @param subpathIndex the index of the subpath to remove * @return the removed subpath on success, 0 otherwise. */ KoSubpath *removeSubpath(int subpathIndex); /** * @brief Adds a subpath at the given index to the path * @param subpath the subpath to add * @param subpathIndex the index at which the new subpath should be inserted * @return true on success, false otherwise e.g. subpathIndex out of bounds */ bool addSubpath(KoSubpath *subpath, int subpathIndex); /** * @brief Combines two path shapes by appending the data of the specified path. * @param path the path to combine with * @return index of the first segment inserted or -1 on failure */ int combine(KoPathShape *path); /** * @brief Creates separate path shapes, one for each existing subpath. * @param separatedPaths the list which contains the separated path shapes * @return true if separating the path was successful, false otherwise */ bool separate(QList &separatedPaths); /** * Returns the specific path shape id. * * Path shape derived shapes have a different shape id which link them * to their respective shape factories. In most cases they do not have * a special tool for editing them. * This function returns the specific shape id for finding the shape * factory from KoShapeRegistry. The default KoPathShapeId is returned * from KoShape::shapeId() so that the generic path editing tool gets * activated when the shape is selected. * * @return the specific shape id */ virtual QString pathShapeId() const; /// Returns a odf/svg string representation of the path data with the given matrix applied. QString toString(const QTransform &matrix = QTransform()) const; /// Returns the fill rule for the path object Qt::FillRule fillRule() const; /// Sets the fill rule to be used for painting the background void setFillRule(Qt::FillRule fillRule); /// Creates path shape from given QPainterPath static KoPathShape *createShapeFromPainterPath(const QPainterPath &path); - /// Returns the viewbox from the given xml element. - static QRect loadOdfViewbox(const KoXmlElement &element); - void setMarker(KoMarker *marker, KoFlake::MarkerPosition pos); KoMarker* marker(KoFlake::MarkerPosition pos) const; bool hasMarkers() const; bool autoFillMarkers() const; void setAutoFillMarkers(bool value); public: struct KRITAFLAKE_EXPORT PointSelectionChangeListener : public ShapeChangeListener { void notifyShapeChanged(ChangeType type, KoShape *shape) override; virtual void recommendPointSelectionChange(KoPathShape *shape, const QList &newSelection) = 0; virtual void notifyPathPointsChanged(KoPathShape *shape) = 0; }; void recommendPointSelectionChange(const QList &newSelection); protected: void notifyPointsChanged(); protected: /// constructor: to be used in cloneShape(), not in descendants! /// \internal /// XXX private? KoPathShape(const KoPathShape &rhs); protected: - /// reimplemented - QString saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const override; - /// reimplemented - void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) override; /** * @brief Add an arc. * * Adds an arc starting at the current point. The arc will be converted to bezier curves. * @param rx x radius of the ellipse * @param ry y radius of the ellipse * @param startAngle the angle where the arc will be started * @param sweepAngle the length of the angle * TODO add param to have angle of the ellipse * @param offset to the first point in the arc * @param curvePoints a array which take the curve points, pass a 'QPointF curvePoins[12]'; * * @return number of points created by the curve */ int arcToCurve(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle, const QPointF &offset, QPointF *curvePoints) const; /** * Get the resize matrix * * This makes sure that also if the newSize isNull that there will be a * very small size of 0.000001 pixels */ QTransform resizeMatrix( const QSizeF &newSize ) const; private: /// close-merges specified subpath void closeMergeSubpathPriv(KoSubpath *subpath); /// closes specified subpath void closeSubpathPriv(KoSubpath *subpath); void updateLastPriv(KoPathPoint **lastPoint); protected: const KoSubpathList &subpaths() const; /// XXX: refactor this using setter? KoSubpathList &subpaths(); void map(const QTransform &matrix); private: class Private; QScopedPointer d; }; Q_DECLARE_METATYPE(KoPathShape*) #endif /* KOPATHSHAPE_H */ diff --git a/libs/flake/KoPathShape_p.h b/libs/flake/KoPathShape_p.h index 0105a935e0..828a87e4b1 100644 --- a/libs/flake/KoPathShape_p.h +++ b/libs/flake/KoPathShape_p.h @@ -1,92 +1,90 @@ /* This file is part of the KDE project * 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. */ #ifndef KOPATHSHAPEPRIVATE_H #define KOPATHSHAPEPRIVATE_H #include "KoPathShape.h" #include "KoMarker.h" #include class KoPathShape::Private { public: explicit Private(); explicit Private(const Private &rhs); QRectF handleRect(const QPointF &p, qreal radius) const; - /// Applies the viewbox transformation defined in the given element - void applyViewboxTransformation(const KoXmlElement &element); void map(const QTransform &matrix); /** * @brief Saves the node types * * This is inspired by inkscape and uses the same mechanism as they do. * The only difference is that they use sodipodi:nodeTypes as element and * we use calligra:nodeTyes as attribute. * This attribute contains of a string which has the node type of each point * in it. The following node types exist: * * c corner * s smooth * z symmetric * * The first point of a path is always of the type c. * If the path is closed the type of the first point is saved in the last element * E.g. you have a closed path with 2 points in it. The first one (start/end of path) * is symmetric and the second one is smooth that will result in the nodeType="czs" * So if there is a closed sub path the nodeTypes contain one more entry then there * are points. That is due to the first and the last point of a closed sub path get * merged into one when they are on the same position. * * @return The node types as string */ QString nodeTypes() const; /** * @brief Loads node types */ void loadNodeTypes(const KoXmlElement &element); /** * @brief Returns subpath at given index * @param subpathIndex the index of the subpath to return * @return subPath on success, or 0 when subpathIndex is out of bounds */ KoSubpath *subPath(int subpathIndex) const; #ifndef NDEBUG /// \internal void paintDebug(QPainter &painter); /** * @brief print debug information about a the points of the path */ void debugPath() const; #endif Qt::FillRule fillRule; KoSubpathList subpaths; QMap> markersNew; bool autoFillMarkers; }; #endif diff --git a/libs/flake/KoPatternBackground.cpp b/libs/flake/KoPatternBackground.cpp index 3193f5062c..570bff44e3 100644 --- a/libs/flake/KoPatternBackground.cpp +++ b/libs/flake/KoPatternBackground.cpp @@ -1,478 +1,315 @@ /* This file is part of the KDE project * 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. */ #include "KoPatternBackground.h" #include "KoShapeSavingContext.h" #include "KoImageData.h" #include "KoImageCollection.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KoPatternBackground::Private : public QSharedData { public: Private() : QSharedData() , repeat(KoPatternBackground::Tiled) , refPoint(KoPatternBackground::TopLeft) , imageCollection(0) , imageData(0) { } ~Private() { delete imageData; } QSizeF targetSize() const { QSizeF size = imageData->imageSize(); if (targetImageSizePercent.width() > 0.0) size.setWidth(0.01 * targetImageSizePercent.width() * size.width()); else if (targetImageSize.width() > 0.0) size.setWidth(targetImageSize.width()); if (targetImageSizePercent.height() > 0.0) size.setHeight(0.01 * targetImageSizePercent.height() * size.height()); else if (targetImageSize.height() > 0.0) size.setHeight(targetImageSize.height()); return size; } QPointF offsetFromRect(const QRectF &fillRect, const QSizeF &imageSize) const { QPointF offset; switch (refPoint) { case KoPatternBackground::TopLeft: offset = fillRect.topLeft(); break; case KoPatternBackground::Top: offset.setX(fillRect.center().x() - 0.5 * imageSize.width()); offset.setY(fillRect.top()); break; case KoPatternBackground::TopRight: offset.setX(fillRect.right() - imageSize.width()); offset.setY(fillRect.top()); break; case KoPatternBackground::Left: offset.setX(fillRect.left()); offset.setY(fillRect.center().y() - 0.5 * imageSize.height()); break; case KoPatternBackground::Center: offset.setX(fillRect.center().x() - 0.5 * imageSize.width()); offset.setY(fillRect.center().y() - 0.5 * imageSize.height()); break; case KoPatternBackground::Right: offset.setX(fillRect.right() - imageSize.width()); offset.setY(fillRect.center().y() - 0.5 * imageSize.height()); break; case KoPatternBackground::BottomLeft: offset.setX(fillRect.left()); offset.setY(fillRect.bottom() - imageSize.height()); break; case KoPatternBackground::Bottom: offset.setX(fillRect.center().x() - 0.5 * imageSize.width()); offset.setY(fillRect.bottom() - imageSize.height()); break; case KoPatternBackground::BottomRight: offset.setX(fillRect.right() - imageSize.width()); offset.setY(fillRect.bottom() - imageSize.height()); break; default: break; } if (refPointOffsetPercent.x() > 0.0) offset += QPointF(0.01 * refPointOffsetPercent.x() * imageSize.width(), 0); if (refPointOffsetPercent.y() > 0.0) offset += QPointF(0, 0.01 * refPointOffsetPercent.y() * imageSize.height()); return offset; } QTransform matrix; KoPatternBackground::PatternRepeat repeat; KoPatternBackground::ReferencePoint refPoint; QSizeF targetImageSize; QSizeF targetImageSizePercent; QPointF refPointOffsetPercent; QPointF tileRepeatOffsetPercent; QPointer imageCollection; KoImageData * imageData; }; // ---------------------------------------------------------------- KoPatternBackground::KoPatternBackground(KoImageCollection *imageCollection) : KoShapeBackground() , d(new Private) { d->imageCollection = imageCollection; Q_ASSERT(d->imageCollection); } KoPatternBackground::~KoPatternBackground() { } bool KoPatternBackground::compareTo(const KoShapeBackground *other) const { Q_UNUSED(other); return false; } void KoPatternBackground::setTransform(const QTransform &matrix) { d->matrix = matrix; } QTransform KoPatternBackground::transform() const { return d->matrix; } void KoPatternBackground::setPattern(const QImage &pattern) { delete d->imageData; if (d->imageCollection) { d->imageData = d->imageCollection->createImageData(pattern); } } void KoPatternBackground::setPattern(KoImageData *imageData) { delete d->imageData; d->imageData = imageData; } QImage KoPatternBackground::pattern() const { if (d->imageData) return d->imageData->image(); return QImage(); } void KoPatternBackground::setRepeat(PatternRepeat repeat) { d->repeat = repeat; } KoPatternBackground::PatternRepeat KoPatternBackground::repeat() const { return d->repeat; } KoPatternBackground::ReferencePoint KoPatternBackground::referencePoint() const { return d->refPoint; } void KoPatternBackground::setReferencePoint(ReferencePoint referencePoint) { d->refPoint = referencePoint; } QPointF KoPatternBackground::referencePointOffset() const { return d->refPointOffsetPercent; } void KoPatternBackground::setReferencePointOffset(const QPointF &offset) { qreal ox = qMax(qreal(0.0), qMin(qreal(100.0), offset.x())); qreal oy = qMax(qreal(0.0), qMin(qreal(100.0), offset.y())); d->refPointOffsetPercent = QPointF(ox, oy); } QPointF KoPatternBackground::tileRepeatOffset() const { return d->tileRepeatOffsetPercent; } void KoPatternBackground::setTileRepeatOffset(const QPointF &offset) { d->tileRepeatOffsetPercent = offset; } QSizeF KoPatternBackground::patternDisplaySize() const { return d->targetSize(); } void KoPatternBackground::setPatternDisplaySize(const QSizeF &size) { d->targetImageSizePercent = QSizeF(); d->targetImageSize = size; } QSizeF KoPatternBackground::patternOriginalSize() const { return d->imageData->imageSize(); } void KoPatternBackground::paint(QPainter &painter, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const { if (! d->imageData) return; painter.save(); if (d->repeat == Tiled) { // calculate scaling of pixmap QSizeF targetSize = d->targetSize(); QSizeF imageSize = d->imageData->imageSize(); qreal scaleX = targetSize.width() / imageSize.width(); qreal scaleY = targetSize.height() / imageSize.height(); QRectF targetRect = fillPath.boundingRect(); // undo scaling on target rectangle targetRect.setWidth(targetRect.width() / scaleX); targetRect.setHeight(targetRect.height() / scaleY); // determine pattern offset QPointF offset = d->offsetFromRect(targetRect, imageSize); // create matrix for pixmap scaling QTransform matrix; matrix.scale(scaleX, scaleY); painter.setClipPath(fillPath); painter.setWorldTransform(matrix, true); painter.drawTiledPixmap(targetRect, d->imageData->pixmap(imageSize.toSize()), -offset); } else if (d->repeat == Original) { QRectF sourceRect(QPointF(0, 0), d->imageData->imageSize()); QRectF targetRect(QPoint(0, 0), d->targetSize()); targetRect.moveCenter(fillPath.boundingRect().center()); painter.setClipPath(fillPath); painter.drawPixmap(targetRect, d->imageData->pixmap(sourceRect.size().toSize()), sourceRect); } else if (d->repeat == Stretched) { painter.setClipPath(fillPath); // undo conversion of the scaling so that we can use a nicely scaled image of the correct size qWarning() << "WARNING: stretched KoPatternBackground painting code is abandoned. The result might be not correct"; const QRectF targetRect = fillPath.boundingRect(); painter.drawPixmap(targetRect.topLeft(), d->imageData->pixmap(targetRect.size().toSize())); } painter.restore(); } -void KoPatternBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) -{ - if (! d->imageData) - return; - - switch (d->repeat) { - case Original: - style.addProperty("style:repeat", "no-repeat"); - break; - case Tiled: - style.addProperty("style:repeat", "repeat"); - break; - case Stretched: - style.addProperty("style:repeat", "stretch"); - break; - } - - if (d->repeat == Tiled) { - QString refPointId = "top-left"; - switch (d->refPoint) { - case TopLeft: refPointId = "top-left"; break; - case Top: refPointId = "top"; break; - case TopRight: refPointId = "top-right"; break; - case Left: refPointId = "left"; break; - case Center: refPointId = "center"; break; - case Right: refPointId = "right"; break; - case BottomLeft: refPointId = "bottom-left"; break; - case Bottom: refPointId = "bottom"; break; - case BottomRight: refPointId = "bottom-right"; break; - } - style.addProperty("draw:fill-image-ref-point", refPointId); - if (d->refPointOffsetPercent.x() > 0.0) - style.addProperty("draw:fill-image-ref-point-x", QString("%1%").arg(d->refPointOffsetPercent.x())); - if (d->refPointOffsetPercent.y() > 0.0) - style.addProperty("draw:fill-image-ref-point-y", QString("%1%").arg(d->refPointOffsetPercent.y())); - } - - if (d->repeat != Stretched) { - QSizeF targetSize = d->targetSize(); - QSizeF imageSize = d->imageData->imageSize(); - if (targetSize.height() != imageSize.height()) - style.addPropertyPt("draw:fill-image-height", targetSize.height()); - if (targetSize.width() != imageSize.width()) - style.addPropertyPt("draw:fill-image-width", targetSize.width()); - } - - KoGenStyle patternStyle(KoGenStyle::FillImageStyle /*no family name*/); - patternStyle.addAttribute("xlink:show", "embed"); - patternStyle.addAttribute("xlink:actuate", "onLoad"); - patternStyle.addAttribute("xlink:type", "simple"); - patternStyle.addAttribute("xlink:href", context.imageHref(d->imageData)); - - QString patternStyleName = context.mainStyles().insert(patternStyle, "picture"); - style.addProperty("draw:fill", "bitmap"); - style.addProperty("draw:fill-image-name", patternStyleName); - - if (d->imageCollection) { - context.addDataCenter(d->imageCollection); - } -} - -bool KoPatternBackground::loadStyle(KoOdfLoadingContext &context, const QSizeF &) -{ - KoStyleStack &styleStack = context.styleStack(); - if (! styleStack.hasProperty(KoXmlNS::draw, "fill")) - return false; - - QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); - if (fillStyle != "bitmap") - return false; - - QString styleName = styleStack.property(KoXmlNS::draw, "fill-image-name"); - - KoXmlElement* e = context.stylesReader().drawStyles("fill-image")[styleName]; - if (! e) - return false; - - const QString href = e->attributeNS(KoXmlNS::xlink, "href", QString()); - if (href.isEmpty()) - return false; - - delete d->imageData; - d->imageData = 0; - if (d->imageCollection) { - d->imageData = d->imageCollection->createImageData(href, context.store()); - } - if (! d->imageData) { - return false; - } - - // read the pattern repeat style - QString style = styleStack.property(KoXmlNS::style, "repeat"); - if (style == "stretch") - d->repeat = Stretched; - else if (style == "no-repeat") - d->repeat = Original; - else - d->repeat = Tiled; - - if (style != "stretch") { - // optional attributes which can override original image size - if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-height")) { - QString height = styleStack.property(KoXmlNS::draw, "fill-image-height"); - if (height.endsWith('%')) - d->targetImageSizePercent.setHeight(height.remove('%').toDouble()); - else - d->targetImageSize.setHeight(KoUnit::parseValue(height)); - } - if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-width")) { - QString width = styleStack.property(KoXmlNS::draw, "fill-image-width"); - if (width.endsWith('%')) - d->targetImageSizePercent.setWidth(width.remove('%').toDouble()); - else - d->targetImageSize.setWidth(KoUnit::parseValue(width)); - } - } - - if (style == "repeat") { - if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point")) { - // align pattern to the given size - QString align = styleStack.property(KoXmlNS::draw, "fill-image-ref-point"); - if (align == "top-left") - d->refPoint = TopLeft; - else if (align == "top") - d->refPoint = Top; - else if (align == "top-right") - d->refPoint = TopRight; - else if (align == "left") - d->refPoint = Left; - else if (align == "center") - d->refPoint = Center; - else if (align == "right") - d->refPoint = Right; - else if (align == "bottom-left") - d->refPoint = BottomLeft; - else if (align == "bottom") - d->refPoint = Bottom; - else if (align == "bottom-right") - d->refPoint = BottomRight; - } - if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point-x")) { - QString pointX = styleStack.property(KoXmlNS::draw, "fill-image-ref-point-x"); - d->refPointOffsetPercent.setX(pointX.remove('%').toDouble()); - } - if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point-y")) { - QString pointY = styleStack.property(KoXmlNS::draw, "fill-image-ref-point-y"); - d->refPointOffsetPercent.setY(pointY.remove('%').toDouble()); - } - if (styleStack.hasProperty(KoXmlNS::draw, "tile-repeat-offset")) { - QString repeatOffset = styleStack.property(KoXmlNS::draw, "tile-repeat-offset"); - QStringList tokens = repeatOffset.split('%'); - if (tokens.count() == 2) { - QString direction = tokens[1].simplified(); - if (direction == "horizontal") - d->tileRepeatOffsetPercent.setX(tokens[0].toDouble()); - else if (direction == "vertical") - d->tileRepeatOffsetPercent.setY(tokens[0].toDouble()); - } - } - } - - return true; -} QRectF KoPatternBackground::patternRectFromFillSize(const QSizeF &size) { QRectF rect; switch (d->repeat) { case Tiled: rect.setTopLeft(d->offsetFromRect(QRectF(QPointF(), size), d->targetSize())); rect.setSize(d->targetSize()); break; case Original: rect.setLeft(0.5 * (size.width() - d->targetSize().width())); rect.setTop(0.5 * (size.height() - d->targetSize().height())); rect.setSize(d->targetSize()); break; case Stretched: rect.setTopLeft(QPointF(0.0, 0.0)); rect.setSize(size); break; } return rect; } diff --git a/libs/flake/KoPatternBackground.h b/libs/flake/KoPatternBackground.h index 5500f51d59..8d79f0a782 100644 --- a/libs/flake/KoPatternBackground.h +++ b/libs/flake/KoPatternBackground.h @@ -1,130 +1,126 @@ /* This file is part of the KDE project * 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. */ #ifndef KOPATTERNBACKGROUND_H #define KOPATTERNBACKGROUND_H #include "KoShapeBackground.h" #include "kritaflake_export.h" #include class KoImageCollection; class KoOdfLoadingContext; class KoPatternBackgroundPrivate; class KoImageData; class QTransform; class QImage; class QPointF; class QRectF; /// A pattern shape background class KRITAFLAKE_EXPORT KoPatternBackground : public KoShapeBackground { public: /// Pattern rendering style enum PatternRepeat { Original, Tiled, Stretched }; /// Pattern reference point enum ReferencePoint { TopLeft, Top, TopRight, Left, Center, Right, BottomLeft, Bottom, BottomRight }; /// Constructs a new pattern background utilizing the given image collection explicit KoPatternBackground(KoImageCollection *collection); ~KoPatternBackground() override; bool compareTo(const KoShapeBackground *other) const override; /// Sets the transform matrix void setTransform(const QTransform &matrix); /// Returns the transform matrix QTransform transform() const; /// Sets a new pattern void setPattern(const QImage &pattern); /// Sets a new pattern. imageData memory is deleted inside this class void setPattern(KoImageData *imageData); /// Returns the pattern QImage pattern() const; /// Sets the pattern repeatgfl void setRepeat(PatternRepeat repeat); /// Returns the pattern repeat PatternRepeat repeat() const; /// Returns the pattern reference point identifier ReferencePoint referencePoint() const; /// Sets the pattern reference point void setReferencePoint(ReferencePoint referencePoint); /// Returns reference point offset in percent of the pattern display size QPointF referencePointOffset() const; /// Sets the reference point offset in percent of the pattern display size void setReferencePointOffset(const QPointF &offset); /// Returns tile repeat offset in percent of the pattern display size QPointF tileRepeatOffset() const; /// Sets the tile repeat offset in percent of the pattern display size void setTileRepeatOffset(const QPointF &offset); /// Returns the pattern display size QSizeF patternDisplaySize() const; /// Sets pattern display size void setPatternDisplaySize(const QSizeF &size); /// Returns the original image size QSizeF patternOriginalSize() const; /// reimplemented from KoShapeBackground void paint(QPainter &painter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override; - /// reimplemented from KoShapeBackground - void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override; - /// reimplemented from KoShapeBackground - bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) override; /// Returns the bounding rect of the pattern image based on the given fill size QRectF patternRectFromFillSize(const QSizeF &size); private: class Private; QSharedDataPointer d; }; #endif // KOPATTERNBACKGROUND_H diff --git a/libs/flake/KoSelection.cpp b/libs/flake/KoSelection.cpp index 09cde55b91..46a66ceb8d 100644 --- a/libs/flake/KoSelection.cpp +++ b/libs/flake/KoSelection.cpp @@ -1,264 +1,255 @@ /* This file is part of the KDE project Copyright (C) 2006 Boudewijn Rempt Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2006 Jan Hambrecht Copyright (C) 2006-2007,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 "KoSelection.h" #include "KoSelection_p.h" #include "KoShapeContainer.h" #include "KoShapeGroup.h" #include "KoPointerEvent.h" #include "KoShapePaintingContext.h" #include "kis_algebra_2d.h" #include "krita_container_utils.h" #include #include "kis_debug.h" KoSelection::KoSelection(QObject *parent) : QObject(parent) , KoShape() , d(new Private) { connect(&d->selectionChangedCompressor, SIGNAL(timeout()), SIGNAL(selectionChanged())); } KoSelection::KoSelection(const KoSelection &rhs) : QObject() , KoShape(rhs) , d(rhs.d) { } KoSelection::~KoSelection() { } void KoSelection::paint(QPainter &painter, KoShapePaintingContext &paintcontext) const { Q_UNUSED(painter); Q_UNUSED(paintcontext); } void KoSelection::setSize(const QSizeF &size) { Q_UNUSED(size); qWarning() << "WARNING: KoSelection::setSize() should never be used!"; } QSizeF KoSelection::size() const { return outlineRect().size(); } QRectF KoSelection::outlineRect() const { const QTransform invertedTransform = transformation().inverted(); QRectF boundingRect; Q_FOREACH (KoShape *shape, selectedVisibleShapes()) { // it is cheaper to invert-transform each outline, than // to group 300+ rotated rectangles into a polygon boundingRect |= invertedTransform.map( shape->absoluteTransformation().map( QPolygonF(shape->outlineRect()))).boundingRect(); } return boundingRect; } QRectF KoSelection::boundingRect() const { return KoShape::boundingRect(selectedVisibleShapes()); } void KoSelection::select(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(shape != this); KIS_SAFE_ASSERT_RECOVER_RETURN(shape); if (!shape->isSelectable() || !shape->isVisible()) { return; } // check recursively if (isSelected(shape)) { return; } // find the topmost parent to select while (KoShapeGroup *parentGroup = dynamic_cast(shape->parent())) { shape = parentGroup; } d->selectedShapes << shape; shape->addShapeChangeListener(this); if (d->selectedShapes.size() == 1) { setTransformation(shape->absoluteTransformation()); } else { setTransformation(QTransform()); } d->selectionChangedCompressor.start(); } void KoSelection::deselect(KoShape *shape) { if (!d->selectedShapes.contains(shape)) return; d->selectedShapes.removeAll(shape); shape->removeShapeChangeListener(this); if (d->selectedShapes.size() == 1) { setTransformation(d->selectedShapes.first()->absoluteTransformation()); } d->selectionChangedCompressor.start(); } void KoSelection::deselectAll() { if (d->selectedShapes.isEmpty()) return; Q_FOREACH (KoShape *shape, d->selectedShapes) { shape->removeShapeChangeListener(this); } // reset the transformation matrix of the selection setTransformation(QTransform()); d->selectedShapes.clear(); d->selectionChangedCompressor.start(); } int KoSelection::count() const { return d->selectedShapes.size(); } bool KoSelection::hitTest(const QPointF &position) const { Q_FOREACH (KoShape *shape, d->selectedShapes) { if (shape->isVisible()) continue; if (shape->hitTest(position)) return true; } return false; } const QList KoSelection::selectedShapes() const { return d->selectedShapes; } const QList KoSelection::selectedVisibleShapes() const { QList shapes = selectedShapes(); KritaUtils::filterContainer (shapes, [](KoShape *shape) { return shape->isVisible(); }); return shapes; } const QList KoSelection::selectedEditableShapes() const { QList shapes = selectedShapes(); KritaUtils::filterContainer (shapes, [](KoShape *shape) { return shape->isShapeEditable(); }); return shapes; } const QList KoSelection::selectedEditableShapesAndDelegates() const { QList shapes; Q_FOREACH (KoShape *shape, selectedShapes()) { QSet delegates = shape->toolDelegates(); if (delegates.isEmpty()) { shapes.append(shape); } else { Q_FOREACH (KoShape *delegatedShape, delegates) { shapes.append(delegatedShape); } } } return shapes; } bool KoSelection::isSelected(const KoShape *shape) const { if (shape == this) return true; const KoShape *tmpShape = shape; while (tmpShape && std::find(d->selectedShapes.begin(), d->selectedShapes.end(), tmpShape) == d->selectedShapes.end()) { tmpShape = tmpShape->parent(); } return tmpShape; } KoShape *KoSelection::firstSelectedShape() const { return !d->selectedShapes.isEmpty() ? d->selectedShapes.first() : 0; } void KoSelection::setActiveLayer(KoShapeLayer *layer) { d->activeLayer = layer; emit currentLayerChanged(layer); } KoShapeLayer* KoSelection::activeLayer() const { return d->activeLayer; } void KoSelection::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape) { Q_UNUSED(shape); if (type == KoShape::Deleted) { deselect(shape); // HACK ALERT: the caller will also remove the listener, which was // removed in deselect(), so re-add it here shape->addShapeChangeListener(this); } } - -void KoSelection::saveOdf(KoShapeSavingContext &) const -{ -} - -bool KoSelection::loadOdf(const KoXmlElement &, KoShapeLoadingContext &) -{ - return true; -} diff --git a/libs/flake/KoSelection.h b/libs/flake/KoSelection.h index a372e331dd..4e80601dfe 100644 --- a/libs/flake/KoSelection.h +++ b/libs/flake/KoSelection.h @@ -1,169 +1,165 @@ /* This file is part of the KDE project Copyright (C) 2006 Boudewijn Rempt Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2007,2009 Thomas Zander Copyright (C) 2006,2007 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSELECTION_H #define KOSELECTION_H #include #include "KoShape.h" #include "KoFlake.h" #include "kritaflake_export.h" class KoShapeLayer; class KoSelectionPrivate; /** * A selection is a shape that contains a number of references * to shapes. That means that a selection can be manipulated in * the same way as a single shape. * * Note that a single shape can be selected in one view, and not in * another, and that in a single view, more than one selection can be * present. So selections should not be seen as singletons, or as * something completely transient. * * A selection, however, should not be selectable. We need to think * a little about the interaction here. */ class KRITAFLAKE_EXPORT KoSelection : public QObject, public KoShape, public KoShape::ShapeChangeListener { Q_OBJECT public: KoSelection(QObject *parent = 0); ~KoSelection() override; void paint(QPainter &painter, KoShapePaintingContext &paintcontext) const override; void setSize(const QSizeF &size) override; QSizeF size() const override; QRectF outlineRect() const override; QRectF boundingRect() const override; /** * Adds a shape to the selection. * * If the shape is a KoShapeGroup all of its child shapes are automatically added * to the selection. * If the shape has no parent or is not a KoShapeGroup, only the given shape is * added to the selection. * If the given shape is a child of a KoShapeGroup and recursive selection is enabled * the all parents and their child shapes up to the toplevel KoShapeGroup are added to * the selection. * * @param shape the shape to add to the selection */ void select(KoShape *shape); /** * Removes a selected shape. * * If the shape is a KoShapeGroup all of its child shapes are automatically removed * from the selection. * If the shape has no parent or is not a KoShapeGroup, only the given shape is * removed from the selection. * If the given shape is a child of a KoShapeGroup and recursive selection is enabled * the all parents and their child shape up to the toplevel KoShapeGroup are removed * from the selection. * * @param shape the shape to remove from the selection */ void deselect(KoShape *shape); /// clear the selections list void deselectAll(); /** * Return the list of selected shapes * @return the list of selected shapes */ const QList selectedShapes() const; /** * Same as selectedShapes() but only for shapes in visible state. Used by * the algorithms that draw shapes on the image */ const QList selectedVisibleShapes() const; /** * Same as selectedShapes() but only for editable shapes. Used by * the algorithms that modify the image */ const QList selectedEditableShapes() const; /** * Same as selectedEditableShapes() but also includes shapes delegates. * Used for */ const QList selectedEditableShapesAndDelegates() const; /** * Return the first selected shape, or 0 if there is nothing selected. */ KoShape *firstSelectedShape() const; /// return true if the shape is selected bool isSelected(const KoShape *shape) const; /// return the selection count, i.e. the number of all selected shapes int count() const; bool hitTest(const QPointF &position) const override; /** * Sets the currently active layer. * @param layer the new active layer */ void setActiveLayer(KoShapeLayer *layer); /** * Returns a currently active layer. * * @return the currently active layer, or zero if there is none */ KoShapeLayer *activeLayer() const; void notifyShapeChanged(ChangeType type, KoShape *shape) override; Q_SIGNALS: /// emitted when the selection is changed void selectionChanged(); /// emitted when the current layer is changed void currentLayerChanged(const KoShapeLayer *layer); -private: - void saveOdf(KoShapeSavingContext &) const override; - bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) override; - protected: KoSelection(const KoSelection &rhs); private: class Private; QSharedDataPointer d; }; #endif diff --git a/libs/flake/KoShape.cpp b/libs/flake/KoShape.cpp index 3d29ec38ff..c1e9b006bc 100644 --- a/libs/flake/KoShape.cpp +++ b/libs/flake/KoShape.cpp @@ -1,2036 +1,1376 @@ /* 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 #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 "KoOdfGradientBackground.h" #include // KoShape::Private KoShape::SharedData::SharedData() : QSharedData() , size(50, 50) , shadow(0) , border(0) , filterEffectStack(0) , transparency(0.0) , zIndex(0) , runThrough(0) , visible(true) , printable(true) , geometryProtected(false) , keepAspect(false) , selectable(true) , protectContent(false) , textRunAroundSide(KoShape::BiggestRunAroundSide) , textRunAroundDistanceLeft(0.0) , textRunAroundDistanceTop(0.0) , textRunAroundDistanceRight(0.0) , textRunAroundDistanceBottom(0.0) , textRunAroundThreshold(0.0) , textRunAroundContour(KoShape::ContourFull) { } KoShape::SharedData::SharedData(const SharedData &rhs) : QSharedData() , size(rhs.size) , shapeId(rhs.shapeId) , name(rhs.name) , localMatrix(rhs.localMatrix) , userData(rhs.userData ? rhs.userData->clone() : 0) , stroke(rhs.stroke) , fill(rhs.fill) , inheritBackground(rhs.inheritBackground) , inheritStroke(rhs.inheritStroke) , 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) , protectContent(rhs.protectContent) , textRunAroundSide(rhs.textRunAroundSide) , textRunAroundDistanceLeft(rhs.textRunAroundDistanceLeft) , textRunAroundDistanceTop(rhs.textRunAroundDistanceTop) , textRunAroundDistanceRight(rhs.textRunAroundDistanceRight) , textRunAroundDistanceBottom(rhs.textRunAroundDistanceBottom) , textRunAroundThreshold(rhs.textRunAroundThreshold) , textRunAroundContour(rhs.textRunAroundContour) { } KoShape::SharedData::~SharedData() { if (shadow && !shadow->deref()) delete shadow; if (filterEffectStack && !filterEffectStack->deref()) delete filterEffectStack; } void KoShape::shapeChangedPriv(KoShape::ChangeType type) { if (d->parent) d->parent->model()->childChanged(this, type); this->shapeChanged(type); Q_FOREACH (KoShape * shape, d->dependees) { shape->shapeChanged(type, this); } Q_FOREACH (KoShape::ShapeChangeListener *listener, d->listeners) { listener->notifyShapeChangedImpl(type, this); } } void KoShape::addShapeManager(KoShapeManager *manager) { d->shapeManagers.insert(manager); } void KoShape::removeShapeManager(KoShapeManager *manager) { d->shapeManagers.remove(manager); } -// static -QString KoShape::SharedData::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 const qint16 KoShape::maxZIndex = std::numeric_limits::max(); const qint16 KoShape::minZIndex = std::numeric_limits::min(); KoShape::KoShape() : d(new Private()), s(new SharedData) { notifyChanged(); } KoShape::KoShape(const KoShape &rhs) : d(new Private()), s(rhs.s) { } KoShape::~KoShape() { shapeChangedPriv(Deleted); d->listeners.clear(); /** * The shape must have already been detached from all the parents and * shape managers. Otherwise we migh accidentally request some RTTI * information, which is not available anymore (we are in d-tor). * * TL;DR: fix the code that caused this destruction without unparenting * instead of trying to remove these assert! */ KIS_SAFE_ASSERT_RECOVER (!d->parent) { d->parent->removeShape(this); } KIS_SAFE_ASSERT_RECOVER (d->shapeManagers.isEmpty()) { Q_FOREACH (KoShapeManager *manager, d->shapeManagers) { manager->shapeInterface()->notifyShapeDestructed(this); } d->shapeManagers.clear(); } } KoShape *KoShape::cloneShape() const { KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "not implemented!"); qWarning() << shapeId() << "cannot be cloned"; return 0; } void KoShape::paintStroke(QPainter &painter, KoShapePaintingContext &paintcontext) const { Q_UNUSED(paintcontext); if (stroke()) { stroke()->paint(this, painter); } } void KoShape::scale(qreal sx, qreal sy) { QPointF pos = position(); QTransform scaleMatrix; scaleMatrix.translate(pos.x(), pos.y()); scaleMatrix.scale(sx, sy); scaleMatrix.translate(-pos.x(), -pos.y()); s->localMatrix = s->localMatrix * scaleMatrix; notifyChanged(); shapeChangedPriv(ScaleChanged); } void KoShape::rotate(qreal angle) { QPointF center = s->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()); s->localMatrix = s->localMatrix * rotateMatrix; notifyChanged(); shapeChangedPriv(RotationChanged); } void KoShape::shear(qreal sx, qreal sy) { QPointF pos = position(); QTransform shearMatrix; shearMatrix.translate(pos.x(), pos.y()); shearMatrix.shear(sx, sy); shearMatrix.translate(-pos.x(), -pos.y()); s->localMatrix = s->localMatrix * shearMatrix; notifyChanged(); shapeChangedPriv(ShearChanged); } void KoShape::setSize(const QSizeF &newSize) { QSizeF oldSize(size()); // always set size, as d->size and size() may vary setSizeImpl(newSize); if (oldSize == newSize) return; notifyChanged(); shapeChangedPriv(SizeChanged); } void KoShape::setSizeImpl(const QSizeF &size) const { s->size = size; } void KoShape::setPosition(const QPointF &newPosition) { QPointF currentPos = position(); if (newPosition == currentPos) return; QTransform translateMatrix; translateMatrix.translate(newPosition.x() - currentPos.x(), newPosition.y() - currentPos.y()); s->localMatrix = s->localMatrix * translateMatrix; notifyChanged(); shapeChangedPriv(PositionChanged); } bool KoShape::hitTest(const QPointF &position) const { if (d->parent && d->parent->isClipped(this) && !d->parent->hitTest(position)) return false; QPointF point = absoluteTransformation().inverted().map(position); QRectF bb = outlineRect(); if (s->stroke) { KoInsets insets; s->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 (! s->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().inverted().map(position - s->shadow->offset()); return bb.contains(point); } QRectF KoShape::boundingRect() const { QTransform transform = absoluteTransformation(); QRectF bb = outlineRect(); if (s->stroke) { KoInsets insets; s->stroke->strokeInsets(this, insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } bb = transform.mapRect(bb); if (s->shadow) { KoInsets insets; s->shadow->insets(insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (s->filterEffectStack) { QRectF clipRect = s->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; } QRectF KoShape::absoluteOutlineRect() const { return absoluteTransformation().map(outline()).boundingRect(); } QRectF KoShape::absoluteOutlineRect(const QList &shapes) { QRectF absoluteOutlineRect; Q_FOREACH (KoShape *shape, shapes) { absoluteOutlineRect |= shape->absoluteOutlineRect(); } return absoluteOutlineRect; } QTransform KoShape::absoluteTransformation() const { QTransform matrix; // apply parents matrix to inherit any transformations done there. KoShapeContainer * container = d->parent; if (container) { if (container->inheritsTransform(this)) { matrix = container->absoluteTransformation(); } else { QSizeF containerSize = container->size(); QPointF containerPos = container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height()); matrix.translate(containerPos.x(), containerPos.y()); } } return s->localMatrix * matrix; } void KoShape::applyAbsoluteTransformation(const QTransform &matrix) { QTransform globalMatrix = absoluteTransformation(); // 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) { s->localMatrix = matrix * s->localMatrix; notifyChanged(); shapeChangedPriv(GenericMatrixChange); } void KoShape::setTransformation(const QTransform &matrix) { s->localMatrix = matrix; notifyChanged(); shapeChangedPriv(GenericMatrixChange); } QTransform KoShape::transformation() const { return s->localMatrix; } KoShape::ChildZOrderPolicy KoShape::childZOrderPolicy() { return ChildZDefault; } bool KoShape::compareShapeZIndex(KoShape *s1, KoShape *s2) { /** * WARNING: Our definition of zIndex is not yet compatible with SVG2's * definition. In SVG stacking context of groups with the same * zIndex are **merged**, while in Krita the contents of groups * is never merged. One group will always below than the other. * Therefore, when zIndex of two groups inside the same parent * coincide, the resulting painting order in Krita is * **UNDEFINED**. * * To avoid this trouble we use KoShapeReorderCommand::mergeInShape() * inside KoShapeCreateCommand. */ /** * The algorithm below doesn't correctly handle the case when the two pointers actually * point to the same shape. So just check it in advance to guarantee strict weak ordering * relation requirement */ if (s1 == s2) return false; // 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) { 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(); shapeChangedPriv(ParentChanged); } bool KoShape::inheritsTransformFromAny(const QList ancestorsInQuestion) const { bool result = false; KoShape *shape = const_cast(this); while (shape) { KoShapeContainer *parent = shape->parent(); if (parent && !parent->inheritsTransform(shape)) { break; } if (ancestorsInQuestion.contains(shape)) { result = true; break; } shape = parent; } return result; } bool KoShape::hasCommonParent(const KoShape *shape) const { const KoShape *thisShape = this; while (thisShape) { const KoShape *otherShape = shape; while (otherShape) { if (thisShape == otherShape) { return true; } otherShape = otherShape->parent(); } thisShape = thisShape->parent(); } return false; } qint16 KoShape::zIndex() const { return s->zIndex; } void KoShape::update() const { if (!d->shapeManagers.empty()) { QRectF rect(boundingRect()); Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->update(rect, this, true); } } } void KoShape::updateAbsolute(const QRectF &rect) const { if (rect.isEmpty() && !rect.isNull()) { return; } if (!d->shapeManagers.empty() && isVisible()) { Q_FOREACH (KoShapeManager *manager, d->shapeManagers) { manager->update(rect); } } } 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 { if (background()) { 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().map(point); } void KoShape::setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor) { QPointF currentAbsPosition = absolutePosition(anchor); QPointF translate = newPosition - currentAbsPosition; QTransform translateMatrix; translateMatrix.translate(translate.x(), translate.y()); applyAbsoluteTransformation(translateMatrix); notifyChanged(); shapeChangedPriv(PositionChanged); } void KoShape::copySettings(const KoShape *shape) { s->size = shape->size(); s->zIndex = shape->zIndex(); s->visible = shape->isVisible(false); // Ensure printable is true by default if (!s->visible) s->printable = true; else s->printable = shape->isPrintable(); s->geometryProtected = shape->isGeometryProtected(); s->protectContent = shape->isContentProtected(); s->selectable = shape->isSelectable(); s->keepAspect = shape->keepAspectRatio(); s->localMatrix = shape->s->localMatrix; } void KoShape::notifyChanged() { Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->notifyShapeChanged(this); } } void KoShape::setUserData(KoShapeUserData *userData) { s->userData.reset(userData); } KoShapeUserData *KoShape::userData() const { return s->userData.data(); } bool KoShape::hasTransparency() const { QSharedPointer bg = background(); return !bg || bg->hasTransparency() || s->transparency > 0.0; } void KoShape::setTransparency(qreal transparency) { s->transparency = qBound(0.0, transparency, 1.0); shapeChangedPriv(TransparencyChanged); notifyChanged(); } qreal KoShape::transparency(bool recursive) const { if (!recursive || !parent()) { return s->transparency; } else { const qreal parentOpacity = 1.0-parent()->transparency(recursive); const qreal childOpacity = 1.0-s->transparency; return 1.0-(parentOpacity*childOpacity); } } KoInsets KoShape::strokeInsets() const { KoInsets answer; if (s->stroke) s->stroke->strokeInsets(this, answer); return answer; } qreal KoShape::rotation() const { // 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(s->localMatrix.m12()) - fabs(s->localMatrix.m21())) > 1e-10) return std::numeric_limits::quiet_NaN(); // check if the matrix has scaling mixed in if (fabs(s->localMatrix.m11() - s->localMatrix.m22()) > 1e-10) return std::numeric_limits::quiet_NaN(); // calculate the angle from the matrix elements qreal angle = atan2(-s->localMatrix.m21(), s->localMatrix.m11()) * 180.0 / M_PI; if (angle < 0.0) angle += 360.0; return angle; } QSizeF KoShape::size() const { return s->size; } QPointF KoShape::position() const { QPointF center = outlineRect().center(); return s->localMatrix.map(center) - center; } KoShape::TextRunAroundSide KoShape::textRunAroundSide() const { return s->textRunAroundSide; } void KoShape::setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrought) { if (side == RunThrough) { if (runThrought == Background) { setRunThrough(-1); } else { setRunThrough(1); } } else { setRunThrough(0); } if ( s->textRunAroundSide == side) { return; } s->textRunAroundSide = side; notifyChanged(); shapeChangedPriv(TextRunAroundChanged); } qreal KoShape::textRunAroundDistanceTop() const { return s->textRunAroundDistanceTop; } void KoShape::setTextRunAroundDistanceTop(qreal distance) { s->textRunAroundDistanceTop = distance; } qreal KoShape::textRunAroundDistanceLeft() const { return s->textRunAroundDistanceLeft; } void KoShape::setTextRunAroundDistanceLeft(qreal distance) { s->textRunAroundDistanceLeft = distance; } qreal KoShape::textRunAroundDistanceRight() const { return s->textRunAroundDistanceRight; } void KoShape::setTextRunAroundDistanceRight(qreal distance) { s->textRunAroundDistanceRight = distance; } qreal KoShape::textRunAroundDistanceBottom() const { return s->textRunAroundDistanceBottom; } void KoShape::setTextRunAroundDistanceBottom(qreal distance) { s->textRunAroundDistanceBottom = distance; } qreal KoShape::textRunAroundThreshold() const { return s->textRunAroundThreshold; } void KoShape::setTextRunAroundThreshold(qreal threshold) { s->textRunAroundThreshold = threshold; } KoShape::TextRunAroundContour KoShape::textRunAroundContour() const { return s->textRunAroundContour; } void KoShape::setTextRunAroundContour(KoShape::TextRunAroundContour contour) { s->textRunAroundContour = contour; } void KoShape::setBackground(QSharedPointer fill) { s->inheritBackground = false; s->fill = fill; shapeChangedPriv(BackgroundChanged); notifyChanged(); } QSharedPointer KoShape::background() const { QSharedPointer bg; if (!s->inheritBackground) { bg = s->fill; } else if (parent()) { bg = parent()->background(); } return bg; } void KoShape::setInheritBackground(bool value) { s->inheritBackground = value; if (s->inheritBackground) { s->fill.clear(); } } bool KoShape::inheritBackground() const { return s->inheritBackground; } void KoShape::setZIndex(qint16 zIndex) { if (s->zIndex == zIndex) return; s->zIndex = zIndex; notifyChanged(); } int KoShape::runThrough() const { return s->runThrough; } void KoShape::setRunThrough(short int runThrough) { s->runThrough = runThrough; } void KoShape::setVisible(bool on) { int _on = (on ? 1 : 0); if (s->visible == _on) return; s->visible = _on; } bool KoShape::isVisible(bool recursive) const { if (!recursive) return s->visible; if (!s->visible) return false; KoShapeContainer * parentShape = parent(); if (parentShape) { return parentShape->isVisible(true); } return true; } void KoShape::setPrintable(bool on) { s->printable = on; } bool KoShape::isPrintable() const { if (s->visible) return s->printable; else return false; } void KoShape::setSelectable(bool selectable) { s->selectable = selectable; } bool KoShape::isSelectable() const { return s->selectable; } void KoShape::setGeometryProtected(bool on) { s->geometryProtected = on; } bool KoShape::isGeometryProtected() const { return s->geometryProtected; } void KoShape::setContentProtected(bool protect) { s->protectContent = protect; } bool KoShape::isContentProtected() const { return s->protectContent; } KoShapeContainer *KoShape::parent() const { return d->parent; } void KoShape::setKeepAspectRatio(bool keepAspect) { s->keepAspect = keepAspect; shapeChangedPriv(KeepAspectRatioChange); notifyChanged(); } bool KoShape::keepAspectRatio() const { return s->keepAspect; } QString KoShape::shapeId() const { return s->shapeId; } void KoShape::setShapeId(const QString &id) { s->shapeId = id; } KoShapeStrokeModelSP KoShape::stroke() const { KoShapeStrokeModelSP stroke; if (!s->inheritStroke) { stroke = s->stroke; } else if (parent()) { stroke = parent()->stroke(); } return stroke; } void KoShape::setStroke(KoShapeStrokeModelSP stroke) { s->inheritStroke = false; s->stroke = stroke; shapeChangedPriv(StrokeChanged); notifyChanged(); } void KoShape::setInheritStroke(bool value) { s->inheritStroke = value; if (s->inheritStroke) { s->stroke.clear(); } } bool KoShape::inheritStroke() const { return s->inheritStroke; } void KoShape::setShadow(KoShapeShadow *shadow) { if (s->shadow) s->shadow->deref(); s->shadow = shadow; if (s->shadow) { s->shadow->ref(); // TODO update changed area } shapeChangedPriv(ShadowChanged); notifyChanged(); } KoShapeShadow *KoShape::shadow() const { return s->shadow; } void KoShape::setBorder(KoBorder *border) { if (s->border) { // The shape owns the border. delete s->border; } s->border = border; shapeChangedPriv(BorderChanged); notifyChanged(); } KoBorder *KoShape::border() const { return s->border; } void KoShape::setClipPath(KoClipPath *clipPath) { s->clipPath.reset(clipPath); shapeChangedPriv(ClipPathChanged); notifyChanged(); } KoClipPath * KoShape::clipPath() const { return s->clipPath.data(); } void KoShape::setClipMask(KoClipMask *clipMask) { s->clipMask.reset(clipMask); shapeChangedPriv(ClipMaskChanged); notifyChanged(); } KoClipMask* KoShape::clipMask() const { return s->clipMask.data(); } QTransform KoShape::transform() const { return s->localMatrix; } QString KoShape::name() const { return s->name; } void KoShape::setName(const QString &name) { s->name = name; } void KoShape::waitUntilReady(bool asynchronous) const { Q_UNUSED(asynchronous); } bool KoShape::isShapeEditable(bool recursive) const { if (!s->visible || s->geometryProtected) return false; if (recursive && d->parent) { return d->parent->isShapeEditable(true); } return true; } -// loading & saving methods -QString KoShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const -{ - // and fill the style - KoShapeStrokeModelSP sm = stroke(); - if (sm) { - sm->fillStyle(style, context); - } - else { - style.addProperty("draw:stroke", "none", KoGenStyle::GraphicType); - } - KoShapeShadow *shadow = this->shadow(); - if (shadow) - shadow->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(s->additionalStyleAttributes.constBegin()); - for (; it != s->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) -{ - - KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); - styleStack.setTypeProperties("graphic"); - - s->fill.clear(); - s->stroke.clear(); - - if (s->shadow && !s->shadow->deref()) { - delete s->shadow; - s->shadow = 0; - } - setBackground(loadOdfFill(context)); - setStroke(loadOdfStroke(element, context)); - setShadow(s->loadOdfShadow(context)); - setBorder(s->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); - } - } - } - - return true; -} - -QSharedPointer KoShape::loadOdfFill(KoShapeLoadingContext &context) const -{ - QString fill = KoShape::SharedData::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 = KoShape::SharedData::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 = KoShape::SharedData::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 *KoShape::SharedData::loadOdfShadow(KoShapeLoadingContext &context) const -{ - KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); - QString shadowStyle = KoShape::SharedData::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 *KoShape::SharedData::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::loadOdfClipContour(const KoXmlElement &element, KoShapeLoadingContext &context, const QSizeF &scaleFactor) -{ - - 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); - s->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 -{ - 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().addAttribute("svg:width", s.width()); - context.xmlWriter().addAttribute("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().addAttribute("svg:x", p.x()); - context.xmlWriter().addAttribute("svg:y", p.y()); - } - - if (attributes & OdfTransformation) { - QTransform matrix = absoluteTransformation() * 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().addAttribute("svg:x", matrix.dx()); - context.xmlWriter().addAttribute("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(s->additionalAttributes.constBegin()); - for (; it != s->additionalAttributes.constEnd(); ++it) { - context.xmlWriter().addAttribute(it.key().toUtf8(), it.value()); - } - } -} - -void KoShape::saveOdfClipContour(KoShapeSavingContext &context, const QSizeF &originalSize) const -{ - - debugFlake << "shape saves contour-polygon"; - if (s->clipPath && !s->clipPath->clipPathShapes().isEmpty()) { - // This will loose data as odf can only save one set of contour whereas - // 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 - s->clipPath->clipPathShapes().first()->saveContourOdf(context, originalSize); - } -} - -// end loading & saving methods - KisHandlePainterHelper KoShape::createHandlePainterHelperView(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius) { const QTransform originalPainterTransform = painter->transform(); painter->setTransform(shape->absoluteTransformation() * converter.documentToView() * painter->transform()); // move c-tor return KisHandlePainterHelper(painter, originalPainterTransform, handleRadius); } KisHandlePainterHelper KoShape::createHandlePainterHelperDocument(QPainter *painter, KoShape *shape, qreal handleRadius) { const QTransform originalPainterTransform = painter->transform(); painter->setTransform(shape->absoluteTransformation() * painter->transform()); // move c-tor return KisHandlePainterHelper(painter, originalPainterTransform, handleRadius); } QPointF KoShape::shapeToDocument(const QPointF &point) const { return absoluteTransformation().map(point); } QRectF KoShape::shapeToDocument(const QRectF &rect) const { return absoluteTransformation().mapRect(rect); } QPointF KoShape::documentToShape(const QPointF &point) const { return absoluteTransformation().inverted().map(point); } QRectF KoShape::documentToShape(const QRectF &rect) const { return absoluteTransformation().inverted().mapRect(rect); } bool KoShape::addDependee(KoShape *shape) { 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) { int index = d->dependees.indexOf(shape); if (index >= 0) d->dependees.removeAt(index); } bool KoShape::hasDependee(KoShape *shape) const { return d->dependees.contains(shape); } QList KoShape::dependees() const { 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) { s->additionalAttributes.insert(name, value); } void KoShape::removeAdditionalAttribute(const QString &name) { s->additionalAttributes.remove(name); } bool KoShape::hasAdditionalAttribute(const QString &name) const { return s->additionalAttributes.contains(name); } QString KoShape::additionalAttribute(const QString &name) const { return s->additionalAttributes.value(name); } void KoShape::setAdditionalStyleAttribute(const char *name, const QString &value) { s->additionalStyleAttributes.insert(name, value); } void KoShape::removeAdditionalStyleAttribute(const char *name) { s->additionalStyleAttributes.remove(name); } KoFilterEffectStack *KoShape::filterEffectStack() const { return s->filterEffectStack; } void KoShape::setFilterEffectStack(KoFilterEffectStack *filterEffectStack) { if (s->filterEffectStack) s->filterEffectStack->deref(); s->filterEffectStack = filterEffectStack; if (s->filterEffectStack) { s->filterEffectStack->ref(); } notifyChanged(); } QSet KoShape::toolDelegates() const { return d->toolDelegates; } void KoShape::setToolDelegates(const QSet &delegates) { d->toolDelegates = delegates; } QString KoShape::hyperLink () const { return s->hyperLink; } void KoShape::setHyperLink(const QString &hyperLink) { s->hyperLink = hyperLink; } 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) { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->listeners.contains(listener)); listener->registerShape(this); d->listeners.append(listener); } void KoShape::removeShapeChangeListener(KoShape::ShapeChangeListener *listener) { KIS_SAFE_ASSERT_RECOVER_RETURN(d->listeners.contains(listener)); d->listeners.removeAll(listener); listener->unregisterShape(this); } QList KoShape::listeners() const { return d->listeners; } 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; } QList KoShape::linearizeSubtreeSorted(const QList &shapes) { QList sortedShapes = shapes; std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); QList result; Q_FOREACH (KoShape *shape, sortedShapes) { result << shape; KoShapeContainer *container = dynamic_cast(shape); if (container) { result << linearizeSubtreeSorted(container->shapes()); } } return result; } diff --git a/libs/flake/KoShape.h b/libs/flake/KoShape.h index d596eca84c..a5c9d6d56e 100644 --- a/libs/flake/KoShape.h +++ b/libs/flake/KoShape.h @@ -1,1219 +1,1109 @@ /* 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 #include #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 KoFilterEffectStack; class KoSnapData; class KoClipPath; class KoClipMask; class KoShapePaintingContext; class KoShapeAnchor; class KoBorder; struct KoInsets; class KoShapeBackground; class KisHandlePainterHelper; class KoShapeManager; /** * 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. *
  1. 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.
  2. *
  3. for shape hierarchies the parent may receive a notification when a child was modified. * This is done though KoShapeContainerModel::childChanged()
  4. *
  5. 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() 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 ClipMaskChanged, ///< 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 the shape or shape's subtree * @return a cloned shape */ virtual KoShape* cloneShape() const; /** * @brief Paint the shape fill * The class extending this one is responsible for painting itself. \p painter is expected * to be preconfigured to work in "document" pixels. * * @param painter used for painting the shape * @param paintcontext the painting context. */ virtual void paint(QPainter &painter, KoShapePaintingContext &paintcontext) const = 0; /** * @brief paintStroke paints the shape's stroked outline * @param painter used for painting the shape * @see applyConversion() * @param paintcontext the painting context. */ virtual void paintStroke(QPainter &painter, KoShapePaintingContext &paintcontext) const; - /** - * 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 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); /** * @return the bounding rect of the outline of the shape measured * in absolute coordinate system. Please note that in contrast to * boundingRect() this rect doesn't include the stroke and other * insets. */ QRectF absoluteOutlineRect() const; /** * Same as a member function, but applies to a list of shapes and returns a * united rect. */ static QRectF absoluteOutlineRect(const QList &shapes); /** * 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 runThrough 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 height 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. * * If the shape inherited the background from its parent, its stops inheriting it, that * is inheritBackground property resets to false. * * @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; /** * @brief setInheritBackground marks a shape as inhiriting the background * from the parent shape. NOTE: The currently selected background is destroyed. * @param value true if the shape should inherit the filling background */ void setInheritBackground(bool value); /** * @brief inheritBackground shows if the shape inherits background from its parent * @return true if the shape inherits the fill */ bool inheritBackground() 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() */ qint16 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(qint16 zIndex); /** * Maximum value of z-index */ static const qint16 maxZIndex; /** * Minimum value of z-index */ static const qint16 minZIndex; /** * 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() const; /** * 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 = true) 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); /** * @brief inheritsTransformFromAny checks if the shape inherits transformation from * any of the shapes listed in \p ancestorsInQuestion. The inheritance is checked * in recursive way. * @return true if there is a (transitive) transformation-wise parent found in \p ancestorsInQuestion */ bool inheritsTransformFromAny(const QList ancestorsInQuestion) const; /** * @return true if this shape has a common parent with \p shape */ bool hasCommonParent(const KoShape *shape) const; /** * 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 absolute coordinates of the canvas 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 updateAbsolute(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. The stroke inheritance becomes disabled. * @param stroke the new stroke, or 0 if there should be no stroke. */ void setStroke(KoShapeStrokeModelSP stroke); /** * @brief setInheritStroke marks a shape as inhiriting the stroke * from the parent shape. NOTE: The currently selected stroke is destroyed. * @param value true if the shape should inherit the stroke style */ void setInheritStroke(bool value); /** * @brief inheritStroke shows if the shape inherits the stroke from its parent * @return true if the shape inherits the stroke style */ bool inheritStroke() const; /** * 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/height 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/height 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. */ QTransform absoluteTransformation() 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); /** * 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 createHandlePainterHelperView(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius = 0.0); static KisHandlePainterHelper createHandlePainterHelperDocument(QPainter *painter, KoShape *shape, qreal handleRadius); /** * @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 finished. * 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(bool asynchronous = true) const; /// checks recursively if the shape or one of its parents is not visible or locked virtual bool isShapeEditable(bool recursive = true) 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); /** * 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); public: struct KRITAFLAKE_EXPORT ShapeChangeListener { virtual ~ShapeChangeListener(); virtual void notifyShapeChanged(ChangeType type, KoShape *shape) = 0; private: friend class KoShape; 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); protected: QList listeners() const; void setSizeImpl(const QSizeF &size) const; public: static QList linearizeSubtree(const QList &shapes); static QList linearizeSubtreeSorted(const QList &shapes); protected: KoShape(const KoShape &rhs); - /* ** 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 - 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 - }; - - /** - * 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. * @param shape the shape. */ virtual void shapeChanged(ChangeType type, KoShape *shape = 0); /// return the current matrix that contains the rotation/scale/position of this shape QTransform transform() const; private: class Private; QScopedPointer d; class SharedData; QSharedDataPointer s; protected: /** * Notify the shape that a change was done. To be used by inheriting shapes. * @param type the change type */ void shapeChangedPriv(KoShape::ChangeType type); private: void addShapeManager(KoShapeManager *manager); void removeShapeManager(KoShapeManager *manager); friend class KoShapeManager; }; Q_DECLARE_METATYPE(KoShape*) #endif diff --git a/libs/flake/KoShapeAnchor.cpp b/libs/flake/KoShapeAnchor.cpp index 5067ea6cc8..b2d5b2cd96 100644 --- a/libs/flake/KoShapeAnchor.cpp +++ b/libs/flake/KoShapeAnchor.cpp @@ -1,526 +1,199 @@ /* This file is part of the KDE project * Copyright (C) 2007, 2009-2010 Thomas Zander * Copyright (C) 2010 Ko Gmbh * Copyright (C) 2011 Matus Hanzes * Copyright (C) 2013 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. */ #include "KoShapeAnchor.h" #include "KoStyleStack.h" #include "KoOdfLoadingContext.h" #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoShapeAnchor::Private { public: Private(KoShape *s) : shape(s) , verticalPos(KoShapeAnchor::VTop) , verticalRel(KoShapeAnchor::VLine) , horizontalPos(KoShapeAnchor::HLeft) , horizontalRel(KoShapeAnchor::HChar) , flowWithText(true) , anchorType(KoShapeAnchor::AnchorToCharacter) , placementStrategy(0) , pageNumber(-1) , textLocation(0) { } QDebug printDebug(QDebug dbg) const { #ifndef NDEBUG dbg.space() << "KoShapeAnchor" << this; dbg.space() << "offset:" << offset; dbg.space() << "shape:" << shape->name(); #endif return dbg.space(); } KoShape * const shape; QPointF offset; KoShapeAnchor::VerticalPos verticalPos; KoShapeAnchor::VerticalRel verticalRel; KoShapeAnchor::HorizontalPos horizontalPos; KoShapeAnchor::HorizontalRel horizontalRel; QString wrapInfluenceOnPosition; bool flowWithText; KoShapeAnchor::AnchorType anchorType; KoShapeAnchor::PlacementStrategy *placementStrategy; int pageNumber; KoShapeAnchor::TextLocation *textLocation; }; KoShapeAnchor::KoShapeAnchor(KoShape *shape) : d(new Private(shape)) { } KoShapeAnchor::~KoShapeAnchor() { if (d->placementStrategy != 0) { delete d->placementStrategy; } delete d; } KoShape *KoShapeAnchor::shape() const { return d->shape; } KoShapeAnchor::AnchorType KoShapeAnchor::anchorType() const { return d->anchorType; } void KoShapeAnchor::setHorizontalPos(HorizontalPos hp) { d->horizontalPos = hp; } KoShapeAnchor::HorizontalPos KoShapeAnchor::horizontalPos() const { return d->horizontalPos; } void KoShapeAnchor::setHorizontalRel(HorizontalRel hr) { d->horizontalRel = hr; } KoShapeAnchor::HorizontalRel KoShapeAnchor::horizontalRel() const { return d->horizontalRel; } void KoShapeAnchor::setVerticalPos(VerticalPos vp) { d->verticalPos = vp; } KoShapeAnchor::VerticalPos KoShapeAnchor::verticalPos() const { return d->verticalPos; } void KoShapeAnchor::setVerticalRel(VerticalRel vr) { d->verticalRel = vr; } KoShapeAnchor::VerticalRel KoShapeAnchor::verticalRel() const { return d->verticalRel; } QString KoShapeAnchor::wrapInfluenceOnPosition() const { return d->wrapInfluenceOnPosition; } bool KoShapeAnchor::flowWithText() const { return d->flowWithText; } int KoShapeAnchor::pageNumber() const { return d->pageNumber; } const QPointF &KoShapeAnchor::offset() const { return d->offset; } void KoShapeAnchor::setOffset(const QPointF &offset) { d->offset = offset; } -void KoShapeAnchor::saveOdf(KoShapeSavingContext &context) const -{ - // anchor-type - switch (d->anchorType) { - case AnchorToCharacter: - shape()->setAdditionalAttribute("text:anchor-type", "char"); - break; - case AnchorAsCharacter: - shape()->setAdditionalAttribute("text:anchor-type", "as-char"); - break; - case AnchorParagraph: - shape()->setAdditionalAttribute("text:anchor-type", "paragraph"); - break; - case AnchorPage: - shape()->setAdditionalAttribute("text:anchor-type", "page"); - break; - default: - break; - } - - // vertical-pos - switch (d->verticalPos) { - case VBelow: - shape()->setAdditionalStyleAttribute("style:vertical-pos", "below"); - break; - case VBottom: - shape()->setAdditionalStyleAttribute("style:vertical-pos", "bottom"); - break; - case VFromTop: - shape()->setAdditionalStyleAttribute("style:vertical-pos", "from-top"); - break; - case VMiddle: - shape()->setAdditionalStyleAttribute("style:vertical-pos", "middle"); - break; - case VTop: - shape()->setAdditionalStyleAttribute("style:vertical-pos", "top"); - break; - default: - break; - } - - // vertical-rel - switch (d->verticalRel) { - case VBaseline: - shape()->setAdditionalStyleAttribute("style:vertical-rel", "baseline"); - break; - case VChar: - shape()->setAdditionalStyleAttribute("style:vertical-rel", "char"); - break; - case VFrame: - shape()->setAdditionalStyleAttribute("style:vertical-rel", "frame"); - break; - case VFrameContent: - shape()->setAdditionalStyleAttribute("style:vertical-rel", "frame-content"); - break; - case VLine: - shape()->setAdditionalStyleAttribute("style:vertical-rel", "line"); - break; - case VPage: - shape()->setAdditionalStyleAttribute("style:vertical-rel", "page"); - break; - case VPageContent: - shape()->setAdditionalStyleAttribute("style:vertical-rel", "page-content"); - break; - case VParagraph: - shape()->setAdditionalStyleAttribute("style:vertical-rel", "paragraph"); - break; - case VParagraphContent: - shape()->setAdditionalStyleAttribute("style:vertical-rel", "paragraph-content"); - break; - case VText: - shape()->setAdditionalStyleAttribute("style:vertical-rel", "text"); - break; - default: - break; - } - - // horizontal-pos - switch (d->horizontalPos) { - case HCenter: - shape()->setAdditionalStyleAttribute("style:horizontal-pos", "center"); - break; - case HFromInside: - shape()->setAdditionalStyleAttribute("style:horizontal-pos", "from-inside"); - break; - case HFromLeft: - shape()->setAdditionalStyleAttribute("style:horizontal-pos", "from-left"); - break; - case HInside: - shape()->setAdditionalStyleAttribute("style:horizontal-posl", "inside"); - break; - case HLeft: - shape()->setAdditionalStyleAttribute("style:horizontal-pos", "left"); - break; - case HOutside: - shape()->setAdditionalStyleAttribute("style:horizontal-pos", "outside"); - break; - case HRight: - shape()->setAdditionalStyleAttribute("style:horizontal-pos", "right"); - break; - default: - break; - } - - // horizontal-rel - switch (d->horizontalRel) { - case HChar: - shape()->setAdditionalStyleAttribute("style:horizontal-rel", "char"); - break; - case HPage: - shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page"); - break; - case HPageContent: - shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page-content"); - break; - case HPageStartMargin: - shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page-start-margin"); - break; - case HPageEndMargin: - shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page-end-margin"); - break; - case HFrame: - shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame"); - break; - case HFrameContent: - shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame-content"); - break; - case HFrameEndMargin: - shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame-end-margin"); - break; - case HFrameStartMargin: - shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame-start-margin"); - break; - case HParagraph: - shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph"); - break; - case HParagraphContent: - shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph-content"); - break; - case HParagraphEndMargin: - shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph-end-margin"); - break; - case HParagraphStartMargin: - shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph-start-margin"); - break; - default: - break; - } - - if (!d->wrapInfluenceOnPosition.isEmpty()) { - shape()->setAdditionalStyleAttribute("draw:wrap-influence-on-position", d->wrapInfluenceOnPosition); - } - - if (d->flowWithText) { - shape()->setAdditionalStyleAttribute("style:flow-with-text", "true"); - } else { - shape()->setAdditionalStyleAttribute("style:flow-with-text", "false"); - } - - if (shape()->parent()) {// an anchor may not yet have been layout-ed - QTransform parentMatrix = shape()->parent()->absoluteTransformation().inverted(); - QTransform shapeMatrix = shape()->absoluteTransformation(); - - qreal dx = d->offset.x() - shapeMatrix.dx()*parentMatrix.m11() - - shapeMatrix.dy()*parentMatrix.m21(); - qreal dy = d->offset.y() - shapeMatrix.dx()*parentMatrix.m12() - - shapeMatrix.dy()*parentMatrix.m22(); - context.addShapeOffset(shape(), QTransform(parentMatrix.m11(),parentMatrix.m12(), - parentMatrix.m21(),parentMatrix.m22(), - dx,dy)); - } - - shape()->saveOdf(context); - - context.removeShapeOffset(shape()); -} - -bool KoShapeAnchor::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) -{ - d->offset = shape()->position(); - - QString anchorType = shape()->additionalAttribute("text:anchor-type"); - - if (anchorType == "char") { - d->anchorType = AnchorToCharacter; - } else if (anchorType == "as-char") { - d->anchorType = AnchorAsCharacter; - d->horizontalRel = HChar; - d->horizontalPos = HLeft; - } else if (anchorType == "paragraph") { - d->anchorType = AnchorParagraph; - } else { - d->anchorType = AnchorPage; - // it has different defaults at least LO thinks so - ODF doesn't define defaults for this - d->horizontalPos = HFromLeft; - d->verticalPos = VFromTop; - d->horizontalRel = HPage; - d->verticalRel = VPage; - } - - if (anchorType == "page" && shape()->hasAdditionalAttribute("text:anchor-page-number")) { - d->pageNumber = shape()->additionalAttribute("text:anchor-page-number").toInt(); - if (d->pageNumber <= 0) { - // invalid if the page-number is invalid (OO.org does the same) - // see https://bugs.kde.org/show_bug.cgi?id=281869 - d->pageNumber = -1; - } - } else { - d->pageNumber = -1; - } - // always make it invisible or it will create empty rects on the first page - // during initial layout. This is because only when we layout it's final page is - // the shape moved away from page 1 - // in KWRootAreaProvider of textlayout it's set back to visible - shape()->setVisible(false); - - // load settings from graphic style - KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); - styleStack.save(); - if (element.hasAttributeNS(KoXmlNS::draw, "style-name")) { - context.odfLoadingContext().fillStyleStack(element, KoXmlNS::draw, "style-name", "graphic"); - styleStack.setTypeProperties("graphic"); - } - QString verticalPos = styleStack.property(KoXmlNS::style, "vertical-pos"); - QString verticalRel = styleStack.property(KoXmlNS::style, "vertical-rel"); - QString horizontalPos = styleStack.property(KoXmlNS::style, "horizontal-pos"); - QString horizontalRel = styleStack.property(KoXmlNS::style, "horizontal-rel"); - d->wrapInfluenceOnPosition = styleStack.property(KoXmlNS::draw, "wrap-influence-on-position"); - QString flowWithText = styleStack.property(KoXmlNS::style, "flow-with-text"); - d->flowWithText = flowWithText.isEmpty() ? false : flowWithText == "true"; - styleStack.restore(); - - // vertical-pos - if (verticalPos == "below") {//svg:y attribute is ignored - d->verticalPos = VBelow; - d->offset.setY(0); - } else if (verticalPos == "bottom") {//svg:y attribute is ignored - d->verticalPos = VBottom; - d->offset.setY(-shape()->size().height()); - } else if (verticalPos == "from-top") { - d->verticalPos = VFromTop; - } else if (verticalPos == "middle") {//svg:y attribute is ignored - d->verticalPos = VMiddle; - d->offset.setY(-(shape()->size().height()/2)); - } else if (verticalPos == "top") {//svg:y attribute is ignored - d->verticalPos = VTop; - d->offset.setY(0); - } - - // vertical-rel - if (verticalRel == "baseline") - d->verticalRel = VBaseline; - else if (verticalRel == "char") - d->verticalRel = VChar; - else if (verticalRel == "frame") - d->verticalRel = VFrame; - else if (verticalRel == "frame-content") - d->verticalRel = VFrameContent; - else if (verticalRel == "line") - d->verticalRel = VLine; - else if (verticalRel == "page") - d->verticalRel = VPage; - else if (verticalRel == "page-content") - d->verticalRel = VPageContent; - else if (verticalRel == "paragraph") - d->verticalRel = VParagraph; - else if (verticalRel == "paragraph-content") - d->verticalRel = VParagraphContent; - else if (verticalRel == "text") - d->verticalRel = VText; - - // horizontal-pos - if (horizontalPos == "center") {//svg:x attribute is ignored - d->horizontalPos = HCenter; - d->offset.setX(-(shape()->size().width()/2)); - } else if (horizontalPos == "from-inside") { - d->horizontalPos = HFromInside; - } else if (horizontalPos == "from-left") { - d->horizontalPos = HFromLeft; - } else if (horizontalPos == "inside") {//svg:x attribute is ignored - d->horizontalPos = HInside; - d->offset.setX(0); - } else if (horizontalPos == "left") {//svg:x attribute is ignored - d->horizontalPos = HLeft; - d->offset.setX(0); - }else if (horizontalPos == "outside") {//svg:x attribute is ignored - d->horizontalPos = HOutside; - d->offset.setX(-shape()->size().width()); - }else if (horizontalPos == "right") {//svg:x attribute is ignored - d->horizontalPos = HRight; - d->offset.setX(-shape()->size().width()); - } - - // horizontal-rel - if (horizontalRel == "char") - d->horizontalRel = HChar; - else if (horizontalRel == "page") - d->horizontalRel = HPage; - else if (horizontalRel == "page-content") - d->horizontalRel = HPageContent; - else if (horizontalRel == "page-start-margin") - d->horizontalRel = HPageStartMargin; - else if (horizontalRel == "page-end-margin") - d->horizontalRel = HPageEndMargin; - else if (horizontalRel == "frame") - d->horizontalRel = HFrame; - else if (horizontalRel == "frame-content") - d->horizontalRel = HFrameContent; - else if (horizontalRel == "frame-end-margin") - d->horizontalRel = HFrameEndMargin; - else if (horizontalRel == "frame-start-margin") - d->horizontalRel = HFrameStartMargin; - else if (horizontalRel == "paragraph") - d->horizontalRel = HParagraph; - else if (horizontalRel == "paragraph-content") - d->horizontalRel = HParagraphContent; - else if (horizontalRel == "paragraph-end-margin") - d->horizontalRel = HParagraphEndMargin; - else if (horizontalRel == "paragraph-start-margin") - d->horizontalRel = HParagraphStartMargin; - - // if svg:x or svg:y should be ignored set new position - shape()->setPosition(d->offset); - - return true; -} - void KoShapeAnchor::setAnchorType(KoShapeAnchor::AnchorType type) { d->anchorType = type; if (type == AnchorAsCharacter) { d->horizontalRel = HChar; d->horizontalPos = HLeft; } } KoShapeAnchor::TextLocation *KoShapeAnchor::textLocation() const { return d->textLocation; } void KoShapeAnchor::setTextLocation(TextLocation *textLocation) { d->textLocation = textLocation; } KoShapeAnchor::PlacementStrategy *KoShapeAnchor::placementStrategy() const { return d->placementStrategy; } void KoShapeAnchor::setPlacementStrategy(PlacementStrategy *placementStrategy) { if (placementStrategy != d->placementStrategy) { delete d->placementStrategy; d->placementStrategy = placementStrategy; } } diff --git a/libs/flake/KoShapeAnchor.h b/libs/flake/KoShapeAnchor.h index 06b3465e7e..82f458c47d 100644 --- a/libs/flake/KoShapeAnchor.h +++ b/libs/flake/KoShapeAnchor.h @@ -1,262 +1,253 @@ /* This file is part of the KDE project * Copyright (C) 2007, 2009 Thomas Zander * Copyright (C) 2011 Matus Hanzes * Copyright (C) 2013 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 KOSHAPEANCHOR_H #define KOSHAPEANCHOR_H #include "kritaflake_export.h" class KoShape; #include class KoShapeLoadingContext; class KoShapeSavingContext; class KoShapeAnchorPrivate; class QTextDocument; class QPointF; class QString; /** * This class is the object that explains how a shape is anchored to something. * * The anchored shape will be positioned (in supporting applications) based on the properties * defined in this class. * * This class can be used in three different ways: * -page anchor * -as-char * -char, paragraph anchor * * If it's a page anchor it just provide the info about how the shape relates to a page with a specific * page number. * * For the other types of anchoring it has to have a TextLocation in a QTextDocument. This TextLocation * can either be an inline character (type as-char) or a position (type char or paragraph) The * KoShapeAnchor and TextLocation connects the anchored-shape to the text flow so the anchored shape * can be repositioned on the canvas if new text is inserted or removed before the anchor character. * * For as-char, char and paragraph use cases: * @see KoAnchorInlineObject * @see KoAnchorTextRange * which are both implemented as subclasses of TextLocation * - * The position of the shape relative to the anchor is called the offset. It's loaded by loadOdf(). + * The position of the shape relative to the anchor is called the offset. * @see PlacementStrategy for more information about the layout of anchors/shapes. */ class KRITAFLAKE_EXPORT KoShapeAnchor { public: /** * This class is an interface that positions the shape linked to text anchor */ class PlacementStrategy { public: PlacementStrategy(){}; virtual ~PlacementStrategy(){}; /** * Reparent the anchored shape to not have a parent shape container (and model) * */ virtual void detachFromModel() = 0; /** * Reparent the anchored shape under an appropriate shape container (and model) * * If needed, it changes the parent KoShapeContainerModel and KoShapeContainer of the anchored * shape. */ virtual void updateContainerModel() = 0; }; class TextLocation { public: TextLocation(){}; virtual ~TextLocation(){}; virtual const QTextDocument *document() const = 0; virtual int position() const = 0; }; enum HorizontalPos { HCenter, HFromInside, HFromLeft, HInside, HLeft, HOutside, HRight }; enum HorizontalRel { //NOTE: update KWAnchoringProperties if you change this HChar, HPage, HPageContent, HPageStartMargin, HPageEndMargin, HFrame, HFrameContent, HFrameEndMargin, HFrameStartMargin, HParagraph, HParagraphContent, HParagraphEndMargin, HParagraphStartMargin }; enum VerticalPos { VBelow, VBottom, VFromTop, VMiddle, VTop }; enum VerticalRel { //NOTE: update KWAnchoringProperties if you change this VBaseline, VChar, VFrame, VFrameContent, VLine, VPage, VPageContent, VParagraph, VParagraphContent, VText }; enum AnchorType { AnchorAsCharacter, AnchorToCharacter, AnchorParagraph, AnchorPage }; /** * Constructor for an in-place anchor. * @param shape the anchored shape that this anchor links to. */ explicit KoShapeAnchor(KoShape *shape); virtual ~KoShapeAnchor(); /** * Return the shape that is linked to from the text anchor. */ KoShape *shape() const; /** * Returns the type of the anchor. * * The text:anchor-type attribute specifies how a frame is bound to a * text document. The anchor position is the point at which a frame is * bound to a text document. The defined values for the text:anchor-type * attribute are; * * - as-char * There is no anchor position. The drawing shape behaves like a * character. * - char * The character after the drawing shape element. * - frame * The parent text box that the current drawing shape element is * contained in. * FIXME we don't support type frame * - page * The page that has the same physical page number as the value of the * text:anchor-page-number attribute that is attached to the drawing * shape element. * - paragraph * The paragraph that the current drawing shape element is contained in. */ AnchorType anchorType() const; /** * Set how the anchor behaves */ void setAnchorType(AnchorType type); /// set the current vertical-pos void setHorizontalPos(HorizontalPos); /// return the current vertical-pos HorizontalPos horizontalPos() const; /// set the current vertical-rel void setHorizontalRel(HorizontalRel); /// return the current vertical-rel HorizontalRel horizontalRel() const; /// set the current horizontal-pos void setVerticalPos(VerticalPos); /// return the current horizontal-pos VerticalPos verticalPos() const; /// set the current horizontal-rel void setVerticalRel(VerticalRel); /// return the current horizontal-rel VerticalRel verticalRel() const; /// return the wrap influence on position QString wrapInfluenceOnPosition() const; /// return if flow-with-text (odf attribute) bool flowWithText() const; /// return the page number of the shape (valid with page anchoring, -1 indicates auto). int pageNumber() const; /// return the offset of the shape from the anchor. const QPointF &offset() const; /// set the new offset of the shape. Causes a new layout soon. void setOffset(const QPointF &offset); - /// Load the additional attributes. - /// This will also make the shape invisible so it doesn't mess up any layout - /// before it's ready to be placed where it belongs - /// The textlayout should make it visible again - bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context); - - /// Save the additional attributes. - void saveOdf(KoShapeSavingContext &context) const; - /// Get extra data structure that is what is actually inside a text document TextLocation *textLocation() const; /// Set extra data structure that is what is actually inside a text document /// We do NOT take ownership (may change in the future) void setTextLocation(TextLocation *textLocation); /// Get placement strategy which is used to position shape linked to text anchor PlacementStrategy *placementStrategy() const; /// Set placement strategy which is used to position shape linked to text anchor /// We take owner ship and will make sure the strategy is deleted void setPlacementStrategy(PlacementStrategy *placementStrategy); private: class Private; Private * const d; }; #endif diff --git a/libs/flake/KoShapeBackground.h b/libs/flake/KoShapeBackground.h index 51f2d762c1..365a125e29 100644 --- a/libs/flake/KoShapeBackground.h +++ b/libs/flake/KoShapeBackground.h @@ -1,68 +1,58 @@ /* This file is part of the KDE project * 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. */ #ifndef KOSHAPEBACKGROUND_H #define KOSHAPEBACKGROUND_H #include "kritaflake_export.h" #include class QSizeF; class QPainter; class QPainterPath; class KoGenStyle; class KoShapeSavingContext; class KoOdfLoadingContext; class KoShapePaintingContext; /** * This is the base class for shape backgrounds. * Derived classes are used to paint the background of * a shape within a given painter path. */ class KRITAFLAKE_EXPORT KoShapeBackground { public: KoShapeBackground(); virtual ~KoShapeBackground(); /// Paints the background using the given fill path virtual void paint(QPainter &painter, KoShapePaintingContext &context, const QPainterPath &fillPath) const = 0; /// Returns if the background has some transparency. virtual bool hasTransparency() const; virtual bool compareTo(const KoShapeBackground *other) const = 0; - /** - * Fills the style object - * @param style object - * @param context used for saving - */ - virtual void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) = 0; - - /// load background from odf styles - virtual bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) = 0; - virtual explicit operator bool() const { return true; } }; #endif // KOSHAPEBACKGROUND_H diff --git a/libs/flake/KoShapeFactoryBase.cpp b/libs/flake/KoShapeFactoryBase.cpp index 476817a5e5..4ee12c4321 100644 --- a/libs/flake/KoShapeFactoryBase.cpp +++ b/libs/flake/KoShapeFactoryBase.cpp @@ -1,259 +1,250 @@ /* This file is part of the KDE project * Copyright (c) 2006 Boudewijn Rempt (boud@valdyas.org) * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2008 C. Boemann * Copyright (C) 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. */ #include "KoShapeFactoryBase.h" #include #include "KoDocumentResourceManager.h" #include "KoDeferredShapeFactoryBase.h" #include "KoShape.h" #include "KoShapeLoadingContext.h" #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoShapeFactoryBase::Private { public: Private(const QString &_id, const QString &_name, const QString &_deferredPluginName) : deferredFactory(0), deferredPluginName(_deferredPluginName), id(_id), name(_name), loadingPriority(0), hidden(false) { } ~Private() { Q_FOREACH (const KoShapeTemplate & t, templates) delete t.properties; templates.clear(); } KoDeferredShapeFactoryBase *deferredFactory; QMutex pluginLoadingMutex; QString deferredPluginName; QList templates; const QString id; const QString name; QString family; QString tooltip; QString iconName; int loadingPriority; QList > xmlElements; // xml name space -> xml element names bool hidden; QList > resourceManagers; }; KoShapeFactoryBase::KoShapeFactoryBase(const QString &id, const QString &name, const QString &deferredPluginName) : d(new Private(id, name, deferredPluginName)) { } KoShapeFactoryBase::~KoShapeFactoryBase() { delete d; } QString KoShapeFactoryBase::toolTip() const { return d->tooltip; } QString KoShapeFactoryBase::iconName() const { return d->iconName; } QString KoShapeFactoryBase::name() const { return d->name; } QString KoShapeFactoryBase::family() const { return d->family; } int KoShapeFactoryBase::loadingPriority() const { return d->loadingPriority; } QList > KoShapeFactoryBase::odfElements() const { return d->xmlElements; } void KoShapeFactoryBase::addTemplate(const KoShapeTemplate ¶ms) { KoShapeTemplate tmplate = params; tmplate.id = d->id; d->templates.append(tmplate); } void KoShapeFactoryBase::setToolTip(const QString & tooltip) { d->tooltip = tooltip; } void KoShapeFactoryBase::setIconName(const char *iconName) { d->iconName = QLatin1String(iconName); } void KoShapeFactoryBase::setFamily(const QString & family) { d->family = family; } QString KoShapeFactoryBase::id() const { return d->id; } QList KoShapeFactoryBase::templates() const { return d->templates; } void KoShapeFactoryBase::setLoadingPriority(int priority) { d->loadingPriority = priority; } void KoShapeFactoryBase::setXmlElementNames(const QString & nameSpace, const QStringList & names) { d->xmlElements.clear(); d->xmlElements.append(QPair(nameSpace, names)); } void KoShapeFactoryBase::setXmlElements(const QList > &elementNamesList) { d->xmlElements = elementNamesList; } bool KoShapeFactoryBase::hidden() const { return d->hidden; } void KoShapeFactoryBase::setHidden(bool hidden) { d->hidden = hidden; } void KoShapeFactoryBase::newDocumentResourceManager(KoDocumentResourceManager *manager) const { d->resourceManagers.append(manager); connect(manager, SIGNAL(destroyed(QObject*)), this, SLOT(pruneDocumentResourceManager(QObject*))); } KoShape *KoShapeFactoryBase::createDefaultShape(KoDocumentResourceManager *documentResources) const { if (!d->deferredPluginName.isEmpty()) { const_cast(this)->getDeferredPlugin(); Q_ASSERT(d->deferredFactory); if (d->deferredFactory) { return d->deferredFactory->createDefaultShape(documentResources); } } return 0; } KoShape *KoShapeFactoryBase::createShape(const KoProperties* properties, KoDocumentResourceManager *documentResources) const { if (!d->deferredPluginName.isEmpty()) { const_cast(this)->getDeferredPlugin(); Q_ASSERT(d->deferredFactory); if (d->deferredFactory) { return d->deferredFactory->createShape(properties, documentResources); } } return createDefaultShape(documentResources); } -KoShape *KoShapeFactoryBase::createShapeFromOdf(const KoXmlElement &element, KoShapeLoadingContext &context) +KoShape *KoShapeFactoryBase::createShapeFromXML(const KoXmlElement &element, KoShapeLoadingContext &context) { KoShape *shape = createDefaultShape(context.documentResourceManager()); if (!shape) return 0; if (shape->shapeId().isEmpty()) shape->setShapeId(id()); - context.odfLoadingContext().styleStack().save(); - bool loaded = shape->loadOdf(element, context); - context.odfLoadingContext().styleStack().restore(); - - if (!loaded) { - delete shape; - return 0; - } - return shape; } void KoShapeFactoryBase::getDeferredPlugin() { QMutexLocker(&d->pluginLoadingMutex); if (d->deferredFactory) return; const QList offers = KoJsonTrader::instance()->query("Krita/Deferred", QString()); Q_ASSERT(offers.size() > 0); Q_FOREACH (QPluginLoader *pluginLoader, offers) { KPluginFactory *factory = qobject_cast(pluginLoader->instance()); KoDeferredShapeFactoryBase *plugin = factory->create(this, QVariantList()); if (plugin && plugin->deferredPluginName() == d->deferredPluginName) { d->deferredFactory = plugin; } } qDeleteAll(offers); } void KoShapeFactoryBase::pruneDocumentResourceManager(QObject *) { QList > rms; Q_FOREACH(QPointer rm, d->resourceManagers) { if (rm) { rms << rm; } } d->resourceManagers = rms; } diff --git a/libs/flake/KoShapeFactoryBase.h b/libs/flake/KoShapeFactoryBase.h index 7744cefffa..221b43841d 100644 --- a/libs/flake/KoShapeFactoryBase.h +++ b/libs/flake/KoShapeFactoryBase.h @@ -1,304 +1,304 @@ /* This file is part of the KDE project * Copyright (c) 2006 Boudewijn Rempt (boud@valdyas.org) * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 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 KOSHAPEFACTORYBASE_H #define KOSHAPEFACTORYBASE_H #include #include #include #include "kritaflake_export.h" #include class KoShape; class KoProperties; class KoShapeConfigWidgetBase; class KoShapeLoadingContext; class KoDocumentResourceManager; class QStringList; #define SHAPETEMPLATE_MIMETYPE "application/x-flake-shapetemplate" #define SHAPEID_MIMETYPE "application/x-flake-shapeId" /** * Contains a KoProperties object that describes the settings of a * particular variant of a shape object, together with a name, a description * and an icon for use in the user interface. */ struct KRITAFLAKE_EXPORT KoShapeTemplate { KoShapeTemplate() { properties = 0; } QString id; ///< The id of the shape QString templateId; ///< The id of this particular template - only has to be unique with the shape QString name; ///< The name to be shown for this template QString family; ///< The family of the shape (possible values are: "funny","arrow") QString toolTip; ///< The tooltip text for the template QString iconName; ///< Icon name /** * The properties which, when passed to the KoShapeFactoryBase::createShape() method * result in the shape this template represents. */ const KoProperties *properties; }; /** * A factory for KoShape objects. * The baseclass for all shape plugins. Each plugin that ships a KoShape should also * ship a factory. That factory will extend this class and set variable data like * a toolTip and icon in the constructor of that extending class. * * An example usage would be: @code class MyShapeFactory : public KoShapeFactoryBase { public: MyShapeFactory() : KoShapeFactoryBase("MyShape", i18n("My Shape")) { setToolTip(i18n("A nice shape")); } ~MyShapeFactory() {} // more methods here }; @endcode * After you created the factory you should create a plugin that can announce the factory to the * KoShapeRegistry. See the KoPluginLoader as well. */ class KRITAFLAKE_EXPORT KoShapeFactoryBase : public QObject { Q_OBJECT public: /** * Create the new factory * @param id a string that will be used internally for referencing the shape, for * example for use by the KoToolBase::activateTemporary. * @param name the user visible name of the shape this factory creates. */ KoShapeFactoryBase(const QString &id, const QString &name, const QString &deferredPluginName = QString()); ~KoShapeFactoryBase() override; /** * Create a list of option panels to show on creating a new shape. * The shape type this factory creates may have general or specific setting panels * that will be shown after inserting a new shape. * The first item in the list will be shown as the first tab in the list of panels, * behind all app specific panels. * This is a separate list as set by setOptionPanels() and fetched by panelFactories() */ virtual QList createShapeOptionPanels() { return QList(); } /** * return the id for the shape this factory creates. * @return the id for the shape this factory creates. */ QString id() const; /** * Return all the templates this factory knows about. * Each template shows a different way to create a shape this factory is specialized in. */ QList templates() const; /** * return a translated tooltip Text for a selector of shapes * @return a translated tooltip Text */ QString toolTip() const; /** * return the basename of the icon for a selector of shapes * @return the basename of the icon for a selector of shapes */ QString iconName() const; /** * return the user visible (and translated) name to be seen by the user. * @return the user visible (and translated) name to be seen by the user. */ QString name() const; /** * return the non-visible name of the family the default shape belongs to. * @return the family name. */ QString family() const; /// lower prio means the shape is more generic and will be checked later int loadingPriority() const; /** * The list of namespaces to the supported elements the factory supports. */ QList > odfElements() const; /// returns true if this shapeFactory is able to load the ODF type /// started at argument element. ('draw:line' / 'draw:frame' / etc) virtual bool supports(const KoXmlElement &element, KoShapeLoadingContext &context) const = 0; /** * The hidden boolean requests if the shape should be hidden in the * shape selector or shown with all its templates. * The default is false * @see setHidden() */ bool hidden() const; /** * This method is called whenever there is a new document resource * manager that is created. The factory may reimplement this in * order to get existing resources or put factory specific resources in. * In case the factory creates new resources it is advised to parent * them to the manager (which is a QObject) for memory management * purposes. * * FIXME: this method is only used by Tables. We should refactor so * it is no longer necessary. * * NOTE: this actually is also used somehow to create the imagecollection * for the picture shape? * * NOTE: we store the documentmanagers in a list, and remove them * from the list on delete. * * @param manager the new manager */ virtual void newDocumentResourceManager(KoDocumentResourceManager *manager) const; /** * This method should be implemented by factories to create a shape that the user * gets when doing a base insert. For example from a script. The created shape * should have its values set to good defaults that the user can then adjust further if * needed. Including the KoShape:setShapeId(), with the Id from this factory * The default shape position is not relevant, it will be moved by the caller. * @param documentResources the resources manager that has all the document wide * resources which can be used to create the object. * @return a new shape * @see createShape() newDocumentResourceManager() */ virtual KoShape *createDefaultShape(KoDocumentResourceManager *documentResources = 0) const; /** * This method should be implemented by factories to create a shape based on a set of * properties that are specifically made for this shape-type. * This method should also set this factories shapeId on the shape using KoShape::setShapeId() * The default implementation just ignores 'params' and calls createDefaultShape() * @return a new shape * @param params the parameters to use when creating the shape * @param documentResources the resources manager that has all the document wide * resources which can be used to create the object. * @see createDefaultShape() newDocumentResourceManager() addTemplate() * @see KoShapeTemplate::properties */ virtual KoShape *createShape(const KoProperties *params, KoDocumentResourceManager *documentResources = 0) const; /** * This method provides the default implementation for creating a shape * from a specified xml element of an odf document. * Most derived factories do not need to reimplement this method, however if a factory * has some special requirements or does something special it is still possible. * One example is creating different shapes depending on the content of the passed * xml element. */ - virtual KoShape *createShapeFromOdf(const KoXmlElement &element, KoShapeLoadingContext &context); + virtual KoShape *createShapeFromXML(const KoXmlElement &element, KoShapeLoadingContext &context); protected: /** * Add a template with the properties of a specific type of shape this factory can generate * using the createShape() method. The factory will take ownership of the properties object * to which the member @p properties of @p params points to and destroy it only in its own destructor. * @param params the new template this factory knows to produce */ void addTemplate(const KoShapeTemplate ¶ms); /** * Set the tooltip to be used for a selector of shapes * @param tooltip the tooltip */ void setToolTip(const QString &tooltip); /** * Set an icon to be used in a selector of shapes * @param iconName the basename (without extension) of the icon */ void setIconName(const char *iconName); /** * Set the family name of the default shape * @param family the family name of the default shape this factory creates. * for example "funny", "arrows", "geometrics". Use "" for default */ void setFamily(const QString &family); /** * Set the loading priority for this icon; higher priority means * the shape is more specific which means it will be earlier in * the queue to try loading a particular odf element. */ void setLoadingPriority(int priority); /** * Set the namespace and element tags used for quick checking whether this shapefactory * is able to create a shape from xml identified by this element * name. * * @param nameSpace the ODF name space (like * urn:oasis:names:tc:opendocument:xmlns:text:1.0, * take it from KoXmlNS.h) * @param elementNames the name of the element itself, like "path" * */ void setXmlElementNames(const QString &nameSpace, const QStringList &elementNames); /** * Set the namespaces and according element tags used for quick checking whether this shapefactory * is able to create a shape from xml identified by this element * name. * * @param elementNamesList containing a list of namespace (like * urn:oasis:names:tc:opendocument:xmlns:text:1.0, * take it from KoXmlNS.h) to a list of elementName of the element itself, like "path" */ void setXmlElements(const QList > &elementNamesList); /** * The hidden boolean requests if the shape should be hidden in the * shape selector or shown with all its templates. * The default is false * @see hidden() */ void setHidden(bool hidden); private: void getDeferredPlugin(); private Q_SLOTS: /// called whenever a document KoDocumentResourceManager is deleted void pruneDocumentResourceManager(QObject *); private: class Private; Private * const d; }; #endif diff --git a/libs/flake/KoShapeGroup.cpp b/libs/flake/KoShapeGroup.cpp index 0ccc7d184d..34403b4c25 100644 --- a/libs/flake/KoShapeGroup.cpp +++ b/libs/flake/KoShapeGroup.cpp @@ -1,274 +1,212 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeGroup.h" #include "KoShapeContainerModel.h" #include "KoShapeContainer_p.h" #include "KoShapeLayer.h" #include "SimpleShapeContainerModel.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoXmlWriter.h" #include "KoXmlReader.h" #include "KoShapeRegistry.h" #include "KoShapeStrokeModel.h" #include "KoShapeShadow.h" #include "KoInsets.h" #include #include class ShapeGroupContainerModel : public SimpleShapeContainerModel { public: ShapeGroupContainerModel(KoShapeGroup *group) : m_group(group) {} ~ShapeGroupContainerModel() override {} ShapeGroupContainerModel(const ShapeGroupContainerModel &rhs, KoShapeGroup *group) : SimpleShapeContainerModel(rhs), m_group(group) { } void add(KoShape *child) override { SimpleShapeContainerModel::add(child); m_group->invalidateSizeCache(); } void remove(KoShape *child) override { SimpleShapeContainerModel::remove(child); m_group->invalidateSizeCache(); } void childChanged(KoShape *shape, KoShape::ChangeType type) override { SimpleShapeContainerModel::childChanged(shape, type); //debugFlake << type; switch (type) { case KoShape::PositionChanged: case KoShape::RotationChanged: case KoShape::ScaleChanged: case KoShape::ShearChanged: case KoShape::SizeChanged: case KoShape::GenericMatrixChange: case KoShape::ParameterChanged: case KoShape::ClipPathChanged : case KoShape::ClipMaskChanged : m_group->invalidateSizeCache(); break; default: break; } } private: // members KoShapeGroup * m_group; }; class KoShapeGroup::Private { public: Private() {} Private(const Private &) {} virtual ~Private() = default; mutable QRectF savedOutlineRect; mutable bool sizeCached = false; }; KoShapeGroup::KoShapeGroup() : KoShapeContainer() , d(new Private) { setModelInit(new ShapeGroupContainerModel(this)); } KoShapeGroup::KoShapeGroup(const KoShapeGroup &rhs) : KoShapeContainer(rhs) , d(new Private(*rhs.d)) { ShapeGroupContainerModel *otherModel = dynamic_cast(rhs.model()); KIS_ASSERT_RECOVER_RETURN(otherModel); setModelInit(new ShapeGroupContainerModel(*otherModel, this)); } KoShapeGroup::~KoShapeGroup() { /** * HACK alert: model will use KoShapeGroup::invalidateSizeCache(), which uses * KoShapeGroup's d-pointer. We have to manually remove child shapes from the * model in the destructor of KoShapeGroup as the instance d is no longer accessible * since ~KoShapeGroup() is executed */ model()->deleteOwnedShapes(); } KoShape *KoShapeGroup::cloneShape() const { return new KoShapeGroup(*this); } void KoShapeGroup::paintComponent(QPainter &painter, KoShapePaintingContext &) const { Q_UNUSED(painter); } bool KoShapeGroup::hitTest(const QPointF &position) const { Q_UNUSED(position); return false; } void KoShapeGroup::tryUpdateCachedSize() const { if (!d->sizeCached) { QRectF bound; Q_FOREACH (KoShape *shape, shapes()) { bound |= shape->transformation().mapRect(shape->outlineRect()); } d->savedOutlineRect = bound; KoShape::setSizeImpl(bound.size()); d->sizeCached = true; } } QSizeF KoShapeGroup::size() const { tryUpdateCachedSize(); return KoShape::size(); } void KoShapeGroup::setSize(const QSizeF &size) { QSizeF oldSize = this->size(); if (!shapeCount() || oldSize.isNull()) return; const QTransform scale = QTransform::fromScale(size.width() / oldSize.width(), size.height() / oldSize.height()); setTransformation(scale * transformation()); KoShapeContainer::setSize(size); } QRectF KoShapeGroup::outlineRect() const { tryUpdateCachedSize(); return d->savedOutlineRect; } QRectF KoShapeGroup::boundingRect() const { QRectF groupBound = KoShape::boundingRect(shapes()); if (shadow()) { KoInsets insets; shadow()->insets(insets); groupBound.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } return groupBound; } -void KoShapeGroup::saveOdf(KoShapeSavingContext & context) const -{ - context.xmlWriter().startElement("draw:g"); - saveOdfAttributes(context, (OdfMandatories ^ (OdfLayer | OdfZIndex)) | OdfAdditionalAttributes); - context.xmlWriter().addAttribute("svg:y", position().y()); - - QList shapes = this->shapes(); - std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); - - Q_FOREACH (KoShape* shape, shapes) { - shape->saveOdf(context); - } - context.xmlWriter().endElement(); -} - -bool KoShapeGroup::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) -{ - loadOdfAttributes(element, context, OdfMandatories | OdfStyle | OdfAdditionalAttributes); - - KoXmlElement child; - QMap usedLayers; - forEachElement(child, element) { - KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, context); - if (shape) { - KoShapeLayer *layer = dynamic_cast(shape->parent()); - if (layer) { - usedLayers[layer]++; - } - addShape(shape); - } - } - KoShapeLayer *parent = 0; - int maxUseCount = 0; - // find most used layer and use this as parent for the group - for (QMap::const_iterator it(usedLayers.constBegin()); it != usedLayers.constEnd(); ++it) { - if (it.value() > maxUseCount) { - maxUseCount = it.value(); - parent = it.key(); - } - } - setParent(parent); - - QRectF bound; - bool boundInitialized = false; - Q_FOREACH (KoShape * shape, shapes()) { - if (! boundInitialized) { - bound = shape->boundingRect(); - boundInitialized = true; - } else - bound = bound.united(shape->boundingRect()); - } - - setSize(bound.size()); - d->sizeCached = true; - setPosition(bound.topLeft()); - - Q_FOREACH (KoShape * shape, shapes()) - shape->setAbsolutePosition(shape->absolutePosition() - bound.topLeft()); - - return true; -} - void KoShapeGroup::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(shape); KoShapeContainer::shapeChanged(type, shape); switch (type) { case KoShape::StrokeChanged: break; default: break; } invalidateSizeCache(); } void KoShapeGroup::invalidateSizeCache() { d->sizeCached = false; } diff --git a/libs/flake/KoShapeGroup.h b/libs/flake/KoShapeGroup.h index 3da60e2e43..cb4253036e 100644 --- a/libs/flake/KoShapeGroup.h +++ b/libs/flake/KoShapeGroup.h @@ -1,96 +1,92 @@ /* This file is part of the KDE project * Copyright (C) 2006 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. */ #ifndef KOSHAPEGROUP_H #define KOSHAPEGROUP_H #include "KoShapeContainer.h" #include #include "kritaflake_export.h" class KoShapeSavingContext; class KoShapeLoadingContext; class KoShapeGroupPrivate; /** * Provide grouping for shapes. * The group shape allows you to add children which will then be grouped in selections * and actions. *

If you have a set of shapes that together make up a bigger shape it is often * useful to group them together so the user will perceive the different shapes as * actually being one. This means that if the user clicks on one shape, all shapes * in the group will be selected at once, making the tools that works on * selections alter all of them at the same time. * *

Note that while this object is also a shape, it is not actually visible and the user * can't interact with it. * *

WARNING: this class is NOT threadsafe, it caches the size in an unsafe way */ class KRITAFLAKE_EXPORT KoShapeGroup : public KoShapeContainer { public: /// Constructor KoShapeGroup(); /// destructor ~KoShapeGroup() override; KoShape* cloneShape() const override; /// This implementation is empty since a group is itself not visible. void paintComponent(QPainter &painter, KoShapePaintingContext &paintcontext) const override; /// always returns false since the group itself can't be selected or hit bool hitTest(const QPointF &position) const override; QSizeF size() const override; void setSize(const QSizeF &size) override; QRectF outlineRect() const override; /// a group's boundingRect QRectF boundingRect() const override; - /// reimplemented from KoShape - void saveOdf(KoShapeSavingContext &context) const override; - // reimplemented - bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override; private: friend class ShapeGroupContainerModel; /** * @brief Invalidate the size cache of the group * * The group shape caches the size of itself as it can be quite expensive to recalculate * the size if there are a lot of subshapes. This function is called when the cache needs * to be invalidated. */ void invalidateSizeCache(); private: KoShapeGroup(const KoShapeGroup &rhs); private: void tryUpdateCachedSize() const; void shapeChanged(ChangeType type, KoShape *shape = 0) override; class Private; QScopedPointer d; }; #endif diff --git a/libs/flake/KoShapeLayer.cpp b/libs/flake/KoShapeLayer.cpp index 1a11b5f6f4..d511c0c256 100644 --- a/libs/flake/KoShapeLayer.cpp +++ b/libs/flake/KoShapeLayer.cpp @@ -1,81 +1,56 @@ /* This file is part of the KDE project Copyright (C) 2006-2007 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeLayer.h" #include #include "SimpleShapeContainerModel.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include #include #include KoShapeLayer::KoShapeLayer() : KoShapeContainer(new SimpleShapeContainerModel()) { setSelectable(false); } KoShapeLayer::KoShapeLayer(KoShapeContainerModel *model) : KoShapeContainer(model) { setSelectable(false); } bool KoShapeLayer::hitTest(const QPointF &position) const { Q_UNUSED(position); return false; } QRectF KoShapeLayer::boundingRect() const { return KoShape::boundingRect(shapes()); } -void KoShapeLayer::saveOdf(KoShapeSavingContext & context) const -{ - QList shapes = this->shapes(); - std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); - - Q_FOREACH (KoShape* shape, shapes) { - shape->saveOdf(context); - } -} - -bool KoShapeLayer::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) -{ - // set layer name - setName(element.attributeNS(KoXmlNS::draw, "name")); - // layer locking - setGeometryProtected(element.attributeNS(KoXmlNS::draw, "protected", "false") == "true"); - // layer visibility - setVisible(element.attributeNS(KoXmlNS::draw, "display", "false") != "none"); - - // add layer by name into shape context - context.addLayer(this, name()); - - return true; -} - void KoShapeLayer::paintComponent(QPainter &, KoShapePaintingContext &) const { } diff --git a/libs/flake/KoShapeLayer.h b/libs/flake/KoShapeLayer.h index a9208a6143..63ec15f917 100644 --- a/libs/flake/KoShapeLayer.h +++ b/libs/flake/KoShapeLayer.h @@ -1,54 +1,52 @@ /* This file is part of the KDE project Copyright (C) 2006-2007 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __KOSHAPELAYER_H__ #define __KOSHAPELAYER_H__ #include "KoShapeContainer.h" #include "kritaflake_export.h" /** * Provides arranging shapes into layers. * This makes it possible to have a higher key of a number of objects * in a document. * A layer is always invisible and unselectable. */ class KRITAFLAKE_EXPORT KoShapeLayer : public KoShapeContainer { public: /// The default constructor KoShapeLayer(); /** * Constructor with custom model * @param model the custom modem */ explicit KoShapeLayer(KoShapeContainerModel *model); /** * Empty implementation, as the layer itself is not visible */ void paintComponent(QPainter &painter, KoShapePaintingContext &paintcontext) const override; bool hitTest(const QPointF &position) const override; QRectF boundingRect() const override; - void saveOdf(KoShapeSavingContext & context) const override; - bool loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) override; }; #endif // __KOSHAPELAYER_H__ diff --git a/libs/flake/KoShapeLoadingContext.cpp b/libs/flake/KoShapeLoadingContext.cpp index 6d6d45ff5e..00d7298f3a 100644 --- a/libs/flake/KoShapeLoadingContext.cpp +++ b/libs/flake/KoShapeLoadingContext.cpp @@ -1,221 +1,201 @@ /* This file is part of the KDE project Copyright (C) 2007-2009, 2011 Thorsten Zachmann Copyright (C) 2007 Jan Hambrecht Copyright (C) 2014-2015 Denis Kuplyakov 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 "KoShapeLoadingContext.h" #include "KoShape.h" #include "KoShapeContainer.h" #include "KoSharedLoadingData.h" #include "KoShapeControllerBase.h" #include "KoImageCollection.h" #include "KoMarkerCollection.h" #include "KoDocumentResourceManager.h" #include "KoLoadingShapeUpdater.h" #include uint qHash(const KoShapeLoadingContext::AdditionalAttributeData & attributeData) { return qHash(attributeData.name); } static QSet s_additionlAttributes; class Q_DECL_HIDDEN KoShapeLoadingContext::Private { public: Private(KoOdfLoadingContext &c, KoDocumentResourceManager *resourceManager) : context(c) , zIndex(0) , documentResources(resourceManager) - , documentRdf(0) , sectionModel(0) { } ~Private() { Q_FOREACH (KoSharedLoadingData * data, sharedData) { delete data; } } KoOdfLoadingContext &context; QMap layers; QMap drawIds; QMap > subIds; QMap sharedData; //FIXME: use QScopedPointer here to auto delete in destructor int zIndex; QMap updaterById; QMap updaterByShape; KoDocumentResourceManager *documentResources; - QObject *documentRdf; - KoSectionModel *sectionModel; -}; + KoSectionModel *sectionModel; }; KoShapeLoadingContext::KoShapeLoadingContext(KoOdfLoadingContext & context, KoDocumentResourceManager *documentResources) : d(new Private(context, documentResources)) { - if (d->documentResources) { - KoMarkerCollection *markerCollection = d->documentResources->resource(KoDocumentResourceManager::MarkerCollection).value(); - if (markerCollection) { - //markerCollection->loadOdf(*this); - } - } } KoShapeLoadingContext::~KoShapeLoadingContext() { delete d; } KoOdfLoadingContext & KoShapeLoadingContext::odfLoadingContext() { return d->context; } KoShapeLayer * KoShapeLoadingContext::layer(const QString & layerName) { return d->layers.value(layerName, 0); } void KoShapeLoadingContext::addLayer(KoShapeLayer * layer, const QString & layerName) { d->layers[ layerName ] = layer; } void KoShapeLoadingContext::clearLayers() { d->layers.clear(); } void KoShapeLoadingContext::addShapeId(KoShape * shape, const QString & id) { d->drawIds.insert(id, shape); QMap::iterator it(d->updaterById.find(id)); while (it != d->updaterById.end() && it.key() == id) { d->updaterByShape.insertMulti(shape, it.value()); it = d->updaterById.erase(it); } } KoShape * KoShapeLoadingContext::shapeById(const QString &id) { return d->drawIds.value(id, 0); } void KoShapeLoadingContext::addShapeSubItemId(KoShape *shape, const QVariant &subItem, const QString &id) { d->subIds.insert(id, QPair(shape, subItem)); } QPair KoShapeLoadingContext::shapeSubItemById(const QString &id) { return d->subIds.value(id); } // TODO make sure to remove the shape from the loading context when loading for it failed and it was deleted. This can also happen when the parent is deleted void KoShapeLoadingContext::updateShape(const QString & id, KoLoadingShapeUpdater * shapeUpdater) { d->updaterById.insertMulti(id, shapeUpdater); } void KoShapeLoadingContext::shapeLoaded(KoShape * shape) { QMap::iterator it(d->updaterByShape.find(shape)); while (it != d->updaterByShape.end() && it.key() == shape) { it.value()->update(shape); delete it.value(); it = d->updaterByShape.erase(it); } } KoImageCollection * KoShapeLoadingContext::imageCollection() { return d->documentResources ? d->documentResources->imageCollection() : 0; } int KoShapeLoadingContext::zIndex() { return d->zIndex++; } void KoShapeLoadingContext::setZIndex(int index) { d->zIndex = index; } void KoShapeLoadingContext::addSharedData(const QString & id, KoSharedLoadingData * data) { QMap::iterator it(d->sharedData.find(id)); // data will not be overwritten if (it == d->sharedData.end()) { d->sharedData.insert(id, data); } else { warnFlake << "The id" << id << "is already registered. Data not inserted"; Q_ASSERT(it == d->sharedData.end()); } } KoSharedLoadingData * KoShapeLoadingContext::sharedData(const QString & id) const { KoSharedLoadingData * data = 0; QMap::const_iterator it(d->sharedData.find(id)); if (it != d->sharedData.constEnd()) { data = it.value(); } return data; } void KoShapeLoadingContext::addAdditionalAttributeData(const AdditionalAttributeData & attributeData) { s_additionlAttributes.insert(attributeData); } QSet KoShapeLoadingContext::additionalAttributeData() { return s_additionlAttributes; } KoDocumentResourceManager *KoShapeLoadingContext::documentResourceManager() const { return d->documentResources; } -QObject *KoShapeLoadingContext::documentRdf() const -{ - return d->documentRdf; -} - - -void KoShapeLoadingContext::setDocumentRdf(QObject *documentRdf) -{ - d->documentRdf = documentRdf; -} - KoSectionModel *KoShapeLoadingContext::sectionModel() { return d->sectionModel; } void KoShapeLoadingContext::setSectionModel(KoSectionModel *sectionModel) { d->sectionModel = sectionModel; } diff --git a/libs/flake/KoShapeLoadingContext.h b/libs/flake/KoShapeLoadingContext.h index 26d7007ee9..84783d812c 100644 --- a/libs/flake/KoShapeLoadingContext.h +++ b/libs/flake/KoShapeLoadingContext.h @@ -1,212 +1,199 @@ /* This file is part of the KDE project Copyright (C) 2007 Thorsten Zachmann Copyright (C) 2007 Jan Hambrecht Copyright (C) 2014-2015 Denis Kuplyakov 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 KOSHAPELOADINGCONTEXT_H #define KOSHAPELOADINGCONTEXT_H #include #include #include #include "kritaflake_export.h" class KoOdfLoadingContext; class KoShapeLayer; class KoShape; class KoShapeControllerBase; class KoLoadingShapeUpdater; class KoImageCollection; class KoSharedLoadingData; class KoDocumentResourceManager; class KoSectionModel; class QVariant; class QObject; /** * Context passed to shapes during loading. * This class holds various variables as well as a context full of variables which all together * form the context of a loading operation. */ class KRITAFLAKE_EXPORT KoShapeLoadingContext { public: /** * Struct to store data about additional attributes that should be loaded during * the shape loading. * * Make sure all parameters point to const char * that stay around. e.g. The a KoXmlNS or * a "tag" defined string e.g. * AdditionalAttributeData( KoXmlNS::presentation, "placeholder", presentation:placeholder" ) */ struct AdditionalAttributeData { AdditionalAttributeData(const QString &ns, const QString &tag, const QString &name) : ns(ns) , tag(tag) , name(name) { } const QString ns; const QString tag; const QString name; bool operator==(const AdditionalAttributeData &other) const { return name == other.name; } }; /** * constructor * @param context the context created for generic ODF loading. * @param documentResources the data of the shape controller. */ KoShapeLoadingContext(KoOdfLoadingContext &context, KoDocumentResourceManager *documentResources); /// destructor ~KoShapeLoadingContext(); /// return the embedded loading context KoOdfLoadingContext &odfLoadingContext(); /// Returns layer referenced by given name KoShapeLayer *layer(const QString &layerName); /// Adds a new layer to be referenced by the given name later void addLayer(KoShapeLayer *layer, const QString &layerName); /** * remove all layers * * This can be used for loading different layer sets per page. */ void clearLayers(); /// register the id for a specific shape void addShapeId(KoShape *shape, const QString &id); /// return the shape formerly registered using addShapeId() KoShape *shapeById(const QString &id); /// register the id for a specific shape sub item void addShapeSubItemId(KoShape *shape, const QVariant &subItem, const QString &id); /// return the shape and subitem formerly registered using addShapeSubItemId() QPair shapeSubItemById(const QString &id); /** * call function on the shapeUpdater when the shape with the id shapeid is inserted * After that destroy the updater. */ void updateShape(const QString &id, KoLoadingShapeUpdater *shapeUpdater); /** * this checks if there is an updater for this shape if yes it calls it * this needs to be done via the shape id and */ void shapeLoaded(KoShape *shape); /// Returns the image collection for loading images KoImageCollection *imageCollection(); /// Get current z-index int zIndex(); /// Set z-index void setZIndex(int index); /** * Add shared data * * This can be use to pass data between shapes on loading. E.g. The decoded text styles * of the TextShape. With that the styles only have to be read once and can be used in * all shapes that also need them. * * The ownership of the added data is passed to the context. The KoShapeLoadingContext will * delete the added data when it is destroyed. * * Data inserted for a specific id will not be overwritten by calling addSharedData with * the same id again. * * You get an assertion when the id is already existing. * * @see KoSharedLoadingData */ void addSharedData(const QString &id, KoSharedLoadingData *data); /** * Get the shared data. * * @see KoSharedLoadingData * * @param id The id used to identify the shared data. * @return The shared data for the id or 0 if there is no shared data for the id. */ KoSharedLoadingData *sharedData(const QString &id) const; /** * @brief Add an additional attribute that should be loaded during shape loading * * An application can use that to set the data for additional attributes that should be * loaded during shape loading. * If attribute is set it will not change if set again. The tag is used to differentiate * the attributes * * @param attributeData The data describing the additional attribute data */ static void addAdditionalAttributeData(const AdditionalAttributeData &attributeData); /** * @brief Get the additional attribute data for loading of a shape * * This is used by KoShape::loadOdfAttributes to load all additional attributes defined * in the returned set. */ static QSet additionalAttributeData(); KoDocumentResourceManager *documentResourceManager() const; - - /** - * @brief get the rdf document - * @return the rdf document, or 0 if there is none set/ - */ - QObject *documentRdf() const; - - /** - * @brief setDocumentRdf sets the rdf document for the loading context - * @param documentRdf the rdf document -- it needs to have been loaded already - */ - void setDocumentRdf(QObject *documentRdf); - /** * @brief returns the current section model * @return the pointer to KoSectionModel */ KoSectionModel *sectionModel(); /** * @brief sets the section model for the loading context * @param sectionModel the section model to set */ void setSectionModel(KoSectionModel *sectionModel); private: // to allow only the KoShapeRegistry access to the KoShapeControllerBase class Private; Private * const d; }; #endif /* KOSHAPELOADINGCONTEXT_H */ diff --git a/libs/flake/KoShapeRegistry.cpp b/libs/flake/KoShapeRegistry.cpp index 61296f7afc..a0d114edbd 100644 --- a/libs/flake/KoShapeRegistry.cpp +++ b/libs/flake/KoShapeRegistry.cpp @@ -1,573 +1,397 @@ /* This file is part of the KDE project * Copyright (c) 2006 Boudewijn Rempt (boud@valdyas.org) * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2006,2008-2010 Thorsten Zachmann * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 2010 Inge Wallin * * 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. */ // Own #include "KoShapeRegistry.h" #include "KoSvgTextShape.h" #include "KoPathShapeFactory.h" #include "KoShapeLoadingContext.h" #include "KoShapeSavingContext.h" #include "KoShapeGroup.h" #include "KoShapeLayer.h" #include "SvgShapeFactory.h" #include #include #include #include #include #include #include #include #include #include #include Q_GLOBAL_STATIC(KoShapeRegistry, s_instance) class Q_DECL_HIDDEN KoShapeRegistry::Private { public: void insertFactory(KoShapeFactoryBase *factory); void init(KoShapeRegistry *q); KoShape *createShapeInternal(const KoXmlElement &fullElement, KoShapeLoadingContext &context, const KoXmlElement &element) const; // Map namespace,tagname to priority:factory QHash, QMultiMap > factoryMap; }; KoShapeRegistry::KoShapeRegistry() : d(new Private()) { } KoShapeRegistry::~KoShapeRegistry() { qDeleteAll(doubleEntries()); qDeleteAll(values()); delete d; } void KoShapeRegistry::Private::init(KoShapeRegistry *q) { KoPluginLoader::PluginsConfig config; config.whiteList = "FlakePlugins"; config.blacklist = "FlakePluginsDisabled"; config.group = "krita"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Flake"), QString::fromLatin1("[X-Flake-PluginVersion] == 28"), config); config.whiteList = "ShapePlugins"; config.blacklist = "ShapePluginsDisabled"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Shape"), QString::fromLatin1("[X-Flake-PluginVersion] == 28"), config); // Also add our hard-coded basic shapes q->add(new KoSvgTextShapeFactory()); q->add(new KoPathShapeFactory(QStringList())); // As long as there is no shape dealing with embedded svg images // we add the svg shape factory here by default q->add(new SvgShapeFactory); // Now all shape factories are registered with us, determine their // associated odf tagname & priority and prepare ourselves for // loading ODF. QList factories = q->values(); for (int i = 0; i < factories.size(); ++i) { insertFactory(factories[i]); } } KoShapeRegistry* KoShapeRegistry::instance() { if (!s_instance.exists()) { s_instance->d->init(s_instance); } return s_instance; } void KoShapeRegistry::addFactory(KoShapeFactoryBase * factory) { add(factory); d->insertFactory(factory); } void KoShapeRegistry::Private::insertFactory(KoShapeFactoryBase *factory) { const QList > odfElements(factory->odfElements()); if (odfElements.isEmpty()) { debugFlake << "Shape factory" << factory->id() << " does not have OdfNamespace defined, ignoring"; } else { int priority = factory->loadingPriority(); for (QList >::const_iterator it(odfElements.begin()); it != odfElements.end(); ++it) { foreach (const QString &elementName, (*it).second) { QPair p((*it).first, elementName); QMultiMap & priorityMap = factoryMap[p]; priorityMap.insert(priority, factory); debugFlake << "Inserting factory" << factory->id() << " for" << p << " with priority " << priority << " into factoryMap making " << priorityMap.size() << " entries. "; } } } } #include #include #include #include namespace { struct ObjectEntry { ObjectEntry() { } ObjectEntry(const ObjectEntry &rhs) : objectXmlContents(rhs.objectXmlContents), objectName(rhs.objectName), isDir(rhs.isDir) { } ~ObjectEntry() { } QByteArray objectXmlContents; // the XML tree in the object QString objectName; // object name in the frame without "./" // This is extracted from objectXmlContents. bool isDir = false; }; // A FileEntry is used to store information about embedded files // inside (i.e. referred to by) an object. struct FileEntry { FileEntry() {} FileEntry(const FileEntry &rhs) : path(rhs.path), mimeType(rhs.mimeType), isDir(rhs.isDir), contents(rhs.contents) { } QString path; // Normalized filename, i.e. without "./". QString mimeType; bool isDir; QByteArray contents; }; QByteArray loadFile(const QString &fileName, KoShapeLoadingContext &context) { // Can't load a file which is a directory, return an invalid QByteArray if (fileName.endsWith('/')) return QByteArray(); KoStore *store = context.odfLoadingContext().store(); QByteArray fileContent; if (!store->open(fileName)) { store->close(); return QByteArray(); } int fileSize = store->size(); fileContent = store->read(fileSize); store->close(); //debugFlake << "File content: " << fileContent; return fileContent; } boost::optional storeFile(const QString &fileName, KoShapeLoadingContext &context) { debugFlake << "Saving file: " << fileName; boost::optional result; QByteArray fileContent = loadFile(fileName, context); if (!fileContent.isNull()) { // Actually store the file in the list. FileEntry entry; entry.path = fileName; if (entry.path.startsWith(QLatin1String("./"))) { entry.path.remove(0, 2); } entry.mimeType = context.odfLoadingContext().mimeTypeForPath(entry.path); entry.isDir = false; entry.contents = fileContent; result = entry; } return result; } void storeXmlRecursive(const KoXmlElement &el, KoXmlWriter &writer, ObjectEntry *object, QHash &unknownNamespaces) { // Start the element; // keep the name in a QByteArray so that it stays valid until end element is called. const QByteArray name(el.nodeName().toLatin1()); writer.startElement(name.constData()); // Child elements // Loop through all the child elements of the draw:frame. KoXmlNode n = el.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { if (n.isElement()) { storeXmlRecursive(n.toElement(), writer, object, unknownNamespaces); } else if (n.isText()) { writer.addTextNode(n.toText().data()/*.toUtf8()*/); } } // End the element writer.endElement(); } QVector storeObjects(const KoXmlElement &element) { QVector result; // Loop through all the child elements of the draw:frame and save them. KoXmlNode n = element.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { debugFlake << "In draw:frame, node =" << n.nodeName(); // This disregards #text, but that's not in the spec anyway so // it doesn't need to be saved. if (!n.isElement()) continue; KoXmlElement el = n.toElement(); ObjectEntry object; QByteArray contentsTmp; QBuffer buffer(&contentsTmp); // the member KoXmlWriter writer(&buffer); // 1. Find out the objectName // Save the normalized filename, i.e. without a starting "./". // An empty string is saved if no name is found. QString name = el.attributeNS(KoXmlNS::xlink, "href", QString()); if (name.startsWith(QLatin1String("./"))) name.remove(0, 2); object.objectName = name; // 2. Copy the XML code. QHash unknownNamespaces; storeXmlRecursive(el, writer, &object, unknownNamespaces); object.objectXmlContents = contentsTmp; // 3, 4: the isDir and manifestEntry members are not set here, // but initialize them anyway. . object.isDir = false; // Has to be initialized to something. result.append(object); } return result; } } #include #include "kis_debug.h" #include #include #include #include #include -KoShape * KoShapeRegistry::createShapeFromOdf(const KoXmlElement & e, KoShapeLoadingContext & context) const +KoShape * KoShapeRegistry::createShapeFromXML(const KoXmlElement & e, KoShapeLoadingContext & context) const { - debugFlake << "Going to check for" << e.namespaceURI() << ":" << e.tagName(); - - KoShape * shape = 0; - - // Handle the case where the element is a draw:frame differently from other cases. - if (e.tagName() == "frame" && e.namespaceURI() == KoXmlNS::draw) { - // If the element is in a frame, the frame is already added by the - // application and we only want to create a shape from the - // embedded element. The very first shape we create is accepted. - // - // FIXME: we might want to have some code to determine which is - // the "best" of the creatable shapes. - - if (e.hasChildNodes()) { - // if we don't ignore white spaces it can be that the first child is not a element so look for the first element - KoXmlNode node = e.firstChild(); - KoXmlElement element; - while (!node.isNull() && element.isNull()) { - element = node.toElement(); - node = node.nextSibling(); - } - - if (!element.isNull()) { - // Check for draw:object - if (element.tagName() == "object" && element.namespaceURI() == KoXmlNS::draw && element.hasChildNodes()) { - // Loop through the elements and find the first one - // that is handled by any shape. - KoXmlNode n = element.firstChild(); - for (; !n.isNull(); n = n.nextSibling()) { - if (n.isElement()) { - debugFlake << "trying for element " << n.toElement().tagName(); - shape = d->createShapeInternal(e, context, n.toElement()); - break; - } - } - if (shape) - debugFlake << "Found a shape for draw:object"; - else - debugFlake << "Found NO shape shape for draw:object"; - } - else { - // If not draw:object, e.g draw:image or draw:plugin - shape = d->createShapeInternal(e, context, element); - } - } - - if (shape) { - debugFlake << "A shape supporting the requested type was found."; - } - else { - // If none of the registered shapes could handle the frame - // contents, try to fetch SVG it from an embedded link - - const KoXmlElement &frameElement = e; - const int frameZIndex = SvgShapeFactory::calculateZIndex(frameElement, context); - - QList resultShapes; - - QVector objects = storeObjects(frameElement); - Q_FOREACH (const ObjectEntry &object, objects) { - if (object.objectName.isEmpty()) continue; - - boost::optional file = storeFile(object.objectName, context); - if (file && !file->contents.isEmpty()) { - QMimeDatabase db; - QMimeType mime = db.mimeTypeForData(file->contents); - - const int zIndex = SvgShapeFactory::calculateZIndex(element, context); - - if (mime.inherits("image/svg+xml")) { - - - KoXmlDocument xmlDoc; - - int line, col; - QString errormessage; - const bool parsed = xmlDoc.setContent(file->contents, &errormessage, &line, &col); - if (!parsed) continue; - - const QRectF bounds = context.documentResourceManager()->documentRectInPixels(); - - // WARNING: Krita 3.x expects all the embedded objects to - // be loaded in default resolution of 72.0 ppi. - // Don't change it to the correct data in the image, - // it will change back compatibility (and this code will - // be deprecated some time soon - // UPDATE (DK): There is actually no difference in what resolution we - // load these shapes, because they will be scaled into - // the bounds of the parent odf-frame - const qreal pixelsPerInch = 72.0; - const qreal forcedFontSizeResolution = 72.0; - - QPointF pos; - pos.setX(KoUnit::parseValue(frameElement.attributeNS(KoXmlNS::svg, "x", QString::number(bounds.x())))); - pos.setY(KoUnit::parseValue(frameElement.attributeNS(KoXmlNS::svg, "y", QString::number(bounds.y())))); - - QSizeF size; - size.setWidth(KoUnit::parseValue(frameElement.attributeNS(KoXmlNS::svg, "width", QString::number(bounds.width())))); - size.setHeight(KoUnit::parseValue(frameElement.attributeNS(KoXmlNS::svg, "height", QString::number(bounds.height())))); - - KoShape *shape = SvgShapeFactory::createShapeFromSvgDirect(xmlDoc.documentElement(), - QRectF(pos, size), - pixelsPerInch, - forcedFontSizeResolution, - zIndex, - context); - - if (shape) { - // NOTE: here we are expected to stretch the internal to the bounds of - // the frame! Sounds weird, but it is what Krita 3.x did. - - const QRectF shapeRect = shape->absoluteOutlineRect(); - const QPointF offset = shapeRect.topLeft(); - const QSizeF fragmentSize = shapeRect.size(); - - if (fragmentSize.isValid()) { - /** - * Yes, you see what you see. The previous versions of Krita used - * QSvgRenderer to render the object, which allegedly truncated the - * object on sides. Even though we don't use pre-rendering now, - * we should still reproduce the old way... - */ - const QSizeF newSize = QSizeF(int(size.width()), int(size.height())); - - shape->applyAbsoluteTransformation( - QTransform::fromTranslate(-offset.x(), -offset.y()) * - QTransform::fromScale( - newSize.width() / fragmentSize.width(), - newSize.height() / fragmentSize.height()) * - QTransform::fromTranslate(pos.x(), pos.y())); - resultShapes.append(shape); - } - } - - } else { - // TODO: implement raster images? - } - } - } - - if (resultShapes.size() == 1) { - shape = resultShapes.takeLast(); - } else if (resultShapes.size() > 1) { - KoShapeGroup *groupShape = new KoShapeGroup; - KoShapeGroupCommand cmd(groupShape, resultShapes); - cmd.redo(); - groupShape->setZIndex(frameZIndex); - shape = groupShape; - } - } - } - } - - // Hardwire the group shape into the loading as it should not appear - // in the shape selector - else if (e.localName() == "g" && e.namespaceURI() == KoXmlNS::draw) { - KoShapeGroup * group = new KoShapeGroup(); - - context.odfLoadingContext().styleStack().save(); - bool loaded = group->loadOdf(e, context); - context.odfLoadingContext().styleStack().restore(); - - if (loaded) { - shape = group; - } - else { - delete group; - } - } else { - shape = d->createShapeInternal(e, context, e); - } - - if (shape) { - context.shapeLoaded(shape); - } - - return shape; + return 0; } KoShape *KoShapeRegistry::Private::createShapeInternal(const KoXmlElement &fullElement, KoShapeLoadingContext &context, const KoXmlElement &element) const { // Pair of namespace, tagname QPair p = QPair(element.namespaceURI(), element.tagName()); // Remove duplicate lookup. if (!factoryMap.contains(p)) return 0; QMultiMap priorityMap = factoryMap.value(p); QList factories = priorityMap.values(); #ifndef NDEBUG debugFlake << "Supported factories for=" << p; foreach (KoShapeFactoryBase *f, factories) debugFlake << f->id() << f->name(); #endif // Loop through all shape factories. If any of them supports this // element, then we let the factory create a shape from it. This // may fail because the element itself is too generic to draw any // real conclusions from it - we actually have to try to load it. // An example of this is the draw:image element which have // potentially hundreds of different image formats to support, // including vector formats. // // If it succeeds, then we use this shape, if it fails, then just // try the next. // // Higher numbers are more specific, map is sorted by keys. for (int i = factories.size() - 1; i >= 0; --i) { KoShapeFactoryBase * factory = factories[i]; if (factory->supports(element, context)) { - KoShape *shape = factory->createShapeFromOdf(fullElement, context); + KoShape *shape = factory->createShapeFromXML(fullElement, context); if (shape) { debugFlake << "Shape found for factory " << factory->id() << factory->name(); // we return the top-level most shape as that's the one that we'll have to // add to the KoShapeManager for painting later (and also to avoid memory leaks) // but don't go past a KoShapeLayer as KoShape adds those from the context // during loading and those are already added. while (shape->parent() && dynamic_cast(shape->parent()) == 0) shape = shape->parent(); return shape; } // Maybe a shape with a lower priority can load our // element, but this attempt has failed. } else { debugFlake << "No support for" << p << "by" << factory->id(); } } return 0; } QList KoShapeRegistry::factoriesForElement(const QString &nameSpace, const QString &elementName) { // Pair of namespace, tagname QPair p = QPair(nameSpace, elementName); QMultiMap priorityMap = d->factoryMap.value(p); QList shapeFactories; // sort list by priority Q_FOREACH (KoShapeFactoryBase *f, priorityMap.values()) { shapeFactories.prepend(f); } return shapeFactories; } diff --git a/libs/flake/KoShapeRegistry.h b/libs/flake/KoShapeRegistry.h index 5465a6d5fe..7d1e56cace 100644 --- a/libs/flake/KoShapeRegistry.h +++ b/libs/flake/KoShapeRegistry.h @@ -1,86 +1,86 @@ /* This file is part of the KDE project * Copyright (c) 2006 Boudewijn Rempt (boud@valdyas.org) * Copyright (C) 2006, 2010 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. */ #ifndef KOSHAPEREGISTRY_H #define KOSHAPEREGISTRY_H #include #include #include #include "kritaflake_export.h" class KoShape; class KoShapeLoadingContext; /** * This singleton class keeps a register of all available flake shapes, * or rather, of the factories that applications can use to create flake * shape objects. */ class KRITAFLAKE_EXPORT KoShapeRegistry : public KoGenericRegistry { public: KoShapeRegistry(); ~KoShapeRegistry() override; /** * Return an instance of the KoShapeRegistry * Creates an instance if that has never happened before and returns the singleton instance. */ static KoShapeRegistry *instance(); /** * Add shape factory for a shape that is not a plugin * This can be used also if you want to have a shape only in one application * * @param factory The factory of the shape */ void addFactory(KoShapeFactoryBase *factory); /** * Use the element to find out which flake plugin can load it, and * returns the loaded shape. The element expected is one of * 'draw:line', 'draw:frame' / etc. * * @returns the shape or 0 if no shape could be created. The shape may have as its parent * set a layer which was previously created and stored in the context. * @see KoShapeLoadingContext::layer() */ - KoShape *createShapeFromOdf(const KoXmlElement &element, KoShapeLoadingContext &context) const; + KoShape *createShapeFromXML(const KoXmlElement &element, KoShapeLoadingContext &context) const; /** * Returns a list of shape factories supporting the specified xml element. * @param nameSpace the namespace of the xml element, see KoXmlNS for valid namespaces * @param elementName the tag name of the element * @return the list of shape factories supporting the specified xml element */ QList factoriesForElement(const QString &nameSpace, const QString &elementName); private: KoShapeRegistry(const KoShapeRegistry&); KoShapeRegistry operator=(const KoShapeRegistry&); class Private; Private * const d; }; #endif diff --git a/libs/flake/KoShapeSavingContext.cpp b/libs/flake/KoShapeSavingContext.cpp index d7d2aa50a8..c612229d79 100644 --- a/libs/flake/KoShapeSavingContext.cpp +++ b/libs/flake/KoShapeSavingContext.cpp @@ -1,346 +1,340 @@ /* This file is part of the KDE project Copyright (C) 2004-2006 David Faure Copyright (C) 2007-2009, 2011 Thorsten Zachmann Copyright (C) 2007 Jan Hambrecht Copyright (C) 2010 Benjamin Port 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 "KoShapeSavingContext.h" #include "KoDataCenterBase.h" #include "KoShapeLayer.h" #include "KoImageData.h" #include "KoMarker.h" #include #include #include #include #include #include #include #include #include class KoShapeSavingContextPrivate { public: KoShapeSavingContextPrivate(KoXmlWriter&, KoGenStyles&, KoEmbeddedDocumentSaver&); ~KoShapeSavingContextPrivate(); KoXmlWriter *xmlWriter; KoShapeSavingContext::ShapeSavingOptions savingOptions; QList layers; QSet dataCenters; QMap sharedData; QMap imageNames; int imageId; QMap images; QHash shapeOffsets; QMap markerRefs; KoGenStyles& mainStyles; KoEmbeddedDocumentSaver& embeddedSaver; QMap references; QMap referenceCounters; QMap > prefixedReferences; }; KoShapeSavingContextPrivate::KoShapeSavingContextPrivate(KoXmlWriter &w, KoGenStyles &s, KoEmbeddedDocumentSaver &e) : xmlWriter(&w), savingOptions(0), imageId(0), mainStyles(s), embeddedSaver(e) { } KoShapeSavingContextPrivate::~KoShapeSavingContextPrivate() { Q_FOREACH (KoSharedSavingData * data, sharedData) { delete data; } } KoShapeSavingContext::KoShapeSavingContext(KoXmlWriter &xmlWriter, KoGenStyles &mainStyles, KoEmbeddedDocumentSaver &embeddedSaver) : d(new KoShapeSavingContextPrivate(xmlWriter, mainStyles, embeddedSaver)) { // by default allow saving of draw:id + xml:id addOption(KoShapeSavingContext::DrawId); } KoShapeSavingContext::~KoShapeSavingContext() { delete d; } KoXmlWriter & KoShapeSavingContext::xmlWriter() { return *d->xmlWriter; } void KoShapeSavingContext::setXmlWriter(KoXmlWriter &xmlWriter) { d->xmlWriter = &xmlWriter; } KoGenStyles & KoShapeSavingContext::mainStyles() { return d->mainStyles; } KoEmbeddedDocumentSaver &KoShapeSavingContext::embeddedSaver() { return d->embeddedSaver; } bool KoShapeSavingContext::isSet(ShapeSavingOption option) const { return d->savingOptions & option; } void KoShapeSavingContext::setOptions(ShapeSavingOptions options) { d->savingOptions = options; } KoShapeSavingContext::ShapeSavingOptions KoShapeSavingContext::options() const { return d->savingOptions; } void KoShapeSavingContext::addOption(ShapeSavingOption option) { d->savingOptions = d->savingOptions | option; } void KoShapeSavingContext::removeOption(ShapeSavingOption option) { if (isSet(option)) d->savingOptions = d->savingOptions ^ option; // xor to remove it. } KoElementReference KoShapeSavingContext::xmlid(const void *referent, const QString& prefix, KoElementReference::GenerationOption counter) { Q_ASSERT(counter == KoElementReference::UUID || (counter == KoElementReference::Counter && !prefix.isEmpty())); if (d->references.contains(referent)) { return d->references[referent]; } KoElementReference ref; if (counter == KoElementReference::Counter) { int referenceCounter = d->referenceCounters[prefix]; referenceCounter++; ref = KoElementReference(prefix, referenceCounter); d->references.insert(referent, ref); d->referenceCounters[prefix] = referenceCounter; } else { if (!prefix.isEmpty()) { ref = KoElementReference(prefix); d->references.insert(referent, ref); } else { d->references.insert(referent, ref); } } if (!prefix.isNull()) { d->prefixedReferences[prefix].append(referent); } return ref; } KoElementReference KoShapeSavingContext::existingXmlid(const void *referent) { if (d->references.contains(referent)) { return d->references[referent]; } else { KoElementReference ref; ref.invalidate(); return ref; } } void KoShapeSavingContext::clearXmlIds(const QString &prefix) { if (d->prefixedReferences.contains(prefix)) { Q_FOREACH (const void* ptr, d->prefixedReferences[prefix]) { d->references.remove(ptr); } d->prefixedReferences.remove(prefix); } if (d->referenceCounters.contains(prefix)) { d->referenceCounters[prefix] = 0; } } void KoShapeSavingContext::addLayerForSaving(const KoShapeLayer *layer) { if (layer && ! d->layers.contains(layer)) d->layers.append(layer); } void KoShapeSavingContext::saveLayerSet(KoXmlWriter &xmlWriter) const { xmlWriter.startElement("draw:layer-set"); Q_FOREACH (const KoShapeLayer * layer, d->layers) { xmlWriter.startElement("draw:layer"); xmlWriter.addAttribute("draw:name", layer->name()); if (layer->isGeometryProtected()) xmlWriter.addAttribute("draw:protected", "true"); if (! layer->isVisible(false)) xmlWriter.addAttribute("draw:display", "none"); xmlWriter.endElement(); // draw:layer } xmlWriter.endElement(); // draw:layer-set } void KoShapeSavingContext::clearLayers() { d->layers.clear(); } QString KoShapeSavingContext::imageHref(const KoImageData *image) { QMap::iterator it(d->imageNames.find(image->key())); if (it == d->imageNames.end()) { QString suffix = image->suffix(); if (suffix.isEmpty()) { it = d->imageNames.insert(image->key(), QString("Pictures/image%1").arg(++d->imageId)); } else { it = d->imageNames.insert(image->key(), QString("Pictures/image%1.%2").arg(++d->imageId).arg(suffix)); } } return it.value(); } QString KoShapeSavingContext::imageHref(const QImage &image) { // TODO this can be optimized to recognize images which have the same content // Also this can use quite a lot of memory as the qimage are all kept until // they are saved to the store in memory QString href = QString("Pictures/image%1.png").arg(++d->imageId); d->images.insert(href, image); return href; } QMap KoShapeSavingContext::imagesToSave() { return d->imageNames; } QString KoShapeSavingContext::markerRef(const KoMarker */*marker*/) { -// QMap::iterator it = d->markerRefs.find(marker); -// if (it == d->markerRefs.end()) { -// it = d->markerRefs.insert(marker, marker->saveOdf(*this)); -// } -// return it.value(); - return QString(); } void KoShapeSavingContext::addDataCenter(KoDataCenterBase * dataCenter) { if (dataCenter) { d->dataCenters.insert(dataCenter); } } bool KoShapeSavingContext::saveDataCenter(KoStore *store, KoXmlWriter* manifestWriter) { bool ok = true; Q_FOREACH (KoDataCenterBase *dataCenter, d->dataCenters) { ok = ok && dataCenter->completeSaving(store, manifestWriter, this); //debugFlake << "ok" << ok; } // Save images for (QMap::iterator it(d->images.begin()); it != d->images.end(); ++it) { if (store->open(it.key())) { KoStoreDevice device(store); ok = ok && it.value().save(&device, "PNG"); store->close(); // TODO error handling if (ok) { const QString mimetype = KisMimeDatabase::mimeTypeForFile(it.key(), false); manifestWriter->addManifestEntry(it.key(), mimetype); } else { warnFlake << "saving image failed"; } } else { ok = false; warnFlake << "saving image failed: open store failed"; } } return ok; } void KoShapeSavingContext::addSharedData(const QString &id, KoSharedSavingData * data) { QMap::iterator it(d->sharedData.find(id)); // data will not be overwritten if (it == d->sharedData.end()) { d->sharedData.insert(id, data); } else { warnFlake << "The id" << id << "is already registered. Data not inserted"; Q_ASSERT(it == d->sharedData.end()); } } KoSharedSavingData * KoShapeSavingContext::sharedData(const QString &id) const { KoSharedSavingData * data = 0; QMap::const_iterator it(d->sharedData.constFind(id)); if (it != d->sharedData.constEnd()) { data = it.value(); } return data; } void KoShapeSavingContext::addShapeOffset(const KoShape *shape, const QTransform &m) { d->shapeOffsets.insert(shape, m); } void KoShapeSavingContext::removeShapeOffset(const KoShape *shape) { d->shapeOffsets.remove(shape); } QTransform KoShapeSavingContext::shapeOffset(const KoShape *shape) const { return d->shapeOffsets.value(shape, QTransform()); } diff --git a/libs/flake/KoShapeStroke.cpp b/libs/flake/KoShapeStroke.cpp index 2f84aa4a63..3ce51cd1cb 100644 --- a/libs/flake/KoShapeStroke.cpp +++ b/libs/flake/KoShapeStroke.cpp @@ -1,418 +1,408 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2006-2008 Jan Hambrecht * Copyright (C) 2007,2009 Thorsten Zachmann * Copyright (C) 2012 Inge Wallin * * 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. */ // Own #include "KoShapeStroke.h" // Posix #include // Qt #include #include // Calligra #include #include // Flake #include "KoShape.h" #include "KoShapeSavingContext.h" #include "KoPathShape.h" #include "KoMarker.h" #include "KoInsets.h" #include #include #include #include "KisQPainterStateSaver.h" #include "kis_global.h" class Q_DECL_HIDDEN KoShapeStroke::Private { public: Private(KoShapeStroke *_q) : q(_q) {} KoShapeStroke *q; void paintBorder(const KoShape *shape, QPainter &painter, const QPen &pen) const; QColor color; QPen pen; QBrush brush; }; namespace { QPair anglesForSegment(KoPathSegment segment) { const qreal eps = 1e-6; if (segment.degree() < 3) { segment = segment.toCubic(); } QList points = segment.controlPoints(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(points.size() == 4, qMakePair(0.0, 0.0)); QPointF vec1 = points[1] - points[0]; QPointF vec2 = points[3] - points[2]; if (vec1.manhattanLength() < eps) { points[1] = segment.pointAt(eps); vec1 = points[1] - points[0]; } if (vec2.manhattanLength() < eps) { points[2] = segment.pointAt(1.0 - eps); vec2 = points[3] - points[2]; } const qreal angle1 = std::atan2(vec1.y(), vec1.x()); const qreal angle2 = std::atan2(vec2.y(), vec2.x()); return qMakePair(angle1, angle2); } } void KoShapeStroke::Private::paintBorder(const KoShape *shape, QPainter &painter, const QPen &pen) const { if (!pen.isCosmetic() && pen.style() != Qt::NoPen) { const KoPathShape *pathShape = dynamic_cast(shape); if (pathShape) { QPainterPath path = pathShape->pathStroke(pen); painter.fillPath(path, pen.brush()); if (!pathShape->hasMarkers()) return; const bool autoFillMarkers = pathShape->autoFillMarkers(); KoMarker *startMarker = pathShape->marker(KoFlake::StartMarker); KoMarker *midMarker = pathShape->marker(KoFlake::MidMarker); KoMarker *endMarker = pathShape->marker(KoFlake::EndMarker); for (int i = 0; i < pathShape->subpathCount(); i++) { const int numSubPoints = pathShape->subpathPointCount(i); if (numSubPoints < 2) continue; const bool isClosedSubpath = pathShape->isClosedSubpath(i); qreal firstAngle = 0.0; { KoPathSegment segment = pathShape->segmentByIndex(KoPathPointIndex(i, 0)); firstAngle= anglesForSegment(segment).first; } const int numSegments = isClosedSubpath ? numSubPoints : numSubPoints - 1; qreal lastAngle = 0.0; { KoPathSegment segment = pathShape->segmentByIndex(KoPathPointIndex(i, numSegments - 1)); lastAngle = anglesForSegment(segment).second; } qreal previousAngle = 0.0; for (int j = 0; j < numSegments; j++) { KoPathSegment segment = pathShape->segmentByIndex(KoPathPointIndex(i, j)); QPair angles = anglesForSegment(segment); const qreal angle1 = angles.first; const qreal angle2 = angles.second; if (j == 0 && startMarker) { const qreal angle = isClosedSubpath ? bisectorAngle(firstAngle, lastAngle) : firstAngle; if (autoFillMarkers) { startMarker->applyShapeStroke(shape, q, segment.first()->point(), pen.widthF(), angle); } startMarker->paintAtPosition(&painter, segment.first()->point(), pen.widthF(), angle); } if (j > 0 && midMarker) { const qreal angle = bisectorAngle(previousAngle, angle1); if (autoFillMarkers) { midMarker->applyShapeStroke(shape, q, segment.first()->point(), pen.widthF(), angle); } midMarker->paintAtPosition(&painter, segment.first()->point(), pen.widthF(), angle); } if (j == numSegments - 1 && endMarker) { const qreal angle = isClosedSubpath ? bisectorAngle(firstAngle, lastAngle) : lastAngle; if (autoFillMarkers) { endMarker->applyShapeStroke(shape, q, segment.second()->point(), pen.widthF(), angle); } endMarker->paintAtPosition(&painter, segment.second()->point(), pen.widthF(), angle); } previousAngle = angle2; } } return; } painter.strokePath(shape->outline(), pen); } } KoShapeStroke::KoShapeStroke() : d(new Private(this)) { d->color = QColor(Qt::black); // we are not rendering stroke with zero width anymore // so lets use a default width of 1.0 d->pen.setWidthF(1.0); } KoShapeStroke::KoShapeStroke(const KoShapeStroke &other) : KoShapeStrokeModel(), d(new Private(this)) { d->color = other.d->color; d->pen = other.d->pen; d->brush = other.d->brush; } KoShapeStroke::KoShapeStroke(qreal lineWidth, const QColor &color) : d(new Private(this)) { d->pen.setWidthF(qMax(qreal(0.0), lineWidth)); d->pen.setJoinStyle(Qt::MiterJoin); d->color = color; } KoShapeStroke::~KoShapeStroke() { delete d; } KoShapeStroke &KoShapeStroke::operator = (const KoShapeStroke &rhs) { if (this == &rhs) return *this; d->pen = rhs.d->pen; d->color = rhs.d->color; d->brush = rhs.d->brush; return *this; } -void KoShapeStroke::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) const -{ - QPen pen = d->pen; - if (d->brush.gradient()) - pen.setBrush(d->brush); - else - pen.setColor(d->color); - KoOdfGraphicStyles::saveOdfStrokeStyle(style, context.mainStyles(), pen); -} - void KoShapeStroke::strokeInsets(const KoShape *shape, KoInsets &insets) const { Q_UNUSED(shape); // '0.5' --- since we draw a line half inside, and half outside the object. qreal extent = 0.5 * (d->pen.widthF() >= 0 ? d->pen.widthF() : 1.0); // if we have square cap, we need a little more space // -> sqrt((0.5*penWidth)^2 + (0.5*penWidth)^2) if (capStyle() == Qt::SquareCap) { extent *= M_SQRT2; } if (joinStyle() == Qt::MiterJoin) { // miter limit in Qt is normalized by the line width (and not half-width) extent = qMax(extent, d->pen.widthF() * miterLimit()); } insets.top = extent; insets.bottom = extent; insets.left = extent; insets.right = extent; } qreal KoShapeStroke::strokeMaxMarkersInset(const KoShape *shape) const { qreal result = 0.0; const KoPathShape *pathShape = dynamic_cast(shape); if (pathShape && pathShape->hasMarkers()) { const qreal lineWidth = d->pen.widthF(); QVector markers; markers << pathShape->marker(KoFlake::StartMarker); markers << pathShape->marker(KoFlake::MidMarker); markers << pathShape->marker(KoFlake::EndMarker); Q_FOREACH (const KoMarker *marker, markers) { if (marker) { result = qMax(result, marker->maxInset(lineWidth)); } } } return result; } bool KoShapeStroke::hasTransparency() const { return d->color.alpha() > 0; } QPen KoShapeStroke::resultLinePen() const { QPen pen = d->pen; if (d->brush.gradient()) { pen.setBrush(d->brush); } else { pen.setColor(d->color); } return pen; } void KoShapeStroke::paint(const KoShape *shape, QPainter &painter) const { KisQPainterStateSaver saver(&painter); d->paintBorder(shape, painter, resultLinePen()); } bool KoShapeStroke::compareFillTo(const KoShapeStrokeModel *other) { if (!other) return false; const KoShapeStroke *stroke = dynamic_cast(other); if (!stroke) return false; return (d->brush.gradient() && d->brush == stroke->d->brush) || (!d->brush.gradient() && d->color == stroke->d->color); } bool KoShapeStroke::compareStyleTo(const KoShapeStrokeModel *other) { if (!other) return false; const KoShapeStroke *stroke = dynamic_cast(other); if (!stroke) return false; QPen pen1 = d->pen; QPen pen2 = stroke->d->pen; // just a random color top avoid comparison of that property pen1.setColor(Qt::magenta); pen2.setColor(Qt::magenta); return pen1 == pen2; } bool KoShapeStroke::isVisible() const { return d->pen.widthF() > 0 && (d->brush.gradient() || d->color.alpha() > 0); } void KoShapeStroke::setCapStyle(Qt::PenCapStyle style) { d->pen.setCapStyle(style); } Qt::PenCapStyle KoShapeStroke::capStyle() const { return d->pen.capStyle(); } void KoShapeStroke::setJoinStyle(Qt::PenJoinStyle style) { d->pen.setJoinStyle(style); } Qt::PenJoinStyle KoShapeStroke::joinStyle() const { return d->pen.joinStyle(); } void KoShapeStroke::setLineWidth(qreal lineWidth) { d->pen.setWidthF(qMax(qreal(0.0), lineWidth)); } qreal KoShapeStroke::lineWidth() const { return d->pen.widthF(); } void KoShapeStroke::setMiterLimit(qreal miterLimit) { d->pen.setMiterLimit(miterLimit); } qreal KoShapeStroke::miterLimit() const { return d->pen.miterLimit(); } QColor KoShapeStroke::color() const { return d->color; } void KoShapeStroke::setColor(const QColor &color) { d->color = color; } void KoShapeStroke::setLineStyle(Qt::PenStyle style, const QVector &dashes) { if (style < Qt::CustomDashLine) { d->pen.setStyle(style); } else { d->pen.setDashPattern(dashes); } } Qt::PenStyle KoShapeStroke::lineStyle() const { return d->pen.style(); } QVector KoShapeStroke::lineDashes() const { return d->pen.dashPattern(); } void KoShapeStroke::setDashOffset(qreal dashOffset) { d->pen.setDashOffset(dashOffset); } qreal KoShapeStroke::dashOffset() const { return d->pen.dashOffset(); } void KoShapeStroke::setLineBrush(const QBrush &brush) { d->brush = brush; } QBrush KoShapeStroke::lineBrush() const { return d->brush; } diff --git a/libs/flake/KoShapeStroke.h b/libs/flake/KoShapeStroke.h index 154e8c0de2..aa00fcea11 100644 --- a/libs/flake/KoShapeStroke.h +++ b/libs/flake/KoShapeStroke.h @@ -1,122 +1,121 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2006-2008 Jan Hambrecht * Copyright (C) 2007,2009 Thorsten Zachmann * Copyright (C) 2012 Inge Wallin * * 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 KOSHAPESTROKE_H #define KOSHAPESTROKE_H #include "KoFlakeTypes.h" #include "KoShapeStrokeModel.h" #include "kritaflake_export.h" #include #include class KoShape; class QPainter; class QBrush; class QPen; struct KoInsets; /** * A border for shapes that draws a single line around the object. */ class KRITAFLAKE_EXPORT KoShapeStroke : public KoShapeStrokeModel { public: /// Constructor for a thin line in black KoShapeStroke(); /// Copy constructor KoShapeStroke(const KoShapeStroke &other); /** * Constructor for a Stroke * @param lineWidth the width, in pt * @param color the color we draw the outline in. */ explicit KoShapeStroke(qreal lineWidth, const QColor &color = Qt::black); ~KoShapeStroke() override; /// Assignment operator KoShapeStroke& operator = (const KoShapeStroke &rhs); /// Sets the lines cap style void setCapStyle(Qt::PenCapStyle style); /// Returns the lines cap style Qt::PenCapStyle capStyle() const; /// Sets the lines join style void setJoinStyle(Qt::PenJoinStyle style); /// Returns the lines join style Qt::PenJoinStyle joinStyle() const; /// Sets the line width void setLineWidth(qreal lineWidth); /// Returns the line width qreal lineWidth() const; /// Sets the miter limit void setMiterLimit(qreal miterLimit); /// Returns the miter limit qreal miterLimit() const; /// Sets the line style void setLineStyle(Qt::PenStyle style, const QVector &dashes); /// Returns the line style Qt::PenStyle lineStyle() const; /// Returns the line dashes QVector lineDashes() const; /// Sets the dash offset void setDashOffset(qreal dashOffset); /// Returns the dash offset qreal dashOffset() const; /// Returns the color QColor color() const; /// Sets the color void setColor(const QColor &color); /// Sets the strokes brush used to fill strokes of this border void setLineBrush(const QBrush & brush); /// Returns the strokes brush QBrush lineBrush() const; // pure virtuals from KoShapeStrokeModel implemented here. - void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) const override; void strokeInsets(const KoShape *shape, KoInsets &insets) const override; qreal strokeMaxMarkersInset(const KoShape *shape) const override; bool hasTransparency() const override; void paint(const KoShape *shape, QPainter &painter) const override; QPen resultLinePen() const; bool compareFillTo(const KoShapeStrokeModel *other) override; bool compareStyleTo(const KoShapeStrokeModel *other) override; bool isVisible() const override; private: class Private; Private * const d; }; Q_DECLARE_METATYPE( KoShapeStroke ) #endif // KOSHAPESTROKE_H diff --git a/libs/flake/KoShapeStrokeModel.h b/libs/flake/KoShapeStrokeModel.h index 45c5ab145d..c94efcc0db 100644 --- a/libs/flake/KoShapeStrokeModel.h +++ b/libs/flake/KoShapeStrokeModel.h @@ -1,91 +1,83 @@ /* This file is part of the KDE project * * Copyright (C) 2006 Thomas Zander * Copyright (C) 2007,2009 Thorsten Zachmann * Copyright (C) 2012 Inge Wallin * * 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 KOSHAPESTROKEMODEL_H #define KOSHAPESTROKEMODEL_H #include "kritaflake_export.h" #include class KoShape; class KoGenStyle; class KoShapeSavingContext; struct KoInsets; class QColor; class QPainter; /** * A model for strokes of KoShapes. * Classes that implement this model will be allowed to draw the stroke of the outline * of a shape. * Note that since the important members take a KoShape as argument it is possible, * and preferred behavior, to have one instance of a stroke that is reused on several * objects. */ class KRITAFLAKE_EXPORT KoShapeStrokeModel { public: virtual ~KoShapeStrokeModel(); - /** - * @brief Fill the style object (aka save) - * - * @param style object - * @param context used for saving - */ - virtual void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) const = 0; - /** * Return a strokeInsets object filled with the size inside the shape that this stroke takes. * @param shape the shape the insets will be calculated for * @param insets the insets object that will be filled and returned. */ virtual void strokeInsets(const KoShape *shape, KoInsets &insets) const = 0; /** * Return a maximum distance that the markers of the shape can take outside the * shape itself */ virtual qreal strokeMaxMarkersInset(const KoShape *shape) const = 0; /** * Returns true if there is some transparency, false if the stroke is fully opaque. * @return if the stroke is transparent. */ virtual bool hasTransparency() const = 0; /** * Paint the stroke. * This method should paint the stroke around shape. * @param shape the shape to paint around * @param painter the painter to paint to, the painter will have the topleft of the * shape as its start coordinate. */ virtual void paint(const KoShape *shape, QPainter &painter) const = 0; virtual bool compareFillTo(const KoShapeStrokeModel *other) = 0; virtual bool compareStyleTo(const KoShapeStrokeModel *other) = 0; virtual bool isVisible() const = 0; }; #endif diff --git a/libs/flake/KoShape_p.h b/libs/flake/KoShape_p.h index 3ca7b9cff1..c119d418e0 100644 --- a/libs/flake/KoShape_p.h +++ b/libs/flake/KoShape_p.h @@ -1,110 +1,99 @@ /* This file is part of the KDE project * Copyright (C) 2009 Thomas Zander * 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. */ #ifndef KOSHAPEPRIVATE_H #define KOSHAPEPRIVATE_H #include "KoShape.h" #include #include #include #include #include #include class KoBorder; class KoShapeManager; class KoShape::SharedData : public QSharedData { public: explicit SharedData(); virtual ~SharedData(); explicit SharedData(const SharedData &rhs); - /** - * Fills the style stack and returns the value of the given style property (e.g fill, stroke). - */ - static QString getStyleProperty(const char *property, KoShapeLoadingContext &context); - - /// Loads the shadow style - KoShapeShadow *loadOdfShadow(KoShapeLoadingContext &context) const; - - // Loads the border style. - KoBorder *loadOdfBorder(KoShapeLoadingContext &context) const; - public: // Members mutable QSizeF size; // size in pt QString shapeId; QString name; ///< the shapes names QTransform localMatrix; ///< the shapes local transformation matrix QScopedPointer userData; QSharedPointer stroke; ///< points to a stroke, or 0 if there is no stroke QSharedPointer fill; ///< Stands for the background color / fill etc. bool inheritBackground = false; bool inheritStroke = false; KoShapeShadow * shadow; ///< the current shape shadow KoBorder *border; ///< the current shape border // XXX: change this to instance instead of pointer QScopedPointer clipPath; ///< the current clip path QScopedPointer clipMask; ///< the current clip mask QMap additionalAttributes; QMap additionalStyleAttributes; KoFilterEffectStack *filterEffectStack; ///< stack of filter effects applied to the shape qreal transparency; ///< the shapes transparency QString hyperLink; //hyperlink for this shape int zIndex : 16; // keep maxZIndex in sync! int runThrough : 16; int visible : 1; int printable : 1; int geometryProtected : 1; int keepAspect : 1; int selectable : 1; int protectContent : 1; KoShape::TextRunAroundSide textRunAroundSide; qreal textRunAroundDistanceLeft; qreal textRunAroundDistanceTop; qreal textRunAroundDistanceRight; qreal textRunAroundDistanceBottom; qreal textRunAroundThreshold; KoShape::TextRunAroundContour textRunAroundContour; }; class KoShape::Private { public: KoShapeContainer *parent; QSet shapeManagers; QSet toolDelegates; QList dependees; ///< list of shape dependent on this shape QList listeners; }; #endif diff --git a/libs/flake/KoTextShapeDataBase.h b/libs/flake/KoTextShapeDataBase.h index a5aab94bc1..c359b5618e 100644 --- a/libs/flake/KoTextShapeDataBase.h +++ b/libs/flake/KoTextShapeDataBase.h @@ -1,145 +1,120 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 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. */ #ifndef KOTEXTSHAPEDATABASE_H #define KOTEXTSHAPEDATABASE_H #include "kritaflake_export.h" #include "KoShapeUserData.h" class KoTextShapeDataBasePrivate; #include class KoShapeLoadingContext; class KoShapeSavingContext; class KoGenStyle; struct KoInsets; class QTextDocument; /** * \internal */ class KRITAFLAKE_EXPORT KoTextShapeDataBase : public KoShapeUserData { Q_OBJECT public: /// constructor KoTextShapeDataBase(); ~KoTextShapeDataBase() override; /// return the document QTextDocument *document() const; /** * Set the margins that will make the shapes text area smaller. * The shape that owns this textShapeData object will layout text in an area * confined by the shape size made smaller by the margins set here. * @param margins the margins that shrink the text area. */ void setShapeMargins(const KoInsets &margins); /** * returns the currently set margins for the shape. */ KoInsets shapeMargins() const; - /** - * Load the text from ODF. - */ - virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) = 0; - - /** - * Save the text to ODF. - */ - virtual void saveOdf(KoShapeSavingContext &context, int from = 0, int to = -1) const = 0; - - /** - * Load the style of the element - * - * This method is used to load the style in case the TextShape is used as TOS. In this case - * the paragraph style of the shape e.g. a custom-shape needs to be applied before we load the - * text so all looks as it should look. - */ - virtual void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) = 0; - /** - * Save the style of the element - * - * This method save the style in case the TextShape is used as TOS. In this case the paragraph - * style of the shape e.g. a custom-shape needs to be saved with the style of the shape. - */ - virtual void saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const = 0; - /** Sets the vertical alignment of all the text inside the shape. */ void setVerticalAlignment(Qt::Alignment alignment); + /** Returns the vertical alignment of all the text in the shape */ Qt::Alignment verticalAlignment() const; /** * Enum to describe the text document's automatic resizing behaviour. */ enum ResizeMethod { /// Resize the shape to fit the content. This makes sure that the text shape takes op /// only as much space as absolutely necessary to fit the entire text into its boundaries. AutoResize, /// Specifies whether or not to automatically increase the width of the drawing object /// if text is added to fit the entire width of the text into its boundaries. /// Compared to AutoResize above this only applied to the width whereas the height is /// not resized. Also this only grows but does not shrink again if text is removed again. AutoGrowWidth, /// Specifies whether or not to automatically increase the height of the drawing object /// if text is added to fit the entire height of the text into its boundaries. AutoGrowHeight, /// This combines the AutoGrowWidth and AutoGrowHeight and automatically increase width /// and height to fit the entire text into its boundaries. AutoGrowWidthAndHeight, /// Shrink the content displayed within the shape to match into the shape's boundaries. This /// will scale the content down as needed to display the whole document. ShrinkToFitResize, /// Deactivates auto-resizing. This is the default resizing method. NoResize }; /** * Specifies how the document should be resized upon a change in the document. * * If auto-resizing is turned on, text will not be wrapped unless enforced by e.g. a newline. * * By default, NoResize is set. */ void setResizeMethod(ResizeMethod method); /** * Returns the auto-resizing mode. By default, this is NoResize. * * @see setResizeMethod */ ResizeMethod resizeMethod() const; protected: /// constructor KoTextShapeDataBase(KoTextShapeDataBasePrivate *); KoTextShapeDataBasePrivate *d_ptr; private: Q_DECLARE_PRIVATE(KoTextShapeDataBase) }; #endif diff --git a/libs/flake/KoTosContainer.cpp b/libs/flake/KoTosContainer.cpp index 484530451f..d401bdc99b 100644 --- a/libs/flake/KoTosContainer.cpp +++ b/libs/flake/KoTosContainer.cpp @@ -1,349 +1,251 @@ /* This file is part of the KDE project * Copyright (C) 2010 Thomas Zander * Copyright (C) 2010 KO GmbH * Copyright (C) 2010 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. */ #include "KoTosContainer.h" #include "KoTosContainer_p.h" #include "KoShapeRegistry.h" #include "KoShapeFactoryBase.h" #include "KoShapeLoadingContext.h" #include "KoTextShapeDataBase.h" #include "KoTosContainerModel.h" #include "KoStyleStack.h" #include "KoOdfLoadingContext.h" #include "KoXmlNS.h" #include "KoGenStyle.h" #include #include #include KoTosContainer::Private::Private() : QSharedData() , resizeBehavior(KoTosContainer::IndependentSizes) { } KoTosContainer::Private::Private(const Private &rhs) : QSharedData() , resizeBehavior(rhs.resizeBehavior) , preferredTextRect(rhs.preferredTextRect) , alignment(rhs.alignment) { } KoTosContainer::Private::~Private() { } KoTosContainer::KoTosContainer() : KoShapeContainer() , d(new Private) { } KoTosContainer::KoTosContainer(const KoTosContainer &rhs) : KoShapeContainer(rhs) , d(rhs.d) { } KoTosContainer::~KoTosContainer() { delete textShape(); } void KoTosContainer::paintComponent(QPainter &, KoShapePaintingContext &) const { } bool KoTosContainer::loadText(const KoXmlElement &element, KoShapeLoadingContext &context) { - KoXmlElement child; - forEachElement(child, element) { - // only recreate the text shape if there's something to be loaded - if (child.localName() == "p" || child.localName() == "list") { - - KoShape *textShape = createTextShape(context.documentResourceManager()); - if (!textShape) { - return false; - } - //apply the style properties to the loaded text - setTextAlignment(d->alignment); - - // In the case of text on shape, we cannot ask the text shape to load - // the odf, since it expects a complete document with style info and - // everything, so we have to use the KoTextShapeData object instead. - KoTextShapeDataBase *shapeData = qobject_cast(textShape->userData()); - Q_ASSERT(shapeData); - shapeData->loadStyle(element, context); - bool loadOdf = shapeData->loadOdf(element, context); - - return loadOdf; - } - } - return true; + return false; } -void KoTosContainer::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) -{ - KoShapeContainer::loadStyle(element, context); - - KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); - styleStack.setTypeProperties("graphic"); - - QString verticalAlign(styleStack.property(KoXmlNS::draw, "textarea-vertical-align")); - Qt::Alignment vAlignment(Qt::AlignTop); - if (verticalAlign == "bottom") { - vAlignment = Qt::AlignBottom; - } else if (verticalAlign == "justify") { - // not yet supported - vAlignment = Qt::AlignVCenter; - } else if (verticalAlign == "middle") { - vAlignment = Qt::AlignVCenter; - } - - QString horizontalAlign(styleStack.property(KoXmlNS::draw, "textarea-horizontal-align")); - Qt::Alignment hAlignment(Qt::AlignLeft); - if (horizontalAlign == "center") { - hAlignment = Qt::AlignCenter; - } else if (horizontalAlign == "justify") { - // not yet supported - hAlignment = Qt::AlignCenter; - } else if (horizontalAlign == "right") { - hAlignment = Qt::AlignRight; - } - - d->alignment = vAlignment | hAlignment; -} - -QString KoTosContainer::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const -{ - Qt::Alignment alignment = textAlignment(); - QString verticalAlignment = "top"; - Qt::Alignment vAlignment(alignment & Qt::AlignVertical_Mask); - if (vAlignment == Qt::AlignBottom) { - verticalAlignment = "bottom"; - } else if (vAlignment == Qt::AlignVCenter || vAlignment == Qt::AlignCenter) { - verticalAlignment = "middle"; - } - - style.addProperty("draw:textarea-vertical-align", verticalAlignment); - - QString horizontalAlignment = "left"; - Qt::Alignment hAlignment(alignment & Qt::AlignHorizontal_Mask); - if (hAlignment == Qt::AlignCenter || hAlignment == Qt::AlignHCenter) { - horizontalAlignment = "center"; - } else if (hAlignment == Qt::AlignJustify) { - horizontalAlignment = "justify"; - } else if (hAlignment == Qt::AlignRight) { - horizontalAlignment = "right"; - } - - style.addProperty("draw:textarea-horizontal-align", horizontalAlignment); - - return KoShapeContainer::saveStyle(style, context); -} - -void KoTosContainer::saveText(KoShapeSavingContext &context) const -{ - KoShape *textShape = this->textShape(); - if (!textShape) { - return; - } - // In the case of text on shape, we cannot ask the text shape to save - // the odf, since it would save all the frame information as well, which - // is wrong. - // Only save the text shape if it has content. - KoTextShapeDataBase *shapeData = qobject_cast(textShape->userData()); - if (shapeData && !shapeData->document()->isEmpty()) { - shapeData->saveOdf(context); - } -} void KoTosContainer::setPlainText(const QString &text) { KoShape *textShape = this->textShape(); if (textShape == 0) { warnFlake << "No text shape present in KoTosContainer"; return; } KoTextShapeDataBase *shapeData = qobject_cast(textShape->userData()); Q_ASSERT(shapeData->document()); shapeData->document()->setPlainText(text); } void KoTosContainer::setResizeBehavior(ResizeBehavior resizeBehavior) { if (d->resizeBehavior == resizeBehavior) { return; } d->resizeBehavior = resizeBehavior; if (model()) { model()->containerChanged(this, KoShape::SizeChanged); } } KoTosContainer::ResizeBehavior KoTosContainer::resizeBehavior() const { return d->resizeBehavior; } void KoTosContainer::setTextAlignment(Qt::Alignment alignment) { KoShape *textShape = this->textShape(); if (textShape == 0) { warnFlake << "No text shape present in KoTosContainer"; return; } // vertical KoTextShapeDataBase *shapeData = qobject_cast(textShape->userData()); shapeData->setVerticalAlignment(alignment); // horizontal Q_ASSERT(shapeData->document()); QTextBlockFormat bf; bf.setAlignment(alignment & Qt::AlignHorizontal_Mask); QTextCursor cursor(shapeData->document()); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); cursor.mergeBlockFormat(bf); d->alignment = alignment; } Qt::Alignment KoTosContainer::textAlignment() const { KoShape *textShape = this->textShape(); if (textShape == 0) { warnFlake << "No text shape present in KoTosContainer"; return Qt::AlignTop; } // vertical KoTextShapeDataBase *shapeData = qobject_cast(textShape->userData()); // the model makes sure it contains a shape that has a KoTextShapeDataBase set so no need to check that Qt::Alignment answer = shapeData->verticalAlignment() & Qt::AlignVertical_Mask; // horizontal Q_ASSERT(shapeData->document()); QTextCursor cursor(shapeData->document()); answer = answer | (cursor.blockFormat().alignment() & Qt::AlignHorizontal_Mask); return answer; } void KoTosContainer::setPreferredTextRect(const QRectF &rect) { d->preferredTextRect = rect; KoShape *textShape = this->textShape(); //debugFlake << rect << textShape << d->resizeBehavior; if (d->resizeBehavior == TextFollowsPreferredTextRect && textShape) { //debugFlake << rect; textShape->setPosition(rect.topLeft()); textShape->setSize(rect.size()); } } QRectF KoTosContainer::preferredTextRect() const { return d->preferredTextRect; } KoShape *KoTosContainer::createTextShape(KoDocumentResourceManager *documentResources) { if (!documentResources) { warnFlake << "KoDocumentResourceManager not found"; return 0; } delete textShape(); delete model(); setModel(new KoTosContainerModel()); QSet delegates; delegates << this; KoShape *textShape = 0; KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get("TextShapeID"); if (factory) { // not installed, that's too bad, but allowed textShape = factory->createDefaultShape(documentResources); Q_ASSERT(textShape); // would be a bug in the text shape; if (d->resizeBehavior == TextFollowsPreferredTextRect) { textShape->setSize(d->preferredTextRect.size()); } else { textShape->setSize(size()); } if (d->resizeBehavior == TextFollowsPreferredTextRect) { textShape->setPosition(d->preferredTextRect.topLeft()); } else { textShape->setPosition(QPointF(0, 0)); } textShape->setSelectable(false); textShape->setRunThrough(runThrough()); KoTextShapeDataBase *shapeData = qobject_cast(textShape->userData()); Q_ASSERT(shapeData); // would be a bug in kotext // TODO check if that is correct depending on the resize mode shapeData->setVerticalAlignment(Qt::AlignVCenter); addShape(textShape); // textShape->setZIndex(zIndex() + 1); // not needed as there as the text shape is the only sub shape delegates << textShape; } else { warnFlake << "Text shape factory not found"; } setToolDelegates(delegates); return textShape; } KoShape *KoTosContainer::textShape() const { const QList subShapes = shapes(); return subShapes.isEmpty() ? 0 : subShapes.at(0); } void KoTosContainer::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(shape); if (model() == 0) { return; } if (type == SizeChanged || type == ContentChanged) { model()->containerChanged(this, type); } // TODO is this needed? #if 0 Q_FOREACH (KoShape *shape, model()->shapes()) shape->notifyChanged(); #endif } void KoTosContainer::setRunThrough(short int runThrough) { KoShape::setRunThrough(runThrough); KoShape *textShape = this->textShape(); if (textShape) { textShape->setRunThrough(runThrough); } } diff --git a/libs/flake/KoTosContainer.h b/libs/flake/KoTosContainer.h index 7a94999bb6..0bef3d9255 100644 --- a/libs/flake/KoTosContainer.h +++ b/libs/flake/KoTosContainer.h @@ -1,131 +1,123 @@ /* This file is part of the KDE project * Copyright (C) 2010 Thomas Zander * Copyright (C) 2010 KO GmbH * Copyright (C) 2010 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 KOTOSCONTAINER_H #define KOTOSCONTAINER_H #include "KoShapeContainer.h" #include "kritaflake_export.h" class KoDocumentResourceManager; /** * Container that is used to wrap a shape with a text on top. * Path shapes inherit from this class to make it possible to have text associated * with them. */ class KRITAFLAKE_EXPORT KoTosContainer : public KoShapeContainer { public: KoTosContainer(); ~KoTosContainer() override; // reimplemented void paintComponent(QPainter &painter, KoShapePaintingContext &paintcontext) const override; // reimplemented virtual bool loadText(const KoXmlElement &element, KoShapeLoadingContext &context); - // reimplemented - virtual void saveText(KoShapeSavingContext &context) const; /// different kinds of resizing behavior to determine how to treat text overflow enum ResizeBehavior { TextFollowsSize, ///< Text area is same size as content, extra text will be clipped FollowTextSize, ///< Content shape will get resized if text grows/shrinks IndependentSizes, ///< The text can get bigger than the content TextFollowsPreferredTextRect ///< The size/position of the text area will follow the preferredTextRect property }; /** * Set the behavior that is used to resize the text or content. * In order to determine what to do when there is too much text to fit or suddenly less * text the user can define the wanted behavior using the ResizeBehavior * @param resizeBehavior the new ResizeBehavior */ void setResizeBehavior(ResizeBehavior resizeBehavior); /** * Returns the current ResizeBehavior. */ ResizeBehavior resizeBehavior() const; /** Sets the alignment of the text. */ void setTextAlignment(Qt::Alignment alignment); /** Returns the alignment of all text */ Qt::Alignment textAlignment() const; /** * Set some plain text to be displayed on the shape. * @param text the full text. */ void setPlainText(const QString &text); /** * Add text the current shape with the specified document resource manager. * * @param documentResources * @return The created text shape or 0 in case it failed */ KoShape *createTextShape(KoDocumentResourceManager *documentResources = 0); void setRunThrough(short int runThrough) override; protected: KoTosContainer(const KoTosContainer &rhs); - //reimplemented - void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) override; - - //reimplemented - QString saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const override; - - /** + /** * Set the current preferred text rectangle. This rect contains the coordinates of * the embedded text shape relative to the content shape. This value is ignored if * resizeBehavior is not TextFollowsPreferredTextRect. * @param rect the new preferred text rectangle */ void setPreferredTextRect(const QRectF &rect); /** * Returns the current preferred text rectangle. */ QRectF preferredTextRect() const; /** * Returns the text shape * * @returns textshape if set or 0 in case it is not yet set */ KoShape *textShape() const; void shapeChanged(ChangeType type, KoShape *shape = 0) override; private: class Private; QSharedDataPointer d; }; #endif diff --git a/libs/flake/KoVectorPatternBackground.cpp b/libs/flake/KoVectorPatternBackground.cpp index a07e721141..9f1fef1b5e 100644 --- a/libs/flake/KoVectorPatternBackground.cpp +++ b/libs/flake/KoVectorPatternBackground.cpp @@ -1,162 +1,150 @@ /* * Copyright (c) 2016 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 "KoVectorPatternBackground.h" #include #include #include #include class KoVectorPatternBackground::Private : public QSharedData { public: Private() : QSharedData() { } ~Private() { qDeleteAll(shapes); shapes.clear(); } QList shapes; KoFlake::CoordinateSystem referenceCoordinates = KoFlake::ObjectBoundingBox; KoFlake::CoordinateSystem contentCoordinates = KoFlake::UserSpaceOnUse; QRectF referenceRect; QTransform patternTransform; }; KoVectorPatternBackground::KoVectorPatternBackground() : KoShapeBackground() , d(new Private) { } KoVectorPatternBackground::~KoVectorPatternBackground() { } bool KoVectorPatternBackground::compareTo(const KoShapeBackground *other) const { Q_UNUSED(other); return false; } void KoVectorPatternBackground::setReferenceCoordinates(KoFlake::CoordinateSystem value) { d->referenceCoordinates = value; } KoFlake::CoordinateSystem KoVectorPatternBackground::referenceCoordinates() const { return d->referenceCoordinates; } void KoVectorPatternBackground::setContentCoordinates(KoFlake::CoordinateSystem value) { d->contentCoordinates = value; } KoFlake::CoordinateSystem KoVectorPatternBackground::contentCoordinates() const { return d->contentCoordinates; } void KoVectorPatternBackground::setReferenceRect(const QRectF &value) { d->referenceRect = value; } QRectF KoVectorPatternBackground::referenceRect() const { return d->referenceRect; } void KoVectorPatternBackground::setPatternTransform(const QTransform &value) { d->patternTransform = value; } QTransform KoVectorPatternBackground::patternTransform() const { return d->patternTransform; } void KoVectorPatternBackground::setShapes(const QList value) { qDeleteAll(d->shapes); d->shapes.clear(); d->shapes = value; } QList KoVectorPatternBackground::shapes() const { return d->shapes; } void KoVectorPatternBackground::paint(QPainter &painter, KoShapePaintingContext &context_Unused, const QPainterPath &fillPath) const { Q_UNUSED(context_Unused); const QPainterPath dstShapeOutline = fillPath; const QRectF dstShapeBoundingBox = dstShapeOutline.boundingRect(); KoBakedShapeRenderer renderer(dstShapeOutline, QTransform(), QTransform(), d->referenceRect, d->contentCoordinates != KoFlake::UserSpaceOnUse, dstShapeBoundingBox, d->referenceCoordinates != KoFlake::UserSpaceOnUse, d->patternTransform); QPainter *patchPainter = renderer.bakeShapePainter(); KoShapePainter p; p.setShapes(d->shapes); p.paint(*patchPainter); // uncomment for debug // renderer.patchImage().save("dd_patch_image.png"); painter.setPen(Qt::NoPen); renderer.renderShape(painter); } bool KoVectorPatternBackground::hasTransparency() const { return true; } - -void KoVectorPatternBackground::fillStyle(KoGenStyle &, KoShapeSavingContext &) -{ - // noop -} - -bool KoVectorPatternBackground::loadStyle(KoOdfLoadingContext &, const QSizeF &Size) -{ - Q_UNUSED(Size); - return true; -} - diff --git a/libs/flake/KoVectorPatternBackground.h b/libs/flake/KoVectorPatternBackground.h index d60d7d8f59..a7faa5f595 100644 --- a/libs/flake/KoVectorPatternBackground.h +++ b/libs/flake/KoVectorPatternBackground.h @@ -1,67 +1,65 @@ /* * Copyright (c) 2016 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 KOVECTORPATTERNBACKGROUND_H #define KOVECTORPATTERNBACKGROUND_H #include #include #include class KoShape; class QPointF; class QRectF; class QTransform; class KoVectorPatternBackground : public KoShapeBackground { public: KoVectorPatternBackground(); ~KoVectorPatternBackground() override; bool compareTo(const KoShapeBackground *other) const override; void setReferenceCoordinates(KoFlake::CoordinateSystem value); KoFlake::CoordinateSystem referenceCoordinates() const; /** * In ViewBox just use the same mode as for referenceCoordinates */ void setContentCoordinates(KoFlake::CoordinateSystem value); KoFlake::CoordinateSystem contentCoordinates() const; void setReferenceRect(const QRectF &value); QRectF referenceRect() const; void setPatternTransform(const QTransform &value); QTransform patternTransform() const; void setShapes(const QList value); QList shapes() const; void paint(QPainter &painter, KoShapePaintingContext &context_Unused, const QPainterPath &fillPath) const override; bool hasTransparency() const override; - void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override; - bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) override; private: class Private; QSharedDataPointer d; }; #endif // KOVECTORPATTERNBACKGROUND_H diff --git a/libs/flake/svg/SvgShapeFactory.cpp b/libs/flake/svg/SvgShapeFactory.cpp index e4fb34b2c8..671e673426 100644 --- a/libs/flake/svg/SvgShapeFactory.cpp +++ b/libs/flake/svg/SvgShapeFactory.cpp @@ -1,174 +1,174 @@ /* This file is part of the KDE project * 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 "SvgShapeFactory.h" #include "SvgParser.h" #include "KoShapeGroup.h" #include "KoShapeGroupCommand.h" #include "KoShapeLoadingContext.h" #include "KoShapeRegistry.h" #include "FlakeDebug.h" #include #include #include #include #include #define SVGSHAPEFACTORYID "SvgShapeFactory" SvgShapeFactory::SvgShapeFactory() : KoShapeFactoryBase(SVGSHAPEFACTORYID, i18n("Embedded svg shape")) { setLoadingPriority(4); setXmlElementNames(QString(KoXmlNS::draw), QStringList("image")); // hide from add shapes docker as the shape is not able to be dragged onto // the canvas as createDefaultShape returns 0. setHidden(true); } SvgShapeFactory::~SvgShapeFactory() { } bool SvgShapeFactory::supports(const KoXmlElement &element, KoShapeLoadingContext &context) const { if (element.localName() == "image" && element.namespaceURI() == KoXmlNS::draw) { QString href = element.attribute("href"); if (href.isEmpty()) return false; // check the mimetype if (href.startsWith(QLatin1String("./"))) { href.remove(0,2); } QString mimetype = context.odfLoadingContext().mimeTypeForPath(href, true); return (mimetype == "image/svg+xml"); } return false; } -KoShape *SvgShapeFactory::createShapeFromOdf(const KoXmlElement &element, KoShapeLoadingContext &context) +KoShape *SvgShapeFactory::createShapeFromXML(const KoXmlElement &element, KoShapeLoadingContext &context) { const KoXmlElement & imageElement(KoXml::namedItemNS(element, KoXmlNS::draw, "image")); if (imageElement.isNull()) { errorFlake << "svg image element not found"; return 0; } if (imageElement.tagName() == "image") { debugFlake << "trying to create shapes form svg image"; QString href = imageElement.attribute("href"); if (href.isEmpty()) return 0; // check the mimetype if (href.startsWith(QLatin1String("./"))) { href.remove(0,2); } QString mimetype = context.odfLoadingContext().mimeTypeForPath(href); debugFlake << mimetype; if (mimetype != "image/svg+xml") return 0; if (!context.odfLoadingContext().store()->open(href)) return 0; KoStoreDevice dev(context.odfLoadingContext().store()); KoXmlDocument xmlDoc; int line, col; QString errormessage; const bool parsed = xmlDoc.setContent(&dev, &errormessage, &line, &col); context.odfLoadingContext().store()->close(); if (! parsed) { errorFlake << "Error while parsing file: " << "at line " << line << " column: " << col << " message: " << errormessage << endl; return 0; } const int zIndex = calculateZIndex(element, context); /** * In Krita 3.x we used hardcoded values for shape resolution and font resolution. * Override them here explicitly, because ODF-based files can be created only in * Krita 3.x. * * NOTE: don't ask me why they differ... */ const qreal hardcodedImageResolution = 90.0; const qreal hardcodedFontResolution = 96.0; return createShapeFromSvgDirect(xmlDoc.documentElement(), QRect(0,0,300,300), hardcodedImageResolution, hardcodedFontResolution, zIndex, context); } return 0; } int SvgShapeFactory::calculateZIndex(const KoXmlElement &element, KoShapeLoadingContext &context) { int zIndex = 0; if (element.hasAttributeNS(KoXmlNS::draw, "z-index")) { zIndex = element.attributeNS(KoXmlNS::draw, "z-index").toInt(); } else { zIndex = context.zIndex(); } return zIndex; } KoShape *SvgShapeFactory::createShapeFromSvgDirect(const KoXmlElement &root, const QRectF &boundsInPixels, const qreal pixelsPerInch, const qreal forcedFontSizeResolution, int zIndex, KoShapeLoadingContext &context, QSizeF *fragmentSize) { SvgParser parser(context.documentResourceManager()); parser.setResolution(boundsInPixels, pixelsPerInch); parser.setForcedFontSizeResolution(forcedFontSizeResolution); QList shapes = parser.parseSvg(root, fragmentSize); if (shapes.isEmpty()) return 0; if (shapes.count() == 1) { KoShape *shape = shapes.first(); shape->setZIndex(zIndex); return shape; } KoShapeGroup *svgGroup = new KoShapeGroup; KoShapeGroupCommand cmd(svgGroup, shapes); cmd.redo(); svgGroup->setZIndex(zIndex); return svgGroup; } diff --git a/libs/flake/svg/SvgShapeFactory.h b/libs/flake/svg/SvgShapeFactory.h index 164a4cdd4e..3c9365982c 100644 --- a/libs/flake/svg/SvgShapeFactory.h +++ b/libs/flake/svg/SvgShapeFactory.h @@ -1,42 +1,42 @@ /* This file is part of the KDE project * 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. */ #ifndef SVGSHAPEFACTORY_H #define SVGSHAPEFACTORY_H #include "kritaflake_export.h" #include "KoShapeFactoryBase.h" /// Use this shape factory to load embedded svg files from odf class KRITAFLAKE_EXPORT SvgShapeFactory : public KoShapeFactoryBase { public: SvgShapeFactory(); ~SvgShapeFactory() override; // reimplemented from KoShapeFactoryBase bool supports(const KoXmlElement &element, KoShapeLoadingContext &context) const override; // reimplemented from KoShapeFactoryBase - KoShape *createShapeFromOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override; + KoShape *createShapeFromXML(const KoXmlElement &element, KoShapeLoadingContext &context) override; static int calculateZIndex(const KoXmlElement &element, KoShapeLoadingContext &context); static KoShape *createShapeFromSvgDirect(const KoXmlElement &root, const QRectF &boundsInPixels, const qreal pixelsPerInch, const qreal forcedFontSizeResolution, int zIndex, KoShapeLoadingContext &context, QSizeF *fragmentSize = 0); }; #endif // SVGSHAPEFACTORY_H diff --git a/libs/flake/tests/MockShapes.h b/libs/flake/tests/MockShapes.h index d6d878d990..376fa792b5 100644 --- a/libs/flake/tests/MockShapes.h +++ b/libs/flake/tests/MockShapes.h @@ -1,248 +1,240 @@ /* * This file is part of Calligra tests * * Copyright (C) 2006-2010 Thomas Zander * * 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 MOCKSHAPES_H #define MOCKSHAPES_H #include #include #include #include #include #include #include "KoShapeManager.h" #include "FlakeDebug.h" #include "KoSnapData.h" #include "KoUnit.h" #include "kritaflake_export.h" class KRITAFLAKE_EXPORT MockShape : public KoShape { public: MockShape() : paintedCount(0) {} void paint(QPainter &painter, KoShapePaintingContext &) const override { Q_UNUSED(painter); //qDebug() << "Shape" << kBacktrace( 10 ); paintedCount++; } - void saveOdf(KoShapeSavingContext &) const override {} - bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) override { - return true; - } mutable int paintedCount; }; class KRITAFLAKE_EXPORT MockContainer : public KoShapeContainer { public: MockContainer(KoShapeContainerModel *model = 0) : KoShapeContainer(model), paintedCount(0) {} void paintComponent(QPainter &painter, KoShapePaintingContext &) const override { Q_UNUSED(painter); //qDebug() << "Container:" << kBacktrace( 10 ); paintedCount++; } - void saveOdf(KoShapeSavingContext &) const override {} - bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) override { - return true; - } mutable int paintedCount; }; class KRITAFLAKE_EXPORT MockGroup : public KoShapeGroup { void paintComponent(QPainter &painter, KoShapePaintingContext &) const override { Q_UNUSED(painter); } }; class KoToolProxy; class KRITAFLAKE_EXPORT MockShapeController : public KoShapeControllerBase { public: void addShapes(const QList shapes) override { Q_FOREACH (KoShape *shape, shapes) { m_shapes.insert(shape); if (m_shapeManager) { m_shapeManager->addShape(shape); } } } void removeShape(KoShape* shape) override { m_shapes.remove(shape); if (m_shapeManager) { m_shapeManager->remove(shape); } } bool contains(KoShape* shape) { return m_shapes.contains(shape); } void setShapeManager(KoShapeManager *shapeManager) { m_shapeManager = shapeManager; } QRectF documentRectInPixels() const override { return QRectF(0,0,100,100); } qreal pixelsPerInch() const override { return 72.0; } private: QSet m_shapes; KoShapeManager *m_shapeManager = 0; }; class KRITAFLAKE_EXPORT MockCanvas : public KoCanvasBase { Q_OBJECT public: MockCanvas(KoShapeControllerBase *aKoShapeControllerBase =0)//made for TestSnapStrategy.cpp : KoCanvasBase(aKoShapeControllerBase), m_shapeManager(new KoShapeManager(this)), m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data())) { if (MockShapeController *controller = dynamic_cast(aKoShapeControllerBase)) { controller->setShapeManager(m_shapeManager.data()); } } ~MockCanvas() override {} void setHorz(qreal pHorz){ m_horz = pHorz; } void setVert(qreal pVert){ m_vert = pVert; } void gridSize(QPointF *offset, QSizeF *spacing) const override { Q_UNUSED(offset); spacing->setWidth(m_horz); spacing->setHeight(m_vert); } bool snapToGrid() const override { return true; } void addCommand(KUndo2Command*) override { } KoShapeManager *shapeManager() const override { return m_shapeManager.data(); } KoSelectedShapesProxy *selectedShapesProxy() const override { return m_selectedShapesProxy.data(); } void updateCanvas(const QRectF&) override {} KoToolProxy * toolProxy() const override { return 0; } KoViewConverter *viewConverter() const override { return 0; } QWidget* canvasWidget() override { return 0; } const QWidget* canvasWidget() const override { return 0; } KoUnit unit() const override { return KoUnit(KoUnit::Millimeter); } void updateInputMethodInfo() override {} void setCursor(const QCursor &) override {} private: QScopedPointer m_shapeManager; QScopedPointer m_selectedShapesProxy; qreal m_horz; qreal m_vert; }; class KRITAFLAKE_EXPORT MockContainerModel : public KoShapeContainerModel { public: MockContainerModel() { resetCounts(); } /// reimplemented void add(KoShape *child) override { m_children.append(child); // note that we explicitly do not check for duplicates here! } /// reimplemented void remove(KoShape *child) override { m_children.removeAll(child); } /// reimplemented void setClipped(const KoShape *, bool) override { } // ignored /// reimplemented bool isClipped(const KoShape *) const override { return false; }// ignored /// reimplemented int count() const override { return m_children.count(); } /// reimplemented QList shapes() const override { return m_children; } /// reimplemented void containerChanged(KoShapeContainer *, KoShape::ChangeType) override { m_containerChangedCalled++; } /// reimplemented void proposeMove(KoShape *, QPointF &) override { m_proposeMoveCalled++; } /// reimplemented void childChanged(KoShape *, KoShape::ChangeType) override { m_childChangedCalled++; } void setInheritsTransform(const KoShape *, bool) override { } bool inheritsTransform(const KoShape *) const override { return false; } int containerChangedCalled() const { return m_containerChangedCalled; } int childChangedCalled() const { return m_childChangedCalled; } int proposeMoveCalled() const { return m_proposeMoveCalled; } void resetCounts() { m_containerChangedCalled = 0; m_childChangedCalled = 0; m_proposeMoveCalled = 0; } private: QList m_children; int m_containerChangedCalled, m_childChangedCalled, m_proposeMoveCalled; }; #endif diff --git a/libs/flake/tests/TestKoShapeFactory.cpp b/libs/flake/tests/TestKoShapeFactory.cpp index af4e7f54f9..8155f1507e 100644 --- a/libs/flake/tests/TestKoShapeFactory.cpp +++ b/libs/flake/tests/TestKoShapeFactory.cpp @@ -1,130 +1,69 @@ /* This file is part of the KDE project * Copyright (c) 2007 Boudewijn Rempt (boud@valdyas.org) * * 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 "TestKoShapeFactory.h" #include #include #include #include #include #include #include #include #include #include void TestKoShapeFactory::testCreateFactory() { KoShapeFactoryBase * factory = new KoPathShapeFactory(QStringList()); QVERIFY(factory != 0); delete factory; } void TestKoShapeFactory::testSupportsKoXmlElement() { } void TestKoShapeFactory::testPriority() { KoShapeFactoryBase * factory = new KoPathShapeFactory(QStringList()); QVERIFY(factory->loadingPriority() == 0); delete factory; } void TestKoShapeFactory::testCreateDefaultShape() { KoShapeFactoryBase * factory = new KoPathShapeFactory(QStringList()); KoShape *shape = factory->createDefaultShape(); QVERIFY(shape != 0); delete shape; delete factory; } void TestKoShapeFactory::testCreateShape() { KoShapeFactoryBase * factory = new KoPathShapeFactory(QStringList()); KoShape *shape = factory->createShape(0); QVERIFY(shape != 0); delete shape; delete factory; } -void TestKoShapeFactory::testOdfElement() -{ - KoShapeFactoryBase * factory = new KoPathShapeFactory(QStringList()); - QVERIFY(factory->odfElements().front().second.contains("path")); - QVERIFY(factory->odfElements().front().second.contains("line")); - QVERIFY(factory->odfElements().front().second.contains("polyline")); - QVERIFY(factory->odfElements().front().second.contains("polygon")); - QVERIFY(factory->odfElements().front().first == KoXmlNS::draw); - - QBuffer xmldevice; - xmldevice.open(QIODevice::WriteOnly); - QTextStream xmlstream(&xmldevice); - xmlstream.setCodec("UTF-8"); - - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmlstream << ""; - xmldevice.close(); - - KoXmlDocument doc; - QString errorMsg; - int errorLine = 0; - int errorColumn = 0; - - QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); - QCOMPARE(errorMsg.isEmpty(), true); - QCOMPARE(errorLine, 0); - QCOMPARE(errorColumn, 0); - - KoXmlElement contentElement = doc.documentElement(); - KoXmlElement bodyElement = contentElement.firstChild().toElement(); - - // XXX: When loading is implemented, these no doubt have to be - // sensibly filled. - KoOdfStylesReader stylesReader; - KoOdfLoadingContext odfContext(stylesReader, 0); - KoShapeLoadingContext shapeContext(odfContext, 0); - - KoXmlElement textElement = bodyElement.firstChild().firstChild().toElement(); - QVERIFY(textElement.tagName() == "p"); - QCOMPARE(factory->supports(textElement, shapeContext), false); - - KoXmlElement pathElement = bodyElement.firstChild().lastChild().toElement(); - QVERIFY(pathElement.tagName() == "path"); - QCOMPARE(factory->supports(pathElement, shapeContext), true); - - KoShape *shape = factory->createDefaultShape(); - QVERIFY(shape); - - QVERIFY(shape->loadOdf(pathElement, shapeContext)); - - delete shape; - delete factory; -} - QTEST_GUILESS_MAIN(TestKoShapeFactory) diff --git a/libs/flake/tests/TestKoShapeFactory.h b/libs/flake/tests/TestKoShapeFactory.h index cec01d0bc9..61144289f2 100644 --- a/libs/flake/tests/TestKoShapeFactory.h +++ b/libs/flake/tests/TestKoShapeFactory.h @@ -1,39 +1,38 @@ /* This file is part of the KDE project * Copyright (c) 2007 Boudewijn Rempt (boud@valdyas.org) * * 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 TestKoShapeFactoryBase_H #define TestKoShapeFactoryBase_H #include class TestKoShapeFactory : public QObject { Q_OBJECT private Q_SLOTS: // tests void testCreateFactory(); void testSupportsKoXmlElement(); void testPriority(); void testCreateDefaultShape(); void testCreateShape(); - void testOdfElement(); }; #endif diff --git a/libs/flake/tests/TestKoShapeRegistry.cpp b/libs/flake/tests/TestKoShapeRegistry.cpp index fc96f26f12..38dd89219a 100644 --- a/libs/flake/tests/TestKoShapeRegistry.cpp +++ b/libs/flake/tests/TestKoShapeRegistry.cpp @@ -1,214 +1,214 @@ /* This file is part of the KDE project * Copyright (c) 2007 Boudewijn Rempt (boud@valdyas.org) * * 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 "TestKoShapeRegistry.h" #include #include #include #include #include #include #include #include #include #include #include "KoShapeRegistry.h" #include "KoShape.h" #include "KoPathShape.h" #include "KoShapeLoadingContext.h" #include #include #include "kis_debug.h" #include void TestKoShapeRegistry::testGetKoShapeRegistryInstance() { KoShapeRegistry * registry = KoShapeRegistry::instance(); QVERIFY(registry != 0); } void TestKoShapeRegistry::testCreateShapes() { QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream.setCodec("UTF-8"); xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmldevice.close(); KoXmlDocument doc; QString errorMsg; int errorLine = 0; int errorColumn = 0; QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); KoXmlElement contentElement = doc.documentElement(); KoXmlElement bodyElement = contentElement.firstChild().toElement(); KoShapeRegistry * registry = KoShapeRegistry::instance(); // XXX: When loading is implemented, these no doubt have to be // sensibly filled. KoOdfStylesReader stylesReader; KoOdfLoadingContext odfContext(stylesReader, 0); KoShapeLoadingContext shapeContext(odfContext, 0); - KoShape * shape = registry->createShapeFromOdf(bodyElement, shapeContext); + KoShape * shape = registry->createShapeFromXML(bodyElement, shapeContext); QVERIFY(shape == 0); KoXmlElement pathElement = bodyElement.firstChild().firstChild().toElement(); - shape = registry->createShapeFromOdf(pathElement, shapeContext); + shape = registry->createShapeFromXML(pathElement, shapeContext); QVERIFY(shape != 0); QVERIFY(shape->shapeId() == KoPathShapeId); } void TestKoShapeRegistry::testCreateFramedShapes() { QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream.setCodec("UTF-8"); xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmldevice.close(); KoXmlDocument doc; QString errorMsg; int errorLine = 0; int errorColumn = 0; QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); KoXmlElement contentElement = doc.documentElement(); KoXmlElement bodyElement = contentElement.firstChild().toElement(); KoShapeRegistry * registry = KoShapeRegistry::instance(); // XXX: When loading is implemented, these no doubt have to be // sensibly filled. KoOdfStylesReader stylesReader; KoOdfLoadingContext odfContext(stylesReader, 0); KoShapeLoadingContext shapeContext(odfContext, 0); - KoShape * shape = registry->createShapeFromOdf(bodyElement, shapeContext); + KoShape * shape = registry->createShapeFromXML(bodyElement, shapeContext); QVERIFY(shape == 0); KoXmlElement pathElement = bodyElement.firstChild().firstChild().toElement(); - shape = registry->createShapeFromOdf(pathElement, shapeContext); + shape = registry->createShapeFromXML(pathElement, shapeContext); QVERIFY(shape != 0); QVERIFY(shape->shapeId() == KoPathShapeId); } #include #include #include #include #include "../../sdk/tests/qimage_test_util.h" void TestKoShapeRegistry::testFramedSvgShapes() { QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream.setCodec("UTF-8"); xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << " "; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmldevice.close(); KoXmlDocument doc; QString errorMsg; int errorLine = 0; int errorColumn = 0; QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); KoXmlElement contentElement = doc.documentElement(); KoXmlElement bodyElement = contentElement.firstChild().toElement(); KoShapeRegistry * registry = KoShapeRegistry::instance(); // XXX: When loading is implemented, these no doubt have to be // sensibly filled. KoOdfStylesReader stylesReader; const QString resourcesBlob = TestUtil::fetchDataFileLazy("odf_frame_resource_store.zip"); QScopedPointer store(KoStore::createStore(resourcesBlob, KoStore::Read, "krita", KoStore::Zip)); QScopedPointer resourceManager(new KoDocumentResourceManager()); resourceManager->setResource(KoDocumentResourceManager::DocumentRectInPixels, QRect(0,0,1000,1000)); QScopedPointer document(new MockShapeController()); QScopedPointer canvas(new MockCanvas(document.data())); QScopedPointer shapeController(new KoShapeController(canvas.data(), document.data())); resourceManager->setGlobalShapeController(shapeController.data()); KoOdfLoadingContext odfContext(stylesReader, store.data()); KoShapeLoadingContext shapeContext(odfContext, resourceManager.data()); KoXmlElement frameElement = bodyElement.firstChild().firstChild().toElement(); QCOMPARE(frameElement.tagName(), QString("frame")); - KoShape *shape = registry->createShapeFromOdf(frameElement, shapeContext); + KoShape *shape = registry->createShapeFromXML(frameElement, shapeContext); QVERIFY(shape); QCOMPARE(shape->absoluteOutlineRect(), QRectF(83, 41, 226,141)); } KISTEST_MAIN(TestKoShapeRegistry) diff --git a/libs/flake/text/KoSvgTextChunkShape.cpp b/libs/flake/text/KoSvgTextChunkShape.cpp index 8b760d2b2c..d96c8d6c01 100644 --- a/libs/flake/text/KoSvgTextChunkShape.cpp +++ b/libs/flake/text/KoSvgTextChunkShape.cpp @@ -1,983 +1,971 @@ /* * 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 v * 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 "KoSvgTextChunkShape.h" #include "KoSvgTextChunkShape_p.h" #include "KoSvgText.h" #include "KoSvgTextProperties.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { void appendLazy(QVector *list, boost::optional value, int iteration, bool hasDefault = true, qreal defaultValue = 0.0) { if (!value) return; if (value && *value == defaultValue && hasDefault == true && list->isEmpty()) return; while (list->size() < iteration) { list->append(defaultValue); } list->append(*value); } void fillTransforms(QVector *xPos, QVector *yPos, QVector *dxPos, QVector *dyPos, QVector *rotate, QVector localTransformations) { for (int i = 0; i < localTransformations.size(); i++) { const KoSvgText::CharTransformation &t = localTransformations[i]; appendLazy(xPos, t.xPos, i, false); appendLazy(yPos, t.yPos, i, false); appendLazy(dxPos, t.dxPos, i); appendLazy(dyPos, t.dyPos, i); appendLazy(rotate, t.rotate, i); } } QVector parseListAttributeX(const QString &value, SvgLoadingContext &context) { QVector result; QStringList list = SvgUtil::simplifyList(value); Q_FOREACH (const QString &str, list) { result << SvgUtil::parseUnitX(context.currentGC(), str); } return result; } QVector parseListAttributeY(const QString &value, SvgLoadingContext &context) { QVector result; QStringList list = SvgUtil::simplifyList(value); Q_FOREACH (const QString &str, list) { result << SvgUtil::parseUnitY(context.currentGC(), str); } return result; } QVector parseListAttributeAngular(const QString &value, SvgLoadingContext &context) { QVector result; QStringList list = SvgUtil::simplifyList(value); Q_FOREACH (const QString &str, list) { result << SvgUtil::parseUnitAngular(context.currentGC(), str); } return result; } QString convertListAttribute(const QVector &values) { QStringList stringValues; Q_FOREACH (qreal value, values) { stringValues.append(KisDomUtils::toString(value)); } return stringValues.join(','); } } struct KoSvgTextChunkShape::Private::LayoutInterface : public KoSvgTextChunkShapeLayoutInterface { LayoutInterface(KoSvgTextChunkShape *_q) : q(_q) {} KoSvgText::AutoValue textLength() const override { return q->s->textLength; } KoSvgText::LengthAdjust lengthAdjust() const override { return q->s->lengthAdjust; } int numChars() const override { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->s->text.isEmpty(), 0); int result = 0; if (!q->shapeCount()) { result = q->s->text.size(); } else { Q_FOREACH (KoShape *shape, q->shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(chunkShape, 0); result += chunkShape->layoutInterface()->numChars(); } } return result; } int relativeCharPos(KoSvgTextChunkShape *child, int pos) const override { QList childShapes = q->shapes(); int result = -1; int numCharsPassed = 0; Q_FOREACH (KoShape *shape, q->shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(chunkShape, 0); if (chunkShape == child) { result = pos + numCharsPassed; break; } else { numCharsPassed += chunkShape->layoutInterface()->numChars(); } } return result; } bool isTextNode() const override { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->s->text.isEmpty(), false); return !q->shapeCount(); } QString nodeText() const override { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->s->text.isEmpty(), 0); return !q->shapeCount() ? q->s->text : QString(); } QVector localCharTransformations() const override { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(isTextNode(), QVector()); const QVector t = q->s->localTransformations; return t.mid(0, qMin(t.size(), q->s->text.size())); } static QString getBidiOpening(KoSvgText::Direction direction, KoSvgText::UnicodeBidi bidi) { using namespace KoSvgText; QString result; if (bidi == BidiEmbed) { result = direction == DirectionLeftToRight ? "\u202a" : "\u202b"; } else if (bidi == BidiOverride) { result = direction == DirectionLeftToRight ? "\u202d" : "\u202e"; } return result; } QVector collectSubChunks() const override { QVector result; if (isTextNode()) { const QString text = q->s->text; const KoSvgText::KoSvgCharChunkFormat format = q->fetchCharFormat(); QVector transforms = q->s->localTransformations; /** * Sometimes SVG can contain the X,Y offsets for the pieces of text that * do not exist, just skip them. */ if (text.size() <= transforms.size()) { transforms.resize(text.size()); } KoSvgText::UnicodeBidi bidi = KoSvgText::UnicodeBidi(q->s->properties.propertyOrDefault(KoSvgTextProperties::UnicodeBidiId).toInt()); KoSvgText::Direction direction = KoSvgText::Direction(q->s->properties.propertyOrDefault(KoSvgTextProperties::DirectionId).toInt()); const QString bidiOpening = getBidiOpening(direction, bidi); if (!bidiOpening.isEmpty()) { result << SubChunk(bidiOpening, format); } if (transforms.isEmpty()) { result << SubChunk(text, format); } else { for (int i = 0; i < transforms.size(); i++) { const KoSvgText::CharTransformation baseTransform = transforms[i]; int subChunkLength = 1; for (int j = i + 1; j < transforms.size(); j++) { if (transforms[j].isNull()) { subChunkLength++; } else { break; } } if (i + subChunkLength >= transforms.size()) { subChunkLength = text.size() - i; } result << SubChunk(text.mid(i, subChunkLength), format, baseTransform); i += subChunkLength - 1; } } if (!bidiOpening.isEmpty()) { result << SubChunk("\u202c", format); } } else { Q_FOREACH (KoShape *shape, q->shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_BREAK(chunkShape); result += chunkShape->layoutInterface()->collectSubChunks(); } } return result; } void addAssociatedOutline(const QRectF &rect) override { KIS_SAFE_ASSERT_RECOVER_RETURN(isTextNode()); QPainterPath path; path.addRect(rect); path |= q->s->associatedOutline; path.setFillRule(Qt::WindingFill); path = path.simplified(); q->s->associatedOutline = path; q->setSize(path.boundingRect().size()); q->notifyChanged(); q->shapeChangedPriv(KoShape::SizeChanged); } void clearAssociatedOutline() override { q->s->associatedOutline = QPainterPath(); q->setSize(QSizeF()); q->notifyChanged(); q->shapeChangedPriv(KoShape::SizeChanged); } private: KoSvgTextChunkShape *q; }; KoSvgTextChunkShape::KoSvgTextChunkShape() : KoShapeContainer() , d(new Private) , s(new SharedData) { d->layoutInterface.reset(new KoSvgTextChunkShape::Private::LayoutInterface(this)); } KoSvgTextChunkShape::KoSvgTextChunkShape(const KoSvgTextChunkShape &rhs) : KoShapeContainer(rhs) , d(new Private) , s(rhs.s) { if (rhs.model()) { SimpleShapeContainerModel *otherModel = dynamic_cast(rhs.model()); KIS_ASSERT_RECOVER_RETURN(otherModel); setModelInit(new SimpleShapeContainerModel(*otherModel)); } d->layoutInterface.reset(new KoSvgTextChunkShape::Private::LayoutInterface(this)); } KoSvgTextChunkShape::~KoSvgTextChunkShape() { } KoShape *KoSvgTextChunkShape::cloneShape() const { return new KoSvgTextChunkShape(*this); } QSizeF KoSvgTextChunkShape::size() const { return outlineRect().size(); } void KoSvgTextChunkShape::setSize(const QSizeF &size) { Q_UNUSED(size); // we do not support resizing! } QRectF KoSvgTextChunkShape::outlineRect() const { return outline().boundingRect(); } QPainterPath KoSvgTextChunkShape::outline() const { QPainterPath result; result.setFillRule(Qt::WindingFill); if (d->layoutInterface->isTextNode()) { result = s->associatedOutline; } else { Q_FOREACH (KoShape *shape, shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_BREAK(chunkShape); result |= chunkShape->outline(); } } return result.simplified(); } void KoSvgTextChunkShape::paintComponent(QPainter &painter, KoShapePaintingContext &paintContext) const { Q_UNUSED(painter); Q_UNUSED(paintContext); } -void KoSvgTextChunkShape::saveOdf(KoShapeSavingContext &context) const -{ - Q_UNUSED(context); -} - -bool KoSvgTextChunkShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) -{ - Q_UNUSED(element); - Q_UNUSED(context); - return false; -} - bool KoSvgTextChunkShape::saveHtml(HtmlSavingContext &context) { // Should we add a newline? Check for vertical movement if we're using rtl or ltr text // XXX: if vertical text, check horizontal movement. QVector xPos; QVector yPos; QVector dxPos; QVector dyPos; QVector rotate; fillTransforms(&xPos, &yPos, &dxPos, &dyPos, &rotate, s->localTransformations); for (int i = 0; i < s->localTransformations.size(); i++) { const KoSvgText::CharTransformation &t = s->localTransformations[i]; appendLazy(&xPos, t.xPos, i, false); appendLazy(&yPos, t.yPos, i, false); appendLazy(&dxPos, t.dxPos, i); appendLazy(&dyPos, t.dyPos, i); } KoSvgTextChunkShape *parent = !isRootTextNode() ? dynamic_cast(this->parent()) : 0; KoSvgTextProperties parentProperties = parent ? parent->textProperties() : KoSvgTextProperties::defaultProperties(); // XXX: we don't save fill, stroke, text length, length adjust or spacing and glyphs. KoSvgTextProperties ownProperties = textProperties().ownProperties(parentProperties); if (isRootTextNode()) { context.shapeWriter().startElement("body", false); if (layoutInterface()->isTextNode()) { context.shapeWriter().startElement("p", false); } // XXX: Save the style? } else if (parent && parent->isRootTextNode()) { context.shapeWriter().startElement("p", false); } else { context.shapeWriter().startElement("span", false); // XXX: Save the style? } QMap attributes = ownProperties.convertToSvgTextAttributes(); if (attributes.size() > 0) { QString styleString; for (auto it = attributes.constBegin(); it != attributes.constEnd(); ++it) { if (QString(it.key().toLatin1().data()).contains("text-anchor")) { QString val = it.value(); if (it.value()=="middle") { val = "center"; } else if (it.value()=="end") { val = "right"; } else { val = "left"; } styleString.append("text-align") .append(": ") .append(val) .append(";" ); } else if (QString(it.key().toLatin1().data()).contains("fill")){ styleString.append("color") .append(": ") .append(it.value()) .append(";" ); } else if (QString(it.key().toLatin1().data()).contains("font-size")){ QString val = it.value(); if (QRegExp ("\\d*").exactMatch(val)) { val.append("pt"); } styleString.append(it.key().toLatin1().data()) .append(": ") .append(val) .append(";" ); } else { styleString.append(it.key().toLatin1().data()) .append(": ") .append(it.value()) .append(";" ); } } context.shapeWriter().addAttribute("style", styleString); } if (layoutInterface()->isTextNode()) { debugFlake << "saveHTML" << this << s->text << xPos << yPos << dxPos << dyPos; // After adding all the styling to the

element, add the text context.shapeWriter().addTextNode(s->text); } else { Q_FOREACH (KoShape *child, this->shapes()) { KoSvgTextChunkShape *childText = dynamic_cast(child); KIS_SAFE_ASSERT_RECOVER(childText) { continue; } childText->saveHtml(context); } } if (isRootTextNode() && layoutInterface()->isTextNode()) { context.shapeWriter().endElement(); // body } context.shapeWriter().endElement(); // p or span return true; } void writeTextListAttribute(const QString &attribute, const QVector &values, KoXmlWriter &writer) { const QString value = convertListAttribute(values); if (!value.isEmpty()) { writer.addAttribute(attribute.toLatin1().data(), value); } } bool KoSvgTextChunkShape::saveSvg(SvgSavingContext &context) { if (isRootTextNode()) { context.shapeWriter().startElement("text", false); if (!context.strippedTextMode()) { context.shapeWriter().addAttribute("id", context.getID(this)); context.shapeWriter().addAttribute("krita:useRichText", s->isRichTextPreferred ? "true" : "false"); SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter()); SvgStyleWriter::saveSvgStyle(this, context); } else { SvgStyleWriter::saveSvgFill(this, context); SvgStyleWriter::saveSvgStroke(this, context); } } else { context.shapeWriter().startElement("tspan", false); if (!context.strippedTextMode()) { SvgStyleWriter::saveSvgBasicStyle(this, context); } } if (layoutInterface()->isTextNode()) { QVector xPos; QVector yPos; QVector dxPos; QVector dyPos; QVector rotate; fillTransforms(&xPos, &yPos, &dxPos, &dyPos, &rotate, s->localTransformations); writeTextListAttribute("x", xPos, context.shapeWriter()); writeTextListAttribute("y", yPos, context.shapeWriter()); writeTextListAttribute("dx", dxPos, context.shapeWriter()); writeTextListAttribute("dy", dyPos, context.shapeWriter()); writeTextListAttribute("rotate", rotate, context.shapeWriter()); } if (!s->textLength.isAuto) { context.shapeWriter().addAttribute("textLength", KisDomUtils::toString(s->textLength.customValue)); if (s->lengthAdjust == KoSvgText::LengthAdjustSpacingAndGlyphs) { context.shapeWriter().addAttribute("lengthAdjust", "spacingAndGlyphs"); } } KoSvgTextChunkShape *parent = !isRootTextNode() ? dynamic_cast(this->parent()) : 0; KoSvgTextProperties parentProperties = parent ? parent->textProperties() : KoSvgTextProperties::defaultProperties(); KoSvgTextProperties ownProperties = textProperties().ownProperties(parentProperties); // we write down stroke/fill iff they are different from the parent's value if (!isRootTextNode()) { if (ownProperties.hasProperty(KoSvgTextProperties::FillId)) { SvgStyleWriter::saveSvgFill(this, context); } if (ownProperties.hasProperty(KoSvgTextProperties::StrokeId)) { SvgStyleWriter::saveSvgStroke(this, context); } } QMap attributes = ownProperties.convertToSvgTextAttributes(); for (auto it = attributes.constBegin(); it != attributes.constEnd(); ++it) { context.shapeWriter().addAttribute(it.key().toLatin1().data(), it.value()); } if (layoutInterface()->isTextNode()) { context.shapeWriter().addTextNode(s->text); } else { Q_FOREACH (KoShape *child, this->shapes()) { KoSvgTextChunkShape *childText = dynamic_cast(child); KIS_SAFE_ASSERT_RECOVER(childText) { continue; } childText->saveSvg(context); } } context.shapeWriter().endElement(); return true; } void KoSvgTextChunkShape::SharedData::loadContextBasedProperties(SvgGraphicsContext *gc) { properties = gc->textProperties; font = gc->font; fontFamiliesList = gc->fontFamiliesList; } void KoSvgTextChunkShape::resetTextShape() { using namespace KoSvgText; s->properties = KoSvgTextProperties(); s->font = QFont(); s->fontFamiliesList = QStringList(); s->textLength = AutoValue(); s->lengthAdjust = LengthAdjustSpacing; s->localTransformations.clear(); s->text.clear(); // all the subchunks are destroyed! // (first detach, then destroy) QList shapesToReset = shapes(); Q_FOREACH (KoShape *shape, shapesToReset) { shape->setParent(0); delete shape; } } bool KoSvgTextChunkShape::loadSvg(const KoXmlElement &e, SvgLoadingContext &context) { SvgGraphicsContext *gc = context.currentGC(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(gc, false); s->loadContextBasedProperties(gc); s->textLength = KoSvgText::parseAutoValueXY(e.attribute("textLength", ""), context, ""); s->lengthAdjust = KoSvgText::parseLengthAdjust(e.attribute("lengthAdjust", "spacing")); QVector xPos = parseListAttributeX(e.attribute("x", ""), context); QVector yPos = parseListAttributeY(e.attribute("y", ""), context); QVector dxPos = parseListAttributeX(e.attribute("dx", ""), context); QVector dyPos = parseListAttributeY(e.attribute("dy", ""), context); QVector rotate = parseListAttributeAngular(e.attribute("rotate", ""), context); const int numLocalTransformations = std::max({xPos.size(), yPos.size(), dxPos.size(), dyPos.size(), rotate.size()}); s->localTransformations.resize(numLocalTransformations); for (int i = 0; i < numLocalTransformations; i++) { if (i < xPos.size()) { s->localTransformations[i].xPos = xPos[i]; } if (i < yPos.size()) { s->localTransformations[i].yPos = yPos[i]; } if (i < dxPos.size() && dxPos[i] != 0.0) { s->localTransformations[i].dxPos = dxPos[i]; } if (i < dyPos.size() && dyPos[i] != 0.0) { s->localTransformations[i].dyPos = dyPos[i]; } if (i < rotate.size()) { s->localTransformations[i].rotate = rotate[i]; } } return true; } namespace { QString cleanUpString(QString text) { text.replace(QRegExp("[\\r\\n\u2028]"), ""); text.replace(QRegExp(" {2,}"), " "); return text; } enum Result { FoundNothing, FoundText, FoundSpace }; Result hasPreviousSibling(KoXmlNode node) { while (!node.isNull()) { if (node.isElement()) { KoXmlElement element = node.toElement(); if (element.tagName() == "text") break; } while (!node.previousSibling().isNull()) { node = node.previousSibling(); while (!node.lastChild().isNull()) { node = node.lastChild(); } if (node.isText()) { KoXmlText textNode = node.toText(); const QString text = cleanUpString(textNode.data()); if (!text.isEmpty()) { // if we are the leading whitespace, we should report that // we are the last if (text == " ") { return hasPreviousSibling(node) == FoundNothing ? FoundNothing : FoundSpace; } return text[text.size() - 1] != ' ' ? FoundText : FoundSpace; } } } node = node.parentNode(); } return FoundNothing; } Result hasNextSibling(KoXmlNode node) { while (!node.isNull()) { while (!node.nextSibling().isNull()) { node = node.nextSibling(); while (!node.firstChild().isNull()) { node = node.firstChild(); } if (node.isText()) { KoXmlText textNode = node.toText(); const QString text = cleanUpString(textNode.data()); // if we are the trailing whitespace, we should report that // we are the last if (text == " ") { return hasNextSibling(node) == FoundNothing ? FoundNothing : FoundSpace; } if (!text.isEmpty()) { return text[0] != ' ' ? FoundText : FoundSpace; } } } node = node.parentNode(); } return FoundNothing; } } bool KoSvgTextChunkShape::loadSvgTextNode(const KoXmlText &text, SvgLoadingContext &context) { SvgGraphicsContext *gc = context.currentGC(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(gc, false); s->loadContextBasedProperties(gc); QString data = cleanUpString(text.data()); const Result leftBorder = hasPreviousSibling(text); const Result rightBorder = hasNextSibling(text); if (data.startsWith(' ') && leftBorder == FoundNothing) { data.remove(0, 1); } if (data.endsWith(' ') && rightBorder != FoundText) { data.remove(data.size() - 1, 1); } if (data == " " && (leftBorder == FoundNothing || rightBorder == FoundNothing)) { data = ""; } //ENTER_FUNCTION() << text.data() << "-->" << data; s->text = data; return !data.isEmpty(); } void KoSvgTextChunkShape::normalizeCharTransformations() { applyParentCharTransformations(s->localTransformations); } void KoSvgTextChunkShape::simplifyFillStrokeInheritance() { if (!isRootTextNode()) { KoShape *parentShape = parent(); KIS_SAFE_ASSERT_RECOVER_RETURN(parentShape); QSharedPointer bg = background(); QSharedPointer parentBg = parentShape->background(); if (!inheritBackground() && ((!bg && !parentBg) || (bg && parentBg && bg->compareTo(parentShape->background().data())))) { setInheritBackground(true); } KoShapeStrokeModelSP stroke = this->stroke(); KoShapeStrokeModelSP parentStroke= parentShape->stroke(); if (!inheritStroke() && ((!stroke && !parentStroke) || (stroke && parentStroke && stroke->compareFillTo(parentShape->stroke().data()) && stroke->compareStyleTo(parentShape->stroke().data())))) { setInheritStroke(true); } } Q_FOREACH (KoShape *shape, shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(chunkShape); chunkShape->simplifyFillStrokeInheritance(); } } KoSvgTextProperties KoSvgTextChunkShape::textProperties() const { KoSvgTextProperties properties = s->properties; properties.setProperty(KoSvgTextProperties::FillId, QVariant::fromValue(KoSvgText::BackgroundProperty(background()))); properties.setProperty(KoSvgTextProperties::StrokeId, QVariant::fromValue(KoSvgText::StrokeProperty(stroke()))); return properties; } bool KoSvgTextChunkShape::isTextNode() const { return d->layoutInterface->isTextNode(); } KoSvgTextChunkShapeLayoutInterface *KoSvgTextChunkShape::layoutInterface() const { return d->layoutInterface.data(); } bool KoSvgTextChunkShape::isRichTextPreferred() const { return isRootTextNode() && s->isRichTextPreferred; } void KoSvgTextChunkShape::setRichTextPreferred(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(isRootTextNode()); s->isRichTextPreferred = value; } bool KoSvgTextChunkShape::isRootTextNode() const { return false; } /**************************************************************************************************/ /* KoSvgTextChunkShape::Private */ /**************************************************************************************************/ KoSvgTextChunkShape::SharedData::SharedData() : QSharedData() { } KoSvgTextChunkShape::SharedData::SharedData(const SharedData &rhs) : QSharedData() , properties(rhs.properties) , font(rhs.font) , fontFamiliesList(rhs.fontFamiliesList) , localTransformations(rhs.localTransformations) , textLength(rhs.textLength) , lengthAdjust(rhs.lengthAdjust) , text(rhs.text) , isRichTextPreferred(rhs.isRichTextPreferred) { } KoSvgTextChunkShape::SharedData::~SharedData() { } #include #include #include KoSvgText::KoSvgCharChunkFormat KoSvgTextChunkShape::fetchCharFormat() const { KoSvgText::KoSvgCharChunkFormat format; format.setFont(s->font); format.setTextAnchor(KoSvgText::TextAnchor(s->properties.propertyOrDefault(KoSvgTextProperties::TextAnchorId).toInt())); KoSvgText::Direction direction = KoSvgText::Direction(s->properties.propertyOrDefault(KoSvgTextProperties::DirectionId).toInt()); format.setLayoutDirection(direction == KoSvgText::DirectionLeftToRight ? Qt::LeftToRight : Qt::RightToLeft); KoSvgText::BaselineShiftMode shiftMode = KoSvgText::BaselineShiftMode(s->properties.propertyOrDefault(KoSvgTextProperties::BaselineShiftModeId).toInt()); // FIXME: we support only 'none', 'sub' and 'super' shifts at the moment. // Please implement 'percentage' as well! // WARNING!!! Qt's setVerticalAlignment() also changes the size of the font! And SVG does not(!) imply it! if (shiftMode == KoSvgText::ShiftSub) { format.setVerticalAlignment(QTextCharFormat::AlignSubScript); } else if (shiftMode == KoSvgText::ShiftSuper) { format.setVerticalAlignment(QTextCharFormat::AlignSuperScript); } KoSvgText::AutoValue letterSpacing = s->properties.propertyOrDefault(KoSvgTextProperties::LetterSpacingId).value(); if (!letterSpacing.isAuto) { format.setFontLetterSpacingType(QFont::AbsoluteSpacing); format.setFontLetterSpacing(letterSpacing.customValue); } KoSvgText::AutoValue wordSpacing = s->properties.propertyOrDefault(KoSvgTextProperties::WordSpacingId).value(); if (!wordSpacing.isAuto) { format.setFontWordSpacing(wordSpacing.customValue); } KoSvgText::AutoValue kerning = s->properties.propertyOrDefault(KoSvgTextProperties::KerningId).value(); if (!kerning.isAuto) { format.setFontKerning(false); format.setFontLetterSpacingType(QFont::AbsoluteSpacing); format.setFontLetterSpacing(format.fontLetterSpacing() + kerning.customValue); } QBrush textBrush = Qt::NoBrush; if (background()) { KoColorBackground *colorBackground = dynamic_cast(background().data()); if (!colorBackground) { qWarning() << "TODO: support gradient and pattern backgrounds for text"; textBrush = Qt::red; } if (colorBackground) { textBrush = colorBackground->brush(); } } format.setForeground(textBrush); QPen textPen = Qt::NoPen; if (stroke()) { KoShapeStroke *stroke = dynamic_cast(this->stroke().data()); if (stroke) { textPen = stroke->resultLinePen(); } } format.setTextOutline(textPen); // TODO: avoid const_cast somehow... format.setAssociatedShape(const_cast(this)); return format; } void KoSvgTextChunkShape::applyParentCharTransformations(const QVector transformations) { if (shapeCount()) { int numCharsPassed = 0; Q_FOREACH (KoShape *shape, shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(chunkShape); const int numCharsInSubtree = chunkShape->layoutInterface()->numChars(); QVector t = transformations.mid(numCharsPassed, numCharsInSubtree); if (t.isEmpty()) break; chunkShape->applyParentCharTransformations(t); numCharsPassed += numCharsInSubtree; if (numCharsPassed >= transformations.size()) break; } } else { for (int i = 0; i < qMin(transformations.size(), s->text.size()); i++) { KIS_SAFE_ASSERT_RECOVER_RETURN(s->localTransformations.size() >= i); if (s->localTransformations.size() == i) { s->localTransformations.append(transformations[i]); } else { s->localTransformations[i].mergeInParentTransformation(transformations[i]); } } } } diff --git a/libs/flake/text/KoSvgTextChunkShape.h b/libs/flake/text/KoSvgTextChunkShape.h index 87f4dbb1d0..ff89beff55 100644 --- a/libs/flake/text/KoSvgTextChunkShape.h +++ b/libs/flake/text/KoSvgTextChunkShape.h @@ -1,184 +1,182 @@ /* * 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 KOSVGTEXTCHUNKSHAPE_H #define KOSVGTEXTCHUNKSHAPE_H #include "kritaflake_export.h" #include #include #include class HtmlSavingContext; class KoSvgTextProperties; class KoSvgTextChunkShapePrivate; class KoSvgTextChunkShapeLayoutInterface; /** * KoSvgTextChunkShape is an elementary block of SVG text object. * * KoSvgTextChunkShape represents either a \ or \ element of SVG. * The chunk shape uses flake hierarchy to represent the DOM hierarchy of the * supplied text. All the attributes of text blocks can be fetched using * textProperties() method. * * KoSvgTextChunkShape uses special text properties object to overcome the * flake's "property inheritance" limitation. Basically, flake doesn't support * the inheritance of shape properties: every shape stores all the properties * that were defined at the stage of loading/creation. KoSvgTextProperties is a * wrapper that allows the user to compare the properties of the two shapes and * return only the ones that are unique for a child shape. That allows us to * generate a correct SVG/markup code that can be edited by the user easily. * * WARNING: beware the difference between "svg-text-chunk" and * KoSvgTextChunkShape! The chunk shape is **not** a "text chunk" in SVG's * definition. According to SVG, "text chunk" is a set of characters anchored to * a specific absolute position on canvas. And KoSvgTextChunkShape is just one * \ or \ element. Obviously, one \ can contain multiple "text * chunks" and, vice versa, a "text chunk" can spread onto multiple \'s. */ class KRITAFLAKE_EXPORT KoSvgTextChunkShape : public KoShapeContainer, public SvgShape { public: KoSvgTextChunkShape(); KoSvgTextChunkShape(const KoSvgTextChunkShape &rhs); ~KoSvgTextChunkShape() override; KoShape* cloneShape() const override; QSizeF size() const override; void setSize(const QSizeF &size) override; QRectF outlineRect() const override; QPainterPath outline() const override; void paintComponent(QPainter &painter, KoShapePaintingContext &paintContext) const override; - void saveOdf(KoShapeSavingContext &Context) const override; - bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &Context) override; bool saveHtml(HtmlSavingContext &context); /** * Reset the text shape into initial state, removing all the child shapes. * This method is used by text-updating code to upload the updated text * into shape. The uploading code first calls resetTextShape() and then adds * new children. */ virtual void resetTextShape(); bool saveSvg(SvgSavingContext &context) override; bool loadSvg(const KoXmlElement &element, SvgLoadingContext &context) override; bool loadSvgTextNode(const KoXmlText &text, SvgLoadingContext &context); /** * Normalize the SVG character transformations * * In SVG x,y,dx,dy,rotate attributes are inherited from the parent shapes * in a curious ways. We do not want to unwind the links on the fly, so we * just parse and adjust them right when the loading completed. After * normalizeCharTransformations() is finished, all the child shapes will * have local transformations set according to the parent's lists. */ void normalizeCharTransformations(); /** * Compress the inheritance of 'fill' and 'stroke'. * * The loading code makes no difference if the fill or stroke was inherited * or not. That is not a problem for normal shapes, but it cannot work for * text shapes. The text has different inheritance rules: if the parent * text chunk has a gradient fill, its inheriting descendants will * **smoothly continue** this gradient. They will not start a new gradient * in their local coordinate system. * * Therefore, after loading, the loading code calls * simplifyFillStrokeInheritance() which marks the coinciding strokes and * fills so that the rendering code will be able to distinguish them in the * future. * * Proper fill inheritance is also needed for the GUI. When the user * changes the color of the parent text chunk, all the inheriting children * should update its color automatically, without GUI recursively * traversing the shapes. * */ void simplifyFillStrokeInheritance(); /** * SVG properties of the text chunk * @return the properties object with fill and stroke included as a property */ KoSvgTextProperties textProperties() const; /** * Return the type of the chunk. * * The chunk can be either a "text chunk", that contains a text string * itself, of an "intermediate chunk" that doesn't contain any text itself, * but works as a group for a set of child chunks, which might be either * text (leaf) or intermediate chunks. Such groups are needed to define a * common text style for a group of '\' objects. * * @return true if the chunk is a "text chunk" false if it is "intermediate chunk" */ bool isTextNode() const; /** * A special interface for KoSvgTextShape's layout code. Don't use it * unless you are KoSvgTextShape. */ KoSvgTextChunkShapeLayoutInterface* layoutInterface() const; /** * WARNING: this propperty is available only if isRootTextNode() is true * * @return true if the shape should be edited in a rich-text editor */ bool isRichTextPreferred() const; /** * WARNING: this propperty is available only if isRootTextNode() is true * * Sets whether the shape should be edited in rich-text editor */ void setRichTextPreferred(bool value); protected: /** * Show if the shape is a root of the text hierarchy. Always true for * KoSvgTextShape and always false for KoSvgTextChunkShape */ virtual bool isRootTextNode() const; protected: KoSvgTextChunkShape(KoSvgTextChunkShapePrivate *dd); private: KoSvgText::KoSvgCharChunkFormat fetchCharFormat() const; void applyParentCharTransformations(const QVector transformations); private: class Private; QScopedPointer d; class SharedData; QSharedDataPointer s; }; #endif // KOSVGTEXTCHUNKSHAPE_H diff --git a/libs/odf/KoBorder.cpp b/libs/odf/KoBorder.cpp index c546abe8ff..dfb6a92ac0 100644 --- a/libs/odf/KoBorder.cpp +++ b/libs/odf/KoBorder.cpp @@ -1,1164 +1,597 @@ /* This file is part of the KDE project * * Copyright (C) 2009 Inge Wallin * Copyright (C) 2009 Thomas Zander * Copyright (C) 2011 Pierre Ducroquet * * 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 "KoBorder.h" #include #include #include #include #include #include class KoBorderPrivate : public QSharedData { public: KoBorderPrivate(); ~KoBorderPrivate(); QMap data; }; KoBorderPrivate::KoBorderPrivate() { } KoBorderPrivate::~KoBorderPrivate() { } KoBorder::BorderData::BorderData() : style(KoBorder::BorderNone) , outerPen(QPen()) , innerPen(QPen()) , spacing(0) { outerPen.setWidthF(0.0f); innerPen.setWidthF(0.0f); } bool KoBorder::BorderData::operator==(const KoBorder::BorderData& other) const { // Left Borders if (style == BorderNone && other.style == BorderNone) { // If both styles are None, then the rest of the values don't // need to be compared. ; } else if (style != other.style) { // If any of them are non-None, and they are different, the // borders are also different. return false; } else { // Here we know that the border styles are the same, now // compare the rest of the values. if (outerPen != other.outerPen) return false; // If the border style == BorderDouble, then compare a couple // of other values too. if (style == BorderDouble) { if (innerPen != other.innerPen) return false; if (spacing != other.spacing) return false; } } return true; } // ---------------------------------------------------------------- KoBorder::KoBorder() : d(new KoBorderPrivate) { } KoBorder::KoBorder(const KoBorder &kb) : d(kb.d) { } KoBorder::~KoBorder() { // No delete because d is a QSharedDataPointer. } // ---------------------------------------------------------------- // operators KoBorder &KoBorder::operator=(const KoBorder &other) { d = other.d; return *this; } bool KoBorder::operator==(const KoBorder &other) const { if (d.data() == other.d.data()) return true; if (d->data.size() != other.d->data.size()) return false; KoBorder::BorderSide key; foreach (key, d->data.keys()) { if (!other.d->data.contains(key)) return false; if (!(other.d->data[key] == d->data[key])) return false; } return true; } // ---------------------------------------------------------------- // public, non-class functions KoBorder::BorderStyle KoBorder::odfBorderStyle(const QString &borderstyle, bool *converted) { // Note: the styles marked "Not odf compatible" below are legacies // from the old words format. There are also lots of border // styles in the MS DOC that we may have to handle at some point. if (converted) *converted = true; if (borderstyle == "none") return BorderNone; if (borderstyle == "solid") return BorderSolid; if (borderstyle == "dashed") return BorderDashed; if (borderstyle == "dotted") return BorderDotted; if (borderstyle == "dot-dash") return BorderDashDot; if (borderstyle == "dot-dot-dash") return BorderDashDotDot; if (borderstyle == "double") return BorderDouble; if (borderstyle == "groove") // Not odf compatible -- see above return BorderGroove; if (borderstyle == "ridge") // Not odf compatible -- see above return BorderRidge; if (borderstyle == "inset") // Not odf compatible -- see above return BorderInset; if (borderstyle == "outset") // Not odf compatible -- see above return BorderOutset; if (borderstyle == "dash-largegap") return KoBorder::BorderDashedLong; if (borderstyle == "slash") // not officially odf, but we support it anyway return KoBorder::BorderSlash; if (borderstyle == "wave") // not officially odf, but we support it anyway return KoBorder::BorderWave; if (borderstyle == "double-wave") // not officially odf, but we support it anyway return KoBorder::BorderDoubleWave; if (converted) *converted = false; return BorderSolid; } QString KoBorder::odfBorderStyleString(BorderStyle borderstyle) { switch (borderstyle) { case BorderDashed: return QString("dashed"); case BorderDotted: return QString("dotted"); case BorderDashDot: return QString("dot-dash"); case BorderDashDotDot: return QString("dot-dot-dash"); case BorderDouble: return QString("double"); case BorderGroove: return QString("groove"); // not odf -- see above case BorderRidge: return QString("ridge"); // not odf -- see above case BorderInset: return QString("inset"); // not odf -- see above case BorderOutset: return QString("outset"); // not odf -- see above case BorderSolid: return QString("solid"); case BorderNone: return QString("none"); default: // Handle unknown types as solid. return QString("solid"); } } QString KoBorder::msoBorderStyleString(BorderStyle borderstyle) { switch (borderstyle) { case KoBorder::BorderDashedLong: return QString("dash-largegap"); case KoBorder::BorderSlash: return QString("slash"); // not officially odf, but we support it anyway case KoBorder::BorderWave: return QString("wave"); // not officially odf, but we support it anyway case KoBorder::BorderDoubleWave: return QString("double-wave"); // not officially odf, but we support it anyway default: // Handle remaining styles as odf type style. return odfBorderStyleString(borderstyle); } } // ---------------------------------------------------------------- // Getters and Setters void KoBorder::setBorderStyle(BorderSide side, BorderStyle style) { if (d->data[side].style == style) { return; } if (!d->data.contains(side)) { BorderData data; data.style = style; d->data[side] = data; } else { d->data[side].style = style; } // Make a best effort to create the best possible dash pattern for the chosen style. // FIXME: KoTableCellStyle::setEdge() should call this function. BorderData &edge = d->data[side]; qreal width = edge.outerPen.widthF(); qreal innerWidth = 0; qreal middleWidth = 0; qreal space = 0; QVector dashes; switch (style) { case KoBorder::BorderNone: width = 0.0; break; case KoBorder::BorderDouble: innerWidth = space = edge.outerPen.width() / 3; //some nice default look width -= (space + innerWidth); edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderDotted: dashes << 1 << 1; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderDashed: dashes << 4 << 1; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderDashedLong: { dashes << 4 << 4; edge.outerPen.setDashPattern(dashes); break; } case KoBorder::BorderTriple: innerWidth = middleWidth = space = width/6; width -= (space + innerWidth); edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderDashDot: dashes << 3 << 3<< 7 << 3; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderDashDotDot: dashes << 2 << 2<< 6 << 2 << 2 << 2; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderWave: edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderSlash: edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderDoubleWave: innerWidth = space = width/3; //some nice default look width -= (space + innerWidth); edge.outerPen.setStyle(Qt::SolidLine); break; default: edge.outerPen.setStyle(Qt::SolidLine); break; } edge.outerPen.setJoinStyle(Qt::MiterJoin); edge.outerPen.setCapStyle(Qt::FlatCap); edge.outerPen.setWidthF(width); edge.spacing = space; edge.innerPen = edge.outerPen; edge.innerPen.setWidthF(innerWidth); } KoBorder::BorderStyle KoBorder::borderStyle(BorderSide side) const { if (!d->data.contains(side)) { return BorderNone; } else { return d->data[side].style; } } void KoBorder::setBorderColor(BorderSide side, const QColor &color) { if (!d->data.contains(side)) { BorderData data; data.outerPen.setColor(color); d->data[side] = data; } else { d->data[side].outerPen.setColor(color); } } QColor KoBorder::borderColor(BorderSide side) const { if (!d->data.contains(side)) { return QColor(); } else { return d->data[side].outerPen.color(); } } void KoBorder::setBorderWidth(BorderSide side, qreal width) { if (!d->data.contains(side)) { BorderData data; data.outerPen.setWidthF(width); d->data[side] = data; } else { d->data[side].outerPen.setWidthF(width); } } qreal KoBorder::borderWidth(BorderSide side) const { if (!d->data.contains(side)) { return 0; } else { if (d->data[side].style == BorderDouble) return (d->data[side].outerPen.widthF() + d->data[side].innerPen.widthF() + d->data[side].spacing); else return d->data[side].outerPen.widthF(); } } void KoBorder::setOuterBorderWidth(BorderSide side, qreal width) { if (!d->data.contains(side)) { BorderData data; data.outerPen.setWidthF(width); d->data[side] = data; } else { d->data[side].outerPen.setWidthF(width); } } qreal KoBorder::outerBorderWidth(BorderSide side) const { if (!d->data.contains(side)) { return 0; } else { return d->data[side].outerPen.widthF(); } } void KoBorder::setInnerBorderWidth(BorderSide side, qreal width) { if (!d->data.contains(side)) { BorderData data; data.innerPen.setWidthF(width); d->data[side] = data; } else { d->data[side].innerPen.setWidthF(width); } } qreal KoBorder::innerBorderWidth(BorderSide side) const { if (!d->data.contains(side)) { return 0; } else { return d->data[side].innerPen.widthF(); } } void KoBorder::setBorderSpacing(BorderSide side, qreal width) { if (!d->data.contains(side)) { BorderData data; data.spacing = width; d->data[side] = data; } else { d->data[side].spacing = width; } } qreal KoBorder::borderSpacing(BorderSide side) const { if (!d->data.contains(side)) { return 0; } else { return d->data[side].spacing; } } KoBorder::BorderData KoBorder::borderData(BorderSide side) const { return d->data.value(side, BorderData()); } void KoBorder::setBorderData(BorderSide side, const BorderData &data) { d->data[side] = data; } // ------------------------------- bool KoBorder::hasBorder() const { if (borderStyle(LeftBorder) != BorderNone && borderWidth(LeftBorder) > 0.0) return true; if (borderStyle(RightBorder) != BorderNone && borderWidth(RightBorder) > 0.0) return true; if (borderStyle(TopBorder) != BorderNone && borderWidth(TopBorder) > 0.0) return true; if (borderStyle(BottomBorder) != BorderNone && borderWidth(BottomBorder) > 0.0) return true; if (borderStyle(TlbrBorder) != BorderNone && borderWidth(TlbrBorder) > 0.0) return true; if (borderStyle(BltrBorder) != BorderNone && borderWidth(BltrBorder) > 0.0) return true; return false; } bool KoBorder::hasBorder(KoBorder::BorderSide side) const { return borderStyle(side) != BorderNone && borderWidth(side) > 0.0; } // ---------------------------------------------------------------- // painting void KoBorder::paint(QPainter &painter, const QRectF &borderRect, BorderPaintArea whereToPaint) const { Q_UNUSED(whereToPaint); // In tables it is apparently best practice to paint the // horizontal lines over the vertical ones. So let's use the same // strategy here. QPointF start; QPointF end; // FIXME: Make KoBorder store pointers to BorderData instead. This is very inefficient. BorderData leftEdge = borderData(KoBorder::LeftBorder); BorderData rightEdge = borderData(KoBorder::RightBorder); BorderData topEdge = borderData(KoBorder::TopBorder); BorderData bottomEdge = borderData(KoBorder::BottomBorder); // Left border if (hasBorder(LeftBorder)) { start = borderRect.topLeft(); end = borderRect.bottomLeft(); paintBorderSide(painter, start, end, &leftEdge, true, hasBorder(TopBorder) ? &topEdge : 0, hasBorder(BottomBorder) ? &bottomEdge : 0, 1); } // Right border if (hasBorder(RightBorder)) { start = borderRect.topRight(); end = borderRect.bottomRight(); paintBorderSide(painter, start, end, &rightEdge, true, hasBorder(TopBorder) ? &topEdge : 0, hasBorder(BottomBorder) ? &bottomEdge : 0, -1); } // Top border if (hasBorder(TopBorder)) { start = borderRect.topLeft(); end = borderRect.topRight(); paintBorderSide(painter, start, end, &topEdge, false, hasBorder(LeftBorder) ? &leftEdge : 0, hasBorder(RightBorder) ? &rightEdge : 0, 1); } // Bottom border if (hasBorder(BottomBorder)) { start = borderRect.bottomLeft(); end = borderRect.bottomRight(); paintBorderSide(painter, start, end, &bottomEdge, false, hasBorder(LeftBorder) ? &leftEdge : 0, hasBorder(RightBorder) ? &rightEdge : 0, -1); } // FIXME: Diagonal borders } void KoBorder::paintBorderSide(QPainter &painter, QPointF lineStart, QPointF lineEnd, BorderData *borderData, bool isVertical, BorderData *neighbour1, BorderData *neighbour2, int inwardsAcross) const { // Adjust the outer line so that it is inside the boundary. qreal displacement = borderData->outerPen.widthF() / qreal(2.0); if (isVertical) { lineStart.setX(lineStart.x() + inwardsAcross * displacement); lineEnd.setX(lineEnd.x() + inwardsAcross * displacement); } else { lineStart.setY(lineStart.y() + inwardsAcross * displacement); lineEnd.setY(lineEnd.y() + inwardsAcross * displacement); } painter.setPen(borderData->outerPen); painter.drawLine(lineStart, lineEnd); if (borderData->style == BorderDouble) { displacement = (borderData->outerPen.widthF() / qreal(2.0) + borderData->spacing + borderData->innerPen.widthF() / qreal(2.0)); if (isVertical) { lineStart.setX(lineStart.x() + inwardsAcross * displacement); lineEnd.setX(lineEnd.x() + inwardsAcross * displacement); } else { lineStart.setY(lineStart.y() + inwardsAcross * displacement); lineEnd.setY(lineEnd.y() + inwardsAcross * displacement); } // Adjust for neighboring inner lines. if (neighbour1 && neighbour1->style == BorderDouble) { displacement = neighbour1->outerPen.widthF() + neighbour1->spacing; if (isVertical) { lineStart.setY(lineStart.y() + displacement); } else { lineStart.setX(lineStart.x() + displacement); } } if (neighbour2 && neighbour2->style == BorderDouble) { displacement = neighbour2->outerPen.widthF() + neighbour2->spacing; if (isVertical) { lineEnd.setY(lineEnd.y() - displacement); } else { lineEnd.setX(lineEnd.x() - displacement); } } // Draw the inner line. painter.setPen(borderData->innerPen); painter.drawLine(lineStart, lineEnd); } } - - -// ---------------------------------------------------------------- -// static functions - - -void parseOdfBorder(const QString &border, QColor *color, - KoBorder::BorderStyle *borderStyle, bool *hasBorderStyle, - qreal *borderWidth, bool *hasBorderWidth) -{ - *hasBorderStyle = false; - *hasBorderWidth = false; - - if (!border.isEmpty() && border != "none" && border != "hidden") { - QStringList borderData = border.split(' ', QString::SkipEmptyParts); - if (borderData.length() > 0) - { - const QColor borderColor = QColor(borderData.last()); - if (borderColor.isValid()) { - *color = borderColor; - borderData.removeLast(); - } - - bool converted = false; - const KoBorder::BorderStyle parsedBorderStyle = KoBorder::odfBorderStyle(borderData.last(), &converted); - if (converted) { - *hasBorderStyle = true; - borderData.removeLast(); - *borderStyle = parsedBorderStyle; - } - - if (!borderData.isEmpty()) { - const qreal parsedBorderWidth = KoUnit::parseValue(borderData[0], 1.0); - *borderWidth = parsedBorderWidth; - *hasBorderWidth = true; - } - } - } -} - -// ---------------------------------------------------------------- -// load and save - -bool KoBorder::loadOdf(const KoXmlElement &style) -{ - bool result = false; - - QString borderString; - bool hasSpecialBorder; - QString specialBorderString; - if (style.hasAttributeNS(KoXmlNS::fo, "border")) { - borderString = style.attributeNS(KoXmlNS::fo, "border"); - if (borderString == "none") { - // We use the "false" to indicate that there is no border - // rather than that the parsing has failed. - return false; - } - - result = true; - if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder"))) { - specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder"); - } - parseAndSetBorder(borderString, hasSpecialBorder, specialBorderString); - } - else { - // No common border attributes, check for the individual ones. - if (style.hasAttributeNS(KoXmlNS::fo, "border-left")) { - result = true; - borderString = style.attributeNS(KoXmlNS::fo, "border-left"); - if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-left"))) { - specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-left"); - } - parseAndSetBorder(LeftBorder, borderString, hasSpecialBorder, specialBorderString); - } - if (style.hasAttributeNS(KoXmlNS::fo, "border-top")) { - result = true; - borderString = style.attributeNS(KoXmlNS::fo, "border-top"); - if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-top"))) { - specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-top"); - } - parseAndSetBorder(TopBorder, borderString, hasSpecialBorder, specialBorderString); - } - if (style.hasAttributeNS(KoXmlNS::fo, "border-right")) { - result = true; - borderString = style.attributeNS(KoXmlNS::fo, "border-right"); - if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-right"))) { - specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-right"); - } - parseAndSetBorder(RightBorder, borderString, hasSpecialBorder, specialBorderString); - } - if (style.hasAttributeNS(KoXmlNS::fo, "border-bottom")) { - result = true; - borderString = style.attributeNS(KoXmlNS::fo, "border-bottom"); - if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-bottom"))) { - specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-bottom"); - } - parseAndSetBorder(BottomBorder, borderString, hasSpecialBorder, specialBorderString); - } - } - - // Diagonals are treated individually and are NOT part of . - if (style.hasAttributeNS(KoXmlNS::style, "diagonal-tl-br")) { - result = true; - borderString = style.attributeNS(KoXmlNS::fo, "border-tl-br"); - if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-tl-br"))) { - specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-tl-br"); - } - parseAndSetBorder(TlbrBorder, borderString, hasSpecialBorder, specialBorderString); - } - if (style.hasAttributeNS(KoXmlNS::style, "diagonal-bl-tr")) { - result = true; - borderString = style.attributeNS(KoXmlNS::fo, "border-bl-tr"); - if ((hasSpecialBorder = style.hasAttributeNS(KoXmlNS::calligra, "specialborder-bl-tr"))) { - specialBorderString = style.attributeNS(KoXmlNS::calligra, "specialborder-bl-tr"); - } - parseAndSetBorder(BltrBorder, borderString, hasSpecialBorder, specialBorderString); - } - - // Handle double borders. - if (style.hasAttributeNS(KoXmlNS::style, "border-line-width")) { - result = true; - QString borderLineWidth = style.attributeNS(KoXmlNS::style, "border-line-width"); - if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { - QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); - setInnerBorderWidth(LeftBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(LeftBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(LeftBorder, KoUnit::parseValue(blw[2], 0.1)); - - setInnerBorderWidth(TopBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(TopBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(TopBorder, KoUnit::parseValue(blw[2], 0.1)); - - setInnerBorderWidth(RightBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(RightBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(RightBorder, KoUnit::parseValue(blw[2], 0.1)); - - setInnerBorderWidth(BottomBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(BottomBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(BottomBorder, KoUnit::parseValue(blw[2], 0.1)); - } - } - else { - if (style.hasAttributeNS(KoXmlNS::style, "border-line-width-left")) { - result = true; - QString borderLineWidth = style.attributeNS(KoXmlNS::style, "border-line-width-left"); - if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { - QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); - setInnerBorderWidth(LeftBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(LeftBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(LeftBorder, KoUnit::parseValue(blw[2], 0.1)); - } - } - if (style.hasAttributeNS(KoXmlNS::style, "border-line-width-top")) { - result = true; - QString borderLineWidth = style.attributeNS(KoXmlNS::style, "border-line-width-top"); - if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { - QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); - setInnerBorderWidth(TopBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(TopBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(TopBorder, KoUnit::parseValue(blw[2], 0.1)); - } - } - if (style.hasAttributeNS(KoXmlNS::style, "border-line-width-right")) { - result = true; - QString borderLineWidth = style.attributeNS(KoXmlNS::style, "border-line-width-right"); - if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { - QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); - setInnerBorderWidth(RightBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(RightBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(RightBorder, KoUnit::parseValue(blw[2], 0.1)); - } - } - if (style.hasAttributeNS(KoXmlNS::style, "border-line-width-bottom")) { - result = true; - QString borderLineWidth = style.attributeNS(KoXmlNS::style, "border-line-width-bottom"); - if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { - QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); - setInnerBorderWidth(BottomBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(BottomBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(BottomBorder, KoUnit::parseValue(blw[2], 0.1)); - } - } - } - - if (style.hasAttributeNS(KoXmlNS::style, "diagonal-tl-br-widths")) { - result = true; - QString borderLineWidth = style.attributeNS(KoXmlNS::style, "diagonal-tl-br-widths"); - if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { - QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); - setInnerBorderWidth(TlbrBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(TlbrBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(TlbrBorder, KoUnit::parseValue(blw[2], 0.1)); - } - } - if (style.hasAttributeNS(KoXmlNS::style, "diagonal-bl-tr-widths")) { - result = true; - QString borderLineWidth = style.attributeNS(KoXmlNS::style, "diagonal-bl-tr-widths"); - if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { - QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); - setInnerBorderWidth(BltrBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(BltrBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(BltrBorder, KoUnit::parseValue(blw[2], 0.1)); - } - } - return result; -} - -bool KoBorder::loadOdf(const KoStyleStack &styleStack) -{ - bool result = false; - - QString borderString; - bool hasSpecialBorder; - QString specialBorderString; - if (styleStack.hasProperty(KoXmlNS::fo, "border")) { - result = true; - borderString = styleStack.property(KoXmlNS::fo, "border"); - if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder"))) { - specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder"); - } - parseAndSetBorder(borderString, hasSpecialBorder, specialBorderString); - } - - // Even if there are common border attributes, check for the - // individual ones since they have precedence. - - if (styleStack.hasProperty(KoXmlNS::fo, "border-left")) { - result = true; - borderString = styleStack.property(KoXmlNS::fo, "border-left"); - if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-left"))) { - specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-left"); - } - parseAndSetBorder(LeftBorder, borderString, hasSpecialBorder, specialBorderString); - } - if (styleStack.hasProperty(KoXmlNS::fo, "border-top")) { - result = true; - borderString = styleStack.property(KoXmlNS::fo, "border-top"); - if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-top"))) { - specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-top"); - } - parseAndSetBorder(TopBorder, borderString, hasSpecialBorder, specialBorderString); - } - if (styleStack.hasProperty(KoXmlNS::fo, "border-right")) { - result = true; - borderString = styleStack.property(KoXmlNS::fo, "border-right"); - if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-right"))) { - specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-right"); - } - parseAndSetBorder(RightBorder, borderString, hasSpecialBorder, specialBorderString); - - } - if (styleStack.hasProperty(KoXmlNS::fo, "border-bottom")) { - result = true; - borderString = styleStack.property(KoXmlNS::fo, "border-bottom"); - if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-bottom"))) { - specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-bottom"); - } - parseAndSetBorder(BottomBorder, borderString, hasSpecialBorder, specialBorderString); - } - - // Diagonals are treated individually and are NOT part of . - if (styleStack.hasProperty(KoXmlNS::style, "diagonal-tl-br")) { - result = true; - borderString = styleStack.property(KoXmlNS::fo, "border-tl-br"); - if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-tl-br"))) { - specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-tl-br"); - } - parseAndSetBorder(TlbrBorder, borderString, hasSpecialBorder, specialBorderString); - } - if (styleStack.hasProperty(KoXmlNS::style, "diagonal-bl-tr")) { - result = true; - borderString = styleStack.property(KoXmlNS::fo, "border-bl-tr"); - if ((hasSpecialBorder = styleStack.hasProperty(KoXmlNS::calligra, "specialborder-bl-tr"))) { - specialBorderString = styleStack.property(KoXmlNS::calligra, "specialborder-bl-tr"); - } - parseAndSetBorder(BltrBorder, borderString, hasSpecialBorder, specialBorderString); - } - - // Handle double borders. - if (styleStack.hasProperty(KoXmlNS::style, "border-line-width")) { - result = true; - QString borderLineWidth = styleStack.property(KoXmlNS::style, "border-line-width"); - if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { - QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); - setInnerBorderWidth(LeftBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(LeftBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(LeftBorder, KoUnit::parseValue(blw[2], 0.1)); - - setInnerBorderWidth(TopBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(TopBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(TopBorder, KoUnit::parseValue(blw[2], 0.1)); - - setInnerBorderWidth(RightBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(RightBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(RightBorder, KoUnit::parseValue(blw[2], 0.1)); - - setInnerBorderWidth(BottomBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(BottomBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(BottomBorder, KoUnit::parseValue(blw[2], 0.1)); - } - } - // Even if there are common border attributes, check for the - // individual ones since they have precedence. - - if (styleStack.hasProperty(KoXmlNS::style, "border-line-width-left")) { - result = true; - QString borderLineWidth = styleStack.property(KoXmlNS::style, "border-line-width-left"); - if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { - QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); - setInnerBorderWidth(LeftBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(LeftBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(LeftBorder, KoUnit::parseValue(blw[2], 0.1)); - } - } - if (styleStack.hasProperty(KoXmlNS::style, "border-line-width-top")) { - result = true; - QString borderLineWidth = styleStack.property(KoXmlNS::style, "border-line-width-top"); - if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { - QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); - setInnerBorderWidth(TopBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(TopBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(TopBorder, KoUnit::parseValue(blw[2], 0.1)); - } - } - if (styleStack.hasProperty(KoXmlNS::style, "border-line-width-right")) { - result = true; - QString borderLineWidth = styleStack.property(KoXmlNS::style, "border-line-width-right"); - if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { - QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); - setInnerBorderWidth(RightBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(RightBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(RightBorder, KoUnit::parseValue(blw[2], 0.1)); - } - } - if (styleStack.hasProperty(KoXmlNS::style, "border-line-width-bottom")) { - result = true; - QString borderLineWidth = styleStack.property(KoXmlNS::style, "border-line-width-bottom"); - if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { - QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); - setInnerBorderWidth(BottomBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(BottomBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(BottomBorder, KoUnit::parseValue(blw[2], 0.1)); - } - } - - // Diagonals are treated individually and are NOT part of . - if (styleStack.hasProperty(KoXmlNS::style, "diagonal-tl-br-widths")) { - result = true; - QString borderLineWidth = styleStack.property(KoXmlNS::style, "diagonal-tl-br-widths"); - if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { - QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); - setInnerBorderWidth(TlbrBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(TlbrBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(TlbrBorder, KoUnit::parseValue(blw[2], 0.1)); - } - } - if (styleStack.hasProperty(KoXmlNS::style, "diagonal-bl-tr-widths")) { - result = true; - QString borderLineWidth = styleStack.property(KoXmlNS::style, "diagonal-bl-tr-widths"); - if (!borderLineWidth.isEmpty() && borderLineWidth != "none" && borderLineWidth != "hidden") { - QStringList blw = borderLineWidth.split(' ', QString::SkipEmptyParts); - setInnerBorderWidth(BltrBorder, KoUnit::parseValue(blw[0], 0.1)); - setBorderSpacing(BltrBorder, KoUnit::parseValue(blw[1], 1.0)); - setBorderWidth(BltrBorder, KoUnit::parseValue(blw[2], 0.1)); - } - } - - return result; -} - - -// Private -void KoBorder::parseAndSetBorder(const QString &borderString, - bool hasSpecialBorder, const QString &specialBorderString) -{ - if (borderString == "none") { - return; - } - - //debugOdf << "*** *** Found border: " << border; - QColor bordersColor; - BorderStyle bordersStyle; - qreal bordersWidth; - bool foundStyle; - bool foundWidth; - parseOdfBorder(borderString, &bordersColor, &bordersStyle, &foundStyle, - &bordersWidth, &foundWidth); - if (bordersColor.isValid()) { - setBorderColor(LeftBorder, bordersColor); - setBorderColor(TopBorder, bordersColor); - setBorderColor(RightBorder, bordersColor); - setBorderColor(BottomBorder, bordersColor); - } - if (hasSpecialBorder) { - bordersStyle = KoBorder::odfBorderStyle(specialBorderString, &foundStyle); - } - - if (foundStyle) { - setBorderStyle(LeftBorder, bordersStyle); - setBorderStyle(TopBorder, bordersStyle); - setBorderStyle(RightBorder, bordersStyle); - setBorderStyle(BottomBorder, bordersStyle); - } - if (foundWidth) { - setBorderWidth(LeftBorder, bordersWidth); - setBorderWidth(TopBorder, bordersWidth); - setBorderWidth(RightBorder, bordersWidth); - setBorderWidth(BottomBorder, bordersWidth); - } -} - -// Private -void KoBorder::parseAndSetBorder(const BorderSide borderSide, const QString &borderString, - bool hasSpecialBorder, const QString &specialBorderString) -{ - QColor borderColor; - BorderStyle borderStyle; - qreal borderWidth; - bool foundStyle; - bool foundWidth; - - parseOdfBorder(borderString, &borderColor, &borderStyle, &foundStyle, - &borderWidth, &foundWidth); - if (borderColor.isValid()) { - setBorderColor(borderSide, borderColor); - } - if (hasSpecialBorder) { - borderStyle = KoBorder::odfBorderStyle(specialBorderString, &foundStyle); - } - - if (foundStyle) { - setBorderStyle( borderSide, borderStyle); - } - if (foundWidth) { - setBorderWidth( borderSide, borderWidth); - } -} - -void KoBorder::saveOdf(KoGenStyle &style, KoGenStyle::PropertyType type) const -{ - // Get the strings that describe respective borders. - QString leftBorderString = QString("%1pt %2 %3") - .arg(QString::number(borderWidth(LeftBorder)), - odfBorderStyleString(borderStyle(LeftBorder)), - borderColor(LeftBorder).name()); - QString rightBorderString = QString("%1pt %2 %3") - .arg(QString::number(borderWidth(RightBorder)), - odfBorderStyleString(borderStyle(RightBorder)), - borderColor(RightBorder).name()); - QString topBorderString = QString("%1pt %2 %3") - .arg(QString::number(borderWidth(TopBorder)), - odfBorderStyleString(borderStyle(TopBorder)), - borderColor(TopBorder).name()); - QString bottomBorderString = QString("%1pt %2 %3") - .arg(QString::number(borderWidth(BottomBorder)), - odfBorderStyleString(borderStyle(BottomBorder)), - borderColor(BottomBorder).name()); - - QString tlbrBorderString = QString("%1pt %2 %3") - .arg(QString::number(borderWidth(TlbrBorder)), - odfBorderStyleString(borderStyle(TlbrBorder)), - borderColor(TlbrBorder).name()); - QString trblBorderString = QString("%1pt %2 %3") - .arg(QString::number(borderWidth(BltrBorder)), - odfBorderStyleString(borderStyle(BltrBorder)), - borderColor(BltrBorder).name()); - - // Get the strings that describe respective special borders (for special mso support). - QString leftBorderSpecialString = msoBorderStyleString(borderStyle(LeftBorder)); - QString rightBorderSpecialString = msoBorderStyleString(borderStyle(RightBorder)); - QString topBorderSpecialString = msoBorderStyleString(borderStyle(TopBorder)); - QString bottomBorderSpecialString = msoBorderStyleString(borderStyle(BottomBorder)); - QString tlbrBorderSpecialString = msoBorderStyleString(borderStyle(TlbrBorder)); - QString trblBorderSpecialString = msoBorderStyleString(borderStyle(BltrBorder)); - - // Check if we can save all borders in one fo:border attribute, or - // if we have to use several different ones like fo:border-left, etc. - if (leftBorderString == rightBorderString - && leftBorderString == topBorderString - && leftBorderString == bottomBorderString) { - - // Yes, they were all the same, so use only fo:border - style.addProperty("fo:border", leftBorderString, type); - style.addProperty("calligra:specialborder-left", leftBorderSpecialString, type); - style.addProperty("calligra:specialborder-right", rightBorderSpecialString, type); - style.addProperty("calligra:specialborder-top", topBorderSpecialString, type); - style.addProperty("calligra:specialborder-bottom", bottomBorderSpecialString, type); - } else { - // No, they were different, so use the individual borders. - //if (leftBorderStyle() != BorderNone) - style.addProperty("fo:border-left", leftBorderString, type); - style.addProperty("calligra:specialborder-left", leftBorderSpecialString, type); - //if (rightBorderStyle() != BorderNone) - style.addProperty("fo:border-right", rightBorderString, type); - style.addProperty("calligra:specialborder-right", rightBorderSpecialString, type); - //if (topBorderStyle() != BorderNone) - style.addProperty("fo:border-top", topBorderString, type); - style.addProperty("calligra:specialborder-top", topBorderSpecialString, type); - //if (bottomBorderStyle() != BorderNone) - style.addProperty("fo:border-bottom", bottomBorderString, type); - style.addProperty("calligra:specialborder-bottom", bottomBorderSpecialString, type); - } - - if (style.type() != KoGenStyle::PageLayoutStyle) { - //if (tlbrBorderStyle() != BorderNone) { - style.addProperty("style:diagonal-tl-br", tlbrBorderString, type); - //} - //if (trblBorderStyle() != BorderNone) { - style.addProperty("style:diagonal-bl-tr", trblBorderString, type); - //} - } - - // Handle double borders - QString leftBorderLineWidth = QString("%1pt %2pt %3pt") - .arg(QString::number(innerBorderWidth(LeftBorder)), - QString::number(borderSpacing(LeftBorder)), - QString::number(borderWidth(LeftBorder))); - QString rightBorderLineWidth = QString("%1pt %2pt %3pt") - .arg(QString::number(innerBorderWidth(RightBorder)), - QString::number(borderSpacing(RightBorder)), - QString::number(borderWidth(RightBorder))); - QString topBorderLineWidth = QString("%1pt %2pt %3pt") - .arg(QString::number(innerBorderWidth(TopBorder)), - QString::number(borderSpacing(TopBorder)), - QString::number(borderWidth(TopBorder))); - QString bottomBorderLineWidth = QString("%1pt %2pt %3pt") - .arg(QString::number(innerBorderWidth(BottomBorder)), - QString::number(borderSpacing(BottomBorder)), - QString::number(borderWidth(BottomBorder))); - - QString tlbrBorderLineWidth = QString("%1pt %2pt %3pt") - .arg(QString::number(innerBorderWidth(TlbrBorder)), - QString::number(borderSpacing(TlbrBorder)), - QString::number(borderWidth(TlbrBorder))); - QString trblBorderLineWidth = QString("%1pt %2pt %3pt") - .arg(QString::number(innerBorderWidth(BltrBorder)), - QString::number(borderSpacing(BltrBorder)), - QString::number(borderWidth(BltrBorder))); - - if (leftBorderLineWidth == rightBorderLineWidth - && leftBorderLineWidth == topBorderLineWidth - && leftBorderLineWidth == bottomBorderLineWidth - && borderStyle(LeftBorder) == borderStyle(RightBorder) - && borderStyle(TopBorder) == borderStyle(BottomBorder) - && borderStyle(TopBorder) == borderStyle(LeftBorder) - && (borderStyle(LeftBorder) == BorderDouble || borderStyle(LeftBorder) == BorderDoubleWave)) { - style.addProperty("style:border-line-width", leftBorderLineWidth, type); - } else { - if (borderStyle(LeftBorder) == BorderDouble || borderStyle(LeftBorder) == BorderDoubleWave) - style.addProperty("style:border-line-width-left", leftBorderLineWidth, type); - if (borderStyle(RightBorder) == BorderDouble || borderStyle(RightBorder) == BorderDoubleWave) - style.addProperty("style:border-line-width-right", rightBorderLineWidth, type); - if (borderStyle(TopBorder) == BorderDouble || borderStyle(TopBorder) == BorderDoubleWave) - style.addProperty("style:border-line-width-top", topBorderLineWidth, type); - if (borderStyle(BottomBorder) == BorderDouble || borderStyle(BottomBorder) == BorderDoubleWave) - style.addProperty("style:border-line-width-bottom", bottomBorderLineWidth, type); - } - - if (style.type() != KoGenStyle::PageLayoutStyle) { - if (borderStyle(TlbrBorder) == BorderDouble || borderStyle(TlbrBorder) == BorderDoubleWave) { - style.addProperty("style:diagonal-tl-br-widths", tlbrBorderLineWidth, type); - } - if (borderStyle(BltrBorder) == BorderDouble || borderStyle(BltrBorder) == BorderDoubleWave) { - style.addProperty("style:diagonal-bl-tr-widths", trblBorderLineWidth, type); - } - } -} diff --git a/libs/odf/KoBorder.h b/libs/odf/KoBorder.h index bcabbf8dec..4c74f27eb2 100644 --- a/libs/odf/KoBorder.h +++ b/libs/odf/KoBorder.h @@ -1,185 +1,170 @@ /* This file is part of the KDE project * * Copyright (C) 2009 Inge wallin * Copyright (C) 2009 Thomas Zander * Copyright (C) 2011 Pierre Ducroquet * * 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 KOBORDER_H #define KOBORDER_H #include "kritaodf_export.h" #include #include #include #include "KoXmlReaderForward.h" #include "KoGenStyle.h" class QPainter; class KoStyleStack; class KoBorderPrivate; class QColor; /** * A container for all properties of a generic border as defined by ODF. * * A border is used in at least the following contexts: * - paragraph * - page * - table * - table cell * */ class KRITAODF_EXPORT KoBorder { public: // Names of the border sides. // // The "rect" we refer to below is the rectangle around the object // with the border. This could be a page, a cell, a paragraph, etc. enum BorderSide { TopBorder = 0, ///< References the border at the top of the rect LeftBorder, ///< References the border at the left side of the rect BottomBorder, ///< References the border at the bottom of the rect RightBorder, ///< References the border at the right side of the rect TlbrBorder, ///< References the border from top left corner to bottom right corner of cell BltrBorder ///< References the border from bottom left corner to top right corner of cell }; /// Names of the different types of borders. // // Note that some of the border types are legacies from the old Words format. enum BorderStyle { BorderNone, ///< no border. This value forces the computed value of 'border-width' to be '0'. BorderDotted, ///< The border is a series of dots. BorderDashed, ///< The border is a series of short line segments. BorderSolid, ///< The border is a single line segment. BorderDouble, ///< The border is two solid lines. The sum of the two lines and the space between them equals the value of 'border-width'. BorderGroove, ///< The border looks as though it were carved into the canvas. (old words type) BorderRidge, ///< The opposite of 'groove': the border looks as though it were coming out of the canvas. (old words type) BorderInset, ///< The border makes the entire box look as though it were embedded in the canvas. (old words type) BorderOutset, ///< The opposite of 'inset': the border makes the entire box look as though it were coming out of the canvas. (old words type) BorderDashedLong, ///< Dashed single border with long spaces BorderTriple, ///< Triple lined border BorderSlash, ///< slash border BorderWave, ///< wave border BorderDoubleWave, ///< double wave border // words legacy BorderDashDot, BorderDashDotDot }; /// Holds data about one border line. struct KRITAODF_EXPORT BorderData { BorderData(); /// Compare the border data with another one bool operator==(const BorderData &other) const; BorderStyle style; ///< The border style. (see KoBorder::BorderStyle) QPen outerPen; ///< Holds the outer line when borderstyle is double and the whole line otherwise QPen innerPen; ///< Holds the inner line when borderstyle is double qreal spacing; ///< Holds the spacing between the outer and inner lines. }; /// Constructor KoBorder(); KoBorder(const KoBorder &kb); /// Destructor ~KoBorder(); /// Assignment KoBorder &operator=(const KoBorder &other); /// Compare the border with another one bool operator==(const KoBorder &other) const; bool operator!=(const KoBorder &other) const { return !operator==(other); } void setBorderStyle(BorderSide side, BorderStyle style); BorderStyle borderStyle(BorderSide side) const; void setBorderColor(BorderSide side, const QColor &color); QColor borderColor(BorderSide side) const; void setBorderWidth(BorderSide side, qreal width); qreal borderWidth(BorderSide side) const; void setOuterBorderWidth(BorderSide side, qreal width); qreal outerBorderWidth(BorderSide side) const; void setInnerBorderWidth(BorderSide side, qreal width); qreal innerBorderWidth(BorderSide side) const; void setBorderSpacing(BorderSide side, qreal width); qreal borderSpacing(BorderSide side) const; BorderData borderData(BorderSide side) const; void setBorderData(BorderSide side, const BorderData &data); bool hasBorder() const; bool hasBorder(BorderSide side) const; enum BorderPaintArea { PaintOnLine, PaintInsideLine }; void paint(QPainter &painter, const QRectF &borderRect, BorderPaintArea whereToPaint = PaintInsideLine) const; - /** - * Load the style from the element - * - * @param style the element containing the style to read from - * @return true when border attributes were found - */ - bool loadOdf(const KoXmlElement &style); - bool loadOdf(const KoStyleStack &styleStack); - void saveOdf(KoGenStyle &style, KoGenStyle::PropertyType type = KoGenStyle::DefaultType) const; - // Some public functions used in other places where borders are handled. // Example: KoParagraphStyle // FIXME: These places should be made to use KoBorder instead. static BorderStyle odfBorderStyle(const QString &borderstyle, bool *converted = 0); static QString odfBorderStyleString(BorderStyle borderstyle); static QString msoBorderStyleString(BorderStyle borderstyle); private: void paintBorderSide(QPainter &painter, QPointF lineStart, QPointF lineEnd, BorderData *borderData, bool isVertical, BorderData *neighbour1, BorderData *neighbor2, int inwardsAcross) const; - void parseAndSetBorder(const QString &border, - bool hasSpecialBorder, const QString &specialBorderString); - void parseAndSetBorder(const BorderSide borderSide, const QString &border, - bool hasSpecialBorder, const QString &specialBorderString); - private: QSharedDataPointer d; }; Q_DECLARE_METATYPE(KoBorder) #endif diff --git a/libs/odf/KoElementReference.cpp b/libs/odf/KoElementReference.cpp index d8b3982407..d58c2bbfd8 100644 --- a/libs/odf/KoElementReference.cpp +++ b/libs/odf/KoElementReference.cpp @@ -1,114 +1,81 @@ /* * Copyright (c) 2011 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 "KoElementReference.h" #include "KoXmlReader.h" #include "KoXmlWriter.h" #include KoElementReference::KoElementReference() : d(new KoElementReferenceData()) { d->xmlid = "id-" + d->xmlid; } KoElementReference::KoElementReference(const QString &prefix) : d(new KoElementReferenceData) { d->xmlid = prefix + "-" + d->xmlid; } KoElementReference::KoElementReference(const QString &prefix, int counter) : d(new KoElementReferenceData) { d->xmlid = QString("%1-%2").arg(prefix).arg(counter); } KoElementReference::KoElementReference(const KoElementReference &other) : d(other.d) { } KoElementReference &KoElementReference::operator=(const KoElementReference &rhs) { if (this == &rhs) return *this; d = rhs.d; return *this; } bool KoElementReference::operator==(const KoElementReference &other) const { return d->xmlid == other.d->xmlid; } bool KoElementReference::operator!=(const KoElementReference &other) const { return !(*this == other); } bool KoElementReference::isValid() const { return (!d->xmlid.isEmpty()); } -void KoElementReference::saveOdf(KoXmlWriter *writer, SaveOption saveOptions) const -{ - if (d->xmlid.isEmpty()) return; - - writer->addAttribute("xml:id", d->xmlid); - - if (saveOptions & DrawId) { - writer->addAttribute("draw:id", d->xmlid); - } - if (saveOptions & TextId) { - writer->addAttribute("text:id", d->xmlid); - } -} - QString KoElementReference::toString() const { return d->xmlid; } -KoElementReference KoElementReference::loadOdf(const KoXmlElement &element) -{ - QString xmlid; - - if (element.hasAttributeNS(KoXmlNS::xml, "id")) { - xmlid = element.attributeNS(KoXmlNS::xml, "id"); - } - else if (element.hasAttributeNS(KoXmlNS::draw, "id")) { - xmlid = element.attributeNS(KoXmlNS::draw, "id"); - } - else if (element.hasAttributeNS(KoXmlNS::text, "id")) { - xmlid = element.attributeNS(KoXmlNS::text, "id"); - } - - d->xmlid = xmlid; - - return *this; -} - void KoElementReference::invalidate() { d->xmlid.clear(); } diff --git a/libs/odf/KoElementReference.h b/libs/odf/KoElementReference.h index 750c3406cf..40b8584a4b 100644 --- a/libs/odf/KoElementReference.h +++ b/libs/odf/KoElementReference.h @@ -1,131 +1,114 @@ /* * Copyright (c) 2011-2012 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 KOELEMENTREFERENCE_H #define KOELEMENTREFERENCE_H #include #include #include #include "KoXmlReaderForward.h" #include "kritaodf_export.h" class KoXmlWriter; class KoElementReferenceData : public QSharedData { public: KoElementReferenceData() { xmlid = QUuid::createUuid().toString(); xmlid.remove('{'); xmlid.remove('}'); } KoElementReferenceData(const KoElementReferenceData &other) : QSharedData(other) , xmlid(other.xmlid) { } ~KoElementReferenceData() {} QString xmlid; }; /** * KoElementReference is used to store unique identifiers for elements in an odf document. * Element references are saved as xml:id and optionally for compatibility also as draw:id * and text:id. * * You can use element references wherever you would have used a QString to refer to the id * of an object. * * Element references are implicitly shared, so you can and should pass them along by value. */ class KRITAODF_EXPORT KoElementReference { public: enum GenerationOption { UUID = 0, Counter = 1 }; enum SaveOption { XmlId = 0x0, DrawId = 0x1, TextId = 0x2 }; Q_DECLARE_FLAGS(SaveOptions, SaveOption) KoElementReference(); explicit KoElementReference(const QString &prefix); KoElementReference(const QString &prefix, int counter); KoElementReference(const KoElementReference &other); KoElementReference &operator=(const KoElementReference &rhs); bool operator==(const KoElementReference &other) const; bool operator!=(const KoElementReference &other) const; /** * @return true if the xmlid is valid, i.e., not null */ bool isValid() const; - /** - * @brief loadOdf creates a new KoElementReference from the given element. If the element - * does not have an xml:id, draw:id or text:id attribute, and invalid element reference - * is returned. - * @param element the element that may contain xml:id, text:id or draw:id. xml:id has - * priority. - * @return a new element reference - */ - KoElementReference loadOdf(const KoXmlElement &element); - - /** - * @brief saveOdf saves this element reference into the currently open element in the xml writer. - * @param writer the writer we save to - * @param saveOption determines which attributes we save. We always save the xml:id. - */ - void saveOdf(KoXmlWriter *writer, SaveOption saveOption = XmlId) const; - /** * @brief toString creates a QString from the element reference * @return a string that represents the element. Can be used in maps etc. */ QString toString() const; /** * Invalidate the reference */ void invalidate(); private: QSharedDataPointer d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KoElementReference::SaveOptions) #endif // KOELEMENTREFERENCE_H diff --git a/libs/odf/KoEmbeddedDocumentSaver.cpp b/libs/odf/KoEmbeddedDocumentSaver.cpp index 3a37beb332..a82890fbd8 100644 --- a/libs/odf/KoEmbeddedDocumentSaver.cpp +++ b/libs/odf/KoEmbeddedDocumentSaver.cpp @@ -1,141 +1,89 @@ /* This file is part of the KDE project Copyright (C) 2004-2006 David Faure Copyright (C) 2007 Thorsten Zachmann Copyright (C) 2010 Thomas Zander Copyright (C) 2011 Inge Wallin 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 "KoEmbeddedDocumentSaver.h" #include #include #include #include #include #include #include #define INTERNAL_PROTOCOL "intern" struct FileEntry { QString path; QByteArray mimeType; // QBA because this is what addManifestEntry wants QByteArray contents; }; class Q_DECL_HIDDEN KoEmbeddedDocumentSaver::Private { public: Private() {} QHash prefixes; // Used in getFilename(); // These will be saved when saveEmbeddedDocuments() is called. QList files; // Embedded files. QList manifestEntries; }; KoEmbeddedDocumentSaver::KoEmbeddedDocumentSaver() : d(new Private()) { } KoEmbeddedDocumentSaver::~KoEmbeddedDocumentSaver() { qDeleteAll(d->files); qDeleteAll(d->manifestEntries); delete d; } QString KoEmbeddedDocumentSaver::getFilename(const QString &prefix) { int index = 1; if (d->prefixes.contains(prefix)) { index = d->prefixes.value(prefix); } // This inserts prefix into the map if it's not there. d->prefixes[prefix] = index + 1; //return prefix + QString("%1").arg(index, 4, 10, QChar('0')); return prefix + QString("%1").arg(index); } - -// Examples: -// Videos/Video1.mov ← the number is autogenerated -// Videos/Video2.mov -// Object1/foo ← the number is autogenerated -// Object1/bar - -// Note: The contents QByteArray is implicitly shared. It needs to be -// copied since otherwise the actual array may disappear before -// the real saving is done. -// -void KoEmbeddedDocumentSaver::embedFile(KoXmlWriter &writer, const char *element, - const QString &path, const QByteArray &mimeType, - const QByteArray &contents) -{ - // Put the file in the list of files to be written to the store later. - FileEntry *entry = new FileEntry; - entry->mimeType = mimeType; - entry->path = path; - entry->contents = contents; - d->files.append(entry); - - writer.startElement(element); - // Write the attributes that refer to the file. - - // - writer.addAttribute("xlink:type", "simple"); - writer.addAttribute("xlink:show", "embed"); - writer.addAttribute("xlink:actuate", "onLoad"); - - debugOdf << "saving reference to embedded file as" << path; - writer.addAttribute("xlink:href", path); - writer.endElement(); -} - -void KoEmbeddedDocumentSaver::saveFile(const QString &path, const QByteArray &mimeType, - const QByteArray &contents) -{ - // Put the file in the list of files to be written to the store later. - FileEntry *entry = new FileEntry; - entry->mimeType = mimeType; - entry->path = path; - entry->contents = contents; - d->files.append(entry); - - debugOdf << "saving reference to embedded file as" << path; -} - -/** - * - */ void KoEmbeddedDocumentSaver::saveManifestEntry(const QString &fullPath, const QString &mediaType, const QString &version) { d->manifestEntries.append(new KoOdfManifestEntry(fullPath, mediaType, version)); } diff --git a/libs/odf/KoEmbeddedDocumentSaver.h b/libs/odf/KoEmbeddedDocumentSaver.h index 121b607f78..a19dedc983 100644 --- a/libs/odf/KoEmbeddedDocumentSaver.h +++ b/libs/odf/KoEmbeddedDocumentSaver.h @@ -1,86 +1,61 @@ /* This file is part of the KDE project Copyright (C) 2007 Thorsten Zachmann Copyright (C) 2011 Inge Wallin 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 KOEMBEDDEDDOCUMENTSAVER_H #define KOEMBEDDEDDOCUMENTSAVER_H #include "kritaodf_export.h" #include class KoXmlWriter; /** * This class is used to save embedded objects in ODF documents. * * @see KoEmbeddedFileSaver */ class KRITAODF_EXPORT KoEmbeddedDocumentSaver { public: KoEmbeddedDocumentSaver(); ~KoEmbeddedDocumentSaver(); /** * Get a unique file name with the given prefix, to be used as a name for an embedded file in the ODF store. * @param the prefix of the filename to be created. * return a unique file name for use in the odf store. */ QString getFilename(const QString &prefix); - /** - * Adds the object specific attributes to the tag, and queues the - * file for saving into the store. - * - * However, it does NOT write the content of the embedded document - * to the store. Saving of the embedded files themselves is done - * in @ref saveEmbeddedFiles. This function should be called from - * within saveOdf in a shape or a document. - */ - void embedFile(KoXmlWriter &writer, const char *element, - const QString &path, const QByteArray &mimeType, - const QByteArray &contents); - - /** - * Queues the file for saving into the store. - * - * Saving of the embedded files themselves is done in @ref - * saveEmbeddedFiles. This function should be called from within - * saveOdf in a shape or a document if you don't wish to have a - * reference to the file within content.xml, e.g. when the file is - * part of an embedded object with embedded files within it. - */ - void saveFile(const QString &path, const QByteArray &mimeType, - const QByteArray &contents); - /** * */ void saveManifestEntry(const QString &fullPath, const QString &mediaType, const QString &version = QString()); private: class Private; Private * const d; Q_DISABLE_COPY(KoEmbeddedDocumentSaver) }; #endif /* KOEMBEDDEDDOCUMENTSAVER_H */ diff --git a/libs/odf/KoFontFace.cpp b/libs/odf/KoFontFace.cpp index c71bfe5816..8d258ea29d 100644 --- a/libs/odf/KoFontFace.cpp +++ b/libs/odf/KoFontFace.cpp @@ -1,153 +1,131 @@ /* This file is part of the KDE project Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). Contact: Suresh Chande suresh.chande@nokia.com 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 "KoFontFace.h" #include #include class KoFontFacePrivate : public QSharedData { public: KoFontFacePrivate(const QString &_name) : name(_name), pitch(KoFontFace::VariablePitch) { } ~KoFontFacePrivate() { } - void saveOdf(KoXmlWriter* xmlWriter) const - { - xmlWriter->startElement("style:font-face"); - xmlWriter->addAttribute("style:name", name); - xmlWriter->addAttribute("svg:font-family", family.isEmpty() ? name : family); - if (!familyGeneric.isEmpty()) - xmlWriter->addAttribute("style:font-family-generic", familyGeneric); - if (!style.isEmpty()) - xmlWriter->addAttribute("svg:font-style", style); - xmlWriter->addAttribute("style:font-pitch", pitch == KoFontFace::FixedPitch ? "fixed" : "variable"); - xmlWriter->endElement(); // style:font-face - } - QString name; //!< for style:name attribute QString family; //!< for svg:font-family attribute QString familyGeneric; //!< for style:font-family-generic attribute QString style; //!< for svg:font-style attribute KoFontFace::Pitch pitch; //!< for style:font-pitch attribute }; KoFontFace::KoFontFace(const QString &_name) : d(new KoFontFacePrivate(_name)) { } KoFontFace::KoFontFace(const KoFontFace &other) : d(other.d) { } KoFontFace::~KoFontFace() { } KoFontFace &KoFontFace::operator=(const KoFontFace &other) { d = other.d; return *this; } bool KoFontFace::operator==(const KoFontFace &other) const { if (isNull() && other.isNull()) return true; return d.data() == other.d.data(); } bool KoFontFace::isNull() const { return d->name.isEmpty(); } QString KoFontFace::name() const { return d->name; } void KoFontFace::setName(const QString &name) { d->name = name; } QString KoFontFace::family() const { return d->family; } void KoFontFace::setFamily(const QString &family) { d->family = family; } QString KoFontFace::familyGeneric() const { return d->familyGeneric; } void KoFontFace::setFamilyGeneric(const QString &familyGeneric) { if (familyGeneric == "decorative" || familyGeneric == "modern" || familyGeneric == "roman" || familyGeneric == "script" || familyGeneric == "swiss" || familyGeneric == "system") { d->familyGeneric = familyGeneric; } } QString KoFontFace::style() const { return d->style; } void KoFontFace::setStyle(const QString &style) { d->style = style; } KoFontFace::Pitch KoFontFace::pitch() const { return d->pitch; } void KoFontFace::setPitch(KoFontFace::Pitch pitch) { d->pitch = pitch; } -void KoFontFace::saveOdf(KoXmlWriter* xmlWriter) const -{ - Q_ASSERT(!isNull()); - if (isNull()) { - warnOdf << "This font face is null and will not be saved: set at least the name"; - return; - } - d->saveOdf(xmlWriter); -} diff --git a/libs/odf/KoFontFace.h b/libs/odf/KoFontFace.h index 2fea70f100..ef365a116d 100644 --- a/libs/odf/KoFontFace.h +++ b/libs/odf/KoFontFace.h @@ -1,95 +1,91 @@ /* This file is part of the KDE project Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). Contact: Suresh Chande suresh.chande@nokia.com 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 KOFONTFACE_H #define KOFONTFACE_H #include #include #include "kritaodf_export.h" class KoXmlWriter; class KoFontFacePrivate; /** * @brief Represents font style. * Font style is defined by the style:font-face element. * @todo add more parameters. * @todo add class KoFontFaceDeclarations instead of adding methods to KoGenStyle? */ class KRITAODF_EXPORT KoFontFace { public: /** * Constructor. Creates font face definition with empty parameters. * * @param name the font name. * * The other are empty. If you don't pass the name, the font face will be considered null. * @see isEmpty() */ explicit KoFontFace(const QString &name = QString()); /** * Copy constructor. */ KoFontFace(const KoFontFace &other); /** * Destructor. */ ~KoFontFace(); /** * @return true if the font face object is null, i.e. has no name assigned. */ bool isNull() const; KoFontFace& operator=(const KoFontFace &other); bool operator==(const KoFontFace &other) const; enum Pitch { FixedPitch, VariablePitch }; //! @todo add enum FamilyGeneric? QString name() const; void setName(const QString &name); QString family() const; void setFamily(const QString &family); QString familyGeneric() const; void setFamilyGeneric(const QString &familyGeneric); QString style() const; void setStyle(const QString &style); KoFontFace::Pitch pitch() const; void setPitch(KoFontFace::Pitch pitch); - /** Saves font face definition into @a xmlWriter as a style:font-face element. - */ - void saveOdf(KoXmlWriter *xmlWriter) const; - private: QSharedDataPointer d; }; #endif /* KOFONTFACE_H */ diff --git a/libs/odf/KoGenStyles.cpp b/libs/odf/KoGenStyles.cpp index f3f5b20009..c2de88a7a0 100644 --- a/libs/odf/KoGenStyles.cpp +++ b/libs/odf/KoGenStyles.cpp @@ -1,546 +1,545 @@ /* This file is part of the KDE project Copyright (C) 2004-2006 David Faure Copyright (C) 2007-2008 Thorsten Zachmann Copyright (C) 2009 Thomas Zander Copyright (C) 2008 Girish Ramakrishnan Copyright (C) 2009 Inge Wallin Copyright (C) 2010 KO GmbH Copyright (C) 2010 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "KoGenStyles.h" #include #include #include #include "KoOdfWriteStore.h" #include "KoFontFace.h" #include #include static const struct { KoGenStyle::Type m_type; const char * m_elementName; const char * m_propertiesElementName; bool m_drawElement; } styleData[] = { { KoGenStyle::TextStyle, "style:style", "style:text-properties", false }, { KoGenStyle::ParagraphStyle, "style:style", "style:paragraph-properties", false }, { KoGenStyle::SectionStyle, "style:style", "style:section-properties", false }, { KoGenStyle::RubyStyle, "style:style", "style:ruby-properties", false }, { KoGenStyle::TableStyle, "style:style", "style:table-properties", false }, { KoGenStyle::TableColumnStyle, "style:style", "style:table-column-properties", false }, { KoGenStyle::TableRowStyle, "style:style", "style:table-row-properties", false }, { KoGenStyle::TableCellStyle, "style:style", "style:table-cell-properties", false }, { KoGenStyle::GraphicStyle, "style:style", "style:graphic-properties", false }, { KoGenStyle::PresentationStyle, "style:style", "style:graphic-properties", false }, { KoGenStyle::DrawingPageStyle, "style:style", "style:drawing-page-properties", false }, { KoGenStyle::ChartStyle, "style:style", "style:chart-properties", false }, { KoGenStyle::ListStyle, "text:list-style", 0, false }, { KoGenStyle::LinearGradientStyle, "svg:linearGradient", 0, true }, { KoGenStyle::RadialGradientStyle, "svg:radialGradient", 0, true }, { KoGenStyle::ConicalGradientStyle, "calligra:conicalGradient", 0, true }, { KoGenStyle::StrokeDashStyle, "draw:stroke-dash", 0, true }, { KoGenStyle::FillImageStyle, "draw:fill-image", 0, true }, { KoGenStyle::HatchStyle, "draw:hatch", "style:graphic-properties", true }, { KoGenStyle::GradientStyle, "draw:gradient", "style:graphic-properties", true }, { KoGenStyle::MarkerStyle, "draw:marker", "style:graphic-properties", true }, { KoGenStyle::PresentationPageLayoutStyle, "style:presentation-page-layout", 0, false }, { KoGenStyle::OutlineLevelStyle, "text:outline-style", 0, false } }; static const unsigned int numStyleData = sizeof(styleData) / sizeof(*styleData); static const struct { KoGenStyle::Type m_type; const char * m_elementName; const char * m_propertiesElementName; bool m_drawElement; } autoStyleData[] = { { KoGenStyle::TextAutoStyle, "style:style", "style:text-properties", false }, { KoGenStyle::ParagraphAutoStyle, "style:style", "style:paragraph-properties", false }, { KoGenStyle::SectionAutoStyle, "style:style", "style:section-properties", false }, { KoGenStyle::RubyAutoStyle, "style:style", "style:ruby-properties", false }, { KoGenStyle::TableAutoStyle, "style:style", "style:table-properties", false }, { KoGenStyle::TableColumnAutoStyle, "style:style", "style:table-column-properties", false }, { KoGenStyle::TableRowAutoStyle, "style:style", "style:table-row-properties", false }, { KoGenStyle::TableCellAutoStyle, "style:style", "style:table-cell-properties", false }, { KoGenStyle::GraphicAutoStyle, "style:style", "style:graphic-properties", false }, { KoGenStyle::PresentationAutoStyle, "style:style", "style:graphic-properties", false }, { KoGenStyle::DrawingPageAutoStyle, "style:style", "style:drawing-page-properties", false }, { KoGenStyle::ChartAutoStyle, "style:style", "style:chart-properties", false }, { KoGenStyle::PageLayoutStyle, "style:page-layout", "style:page-layout-properties", false }, { KoGenStyle::ListAutoStyle, "text:list-style", 0, false }, { KoGenStyle::NumericNumberStyle, "number:number-style", 0, false }, { KoGenStyle::NumericFractionStyle, "number:number-style", 0, false }, { KoGenStyle::NumericScientificStyle, "number:number-style", 0, false }, { KoGenStyle::NumericDateStyle, "number:date-style", 0, false }, { KoGenStyle::NumericTimeStyle, "number:time-style", 0, false }, { KoGenStyle::NumericPercentageStyle, "number:percentage-style", 0, false }, { KoGenStyle::NumericCurrencyStyle, "number:currency-style", 0, false }, { KoGenStyle::NumericBooleanStyle, "number:boolean-style", 0, false }, { KoGenStyle::NumericTextStyle, "number:text-style", 0, false } }; static const unsigned int numAutoStyleData = sizeof(autoStyleData) / sizeof(*autoStyleData); static void insertRawOdfStyles(const QByteArray& xml, QByteArray& styles) { if (xml.isEmpty()) return; if (!styles.isEmpty() && !styles.endsWith('\n') && !xml.startsWith('\n')) { styles.append('\n'); } styles.append(xml); } class Q_DECL_HIDDEN KoGenStyles::Private { public: Private(KoGenStyles *q) : q(q) { } ~Private() { } QList styles(bool autoStylesInStylesDotXml, KoGenStyle::Type type) const; void saveOdfAutomaticStyles(KoXmlWriter* xmlWriter, bool autoStylesInStylesDotXml, const QByteArray& rawOdfAutomaticStyles) const; void saveOdfDocumentStyles(KoXmlWriter* xmlWriter) const; void saveOdfMasterStyles(KoXmlWriter* xmlWriter) const; QString makeUniqueName(const QString& base, const QByteArray &family, InsertionFlags flags) const; /** * Save font face declarations * * This creates the office:font-face-decls tag containing all font face * declarations */ void saveOdfFontFaceDecls(KoXmlWriter* xmlWriter) const; /// style definition -> name StyleMap styleMap; /// Map with the style name as key. /// This map is mainly used to check for name uniqueness QMap > styleNames; QMap > autoStylesInStylesDotXml; /// List of styles (used to preserve ordering) QList styleList; /// map for saving default styles QMap defaultStyles; /// font faces QMap fontFaces; StyleMap::iterator insertStyle(const KoGenStyle &style, const QString &name, InsertionFlags flags); struct RelationTarget { QString target; // the style we point to QString attribute; // the attribute name used for the relation }; QHash relations; // key is the name of the source style QByteArray rawOdfDocumentStyles; QByteArray rawOdfAutomaticStyles_stylesDotXml; QByteArray rawOdfAutomaticStyles_contentDotXml; QByteArray rawOdfMasterStyles; QByteArray rawOdfFontFaceDecls; KoGenStyles *q; }; QList KoGenStyles::Private::styles(bool autoStylesInStylesDotXml, KoGenStyle::Type type) const { QList lst; QList::const_iterator it = styleList.constBegin(); const QList::const_iterator end = styleList.constEnd(); for (; it != end ; ++it) { if ((*it).style->type() == type && (*it).style->autoStyleInStylesDotXml() == autoStylesInStylesDotXml) { lst.append(*it); } } return lst; } void KoGenStyles::Private::saveOdfAutomaticStyles(KoXmlWriter* xmlWriter, bool autoStylesInStylesDotXml, const QByteArray& rawOdfAutomaticStyles) const { xmlWriter->startElement("office:automatic-styles"); for (uint i = 0; i < numAutoStyleData; ++i) { QList stylesList = styles(autoStylesInStylesDotXml, autoStyleData[i].m_type); QList::const_iterator it = stylesList.constBegin(); for (; it != stylesList.constEnd() ; ++it) { (*it).style->writeStyle(xmlWriter, *q, autoStyleData[i].m_elementName, (*it).name, autoStyleData[i].m_propertiesElementName, true, autoStyleData[i].m_drawElement); } } if (!rawOdfAutomaticStyles.isEmpty()) { xmlWriter->addCompleteElement(rawOdfAutomaticStyles.constData()); } xmlWriter->endElement(); // office:automatic-styles } void KoGenStyles::Private::saveOdfDocumentStyles(KoXmlWriter* xmlWriter) const { xmlWriter->startElement("office:styles"); for (uint i = 0; i < numStyleData; ++i) { const QMap::const_iterator it(defaultStyles.constFind(styleData[i].m_type)); if (it != defaultStyles.constEnd()) { it.value().writeStyle(xmlWriter, *q, "style:default-style", "", styleData[i].m_propertiesElementName, true, styleData[i].m_drawElement); } } for (uint i = 0; i < numStyleData; ++i) { QList stylesList(styles(false, styleData[i].m_type)); QList::const_iterator it = stylesList.constBegin(); for (; it != stylesList.constEnd() ; ++it) { if (relations.contains(it->name)) { KoGenStyles::Private::RelationTarget relation = relations.value(it->name); KoGenStyle styleCopy = *(*it).style; styleCopy.addAttribute(relation.attribute, relation.target); styleCopy.writeStyle(xmlWriter, *q, styleData[i].m_elementName, (*it).name, styleData[i].m_propertiesElementName, true, styleData[i].m_drawElement); } else { (*it).style->writeStyle(xmlWriter, *q, styleData[i].m_elementName, (*it).name, styleData[i].m_propertiesElementName, true, styleData[i].m_drawElement); } } } if (!rawOdfDocumentStyles.isEmpty()) { xmlWriter->addCompleteElement(rawOdfDocumentStyles.constData()); } xmlWriter->endElement(); // office:styles } void KoGenStyles::Private::saveOdfMasterStyles(KoXmlWriter* xmlWriter) const { xmlWriter->startElement("office:master-styles"); QList stylesList = styles(false, KoGenStyle::MasterPageStyle); QList::const_iterator it = stylesList.constBegin(); for (; it != stylesList.constEnd() ; ++it) { (*it).style->writeStyle(xmlWriter, *q, "style:master-page", (*it).name, 0); } if (!rawOdfMasterStyles.isEmpty()) { xmlWriter->addCompleteElement(rawOdfMasterStyles.constData()); } xmlWriter->endElement(); // office:master-styles } void KoGenStyles::Private::saveOdfFontFaceDecls(KoXmlWriter* xmlWriter) const { if (fontFaces.isEmpty()) return; xmlWriter->startElement("office:font-face-decls"); for (QMap::ConstIterator it(fontFaces.constBegin()); it != fontFaces.constEnd(); ++it) { - it.value().saveOdf(xmlWriter); } if (!rawOdfFontFaceDecls.isEmpty()) { xmlWriter->addCompleteElement(rawOdfFontFaceDecls.constData()); } xmlWriter->endElement(); // office:font-face-decls } QString KoGenStyles::Private::makeUniqueName(const QString& base, const QByteArray &family, InsertionFlags flags) const { // If this name is not used yet, and numbering isn't forced, then the given name is ok. if ((flags & DontAddNumberToName) && !autoStylesInStylesDotXml[family].contains(base) && !styleNames[family].contains(base)) return base; int num = 1; QString name; do { name = base + QString::number(num++); } while (autoStylesInStylesDotXml[family].contains(name) || styleNames[family].contains(name)); return name; } //------------------------ KoGenStyles::KoGenStyles() : d(new Private(this)) { } KoGenStyles::~KoGenStyles() { delete d; } QString KoGenStyles::insert(const KoGenStyle& style, const QString& baseName, InsertionFlags flags) { // if it is a default style it has to be saved differently if (style.isDefaultStyle()) { // we can have only one default style per type Q_ASSERT(!d->defaultStyles.contains(style.type())); // default style is only possible for style:style in office:style types Q_ASSERT(style.type() == KoGenStyle::TextStyle || style.type() == KoGenStyle::ParagraphStyle || style.type() == KoGenStyle::SectionStyle || style.type() == KoGenStyle::RubyStyle || style.type() == KoGenStyle::TableStyle || style.type() == KoGenStyle::TableColumnStyle || style.type() == KoGenStyle::TableRowStyle || style.type() == KoGenStyle::TableCellStyle || style.type() == KoGenStyle::GraphicStyle || style.type() == KoGenStyle::PresentationStyle || style.type() == KoGenStyle::DrawingPageStyle || style.type() == KoGenStyle::ChartStyle); d->defaultStyles.insert(style.type(), style); // default styles don't have a name return QString(); } if (flags & AllowDuplicates) { StyleMap::iterator it = d->insertStyle(style, baseName, flags); return it.value(); } StyleMap::iterator it = d->styleMap.find(style); if (it == d->styleMap.end()) { // Not found, try if this style is in fact equal to its parent (the find above // wouldn't have found it, due to m_parentName being set). if (!style.parentName().isEmpty()) { KoGenStyle testStyle(style); const KoGenStyle* parentStyle = this->style(style.parentName(), style.familyName()); // ## linear search if (!parentStyle) { debugOdf << "baseName=" << baseName << "parent style" << style.parentName() << "not found in collection"; } else { // TODO remove if (testStyle.m_familyName != parentStyle->m_familyName) { warnOdf << "baseName=" << baseName << "family=" << testStyle.m_familyName << "parent style" << style.parentName() << "has a different family:" << parentStyle->m_familyName; } testStyle.m_parentName = parentStyle->m_parentName; // Exclude the type from the comparison. It's ok for an auto style // to have a user style as parent; they can still be identical testStyle.m_type = parentStyle->m_type; // Also it's ok to not have the display name of the parent style // in the auto style QMap::const_iterator it = parentStyle->m_attributes.find("style:display-name"); if (it != parentStyle->m_attributes.end()) testStyle.addAttribute("style:display-name", *it); if (*parentStyle == testStyle) return style.parentName(); } } it = d->insertStyle(style, baseName, flags); } return it.value(); } KoGenStyles::StyleMap::iterator KoGenStyles::Private::insertStyle(const KoGenStyle &style, const QString& baseName, InsertionFlags flags) { QString styleName(baseName); if (styleName.isEmpty()) { switch (style.type()) { case KoGenStyle::ParagraphAutoStyle: styleName = 'P'; break; case KoGenStyle::ListAutoStyle: styleName = 'L'; break; case KoGenStyle::TextAutoStyle: styleName = 'T'; break; default: styleName = 'A'; // for "auto". } flags &= ~DontAddNumberToName; // i.e. force numbering } styleName = makeUniqueName(styleName, style.m_familyName, flags); if (style.autoStyleInStylesDotXml()) autoStylesInStylesDotXml[style.m_familyName].insert(styleName); else styleNames[style.m_familyName].insert(styleName); KoGenStyles::StyleMap::iterator it = styleMap.insert(style, styleName); NamedStyle s; s.style = &it.key(); s.name = styleName; styleList.append(s); return it; } KoGenStyles::StyleMap KoGenStyles::styles() const { return d->styleMap; } QList KoGenStyles::styles(KoGenStyle::Type type) const { return d->styles(false, type); } const KoGenStyle* KoGenStyles::style(const QString &name, const QByteArray &family) const { QList::const_iterator it = d->styleList.constBegin(); const QList::const_iterator end = d->styleList.constEnd(); for (; it != end ; ++it) { if ((*it).name == name && (*it).style->familyName() == family) { return (*it).style; } } return 0; } KoGenStyle* KoGenStyles::styleForModification(const QString &name, const QByteArray &family) { return const_cast(style(name, family)); } void KoGenStyles::markStyleForStylesXml(const QString &name, const QByteArray &family) { Q_ASSERT(d->styleNames[family].contains(name)); d->styleNames[family].remove(name); d->autoStylesInStylesDotXml[family].insert(name); styleForModification(name, family)->setAutoStyleInStylesDotXml(true); } void KoGenStyles::insertFontFace(const KoFontFace &face) { Q_ASSERT(!face.isNull()); if (face.isNull()) { warnOdf << "This font face is null and will not be added to styles: set at least the name"; return; } d->fontFaces.insert(face.name(), face); // replaces prev item } KoFontFace KoGenStyles::fontFace(const QString& name) const { return d->fontFaces.value(name); } bool KoGenStyles::saveOdfStylesDotXml(KoStore* store, KoXmlWriter* manifestWriter) const { if (!store->open("styles.xml")) return false; manifestWriter->addManifestEntry("styles.xml", "text/xml"); KoStoreDevice stylesDev(store); KoXmlWriter* stylesWriter = KoOdfWriteStore::createOasisXmlWriter(&stylesDev, "office:document-styles"); d->saveOdfFontFaceDecls(stylesWriter); d->saveOdfDocumentStyles(stylesWriter); d->saveOdfAutomaticStyles(stylesWriter, true, d->rawOdfAutomaticStyles_stylesDotXml); d->saveOdfMasterStyles(stylesWriter); stylesWriter->endElement(); // root element (office:document-styles) stylesWriter->endDocument(); delete stylesWriter; if (!store->close()) // done with styles.xml return false; return true; } void KoGenStyles::saveOdfStyles(StylesPlacement placement, KoXmlWriter* xmlWriter) const { switch (placement) { case DocumentStyles: d->saveOdfDocumentStyles(xmlWriter); break; case MasterStyles: d->saveOdfMasterStyles(xmlWriter); break; case DocumentAutomaticStyles: d->saveOdfAutomaticStyles(xmlWriter, false, d->rawOdfAutomaticStyles_contentDotXml); break; case StylesXmlAutomaticStyles: d->saveOdfAutomaticStyles(xmlWriter, true, d->rawOdfAutomaticStyles_stylesDotXml); break; case FontFaceDecls: d->saveOdfFontFaceDecls(xmlWriter); break; } } void KoGenStyles::insertRawOdfStyles(StylesPlacement placement, const QByteArray& xml) { switch (placement) { case DocumentStyles: ::insertRawOdfStyles(xml, d->rawOdfDocumentStyles); break; case MasterStyles: ::insertRawOdfStyles(xml, d->rawOdfMasterStyles); break; case DocumentAutomaticStyles: ::insertRawOdfStyles(xml, d->rawOdfAutomaticStyles_contentDotXml); break; case StylesXmlAutomaticStyles: ::insertRawOdfStyles(xml, d->rawOdfAutomaticStyles_stylesDotXml); break; case FontFaceDecls: ::insertRawOdfStyles(xml, d->rawOdfFontFaceDecls); break; } } void KoGenStyles::insertStyleRelation(const QString &source, const QString &target, const char *tagName) { KoGenStyles::Private::RelationTarget relation; relation.target = target; relation.attribute = QString(tagName); d->relations.insert(source, relation); } QDebug operator<<(QDebug dbg, const KoGenStyles& styles) { dbg.nospace() << "KoGenStyles:"; QList::const_iterator it = styles.d->styleList.constBegin(); const QList::const_iterator end = styles.d->styleList.constEnd(); for (; it != end ; ++it) { dbg.nospace() << (*it).name; } for (QMap >::const_iterator familyIt(styles.d->styleNames.constBegin()); familyIt != styles.d->styleNames.constEnd(); ++familyIt) { for (QSet::const_iterator it(familyIt.value().constBegin()); it != familyIt.value().constEnd(); ++it) { dbg.space() << "style:" << *it; } } #ifndef NDEBUG for (QMap >::const_iterator familyIt(styles.d->autoStylesInStylesDotXml.constBegin()); familyIt != styles.d->autoStylesInStylesDotXml.constEnd(); ++familyIt) { for (QSet::const_iterator it(familyIt.value().constBegin()); it != familyIt.value().constEnd(); ++it) { dbg.space() << "auto style for style.xml:" << *it; } } #endif return dbg.space(); } diff --git a/libs/odf/KoShadowStyle.cpp b/libs/odf/KoShadowStyle.cpp index 1ee7b81e7c..12c07450b1 100644 --- a/libs/odf/KoShadowStyle.cpp +++ b/libs/odf/KoShadowStyle.cpp @@ -1,164 +1,95 @@ /* This file is part of the KDE project * * Copyright (C) 2011 Pierre Ducroquet * * 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 "KoShadowStyle.h" #include // KoShadowStyle private class class KoShadowStylePrivate: public QSharedData { public: KoShadowStylePrivate(); ~KoShadowStylePrivate(); QVector shadows; }; KoShadowStylePrivate::KoShadowStylePrivate() { } KoShadowStylePrivate::~KoShadowStylePrivate() { } // KoShadowStyle::ShadowData structure KoShadowStyle::ShadowData::ShadowData() : color(), offset(0, 0), radius(0.0) { } bool KoShadowStyle::ShadowData::operator==(const KoShadowStyle::ShadowData &other) const { return (color == other.color) && (offset == other.offset) && (radius == other.radius); } // KoShadowStyle class KoShadowStyle::KoShadowStyle() : d(new KoShadowStylePrivate) { } KoShadowStyle::KoShadowStyle(const KoShadowStyle &other) : d(other.d) { } KoShadowStyle::~KoShadowStyle() { } bool KoShadowStyle::operator==(const KoShadowStyle &other) const { if (d.data() == other.d.data()) return true; if (shadowCount() != other.shadowCount()) return false; foreach (const ShadowData &data, d->shadows) { if (!other.d->shadows.contains(data)) return false; } return true; } bool KoShadowStyle::operator!=(const KoShadowStyle &other) const { return !operator==(other); } -// load value string as specified by CSS2 §7.16.5 "text-shadow" -bool KoShadowStyle::loadOdf (const QString &data) -{ - if (data == QLatin1String("none")) - return true; - - const QStringList sub_shadows = data.split(QLatin1Char(',')); - foreach (const QString &shadow, sub_shadows) { - QStringList words = shadow.split(QLatin1Char(' '), QString::SkipEmptyParts); - if (words.isEmpty()) - return false; - - KoShadowStyle::ShadowData currentData; - - // look for color at begin - QColor shadowColor(words.first()); - if (shadowColor.isValid()) { - currentData.color = shadowColor; - words.removeFirst(); - } else if (words.length() > 2) { - // look for color at end, if there could be one - shadowColor = QColor(words.last()); - if (shadowColor.isValid()) { - currentData.color = shadowColor; - words.removeLast(); - } - } - // We keep an invalid color.if none was found - - // "Each shadow effect must specify a shadow offset and may optionally - // specify a blur radius and a shadow color.", from CSS2 §7.16.5 "text-shadow" - // But for some reason also no offset has been accepted before. TODO: which? - if (! words.isEmpty()) { - if ((words.length() < 2) || (words.length() > 3)) - return false; - - // Parse offset - currentData.offset.setX(KoUnit::parseValue(words.at(0), 0.0)); - currentData.offset.setY(KoUnit::parseValue(words.at(1), 0.0)); - // Parse blur radius if present - if (words.length() == 3) - currentData.radius = KoUnit::parseValue(words.at(2), 0.0); - } - d->shadows << currentData; - } - return true; -} - int KoShadowStyle::shadowCount() const { return d->shadows.size(); } -QString KoShadowStyle::saveOdf() const -{ - if (d->shadows.isEmpty()) - return QLatin1String("none"); - - QStringList parts; - const QString pt = QLatin1String("%1pt"); - foreach (const ShadowData &data, d->shadows) { - QStringList elements; - if (data.color.isValid()) { - elements << data.color.name(); - } - elements << pt.arg(data.offset.x()) << pt.arg(data.offset.y()); - if (data.radius != 0) - elements << pt.arg(data.radius); - - parts << elements.join(QLatin1String(" ")); - } - return parts.join(QLatin1String(",")); -} - diff --git a/libs/odf/KoShadowStyle.h b/libs/odf/KoShadowStyle.h index 8f3b2e5b08..ac9a3a653a 100644 --- a/libs/odf/KoShadowStyle.h +++ b/libs/odf/KoShadowStyle.h @@ -1,95 +1,81 @@ /* This file is part of the KDE project * * Copyright (C) 2011 Pierre Ducroquet * * 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 KOSHADOWSTYLE_H #define KOSHADOWSTYLE_H #include "kritaodf_export.h" #include #include #include #include class KoShadowStylePrivate; /** * A container and parser for shadows as defined in the * OpenDocument specification. * Applies to at least : * - graphic elements, * - headers-footers, * - pages, * - paragraphs, * - tables and table cells. */ class KRITAODF_EXPORT KoShadowStyle { public: /// Default constructor, constructs an empty shadow KoShadowStyle(); /// Copy constructor KoShadowStyle(const KoShadowStyle &other); ~KoShadowStyle(); // Holds data about one of the shadow this shadow contains struct KRITAODF_EXPORT ShadowData { ShadowData(); bool operator==(const ShadowData &other) const; QColor color; QPointF offset; qreal radius; }; bool operator==(const KoShadowStyle &other) const; bool operator!=(const KoShadowStyle &other) const; - /** - * Loads the given OpenDocument-defined shadow - * in this KoShadow object. - * @param shadow the shadow to parse - * @return true when the parsing was successful - */ - bool loadOdf(const QString &shadow); - - /** - * Returns this shadow as a string formatted like an - * OpenDocument-defined shadow. - */ - QString saveOdf() const; - /** * Returns the number of shadows that are contained in this shadow */ int shadowCount() const; private: QSharedDataPointer d; }; Q_DECLARE_TYPEINFO(KoShadowStyle::ShadowData, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(KoShadowStyle) #endif diff --git a/libs/odf/KoTableProperties.h b/libs/odf/KoTableProperties.h index e97e2d5c94..85c44c9405 100644 --- a/libs/odf/KoTableProperties.h +++ b/libs/odf/KoTableProperties.h @@ -1,65 +1,64 @@ /* * Copyright (c) 2010 Carlos Licea * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 KOTABLEPROPERTIES_H #define KOTABLEPROPERTIES_H #include "KoTblStyle.h" class KoTableTemplate; class TableProperties { public: TableStyle* tableStyle(); void setTableStyle(TableStyle* style); TableTemplate* tableTemplate(); void setTableTemplate(KoTableTemplate* tableTemplate); TableTemplateFlags templateFlags(); void setTemplateFlags(TableTemplateFlags templateFlags); bool printable() const; void setPrintable(bool printable); void setPrintRange(CellRange cellRange); CellRange printRange() const; void setName(QString name); QString name() const; void setProtected(bool isProtected); bool isPprotected() const; void setPlainPassword(QString password, QString uri = "http://www.w3.org/2000/09/xmldsig#sha1"); private: - void saveOdf(KoXmlWriter* writer, KoGenStyles* styles); TableStyle* m_style; TableTemplate* m_template; TableTemplateFlags m_templateFlags; bool m_printable; CellRange m_printRange; QString m_name; bool m_protected; QString m_password; }; #endif diff --git a/libs/ui/KisReferenceImage.h b/libs/ui/KisReferenceImage.h index fdc690d34d..06328166ec 100644 --- a/libs/ui/KisReferenceImage.h +++ b/libs/ui/KisReferenceImage.h @@ -1,97 +1,94 @@ /* * Copyright (C) 2017 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KISREFERENCEIMAGE_H #define KISREFERENCEIMAGE_H #include #include #include #include #include class QImage; class QPointF; class QPainter; class QRectF; class KoStore; class KisCoordinatesConverter; class KisCanvas2; /** * @brief The KisReferenceImage class represents a single reference image */ class KRITAUI_EXPORT KisReferenceImage : public KoTosContainer { public: struct KRITAUI_EXPORT SetSaturationCommand : public KUndo2Command { QVector images; QVector oldSaturations; qreal newSaturation; explicit SetSaturationCommand(const QList &images, qreal newSaturation, KUndo2Command *parent = 0); void undo() override; void redo() override; }; KisReferenceImage(); KisReferenceImage(const KisReferenceImage &rhs); ~KisReferenceImage(); KoShape *cloneShape() const override; /** * Load a reference image from specified file. * If parent is provided and the image cannot be loaded, a warning message will be displayed to user. * @return reference image or null if one could not be loaded */ static KisReferenceImage * fromFile(const QString &filename, const KisCoordinatesConverter &converter, QWidget *parent /*= nullptr*/); static KisReferenceImage * fromClipboard(const KisCoordinatesConverter &converter); void setSaturation(qreal saturation); qreal saturation() const; void setEmbed(bool embed); bool embed(); bool hasLocalFile(); void setFilename(const QString &filename); QString filename() const; QString internalFile() const; void paint(QPainter &gc, KoShapePaintingContext &paintcontext) const override; - bool loadOdf(const KoXmlElement &/*element*/, KoShapeLoadingContext &/*context*/) override { return false; } - void saveOdf(KoShapeSavingContext &/*context*/) const override {} - QColor getPixel(QPointF position); void saveXml(QDomDocument &document, QDomElement &parentElement, int id); bool saveImage(KoStore *store) const; static KisReferenceImage * fromXml(const QDomElement &elem); bool loadImage(KoStore *store); private: struct Private; QSharedDataPointer d; }; #endif // KISREFERENCEIMAGE_H diff --git a/libs/ui/flake/kis_node_shape.cpp b/libs/ui/flake/kis_node_shape.cpp index f7c599384a..f654c39854 100644 --- a/libs/ui/flake/kis_node_shape.cpp +++ b/libs/ui/flake/kis_node_shape.cpp @@ -1,152 +1,144 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_shape.h" #include #include #include #include #include #include #include #include #include "kis_shape_layer.h" struct KisNodeShape::Private { public: KisNodeSP node; }; KisNodeShape::KisNodeShape(KisNodeSP node) : KoShapeLayer() , m_d(new Private()) { m_d->node = node; setShapeId(KIS_NODE_SHAPE_ID); setSelectable(false); connect(node, SIGNAL(sigNodeChangedInternal()), SLOT(editabilityChanged())); editabilityChanged(); // Correctly set the lock at loading } KisNodeShape::~KisNodeShape() { if (KoToolManager::instance()) { KoCanvasController *canvasController = KoToolManager::instance()->activeCanvasController(); // If we're the active layer, we should tell the active selection we're dead meat. if (canvasController && canvasController->canvas()) { KoSelection *activeSelection = canvasController->canvas()->selectedShapesProxy()->selection(); KoShapeLayer *activeLayer = activeSelection->activeLayer(); if (activeLayer == this){ activeSelection->setActiveLayer(0); } } } delete m_d; } KisNodeSP KisNodeShape::node() { return m_d->node; } bool KisNodeShape::checkIfDescendant(KoShapeLayer *activeLayer) { bool found(false); KoShapeLayer *layer = activeLayer; while(layer && !(found = layer == this)) { layer = dynamic_cast(layer->parent()); } return found; } void KisNodeShape::editabilityChanged() { if (m_d->node->inherits("KisShapeLayer")) { setGeometryProtected(!m_d->node->isEditable()); } else { setGeometryProtected(false); } Q_FOREACH (KoShape *shape, this->shapes()) { KisNodeShape *node = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER(node) { continue; } if (node) { node->editabilityChanged(); } } /** * Editability of a child depends on the editablity * of its parent. So when we change one's editability, * we need to search for active children and reactivate them */ KoCanvasController *canvasController = KoToolManager::instance()->activeCanvasController(); if(canvasController && canvasController->canvas()) { KoSelection *activeSelection = canvasController->canvas()->selectedShapesProxy()->selection(); KoShapeLayer *activeLayer = activeSelection->activeLayer(); KisShapeLayer *shapeLayer = dynamic_cast(m_d->node.data()); if(activeLayer && (checkIfDescendant(activeLayer) || (shapeLayer && shapeLayer == activeLayer))) { activeSelection->setActiveLayer(activeLayer); } } } QSizeF KisNodeShape::size() const { return boundingRect().size(); } QRectF KisNodeShape::boundingRect() const { return QRectF(); } void KisNodeShape::setPosition(const QPointF &) { } void KisNodeShape::paint(QPainter &, KoShapePaintingContext &) const { } -void KisNodeShape::saveOdf(KoShapeSavingContext &) const -{ -} - -bool KisNodeShape::loadOdf(const KoXmlElement &, KoShapeLoadingContext &) -{ - return false; -} diff --git a/libs/ui/flake/kis_node_shape.h b/libs/ui/flake/kis_node_shape.h index dbe4b3e85d..05e598f57b 100644 --- a/libs/ui/flake/kis_node_shape.h +++ b/libs/ui/flake/kis_node_shape.h @@ -1,64 +1,62 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_NODE_SHAPE_H_ #define KIS_NODE_SHAPE_H_ #include #include #include #include #define KIS_NODE_SHAPE_ID "KisNodeShape" /** * A KisNodeShape is a flake wrapper around Krita nodes. It is used * for dealing with currently active node for tools. */ class KRITAUI_EXPORT KisNodeShape : public QObject, public KoShapeLayer { Q_OBJECT public: KisNodeShape(KisNodeSP node); ~KisNodeShape() override; KisNodeSP node(); // Empty implementations as the node is not painted anywhere QSizeF size() const override; QRectF boundingRect() const override; void setPosition(const QPointF &) override; void paint(QPainter &painter, KoShapePaintingContext &paintcontext) const override; - void saveOdf(KoShapeSavingContext & context) const override; - bool loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) override; private Q_SLOTS: void editabilityChanged(); private: bool checkIfDescendant(KoShapeLayer *activeLayer); private: struct Private; Private * const m_d; }; #endif diff --git a/libs/ui/flake/kis_shape_layer.cc b/libs/ui/flake/kis_shape_layer.cc index 4ce558f5b8..35ebf2f84a 100644 --- a/libs/ui/flake/kis_shape_layer.cc +++ b/libs/ui/flake/kis_shape_layer.cc @@ -1,759 +1,687 @@ /* * Copyright (c) 2006-2008 Boudewijn Rempt * Copyright (c) 2007 Thomas Zander * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2011 Jan Hambrecht * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_layer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include -#include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SvgWriter.h" #include "SvgParser.h" #include #include #include "kis_default_bounds.h" #include #include "kis_shape_layer_canvas.h" #include "kis_image_view_converter.h" #include #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_effect_mask.h" #include "commands/KoShapeReorderCommand.h" #include "kis_do_something_command.h" #include #include #include #include class ShapeLayerContainerModel : public SimpleShapeContainerModel { public: ShapeLayerContainerModel(KisShapeLayer *parent) : q(parent) {} void add(KoShape *child) override { SimpleShapeContainerModel::add(child); /** * The shape is always added with the absolute transformation set appropriately. * Here we should just squeeze it into the layer's transformation. */ KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(); child->applyAbsoluteTransformation(parentTransform.inverted()); } } void remove(KoShape *child) override { KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(); child->applyAbsoluteTransformation(parentTransform); } SimpleShapeContainerModel::remove(child); } void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree) override { q->shapeManager()->addShape(shape); SimpleShapeContainerModel::shapeHasBeenAddedToHierarchy(shape, addedToSubtree); } void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree) override { q->shapeManager()->remove(shape); SimpleShapeContainerModel::shapeToBeRemovedFromHierarchy(shape, removedFromSubtree); } private: KisShapeLayer *q; }; struct KisShapeLayer::Private { public: Private() : canvas(0) , controller(0) , x(0) , y(0) {} KisPaintDeviceSP paintDevice; KisShapeLayerCanvasBase * canvas; KoShapeControllerBase* controller; int x; int y; }; KisShapeLayer::KisShapeLayer(KoShapeControllerBase* controller, KisImageWSP image, const QString &name, quint8 opacity) : KisExternalLayer(image, name, opacity), KoShapeLayer(new ShapeLayerContainerModel(this)), m_d(new Private()) { initShapeLayer(controller); } KisShapeLayer::KisShapeLayer(const KisShapeLayer& rhs) : KisShapeLayer(rhs, rhs.m_d->controller) { } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, KoShapeControllerBase* controller, KisShapeLayerCanvasBase *canvas) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _rhs here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // copy the projection to avoid extra round of updates! initShapeLayer(controller, _rhs.m_d->paintDevice, canvas); /** * The transformaitons of the added shapes are automatically merged into the transformation * of the layer, so we should apply this extra transform separately */ const QTransform thisInvertedTransform = this->absoluteTransformation().inverted(); m_d->canvas->shapeManager()->setUpdatesBlocked(true); Q_FOREACH (KoShape *shape, _rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation() * thisInvertedTransform); addShape(clonedShape); } m_d->canvas->shapeManager()->setUpdatesBlocked(false); } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, const KisShapeLayer &_addShapes) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _merge here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // Make sure our new layer is visible otherwise the shapes cannot be painted. setVisible(true); initShapeLayer(_rhs.m_d->controller); /** * With current implementation this matrix will always be an identity, because * we do not copy the transformation from any of the source layers. But we should * handle this anyway, to not be caught by this in the future. */ const QTransform thisInvertedTransform = this->absoluteTransformation().inverted(); QList shapesAbove; QList shapesBelow; // copy in _rhs's shapes Q_FOREACH (KoShape *shape, _rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation() * thisInvertedTransform); shapesBelow.append(clonedShape); } // copy in _addShapes's shapes Q_FOREACH (KoShape *shape, _addShapes.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation() * thisInvertedTransform); shapesAbove.append(clonedShape); } QList shapes = KoShapeReorderCommand::mergeDownShapes(shapesBelow, shapesAbove); KoShapeReorderCommand cmd(shapes); cmd.redo(); Q_FOREACH (KoShape *shape, shapesBelow + shapesAbove) { addShape(shape); } } KisShapeLayer::KisShapeLayer(KoShapeControllerBase* controller, KisImageWSP image, const QString &name, quint8 opacity, KisShapeLayerCanvasBase *canvas) : KisExternalLayer(image, name, opacity) , KoShapeLayer(new ShapeLayerContainerModel(this)) , m_d(new Private()) { initShapeLayer(controller, nullptr, canvas); } KisShapeLayer::~KisShapeLayer() { /** * Small hack alert: we should avoid updates on shape deletion */ m_d->canvas->prepareForDestroying(); Q_FOREACH (KoShape *shape, shapes()) { shape->setParent(0); delete shape; } delete m_d->canvas; delete m_d; } void KisShapeLayer::initShapeLayer(KoShapeControllerBase* controller, KisPaintDeviceSP copyFromProjection, KisShapeLayerCanvasBase *canvas) { setSupportsLodMoves(false); setShapeId(KIS_SHAPE_LAYER_ID); KIS_ASSERT_RECOVER_NOOP(this->image()); if (!copyFromProjection) { m_d->paintDevice = new KisPaintDevice(image()->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(this->image())); m_d->paintDevice->setParentNode(this); } else { m_d->paintDevice = new KisPaintDevice(*copyFromProjection); } if (!canvas) { auto *slCanvas = new KisShapeLayerCanvas(this, image()); slCanvas->setProjection(m_d->paintDevice); canvas = slCanvas; } m_d->canvas = canvas; m_d->canvas->moveToThread(this->thread()); m_d->controller = controller; m_d->canvas->shapeManager()->selection()->disconnect(this); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*)), this, SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(this, SIGNAL(sigMoveShapes(QPointF)), SLOT(slotMoveShapes(QPointF))); } bool KisShapeLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } void KisShapeLayer::setImage(KisImageWSP _image) { KisLayer::setImage(_image); m_d->canvas->setImage(_image); m_d->paintDevice->convertTo(_image->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(_image)); } KisLayerSP KisShapeLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { KisShapeLayer *prevShape = dynamic_cast(prevLayer.data()); if (prevShape) return new KisShapeLayer(*prevShape, *this); else return KisExternalLayer::createMergedLayerTemplate(prevLayer); } void KisShapeLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { if (!dynamic_cast(dstLayer.data())) { KisLayer::fillMergedLayerTemplate(dstLayer, prevLayer); } } void KisShapeLayer::setParent(KoShapeContainer *parent) { Q_UNUSED(parent) KIS_ASSERT_RECOVER_RETURN(0); } QIcon KisShapeLayer::icon() const { return KisIconUtils::loadIcon("vectorLayer"); } KisPaintDeviceSP KisShapeLayer::original() const { return m_d->paintDevice; } KisPaintDeviceSP KisShapeLayer::paintDevice() const { return 0; } qint32 KisShapeLayer::x() const { return m_d->x; } qint32 KisShapeLayer::y() const { return m_d->y; } void KisShapeLayer::setX(qint32 x) { qint32 delta = x - this->x(); QPointF diff = QPointF(m_d->canvas->viewConverter()->viewToDocumentX(delta), 0); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->x = x; } void KisShapeLayer::setY(qint32 y) { qint32 delta = y - this->y(); QPointF diff = QPointF(0, m_d->canvas->viewConverter()->viewToDocumentY(delta)); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->y = y; } namespace { void filterTransformableShapes(QList &shapes) { auto it = shapes.begin(); while (it != shapes.end()) { if (shapes.size() == 1) break; if ((*it)->inheritsTransformFromAny(shapes)) { it = shapes.erase(it); } else { ++it; } } } } QList KisShapeLayer::shapesToBeTransformed() { QList shapes = shapeManager()->shapes(); // We expect that **all** the shapes inherit the transform from its parent // SANITY_CHECK: we expect all the shapes inside the // shape layer to inherit transform! Q_FOREACH (KoShape *shape, shapes) { if (shape->parent()) { KIS_SAFE_ASSERT_RECOVER(shape->parent()->inheritsTransform(shape)) { break; } } } shapes << this; filterTransformableShapes(shapes); return shapes; } void KisShapeLayer::slotMoveShapes(const QPointF &diff) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return; KoShapeMoveCommand cmd(shapes, diff); cmd.redo(); } void KisShapeLayer::slotTransformShapes(const QTransform &newTransform) { KoShapeTransformCommand cmd({this}, {transformation()}, {newTransform}); cmd.redo(); } bool KisShapeLayer::accept(KisNodeVisitor& visitor) { return visitor.visit(this); } void KisShapeLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } KoShapeManager* KisShapeLayer::shapeManager() const { return m_d->canvas->shapeManager(); } KoViewConverter* KisShapeLayer::converter() const { return m_d->canvas->viewConverter(); } bool KisShapeLayer::visible(bool recursive) const { return KisExternalLayer::visible(recursive); } void KisShapeLayer::setVisible(bool visible, bool isLoading) { const bool oldVisible = this->visible(false); KoShapeLayer::setVisible(visible); KisExternalLayer::setVisible(visible, isLoading); if (visible && !oldVisible && m_d->canvas->hasChangedWhileBeingInvisible()) { m_d->canvas->rerenderAfterBeingInvisible(); } } void KisShapeLayer::setUserLocked(bool value) { KoShapeLayer::setGeometryProtected(value); KisExternalLayer::setUserLocked(value); } bool KisShapeLayer::isShapeEditable(bool recursive) const { return KoShapeLayer::isShapeEditable(recursive) && isEditable(true); } // we do not override KoShape::setGeometryProtected() as we consider // the user not being able to access the layer shape from Krita UI! void KisShapeLayer::forceUpdateTimedNode() { m_d->canvas->forceRepaint(); } bool KisShapeLayer::hasPendingTimedUpdates() const { return m_d->canvas->hasPendingUpdates(); } void KisShapeLayer::forceUpdateHiddenAreaOnOriginal() { m_d->canvas->forceRepaintWithHiddenAreas(); } bool KisShapeLayer::saveShapesToStore(KoStore *store, QList shapes, const QSizeF &sizeInPt) { if (!store->open("content.svg")) { return false; } KoStoreDevice storeDev(store); storeDev.open(QIODevice::WriteOnly); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes); writer.save(storeDev, sizeInPt); if (!store->close()) { return false; } return true; } QList KisShapeLayer::createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize) { QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc = SvgParser::createDocumentFromSvg(device, &errorMsg, &errorLine, &errorColumn); if (doc.isNull()) { errKrita << "Parsing error in " << "contents.svg" << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3" , errorLine , errorColumn , errorMsg); } SvgParser parser(resourceManager); parser.setXmlBaseDir(baseXmlDir); parser.setResolution(rectInPixels /* px */, resolutionPPI /* ppi */); return parser.parseSvg(doc.documentElement(), fragmentSize); } bool KisShapeLayer::saveLayer(KoStore * store) const { // FIXME: we handle xRes() only! const QSizeF sizeInPx = image()->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / image()->xRes(), sizeInPx.height() / image()->yRes()); return saveShapesToStore(store, this->shapes(), sizeInPt); } bool KisShapeLayer::loadSvg(QIODevice *device, const QString &baseXmlDir) { QSizeF fragmentSize; // unused! KisImageSP image = this->image(); // FIXME: we handle xRes() only! KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(image->xRes(), image->yRes())); const qreal resolutionPPI = 72.0 * image->xRes(); QList shapes = createShapesFromSvg(device, baseXmlDir, image->bounds(), resolutionPPI, m_d->controller->resourceManager(), &fragmentSize); Q_FOREACH (KoShape *shape, shapes) { addShape(shape); } return true; } bool KisShapeLayer::loadLayer(KoStore* store) { if (!store) { warnKrita << i18n("No store backend"); return false; } if (store->open("content.svg")) { KoStoreDevice storeDev(store); storeDev.open(QIODevice::ReadOnly); loadSvg(&storeDev, ""); store->close(); return true; } - KoOdfReadStore odfStore(store); - QString errorMessage; - - odfStore.loadAndParse(errorMessage); - - if (!errorMessage.isEmpty()) { - warnKrita << errorMessage; - return false; - } - - KoXmlElement contents = odfStore.contentDoc().documentElement(); - - // dbgKrita <<"Start loading OASIS document..." << contents.text(); - // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName(); - // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI(); - // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement(); - - KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body")); - - if (body.isNull()) { - //setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) ); - return false; - } - - body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing"); - if (body.isNull()) { - //setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) ); - return false; - } - - KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page")); - if (page.isNull()) { - //setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) ); - return false; - } - - KoXmlElement * master = 0; - if (odfStore.styles().masterPages().contains("Standard")) - master = odfStore.styles().masterPages().value("Standard"); - else if (odfStore.styles().masterPages().contains("Default")) - master = odfStore.styles().masterPages().value("Default"); - else if (! odfStore.styles().masterPages().empty()) - master = odfStore.styles().masterPages().begin().value(); - - // We work fine without a master page - - KoOdfLoadingContext context(odfStore.styles(), odfStore.store()); - context.setManifestFile(QString("tar:/") + odfStore.store()->currentPath() + "META-INF/manifest.xml"); - KoShapeLoadingContext shapeContext(context, m_d->controller->resourceManager()); - - - KoXmlElement layerElement; - forEachElement(layerElement, context.stylesReader().layerSet()) { - // FIXME: investigate what is this - // KoShapeLayer * l = new KoShapeLayer(); - if (!loadOdf(layerElement, shapeContext)) { - dbgKrita << "Could not load vector layer!"; - return false; - } - } - - KoXmlElement child; - forEachElement(child, page) { - KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext); - if (shape) { - addShape(shape); - } - } - - return true; + return false; } void KisShapeLayer::resetCache() { m_d->canvas->resetCache(); } KUndo2Command* KisShapeLayer::crop(const QRect & rect) { QPoint oldPos(x(), y()); QPoint newPos = oldPos - rect.topLeft(); return new KisNodeMoveCommand2(this, oldPos, newPos); } class TransformShapeLayerDeferred : public KUndo2Command { public: TransformShapeLayerDeferred(KisShapeLayer *shapeLayer, const QTransform &globalDocTransform) : m_shapeLayer(shapeLayer), m_globalDocTransform(globalDocTransform), m_blockingConnection(std::bind(&KisShapeLayer::slotTransformShapes, shapeLayer, std::placeholders::_1)) { } void undo() { KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() != qApp->thread()); m_blockingConnection.start(m_savedTransform); } void redo() { m_savedTransform = m_shapeLayer->transformation(); const QTransform globalTransform = m_shapeLayer->absoluteTransformation(); const QTransform localTransform = globalTransform * m_globalDocTransform * globalTransform.inverted(); KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() != qApp->thread()); m_blockingConnection.start(localTransform * m_savedTransform); } private: KisShapeLayer *m_shapeLayer; QTransform m_globalDocTransform; QTransform m_savedTransform; KisSafeBlockingQueueConnectionProxy m_blockingConnection; }; KUndo2Command* KisShapeLayer::transform(const QTransform &transform) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return 0; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shapes.size() == 1 && shapes.first() == this, 0); /** * We cannot transform shapes in the worker thread. Therefor we emit blocking-queued * signal to transform them in the GUI thread and then return. */ KisImageViewConverter *converter = dynamic_cast(this->converter()); QTransform docSpaceTransform = converter->documentToView() * transform * converter->viewToDocument(); return new TransformShapeLayerDeferred(this, docSpaceTransform); } KUndo2Command *KisShapeLayer::setProfile(const KoColorProfile *profile) { using namespace KisDoSomethingCommandOps; KUndo2Command *cmd = new KUndo2Command(); new KisDoSomethingCommand(this, false, cmd); m_d->paintDevice->setProfile(profile, cmd); new KisDoSomethingCommand(this, true, cmd); return cmd; } KUndo2Command *KisShapeLayer::convertTo(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { using namespace KisDoSomethingCommandOps; KUndo2Command *cmd = new KUndo2Command(); new KisDoSomethingCommand(this, false, cmd); m_d->paintDevice->convertTo(dstColorSpace, renderingIntent, conversionFlags, cmd); new KisDoSomethingCommand(this, true, cmd); return cmd; } KoShapeControllerBase *KisShapeLayer::shapeController() const { return m_d->controller; } diff --git a/libs/ui/flake/kis_shape_selection.cpp b/libs/ui/flake/kis_shape_selection.cpp index be3190bb96..3caac5d8de 100644 --- a/libs/ui/flake/kis_shape_selection.cpp +++ b/libs/ui/flake/kis_shape_selection.cpp @@ -1,412 +1,340 @@ /* * Copyright (c) 2010 Sven Langkamp * Copyright (c) 2011 Jan Hambrecht * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_selection.h" #include #include #include #include #include #include #include #include #include #include #include #include #include -#include -#include -#include -#include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_shape_selection_model.h" #include "kis_shape_selection_canvas.h" #include "kis_take_all_shapes_command.h" #include "kis_image_view_converter.h" #include "kis_shape_layer.h" #include KisShapeSelection::KisShapeSelection(KoShapeControllerBase *shapeControllerBase, KisImageWSP image, KisSelectionWSP selection) : KoShapeLayer(m_model = new KisShapeSelectionModel(image, selection, this)) , m_image(image) , m_shapeControllerBase(shapeControllerBase) { Q_ASSERT(m_image); setShapeId("KisShapeSelection"); setSelectable(false); m_converter = new KisImageViewConverter(image); m_canvas = new KisShapeSelectionCanvas(shapeControllerBase); m_canvas->shapeManager()->addShape(this); m_model->setObjectName("KisShapeSelectionModel"); m_model->moveToThread(image->thread()); m_canvas->setObjectName("KisShapeSelectionCanvas"); m_canvas->moveToThread(image->thread()); connect(this, SIGNAL(sigMoveShapes(QPointF)), SLOT(slotMoveShapes(QPointF))); } KisShapeSelection::~KisShapeSelection() { m_model->setShapeSelection(0); delete m_canvas; delete m_converter; } KisShapeSelection::KisShapeSelection(const KisShapeSelection& rhs, KisSelection* selection) : KoShapeLayer(m_model = new KisShapeSelectionModel(rhs.m_image, selection, this)) { m_image = rhs.m_image; m_shapeControllerBase = rhs.m_shapeControllerBase; m_converter = new KisImageViewConverter(m_image); m_canvas = new KisShapeSelectionCanvas(m_shapeControllerBase); // TODO: refactor shape selection to pass signals // via KoShapeManager, not via the model m_canvas->shapeManager()->setUpdatesBlocked(true); m_model->setUpdatesEnabled(false); m_canvas->shapeManager()->addShape(this); Q_FOREACH (KoShape *shape, rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } this->addShape(clonedShape); } m_canvas->shapeManager()->setUpdatesBlocked(false); m_model->setUpdatesEnabled(true); } KisSelectionComponent* KisShapeSelection::clone(KisSelection* selection) { /** * TODO: make cloning of vector selections safe! Right now it crashes * on Windows because of manipulations with timers from non-gui thread. */ KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); return new KisShapeSelection(*this, selection); } bool KisShapeSelection::saveSelection(KoStore * store) const { const QSizeF sizeInPx = m_image->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / m_image->xRes(), sizeInPx.height() / m_image->yRes()); return KisShapeLayer::saveShapesToStore(store, this->shapes(), sizeInPt); } bool KisShapeSelection::loadSelection(KoStore* store) { QSizeF fragmentSize; // unused! // FIXME: we handle xRes() only! KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(m_image->xRes(), m_image->yRes())); const qreal resolutionPPI = 72.0 * m_image->xRes(); QList shapes; if (store->open("content.svg")) { KoStoreDevice storeDev(store); storeDev.open(QIODevice::ReadOnly); shapes = KisShapeLayer::createShapesFromSvg(&storeDev, "", m_image->bounds(), resolutionPPI, m_canvas->shapeController()->resourceManager(), &fragmentSize); store->close(); Q_FOREACH (KoShape *shape, shapes) { addShape(shape); } return true; } - - KoOdfReadStore odfStore(store); - QString errorMessage; - - odfStore.loadAndParse(errorMessage); - - if (!errorMessage.isEmpty()) { - dbgKrita << errorMessage; - return false; - } - - KoXmlElement contents = odfStore.contentDoc().documentElement(); - - // dbgKrita <<"Start loading OASIS document..." << contents.text(); - // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName(); - // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI(); - // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement(); - - KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body")); - - if (body.isNull()) { - dbgKrita << "No office:body found!"; - //setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) ); - return false; - } - - body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing"); - if (body.isNull()) { - dbgKrita << "No office:drawing found!"; - //setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) ); - return false; - } - - KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page")); - if (page.isNull()) { - dbgKrita << "No office:drawing found!"; - //setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) ); - return false; - } - - KoXmlElement * master = 0; - if (odfStore.styles().masterPages().contains("Standard")) - master = odfStore.styles().masterPages().value("Standard"); - else if (odfStore.styles().masterPages().contains("Default")) - master = odfStore.styles().masterPages().value("Default"); - else if (! odfStore.styles().masterPages().empty()) - master = odfStore.styles().masterPages().begin().value(); - - KoOdfLoadingContext context(odfStore.styles(), odfStore.store()); - KoShapeLoadingContext shapeContext(context, 0); - - KoXmlElement layerElement; - forEachElement(layerElement, context.stylesReader().layerSet()) { - if (!loadOdf(layerElement, shapeContext)) { - dbgKrita << "Could not load vector layer!"; - return false; - } - } - - KoXmlElement child; - forEachElement(child, page) { - KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext); - if (shape) { - addShape(shape); - } - } - - return true; + return false; } void KisShapeSelection::setUpdatesEnabled(bool enabled) { m_model->setUpdatesEnabled(enabled); } bool KisShapeSelection::updatesEnabled() const { return m_model->updatesEnabled(); } KUndo2Command* KisShapeSelection::resetToEmpty() { return new KisTakeAllShapesCommand(this, true, false); } bool KisShapeSelection::isEmpty() const { return !m_model->count(); } QPainterPath KisShapeSelection::outlineCache() const { return m_outline; } bool KisShapeSelection::outlineCacheValid() const { return true; } void KisShapeSelection::recalculateOutlineCache() { QTransform resolutionMatrix; resolutionMatrix.scale(m_image->xRes(), m_image->yRes()); QList shapesList = shapes(); QPainterPath outline; Q_FOREACH (KoShape * shape, shapesList) { /** * WARNING: we should unite all the shapes in image coordinates, * not in points. Boolean operations inside the QPainterPath * linearize the curves into lines and they use absolute values * for thresholds. * * See KritaUtils::pathShapeBooleanSpaceWorkaround() for more info */ QTransform shapeMatrix = shape->absoluteTransformation(); outline = outline.united(resolutionMatrix.map(shapeMatrix.map(shape->outline()))); } m_outline = outline; } void KisShapeSelection::paintComponent(QPainter& painter, KoShapePaintingContext &) const { Q_UNUSED(painter); } void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection) { Q_ASSERT(projection); Q_ASSERT(m_image); QRectF boundingRect = outlineCache().boundingRect(); renderSelection(projection, boundingRect.toAlignedRect()); } void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection, const QRect& r) { Q_ASSERT(projection); renderSelection(projection, r); } void KisShapeSelection::renderSelection(KisPaintDeviceSP projection, const QRect& requestedRect) { KIS_SAFE_ASSERT_RECOVER_RETURN(projection); KIS_SAFE_ASSERT_RECOVER_RETURN(m_image); const qint32 MASK_IMAGE_WIDTH = 256; const qint32 MASK_IMAGE_HEIGHT = 256; const QPainterPath selectionOutline = outlineCache(); if (*projection->defaultPixel().data() == OPACITY_TRANSPARENT_U8) { projection->clear(requestedRect); } else { KoColor transparentColor(Qt::transparent, projection->colorSpace()); projection->fill(requestedRect, transparentColor); } const QRect r = requestedRect & selectionOutline.boundingRect().toAlignedRect(); QImage polygonMaskImage(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT, QImage::Format_ARGB32); QPainter maskPainter(&polygonMaskImage); maskPainter.setRenderHint(QPainter::Antialiasing, true); // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. for (qint32 x = r.x(); x < r.x() + r.width(); x += MASK_IMAGE_WIDTH) { for (qint32 y = r.y(); y < r.y() + r.height(); y += MASK_IMAGE_HEIGHT) { maskPainter.fillRect(polygonMaskImage.rect(), Qt::black); maskPainter.translate(-x, -y); maskPainter.fillPath(selectionOutline, Qt::white); maskPainter.translate(x, y); qint32 rectWidth = qMin(r.x() + r.width() - x, MASK_IMAGE_WIDTH); qint32 rectHeight = qMin(r.y() + r.height() - y, MASK_IMAGE_HEIGHT); KisSequentialIterator it(projection, QRect(x, y, rectWidth, rectHeight)); while (it.nextPixel()) { (*it.rawData()) = qRed(polygonMaskImage.pixel(it.x() - x, it.y() - y)); } } } } KoShapeManager* KisShapeSelection::shapeManager() const { return m_canvas->shapeManager(); } KisShapeSelectionFactory::KisShapeSelectionFactory() : KoShapeFactoryBase("KisShapeSelection", "selection shape container") { setHidden(true); } void KisShapeSelection::moveX(qint32 x) { const QPointF diff(x / m_image->xRes(), 0); emit sigMoveShapes(diff); } void KisShapeSelection::moveY(qint32 y) { const QPointF diff(0, y / m_image->yRes()); emit sigMoveShapes(diff); } void KisShapeSelection::slotMoveShapes(const QPointF &diff) { Q_FOREACH (KoShape* shape, shapeManager()->shapes()) { if (shape != this) { QPointF pos = shape->position(); shape->setPosition(pos + diff); } } } // TODO same code as in vector layer, refactor! KUndo2Command* KisShapeSelection::transform(const QTransform &transform) { QList shapes = m_canvas->shapeManager()->shapes(); if(shapes.isEmpty()) return 0; QTransform realTransform = m_converter->documentToView() * transform * m_converter->viewToDocument(); QList oldTransformations; QList newTransformations; // this code won't work if there are shapes, that inherit the transformation from the parent container. // the chart and tree shapes are examples for that, but they aren't used in krita and there are no other shapes like that. Q_FOREACH (const KoShape* shape, shapes) { QTransform oldTransform = shape->transformation(); oldTransformations.append(oldTransform); if (dynamic_cast(shape)) { newTransformations.append(oldTransform); } else { QTransform globalTransform = shape->absoluteTransformation(); QTransform localTransform = globalTransform * realTransform * globalTransform.inverted(); newTransformations.append(localTransform*oldTransform); } } return new KoShapeTransformCommand(shapes, oldTransformations, newTransformations); } diff --git a/plugins/flake/imageshape/ImageShape.cpp b/plugins/flake/imageshape/ImageShape.cpp index 9a97c6341a..f94a70aa27 100644 --- a/plugins/flake/imageshape/ImageShape.cpp +++ b/plugins/flake/imageshape/ImageShape.cpp @@ -1,186 +1,173 @@ /* * Copyright (c) 2016 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 #include "ImageShape.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include #include "kis_dom_utils.h" #include #include "KisQPainterStateSaver.h" struct Q_DECL_HIDDEN ImageShape::Private : public QSharedData { Private() {} Private(const Private &rhs) : QSharedData(), image(rhs.image), ratioParser(rhs.ratioParser ? new SvgUtil::PreserveAspectRatioParser(*rhs.ratioParser) : 0), viewBoxTransform(rhs.viewBoxTransform) { } QImage image; QScopedPointer ratioParser; QTransform viewBoxTransform; }; ImageShape::ImageShape() : m_d(new Private) { } ImageShape::ImageShape(const ImageShape &rhs) : KoTosContainer(rhs), m_d(rhs.m_d) { } ImageShape::~ImageShape() { } KoShape *ImageShape::cloneShape() const { return new ImageShape(*this); } void ImageShape::paint(QPainter &painter, KoShapePaintingContext &paintContext) const { Q_UNUSED(paintContext); KisQPainterStateSaver saver(&painter); const QRectF myrect(QPointF(), size()); painter.setRenderHint(QPainter::SmoothPixmapTransform); painter.setClipRect(QRectF(QPointF(), size()), Qt::IntersectClip); painter.setTransform(m_d->viewBoxTransform, true); painter.drawImage(QPoint(), m_d->image); } void ImageShape::setSize(const QSizeF &size) { KoTosContainer::setSize(size); } -void ImageShape::saveOdf(KoShapeSavingContext &context) const -{ - Q_UNUSED(context); -} - -bool ImageShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) -{ - Q_UNUSED(element); - Q_UNUSED(context); - - return false; -} - bool ImageShape::saveSvg(SvgSavingContext &context) { const QString uid = context.createUID("image"); context.shapeWriter().startElement("image"); context.shapeWriter().addAttribute("id", uid); SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter()); context.shapeWriter().addAttribute("width", QString("%1px").arg(KisDomUtils::toString(size().width()))); context.shapeWriter().addAttribute("height", QString("%1px").arg(KisDomUtils::toString(size().height()))); QString aspectString = m_d->ratioParser->toString(); if (!aspectString.isEmpty()) { context.shapeWriter().addAttribute("preserveAspectRatio", aspectString); } QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); if (m_d->image.save(&buffer, "PNG")) { const QString mimeType = KisMimeDatabase::mimeTypeForSuffix("*.png"); context.shapeWriter().addAttribute("xlink:href", "data:"+ mimeType + ";base64," + ba.toBase64()); } context.shapeWriter().endElement(); // image return true; } bool ImageShape::loadSvg(const KoXmlElement &element, SvgLoadingContext &context) { const qreal x = SvgUtil::parseUnitX(context.currentGC(), element.attribute("x")); const qreal y = SvgUtil::parseUnitY(context.currentGC(), element.attribute("y")); const qreal w = SvgUtil::parseUnitX(context.currentGC(), element.attribute("width")); const qreal h = SvgUtil::parseUnitY(context.currentGC(), element.attribute("height")); setSize(QSizeF(w, h)); setPosition(QPointF(x, y)); if (w == 0.0 || h == 0.0) { setVisible(false); } QString fileName = element.attribute("xlink:href"); QByteArray data; if (fileName.startsWith("data:")) { QRegularExpression re("data:(.+?);base64,(.+)"); QRegularExpressionMatch match = re.match(fileName); data = match.captured(2).toLatin1(); data = QByteArray::fromBase64(data); } else { data = context.fetchExternalFile(fileName); } if (!data.isEmpty()) { QBuffer buffer(&data); m_d->image.load(&buffer, ""); } const QString aspectString = element.attribute("preserveAspectRatio", "xMidYMid meet"); m_d->ratioParser.reset(new SvgUtil::PreserveAspectRatioParser(aspectString)); if (!m_d->image.isNull()) { m_d->viewBoxTransform = QTransform::fromScale(w / m_d->image.width(), h / m_d->image.height()); SvgUtil::parseAspectRatio(*m_d->ratioParser, QRectF(QPointF(), size()), QRect(QPoint(), m_d->image.size()), &m_d->viewBoxTransform); } if (m_d->ratioParser->defer) { // TODO: } return true; } diff --git a/plugins/flake/imageshape/ImageShape.h b/plugins/flake/imageshape/ImageShape.h index 108cc8c28f..a5789a0b5a 100644 --- a/plugins/flake/imageshape/ImageShape.h +++ b/plugins/flake/imageshape/ImageShape.h @@ -1,56 +1,53 @@ /* * Copyright (c) 2016 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 IMAGESHAPE_H #define IMAGESHAPE_H #include #include "KoTosContainer.h" #include #define ImageShapeId "ImageShape" class ImageShape : public KoTosContainer, public SvgShape { public: ImageShape(); ~ImageShape() override; KoShape *cloneShape() const override; void paint(QPainter &painter, KoShapePaintingContext &paintContext) const override; void setSize(const QSizeF &size) override; - void saveOdf(KoShapeSavingContext &context) const override; - bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override; - bool saveSvg(SvgSavingContext &context) override; bool loadSvg(const KoXmlElement &element, SvgLoadingContext &context) override; private: ImageShape(const ImageShape &rhs); private: struct Private; QSharedDataPointer m_d; }; #endif // IMAGESHAPE_H diff --git a/plugins/flake/pathshapes/ellipse/EllipseShape.cpp b/plugins/flake/pathshapes/ellipse/EllipseShape.cpp index 345a19512f..2e2758e9d2 100644 --- a/plugins/flake/pathshapes/ellipse/EllipseShape.cpp +++ b/plugins/flake/pathshapes/ellipse/EllipseShape.cpp @@ -1,549 +1,456 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006,2008 Jan Hambrecht 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 "EllipseShape.h" #include #include #include #include #include #include -#include #include #include #include #include #include #include "kis_global.h" #include EllipseShape::EllipseShape() : m_startAngle(0) , m_endAngle(0) , m_kindAngle(M_PI) , m_type(Arc) { QList handles; handles.push_back(QPointF(100, 50)); handles.push_back(QPointF(100, 50)); handles.push_back(QPointF(0, 50)); setHandles(handles); QSizeF size(100, 100); m_radii = QPointF(size.width() / 2.0, size.height() / 2.0); m_center = QPointF(m_radii.x(), m_radii.y()); updatePath(size); } EllipseShape::EllipseShape(const EllipseShape &rhs) : KoParameterShape(rhs), m_startAngle(rhs.m_startAngle), m_endAngle(rhs.m_endAngle), m_kindAngle(rhs.m_kindAngle), m_center(rhs.m_center), m_radii(rhs.m_radii), m_type(rhs.m_type) { } EllipseShape::~EllipseShape() { } KoShape *EllipseShape::cloneShape() const { return new EllipseShape(*this); } -void EllipseShape::saveOdf(KoShapeSavingContext &context) const -{ - if (isParametricShape()) { - context.xmlWriter().startElement("draw:ellipse"); - saveOdfAttributes(context, OdfAllAttributes); - - switch (m_type) { - case Arc: - context.xmlWriter().addAttribute("draw:kind", sweepAngle() == 360 ? "full" : "arc"); - break; - case Pie: - context.xmlWriter().addAttribute("draw:kind", "section"); - break; - case Chord: - context.xmlWriter().addAttribute("draw:kind", "cut"); - break; - default: - context.xmlWriter().addAttribute("draw:kind", "full"); - } - if (m_type != Arc || sweepAngle() != 360) { - context.xmlWriter().addAttribute("draw:start-angle", m_startAngle); - context.xmlWriter().addAttribute("draw:end-angle", m_endAngle); - } - saveText(context); - context.xmlWriter().endElement(); - } else { - KoPathShape::saveOdf(context); - } -} - -bool EllipseShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) -{ - QSizeF size; - - bool radiusGiven = true; - - QString kind = element.attributeNS(KoXmlNS::draw, "kind", "full"); - - if (element.hasAttributeNS(KoXmlNS::svg, "rx") && element.hasAttributeNS(KoXmlNS::svg, "ry")) { - qreal rx = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "rx")); - qreal ry = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "ry")); - size = QSizeF(2 * rx, 2 * ry); - } else if (element.hasAttributeNS(KoXmlNS::svg, "r")) { - qreal r = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "r")); - size = QSizeF(2 * r, 2 * r); - } else { - size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); - size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); -#ifndef NWORKAROUND_ODF_BUGS - radiusGiven = KoOdfWorkaround::fixEllipse(kind, context); -#else - radiusGiven = false; -#endif - } - setSize(size); - - QPointF pos; - - if (element.hasAttributeNS(KoXmlNS::svg, "cx") && element.hasAttributeNS(KoXmlNS::svg, "cy")) { - qreal cx = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "cx")); - qreal cy = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "cy")); - pos = QPointF(cx - 0.5 * size.width(), cy - 0.5 * size.height()); - } else { - pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); - pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); - } - setPosition(pos); - - if (kind == "section") { - setType(Pie); - } else if (kind == "cut") { - setType(Chord); - } else { - setType(Arc); - } - - setStartAngle(element.attributeNS(KoXmlNS::draw, "start-angle", "0").toDouble()); - setEndAngle(element.attributeNS(KoXmlNS::draw, "end-angle", "360").toDouble()); - if (!radiusGiven) { - // is the size was given by width and height we have to reset the data as the size of the - // part of the cut/pie is given. - setSize(size); - setPosition(pos); - } - - loadOdfAttributes(element, context, OdfMandatories | OdfTransformation | OdfAdditionalAttributes); - - loadText(element, context); - - return true; -} - void EllipseShape::setSize(const QSizeF &newSize) { QTransform matrix(resizeMatrix(newSize)); m_center = matrix.map(m_center); m_radii = matrix.map(m_radii); KoParameterShape::setSize(newSize); } QPointF EllipseShape::normalize() { QPointF offset(KoParameterShape::normalize()); QTransform matrix; matrix.translate(-offset.x(), -offset.y()); m_center = matrix.map(m_center); return offset; } void EllipseShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); QPointF p(point); QPointF diff(m_center - point); diff.setX(-diff.x()); qreal angle = 0; if (diff.x() == 0) { angle = (diff.y() < 0 ? 270 : 90) * M_PI / 180.0; } else { diff.setY(diff.y() * m_radii.x() / m_radii.y()); angle = atan(diff.y() / diff.x()); if (angle < 0) { angle += M_PI; } if (diff.y() < 0) { angle += M_PI; } } QList handles = this->handles(); switch (handleId) { case 0: p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y())); m_startAngle = kisRadiansToDegrees(angle); handles[handleId] = p; break; case 1: p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y())); m_endAngle = kisRadiansToDegrees(angle); handles[handleId] = p; break; case 2: { QList kindHandlePositions; kindHandlePositions.push_back(QPointF(m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y()))); kindHandlePositions.push_back(m_center); kindHandlePositions.push_back((handles[0] + handles[1]) / 2.0); QPointF diff = m_center * 2.0; int handlePos = 0; for (int i = 0; i < kindHandlePositions.size(); ++i) { QPointF pointDiff(p - kindHandlePositions[i]); if (i == 0 || qAbs(pointDiff.x()) + qAbs(pointDiff.y()) < qAbs(diff.x()) + qAbs(diff.y())) { diff = pointDiff; handlePos = i; } } handles[handleId] = kindHandlePositions[handlePos]; m_type = EllipseType(handlePos); } break; } setHandles(handles); if (handleId != 2) { updateKindHandle(); } } void EllipseShape::updatePath(const QSizeF &size) { Q_UNUSED(size); QPointF startpoint(handles()[0]); QPointF curvePoints[12]; const qreal distance = sweepAngle(); const bool sameAngles = distance > 359.9; int pointCnt = arcToCurve(m_radii.x(), m_radii.y(), m_startAngle, distance, startpoint, curvePoints); KIS_SAFE_ASSERT_RECOVER_RETURN(pointCnt); int curvePointCount = 1 + pointCnt / 3; int requiredPointCount = curvePointCount; if (m_type == Pie) { requiredPointCount++; } else if (m_type == Arc && sameAngles) { curvePointCount--; requiredPointCount--; } createPoints(requiredPointCount); KoSubpath &points = *subpaths()[0]; int curveIndex = 0; points[0]->setPoint(startpoint); points[0]->removeControlPoint1(); points[0]->setProperty(KoPathPoint::StartSubpath); for (int i = 1; i < curvePointCount; ++i) { points[i - 1]->setControlPoint2(curvePoints[curveIndex++]); points[i]->setControlPoint1(curvePoints[curveIndex++]); points[i]->setPoint(curvePoints[curveIndex++]); points[i]->removeControlPoint2(); } if (m_type == Pie) { points[requiredPointCount - 1]->setPoint(m_center); points[requiredPointCount - 1]->removeControlPoint1(); points[requiredPointCount - 1]->removeControlPoint2(); } else if (m_type == Arc && sameAngles) { points[curvePointCount - 1]->setControlPoint2(curvePoints[curveIndex]); points[0]->setControlPoint1(curvePoints[++curveIndex]); } for (int i = 0; i < requiredPointCount; ++i) { points[i]->unsetProperty(KoPathPoint::StopSubpath); points[i]->unsetProperty(KoPathPoint::CloseSubpath); } subpaths()[0]->last()->setProperty(KoPathPoint::StopSubpath); if (m_type == Arc && !sameAngles) { subpaths()[0]->first()->unsetProperty(KoPathPoint::CloseSubpath); subpaths()[0]->last()->unsetProperty(KoPathPoint::CloseSubpath); } else { subpaths()[0]->first()->setProperty(KoPathPoint::CloseSubpath); subpaths()[0]->last()->setProperty(KoPathPoint::CloseSubpath); } notifyPointsChanged(); normalize(); } void EllipseShape::createPoints(int requiredPointCount) { if (subpaths().count() != 1) { clear(); subpaths().append(new KoSubpath()); } int currentPointCount = subpaths()[0]->count(); if (currentPointCount > requiredPointCount) { for (int i = 0; i < currentPointCount - requiredPointCount; ++i) { delete subpaths()[0]->front(); subpaths()[0]->pop_front(); } } else if (requiredPointCount > currentPointCount) { for (int i = 0; i < requiredPointCount - currentPointCount; ++i) { subpaths()[0]->append(new KoPathPoint(this, QPointF())); } } notifyPointsChanged(); } void EllipseShape::updateKindHandle() { qreal angle = 0.5 * (m_startAngle + m_endAngle); if (m_startAngle > m_endAngle) { angle += 180.0; } m_kindAngle = normalizeAngle(kisDegreesToRadians(angle)); QList handles = this->handles(); switch (m_type) { case Arc: handles[2] = m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y()); break; case Pie: handles[2] = m_center; break; case Chord: handles[2] = (handles[0] + handles[1]) / 2.0; break; } setHandles(handles); } void EllipseShape::updateAngleHandles() { qreal startRadian = kisDegreesToRadians(normalizeAngleDegrees(m_startAngle)); qreal endRadian = kisDegreesToRadians(normalizeAngleDegrees(m_endAngle)); QList handles = this->handles(); handles[0] = m_center + QPointF(cos(startRadian) * m_radii.x(), -sin(startRadian) * m_radii.y()); handles[1] = m_center + QPointF(cos(endRadian) * m_radii.x(), -sin(endRadian) * m_radii.y()); setHandles(handles); } qreal EllipseShape::sweepAngle() const { const qreal a1 = normalizeAngle(kisDegreesToRadians(m_startAngle)); const qreal a2 = normalizeAngle(kisDegreesToRadians(m_endAngle)); qreal sAngle = a2 - a1; if (a1 > a2) { sAngle = 2 * M_PI + sAngle; } if (qAbs(a1 - a2) < 0.05 / M_PI) { sAngle = 2 * M_PI; } return kisRadiansToDegrees(sAngle); } void EllipseShape::setType(EllipseType type) { m_type = type; updateKindHandle(); updatePath(size()); } EllipseShape::EllipseType EllipseShape::type() const { return m_type; } void EllipseShape::setStartAngle(qreal angle) { m_startAngle = angle; updateKindHandle(); updateAngleHandles(); updatePath(size()); } qreal EllipseShape::startAngle() const { return m_startAngle; } void EllipseShape::setEndAngle(qreal angle) { m_endAngle = angle; updateKindHandle(); updateAngleHandles(); updatePath(size()); } qreal EllipseShape::endAngle() const { return m_endAngle; } QString EllipseShape::pathShapeId() const { return EllipseShapeId; } bool EllipseShape::saveSvg(SvgSavingContext &context) { // let basic path saiving code handle our saving if (!isParametricShape()) return false; if (type() == EllipseShape::Arc && startAngle() == endAngle()) { const QSizeF size = this->size(); const bool isCircle = size.width() == size.height(); context.shapeWriter().startElement(isCircle ? "circle" : "ellipse"); context.shapeWriter().addAttribute("id", context.getID(this)); SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter()); if (isCircle) { context.shapeWriter().addAttribute("r", 0.5 * size.width()); } else { context.shapeWriter().addAttribute("rx", 0.5 * size.width()); context.shapeWriter().addAttribute("ry", 0.5 * size.height()); } context.shapeWriter().addAttribute("cx", 0.5 * size.width()); context.shapeWriter().addAttribute("cy", 0.5 * size.height()); SvgStyleWriter::saveSvgStyle(this, context); context.shapeWriter().endElement(); } else { context.shapeWriter().startElement("path"); context.shapeWriter().addAttribute("id", context.getID(this)); SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter()); context.shapeWriter().addAttribute("sodipodi:type", "arc"); context.shapeWriter().addAttribute("sodipodi:rx", m_radii.x()); context.shapeWriter().addAttribute("sodipodi:ry", m_radii.y()); context.shapeWriter().addAttribute("sodipodi:cx", m_center.x()); context.shapeWriter().addAttribute("sodipodi:cy", m_center.y()); context.shapeWriter().addAttribute("sodipodi:start", 2 * M_PI - kisDegreesToRadians(endAngle())); context.shapeWriter().addAttribute("sodipodi:end", 2 * M_PI - kisDegreesToRadians(startAngle())); switch (type()) { case Pie: // noop break; case Chord: context.shapeWriter().addAttribute("sodipodi:arc-type", "chord"); break; case Arc: context.shapeWriter().addAttribute("sodipodi:open", "true"); break; } context.shapeWriter().addAttribute("d", this->toString(context.userSpaceTransform())); SvgStyleWriter::saveSvgStyle(this, context); context.shapeWriter().endElement(); } return true; } bool EllipseShape::loadSvg(const KoXmlElement &element, SvgLoadingContext &context) { qreal rx = 0, ry = 0; qreal cx = 0; qreal cy = 0; qreal start = 0; qreal end = 0; EllipseType type = Arc; const QString extendedNamespace = element.attribute("sodipodi:type") == "arc" ? "sodipodi" : element.attribute("krita:type") == "arc" ? "krita" : ""; if (element.tagName() == "ellipse") { rx = SvgUtil::parseUnitX(context.currentGC(), element.attribute("rx")); ry = SvgUtil::parseUnitY(context.currentGC(), element.attribute("ry")); cx = SvgUtil::parseUnitX(context.currentGC(), element.attribute("cx", "0")); cy = SvgUtil::parseUnitY(context.currentGC(), element.attribute("cy", "0")); } else if (element.tagName() == "circle") { rx = ry = SvgUtil::parseUnitXY(context.currentGC(), element.attribute("r")); cx = SvgUtil::parseUnitX(context.currentGC(), element.attribute("cx", "0")); cy = SvgUtil::parseUnitY(context.currentGC(), element.attribute("cy", "0")); } else if (element.tagName() == "path" && !extendedNamespace.isEmpty()) { rx = SvgUtil::parseUnitX(context.currentGC(), element.attribute(extendedNamespace + ":rx")); ry = SvgUtil::parseUnitY(context.currentGC(), element.attribute(extendedNamespace + ":ry")); cx = SvgUtil::parseUnitX(context.currentGC(), element.attribute(extendedNamespace + ":cx", "0")); cy = SvgUtil::parseUnitY(context.currentGC(), element.attribute(extendedNamespace + ":cy", "0")); start = 2 * M_PI - SvgUtil::parseNumber(element.attribute(extendedNamespace + ":end")); end = 2 * M_PI - SvgUtil::parseNumber(element.attribute(extendedNamespace + ":start")); const QString kritaArcType = element.attribute("sodipodi:arc-type", element.attribute("krita:arcType")); if (kritaArcType.isEmpty()) { if (element.attribute("sodipodi:open", "false") == "false") { type = Pie; } } else if (kritaArcType == "pie") { type = Pie; } else if (kritaArcType == "chord") { type = Chord; } } else { return false; } setSize(QSizeF(2 * rx, 2 * ry)); setPosition(QPointF(cx - rx, cy - ry)); if (rx == 0.0 || ry == 0.0) { setVisible(false); } if (start != 0 || start != end) { setStartAngle(kisRadiansToDegrees(start)); setEndAngle(kisRadiansToDegrees(end)); setType(type); } return true; } diff --git a/plugins/flake/pathshapes/ellipse/EllipseShape.h b/plugins/flake/pathshapes/ellipse/EllipseShape.h index bdaa481044..e69db26a74 100644 --- a/plugins/flake/pathshapes/ellipse/EllipseShape.h +++ b/plugins/flake/pathshapes/ellipse/EllipseShape.h @@ -1,122 +1,118 @@ /* This file is part of the KDE project Copyright (C) 2006-2007 Thorsten Zachmann Copyright (C) 2006 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 KOELLIPSESHAPE_H #define KOELLIPSESHAPE_H #include "KoParameterShape.h" #include #define EllipseShapeId "EllipseShape" /** * This class adds support for arc, pie, chord, circle and ellipse * shapes. The ellipse/circle radii are defined by the actual size * of the ellipse shape which can be changed with the setSize * method. */ class EllipseShape : public KoParameterShape, public SvgShape { public: /// the possible ellipse types enum EllipseType { Arc = 0, ///< an ellipse arc Pie = 1, ///< an ellipse pie Chord = 2 ///< an ellipse chord }; EllipseShape(); ~EllipseShape() override; KoShape* cloneShape() const override; void setSize(const QSizeF &newSize) override; QPointF normalize() override; /** * Sets the type of the ellipse. * @param type the new ellipse type */ void setType(EllipseType type); /// Returns the actual ellipse type EllipseType type() const; /** * Sets the start angle of the ellipse. * @param angle the new start angle in degree */ void setStartAngle(qreal angle); /// Returns the actual ellipse start angle in degree qreal startAngle() const; /** * Sets the end angle of the ellipse. * @param angle the new end angle in degree */ void setEndAngle(qreal angle); /// Returns the actual ellipse end angle in degree qreal endAngle() const; /// reimplemented QString pathShapeId() const override; /// reimplemented from SvgShape bool saveSvg(SvgSavingContext &context) override; /// reimplemented from SvgShape bool loadSvg(const KoXmlElement &element, SvgLoadingContext &context) override; protected: - // reimplemented - void saveOdf(KoShapeSavingContext &context) const override; - // reimplemented - bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override; void moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers = Qt::NoModifier) override; void updatePath(const QSizeF &size) override; void createPoints(int requiredPointCount); private: qreal sweepAngle() const; void updateKindHandle(); void updateAngleHandles(); EllipseShape(const EllipseShape &rhs); // start angle in degree qreal m_startAngle; // end angle in degree qreal m_endAngle; // angle for modifying the kind in radiant qreal m_kindAngle; // the center of the ellipse QPointF m_center; // the radii of the ellipse QPointF m_radii; // the actual ellipse type EllipseType m_type; }; #endif /* KOELLIPSESHAPE_H */ diff --git a/plugins/flake/pathshapes/enhancedpath/EnhancedPathHandle.cpp b/plugins/flake/pathshapes/enhancedpath/EnhancedPathHandle.cpp index 30d81576b8..4ccd9d7fd2 100644 --- a/plugins/flake/pathshapes/enhancedpath/EnhancedPathHandle.cpp +++ b/plugins/flake/pathshapes/enhancedpath/EnhancedPathHandle.cpp @@ -1,238 +1,158 @@ /* This file is part of the KDE project * Copyright (C) 2007 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "EnhancedPathHandle.h" #include "EnhancedPathShape.h" #include "EnhancedPathParameter.h" #include #include #include #include #include -#include #include EnhancedPathHandle::EnhancedPathHandle(EnhancedPathShape *parent) : m_parent(parent) , m_positionX(0) , m_positionY(0) , m_minimumX(0) , m_minimumY(0) , m_maximumX(0) , m_maximumY(0) , m_polarX(0) , m_polarY(0) , m_minRadius(0) , m_maxRadius(0) { Q_ASSERT(m_parent); } EnhancedPathHandle::~EnhancedPathHandle() { } bool EnhancedPathHandle::hasPosition() const { return m_positionX && m_positionY; } void EnhancedPathHandle::setPosition(EnhancedPathParameter *positionX, EnhancedPathParameter *positionY) { m_positionX = positionX; m_positionY = positionY; } QPointF EnhancedPathHandle::position() { if (!hasPosition()) { return QPointF(); } QPointF position(m_positionX->evaluate(), m_positionY->evaluate()); if (isPolar()) { // convert polar coordinates into cartesian coordinates QPointF center(m_polarX->evaluate(), m_polarY->evaluate()); qreal angleInRadian = position.x() * M_PI / 180.0; position = center + position.y() * QPointF(cos(angleInRadian), sin(angleInRadian)); } return position; } void EnhancedPathHandle::changePosition(const QPointF &position) { if (!hasPosition()) { return; } QPointF constrainedPosition(position); if (isPolar()) { // convert cartesian coordinates into polar coordinates QPointF polarCenter(m_polarX->evaluate(), m_polarY->evaluate()); QPointF diff = constrainedPosition - polarCenter; // compute the polar radius qreal radius = sqrt(diff.x() * diff.x() + diff.y() * diff.y()); // compute the polar angle qreal angle = atan2(diff.y(), diff.x()); if (angle < 0.0) { angle += 2 * M_PI; } // constrain the radius if (m_minRadius) { radius = qMax(m_minRadius->evaluate(), radius); } if (m_maxRadius) { radius = qMin(m_maxRadius->evaluate(), radius); } constrainedPosition.setX(angle * 180.0 / M_PI); constrainedPosition.setY(radius); } else { // constrain x coordinate if (m_minimumX) { constrainedPosition.setX(qMax(m_minimumX->evaluate(), constrainedPosition.x())); } if (m_maximumX) { constrainedPosition.setX(qMin(m_maximumX->evaluate(), constrainedPosition.x())); } // constrain y coordinate if (m_minimumY) { constrainedPosition.setY(qMax(m_minimumY->evaluate(), constrainedPosition.y())); } if (m_maximumY) { constrainedPosition.setY(qMin(m_maximumY->evaluate(), constrainedPosition.y())); } } m_positionX->modify(constrainedPosition.x()); m_positionY->modify(constrainedPosition.y()); } void EnhancedPathHandle::setRangeX(EnhancedPathParameter *minX, EnhancedPathParameter *maxX) { m_minimumX = minX; m_maximumX = maxX; } void EnhancedPathHandle::setRangeY(EnhancedPathParameter *minY, EnhancedPathParameter *maxY) { m_minimumY = minY; m_maximumY = maxY; } void EnhancedPathHandle::setPolarCenter(EnhancedPathParameter *polarX, EnhancedPathParameter *polarY) { m_polarX = polarX; m_polarY = polarY; } void EnhancedPathHandle::setRadiusRange(EnhancedPathParameter *minRadius, EnhancedPathParameter *maxRadius) { m_minRadius = minRadius; m_maxRadius = maxRadius; } bool EnhancedPathHandle::isPolar() const { return m_polarX && m_polarY; } - -void EnhancedPathHandle::saveOdf(KoShapeSavingContext &context) const -{ - if (!hasPosition()) { - return; - } - context.xmlWriter().startElement("draw:handle"); - context.xmlWriter().addAttribute("draw:handle-position", m_positionX->toString() + ' ' + m_positionY->toString()); - if (isPolar()) { - context.xmlWriter().addAttribute("draw:handle-polar", m_polarX->toString() + ' ' + m_polarY->toString()); - if (m_minRadius) { - context.xmlWriter().addAttribute("draw:handle-radius-range-minimum", m_minRadius->toString()); - } - if (m_maxRadius) { - context.xmlWriter().addAttribute("draw:handle-radius-range-maximum", m_maxRadius->toString()); - } - } else { - if (m_minimumX) { - context.xmlWriter().addAttribute("draw:handle-range-x-minimum", m_minimumX->toString()); - } - if (m_maximumX) { - context.xmlWriter().addAttribute("draw:handle-range-x-maximum", m_maximumX->toString()); - } - if (m_minimumY) { - context.xmlWriter().addAttribute("draw:handle-range-y-minimum", m_minimumY->toString()); - } - if (m_maximumY) { - context.xmlWriter().addAttribute("draw:handle-range-y-maximum", m_maximumY->toString()); - } - } - context.xmlWriter().endElement(); // draw:handle -} - -bool EnhancedPathHandle::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) -{ - if (element.localName() != "handle" || element.namespaceURI() != KoXmlNS::draw) { - return false; - } - - QString position = element.attributeNS(KoXmlNS::draw, "handle-position"); -#ifndef NWORKAROUND_ODF_BUGS - KoOdfWorkaround::fixEnhancedPathPolarHandlePosition(position, element, context); -#endif - QStringList tokens = position.simplified().split(' '); - if (tokens.count() != 2) { - return false; - } - - setPosition(m_parent->parameter(tokens[0]), m_parent->parameter(tokens[1])); - - // check if we have a polar handle - if (element.hasAttributeNS(KoXmlNS::draw, "handle-polar")) { - QString polar = element.attributeNS(KoXmlNS::draw, "handle-polar"); - QStringList tokens = polar.simplified().split(' '); - if (tokens.count() == 2) { - setPolarCenter(m_parent->parameter(tokens[0]), m_parent->parameter(tokens[1])); - - QString minRadius = element.attributeNS(KoXmlNS::draw, "handle-radius-range-minimum"); - QString maxRadius = element.attributeNS(KoXmlNS::draw, "handle-radius-range-maximum"); - if (!minRadius.isEmpty() && !maxRadius.isEmpty()) { - setRadiusRange(m_parent->parameter(minRadius), m_parent->parameter(maxRadius)); - } - } - } else { - QString minX = element.attributeNS(KoXmlNS::draw, "handle-range-x-minimum"); - QString maxX = element.attributeNS(KoXmlNS::draw, "handle-range-x-maximum"); - if (!minX.isEmpty() && ! maxX.isEmpty()) { - setRangeX(m_parent->parameter(minX), m_parent->parameter(maxX)); - } - - QString minY = element.attributeNS(KoXmlNS::draw, "handle-range-y-minimum"); - QString maxY = element.attributeNS(KoXmlNS::draw, "handle-range-y-maximum"); - if (!minY.isEmpty() && ! maxY.isEmpty()) { - setRangeY(m_parent->parameter(minY), m_parent->parameter(maxY)); - } - } - - return hasPosition(); -} diff --git a/plugins/flake/pathshapes/enhancedpath/EnhancedPathHandle.h b/plugins/flake/pathshapes/enhancedpath/EnhancedPathHandle.h index 5603585122..43571b0f92 100644 --- a/plugins/flake/pathshapes/enhancedpath/EnhancedPathHandle.h +++ b/plugins/flake/pathshapes/enhancedpath/EnhancedPathHandle.h @@ -1,138 +1,134 @@ /* This file is part of the KDE project * Copyright (C) 2007 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOENHANCEDPATHHANDLE_H #define KOENHANCEDPATHHANDLE_H #include #include class EnhancedPathShape; class EnhancedPathParameter; class KoShapeSavingContext; class KoShapeLoadingContext; /** * An interaction handle used by the EnhancedPathShape for * changing the shape interactively. */ class EnhancedPathHandle { public: /** * Constructs a new empty handle; * * Note that an empty handle is not valid, as long as there are no * positional parameters set with setPosition. */ explicit EnhancedPathHandle(EnhancedPathShape *parent); /// Destroys the handle ~EnhancedPathHandle(); /** * Evaluates the position of the handle. * @return the actual handle position */ QPointF position(); /** * Attempts to changes the position of the handle. * Only the coordinates of the handle which reference a modifier * can be changed. The new position is automatically stored into * the modifier of the given enhanced path. * * @param position the new position the handle to set * @param path the enhanced path the handle is referenced from */ void changePosition(const QPointF &position); /// Returns if the handle has valid positional parameters.S bool hasPosition() const; /** * Sets the positional parameters, making the handle valid. * * It replaces the actual positional parameters. * * @param positionX the x-coordinate of the handle position * @param positionY the y-coordinate of the handle position */ void setPosition(EnhancedPathParameter *positionX, EnhancedPathParameter *positionY); /** * Sets the range of the handles x-coordinate. * * A zero pointer has the effect of no maximum/minimum value. * * @param minX the minimum x-coordinate * @param maxX the maximum x-coordinate */ void setRangeX(EnhancedPathParameter *minX, EnhancedPathParameter *maxX); /** * Sets the range of the handles y-coordinate. * * A zero pointer has the effect of no maximum/minimum value. * * @param minY the minimum y-coordinate * @param maxY the maximum y-coordinate */ void setRangeY(EnhancedPathParameter *minY, EnhancedPathParameter *maxY); /** * Sets the center of a polar handle. * * If both parameters are valid pointers, then the handle behaves like * a polar handle. This means the x-coordinate of the position represents * an angle in degree and the y-coordinate a radius. * * @param polarX the polar center x-coordinate * @param polarY the polar center y-coordinate */ void setPolarCenter(EnhancedPathParameter *polarX, EnhancedPathParameter *polarY); /** * Sets the range of the radius for polar handles. * @param minRadius the minimum polar radius * @param maxRadius the maximum polar radius */ void setRadiusRange(EnhancedPathParameter *minRadius, EnhancedPathParameter *maxRadius); - /// save to the given shape saving context - void saveOdf(KoShapeSavingContext &context) const; - /// load handle from given element - bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context); private: /// Returns if handle is polar bool isPolar() const; EnhancedPathShape *m_parent; ///< the enhanced path shape owning this handle EnhancedPathParameter *m_positionX; ///< the position x-coordinate EnhancedPathParameter *m_positionY; ///< the position y-coordinate EnhancedPathParameter *m_minimumX; ///< the minimum x-coordinate EnhancedPathParameter *m_minimumY; ///< the minmum y-coordinate EnhancedPathParameter *m_maximumX; ///< the maximum x-coordinate EnhancedPathParameter *m_maximumY; ///< the maximum y-coordinate EnhancedPathParameter *m_polarX; ///< the polar center x-coordinate EnhancedPathParameter *m_polarY; ///< the polar center y-coordinate EnhancedPathParameter *m_minRadius; ///< the minimum polar radius EnhancedPathParameter *m_maxRadius; ///< the maximum polar radius }; #endif // KOENHANCEDPATHHANDLE_H diff --git a/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.cpp b/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.cpp index ca9161e214..8502048819 100644 --- a/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.cpp +++ b/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.cpp @@ -1,741 +1,558 @@ /* This file is part of the KDE project * Copyright (C) 2007,2010,2011 Jan Hambrecht * Copyright (C) 2009-2010 Thomas Zander * Copyright (C) 2010 Carlos Licea * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Contact: Suresh Chande suresh.chande@nokia.com * Copyright (C) 2009-2010 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. */ #include #include "EnhancedPathShape.h" #include "EnhancedPathCommand.h" #include "EnhancedPathParameter.h" #include "EnhancedPathHandle.h" #include "EnhancedPathFormula.h" #include #include #include #include #include #include -#include #include EnhancedPathShape::EnhancedPathShape(const QRect &viewBox) : m_viewBox(viewBox) , m_viewBoxOffset(0.0, 0.0) , m_mirrorVertically(false) , m_mirrorHorizontally(false) , m_pathStretchPointX(-1) , m_pathStretchPointY(-1) , m_cacheResults(false) { } EnhancedPathShape::EnhancedPathShape(const EnhancedPathShape &rhs) : KoParameterShape(rhs), m_viewBox(rhs.m_viewBox), m_viewBound(rhs.m_viewBound), m_viewMatrix(rhs.m_viewMatrix), m_mirrorMatrix(rhs.m_mirrorMatrix), m_viewBoxOffset(rhs.m_viewBoxOffset), m_textArea(rhs.m_textArea), m_commands(rhs.m_commands), m_enhancedHandles(rhs.m_enhancedHandles), m_formulae(rhs.m_formulae), m_modifiers(rhs.m_modifiers), m_parameters(rhs.m_parameters), m_mirrorVertically(rhs.m_mirrorVertically), m_mirrorHorizontally(rhs.m_mirrorHorizontally), m_pathStretchPointX(rhs.m_pathStretchPointX), m_pathStretchPointY(rhs.m_pathStretchPointY), m_resultCache(rhs.m_resultCache), m_cacheResults(rhs.m_cacheResults) { } EnhancedPathShape::~EnhancedPathShape() { reset(); } KoShape *EnhancedPathShape::cloneShape() const { return new EnhancedPathShape(*this); } void EnhancedPathShape::reset() { qDeleteAll(m_commands); m_commands.clear(); qDeleteAll(m_enhancedHandles); m_enhancedHandles.clear(); setHandles(QList()); qDeleteAll(m_formulae); m_formulae.clear(); qDeleteAll(m_parameters); m_parameters.clear(); m_modifiers.clear(); m_viewMatrix.reset(); m_viewBoxOffset = QPointF(); clear(); m_textArea.clear(); } void EnhancedPathShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); EnhancedPathHandle *handle = m_enhancedHandles[ handleId ]; if (handle) { handle->changePosition(shapeToViewbox(point)); } } void EnhancedPathShape::updatePath(const QSizeF &size) { if (isParametricShape()) { clear(); enableResultCache(true); foreach (EnhancedPathCommand *cmd, m_commands) { cmd->execute(); } enableResultCache(false); qreal stretchPointsScale = 1; bool isStretched = useStretchPoints(size, stretchPointsScale); m_viewBound = outline().boundingRect(); m_mirrorMatrix.reset(); m_mirrorMatrix.translate(m_viewBound.center().x(), m_viewBound.center().y()); m_mirrorMatrix.scale(m_mirrorHorizontally ? -1 : 1, m_mirrorVertically ? -1 : 1); m_mirrorMatrix.translate(-m_viewBound.center().x(), -m_viewBound.center().y()); QTransform matrix(1.0, 0.0, 0.0, 1.0, m_viewBoxOffset.x(), m_viewBoxOffset.y()); // if stretch points are set than stretch the path manually if (isStretched) { //if the path was stretched manually the stretch matrix is not more valid //and it has to be recalculated so that stretching in x and y direction is the same matrix.scale(stretchPointsScale, stretchPointsScale); matrix = m_mirrorMatrix * matrix; } else { matrix = m_mirrorMatrix * m_viewMatrix * matrix; } foreach (KoSubpath *subpath, subpaths()) { foreach (KoPathPoint *point, *subpath) { point->map(matrix); } } const int handleCount = m_enhancedHandles.count(); QList handles; for (int i = 0; i < handleCount; ++i) { handles.append(matrix.map(m_enhancedHandles[i]->position())); } setHandles(handles); normalize(); } } void EnhancedPathShape::setSize(const QSizeF &newSize) { // handle offset KoParameterShape::setSize(newSize); // calculate scaling factors from viewbox size to shape size qreal xScale = m_viewBound.width() == 0 ? 1 : newSize.width() / m_viewBound.width(); qreal yScale = m_viewBound.height() == 0 ? 1 : newSize.height() / m_viewBound.height(); // create view matrix, take mirroring into account m_viewMatrix.reset(); m_viewMatrix.scale(xScale, yScale); updatePath(newSize); } QPointF EnhancedPathShape::normalize() { QPointF offset = KoParameterShape::normalize(); m_viewBoxOffset -= offset; return offset; } QPointF EnhancedPathShape::shapeToViewbox(const QPointF &point) const { return (m_mirrorMatrix * m_viewMatrix).inverted().map(point - m_viewBoxOffset); } void EnhancedPathShape::evaluateHandles() { const int handleCount = m_enhancedHandles.count(); QList handles; for (int i = 0; i < handleCount; ++i) { handles.append(m_enhancedHandles[i]->position()); } setHandles(handles); } QRect EnhancedPathShape::viewBox() const { return m_viewBox; } qreal EnhancedPathShape::evaluateReference(const QString &reference) { if (reference.isEmpty()) { return 0.0; } const char c = reference[0].toLatin1(); qreal res = 0.0; switch (c) { // referenced modifier case '$': { bool success = false; int modifierIndex = reference.mid(1).toInt(&success); res = m_modifiers.value(modifierIndex); break; } // referenced formula case '?': { QString fname = reference.mid(1); if (m_cacheResults && m_resultCache.contains(fname)) { res = m_resultCache.value(fname); } else { FormulaStore::const_iterator formulaIt = m_formulae.constFind(fname); if (formulaIt != m_formulae.constEnd()) { EnhancedPathFormula *formula = formulaIt.value(); if (formula) { res = formula->evaluate(); if (m_cacheResults) { m_resultCache.insert(fname, res); } } } } break; } // maybe an identifier ? default: EnhancedPathNamedParameter p(reference, this); res = p.evaluate(); break; } return res; } qreal EnhancedPathShape::evaluateConstantOrReference(const QString &val) { bool ok = true; qreal res = val.toDouble(&ok); if (ok) { return res; } return evaluateReference(val); } void EnhancedPathShape::modifyReference(const QString &reference, qreal value) { if (reference.isEmpty()) { return; } const char c = reference[0].toLatin1(); if (c == '$') { bool success = false; int modifierIndex = reference.mid(1).toInt(&success); if (modifierIndex >= 0 && modifierIndex < m_modifiers.count()) { m_modifiers[modifierIndex] = value; } } } EnhancedPathParameter *EnhancedPathShape::parameter(const QString &text) { Q_ASSERT(! text.isEmpty()); ParameterStore::const_iterator parameterIt = m_parameters.constFind(text); if (parameterIt != m_parameters.constEnd()) { return parameterIt.value(); } else { EnhancedPathParameter *parameter = 0; const char c = text[0].toLatin1(); if (c == '$' || c == '?') { parameter = new EnhancedPathReferenceParameter(text, this); } else { bool success = false; qreal constant = text.toDouble(&success); if (success) { parameter = new EnhancedPathConstantParameter(constant, this); } else { Identifier identifier = EnhancedPathNamedParameter::identifierFromString(text); if (identifier != IdentifierUnknown) { parameter = new EnhancedPathNamedParameter(identifier, this); } } } if (parameter) { m_parameters[text] = parameter; } return parameter; } } void EnhancedPathShape::addFormula(const QString &name, const QString &formula) { if (name.isEmpty() || formula.isEmpty()) { return; } m_formulae[name] = new EnhancedPathFormula(formula, this); } void EnhancedPathShape::addHandle(const QMap &handle) { if (handle.isEmpty()) { return; } if (!handle.contains("draw:handle-position")) { return; } QVariant position = handle.value("draw:handle-position"); QStringList tokens = position.toString().simplified().split(' '); if (tokens.count() < 2) { return; } EnhancedPathHandle *newHandle = new EnhancedPathHandle(this); newHandle->setPosition(parameter(tokens[0]), parameter(tokens[1])); // check if we have a polar handle if (handle.contains("draw:handle-polar")) { QVariant polar = handle.value("draw:handle-polar"); QStringList tokens = polar.toString().simplified().split(' '); if (tokens.count() == 2) { newHandle->setPolarCenter(parameter(tokens[0]), parameter(tokens[1])); QVariant minRadius = handle.value("draw:handle-radius-range-minimum"); QVariant maxRadius = handle.value("draw:handle-radius-range-maximum"); if (minRadius.isValid() && maxRadius.isValid()) { newHandle->setRadiusRange(parameter(minRadius.toString()), parameter(maxRadius.toString())); } } } else { QVariant minX = handle.value("draw:handle-range-x-minimum"); QVariant maxX = handle.value("draw:handle-range-x-maximum"); if (minX.isValid() && maxX.isValid()) { newHandle->setRangeX(parameter(minX.toString()), parameter(maxX.toString())); } QVariant minY = handle.value("draw:handle-range-y-minimum"); QVariant maxY = handle.value("draw:handle-range-y-maximum"); if (minY.isValid() && maxY.isValid()) { newHandle->setRangeY(parameter(minY.toString()), parameter(maxY.toString())); } } m_enhancedHandles.append(newHandle); evaluateHandles(); } void EnhancedPathShape::addModifiers(const QString &modifiers) { if (modifiers.isEmpty()) { return; } QStringList tokens = modifiers.simplified().split(' '); int tokenCount = tokens.count(); for (int i = 0; i < tokenCount; ++i) { m_modifiers.append(tokens[i].toDouble()); } } void EnhancedPathShape::addCommand(const QString &command) { addCommand(command, true); } void EnhancedPathShape::addCommand(const QString &command, bool triggerUpdate) { QString commandStr = command.simplified(); if (commandStr.isEmpty()) { return; } // the first character is the command EnhancedPathCommand *cmd = new EnhancedPathCommand(commandStr[0], this); // strip command char commandStr = commandStr.mid(1).simplified(); // now parse the command parameters if (!commandStr.isEmpty()) { QStringList tokens = commandStr.split(' '); for (int i = 0; i < tokens.count(); ++i) { cmd->addParameter(parameter(tokens[i])); } } m_commands.append(cmd); if (triggerUpdate) { updatePath(size()); } } bool EnhancedPathShape::useStretchPoints(const QSizeF &size, qreal &scale) { bool retval = false; if (m_pathStretchPointX != -1 && m_pathStretchPointY != -1) { qreal scaleX = size.width(); qreal scaleY = size.height(); if (qreal(m_viewBox.width()) / m_viewBox.height() < qreal(scaleX) / scaleY) { qreal deltaX = (scaleX * m_viewBox.height()) / scaleY - m_viewBox.width(); foreach (KoSubpath *subpath, subpaths()) { foreach (KoPathPoint *currPoint, *subpath) { if (currPoint->point().x() >= m_pathStretchPointX && currPoint->controlPoint1().x() >= m_pathStretchPointX && currPoint->controlPoint2().x() >= m_pathStretchPointX) { currPoint->setPoint(QPointF(currPoint->point().x() + deltaX, currPoint->point().y())); currPoint->setControlPoint1(QPointF(currPoint->controlPoint1().x() + deltaX, currPoint->controlPoint1().y())); currPoint->setControlPoint2(QPointF(currPoint->controlPoint2().x() + deltaX, currPoint->controlPoint2().y())); retval = true; } } } scale = scaleY / m_viewBox.height(); } else if (qreal(m_viewBox.width()) / m_viewBox.height() > qreal(scaleX) / scaleY) { qreal deltaY = (m_viewBox.width() * scaleY) / scaleX - m_viewBox.height(); foreach (KoSubpath *subpath, subpaths()) { foreach (KoPathPoint *currPoint, *subpath) { if (currPoint->point().y() >= m_pathStretchPointY && currPoint->controlPoint1().y() >= m_pathStretchPointY && currPoint->controlPoint2().y() >= m_pathStretchPointY) { currPoint->setPoint(QPointF(currPoint->point().x(), currPoint->point().y() + deltaY)); currPoint->setControlPoint1(QPointF(currPoint->controlPoint1().x(), currPoint->controlPoint1().y() + deltaY)); currPoint->setControlPoint2(QPointF(currPoint->controlPoint2().x(), currPoint->controlPoint2().y() + deltaY)); retval = true; } } } scale = scaleX / m_viewBox.width(); } notifyPointsChanged(); } return retval; } -void EnhancedPathShape::saveOdf(KoShapeSavingContext &context) const -{ - if (isParametricShape()) { - context.xmlWriter().startElement("draw:custom-shape"); - - const QSizeF currentSize = outline().boundingRect().size(); - - // save the right position so that when loading we fit the viewbox - // to the right position without getting any wrong scaling - // -> calculate the right position from the current 0 position / viewbound ratio - // this is e.g. the case when there is a callout that goes into negative viewbound coordinates - QPointF topLeft = m_viewBound.topLeft(); - QPointF diff; - if (qAbs(topLeft.x()) > 1E-5) { - diff.setX(topLeft.x()*currentSize.width() / m_viewBound.width()); - } - if (qAbs(topLeft.y()) > 1E-5) { - diff.setY(topLeft.y()*currentSize.height() / m_viewBound.height()); - } - - if (diff.isNull()) { - saveOdfAttributes(context, OdfAllAttributes & ~OdfSize); - } else { - //FIXME: this needs to be fixed for shapes that are transformed by rotation or skewing - QTransform offset(context.shapeOffset(this)); - QTransform newOffset(offset); - newOffset.translate(-diff.x(), -diff.y()); - context.addShapeOffset(this, newOffset); - saveOdfAttributes(context, OdfAllAttributes & ~OdfSize); - if (offset.isIdentity()) { - context.removeShapeOffset(this); - } else { - context.addShapeOffset(this, offset); - } - } - - // save the right size so that when loading we fit the viewbox - // to the right size without getting any wrong scaling - // -> calculate the right size from the current size/viewbound ratio - context.xmlWriter().addAttribute("svg:width", currentSize.width() == 0 ? 0 : m_viewBox.width()*currentSize.width() / m_viewBound.width()); - context.xmlWriter().addAttribute("svg:height", currentSize.height() == 0 ? 0 : m_viewBox.height()*currentSize.height() / m_viewBound.height()); - - saveText(context); - - context.xmlWriter().startElement("draw:enhanced-geometry"); - context.xmlWriter().addAttribute("svg:viewBox", QString("%1 %2 %3 %4").arg(m_viewBox.x()).arg(m_viewBox.y()).arg(m_viewBox.width()).arg(m_viewBox.height())); - - if (m_pathStretchPointX != -1) { - context.xmlWriter().addAttribute("draw:path-stretchpoint-x", m_pathStretchPointX); - } - if (m_pathStretchPointY != -1) { - context.xmlWriter().addAttribute("draw:path-stretchpoint-y", m_pathStretchPointY); - } - - if (m_mirrorHorizontally) { - context.xmlWriter().addAttribute("draw:mirror-horizontal", "true"); - } - if (m_mirrorVertically) { - context.xmlWriter().addAttribute("draw:mirror-vertical", "true"); - } - - QString modifiers; - foreach (qreal modifier, m_modifiers) { - modifiers += QString::number(modifier) + ' '; - } - context.xmlWriter().addAttribute("draw:modifiers", modifiers.trimmed()); - - if (m_textArea.size() >= 4) { - context.xmlWriter().addAttribute("draw:text-areas", m_textArea.join(" ")); - } - - QString path; - foreach (EnhancedPathCommand *c, m_commands) { - path += c->toString() + ' '; - } - context.xmlWriter().addAttribute("draw:enhanced-path", path.trimmed()); - - FormulaStore::const_iterator i = m_formulae.constBegin(); - for (; i != m_formulae.constEnd(); ++i) { - context.xmlWriter().startElement("draw:equation"); - context.xmlWriter().addAttribute("draw:name", i.key()); - context.xmlWriter().addAttribute("draw:formula", i.value()->toString()); - context.xmlWriter().endElement(); // draw:equation - } - - foreach (EnhancedPathHandle *handle, m_enhancedHandles) { - handle->saveOdf(context); - } - - context.xmlWriter().endElement(); // draw:enhanced-geometry - context.xmlWriter().endElement(); // draw:custom-shape - - } else { - KoPathShape::saveOdf(context); - } -} - -bool EnhancedPathShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) -{ - reset(); - - const KoXmlElement enhancedGeometry(KoXml::namedItemNS(element, KoXmlNS::draw, "enhanced-geometry")); - if (!enhancedGeometry.isNull()) { - - setPathStretchPointX(enhancedGeometry.attributeNS(KoXmlNS::draw, "path-stretchpoint-x", "-1").toDouble()); - setPathStretchPointY(enhancedGeometry.attributeNS(KoXmlNS::draw, "path-stretchpoint-y", "-1").toDouble()); - - // load the modifiers - QString modifiers = enhancedGeometry.attributeNS(KoXmlNS::draw, "modifiers", ""); - if (!modifiers.isEmpty()) { - addModifiers(modifiers); - } - - m_textArea = enhancedGeometry.attributeNS(KoXmlNS::draw, "text-areas", "").split(' '); - if (m_textArea.size() >= 4) { - setResizeBehavior(TextFollowsPreferredTextRect); - } - - KoXmlElement grandChild; - forEachElement(grandChild, enhancedGeometry) { - if (grandChild.namespaceURI() != KoXmlNS::draw) { - continue; - } - if (grandChild.localName() == "equation") { - QString name = grandChild.attributeNS(KoXmlNS::draw, "name"); - QString formula = grandChild.attributeNS(KoXmlNS::draw, "formula"); - addFormula(name, formula); - } else if (grandChild.localName() == "handle") { - EnhancedPathHandle *handle = new EnhancedPathHandle(this); - if (handle->loadOdf(grandChild, context)) { - m_enhancedHandles.append(handle); - evaluateHandles(); - } else { - delete handle; - } - } - - } - - setMirrorHorizontally(enhancedGeometry.attributeNS(KoXmlNS::draw, "mirror-horizontal") == "true"); - setMirrorVertically(enhancedGeometry.attributeNS(KoXmlNS::draw, "mirror-vertical") == "true"); - - // load the enhanced path data - QString path = enhancedGeometry.attributeNS(KoXmlNS::draw, "enhanced-path", ""); -#ifndef NWORKAROUND_ODF_BUGS - KoOdfWorkaround::fixEnhancedPath(path, enhancedGeometry, context); -#endif - // load the viewbox - m_viewBox = loadOdfViewbox(enhancedGeometry); - - if (!path.isEmpty()) { - parsePathData(path); - } - - if (m_viewBox.isEmpty()) { - // if there is no view box defined make it is big as the path. - m_viewBox = m_viewBound.toAlignedRect(); - } - - } - - QSizeF size; - size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); - size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); - // the viewbox is to be fitted into the size of the shape, so before setting - // the size we just loaded // we set the viewbox to be the basis to calculate - // the viewbox matrix from - m_viewBound = m_viewBox; - setSize(size); - - QPointF pos; - pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); - pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); - setPosition(pos - m_viewMatrix.map(QPointF(0, 0)) - m_viewBoxOffset); - - loadOdfAttributes(element, context, OdfMandatories | OdfTransformation | OdfAdditionalAttributes); - - loadText(element, context); - - return true; -} - void EnhancedPathShape::parsePathData(const QString &data) { if (data.isEmpty()) { return; } int start = -1; bool separator = true; for (int i = 0; i < data.length(); ++i) { QChar ch = data.at(i); ushort uni_ch = ch.unicode(); if (separator && (uni_ch == 'M' || uni_ch == 'L' || uni_ch == 'C' || uni_ch == 'Z' || uni_ch == 'N' || uni_ch == 'F' || uni_ch == 'S' || uni_ch == 'T' || uni_ch == 'U' || uni_ch == 'A' || uni_ch == 'B' || uni_ch == 'W' || uni_ch == 'V' || uni_ch == 'X' || uni_ch == 'Y' || uni_ch == 'Q')) { if (start != -1) { // process last chars addCommand(data.mid(start, i - start), false); } start = i; } separator = ch.isSpace(); } if (start < data.length()) { addCommand(data.mid(start)); } if (start != -1) { updatePath(size()); } } void EnhancedPathShape::setMirrorHorizontally(bool mirrorHorizontally) { if (m_mirrorHorizontally != mirrorHorizontally) { m_mirrorHorizontally = mirrorHorizontally; updatePath(size()); } } void EnhancedPathShape::setMirrorVertically(bool mirrorVertically) { if (m_mirrorVertically != mirrorVertically) { m_mirrorVertically = mirrorVertically; updatePath(size()); } } void EnhancedPathShape::shapeChanged(ChangeType type, KoShape *shape) { KoParameterShape::shapeChanged(type, shape); if (!shape || shape == this) { if (type == ParentChanged || type == ParameterChanged) { updateTextArea(); } } } void EnhancedPathShape::updateTextArea() { if (m_textArea.size() >= 4) { QRectF r = m_viewBox; r.setLeft(evaluateConstantOrReference(m_textArea[0])); r.setTop(evaluateConstantOrReference(m_textArea[1])); r.setRight(evaluateConstantOrReference(m_textArea[2])); r.setBottom(evaluateConstantOrReference(m_textArea[3])); r = m_viewMatrix.mapRect(r).translated(m_viewBoxOffset); setPreferredTextRect(r); } } void EnhancedPathShape::enableResultCache(bool enable) { m_resultCache.clear(); m_cacheResults = enable; } void EnhancedPathShape::setPathStretchPointX(qreal pathStretchPointX) { if (m_pathStretchPointX != pathStretchPointX) { m_pathStretchPointX = pathStretchPointX; } } void EnhancedPathShape::setPathStretchPointY(qreal pathStretchPointY) { if (m_pathStretchPointY != pathStretchPointY) { m_pathStretchPointY = pathStretchPointY; } } diff --git a/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.h b/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.h index 46d74f9b8e..2ee916238d 100644 --- a/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.h +++ b/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.h @@ -1,185 +1,181 @@ /* This file is part of the KDE project * Copyright (C) 2007,2010,2011 Jan Hambrecht * Copyright (C) 2010 Carlos Licea * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Contact: Suresh Chande suresh.chande@nokia.com * * 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 KOENHANCEDPATHSHAPE_H #define KOENHANCEDPATHSHAPE_H #include #include #include #include #include #define EnhancedPathShapeId "EnhancedPathShape" class EnhancedPathCommand; class EnhancedPathHandle; class EnhancedPathFormula; class EnhancedPathParameter; class KoShapeSavingContext; class KoShapeLoadingContext; /** * An enhanced shape is a custom shape which can be defined * by enhanced geometry data. * The data consists of a list of commands like moveto, * lineto, curveto, etc. which are used to create the outline * of the shape. The coordinates or parameters of the commands * can be constant values, named variables (identifiers), * modifiers, functions or formulae. */ class EnhancedPathShape : public KoParameterShape { public: EnhancedPathShape(const QRect &viewBox); ~EnhancedPathShape() override; KoShape* cloneShape() const override; /** * Evaluates the given reference to a identifier, modifier or formula. * @param reference the reference to evaluate * @return the result of the evaluation */ qreal evaluateReference(const QString &reference); /** * Evaluates the given constant or reference to a identifier, modifier * or formula. * @param val the value to evaluate * @return the result of the evaluation */ qreal evaluateConstantOrReference(const QString &val); /** * Attempts to modify a given reference. * * Only modifiers can me modified, others silently ignore the attempt. * * @param reference the reference to modify * @param value the new value */ void modifyReference(const QString &reference, qreal value); // from KoShape void setSize(const QSizeF &newSize) override; // from KoParameterShape QPointF normalize() override; /// Add formula with given name and textual representation void addFormula(const QString &name, const QString &formula); /// Add a single handle with format: x y minX maxX minY maxY void addHandle(const QMap &handle); /// Add modifiers with format: modifier0 modifier1 modifier2 ... void addModifiers(const QString &modifiers); /// Add command for instance "M 0 0" void addCommand(const QString &command); /// Returns the viewbox of the enhanced path shape QRect viewBox() const; /// Converts from shape coordinates to viewbox coordinates QPointF shapeToViewbox(const QPointF &point) const; /// Sets if the shape is to be mirrored horizontally before applying any other transformations //NOTE: in the standard nothing is mentioned about the priorities of the transformations" //it's assumed like this because of the behavior shwon in OOo void setMirrorHorizontally(bool mirrorHorizontally); /// Sets if the shape is to be mirrored vertically before applying any other transformations //NOTE: in the standard nothing is mentioned about the priorities of the transformations" //it's assumed like this because of the behavior shown in OOo void setMirrorVertically(bool mirrorVertically); // Sets member variable representing draw:path-stretchpoint-x attribute void setPathStretchPointX(qreal pathStretchPointX); // Sets member variable representing draw:path-stretchpoint-y attribute void setPathStretchPointY(qreal pathStretchPointY); /// Returns parameter from given textual representation EnhancedPathParameter *parameter(const QString &text); protected: - // from KoShape - void saveOdf(KoShapeSavingContext &context) const override; - // from KoShape - bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override; //from KoShape void shapeChanged(ChangeType type, KoShape *shape = 0) override; // from KoParameterShape void moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers = Qt::NoModifier) override; // from KoParameterShape void updatePath(const QSizeF &size) override; private: EnhancedPathShape(const EnhancedPathShape &rhs); void evaluateHandles(); void reset(); /// parses the enhanced path data void parsePathData(const QString &data); /// Adds a new command void addCommand(const QString &command, bool triggerUpdate); /// Updates the size and position of an optionally existing text-on-shape text area void updateTextArea(); /// Enables caching results void enableResultCache(bool enable); // This function checks if draw:path-stretchpoint-x or draw:path-stretchpoint-y attributes are set. // If the attributes are set the path shape coordinates (m_subpaths) are changed so that the form // of the shape is preserved after stretching. It is needed for example in round-rectangles, to // have the corners round after stretching. Without it the corners would be elliptical. // Returns true if any points were actually changed, otherwise false. bool useStretchPoints(const QSizeF &size, qreal &scale); typedef QMap FormulaStore; typedef QList ModifierStore; typedef QMap ParameterStore; QRect m_viewBox; ///< the viewbox rectangle QRectF m_viewBound; ///< the bounding box of the path in viewbox coordinates QTransform m_viewMatrix; ///< matrix to convert from viewbox coordinates to shape coordinates QTransform m_mirrorMatrix; ///< matrix to used for mirroring QPointF m_viewBoxOffset; QStringList m_textArea; QList m_commands; ///< the commands creating the outline QList m_enhancedHandles; ///< the handles for modifying the shape FormulaStore m_formulae; ///< the formulae ModifierStore m_modifiers; ///< the modifier values ParameterStore m_parameters; ///< the shared parameters bool m_mirrorVertically; /// m_resultCache; ///< cache for intermediate results used when evaluating path bool m_cacheResults; ///< indicates if result cache is enabled }; #endif // KOENHANCEDPATHSHAPE_H diff --git a/plugins/flake/pathshapes/rectangle/RectangleShape.cpp b/plugins/flake/pathshapes/rectangle/RectangleShape.cpp index 8b5a99aa4c..010e5d0d41 100644 --- a/plugins/flake/pathshapes/rectangle/RectangleShape.cpp +++ b/plugins/flake/pathshapes/rectangle/RectangleShape.cpp @@ -1,383 +1,340 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2008 Jan Hambrecht 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 "RectangleShape.h" #include #include #include #include #include #include #include #include #include #include #include RectangleShape::RectangleShape() : KoParameterShape() , m_cornerRadiusX(0) , m_cornerRadiusY(0) { QList handles; handles.push_back(QPointF(100, 0)); handles.push_back(QPointF(100, 0)); setHandles(handles); QSizeF size(100, 100); updatePath(size); } RectangleShape::RectangleShape(const RectangleShape &rhs) : KoParameterShape(rhs), m_cornerRadiusX(rhs.m_cornerRadiusX), m_cornerRadiusY(rhs.m_cornerRadiusY) { } RectangleShape::~RectangleShape() { } KoShape *RectangleShape::cloneShape() const { return new RectangleShape(*this); } -bool RectangleShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) -{ - loadOdfAttributes(element, context, OdfMandatories | OdfGeometry | OdfAdditionalAttributes); - - if (element.hasAttributeNS(KoXmlNS::svg, "rx") && element.hasAttributeNS(KoXmlNS::svg, "ry")) { - qreal rx = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "rx", "0")); - qreal ry = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "ry", "0")); - m_cornerRadiusX = rx / (0.5 * size().width()) * 100; - m_cornerRadiusY = ry / (0.5 * size().height()) * 100; - } else { - QString cornerRadius = element.attributeNS(KoXmlNS::draw, "corner-radius", ""); - if (!cornerRadius.isEmpty()) { - qreal radius = KoUnit::parseValue(cornerRadius); - m_cornerRadiusX = qMin(radius / (0.5 * size().width()) * 100, qreal(100)); - m_cornerRadiusY = qMin(radius / (0.5 * size().height()) * 100, qreal(100)); - } - } - - updatePath(size()); - updateHandles(); - - loadOdfAttributes(element, context, OdfTransformation); - loadText(element, context); - - return true; -} - -void RectangleShape::saveOdf(KoShapeSavingContext &context) const -{ - if (isParametricShape()) { - context.xmlWriter().startElement("draw:rect"); - saveOdfAttributes(context, OdfAllAttributes); - if (m_cornerRadiusX > 0 && m_cornerRadiusY > 0) { - context.xmlWriter().addAttribute("svg:rx", m_cornerRadiusX * (0.5 * size().width()) / 100.0); - context.xmlWriter().addAttribute("svg:ry", m_cornerRadiusY * (0.5 * size().height()) / 100.0); - } - saveText(context); - context.xmlWriter().endElement(); - } else { - KoPathShape::saveOdf(context); - } -} - void RectangleShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); QPointF p(point); qreal width2 = size().width() / 2.0; qreal height2 = size().height() / 2.0; switch (handleId) { case 0: if (p.x() < width2) { p.setX(width2); } else if (p.x() > size().width()) { p.setX(size().width()); } p.setY(0); m_cornerRadiusX = (size().width() - p.x()) / width2 * 100.0; if (!(modifiers & Qt::ControlModifier)) { m_cornerRadiusY = (size().width() - p.x()) / height2 * 100.0; } break; case 1: if (p.y() < 0) { p.setY(0); } else if (p.y() > height2) { p.setY(height2); } p.setX(size().width()); m_cornerRadiusY = p.y() / height2 * 100.0; if (!(modifiers & Qt::ControlModifier)) { m_cornerRadiusX = p.y() / width2 * 100.0; } break; } // this is needed otherwise undo/redo might not end in the same result if (100 - m_cornerRadiusX < 1e-10) { m_cornerRadiusX = 100; } if (100 - m_cornerRadiusY < 1e-10) { m_cornerRadiusY = 100; } updateHandles(); } void RectangleShape::updateHandles() { QList handles; handles.append(QPointF(size().width() - m_cornerRadiusX / 100.0 * 0.5 * size().width(), 0.0)); handles.append(QPointF(size().width(), m_cornerRadiusY / 100.0 * 0.5 * size().height())); setHandles(handles); } void RectangleShape::updatePath(const QSizeF &size) { qreal rx = 0; qreal ry = 0; if (m_cornerRadiusX > 0 && m_cornerRadiusY > 0) { rx = size.width() / 200.0 * m_cornerRadiusX; ry = size.height() / 200.0 * m_cornerRadiusY; } qreal x2 = size.width() - rx; qreal y2 = size.height() - ry; QPointF curvePoints[12]; int requiredCurvePointCount = 4; if (rx && m_cornerRadiusX < 100) { requiredCurvePointCount += 2; } if (ry && m_cornerRadiusY < 100) { requiredCurvePointCount += 2; } createPoints(requiredCurvePointCount); KoSubpath &points = *subpaths()[0]; int cp = 0; // first path starts and closes path points[cp]->setProperty(KoPathPoint::StartSubpath); points[cp]->setProperty(KoPathPoint::CloseSubpath); points[cp]->setPoint(QPointF(rx, 0)); points[cp]->removeControlPoint1(); points[cp]->removeControlPoint2(); if (m_cornerRadiusX < 100 || m_cornerRadiusY == 0) { // end point of the top edge points[++cp]->setPoint(QPointF(x2, 0)); points[cp]->removeControlPoint1(); points[cp]->removeControlPoint2(); } if (rx) { // the top right radius arcToCurve(rx, ry, 90, -90, points[cp]->point(), curvePoints); points[cp]->setControlPoint2(curvePoints[0]); points[++cp]->setControlPoint1(curvePoints[1]); points[cp]->setPoint(curvePoints[2]); points[cp]->removeControlPoint2(); } if (m_cornerRadiusY < 100 || m_cornerRadiusX == 0) { // the right edge points[++cp]->setPoint(QPointF(size.width(), y2)); points[cp]->removeControlPoint1(); points[cp]->removeControlPoint2(); } if (rx) { // the bottom right radius arcToCurve(rx, ry, 0, -90, points[cp]->point(), curvePoints); points[cp]->setControlPoint2(curvePoints[0]); points[++cp]->setControlPoint1(curvePoints[1]); points[cp]->setPoint(curvePoints[2]); points[cp]->removeControlPoint2(); } if (m_cornerRadiusX < 100 || m_cornerRadiusY == 0) { // the bottom edge points[++cp]->setPoint(QPointF(rx, size.height())); points[cp]->removeControlPoint1(); points[cp]->removeControlPoint2(); } if (rx) { // the bottom left radius arcToCurve(rx, ry, 270, -90, points[cp]->point(), curvePoints); points[cp]->setControlPoint2(curvePoints[0]); points[++cp]->setControlPoint1(curvePoints[1]); points[cp]->setPoint(curvePoints[2]); points[cp]->removeControlPoint2(); } if ((m_cornerRadiusY < 100 || m_cornerRadiusX == 0) && ry) { // the right edge points[++cp]->setPoint(QPointF(0, ry)); points[cp]->removeControlPoint1(); points[cp]->removeControlPoint2(); } if (rx) { // the top left radius arcToCurve(rx, ry, 180, -90, points[cp]->point(), curvePoints); points[cp]->setControlPoint2(curvePoints[0]); points[0]->setControlPoint1(curvePoints[1]); points[0]->setPoint(curvePoints[2]); } // unset all stop/close path properties for (int i = 1; i < cp; ++i) { points[i]->unsetProperty(KoPathPoint::StopSubpath); points[i]->unsetProperty(KoPathPoint::CloseSubpath); } // last point stops and closes path points.last()->setProperty(KoPathPoint::StopSubpath); points.last()->setProperty(KoPathPoint::CloseSubpath); notifyPointsChanged(); } void RectangleShape::createPoints(int requiredPointCount) { if (subpaths().count() != 1) { clear(); subpaths().append(new KoSubpath()); } int currentPointCount = subpaths()[0]->count(); if (currentPointCount > requiredPointCount) { for (int i = 0; i < currentPointCount - requiredPointCount; ++i) { delete subpaths()[0]->front(); subpaths()[0]->pop_front(); } } else if (requiredPointCount > currentPointCount) { for (int i = 0; i < requiredPointCount - currentPointCount; ++i) { subpaths()[0]->append(new KoPathPoint(this, QPointF())); } } notifyPointsChanged(); } qreal RectangleShape::cornerRadiusX() const { return m_cornerRadiusX; } void RectangleShape::setCornerRadiusX(qreal radius) { radius = qBound(0.0, radius, 100.0); m_cornerRadiusX = radius; updatePath(size()); updateHandles(); } qreal RectangleShape::cornerRadiusY() const { return m_cornerRadiusY; } void RectangleShape::setCornerRadiusY(qreal radius) { radius = qBound(0.0, radius, 100.0); m_cornerRadiusY = radius; updatePath(size()); updateHandles(); } QString RectangleShape::pathShapeId() const { return RectangleShapeId; } bool RectangleShape::saveSvg(SvgSavingContext &context) { // let basic path saiving code handle our saving if (!isParametricShape()) return false; context.shapeWriter().startElement("rect"); context.shapeWriter().addAttribute("id", context.getID(this)); SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter()); SvgStyleWriter::saveSvgStyle(this, context); const QSizeF size = this->size(); context.shapeWriter().addAttribute("width", size.width()); context.shapeWriter().addAttribute("height", size.height()); double rx = cornerRadiusX(); if (rx > 0.0) { context.shapeWriter().addAttribute("rx", 0.01 * rx * 0.5 * size.width()); } double ry = cornerRadiusY(); if (ry > 0.0) { context.shapeWriter().addAttribute("ry", 0.01 * ry * 0.5 * size.height()); } context.shapeWriter().endElement(); return true; } bool RectangleShape::loadSvg(const KoXmlElement &element, SvgLoadingContext &context) { const qreal x = SvgUtil::parseUnitX(context.currentGC(), element.attribute("x")); const qreal y = SvgUtil::parseUnitY(context.currentGC(), element.attribute("y")); const qreal w = SvgUtil::parseUnitX(context.currentGC(), element.attribute("width")); const qreal h = SvgUtil::parseUnitY(context.currentGC(), element.attribute("height")); const QString rxStr = element.attribute("rx"); const QString ryStr = element.attribute("ry"); qreal rx = rxStr.isEmpty() ? 0.0 : SvgUtil::parseUnitX(context.currentGC(), rxStr); qreal ry = ryStr.isEmpty() ? 0.0 : SvgUtil::parseUnitY(context.currentGC(), ryStr); // if one radius is given but not the other, use the same value for both if (!rxStr.isEmpty() && ryStr.isEmpty()) { ry = rx; } if (rxStr.isEmpty() && !ryStr.isEmpty()) { rx = ry; } setSize(QSizeF(w, h)); setPosition(QPointF(x, y)); if (rx >= 0.0) { setCornerRadiusX(qMin(qreal(100.0), qreal(rx / (0.5 * w) * 100.0))); } if (ry >= 0.0) { setCornerRadiusY(qMin(qreal(100.0), qreal(ry / (0.5 * h) * 100.0))); } if (w == 0.0 || h == 0.0) { setVisible(false); } return true; } diff --git a/plugins/flake/pathshapes/rectangle/RectangleShape.h b/plugins/flake/pathshapes/rectangle/RectangleShape.h index 08c828c2b2..0264b8a9bc 100644 --- a/plugins/flake/pathshapes/rectangle/RectangleShape.h +++ b/plugins/flake/pathshapes/rectangle/RectangleShape.h @@ -1,97 +1,90 @@ /* This file is part of the KDE project Copyright (C) 2006-2007 Thorsten Zachmann Copyright (C) 2006-2007 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KORECTANGLESHAPE_H #define KORECTANGLESHAPE_H #include "KoParameterShape.h" #include #define RectangleShapeId "RectangleShape" /** * The RectangleShape is a shape that represents a rectangle. * The rectangle can have rounded corners, with different corner * radii in x- and y-direction. */ class RectangleShape : public KoParameterShape, public SvgShape { public: RectangleShape(); ~RectangleShape() override; KoShape* cloneShape() const override; /// Returns the corner radius in x-direction qreal cornerRadiusX() const; /** * Sets the corner radius in x-direction. * * The corner x-radius is a percent value (a number between 0 and 100) * of the half size of the rectangles width. * * @param radius the new corner radius in x-direction */ void setCornerRadiusX(qreal radius); /// Returns the corner radius in y-direction qreal cornerRadiusY() const; /** * Sets the corner radius in y-direction. * * The corner y-radius is a percent value (a number between 0 and 100) * of the half size of the rectangles height. * * @param radius the new corner radius in y-direction */ void setCornerRadiusY(qreal radius); - - /// reimplemented - bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override; - - /// reimplemented - void saveOdf(KoShapeSavingContext &context) const override; - /// reimplemented QString pathShapeId() const override; /// reimplemented from SvgShape bool saveSvg(SvgSavingContext &context) override; /// reimplemented from SvgShape bool loadSvg(const KoXmlElement &element, SvgLoadingContext &context) override; protected: RectangleShape(const RectangleShape &rhs); void moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers = Qt::NoModifier) override; void updatePath(const QSizeF &size) override; void createPoints(int requiredPointCount); void updateHandles(); private: qreal m_cornerRadiusX; ///< in percent of half of the rectangle width (a number between 0 and 100) qreal m_cornerRadiusY; ///< in percent of half of the rectangle height (a number between 0 and 100) }; #endif /* KORECTANGLESHAPE_H */ diff --git a/plugins/flake/pathshapes/spiral/SpiralShape.cpp b/plugins/flake/pathshapes/spiral/SpiralShape.cpp index a6b11ba06b..5232e0ec90 100644 --- a/plugins/flake/pathshapes/spiral/SpiralShape.cpp +++ b/plugins/flake/pathshapes/spiral/SpiralShape.cpp @@ -1,317 +1,303 @@ /* This file is part of the KDE project Copyright (C) 2007 Rob Buis 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 "SpiralShape.h" #include #include #include #include #include #include #include #include "kis_assert.h" SpiralShape::SpiralShape() : m_fade(.9) , m_kindAngle(M_PI) , m_radii(100.0, 100.0) , m_type(Curve) , m_clockwise(true) { //m_handles.push_back(QPointF(50, 0)); //m_handles.push_back(QPointF(50, 50)); //m_handles.push_back(QPointF(0, 50)); createPath(QSizeF(m_radii.x(), m_radii.y())); } SpiralShape::SpiralShape(const SpiralShape &rhs) : KoParameterShape(rhs), m_fade(rhs.m_fade), m_kindAngle(rhs.m_kindAngle), m_center(rhs.m_center), m_radii(rhs.m_radii), m_type(rhs.m_type), m_clockwise(rhs.m_clockwise) { Q_FOREACH(KoPathPoint *point, rhs.m_points) { KIS_ASSERT_RECOVER(point) { continue; } m_points << new KoPathPoint(*point, this); } } SpiralShape::~SpiralShape() { } KoShape *SpiralShape::cloneShape() const { return new SpiralShape(*this); } -void SpiralShape::saveOdf(KoShapeSavingContext &context) const -{ - // TODO? - KoPathShape::saveOdf(context); -} - -bool SpiralShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &/*context*/) -{ - Q_UNUSED(element); - - // TODO? - return true; -} - void SpiralShape::setSize(const QSizeF &newSize) { QTransform matrix(resizeMatrix(newSize)); m_center = matrix.map(m_center); m_radii = matrix.map(m_radii); KoParameterShape::setSize(newSize); } QPointF SpiralShape::normalize() { QPointF offset(KoParameterShape::normalize()); QTransform matrix; matrix.translate(-offset.x(), -offset.y()); m_center = matrix.map(m_center); return offset; } void SpiralShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(handleId); Q_UNUSED(point); Q_UNUSED(modifiers); #if 0 QPointF p(point); QPointF diff(m_center - point); diff.setX(-diff.x()); qreal angle = 0; if (diff.x() == 0) { angle = (diff.y() < 0 ? 270 : 90) * M_PI / 180.0; } else { diff.setY(diff.y() * m_radii.x() / m_radii.y()); angle = atan(diff.y() / diff.x()); if (angle < 0) { angle = M_PI + angle; } if (diff.y() < 0) { angle += M_PI; } } switch (handleId) { case 0: p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y())); m_handles[handleId] = p; updateKindHandle(); break; case 1: p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y())); m_handles[handleId] = p; updateKindHandle(); break; case 2: { QList kindHandlePositions; kindHandlePositions.push_back(QPointF(m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y()))); kindHandlePositions.push_back(m_center); kindHandlePositions.push_back((m_handles[0] + m_handles[1]) / 2.0); QPointF diff = m_center * 2.0; int handlePos = 0; for (int i = 0; i < kindHandlePositions.size(); ++i) { QPointF pointDiff(p - kindHandlePositions[i]); if (i == 0 || qAbs(pointDiff.x()) + qAbs(pointDiff.y()) < qAbs(diff.x()) + qAbs(diff.y())) { diff = pointDiff; handlePos = i; } } m_handles[handleId] = kindHandlePositions[handlePos]; m_type = SpiralType(handlePos); } break; } #endif } void SpiralShape::updatePath(const QSizeF &size) { createPath(size); normalize(); #if 0 Q_UNUSED(size); QPointF startpoint(m_handles[0]); QPointF curvePoints[12]; int pointCnt = arcToCurve(m_radii.x(), m_radii.y(), m_startAngle, sweepAngle(), startpoint, curvePoints); int cp = 0; m_points[cp]->setPoint(startpoint); m_points[cp]->unsetProperty(KoPathPoint::HasControlPoint1); for (int i = 0; i < pointCnt; i += 3) { m_points[cp]->setControlPoint2(curvePoints[i]); m_points[++cp]->setControlPoint1(curvePoints[i + 1]); m_points[cp]->setPoint(curvePoints[i + 2]); m_points[cp]->unsetProperty(KoPathPoint::HasControlPoint2); } if (m_type == Curve) { m_points[++cp]->setPoint(m_center); m_points[cp]->unsetProperty(KoPathPoint::HasControlPoint1); m_points[cp]->unsetProperty(KoPathPoint::HasControlPoint2); } else if (m_type == Line && m_startAngle == m_endAngle) { m_points[0]->setControlPoint1(m_points[cp]->controlPoint1()); m_points[0]->setPoint(m_points[cp]->point()); --cp; } d->m_subpaths[0]->clear(); for (int i = 0; i <= cp; ++i) { if (i < cp || (m_type == Line && m_startAngle != m_endAngle)) { m_points[i]->unsetProperty(KoPathPoint::CloseSubpath); } else { m_points[i]->setProperty(KoPathPoint::CloseSubpath); } d->m_subpaths[0]->push_back(m_points[i]); } #endif } void SpiralShape::createPath(const QSizeF &size) { Q_UNUSED(size); clear(); QPointF center = QPointF(m_radii.x() / 2.0, m_radii.y() / 2.0); //moveTo(QPointF(size.width(), m_radii.y())); qreal adv_ang = (m_clockwise ? -1.0 : 1.0) * M_PI_2; // radius of first segment is non-faded radius: qreal m_radius = m_radii.x() / 2.0; qreal r = m_radius; QPointF oldP(center.x(), (m_clockwise ? -1.0 : 1.0) * m_radius + center.y()); QPointF newP; QPointF newCenter(center); moveTo(oldP); uint m_segments = 10; //m_handles[0] = oldP; for (uint i = 0; i < m_segments; ++i) { newP.setX(r * cos(adv_ang * (i + 2)) + newCenter.x()); newP.setY(r * sin(adv_ang * (i + 2)) + newCenter.y()); if (m_type == Curve) { qreal rx = qAbs(oldP.x() - newP.x()); qreal ry = qAbs(oldP.y() - newP.y()); if (m_clockwise) { arcTo(rx, ry, ((i + 1) % 4) * 90, 90); } else { arcTo(rx, ry, 360 - ((i + 1) % 4) * 90, -90); } } else { lineTo(newP); } newCenter += (newP - newCenter) * (1.0 - m_fade); oldP = newP; r *= m_fade; } //m_handles[1] = QPointF(center.x(), (m_clockwise ? -1.0 : 1.0) * m_radius + center.y()); m_points = *subpaths()[0]; notifyPointsChanged(); } void SpiralShape::updateKindHandle() { /* m_kindAngle = (m_startAngle + m_endAngle) * M_PI / 360.0; if (m_startAngle > m_endAngle) { m_kindAngle += M_PI; } switch (m_type) { case Curve: m_handles[2] = m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y()); break; case Line: m_handles[2] = m_center; break; } */ } void SpiralShape::updateAngleHandles() { // qreal startRadian = m_startAngle * M_PI / 180.0; // qreal endRadian = m_endAngle * M_PI / 180.0; // m_handles[0] = m_center + QPointF(cos(startRadian) * m_radii.x(), -sin(startRadian) * m_radii.y()); // m_handles[1] = m_center + QPointF(cos(endRadian) * m_radii.x(), -sin(endRadian) * m_radii.y()); } void SpiralShape::setType(SpiralType type) { m_type = type; updateKindHandle(); updatePath(size()); } SpiralShape::SpiralType SpiralShape::type() const { return m_type; } void SpiralShape::setFade(qreal fade) { m_fade = fade; updateKindHandle(); //updateAngleHandles(); updatePath(size()); } qreal SpiralShape::fade() const { return m_fade; } bool SpiralShape::clockWise() const { return m_clockwise; } void SpiralShape::setClockWise(bool clockWise) { m_clockwise = clockWise; updateKindHandle(); //updateAngleHandles(); updatePath(size()); } QString SpiralShape::pathShapeId() const { return SpiralShapeId; } diff --git a/plugins/flake/pathshapes/spiral/SpiralShape.h b/plugins/flake/pathshapes/spiral/SpiralShape.h index d58d6c6245..b6d30a7705 100644 --- a/plugins/flake/pathshapes/spiral/SpiralShape.h +++ b/plugins/flake/pathshapes/spiral/SpiralShape.h @@ -1,105 +1,100 @@ /* This file is part of the KDE project Copyright (C) 2007 Rob Buis 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 KOSPIRALSHAPE_H #define KOSPIRALSHAPE_H #include "KoParameterShape.h" #define SpiralShapeId "SpiralShape" /** * This class adds support for the spiral * shape. */ class SpiralShape : public KoParameterShape { public: /// the possible spiral types enum SpiralType { Curve = 0, ///< spiral uses curves Line = 1 ///< spiral uses lines }; SpiralShape(); ~SpiralShape() override; KoShape* cloneShape() const override; void setSize(const QSizeF &newSize) override; QPointF normalize() override; /** * Sets the type of the spiral. * @param type the new spiral type */ void setType(SpiralType type); /// Returns the actual spiral type SpiralType type() const; /** * Sets the fade parameter of the spiral. * @param angle the new start angle in degree */ void setFade(qreal fade); /// Returns the actual fade parameter qreal fade() const; bool clockWise() const; void setClockWise(bool clockwise); /// reimplemented QString pathShapeId() const override; protected: SpiralShape(const SpiralShape &rhs); - // reimplemented - void saveOdf(KoShapeSavingContext &context) const override; - // reimplemented - bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override; - void moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers = Qt::NoModifier) override; void updatePath(const QSizeF &size) override; void createPath(const QSizeF &size); private: void updateKindHandle(); void updateAngleHandles(); // fade parameter qreal m_fade; // angle for modifying the kind in radiant qreal m_kindAngle; // the center of the spiral QPointF m_center; // the radii of the spiral QPointF m_radii; // the actual spiral type SpiralType m_type; // bool m_clockwise; KoSubpath m_points; }; #endif /* KOSPIRALSHAPE_H */ diff --git a/plugins/flake/pathshapes/star/StarShape.cpp b/plugins/flake/pathshapes/star/StarShape.cpp index 86982c7ff5..5a44043d46 100644 --- a/plugins/flake/pathshapes/star/StarShape.cpp +++ b/plugins/flake/pathshapes/star/StarShape.cpp @@ -1,455 +1,303 @@ /* This file is part of the KDE project Copyright (C) 2006-2009 Jan Hambrecht 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 "StarShape.h" #include #include #include #include #include #include #include #include #include StarShape::StarShape() : m_cornerCount(5) , m_zoomX(1.0) , m_zoomY(1.0) , m_convex(false) { m_radius[base] = 25.0; m_radius[tip] = 50.0; m_angles[base] = m_angles[tip] = defaultAngleRadian(); m_roundness[base] = m_roundness[tip] = 0.0f; m_center = QPointF(50, 50); updatePath(QSize(100, 100)); } StarShape::StarShape(const StarShape &rhs) : KoParameterShape(rhs), m_cornerCount(rhs.m_cornerCount), m_radius(rhs.m_radius), m_angles(rhs.m_angles), m_zoomX(rhs.m_zoomX), m_zoomY(rhs.m_zoomY), m_roundness(rhs.m_roundness), m_center(rhs.m_center), m_convex(rhs.m_convex) { } StarShape::~StarShape() { } KoShape *StarShape::cloneShape() const { return new StarShape(*this); } void StarShape::setCornerCount(uint cornerCount) { if (cornerCount >= 3) { double oldDefaultAngle = defaultAngleRadian(); m_cornerCount = cornerCount; double newDefaultAngle = defaultAngleRadian(); m_angles[base] += newDefaultAngle - oldDefaultAngle; m_angles[tip] += newDefaultAngle - oldDefaultAngle; updatePath(QSize()); } } uint StarShape::cornerCount() const { return m_cornerCount; } void StarShape::setBaseRadius(qreal baseRadius) { m_radius[base] = fabs(baseRadius); updatePath(QSize()); } qreal StarShape::baseRadius() const { return m_radius[base]; } void StarShape::setTipRadius(qreal tipRadius) { m_radius[tip] = fabs(tipRadius); updatePath(QSize()); } qreal StarShape::tipRadius() const { return m_radius[tip]; } void StarShape::setBaseRoundness(qreal baseRoundness) { m_roundness[base] = baseRoundness; updatePath(QSize()); } void StarShape::setTipRoundness(qreal tipRoundness) { m_roundness[tip] = tipRoundness; updatePath(QSize()); } void StarShape::setConvex(bool convex) { m_convex = convex; updatePath(QSize()); } bool StarShape::convex() const { return m_convex; } QPointF StarShape::starCenter() const { return m_center; } void StarShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { if (modifiers & Qt::ShiftModifier) { QPointF handle = handles()[handleId]; QPointF tangentVector = point - handle; qreal distance = sqrt(tangentVector.x() * tangentVector.x() + tangentVector.y() * tangentVector.y()); QPointF radialVector = handle - m_center; // cross product to determine in which direction the user is dragging qreal moveDirection = radialVector.x() * tangentVector.y() - radialVector.y() * tangentVector.x(); // make the roundness stick to zero if distance is under a certain value float snapDistance = 3.0; if (distance >= 0.0) { distance = distance < snapDistance ? 0.0 : distance - snapDistance; } else { distance = distance > -snapDistance ? 0.0 : distance + snapDistance; } // control changes roundness on both handles, else only the actual handle roundness is changed if (modifiers & Qt::ControlModifier) { m_roundness[handleId] = moveDirection < 0.0f ? distance : -distance; } else { m_roundness[base] = m_roundness[tip] = moveDirection < 0.0f ? distance : -distance; } } else { QPointF distVector = point - m_center; // unapply scaling distVector.rx() /= m_zoomX; distVector.ry() /= m_zoomY; m_radius[handleId] = sqrt(distVector.x() * distVector.x() + distVector.y() * distVector.y()); qreal angle = atan2(distVector.y(), distVector.x()); if (angle < 0.0) { angle += 2.0 * M_PI; } qreal diffAngle = angle - m_angles[handleId]; qreal radianStep = M_PI / static_cast(m_cornerCount); if (handleId == tip) { m_angles[tip] += diffAngle - radianStep; m_angles[base] += diffAngle - radianStep; } else { // control make the base point move freely if (modifiers & Qt::ControlModifier) { m_angles[base] += diffAngle - 2 * radianStep; } else { m_angles[base] = m_angles[tip]; } } } } void StarShape::updatePath(const QSizeF &size) { Q_UNUSED(size); qreal radianStep = M_PI / static_cast(m_cornerCount); createPoints(m_convex ? m_cornerCount : 2 * m_cornerCount); KoSubpath &points = *subpaths()[0]; uint index = 0; for (uint i = 0; i < 2 * m_cornerCount; ++i) { uint cornerType = i % 2; if (cornerType == base && m_convex) { continue; } qreal radian = static_cast((i + 1) * radianStep) + m_angles[cornerType]; QPointF cornerPoint = QPointF(m_zoomX * m_radius[cornerType] * cos(radian), m_zoomY * m_radius[cornerType] * sin(radian)); points[index]->setPoint(m_center + cornerPoint); points[index]->unsetProperty(KoPathPoint::StopSubpath); points[index]->unsetProperty(KoPathPoint::CloseSubpath); if (m_roundness[cornerType] > 1e-10 || m_roundness[cornerType] < -1e-10) { // normalized cross product to compute tangential vector for handle point QPointF tangentVector(cornerPoint.y() / m_radius[cornerType], -cornerPoint.x() / m_radius[cornerType]); points[index]->setControlPoint2(points[index]->point() - m_roundness[cornerType] * tangentVector); points[index]->setControlPoint1(points[index]->point() + m_roundness[cornerType] * tangentVector); } else { points[index]->removeControlPoint1(); points[index]->removeControlPoint2(); } index++; } // first path starts and closes path points[0]->setProperty(KoPathPoint::StartSubpath); points[0]->setProperty(KoPathPoint::CloseSubpath); // last point stops and closes path points.last()->setProperty(KoPathPoint::StopSubpath); points.last()->setProperty(KoPathPoint::CloseSubpath); normalize(); QList handles; handles.push_back(points.at(tip)->point()); if (!m_convex) { handles.push_back(points.at(base)->point()); } setHandles(handles); m_center = computeCenter(); } void StarShape::createPoints(int requiredPointCount) { if (subpaths().count() != 1) { clear(); subpaths().append(new KoSubpath()); } int currentPointCount = subpaths()[0]->count(); if (currentPointCount > requiredPointCount) { for (int i = 0; i < currentPointCount - requiredPointCount; ++i) { delete subpaths()[0]->front(); subpaths()[0]->pop_front(); } } else if (requiredPointCount > currentPointCount) { for (int i = 0; i < requiredPointCount - currentPointCount; ++i) { subpaths()[0]->append(new KoPathPoint(this, QPointF())); } } notifyPointsChanged(); } void StarShape::setSize(const QSizeF &newSize) { QTransform matrix(resizeMatrix(newSize)); m_zoomX *= matrix.m11(); m_zoomY *= matrix.m22(); // this transforms the handles KoParameterShape::setSize(newSize); m_center = computeCenter(); } QPointF StarShape::computeCenter() const { KoSubpath &points = *subpaths()[0]; QPointF center(0, 0); for (uint i = 0; i < m_cornerCount; ++i) { if (m_convex) { center += points[i]->point(); } else { center += points[2 * i]->point(); } } if (m_cornerCount > 0) { return center / static_cast(m_cornerCount); } return center; } -bool StarShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) -{ - bool loadAsCustomShape = false; - - if (element.localName() == "custom-shape") { - QString drawEngine = element.attributeNS(KoXmlNS::draw, "engine", ""); - if (drawEngine != "calligra:star") { - return false; - } - loadAsCustomShape = true; - } else if (element.localName() != "regular-polygon") { - return false; - } - - m_radius[tip] = 50; - m_center = QPointF(50, 50); - - if (!loadAsCustomShape) { - QString corners = element.attributeNS(KoXmlNS::draw, "corners", ""); - if (!corners.isEmpty()) { - m_cornerCount = corners.toUInt(); - // initialize default angles of tip and base - m_angles[base] = m_angles[tip] = defaultAngleRadian(); - } - - m_convex = (element.attributeNS(KoXmlNS::draw, "concave", "false") == "false"); - - if (m_convex) { - m_radius[base] = m_radius[tip]; - } else { - // sharpness is radius of ellipse on which inner polygon points are located - // 0% means all polygon points are on a single ellipse - // 100% means inner points are located at polygon center point - QString sharpness = element.attributeNS(KoXmlNS::draw, "sharpness", ""); - if (!sharpness.isEmpty() && sharpness.right(1) == "%") { - float percent = sharpness.left(sharpness.length() - 1).toFloat(); - m_radius[base] = m_radius[tip] * (100 - percent) / 100; - } - } - } else { - QString drawData = element.attributeNS(KoXmlNS::draw, "data"); - if (drawData.isEmpty()) { - return false; - } - - QStringList properties = drawData.split(';'); - if (properties.count() == 0) { - return false; - } - - foreach (const QString &property, properties) { - QStringList pair = property.split(':'); - if (pair.count() != 2) { - continue; - } - if (pair[0] == "corners") { - m_cornerCount = pair[1].toInt(); - } else if (pair[0] == "concave") { - m_convex = (pair[1] == "false"); - } else if (pair[0] == "baseRoundness") { - m_roundness[base] = pair[1].toDouble(); - } else if (pair[0] == "tipRoundness") { - m_roundness[tip] = pair[1].toDouble(); - } else if (pair[0] == "baseAngle") { - m_angles[base] = pair[1].toDouble(); - } else if (pair[0] == "tipAngle") { - m_angles[tip] = pair[1].toDouble(); - } else if (pair[0] == "sharpness") { - float percent = pair[1].left(pair[1].length() - 1).toFloat(); - m_radius[base] = m_radius[tip] * (100 - percent) / 100; - } - } - - if (m_convex) { - m_radius[base] = m_radius[tip]; - } - } - - updatePath(QSizeF()); - - // reset transformation - setTransformation(QTransform()); - - loadOdfAttributes(element, context, OdfAllAttributes); - loadText(element, context); - - return true; -} - -void StarShape::saveOdf(KoShapeSavingContext &context) const -{ - if (isParametricShape()) { - double defaultAngle = defaultAngleRadian(); - bool hasRoundness = m_roundness[tip] != 0.0f || m_roundness[base] != 0.0f; - bool hasAngleOffset = m_angles[base] != defaultAngle || m_angles[tip] != defaultAngle; - if (hasRoundness || hasAngleOffset) { - // draw:regular-polygon has no means of saving roundness - // so we save as a custom shape with a specific draw:engine - context.xmlWriter().startElement("draw:custom-shape"); - saveOdfAttributes(context, OdfAllAttributes); - - // now write the special shape data - context.xmlWriter().addAttribute("draw:engine", "calligra:star"); - // create the data attribute - QString drawData = QString("corners:%1;").arg(m_cornerCount); - drawData += m_convex ? "concave:false;" : "concave:true;"; - if (!m_convex) { - // sharpness is radius of ellipse on which inner polygon points are located - // 0% means all polygon points are on a single ellipse - // 100% means inner points are located at polygon center point - qreal percent = (m_radius[tip] - m_radius[base]) / m_radius[tip] * 100.0; - drawData += QString("sharpness:%1%;").arg(percent); - } - if (m_roundness[base] != 0.0f) { - drawData += QString("baseRoundness:%1;").arg(m_roundness[base]); - } - if (m_roundness[tip] != 0.0f) { - drawData += QString("tipRoundness:%1;").arg(m_roundness[tip]); - } - drawData += QString("baseAngle:%1;").arg(m_angles[base]); - drawData += QString("tipAngle:%1;").arg(m_angles[tip]); - - context.xmlWriter().addAttribute("draw:data", drawData); - - saveText(context); - - // write a enhanced geometry element for compatibility with other applications - context.xmlWriter().startElement("draw:enhanced-geometry"); - context.xmlWriter().addAttribute("draw:enhanced-path", toString(transformation())); - context.xmlWriter().endElement(); // draw:enhanced-geometry - - context.xmlWriter().endElement(); // draw:custom-shape - } else { - context.xmlWriter().startElement("draw:regular-polygon"); - saveOdfAttributes(context, OdfAllAttributes); - context.xmlWriter().addAttribute("draw:corners", m_cornerCount); - context.xmlWriter().addAttribute("draw:concave", m_convex ? "false" : "true"); - if (!m_convex) { - // sharpness is radius of ellipse on which inner polygon points are located - // 0% means all polygon points are on a single ellipse - // 100% means inner points are located at polygon center point - qreal percent = (m_radius[tip] - m_radius[base]) / m_radius[tip] * 100.0; - context.xmlWriter().addAttribute("draw:sharpness", QString("%1%").arg(percent)); - } - saveText(context); - context.xmlWriter().endElement(); - } - } else { - KoPathShape::saveOdf(context); - } -} - QString StarShape::pathShapeId() const { return StarShapeId; } double StarShape::defaultAngleRadian() const { qreal radianStep = M_PI / static_cast(m_cornerCount); return M_PI_2 - 2 * radianStep; } diff --git a/plugins/flake/pathshapes/star/StarShape.h b/plugins/flake/pathshapes/star/StarShape.h index a6740aca9d..eccacb617d 100644 --- a/plugins/flake/pathshapes/star/StarShape.h +++ b/plugins/flake/pathshapes/star/StarShape.h @@ -1,150 +1,146 @@ /* This file is part of the KDE project 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. */ #ifndef KOSTARSHAPE_H #define KOSTARSHAPE_H #include #include #define StarShapeId "StarShape" /** * The star shape is a shape that can represent a star or * a regular polygon. There a some properties which can * be changed to control the appearance of the shape * like the number of corners, the inner/outer radius * and the corner roundness. */ class StarShape : public KoParameterShape { public: StarShape(); ~StarShape() override; KoShape* cloneShape() const override; /** * Sets the number of corners. * * The minimum accepted number of corners is 3. * If the star is set to be convex (like a regular polygon), * the corner count equals the number of polygon points. * For a real star it represents the number of legs the star has. * * @param cornerCount the new number of corners */ void setCornerCount(uint cornerCount); /// Returns the number of corners uint cornerCount() const; /** * Sets the radius of the base points. * The base radius has no meaning if the star is set convex. * @param baseRadius the new base radius */ void setBaseRadius(qreal baseRadius); /// Returns the base radius qreal baseRadius() const; /** * Sets the radius of the tip points. * @param tipRadius the new tip radius */ void setTipRadius(qreal tipRadius); /// Returns the tip radius qreal tipRadius() const; /** * Sets the roundness at the base points. * * A roundness value of zero disables the roundness. * * @param baseRoundness the new base roundness */ void setBaseRoundness(qreal baseRoundness); /** * Sets the roundness at the tip points. * * A roundness value of zero disables the roundness. * * @param tipRoundness the new base roundness */ void setTipRoundness(qreal tipRoundness); /** * Sets the star to be convex, looking like a polygon. * @param convex if true makes shape behave like regular polygon */ void setConvex(bool convex); /// Returns if the star represents a regular polygon. bool convex() const; /** * Returns the star center point in shape coordinates. * * The star center is the weight center of the star and not necessarily * coincident with the shape center point. */ QPointF starCenter() const; /// reimplemented void setSize(const QSizeF &newSize) override; /// reimplemented - bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override; - /// reimplemented - void saveOdf(KoShapeSavingContext &context) const override; - /// reimplemented QString pathShapeId() const override; protected: StarShape(const StarShape &rhs); void moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers = Qt::NoModifier) override; void updatePath(const QSizeF &size) override; /// recreates the path points when the corner count or convexity changes void createPoints(int requiredPointCount); private: /// Computes the star center point from the inner points QPointF computeCenter() const; /// Returns the default offset angle in radian double defaultAngleRadian() const; /// the handle types enum Handles { tip = 0, base = 1 }; uint m_cornerCount; ///< number of corners std::array m_radius; ///< the different radii std::array m_angles; ///< the offset angles qreal m_zoomX; ///< scaling in x qreal m_zoomY; ///< scaling in y std::array m_roundness; ///< the roundness at the handles QPointF m_center; ///< the star center point bool m_convex; ///< controls if the star is convex }; #endif /* KOSTARSHAPE_H */