diff --git a/krita/pics/svg/dark_brush_ratio.svg b/krita/pics/svg/dark_brush_ratio.svg new file mode 100644 index 0000000000..c82751844f --- /dev/null +++ b/krita/pics/svg/dark_brush_ratio.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/krita/pics/svg/dark_brush_rotation.svg b/krita/pics/svg/dark_brush_rotation.svg new file mode 100644 index 0000000000..592eebba9a --- /dev/null +++ b/krita/pics/svg/dark_brush_rotation.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/krita/pics/svg/dark_brush_size.svg b/krita/pics/svg/dark_brush_size.svg new file mode 100644 index 0000000000..dc2c89c629 --- /dev/null +++ b/krita/pics/svg/dark_brush_size.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/krita/pics/svg/light_brush_ratio.svg b/krita/pics/svg/light_brush_ratio.svg new file mode 100644 index 0000000000..0f368422d0 --- /dev/null +++ b/krita/pics/svg/light_brush_ratio.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/krita/pics/svg/light_brush_rotation.svg b/krita/pics/svg/light_brush_rotation.svg new file mode 100644 index 0000000000..e82f1a7c49 --- /dev/null +++ b/krita/pics/svg/light_brush_rotation.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/krita/pics/svg/light_brush_size.svg b/krita/pics/svg/light_brush_size.svg new file mode 100644 index 0000000000..e4baa5e27e --- /dev/null +++ b/krita/pics/svg/light_brush_size.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/krita/pics/svg/svg-icons.qrc b/krita/pics/svg/svg-icons.qrc index 312f78b568..a163ad11ba 100644 --- a/krita/pics/svg/svg-icons.qrc +++ b/krita/pics/svg/svg-icons.qrc @@ -1,150 +1,156 @@ broken-preset.svgz dark_addblankframe.svg dark_addcolor.svg dark_addduplicateframe.svg + dark_brush_size.svg + dark_brush_rotation.svg + dark_brush_ratio.svg dark_deletekeyframe.svg dark_docker_lock_a.svg dark_docker_lock_b.svg dark_layer-locked.svg dark_layer-unlocked.svg dark_nextframe.svg dark_nextkeyframe.svg dark_lastframe.svg dark_prevkeyframe.svg dark_firstframe.svg dark_pallete_librarysvg.svg dark_passthrough-disabled.svg dark_passthrough-enabled.svg dark_prevframe.svg dark_selection-mode_ants.svg dark_selection-mode_invisible.svg dark_selection-mode_mask.svg dark_transparency-disabled.svg dark_transparency-enabled.svg dark_trim-to-image.svg delete.svgz layer-style-disabled.svg layer-style-enabled.svg light_addblankframe.svg light_addcolor.svg light_addduplicateframe.svg + light_brush_size.svg + light_brush_rotation.svg + light_brush_ratio.svg light_deletekeyframe.svg light_docker_lock_a.svg light_docker_lock_b.svg light_layer-locked.svg light_layer-unlocked.svg light_nextframe.svg light_pallete_library.svg light_passthrough-disabled.svgz light_passthrough-enabled.svgz light_prevframe.svg light_nextkeyframe.svg light_lastframe.svg light_prevkeyframe.svg light_firstframe.svg light_selection-mode_ants.svg light_selection-mode_invisible.svg light_selection-mode_mask.svg light_timeline_keyframe.svg light_transparency-disabled.svg light_transparency-enabled.svg light_trim-to-image.svg paintop_presets_disabled.svgz paintop_settings_01.svgz selection-info.svg selection-mode_invisible.svg svg-icons.qrc transparency-locked.svg transparency-unlocked.svg workspace-chooser.svg light_lazyframeOn.svg light_lazyframeOff.svg dark_lazyframeOn.svg dark_lazyframeOff.svg dark_mirror-view.svg light_mirror-view.svg dark_rotation-reset.svg light_rotation-reset.svg light_smoothing-basic.svg light_smoothing-no.svg light_smoothing-stabilizer.svg light_smoothing-weighted.svg dark_smoothing-basic.svg dark_smoothing-no.svg dark_smoothing-stabilizer.svg dark_smoothing-weighted.svg light_merge-layer-below.svg dark_merge-layer-below.svg light_rotate-canvas-left.svg light_rotate-canvas-right.svg dark_rotate-canvas-left.svg dark_rotate-canvas-right.svg light_gmic.svg dark_gmic.svg light_split-layer.svg dark_split-layer.svg light_color-to-alpha.svg dark_color-to-alpha.svg light_preset-switcher.svg dark_preset-switcher.svg dark_animation_play.svg dark_animation_stop.svg dark_dropframe.svg dark_droppedframes.svg light_animation_play.svg light_animation_stop.svg light_dropframe.svg light_droppedframes.svg dark_landscape.svg dark_portrait.svg light_landscape.svg light_portrait.svg dark_interpolation_constant.svg dark_interpolation_linear.svg dark_interpolation_bezier.svg dark_interpolation_sharp.svg dark_interpolation_smooth.svg light_interpolation_bezier.svg light_interpolation_constant.svg light_interpolation_linear.svg light_interpolation_sharp.svg light_interpolation_smooth.svg dark_audio-none.svg dark_audio-volume-high.svg dark_audio-volume-mute.svg dark_keyframe-add.svg dark_keyframe-remove.svg dark_zoom-fit.svg dark_zoom-horizontal.svg dark_zoom-vertical.svg light_audio-none.svg light_audio-volume-high.svg light_audio-volume-mute.svg light_keyframe-add.svg light_keyframe-remove.svg light_zoom-fit.svg light_zoom-horizontal.svg light_zoom-vertical.svg dark_showColoring.svg dark_showMarks.svg dark_showColoringOff.svg dark_showMarksOff.svg dark_updateColorize.svg light_showColoring.svg light_showMarks.svg light_showColoringOff.svg light_showMarksOff.svg light_updateColorize.svg diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index a92a04f737..48573ebd8b 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -1,19 +1,22 @@ +include_directories(libpaintop) + +add_subdirectory( libpaintop ) add_subdirectory( version ) add_subdirectory( global ) add_subdirectory( koplugin ) add_subdirectory( widgetutils ) add_subdirectory( widgets ) add_subdirectory( store ) add_subdirectory( odf ) add_subdirectory( flake ) add_subdirectory( basicflakes ) add_subdirectory( pigment ) add_subdirectory( command ) add_subdirectory( brush ) add_subdirectory( psd ) add_subdirectory( color ) add_subdirectory( image ) add_subdirectory( ui ) add_subdirectory( vectorimage ) add_subdirectory( impex ) add_subdirectory( libkis ) diff --git a/libs/flake/KoFilterEffect.h b/libs/flake/KoFilterEffect.h index f0173f79c5..2817f2861c 100644 --- a/libs/flake/KoFilterEffect.h +++ b/libs/flake/KoFilterEffect.h @@ -1,170 +1,170 @@ /* This file is part of the KDE project * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2009 Jan Hambrecht * * 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 * Library 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 _KO_FILTER_EFFECT_H_ #define _KO_FILTER_EFFECT_H_ class QImage; class QString; class QRectF; class KoXmlWriter; class KoFilterEffectRenderContext; class KoFilterEffectLoadingContext; -class KoXmlElement; +#include #include "kritaflake_export.h" #include /** * This is the base for filter effect (blur, invert...) that can be applied on a shape. * All sizes and coordinates of the filter effect are stored in object bounding box * coordinates, where (0,0) refers to the top-left corner of a shapes bounding rect * and (1,1) refers to the bottom-right corner. * When loading, a transformation matrix is given to convert from user space coordinates. * Another transformation matrix is given via the render context to convert back to * user space coordinates when applying the effect. * Using object bounding box coordinates internally makes it easy to share effects * between shapes or even between users via the filter effect resources. */ class KRITAFLAKE_EXPORT KoFilterEffect { public: KoFilterEffect(const QString &id, const QString &name); virtual ~KoFilterEffect(); /// Returns the user visible name of the filter QString name() const; /// Returns the unique id of the filter QString id() const; /// Sets the region the filter is applied to in bounding box units void setFilterRect(const QRectF &filterRect); /// Returns the region this filter is applied to in bounding box units QRectF filterRect() const; /// Returns the region this filter is applied to for the given bounding rect QRectF filterRectForBoundingRect(const QRectF &boundingRect) const; /** * Sets the name of the output image * * The name is used so that other effects can reference * the output of this effect as one of their input images. * * @param output the output image name */ void setOutput(const QString &output); /// Returns the name of the output image QString output() const; /** * Returns list of named input images of this filter effect. * * These names identify the input images for this filter effect. * These can be one of the keywords SourceGraphic, SourceAlpha, * BackgroundImage, BackgroundAlpha, FillPaint or StrokePaint, * as well as a named output of another filter effect in the stack. * An empty input list of the first effect in the stack default to * SourceGraphic, whereas on subsequent effects it defaults to the * result of the previous filter effect. */ QList inputs() const; /// Adds a new input at the end of the input list void addInput(const QString &input); /// Inserts an input at the giben position in the input list void insertInput(int index, const QString &input); /// Sets an existing input to a new value void setInput(int index, const QString &input); /// Removes an input from the given position in the input list void removeInput(int index); /** * Return the required number of input images. * The default required number of input images is 1. * Derived classes should call setRequiredInputCount to set * a different number. */ int requiredInputCount() const; /** * Returns the maximal number of input images. * The default maximal number of input images is 1. * Derived classes should call setMaximalInputCount to set * a different number. */ int maximalInputCount() const; /** * Apply the effect on an image. * @param image the image the filter should be applied to * @param context the render context providing additional data */ virtual QImage processImage(const QImage &image, const KoFilterEffectRenderContext &context) const = 0; /** * Apply the effect on a list of images. * @param images the images the filter should be applied to * @param context the render context providing additional data */ virtual QImage processImages(const QList &images, const KoFilterEffectRenderContext &context) const; /** * Loads data from given xml element. * @param element the xml element to load data from * @param context the loading context providing additional data * @return true if loading was successful, else false */ virtual bool load(const KoXmlElement &element, const KoFilterEffectLoadingContext &context) = 0; /** * Writes custom data to given xml element. * @param writer the xml writer to write data to */ virtual void save(KoXmlWriter &writer) = 0; protected: /// Sets the required number of input images void setRequiredInputCount(int count); /// Sets the maximal number of input images void setMaximalInputCount(int count); /** * Saves common filter attributes * * Saves result, subregion and input attributes. The input attrinbute * is only saved if required, maximal and actual input count equals 1. * All other filters have to write inputs on their own. */ void saveCommonAttributes(KoXmlWriter &writer); private: class Private; Private* const d; }; #endif // _KO_FILTER_EFFECT_H_ diff --git a/libs/flake/KoFilterEffectRegistry.h b/libs/flake/KoFilterEffectRegistry.h index c79213c745..1176385d2f 100644 --- a/libs/flake/KoFilterEffectRegistry.h +++ b/libs/flake/KoFilterEffectRegistry.h @@ -1,61 +1,61 @@ /* This file is part of the KDE project * Copyright (c) 2009 Jan Hambrecht * * 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 * Library 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 KOFILTEREFFECTREGISTRY_H #define KOFILTEREFFECTREGISTRY_H #include #include #include "kritaflake_export.h" -class KoXmlElement; +#include class KoFilterEffectLoadingContext; class KoFilterEffect; class KRITAFLAKE_EXPORT KoFilterEffectRegistry : public KoGenericRegistry { public: KoFilterEffectRegistry(); virtual ~KoFilterEffectRegistry(); /** * Return the only instance of KoFilterEffectRegistry. * Creates an instance on the first call. */ static KoFilterEffectRegistry *instance(); /** * Creates filter effect from given xml element. * @param element the xml element to load form * @return the created filter effect if successful, otherwise returns 0 */ KoFilterEffect *createFilterEffectFromXml(const KoXmlElement &element, const KoFilterEffectLoadingContext &context); private: KoFilterEffectRegistry(const KoFilterEffectRegistry&); KoFilterEffectRegistry operator=(const KoFilterEffectRegistry&); void init(); class Private; Private * const d; }; #endif // KOFILTEREFFECTREGISTRY_H diff --git a/libs/flake/KoFrameShape.h b/libs/flake/KoFrameShape.h index 062bfd2ffd..466a85e1e4 100644 --- a/libs/flake/KoFrameShape.h +++ b/libs/flake/KoFrameShape.h @@ -1,94 +1,94 @@ /* 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; -class KoXmlElement; +#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); /** * 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/KoMarker.h b/libs/flake/KoMarker.h index a50bc27b4f..2413ced08b 100644 --- a/libs/flake/KoMarker.h +++ b/libs/flake/KoMarker.h @@ -1,122 +1,123 @@ /* 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. */ #ifndef KOMARKER_H #define KOMARKER_H #include #include #include "kritaflake_export.h" #include -class KoXmlElement; +#include + class KoShapeLoadingContext; class KoShapeSavingContext; class QString; class QPainterPath; class KoShape; class QPainter; class KoShapeStroke; class KRITAFLAKE_EXPORT KoMarker : public QSharedData { public: KoMarker(); ~KoMarker(); /** * Display name of the marker * * @return Display name of the marker */ QString name() const; KoMarker(const KoMarker &rhs); bool operator==(const KoMarker &other) const; enum MarkerCoordinateSystem { StrokeWidth, UserSpaceOnUse }; void setCoordinateSystem(MarkerCoordinateSystem value); MarkerCoordinateSystem coordinateSystem() const; static MarkerCoordinateSystem coordinateSystemFromString(const QString &value); static QString coordinateSystemToString(MarkerCoordinateSystem value); void setReferencePoint(const QPointF &value); QPointF referencePoint() const; void setReferenceSize(const QSizeF &size); QSizeF referenceSize() const; bool hasAutoOtientation() const; void setAutoOrientation(bool value); // measured in radians! qreal explicitOrientation() const; // measured in radians! void setExplicitOrientation(qreal value); void setShapes(const QList &shapes); QList shapes() const; /** * @brief paintAtOrigin paints the marker at the position \p pos. * Scales and rotates the masrker if needed. */ void paintAtPosition(QPainter *painter, const QPointF &pos, qreal strokeWidth, qreal nodeAngle); /** * Return maximum distance that the marker can take outside the shape itself */ qreal maxInset(qreal strokeWidth) const; /** * Bounding rect of the marker in local coordinates. It is assumed that the marker * is painted with the reference point placed at position (0,0) */ QRectF boundingRect(qreal strokeWidth, qreal nodeAngle) const; /** * Outline of the marker in local coordinates. It is assumed that the marker * is painted with the reference point placed at position (0,0) */ QPainterPath outline(qreal strokeWidth, qreal nodeAngle) const; /** * Draws a preview of the marker in \p previewRect of \p painter */ void drawPreview(QPainter *painter, const QRectF &previewRect, const QPen &pen, KoFlake::MarkerPosition position); void applyShapeStroke(KoShape *shape, KoShapeStroke *stroke, const QPointF &pos, qreal strokeWidth, qreal nodeAngle); private: class Private; Private * const d; }; Q_DECLARE_METATYPE(KoMarker*) #endif /* KOMARKER_H */ diff --git a/libs/flake/KoMarkerCollection.cpp b/libs/flake/KoMarkerCollection.cpp index 22c53dffca..f723f34dee 100644 --- a/libs/flake/KoMarkerCollection.cpp +++ b/libs/flake/KoMarkerCollection.cpp @@ -1,142 +1,139 @@ /* 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 "KoMarkerCollection.h" #include #include #include "KoMarker.h" #include #include #include #include #include #include #include #include "kis_debug.h" // WARNING: there is a bug in GCC! It doesn't warn that we are // deleting an uninitialized type here! #include class Q_DECL_HIDDEN KoMarkerCollection::Private { public: ~Private() { } QList > markers; }; KoMarkerCollection::KoMarkerCollection(QObject *parent) : QObject(parent) , d(new Private) { // Add no marker so the user can remove a marker from the line. d->markers.append(QExplicitlySharedDataPointer(0)); // Add default markers loadDefaultMarkers(); } KoMarkerCollection::~KoMarkerCollection() { delete d; } void KoMarkerCollection::loadMarkersFromFile(const QString &svgFile) { QFile file(svgFile); if (!file.exists()) return; if (!file.open(QIODevice::ReadOnly)) return; - QXmlStreamReader reader(&file); - reader.setNamespaceProcessing(false); - QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc; - bool ok = doc.setContent(&reader, &errorMsg, &errorLine, &errorColumn); + bool ok = doc.setContent(&file, false, &errorMsg, &errorLine, &errorColumn); if (!ok) { errKrita << "Parsing error in " << svgFile << "! 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); return; } KoDocumentResourceManager manager; SvgParser parser(&manager); parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values parser.setXmlBaseDir(QFileInfo(svgFile).absolutePath()); parser.setFileFetcher( [](const QString &fileName) { QFile file(fileName); if (!file.exists()) return QByteArray(); file.open(QIODevice::ReadOnly); return file.readAll(); }); QSizeF fragmentSize; QList shapes = parser.parseSvg(doc.documentElement(), &fragmentSize); qDeleteAll(shapes); Q_FOREACH (const QExplicitlySharedDataPointer &marker, parser.knownMarkers()) { addMarker(marker.data()); } } void KoMarkerCollection::loadDefaultMarkers() { QString filePath = KoResourcePaths::findResource("data", "styles/markers.svg"); loadMarkersFromFile(filePath); } QList KoMarkerCollection::markers() const { QList markerList; foreach (const QExplicitlySharedDataPointer& m, d->markers){ markerList.append(m.data()); } return markerList; } KoMarker * KoMarkerCollection::addMarker(KoMarker *marker) { foreach (const QExplicitlySharedDataPointer& m, d->markers) { if (marker == m.data()) { return marker; } if (m && *marker == *m) { debugFlake << "marker is the same as other"; return m.data(); } } d->markers.append(QExplicitlySharedDataPointer(marker)); return marker; } diff --git a/libs/flake/KoMarkerCollection.h b/libs/flake/KoMarkerCollection.h index 56a280da9f..044b79d963 100644 --- a/libs/flake/KoMarkerCollection.h +++ b/libs/flake/KoMarkerCollection.h @@ -1,68 +1,68 @@ /* 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. */ #ifndef KOMARKERCOLLECTION_H #define KOMARKERCOLLECTION_H #include "kritaflake_export.h" #include #include #include #include class KoMarker; -class KoXmlElement; +#include class KoShapeLoadingContext; class KRITAFLAKE_EXPORT KoMarkerCollection : public QObject { Q_OBJECT public: explicit KoMarkerCollection(QObject *parent = 0); virtual ~KoMarkerCollection(); QList markers() const; /** * Add marker to collection * * The collection checks if a marker with the same content exists and if so deletes the * passed marker and returns a pointer to an existing marker. If no such marker exists it * adds the marker and return the same pointer as passed. * Calling that function passes ownership of the marker to this class. * * @param marker Marker to add * @return pointer to marker that should be used. This might be different to the marker passed */ KoMarker * addMarker(KoMarker *marker); void loadMarkersFromFile(const QString &svgFile); private: /// load the markers that are available per default. void loadDefaultMarkers(); class Private; Private * const d; }; Q_DECLARE_METATYPE(KoMarkerCollection *) #endif /* KOMARKERCOLLECTION_H */ diff --git a/libs/flake/KoOdfGradientBackground.h b/libs/flake/KoOdfGradientBackground.h index 2170ef2aac..bd5210e363 100644 --- a/libs/flake/KoOdfGradientBackground.h +++ b/libs/flake/KoOdfGradientBackground.h @@ -1,66 +1,66 @@ /* 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; class KoOdfGradientBackgroundPrivate; -class KoXmlElement; +#include class KoGenStyles; class KoGenStyle; /// Gradients from odf that are not native to Qt class KoOdfGradientBackground : public KoShapeBackground { public: // constructor KoOdfGradientBackground(); // destructor virtual ~KoOdfGradientBackground(); bool compareTo(const KoShapeBackground *other) const override; /// reimplemented from KoShapeBackground virtual void fillStyle(KoGenStyle& style, KoShapeSavingContext& context); /// reimplemented from KoShapeBackground virtual bool loadStyle(KoOdfLoadingContext& context, const QSizeF& shapeSize); /// reimplemented from KoShapeBackground virtual void paint(QPainter& painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath& fillPath) const; 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: Q_DECLARE_PRIVATE(KoOdfGradientBackground) Q_DISABLE_COPY(KoOdfGradientBackground) }; #endif diff --git a/libs/flake/KoOdfWorkaround.h b/libs/flake/KoOdfWorkaround.h index 684828c37d..038f4cf8c9 100644 --- a/libs/flake/KoOdfWorkaround.h +++ b/libs/flake/KoOdfWorkaround.h @@ -1,162 +1,162 @@ /* 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 -class KoXmlElement; +#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 http://www.openoffice.org/issues/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 + * 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/KoParameterShape.cpp b/libs/flake/KoParameterShape.cpp index 684e5a09ee..2c41e93d60 100644 --- a/libs/flake/KoParameterShape.cpp +++ b/libs/flake/KoParameterShape.cpp @@ -1,165 +1,164 @@ /* This file is part of the KDE project Copyright (C) 2006 Thorsten Zachmann Copyright (C) 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 "KoParameterShape.h" #include "KoParameterShape_p.h" #include #include #include KoParameterShapePrivate::KoParameterShapePrivate(KoParameterShape *shape) : KoPathShapePrivate(shape), parametric(true) { } KoParameterShapePrivate::KoParameterShapePrivate(const KoParameterShapePrivate &rhs, KoParameterShape *q) : KoPathShapePrivate(rhs, q), handles(rhs.handles) { } KoParameterShape::KoParameterShape() : KoPathShape(new KoParameterShapePrivate(this)) { } KoParameterShape::KoParameterShape(KoParameterShapePrivate *dd) : KoPathShape(dd) { } KoParameterShape::~KoParameterShape() { } void KoParameterShape::moveHandle(int handleId, const QPointF & point, Qt::KeyboardModifiers modifiers) { Q_D(KoParameterShape); if (handleId >= d->handles.size()) { warnFlake << "handleId out of bounds"; return; } update(); // function to do special stuff moveHandleAction(handleId, documentToShape(point), modifiers); - updatePath(size()); update(); } int KoParameterShape::handleIdAt(const QRectF & rect) const { Q_D(const KoParameterShape); int handle = -1; for (int i = 0; i < d->handles.size(); ++i) { if (rect.contains(d->handles.at(i))) { handle = i; break; } } return handle; } QPointF KoParameterShape::handlePosition(int handleId) const { Q_D(const KoParameterShape); return d->handles.value(handleId); } void KoParameterShape::paintHandles(KisHandlePainterHelper &handlesHelper) { Q_D(KoParameterShape); QList::const_iterator it(d->handles.constBegin()); for (; it != d->handles.constEnd(); ++it) { handlesHelper.drawGradientHandle(*it); } } void KoParameterShape::paintHandle(KisHandlePainterHelper &handlesHelper, int handleId) { Q_D(KoParameterShape); handlesHelper.drawGradientHandle(d->handles[handleId]); } void KoParameterShape::setSize(const QSizeF &newSize) { Q_D(KoParameterShape); QTransform matrix(resizeMatrix(newSize)); for (int i = 0; i < d->handles.size(); ++i) { d->handles[i] = matrix.map(d->handles[i]); } KoPathShape::setSize(newSize); } QPointF KoParameterShape::normalize() { Q_D(KoParameterShape); QPointF offset(KoPathShape::normalize()); QTransform matrix; matrix.translate(-offset.x(), -offset.y()); for (int i = 0; i < d->handles.size(); ++i) { d->handles[i] = matrix.map(d->handles[i]); } return offset; } bool KoParameterShape::isParametricShape() const { Q_D(const KoParameterShape); return d->parametric; } void KoParameterShape::setParametricShape(bool parametric) { Q_D(KoParameterShape); d->parametric = parametric; update(); } QList KoParameterShape::handles() const { Q_D(const KoParameterShape); return d->handles; } void KoParameterShape::setHandles(const QList &handles) { Q_D(KoParameterShape); d->handles = handles; d->shapeChanged(ParameterChanged); } int KoParameterShape::handleCount() const { Q_D(const KoParameterShape); return d->handles.count(); } diff --git a/libs/flake/KoShapeAnchor.h b/libs/flake/KoShapeAnchor.h index 8a3e700416..06b3465e7e 100644 --- a/libs/flake/KoShapeAnchor.h +++ b/libs/flake/KoShapeAnchor.h @@ -1,262 +1,262 @@ /* 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; -class KoXmlElement; +#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(). * @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/KoTextShapeDataBase.h b/libs/flake/KoTextShapeDataBase.h index e217ff7875..36f4aab9a8 100644 --- a/libs/flake/KoTextShapeDataBase.h +++ b/libs/flake/KoTextShapeDataBase.h @@ -1,145 +1,145 @@ /* 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; -class KoXmlElement; +#include class KoShapeLoadingContext; class KoShapeSavingContext; class KoGenStyle; struct KoInsets; class QTextDocument; /** * \internal */ class KRITAFLAKE_EXPORT KoTextShapeDataBase : public KoShapeUserData { Q_OBJECT public: /// constructor KoTextShapeDataBase(); virtual ~KoTextShapeDataBase(); /// 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/KoUnavailShape.cpp b/libs/flake/KoUnavailShape.cpp index 240a9d2ba1..3e407cb220 100644 --- a/libs/flake/KoUnavailShape.cpp +++ b/libs/flake/KoUnavailShape.cpp @@ -1,656 +1,657 @@ /* This file is part of the KDE project * * Copyright (C) 2010-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. */ // Own #include "KoUnavailShape.h" // Qt #include #include #include #include #include #include #include #include #include // Calligra #include #include #include #include #include #include #include #include #include "KoShapeLoadingContext.h" #include "KoShapeSavingContext.h" #include "SimpleShapeContainerModel.h" #include "KoShapeBackground.h" #include // The XML of a frame looks something like this: -// +// // 1. // 2. // 3. // 4. // // or -// +// // 1. -// 2. ...inline xml here... +// 2. ...inline xml here... // 3. // 4. // // We define each Xml statement on lines 2 and 3 above as an "object". // (Strictly only the first child element is an object in the ODF sense, // but we have to have some terminology here.) -// +// // In an ODF frame, only the first line, i.e. the first object // contains the real contents. All the rest of the objects are used / // shown if we cannot handle the first one. The most common cases are // that there is only one object inside the frame OR that there are 2 // and the 2nd is a picture. // // Sometimes, e.g. in the case of an embedded document, the reference // points not to a file but to a directory structure inside the ODF -// store. +// store. // // When we load and save in the UnavailShape, we have to be general // enough to cover all possible cases of references and inline XML, // embedded files and embedded directory structures. // // We also have to be careful because we cannot reuse the object names // that are in the original files when saving. Instead we need to // create new object names because the ones that were used in the // original file may already be used by other embedded files/objects // that are saved by other shapes. // // FIXME: There should only be ONE place where new object / file names // are generated, not 2(?) like there are now: // KoEmbeddedDocumentSaver and the KoImageCollection. // // An ObjectEntry is used to store information about objects in the // frame, as defined above. struct ObjectEntry { QByteArray objectXmlContents; // the XML tree in the object QString objectName; // object name in the frame without "./" // This is extracted from objectXmlContents. bool isDir; KoOdfManifestEntry *manifestEntry; // manifest entry for the above. }; // A FileEntry is used to store information about embedded files // inside (i.e. referred to by) an object. struct FileEntry { QString path; // Normalized filename, i.e. without "./". QString mimeType; bool isDir; QByteArray contents; }; class KoUnavailShape::Private { public: Private(KoUnavailShape* qq); ~Private(); void draw(QPainter &painter) const; void drawNull(QPainter &painter) const; void storeObjects(const KoXmlElement &element); void storeXmlRecursive(const KoXmlElement &el, KoXmlWriter &writer, ObjectEntry *object, QHash &unknownNamespaces); void storeFile(const QString &filename, KoShapeLoadingContext &context); QByteArray loadFile(const QString &filename, KoShapeLoadingContext &context); // Objects inside the frame. For each file, we store: // - The XML code for the object // - Any embedded files (names, contents) that are referenced by xlink:href // - Whether they are directories, i.e. if they contain a file tree and not just one file. // - The manifest entries QList objectEntries; // Embedded files QList embeddedFiles; // List of embedded files. // Some cached values. QPixmap questionMark; QPixmap pixmapPreview; QSvgRenderer *scalablePreview; KoUnavailShape* q; }; KoUnavailShape::Private::Private(KoUnavailShape* qq) : scalablePreview(new QSvgRenderer()) , q(qq) { // Get the question mark "icon". questionMark.load(":/questionmark.png"); } KoUnavailShape::Private::~Private() { qDeleteAll(objectEntries); qDeleteAll(embeddedFiles); // It's a QObject, but we haven't parented it. delete(scalablePreview); } // ---------------------------------------------------------------- // The main class KoUnavailShape::KoUnavailShape() : KoFrameShape( "", "" ) , KoShapeContainer(new SimpleShapeContainerModel()) , d(new Private(this)) { setShapeId(KoUnavailShape_SHAPEID); // Default size of the shape. KoShape::setSize( QSizeF( CM_TO_POINT( 5 ), CM_TO_POINT( 3 ) ) ); } KoUnavailShape::~KoUnavailShape() { delete d; } void KoUnavailShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { applyConversion(painter, converter); // If the frame is empty, just draw a background. debugFlake << "Number of objects:" << d->objectEntries.size(); if (d->objectEntries.isEmpty()) { // But... only try to draw the background if there's one such if (background()) { QPainterPath p; p.addRect(QRectF(QPointF(), size())); background()->paint(painter, converter, paintContext, p); } } else { if(shapes().isEmpty()) { d->draw(painter); } } } void KoUnavailShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); } void KoUnavailShape::Private::draw(QPainter &painter) const { painter.save(); painter.setRenderHint(QPainter::Antialiasing); // Run through the previews in order of preference. Draw a placeholder // questionmark if there is no preview available for rendering. if (scalablePreview->isValid()) { QRect bounds(0, 0, q->boundingRect().width(), q->boundingRect().height()); scalablePreview->render(&painter, bounds); } else if (!pixmapPreview.isNull()) { QRect bounds(0, 0, q->boundingRect().width(), q->boundingRect().height()); painter.setRenderHint(QPainter::SmoothPixmapTransform); painter.drawPixmap(bounds, pixmapPreview); } else if (q->shapes().isEmpty()) { // Draw a nice question mark with a frame around it if there // is no other preview image. If there is a contained image // shape, we don't need to draw anything. // Get the question mark "icon". // FIXME: We should be able to use d->questionMark here. QPixmap questionMark; questionMark.load(":/questionmark.png"); // The size of the image is: // - the size of the shape if shapesize < 2cm // - 2 cm if 2cm <= shapesize <= 8cm // - shapesize / 4 if shapesize > 8cm qreal width = q->size().width(); qreal height = q->size().height(); qreal picSize = CM_TO_POINT(2); // Default size is 2 cm. if (width < CM_TO_POINT(2) || height < CM_TO_POINT(2)) picSize = qMin(width, height); else if (width > CM_TO_POINT(8) && height > CM_TO_POINT(8)) picSize = qMin(width, height) / qreal(4.0); painter.drawPixmap((width - picSize) / qreal(2.0), (height - picSize) / qreal(2.0), picSize, picSize, questionMark); // Draw a gray rectangle around the shape. painter.setPen(QPen(QColor(172, 196, 206), 0)); painter.drawRect(QRectF(QPointF(0,0), q->size())); } painter.restore(); } void KoUnavailShape::Private::drawNull(QPainter &painter) const { QRectF rect(QPointF(0,0), q->size()); painter.save(); // Draw a simple cross in a rectangle just to indicate that there is something here. painter.drawLine(rect.topLeft(), rect.bottomRight()); painter.drawLine(rect.bottomLeft(), rect.topRight()); painter.restore(); } // ---------------------------------------------------------------- // Loading and Saving void KoUnavailShape::saveOdf(KoShapeSavingContext & context) const { debugFlake << "START SAVING ##################################################"; KoEmbeddedDocumentSaver &fileSaver = context.embeddedSaver(); KoXmlWriter &writer = context.xmlWriter(); writer.startElement("draw:frame"); // See also loadOdf() in loadOdfAttributes. saveOdfAttributes( context, OdfAllAttributes ); // Write the stored XML to the file, but don't reuse object names. int lap = 0; QString newName; foreach (const ObjectEntry *object, d->objectEntries) { QByteArray xmlArray(object->objectXmlContents); QString objectName(object->objectName); // Possibly empty. KoOdfManifestEntry *manifestEntry(object->manifestEntry); // Create a name for this object. If this is not the first // object, i.e. a replacement object (most likely a picture), // then reuse the name but put it in ReplacementObjects. if (++lap == 1) { // The first lap in the loop is the actual object. All // other laps are replacement objects. newName = fileSaver.getFilename("Object "); } else if (lap == 2) { newName = "ObjectReplacements/" + newName; } else // FIXME: what should replacement 2 and onwards be called? newName = newName + "_"; // If there was a previous object name, replace it with the new one. if (!objectName.isEmpty() && manifestEntry) { // FIXME: We must make a copy of the byte array here because // otherwise we won't be able to save > 1 time. xmlArray.replace(objectName.toLatin1(), newName.toLatin1()); } writer.addCompleteElement(xmlArray.data()); // If the objectName is empty, this may be inline XML. // If so, we are done now. if (objectName.isEmpty() || !manifestEntry) { continue; } // Save embedded files for this object. foreach (FileEntry *entry, d->embeddedFiles) { QString fileName(entry->path); // If we found a file for this object, we need to write it // but with the new object name instead of the old one. if (!fileName.startsWith(objectName)) continue; debugFlake << "Object name: " << objectName << "newName: " << newName << "filename: " << fileName << "isDir: " << entry->isDir; fileName.replace(objectName, newName); fileName.prepend("./"); debugFlake << "New filename: " << fileName; // FIXME: Check if we need special treatment of directories. fileSaver.saveFile(fileName, entry->mimeType.toLatin1(), entry->contents); } // Write the manifest entry for the object itself. If it's a // file, the manifest is already written by saveFile, so skip // it here. if (object->isDir) { fileSaver.saveManifestEntry(newName + '/', manifestEntry->mediaType(), manifestEntry->version()); } } writer.endElement(); // draw:frame } bool KoUnavailShape::loadOdf(const KoXmlElement &frameElement, KoShapeLoadingContext &context) { debugFlake << "START LOADING ##################################################"; //debugFlake << "Loading ODF frame in the KoUnavailShape. Element = " // << frameElement.tagName(); loadOdfAttributes(frameElement, context, OdfAllAttributes); // NOTE: We cannot use loadOdfFrame() because we want to save all // the things inside the frame, not just one of them, like // loadOdfFrame() provides. // Get the manifest. QList manifest = context.odfLoadingContext().manifestEntries(); #if 0 // Enable to show all manifest entries. debugFlake << "MANIFEST: "; foreach (KoOdfManifestEntry *entry, manifest) { debugFlake << entry->mediaType() << entry->fullPath() << entry->version(); } #endif // 1. Get the XML contents of the objects from the draw:frame. As // a side effect, this extracts the object names from all // xlink:href and stores them into d->objectNames. The saved // xml contents itself is saved into d->objectXmlContents // (QByteArray) so we can save it back from saveOdf(). d->storeObjects(frameElement); #if 1 // Debug only debugFlake << "----------------------------------------------------------------"; debugFlake << "After storeObjects():"; foreach (ObjectEntry *object, d->objectEntries) { debugFlake << "objectXmlContents: " << object->objectXmlContents << "objectName: " << object->objectName; // Note: at this point, isDir and manifestEntry are not set. #endif } // 2. Loop through the objects that were found in the frame and // save all the files associated with them. Some of the // objects are files, and some are directories. The // directories are searched and the files within are saved as // well. // // In this loop, isDir and manifestEntry of each ObjectEntry are set. bool foundPreview = false; foreach (ObjectEntry *object, d->objectEntries) { QString objectName = object->objectName; if (objectName.isEmpty()) continue; debugFlake << "Storing files for object named:" << objectName; // Try to find out if the entry is a directory. // If the object is a directory, then save all the files // inside it, otherwise save the file as it is. QString dirName = objectName + '/'; bool isDir = !context.odfLoadingContext().mimeTypeForPath(dirName).isEmpty(); if (isDir) { // A directory: the files can be found in the manifest. foreach (KoOdfManifestEntry *entry, manifest) { if (entry->fullPath() == dirName) continue; if (entry->fullPath().startsWith(dirName)) { d->storeFile(entry->fullPath(), context); } } } else { // A file: save it. d->storeFile(objectName, context); } // Get the manifest entry for this object. KoOdfManifestEntry *entry = 0; QString entryName = isDir ? dirName : objectName; for (int j = 0; j < manifest.size(); ++j) { KoOdfManifestEntry *temp = manifest.value(j); if (temp->fullPath() == entryName) { entry = new KoOdfManifestEntry(*temp); break; } } object->isDir = isDir; object->manifestEntry = entry; // If we have not already found a preview in previous times // through the loop, then see if this one may be a preview. if (!foundPreview) { debugFlake << "Attempting to load preview from " << objectName; QByteArray previewData = d->loadFile(objectName, context); // Check to see if we know the mimetype for this entry. Specifically: // 1. Check to see if the item is a loadable SVG file // FIXME: Perhaps check in the manifest first? But this // seems to work well. d->scalablePreview->load(previewData); if (d->scalablePreview->isValid()) { debugFlake << "Found scalable preview image!"; d->scalablePreview->setViewBox(d->scalablePreview->boundsOnElement("svg")); foundPreview = true; continue; } // 2. Otherwise check to see if it's a loadable pixmap file d->pixmapPreview.loadFromData(previewData); if (!d->pixmapPreview.isNull()) { debugFlake << "Found pixel based preview image!"; foundPreview = true; } } } #if 0 // Enable to get more detailed debug messages debugFlake << "Object manifest entries:"; for (int i = 0; i < d->manifestEntries.size(); ++i) { KoOdfManifestEntry *entry = d->manifestEntries.value(i); debugFlake << i << ":" << entry; if (entry) debugFlake << entry->fullPath() << entry->mediaType() << entry->version(); else debugFlake << "--"; } debugFlake << "END LOADING ####################################################"; #endif return true; } // Load the actual contents inside the frame. bool KoUnavailShape::loadOdfFrameElement(const KoXmlElement & /*element*/, KoShapeLoadingContext &/*context*/) { return true; } // ---------------------------------------------------------------- // Private functions void KoUnavailShape::Private::storeObjects(const KoXmlElement &element) { // 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 = new ObjectEntry; 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. object->manifestEntry = 0; objectEntries.append(object); } } void KoUnavailShape::Private::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()); +#ifndef KOXML_USE_QDOM // Copy all the attributes, including namespaces. QList< QPair > attributeNames = el.attributeFullNames(); for (int i = 0; i < attributeNames.size(); ++i) { QPair attrPair(attributeNames.value(i)); if (attrPair.first.isEmpty()) { writer.addAttribute(attrPair.second.toLatin1(), el.attribute(attrPair.second)); } else { // This somewhat convoluted code is because we need the // namespace, not the namespace URI. QString nsShort = KoXmlNS::nsURI2NS(attrPair.first.toLatin1()); // in case we don't find the namespace in our list create a own one and use that // so the document created on saving is valid. if (nsShort.isEmpty()) { nsShort = unknownNamespaces.value(attrPair.first); if (nsShort.isEmpty()) { nsShort = QString("ns%1").arg(unknownNamespaces.size() + 1); unknownNamespaces.insert(attrPair.first, nsShort); } QString name = QString("xmlns:") + nsShort; writer.addAttribute(name.toLatin1(), attrPair.first.toLatin1()); } QString attr(nsShort + ':' + attrPair.second); writer.addAttribute(attr.toLatin1(), el.attributeNS(attrPair.first, attrPair.second)); } } - +#endif // 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(); } /** * This function stores the embedded file in an internal store - it does not save files to disk, * and thus it is named in this manner, to avoid the function being confused with functions which * save files to disk. */ void KoUnavailShape::Private::storeFile(const QString &fileName, KoShapeLoadingContext &context) { debugFlake << "Saving file: " << fileName; // Directories need to be saved too, but they don't have any file contents. if (fileName.endsWith('/')) { FileEntry *entry = new FileEntry; entry->path = fileName; entry->mimeType = context.odfLoadingContext().mimeTypeForPath(entry->path); entry->isDir = true; embeddedFiles.append(entry); } QByteArray fileContent = loadFile(fileName, context); if (fileContent.isNull()) return; // Actually store the file in the list. FileEntry *entry = new FileEntry; 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; embeddedFiles.append(entry); debugFlake << "File length: " << fileContent.size(); } QByteArray KoUnavailShape::Private::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; } diff --git a/libs/flake/svg/SvgCssHelper.h b/libs/flake/svg/SvgCssHelper.h index a3f26dfc32..5ddd5b3141 100644 --- a/libs/flake/svg/SvgCssHelper.h +++ b/libs/flake/svg/SvgCssHelper.h @@ -1,48 +1,48 @@ /* This file is part of the KDE project * Copyright (C) 2009 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 SVGCSSHELPER_H #define SVGCSSHELPER_H #include -class KoXmlElement; +#include class SvgCssHelper { public: SvgCssHelper(); ~SvgCssHelper(); /// Parses css style sheet in given xml element void parseStylesheet(const KoXmlElement &); /** * Matches css styles to given xml element and returns them * @param element the element to match styles for * @return list of matching css styles sorted by priority */ QStringList matchStyles(const KoXmlElement &element) const; private: class Private; Private * const d; }; #endif // SVGCSSHELPER_H diff --git a/libs/flake/svg/SvgParser.cpp b/libs/flake/svg/SvgParser.cpp index 970730f12f..35de6b9972 100644 --- a/libs/flake/svg/SvgParser.cpp +++ b/libs/flake/svg/SvgParser.cpp @@ -1,1544 +1,1545 @@ /* This file is part of the KDE project * Copyright (C) 2002-2005,2007 Rob Buis * Copyright (C) 2002-2004 Nicolas Goutte * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005-2009 Jan Hambrecht * Copyright (C) 2005,2007 Thomas Zander * Copyright (C) 2006-2007 Inge Wallin * Copyright (C) 2007-2008,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 "SvgParser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include "KoFilterEffectStack.h" #include "KoFilterEffectLoadingContext.h" #include #include #include #include "SvgUtil.h" #include "SvgShape.h" #include "SvgGraphicContext.h" #include "SvgFilterHelper.h" #include "SvgGradientHelper.h" #include "SvgClipPathHelper.h" #include "parsers/SvgTransformParser.h" #include "kis_pointer_utils.h" #include #include #include "kis_debug.h" #include "kis_global.h" SvgParser::SvgParser(KoDocumentResourceManager *documentResourceManager) : m_context(documentResourceManager) , m_documentResourceManager(documentResourceManager) { } SvgParser::~SvgParser() { } void SvgParser::setXmlBaseDir(const QString &baseDir) { m_context.setInitialXmlBaseDir(baseDir); setFileFetcher( [this](const QString &name) { const QString fileName = m_context.xmlBaseDir() + QDir::separator() + name; QFile file(fileName); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(file.exists(), QByteArray()); file.open(QIODevice::ReadOnly); return file.readAll(); }); } void SvgParser::setResolution(const QRectF boundsInPixels, qreal pixelsPerInch) { KIS_ASSERT(!m_context.currentGC()); m_context.pushGraphicsContext(); m_context.currentGC()->pixelsPerInch = pixelsPerInch; const qreal scale = 72.0 / pixelsPerInch; const QTransform t = QTransform::fromScale(scale, scale); m_context.currentGC()->currentBoundingBox = boundsInPixels; m_context.currentGC()->matrix = t; } QList SvgParser::shapes() const { return m_shapes; } // Helper functions // --------------------------------------------------------------------------------------- SvgGradientHelper* SvgParser::findGradient(const QString &id) { SvgGradientHelper *result = 0; // check if gradient was already parsed, and return it if (m_gradients.contains(id)) { result = &m_gradients[ id ]; } // check if gradient was stored for later parsing if (!result && m_context.hasDefinition(id)) { const KoXmlElement &e = m_context.definition(id); if (e.tagName().contains("Gradient")) { result = parseGradient(m_context.definition(id)); } } return result; } QSharedPointer SvgParser::findPattern(const QString &id, const KoShape *shape) { QSharedPointer result; // check if gradient was stored for later parsing if (m_context.hasDefinition(id)) { const KoXmlElement &e = m_context.definition(id); if (e.tagName() == "pattern") { result = parsePattern(m_context.definition(id), shape); } } return result; } SvgFilterHelper* SvgParser::findFilter(const QString &id, const QString &href) { // check if filter was already parsed, and return it if (m_filters.contains(id)) return &m_filters[ id ]; // check if filter was stored for later parsing if (!m_context.hasDefinition(id)) return 0; const KoXmlElement &e = m_context.definition(id); - if (e.childNodesCount() == 0) { + if (KoXml::childNodesCount(e) == 0) { QString mhref = e.attribute("xlink:href").mid(1); if (m_context.hasDefinition(mhref)) return findFilter(mhref, id); else return 0; } else { // ok parse filter now if (! parseFilter(m_context.definition(id), m_context.definition(href))) return 0; } // return successfully parsed filter or 0 QString n; if (href.isEmpty()) n = id; else n = href; if (m_filters.contains(n)) return &m_filters[ n ]; else return 0; } SvgClipPathHelper* SvgParser::findClipPath(const QString &id) { return m_clipPaths.contains(id) ? &m_clipPaths[id] : 0; } // Parsing functions // --------------------------------------------------------------------------------------- qreal SvgParser::parseUnit(const QString &unit, bool horiz, bool vert, const QRectF &bbox) { return SvgUtil::parseUnit(m_context.currentGC(), unit, horiz, vert, bbox); } qreal SvgParser::parseUnitX(const QString &unit) { return SvgUtil::parseUnitX(m_context.currentGC(), unit); } qreal SvgParser::parseUnitY(const QString &unit) { return SvgUtil::parseUnitY(m_context.currentGC(), unit); } qreal SvgParser::parseUnitXY(const QString &unit) { return SvgUtil::parseUnitXY(m_context.currentGC(), unit); } qreal SvgParser::parseAngular(const QString &unit) { return SvgUtil::parseUnitAngular(m_context.currentGC(), unit); } SvgGradientHelper* SvgParser::parseGradient(const KoXmlElement &e) { // IMPROVEMENTS: // - Store the parsed colorstops in some sort of a cache so they don't need to be parsed again. // - A gradient inherits attributes it does not have from the referencing gradient. // - Gradients with no color stops have no fill or stroke. // - Gradients with one color stop have a solid color. SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return 0; SvgGradientHelper gradHelper; QString gradientId = e.attribute("id"); if (gradientId.isEmpty()) return 0; // check if we have this gradient already parsed // copy existing gradient if it exists if (m_gradients.contains(gradientId)) { return &m_gradients[gradientId]; } if (e.hasAttribute("xlink:href")) { // strip the '#' symbol QString href = e.attribute("xlink:href").mid(1); if (!href.isEmpty()) { // copy the referenced gradient if found SvgGradientHelper *pGrad = findGradient(href); if (pGrad) { gradHelper = *pGrad; } } } const QGradientStops defaultStops = gradHelper.gradient()->stops(); if (e.attribute("gradientUnits") == "userSpaceOnUse") { gradHelper.setGradientUnits(KoFlake::UserSpaceOnUse); } m_context.pushGraphicsContext(e); uploadStyleToContext(e); if (e.tagName() == "linearGradient") { QLinearGradient *g = new QLinearGradient(); if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setStart(QPointF(SvgUtil::fromPercentage(e.attribute("x1", "0%")), SvgUtil::fromPercentage(e.attribute("y1", "0%")))); g->setFinalStop(QPointF(SvgUtil::fromPercentage(e.attribute("x2", "100%")), SvgUtil::fromPercentage(e.attribute("y2", "0%")))); } else { g->setStart(QPointF(parseUnitX(e.attribute("x1")), parseUnitY(e.attribute("y1")))); g->setFinalStop(QPointF(parseUnitX(e.attribute("x2")), parseUnitY(e.attribute("y2")))); } gradHelper.setGradient(g); } else if (e.tagName() == "radialGradient") { QRadialGradient *g = new QRadialGradient(); if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setCenter(QPointF(SvgUtil::fromPercentage(e.attribute("cx", "50%")), SvgUtil::fromPercentage(e.attribute("cy", "50%")))); g->setRadius(SvgUtil::fromPercentage(e.attribute("r", "50%"))); g->setFocalPoint(QPointF(SvgUtil::fromPercentage(e.attribute("fx", "50%")), SvgUtil::fromPercentage(e.attribute("fy", "50%")))); } else { g->setCenter(QPointF(parseUnitX(e.attribute("cx")), parseUnitY(e.attribute("cy")))); g->setFocalPoint(QPointF(parseUnitX(e.attribute("fx")), parseUnitY(e.attribute("fy")))); g->setRadius(parseUnitXY(e.attribute("r"))); } gradHelper.setGradient(g); } else { qDebug() << "WARNING: Failed to parse gradient with tag" << e.tagName(); } // handle spread method QGradient::Spread spreadMethod = QGradient::PadSpread; QString spreadMethodStr = e.attribute("spreadMethod"); if (!spreadMethodStr.isEmpty()) { if (spreadMethodStr == "reflect") { spreadMethod = QGradient::ReflectSpread; } else if (spreadMethodStr == "repeat") { spreadMethod = QGradient::RepeatSpread; } } gradHelper.setSpreadMode(spreadMethod); // Parse the color stops. m_context.styleParser().parseColorStops(gradHelper.gradient(), e, gc, defaultStops); if (e.hasAttribute("gradientTransform")) { SvgTransformParser p(e.attribute("gradientTransform")); if (p.isValid()) { gradHelper.setTransform(p.transform()); } } m_context.popGraphicsContext(); m_gradients.insert(gradientId, gradHelper); return &m_gradients[gradientId]; } inline QPointF bakeShapeOffset(const QTransform &patternTransform, const QPointF &shapeOffset) { QTransform result = patternTransform * QTransform::fromTranslate(-shapeOffset.x(), -shapeOffset.y()) * patternTransform.inverted(); KIS_ASSERT_RECOVER_NOOP(result.type() <= QTransform::TxTranslate); return QPointF(result.dx(), result.dy()); } QSharedPointer SvgParser::parsePattern(const KoXmlElement &e, const KoShape *shape) { /** * Unlike the gradient parsing function, this method is called every time we * *reference* the pattern, not when we define it. Therefore we can already * use the coordinate system of the destination. */ QSharedPointer pattHelper; SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return pattHelper; const QString patternId = e.attribute("id"); if (patternId.isEmpty()) return pattHelper; pattHelper = toQShared(new KoVectorPatternBackground); if (e.hasAttribute("xlink:href")) { // strip the '#' symbol QString href = e.attribute("xlink:href").mid(1); if (!href.isEmpty() &&href != patternId) { // copy the referenced pattern if found QSharedPointer pPatt = findPattern(href, shape); if (pPatt) { pattHelper = pPatt; } } } pattHelper->setReferenceCoordinates( KoFlake::coordinatesFromString(e.attribute("patternUnits"), pattHelper->referenceCoordinates())); pattHelper->setContentCoordinates( KoFlake::coordinatesFromString(e.attribute("patternContentUnits"), pattHelper->contentCoordinates())); if (e.hasAttribute("patternTransform")) { SvgTransformParser p(e.attribute("patternTransform")); if (p.isValid()) { pattHelper->setPatternTransform(p.transform()); } } if (pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox) { QRectF referenceRect( SvgUtil::fromPercentage(e.attribute("x", "0%")), SvgUtil::fromPercentage(e.attribute("y", "0%")), SvgUtil::fromPercentage(e.attribute("width", "0%")), // 0% is according to SVG 1.1, don't ask me why! SvgUtil::fromPercentage(e.attribute("height", "0%"))); // 0% is according to SVG 1.1, don't ask me why! pattHelper->setReferenceRect(referenceRect); } else { QRectF referenceRect( parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0")), parseUnitX(e.attribute("width", "0")), // 0 is according to SVG 1.1, don't ask me why! parseUnitY(e.attribute("height", "0"))); // 0 is according to SVG 1.1, don't ask me why! pattHelper->setReferenceRect(referenceRect); } /** * In Krita shapes X,Y coordinates are baked into the the shape global transform, but * the pattern should be painted in "user" coordinates. Therefore, we should handle * this offfset separately. * * TODO: Please also not that this offset is different from extraShapeOffset(), * because A.inverted() * B != A * B.inverted(). I'm not sure which variant is * correct (DK) */ const QTransform dstShapeTransform = shape->absoluteTransformation(0); const QTransform shapeOffsetTransform = dstShapeTransform * gc->matrix.inverted(); KIS_SAFE_ASSERT_RECOVER_NOOP(shapeOffsetTransform.type() <= QTransform::TxTranslate); const QPointF extraShapeOffset(shapeOffsetTransform.dx(), shapeOffsetTransform.dy()); m_context.pushGraphicsContext(e); gc = m_context.currentGC(); gc->workaroundClearInheritedFillProperties(); // HACK! // start building shape tree from scratch gc->matrix = QTransform(); const QRectF boundingRect = shape->outline().boundingRect()/*.translated(extraShapeOffset)*/; const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); // WARNING1: OBB and ViewBox transformations are *baked* into the pattern shapes! // although we expect the pattern be reusable, but it is not so! // WARNING2: the pattern shapes are stored in *User* coordinate system, although // the "official" content system might be either OBB or User. It means that // this baked transform should be stripped before writing the shapes back // into SVG if (e.hasAttribute("viewBox")) { gc->currentBoundingBox = pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox ? relativeToShape.mapRect(pattHelper->referenceRect()) : pattHelper->referenceRect(); applyViewBoxTransform(e); pattHelper->setContentCoordinates(pattHelper->referenceCoordinates()); } else if (pattHelper->contentCoordinates() == KoFlake::ObjectBoundingBox) { gc->matrix = relativeToShape * gc->matrix; } // We do *not* apply patternTransform here! Here we only bake the untransformed // version of the shape. The transformed one will be done in the very end while rendering. QList patternShapes = parseContainer(e); if (pattHelper->contentCoordinates() == KoFlake::UserSpaceOnUse) { // In Krita we normalize the shapes, bake this transform into the pattern shapes const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset); Q_FOREACH (KoShape *shape, patternShapes) { shape->applyAbsoluteTransformation(QTransform::fromTranslate(offset.x(), offset.y())); } } if (pattHelper->referenceCoordinates() == KoFlake::UserSpaceOnUse) { // In Krita we normalize the shapes, bake this transform into reference rect // NOTE: this is possible *only* when pattern transform is not perspective // (which is always true for SVG) const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset); QRectF ref = pattHelper->referenceRect(); ref.translate(offset); pattHelper->setReferenceRect(ref); } m_context.popGraphicsContext(); gc = m_context.currentGC(); if (!patternShapes.isEmpty()) { pattHelper->setShapes(patternShapes); } return pattHelper; } bool SvgParser::parseFilter(const KoXmlElement &e, const KoXmlElement &referencedBy) { SvgFilterHelper filter; // Use the filter that is referencing, or if there isn't one, the original filter KoXmlElement b; if (!referencedBy.isNull()) b = referencedBy; else b = e; // check if we are referencing another filter if (e.hasAttribute("xlink:href")) { QString href = e.attribute("xlink:href").mid(1); if (! href.isEmpty()) { // copy the referenced filter if found SvgFilterHelper *refFilter = findFilter(href); if (refFilter) filter = *refFilter; } } else { filter.setContent(b); } if (b.attribute("filterUnits") == "userSpaceOnUse") filter.setFilterUnits(KoFlake::UserSpaceOnUse); if (b.attribute("primitiveUnits") == "objectBoundingBox") filter.setPrimitiveUnits(KoFlake::ObjectBoundingBox); // parse filter region rectangle if (filter.filterUnits() == KoFlake::UserSpaceOnUse) { filter.setPosition(QPointF(parseUnitX(b.attribute("x")), parseUnitY(b.attribute("y")))); filter.setSize(QSizeF(parseUnitX(b.attribute("width")), parseUnitY(b.attribute("height")))); } else { // x, y, width, height are in percentages of the object referencing the filter // so we just parse the percentages filter.setPosition(QPointF(SvgUtil::fromPercentage(b.attribute("x", "-0.1")), SvgUtil::fromPercentage(b.attribute("y", "-0.1")))); filter.setSize(QSizeF(SvgUtil::fromPercentage(b.attribute("width", "1.2")), SvgUtil::fromPercentage(b.attribute("height", "1.2")))); } m_filters.insert(b.attribute("id"), filter); return true; } bool SvgParser::parseMarker(const KoXmlElement &e) { const QString id = e.attribute("id"); if (id.isEmpty()) return false; QScopedPointer marker(new KoMarker()); marker->setCoordinateSystem( KoMarker::coordinateSystemFromString(e.attribute("markerUnits", "strokeWidth"))); marker->setReferencePoint(QPointF(parseUnitX(e.attribute("refX")), parseUnitY(e.attribute("refY")))); marker->setReferenceSize(QSizeF(parseUnitX(e.attribute("markerWidth", "3")), parseUnitY(e.attribute("markerHeight", "3")))); const QString orientation = e.attribute("orient", "0"); if (orientation == "auto") { marker->setAutoOrientation(true); } else { marker->setExplicitOrientation(parseAngular(orientation)); } // ensure that the clip path is loaded in local coordinates system m_context.pushGraphicsContext(e, false); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->currentBoundingBox = QRectF(QPointF(0, 0), marker->referenceSize()); KoShape *markerShape = parseGroup(e); m_context.popGraphicsContext(); if (!markerShape) return false; marker->setShapes({markerShape}); m_markers.insert(id, QExplicitlySharedDataPointer(marker.take())); return true; } bool SvgParser::parseClipPath(const KoXmlElement &e) { SvgClipPathHelper clipPath; const QString id = e.attribute("id"); if (id.isEmpty()) return false; clipPath.setClipPathUnits( KoFlake::coordinatesFromString(e.attribute("clipPathUnits"), KoFlake::UserSpaceOnUse)); // ensure that the clip path is loaded in local coordinates system m_context.pushGraphicsContext(e); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK! KoShape *clipShape = parseGroup(e); m_context.popGraphicsContext(); if (!clipShape) return false; clipPath.setShapes({clipShape}); m_clipPaths.insert(id, clipPath); return true; } bool SvgParser::parseClipMask(const KoXmlElement &e) { QSharedPointer clipMask(new KoClipMask); const QString id = e.attribute("id"); if (id.isEmpty()) return false; clipMask->setCoordinates(KoFlake::coordinatesFromString(e.attribute("maskUnits"), KoFlake::ObjectBoundingBox)); clipMask->setContentCoordinates(KoFlake::coordinatesFromString(e.attribute("maskContentUnits"), KoFlake::UserSpaceOnUse)); QRectF maskRect; if (clipMask->coordinates() == KoFlake::ObjectBoundingBox) { maskRect.setRect( SvgUtil::fromPercentage(e.attribute("x", "-10%")), SvgUtil::fromPercentage(e.attribute("y", "-10%")), SvgUtil::fromPercentage(e.attribute("width", "120%")), SvgUtil::fromPercentage(e.attribute("height", "120%"))); } else { maskRect.setRect( parseUnitX(e.attribute("x", "-10%")), // yes, percents are insane in this case, parseUnitY(e.attribute("y", "-10%")), // but this is what SVG 1.1 tells us... parseUnitX(e.attribute("width", "120%")), parseUnitY(e.attribute("height", "120%"))); } clipMask->setMaskRect(maskRect); // ensure that the clip mask is loaded in local coordinates system m_context.pushGraphicsContext(e); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK! KoShape *clipShape = parseGroup(e); m_context.popGraphicsContext(); if (!clipShape) return false; clipMask->setShapes({clipShape}); m_clipMasks.insert(id, clipMask); return true; } void SvgParser::uploadStyleToContext(const KoXmlElement &e) { SvgStyles styles = m_context.styleParser().collectStyles(e); m_context.styleParser().parseFont(styles); m_context.styleParser().parseStyle(styles); } void SvgParser::applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { if (!shape) return; SvgGraphicsContext *gc = m_context.currentGC(); KIS_ASSERT(gc); if (!dynamic_cast(shape)) { applyFillStyle(shape); applyStrokeStyle(shape); } if (KoPathShape *pathShape = dynamic_cast(shape)) { applyMarkers(pathShape); } applyFilter(shape); applyClipping(shape, shapeToOriginalUserCoordinates); applyMaskClipping(shape, shapeToOriginalUserCoordinates); if (!gc->display || !gc->visible) { /** * WARNING: here is a small inconsistency with the standard: * in the standard, 'display' is not inherited, but in * flake it is! * * NOTE: though the standard says: "A value of 'display:none' indicates * that the given element and ***its children*** shall not be * rendered directly". Therefore, using setVisible(false) is fully * legitimate here (DK 29.11.16). */ shape->setVisible(false); } shape->setTransparency(1.0 - gc->opacity); } void SvgParser::applyStyle(KoShape *obj, const KoXmlElement &e, const QPointF &shapeToOriginalUserCoordinates) { applyStyle(obj, m_context.styleParser().collectStyles(e), shapeToOriginalUserCoordinates); } void SvgParser::applyStyle(KoShape *obj, const SvgStyles &styles, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; m_context.styleParser().parseStyle(styles); if (!obj) return; if (!dynamic_cast(obj)) { applyFillStyle(obj); applyStrokeStyle(obj); } if (KoPathShape *pathShape = dynamic_cast(obj)) { applyMarkers(pathShape); } applyFilter(obj); applyClipping(obj, shapeToOriginalUserCoordinates); applyMaskClipping(obj, shapeToOriginalUserCoordinates); if (!gc->display || !gc->visible) { obj->setVisible(false); } obj->setTransparency(1.0 - gc->opacity); } QGradient* prepareGradientForShape(const SvgGradientHelper *gradient, const KoShape *shape, const SvgGraphicsContext *gc, QTransform *transform) { QGradient *resultGradient = 0; KIS_ASSERT(transform); if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) { resultGradient = KoFlake::cloneGradient(gradient->gradient()); *transform = gradient->transform(); } else { if (gradient->gradient()->type() == QGradient::LinearGradient) { /** * Create a converted gradient that looks the same, but linked to the * bounding rect of the shape, so it would be transformed with the shape */ const QRectF boundingRect = shape->outline().boundingRect(); const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); const QTransform relativeToUser = relativeToShape * shape->transformation() * gc->matrix.inverted(); const QTransform userToRelative = relativeToUser.inverted(); const QLinearGradient *o = static_cast(gradient->gradient()); QLinearGradient *g = new QLinearGradient(); g->setStart(userToRelative.map(o->start())); g->setFinalStop(userToRelative.map(o->finalStop())); g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setStops(o->stops()); g->setSpread(o->spread()); resultGradient = g; *transform = relativeToUser * gradient->transform() * userToRelative; } else if (gradient->gradient()->type() == QGradient::RadialGradient) { // For radial and conical gradients such conversion is not possible resultGradient = KoFlake::cloneGradient(gradient->gradient()); *transform = gradient->transform() * gc->matrix * shape->transformation().inverted(); } } return resultGradient; } void SvgParser::applyFillStyle(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->fillType == SvgGraphicsContext::None) { shape->setBackground(QSharedPointer(0)); } else if (gc->fillType == SvgGraphicsContext::Solid) { shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor))); } else if (gc->fillType == SvgGraphicsContext::Complex) { // try to find referenced gradient SvgGradientHelper *gradient = findGradient(gc->fillId); if (gradient) { QTransform transform; QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform); if (result) { QSharedPointer bg; bg = toQShared(new KoGradientBackground(result)); bg->setTransform(transform); shape->setBackground(bg); } } else { QSharedPointer pattern = findPattern(gc->fillId, shape); if (pattern) { shape->setBackground(pattern); } else { // no referenced fill found, use fallback color shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor))); } } } KoPathShape *path = dynamic_cast(shape); if (path) path->setFillRule(gc->fillRule); } void applyDashes(const KoShapeStrokeSP srcStroke, KoShapeStrokeSP dstStroke) { const double lineWidth = srcStroke->lineWidth(); QVector dashes = srcStroke->lineDashes(); // apply line width to dashes and dash offset if (dashes.count() && lineWidth > 0.0) { const double dashOffset = srcStroke->dashOffset(); QVector dashes = srcStroke->lineDashes(); for (int i = 0; i < dashes.count(); ++i) { dashes[i] /= lineWidth; } dstStroke->setLineStyle(Qt::CustomDashLine, dashes); dstStroke->setDashOffset(dashOffset / lineWidth); } else { dstStroke->setLineStyle(Qt::SolidLine, QVector()); } } void SvgParser::applyStrokeStyle(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->strokeType == SvgGraphicsContext::None) { shape->setStroke(KoShapeStrokeModelSP()); } else if (gc->strokeType == SvgGraphicsContext::Solid) { KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } else if (gc->strokeType == SvgGraphicsContext::Complex) { // try to find referenced gradient SvgGradientHelper *gradient = findGradient(gc->strokeId); if (gradient) { QTransform transform; QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform); if (result) { QBrush brush = *result; delete result; brush.setTransform(transform); KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); stroke->setLineBrush(brush); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } } else { // no referenced stroke found, use fallback color KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } } } void SvgParser::applyFilter(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->filterId.isEmpty()) return; SvgFilterHelper *filter = findFilter(gc->filterId); if (! filter) return; KoXmlElement content = filter->content(); // parse filter region QRectF bound(shape->position(), shape->size()); // work on bounding box without viewbox tranformation applied // so user space coordinates of bounding box and filter region match up bound = gc->viewboxTransform.inverted().mapRect(bound); QRectF filterRegion(filter->position(bound), filter->size(bound)); // convert filter region to boundingbox units QRectF objectFilterRegion; objectFilterRegion.setTopLeft(SvgUtil::userSpaceToObject(filterRegion.topLeft(), bound)); objectFilterRegion.setSize(SvgUtil::userSpaceToObject(filterRegion.size(), bound)); KoFilterEffectLoadingContext context(m_context.xmlBaseDir()); context.setShapeBoundingBox(bound); // enable units conversion context.enableFilterUnitsConversion(filter->filterUnits() == KoFlake::UserSpaceOnUse); context.enableFilterPrimitiveUnitsConversion(filter->primitiveUnits() == KoFlake::UserSpaceOnUse); KoFilterEffectRegistry *registry = KoFilterEffectRegistry::instance(); KoFilterEffectStack *filterStack = 0; QSet stdInputs; stdInputs << "SourceGraphic" << "SourceAlpha"; stdInputs << "BackgroundImage" << "BackgroundAlpha"; stdInputs << "FillPaint" << "StrokePaint"; QMap inputs; // create the filter effects and add them to the shape for (KoXmlNode n = content.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement primitive = n.toElement(); KoFilterEffect *filterEffect = registry->createFilterEffectFromXml(primitive, context); if (!filterEffect) { debugFlake << "filter effect" << primitive.tagName() << "is not implemented yet"; continue; } const QString input = primitive.attribute("in"); if (!input.isEmpty()) { filterEffect->setInput(0, input); } const QString output = primitive.attribute("result"); if (!output.isEmpty()) { filterEffect->setOutput(output); } QRectF subRegion; // parse subregion if (filter->primitiveUnits() == KoFlake::UserSpaceOnUse) { const QString xa = primitive.attribute("x"); const QString ya = primitive.attribute("y"); const QString wa = primitive.attribute("width"); const QString ha = primitive.attribute("height"); if (xa.isEmpty() || ya.isEmpty() || wa.isEmpty() || ha.isEmpty()) { bool hasStdInput = false; bool isFirstEffect = filterStack == 0; // check if one of the inputs is a standard input Q_FOREACH (const QString &input, filterEffect->inputs()) { if ((isFirstEffect && input.isEmpty()) || stdInputs.contains(input)) { hasStdInput = true; break; } } if (hasStdInput || primitive.tagName() == "feImage") { // default to 0%, 0%, 100%, 100% subRegion.setTopLeft(QPointF(0, 0)); subRegion.setSize(QSizeF(1, 1)); } else { // defaults to bounding rect of all referenced nodes Q_FOREACH (const QString &input, filterEffect->inputs()) { if (!inputs.contains(input)) continue; KoFilterEffect *inputFilter = inputs[input]; if (inputFilter) subRegion |= inputFilter->filterRect(); } } } else { const qreal x = parseUnitX(xa); const qreal y = parseUnitY(ya); const qreal w = parseUnitX(wa); const qreal h = parseUnitY(ha); subRegion.setTopLeft(SvgUtil::userSpaceToObject(QPointF(x, y), bound)); subRegion.setSize(SvgUtil::userSpaceToObject(QSizeF(w, h), bound)); } } else { // x, y, width, height are in percentages of the object referencing the filter // so we just parse the percentages const qreal x = SvgUtil::fromPercentage(primitive.attribute("x", "0")); const qreal y = SvgUtil::fromPercentage(primitive.attribute("y", "0")); const qreal w = SvgUtil::fromPercentage(primitive.attribute("width", "1")); const qreal h = SvgUtil::fromPercentage(primitive.attribute("height", "1")); subRegion = QRectF(QPointF(x, y), QSizeF(w, h)); } filterEffect->setFilterRect(subRegion); if (!filterStack) filterStack = new KoFilterEffectStack(); filterStack->appendFilterEffect(filterEffect); inputs[filterEffect->output()] = filterEffect; } if (filterStack) { filterStack->setClipRect(objectFilterRegion); shape->setFilterEffectStack(filterStack); } } void SvgParser::applyMarkers(KoPathShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; if (!gc->markerStartId.isEmpty() && m_markers.contains(gc->markerStartId)) { shape->setMarker(m_markers[gc->markerStartId].data(), KoFlake::StartMarker); } if (!gc->markerMidId.isEmpty() && m_markers.contains(gc->markerMidId)) { shape->setMarker(m_markers[gc->markerMidId].data(), KoFlake::MidMarker); } if (!gc->markerEndId.isEmpty() && m_markers.contains(gc->markerEndId)) { shape->setMarker(m_markers[gc->markerEndId].data(), KoFlake::EndMarker); } shape->setAutoFillMarkers(gc->autoFillMarkers); } void SvgParser::applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->clipPathId.isEmpty()) return; SvgClipPathHelper *clipPath = findClipPath(gc->clipPathId); if (!clipPath || clipPath->isEmpty()) return; QList shapes; Q_FOREACH (KoShape *item, clipPath->shapes()) { KoShape *clonedShape = item->cloneShape(); KIS_ASSERT_RECOVER(clonedShape) { continue; } shapes.append(clonedShape); } if (!shapeToOriginalUserCoordinates.isNull()) { const QTransform t = QTransform::fromTranslate(shapeToOriginalUserCoordinates.x(), shapeToOriginalUserCoordinates.y()); Q_FOREACH(KoShape *s, shapes) { s->applyAbsoluteTransformation(t); } } KoClipPath *clipPathObject = new KoClipPath(shapes, clipPath->clipPathUnits() == KoFlake::ObjectBoundingBox ? KoFlake::ObjectBoundingBox : KoFlake::UserSpaceOnUse); shape->setClipPath(clipPathObject); } void SvgParser::applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; if (gc->clipMaskId.isEmpty()) return; QSharedPointer originalClipMask = m_clipMasks.value(gc->clipMaskId); if (!originalClipMask || originalClipMask->isEmpty()) return; KoClipMask *clipMask = originalClipMask->clone(); clipMask->setExtraShapeOffset(shapeToOriginalUserCoordinates); shape->setClipMask(clipMask); } KoShape* SvgParser::parseUse(const KoXmlElement &e) { KoShape *result = 0; QString id = e.attribute("xlink:href"); if (!id.isEmpty()) { QString key = id.mid(1); if (m_context.hasDefinition(key)) { SvgGraphicsContext *gc = m_context.pushGraphicsContext(e); // TODO: parse 'width' and 'hight' as well gc->matrix.translate(parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0"))); const KoXmlElement &referencedElement = m_context.definition(key); result = parseGroup(e, referencedElement); m_context.popGraphicsContext(); } } return result; } void SvgParser::addToGroup(QList shapes, KoShapeGroup *group) { m_shapes += shapes; if (!group || shapes.isEmpty()) return; // not clipped, but inherit transform KoShapeGroupCommand cmd(group, shapes, false, true, false); cmd.redo(); } QList SvgParser::parseSvg(const KoXmlElement &e, QSizeF *fragmentSize) { // check if we are the root svg element const bool isRootSvg = m_context.isRootContext(); // parse 'transform' field if preset SvgGraphicsContext *gc = m_context.pushGraphicsContext(e); applyStyle(0, e, QPointF()); const QString w = e.attribute("width"); const QString h = e.attribute("height"); const qreal width = w.isEmpty() ? 666.0 : parseUnitX(w); const qreal height = h.isEmpty() ? 555.0 : parseUnitY(h); QSizeF svgFragmentSize(QSizeF(width, height)); if (fragmentSize) { *fragmentSize = svgFragmentSize; } gc->currentBoundingBox = QRectF(QPointF(0, 0), svgFragmentSize); if (!isRootSvg) { // x and y attribute has no meaning for outermost svg elements const qreal x = parseUnit(e.attribute("x", "0")); const qreal y = parseUnit(e.attribute("y", "0")); QTransform move = QTransform::fromTranslate(x, y); gc->matrix = move * gc->matrix; } applyViewBoxTransform(e); QList shapes; // SVG 1.1: skip the rendering of the element if it has null viewBox if (gc->currentBoundingBox.isValid()) { shapes = parseContainer(e); } m_context.popGraphicsContext(); return shapes; } void SvgParser::applyViewBoxTransform(const KoXmlElement &element) { SvgGraphicsContext *gc = m_context.currentGC(); QRectF viewRect = gc->currentBoundingBox; QTransform viewTransform; if (SvgUtil::parseViewBox(gc, element, gc->currentBoundingBox, &viewRect, &viewTransform)) { gc->matrix = viewTransform * gc->matrix; gc->currentBoundingBox = viewRect; } } QList > SvgParser::knownMarkers() const { return m_markers.values(); } void SvgParser::setFileFetcher(SvgParser::FileFetcherFunc func) { m_context.setFileFetcher(func); } inline QPointF extraShapeOffset(const KoShape *shape, const QTransform coordinateSystemOnLoading) { const QTransform shapeToOriginalUserCoordinates = shape->absoluteTransformation(0).inverted() * coordinateSystemOnLoading; KIS_SAFE_ASSERT_RECOVER_NOOP(shapeToOriginalUserCoordinates.type() <= QTransform::TxTranslate); return QPointF(shapeToOriginalUserCoordinates.dx(), shapeToOriginalUserCoordinates.dy()); } KoShape* SvgParser::parseGroup(const KoXmlElement &b, const KoXmlElement &overrideChildrenFrom) { m_context.pushGraphicsContext(b); KoShapeGroup *group = new KoShapeGroup(); group->setZIndex(m_context.nextZIndex()); // groups should also have their own coordinate system! group->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(group, m_context.currentGC()->matrix); uploadStyleToContext(b); QList childShapes; if (!overrideChildrenFrom.isNull()) { // we upload styles from both: and uploadStyleToContext(overrideChildrenFrom); childShapes = parseSingleElement(overrideChildrenFrom); } else { childShapes = parseContainer(b); } // handle id applyId(b.attribute("id"), group); addToGroup(childShapes, group); applyCurrentStyle(group, extraOffset); // apply style to this group after size is set m_context.popGraphicsContext(); return group; } QList SvgParser::parseContainer(const KoXmlElement &e) { QList shapes; // are we parsing a switch container bool isSwitch = e.tagName() == "switch"; for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement b = n.toElement(); if (b.isNull()) continue; if (isSwitch) { // if we are parsing a switch check the requiredFeatures, requiredExtensions // and systemLanguage attributes // TODO: evaluate feature list if (b.hasAttribute("requiredFeatures")) { continue; } if (b.hasAttribute("requiredExtensions")) { // we do not support any extensions continue; } if (b.hasAttribute("systemLanguage")) { // not implemeted yet } } QList currentShapes = parseSingleElement(b); shapes.append(currentShapes); // if we are parsing a switch, stop after the first supported element if (isSwitch && !currentShapes.isEmpty()) break; } return shapes; } QList SvgParser::parseSingleElement(const KoXmlElement &b) { QList shapes; // save definition for later instantiation with 'use' m_context.addDefinition(b); if (b.tagName() == "svg") { shapes += parseSvg(b); } else if (b.tagName() == "g" || b.tagName() == "a" || b.tagName() == "symbol") { // treat svg link as group so we don't miss its child elements shapes += parseGroup(b); } else if (b.tagName() == "switch") { m_context.pushGraphicsContext(b); shapes += parseContainer(b); m_context.popGraphicsContext(); } else if (b.tagName() == "defs") { - if (b.childNodesCount() > 0) { + if (KoXml::childNodesCount(b) > 0) { /** * WARNING: 'defs' are basically 'display:none' style, therefore they should not play * any role in shapes outline calculation. But setVisible(false) shapes do! * Should be fixed in the future! */ KoShape *defsShape = parseGroup(b); defsShape->setVisible(false); } } else if (b.tagName() == "linearGradient" || b.tagName() == "radialGradient") { } else if (b.tagName() == "pattern") { } else if (b.tagName() == "filter") { parseFilter(b); } else if (b.tagName() == "clipPath") { parseClipPath(b); } else if (b.tagName() == "mask") { parseClipMask(b); } else if (b.tagName() == "marker") { parseMarker(b); } else if (b.tagName() == "style") { m_context.addStyleSheet(b); } else if (b.tagName() == "rect" || b.tagName() == "ellipse" || b.tagName() == "circle" || b.tagName() == "line" || b.tagName() == "polyline" || b.tagName() == "polygon" || b.tagName() == "path" || b.tagName() == "image" || b.tagName() == "text") { KoShape *shape = createObjectDirect(b); if (shape) shapes.append(shape); } else if (b.tagName() == "use") { shapes += parseUse(b); } else if (b.tagName() == "color-profile") { m_context.parseProfile(b); } else { // this is an unknown element, so try to load it anyway // there might be a shape that handles that element KoShape *shape = createObject(b); if (shape) { shapes.append(shape); } } return shapes; } // Creating functions // --------------------------------------------------------------------------------------- KoShape * SvgParser::createPath(const KoXmlElement &element) { KoShape *obj = 0; if (element.tagName() == "line") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { double x1 = element.attribute("x1").isEmpty() ? 0.0 : parseUnitX(element.attribute("x1")); double y1 = element.attribute("y1").isEmpty() ? 0.0 : parseUnitY(element.attribute("y1")); double x2 = element.attribute("x2").isEmpty() ? 0.0 : parseUnitX(element.attribute("x2")); double y2 = element.attribute("y2").isEmpty() ? 0.0 : parseUnitY(element.attribute("y2")); path->clear(); path->moveTo(QPointF(x1, y1)); path->lineTo(QPointF(x2, y2)); path->normalize(); obj = path; } } else if (element.tagName() == "polyline" || element.tagName() == "polygon") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { path->clear(); bool bFirst = true; QString points = element.attribute("points").simplified(); points.replace(',', ' '); points.remove('\r'); points.remove('\n'); QStringList pointList = points.split(' ', QString::SkipEmptyParts); for (QStringList::Iterator it = pointList.begin(); it != pointList.end(); ++it) { QPointF point; point.setX(SvgUtil::fromUserSpace((*it).toDouble())); ++it; if (it == pointList.end()) break; point.setY(SvgUtil::fromUserSpace((*it).toDouble())); if (bFirst) { path->moveTo(point); bFirst = false; } else path->lineTo(point); } if (element.tagName() == "polygon") path->close(); path->setPosition(path->normalize()); obj = path; } } else if (element.tagName() == "path") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { path->clear(); KoPathShapeLoader loader(path); loader.parseSvg(element.attribute("d"), true); path->setPosition(path->normalize()); QPointF newPosition = QPointF(SvgUtil::fromUserSpace(path->position().x()), SvgUtil::fromUserSpace(path->position().y())); QSizeF newSize = QSizeF(SvgUtil::fromUserSpace(path->size().width()), SvgUtil::fromUserSpace(path->size().height())); path->setSize(newSize); path->setPosition(newPosition); obj = path; } } return obj; } KoShape * SvgParser::createObjectDirect(const KoXmlElement &b) { m_context.pushGraphicsContext(b); uploadStyleToContext(b); KoShape *obj = createShapeFromElement(b, m_context); if (obj) { obj->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix); applyCurrentStyle(obj, extraOffset); // handle id applyId(b.attribute("id"), obj); obj->setZIndex(m_context.nextZIndex()); } m_context.popGraphicsContext(); return obj; } KoShape * SvgParser::createObject(const KoXmlElement &b, const SvgStyles &style) { m_context.pushGraphicsContext(b); KoShape *obj = createShapeFromElement(b, m_context); if (obj) { obj->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix); SvgStyles objStyle = style.isEmpty() ? m_context.styleParser().collectStyles(b) : style; m_context.styleParser().parseFont(objStyle); applyStyle(obj, objStyle, extraOffset); // handle id applyId(b.attribute("id"), obj); obj->setZIndex(m_context.nextZIndex()); } m_context.popGraphicsContext(); return obj; } KoShape * SvgParser::createShapeFromElement(const KoXmlElement &element, SvgLoadingContext &context) { KoShape *object = 0; const QString tagName = SvgUtil::mapExtendedShapeTag(element.tagName(), element); QList factories = KoShapeRegistry::instance()->factoriesForElement(KoXmlNS::svg, tagName); foreach (KoShapeFactoryBase *f, factories) { KoShape *shape = f->createDefaultShape(m_documentResourceManager); if (!shape) continue; SvgShape *svgShape = dynamic_cast(shape); if (!svgShape) { delete shape; continue; } // reset transformation that might come from the default shape shape->setTransformation(QTransform()); // reset border KoShapeStrokeModelSP oldStroke = shape->stroke(); shape->setStroke(KoShapeStrokeModelSP()); // reset fill shape->setBackground(QSharedPointer(0)); if (!svgShape->loadSvg(element, context)) { delete shape; continue; } object = shape; break; } if (!object) { object = createPath(element); } return object; } KoShape * SvgParser::createShape(const QString &shapeID) { KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(shapeID); if (!factory) { debugFlake << "Could not find factory for shape id" << shapeID; return 0; } KoShape *shape = factory->createDefaultShape(m_documentResourceManager); if (!shape) { debugFlake << "Could not create Default shape for shape id" << shapeID; return 0; } if (shape->shapeId().isEmpty()) shape->setShapeId(factory->id()); // reset tranformation that might come from the default shape shape->setTransformation(QTransform()); // reset border KoShapeStrokeModelSP oldStroke = shape->stroke(); shape->setStroke(KoShapeStrokeModelSP()); // reset fill shape->setBackground(QSharedPointer(0)); return shape; } void SvgParser::applyId(const QString &id, KoShape *shape) { if (id.isEmpty()) return; shape->setName(id); m_context.registerShape(id, shape); } diff --git a/libs/flake/svg/SvgShape.h b/libs/flake/svg/SvgShape.h index 70f11ad1ae..26d1d042e1 100644 --- a/libs/flake/svg/SvgShape.h +++ b/libs/flake/svg/SvgShape.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 SVGSHAPE_H #define SVGSHAPE_H #include "kritaflake_export.h" class SvgSavingContext; class SvgLoadingContext; -class KoXmlElement; +#include /// An interface providing svg loading and saving routines class KRITAFLAKE_EXPORT SvgShape { public: virtual ~SvgShape(); /// Saves data utilizing specified svg saving context virtual bool saveSvg(SvgSavingContext &context); /// Loads data from specified svg element virtual bool loadSvg(const KoXmlElement &element, SvgLoadingContext &context); }; #endif // SVGSHAPE_H diff --git a/libs/flake/svg/SvgStyleParser.h b/libs/flake/svg/SvgStyleParser.h index c5439e3294..93a0c8eb4f 100644 --- a/libs/flake/svg/SvgStyleParser.h +++ b/libs/flake/svg/SvgStyleParser.h @@ -1,78 +1,78 @@ /* This file is part of the KDE project * Copyright (C) 2002-2003,2005 Rob Buis * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005,2007-2009 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 SVGSTYLEPARSER_H #define SVGSTYLEPARSER_H #include "kritaflake_export.h" #include #include typedef QMap SvgStyles; class SvgLoadingContext; class SvgGraphicsContext; -class KoXmlElement; +#include class QColor; class QGradient; class KRITAFLAKE_EXPORT SvgStyleParser { public: explicit SvgStyleParser(SvgLoadingContext &context); ~SvgStyleParser(); /// Parses specified style attributes void parseStyle(const SvgStyles &styles); /// Parses font attributes void parseFont(const SvgStyles &styles); /// Parses a color attribute bool parseColor(QColor &, const QString &); /// Parses gradient color stops void parseColorStops(QGradient *, const KoXmlElement &, SvgGraphicsContext *context, const QGradientStops &defaultStops); /// Creates style map from given xml element SvgStyles collectStyles(const KoXmlElement &); /// Merges two style elements, returning the merged style SvgStyles mergeStyles(const SvgStyles &, const SvgStyles &); /// Merges two style elements, returning the merged style SvgStyles mergeStyles(const KoXmlElement &, const KoXmlElement &); SvgStyles parseOneCssStyle(const QString &style, const QStringList &interestingAttributes); private: /// Parses a single style attribute void parsePA(SvgGraphicsContext *, const QString &, const QString &); /// Returns inherited attribute value for specified element QString inheritedAttribute(const QString &attributeName, const KoXmlElement &e); class Private; Private * const d; }; #endif // SVGSTYLEPARSER_H diff --git a/libs/flake/svg/SvgUtil.cpp b/libs/flake/svg/SvgUtil.cpp index 454cc1968c..0b37cc407c 100644 --- a/libs/flake/svg/SvgUtil.cpp +++ b/libs/flake/svg/SvgUtil.cpp @@ -1,513 +1,517 @@ /* This file is part of the KDE project * Copyright (C) 2009 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 "SvgUtil.h" #include "SvgGraphicContext.h" #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "kis_global.h" #include "kis_dom_utils.h" #define DPI 72.0 #define DEG2RAD(degree) degree/180.0*M_PI double SvgUtil::fromUserSpace(double value) { return value; } double SvgUtil::toUserSpace(double value) { return value; } double SvgUtil::ptToPx(SvgGraphicsContext *gc, double value) { return value * gc->pixelsPerInch / DPI; } QPointF SvgUtil::toUserSpace(const QPointF &point) { return QPointF(toUserSpace(point.x()), toUserSpace(point.y())); } QRectF SvgUtil::toUserSpace(const QRectF &rect) { return QRectF(toUserSpace(rect.topLeft()), toUserSpace(rect.size())); } QSizeF SvgUtil::toUserSpace(const QSizeF &size) { return QSizeF(toUserSpace(size.width()), toUserSpace(size.height())); } double SvgUtil::toPercentage(QString s) { if (s.endsWith('%')) return s.remove('%').toDouble(); else return s.toDouble() * 100.0; } double SvgUtil::fromPercentage(QString s) { if (s.endsWith('%')) return s.remove('%').toDouble() / 100.0; else return s.toDouble(); } QPointF SvgUtil::objectToUserSpace(const QPointF &position, const QRectF &objectBound) { qreal x = objectBound.left() + position.x() * objectBound.width(); qreal y = objectBound.top() + position.y() * objectBound.height(); return QPointF(x, y); } QSizeF SvgUtil::objectToUserSpace(const QSizeF &size, const QRectF &objectBound) { qreal w = size.width() * objectBound.width(); qreal h = size.height() * objectBound.height(); return QSizeF(w, h); } QPointF SvgUtil::userSpaceToObject(const QPointF &position, const QRectF &objectBound) { qreal x = 0.0; if (objectBound.width() != 0) x = (position.x() - objectBound.x()) / objectBound.width(); qreal y = 0.0; if (objectBound.height() != 0) y = (position.y() - objectBound.y()) / objectBound.height(); return QPointF(x, y); } QSizeF SvgUtil::userSpaceToObject(const QSizeF &size, const QRectF &objectBound) { qreal w = objectBound.width() != 0 ? size.width() / objectBound.width() : 0.0; qreal h = objectBound.height() != 0 ? size.height() / objectBound.height() : 0.0; return QSizeF(w, h); } QString SvgUtil::transformToString(const QTransform &transform) { if (transform.isIdentity()) return QString(); if (transform.type() == QTransform::TxTranslate) { return QString("translate(%1, %2)") .arg(KisDomUtils::toString(toUserSpace(transform.dx()))) .arg(KisDomUtils::toString(toUserSpace(transform.dy()))); } else { return QString("matrix(%1 %2 %3 %4 %5 %6)") .arg(KisDomUtils::toString(transform.m11())) .arg(KisDomUtils::toString(transform.m12())) .arg(KisDomUtils::toString(transform.m21())) .arg(KisDomUtils::toString(transform.m22())) .arg(KisDomUtils::toString(toUserSpace(transform.dx()))) .arg(KisDomUtils::toString(toUserSpace(transform.dy()))); } } bool SvgUtil::parseViewBox(SvgGraphicsContext *gc, const KoXmlElement &e, const QRectF &elementBounds, QRectF *_viewRect, QTransform *_viewTransform) { Q_UNUSED(gc) KIS_ASSERT(_viewRect); KIS_ASSERT(_viewTransform); QString viewBoxStr = e.attribute("viewBox"); if (viewBoxStr.isEmpty()) return false; bool result = false; QRectF viewBoxRect; // this is a workaround for bug 260429 for a file generated by blender // who has px in the viewbox which is wrong. // reported as bug http://projects.blender.org/tracker/?group_id=9&atid=498&func=detail&aid=30971 viewBoxStr.remove("px"); QStringList points = viewBoxStr.replace(',', ' ').simplified().split(' '); if (points.count() == 4) { viewBoxRect.setX(SvgUtil::fromUserSpace(points[0].toFloat())); viewBoxRect.setY(SvgUtil::fromUserSpace(points[1].toFloat())); viewBoxRect.setWidth(SvgUtil::fromUserSpace(points[2].toFloat())); viewBoxRect.setHeight(SvgUtil::fromUserSpace(points[3].toFloat())); result = true; } else { // TODO: WARNING! } if (!result) return false; QTransform viewBoxTransform = QTransform::fromTranslate(-viewBoxRect.x(), -viewBoxRect.y()) * QTransform::fromScale(elementBounds.width() / viewBoxRect.width(), elementBounds.height() / viewBoxRect.height()) * QTransform::fromTranslate(elementBounds.x(), elementBounds.y()); const QString aspectString = e.attribute("preserveAspectRatio"); if (!aspectString.isEmpty()) { PreserveAspectRatioParser p(aspectString); parseAspectRatio(p, elementBounds, viewBoxRect, &viewBoxTransform); } *_viewRect = viewBoxRect; *_viewTransform = viewBoxTransform; return result; } void SvgUtil::parseAspectRatio(const PreserveAspectRatioParser &p, const QRectF &elementBounds, const QRectF &viewBoxRect, QTransform *_viewTransform) { if (p.mode != Qt::IgnoreAspectRatio) { QTransform viewBoxTransform = *_viewTransform; const qreal tan1 = viewBoxRect.height() / viewBoxRect.width(); const qreal tan2 = elementBounds.height() / elementBounds.width(); const qreal uniformScale = (p.mode == Qt::KeepAspectRatioByExpanding) ^ (tan1 > tan2) ? elementBounds.height() / viewBoxRect.height() : elementBounds.width() / viewBoxRect.width(); viewBoxTransform = QTransform::fromTranslate(-viewBoxRect.x(), -viewBoxRect.y()) * QTransform::fromScale(uniformScale, uniformScale) * QTransform::fromTranslate(elementBounds.x(), elementBounds.y()); const QPointF viewBoxAnchor = viewBoxTransform.map(p.rectAnchorPoint(viewBoxRect)); const QPointF elementAnchor = p.rectAnchorPoint(elementBounds); const QPointF offset = elementAnchor - viewBoxAnchor; viewBoxTransform = viewBoxTransform * QTransform::fromTranslate(offset.x(), offset.y()); *_viewTransform = viewBoxTransform; } } qreal SvgUtil::parseUnit(SvgGraphicsContext *gc, const QString &unit, bool horiz, bool vert, const QRectF &bbox) { if (unit.isEmpty()) return 0.0; QByteArray unitLatin1 = unit.toLatin1(); // TODO : percentage? const char *start = unitLatin1.data(); if (!start) { return 0.0; } qreal value = 0.0; const char *end = parseNumber(start, value); if (int(end - start) < unit.length()) { if (unit.right(2) == "px") value = SvgUtil::fromUserSpace(value); else if (unit.right(2) == "pt") value = ptToPx(gc, value); else if (unit.right(2) == "cm") value = ptToPx(gc, CM_TO_POINT(value)); else if (unit.right(2) == "pc") value = ptToPx(gc, PI_TO_POINT(value)); else if (unit.right(2) == "mm") value = ptToPx(gc, MM_TO_POINT(value)); else if (unit.right(2) == "in") value = ptToPx(gc, INCH_TO_POINT(value)); else if (unit.right(2) == "em") value = ptToPx(gc, value * gc->font.pointSize()); else if (unit.right(2) == "ex") { QFontMetrics metrics(gc->font); value = ptToPx(gc, value * metrics.xHeight()); } else if (unit.right(1) == "%") { if (horiz && vert) value = (value / 100.0) * (sqrt(pow(bbox.width(), 2) + pow(bbox.height(), 2)) / sqrt(2.0)); else if (horiz) value = (value / 100.0) * bbox.width(); else if (vert) value = (value / 100.0) * bbox.height(); } } else { value = SvgUtil::fromUserSpace(value); } /*else { if( m_gc.top() ) { if( horiz && vert ) value *= sqrt( pow( m_gc.top()->matrix.m11(), 2 ) + pow( m_gc.top()->matrix.m22(), 2 ) ) / sqrt( 2.0 ); else if( horiz ) value /= m_gc.top()->matrix.m11(); else if( vert ) value /= m_gc.top()->matrix.m22(); } }*/ //value *= 90.0 / DPI; return value; } qreal SvgUtil::parseUnitX(SvgGraphicsContext *gc, const QString &unit) { if (gc->forcePercentage) { return SvgUtil::fromPercentage(unit) * gc->currentBoundingBox.width(); } else { return SvgUtil::parseUnit(gc, unit, true, false, gc->currentBoundingBox); } } qreal SvgUtil::parseUnitY(SvgGraphicsContext *gc, const QString &unit) { if (gc->forcePercentage) { return SvgUtil::fromPercentage(unit) * gc->currentBoundingBox.height(); } else { return SvgUtil::parseUnit(gc, unit, false, true, gc->currentBoundingBox); } } qreal SvgUtil::parseUnitXY(SvgGraphicsContext *gc, const QString &unit) { if (gc->forcePercentage) { const qreal value = SvgUtil::fromPercentage(unit); return value * sqrt(pow(gc->currentBoundingBox.width(), 2) + pow(gc->currentBoundingBox.height(), 2)) / sqrt(2.0); } else { return SvgUtil::parseUnit(gc, unit, true, true, gc->currentBoundingBox); } } qreal SvgUtil::parseUnitAngular(SvgGraphicsContext *gc, const QString &unit) { Q_UNUSED(gc); qreal value = 0.0; if (unit.isEmpty()) return value; QByteArray unitLatin1 = unit.toLower().toLatin1(); const char *start = unitLatin1.data(); if (!start) return value; const char *end = parseNumber(start, value); if (int(end - start) < unit.length()) { if (unit.right(3) == "deg") { value = kisDegreesToRadians(value); } else if (unit.right(4) == "grad") { value *= M_PI / 200; } else if (unit.right(3) == "rad") { // noop! } else { value = kisDegreesToRadians(value); } } else { value = kisDegreesToRadians(value); } return value; } qreal SvgUtil::parseNumber(const QString &string) { qreal value = 0.0; if (string.isEmpty()) return value; QByteArray unitLatin1 = string.toLatin1(); const char *start = unitLatin1.data(); if (!start) return value; const char *end = parseNumber(start, value); KIS_SAFE_ASSERT_RECOVER_NOOP(int(end - start) == string.length()); return value; } const char * SvgUtil::parseNumber(const char *ptr, qreal &number) { int integer, exponent; qreal decimal, frac; int sign, expsign; exponent = 0; integer = 0; frac = 1.0; decimal = 0; sign = 1; expsign = 1; // read the sign if (*ptr == '+') { ptr++; } else if (*ptr == '-') { ptr++; sign = -1; } // read the integer part while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') integer = (integer * 10) + *(ptr++) - '0'; if (*ptr == '.') { // read the decimals ptr++; while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') decimal += (*(ptr++) - '0') * (frac *= 0.1); } if (*ptr == 'e' || *ptr == 'E') { // read the exponent part ptr++; // read the sign of the exponent if (*ptr == '+') { ptr++; } else if (*ptr == '-') { ptr++; expsign = -1; } exponent = 0; while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') { exponent *= 10; exponent += *ptr - '0'; ptr++; } } number = integer + decimal; number *= sign * pow((double)10, double(expsign * exponent)); return ptr; } QString SvgUtil::mapExtendedShapeTag(const QString &tagName, const KoXmlElement &element) { QString result = tagName; if (tagName == "path") { QString kritaType = element.attribute("krita:type", ""); QString sodipodiType = element.attribute("sodipodi:type", ""); if (kritaType == "arc") { result = "krita:arc"; } else if (sodipodiType == "arc") { result = "sodipodi:arc"; } + if (kritaType == "calligraphic-stroke") { + result = "krita:calligraphic-stroke"; + } + } return result; } SvgUtil::PreserveAspectRatioParser::PreserveAspectRatioParser(const QString &str) { QRegExp rexp("(defer)?\\s*(none|(x(Min|Max|Mid)Y(Min|Max|Mid)))\\s*(meet|slice)?", Qt::CaseInsensitive); int index = rexp.indexIn(str.toLower()); if (index >= 0) { if (rexp.cap(1) == "defer") { defer = true; } if (rexp.cap(2) != "none") { xAlignment = alignmentFromString(rexp.cap(4)); yAlignment = alignmentFromString(rexp.cap(5)); mode = rexp.cap(6) == "slice" ? Qt::KeepAspectRatioByExpanding : Qt::KeepAspectRatio; } } } QPointF SvgUtil::PreserveAspectRatioParser::rectAnchorPoint(const QRectF &rc) const { return QPointF(alignedValue(rc.x(), rc.x() + rc.width(), xAlignment), alignedValue(rc.y(), rc.y() + rc.height(), yAlignment)); } QString SvgUtil::PreserveAspectRatioParser::toString() const { QString result; if (!defer && xAlignment == Middle && yAlignment == Middle && mode == Qt::KeepAspectRatio) { return result; } if (defer) { result += "defer "; } if (mode == Qt::IgnoreAspectRatio) { result += "none"; } else { result += QString("x%1Y%2") .arg(alignmentToString(xAlignment)) .arg(alignmentToString(yAlignment)); if (mode == Qt::KeepAspectRatioByExpanding) { result += " slice"; } } return result; } SvgUtil::PreserveAspectRatioParser::Alignment SvgUtil::PreserveAspectRatioParser::alignmentFromString(const QString &str) const { return str == "max" ? Max : str == "mid" ? Middle : Min; } QString SvgUtil::PreserveAspectRatioParser::alignmentToString(SvgUtil::PreserveAspectRatioParser::Alignment alignment) const { return alignment == Max ? "Max" : alignment == Min ? "Min" : "Mid"; } qreal SvgUtil::PreserveAspectRatioParser::alignedValue(qreal min, qreal max, SvgUtil::PreserveAspectRatioParser::Alignment alignment) { qreal result = min; switch (alignment) { case Min: result = min; break; case Middle: result = 0.5 * (min + max); break; case Max: result = max; break; } return result; } diff --git a/libs/flake/svg/SvgUtil.h b/libs/flake/svg/SvgUtil.h index 5b16cb3c25..793d76ee7d 100644 --- a/libs/flake/svg/SvgUtil.h +++ b/libs/flake/svg/SvgUtil.h @@ -1,141 +1,141 @@ /* This file is part of the KDE project * Copyright (C) 2009 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 SVGUTIL_H #define SVGUTIL_H #include "kritaflake_export.h" #include class QString; class SvgGraphicsContext; class QTransform; -class KoXmlElement; +#include class KRITAFLAKE_EXPORT SvgUtil { public: // remove later! pixels *are* user coordinates static double fromUserSpace(double value); static double toUserSpace(double value); static double ptToPx(SvgGraphicsContext *gc, double value); /// Converts given point from points to userspace units. static QPointF toUserSpace(const QPointF &point); /// Converts given rectangle from points to userspace units. static QRectF toUserSpace(const QRectF &rect); /// Converts given rectangle from points to userspace units. static QSizeF toUserSpace(const QSizeF &size); /** * Parses the given string containing a percentage number. * @param s the input string containing the percentage * @return the percentage number normalized to 0..100 */ static double toPercentage(QString s); /** * Parses the given string containing a percentage number. * @param s the input string containing the percentage * @return the percentage number normalized to 0..1 */ static double fromPercentage(QString s); /** * Converts position from objectBoundingBox units to userSpace units. */ static QPointF objectToUserSpace(const QPointF &position, const QRectF &objectBound); /** * Converts size from objectBoundingBox units to userSpace units. */ static QSizeF objectToUserSpace(const QSizeF &size, const QRectF &objectBound); /** * Converts position from userSpace units to objectBoundingBox units. */ static QPointF userSpaceToObject(const QPointF &position, const QRectF &objectBound); /** * Converts size from userSpace units to objectBoundingBox units. */ static QSizeF userSpaceToObject(const QSizeF &size, const QRectF &objectBound); /// Converts specified transformation to a string static QString transformToString(const QTransform &transform); /// Parses a viewbox attribute into an rectangle static bool parseViewBox(SvgGraphicsContext *gc, const KoXmlElement &e, const QRectF &elementBounds, QRectF *_viewRect, QTransform *_viewTransform); struct PreserveAspectRatioParser; static void parseAspectRatio(const PreserveAspectRatioParser &p, const QRectF &elementBounds, const QRectF &viewRect, QTransform *_viewTransform); /// Parses a length attribute static qreal parseUnit(SvgGraphicsContext *gc, const QString &, bool horiz = false, bool vert = false, const QRectF &bbox = QRectF()); /// parses a length attribute in x-direction static qreal parseUnitX(SvgGraphicsContext *gc, const QString &unit); /// parses a length attribute in y-direction static qreal parseUnitY(SvgGraphicsContext *gc, const QString &unit); /// parses a length attribute in xy-direction static qreal parseUnitXY(SvgGraphicsContext *gc, const QString &unit); /// parses angle, result in *radians*! static qreal parseUnitAngular(SvgGraphicsContext *gc, const QString &unit); /// parses the number into parameter number static const char * parseNumber(const char *ptr, qreal &number); static qreal parseNumber(const QString &string); static QString mapExtendedShapeTag(const QString &tagName, const KoXmlElement &element); struct PreserveAspectRatioParser { PreserveAspectRatioParser(const QString &str); enum Alignment { Min, Middle, Max }; bool defer = false; Qt::AspectRatioMode mode = Qt::IgnoreAspectRatio; Alignment xAlignment = Min; Alignment yAlignment = Min; QPointF rectAnchorPoint(const QRectF &rc) const; QString toString() const; private: Alignment alignmentFromString(const QString &str) const; QString alignmentToString(Alignment alignment) const; static qreal alignedValue(qreal min, qreal max, Alignment alignment); }; }; #endif // SVGUTIL_H diff --git a/libs/image/brushengine/kis_paint_information.cc b/libs/image/brushengine/kis_paint_information.cc index 2a6deea6a3..956c838ec2 100644 --- a/libs/image/brushengine/kis_paint_information.cc +++ b/libs/image/brushengine/kis_paint_information.cc @@ -1,572 +1,583 @@ /* * Copyright (c) 2007,2010 Cyrille Berger * * 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 #include #include #include "kis_paintop.h" #include "kis_algebra_2d.h" #include "kis_lod_transform.h" #include struct KisPaintInformation::Private { EIGEN_MAKE_ALIGNED_OPERATOR_NEW Private(const QPointF & pos_, qreal pressure_, qreal xTilt_, qreal yTilt_, qreal rotation_, qreal tangentialPressure_, qreal perspective_, qreal time_, qreal speed_, bool isHoveringMode_) : pos(pos_), pressure(pressure_), xTilt(xTilt_), yTilt(yTilt_), rotation(rotation_), tangentialPressure(tangentialPressure_), perspective(perspective_), time(time_), speed(speed_), isHoveringMode(isHoveringMode_), randomSource(0), currentDistanceInfo(0), levelOfDetail(0) { } ~Private() { KIS_ASSERT_RECOVER_NOOP(!currentDistanceInfo); } Private(const Private &rhs) { copy(rhs); } Private& operator=(const Private &rhs) { copy(rhs); return *this; } void copy(const Private &rhs) { + KIS_ASSERT(!rhs.currentDistanceInfo); + pos = rhs.pos; pressure = rhs.pressure; xTilt = rhs.xTilt; yTilt = rhs.yTilt; rotation = rhs.rotation; tangentialPressure = rhs.tangentialPressure; perspective = rhs.perspective; time = rhs.time; speed = rhs.speed; isHoveringMode = rhs.isHoveringMode; randomSource = rhs.randomSource; currentDistanceInfo = rhs.currentDistanceInfo; canvasRotation = rhs.canvasRotation; canvasMirroredH = rhs.canvasMirroredH; if (rhs.drawingAngleOverride) { drawingAngleOverride.reset(new qreal(*rhs.drawingAngleOverride)); } levelOfDetail = rhs.levelOfDetail; } QPointF pos; qreal pressure; qreal xTilt; qreal yTilt; qreal rotation; qreal tangentialPressure; qreal perspective; qreal time; qreal speed; bool isHoveringMode; KisRandomSourceSP randomSource; int canvasRotation; bool canvasMirroredH; QScopedPointer drawingAngleOverride; KisDistanceInformation *currentDistanceInfo; int levelOfDetail; void registerDistanceInfo(KisDistanceInformation *di) { currentDistanceInfo = di; } void unregisterDistanceInfo() { currentDistanceInfo = 0; } }; KisPaintInformation::DistanceInformationRegistrar:: DistanceInformationRegistrar(KisPaintInformation *_p, KisDistanceInformation *distanceInfo) : p(_p) { p->d->registerDistanceInfo(distanceInfo); } +KisPaintInformation::DistanceInformationRegistrar:: +DistanceInformationRegistrar(KisPaintInformation::DistanceInformationRegistrar &&rhs) + : p(rhs.p) +{ + rhs.p = 0; +} + KisPaintInformation::DistanceInformationRegistrar:: ~DistanceInformationRegistrar() { - p->d->unregisterDistanceInfo(); + if (p) { + p->d->unregisterDistanceInfo(); + } } KisPaintInformation::KisPaintInformation(const QPointF & pos, qreal pressure, qreal xTilt, qreal yTilt, qreal rotation, qreal tangentialPressure, qreal perspective, qreal time, qreal speed) : d(new Private(pos, pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, time, speed, false)) { } KisPaintInformation::KisPaintInformation(const QPointF & pos, qreal pressure, qreal xTilt, qreal yTilt, qreal rotation) : d(new Private(pos, pressure, xTilt, yTilt, rotation, 0.0, 1.0, 0.0, 0.0, false)) { } KisPaintInformation::KisPaintInformation(const QPointF &pos, qreal pressure) : d(new Private(pos, pressure, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, false)) { } KisPaintInformation::KisPaintInformation(const KisPaintInformation& rhs) : d(new Private(*rhs.d)) { } void KisPaintInformation::operator=(const KisPaintInformation & rhs) { *d = *rhs.d; } KisPaintInformation::~KisPaintInformation() { delete d; } bool KisPaintInformation::isHoveringMode() const { return d->isHoveringMode; } KisPaintInformation KisPaintInformation::createHoveringModeInfo(const QPointF &pos, qreal pressure, qreal xTilt, qreal yTilt, qreal rotation, qreal tangentialPressure, qreal perspective, qreal speed, int canvasrotation, bool canvasMirroredH) { KisPaintInformation info(pos, pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, 0, speed); info.d->isHoveringMode = true; info.d->canvasRotation = canvasrotation; info.d->canvasMirroredH = canvasMirroredH; return info; } int KisPaintInformation::canvasRotation() const { return d->canvasRotation; } void KisPaintInformation::setCanvasRotation(int rotation) { if (rotation < 0) { d->canvasRotation= 360- abs(rotation % 360); } else { d->canvasRotation= rotation % 360; } } bool KisPaintInformation::canvasMirroredH() const { return d->canvasMirroredH; } void KisPaintInformation::setCanvasHorizontalMirrorState(bool mir) { d->canvasMirroredH = mir; } void KisPaintInformation::toXML(QDomDocument&, QDomElement& e) const { // hovering mode infos are not supposed to be saved KIS_ASSERT_RECOVER_NOOP(!d->isHoveringMode); e.setAttribute("pointX", QString::number(pos().x(), 'g', 15)); e.setAttribute("pointY", QString::number(pos().y(), 'g', 15)); e.setAttribute("pressure", QString::number(pressure(), 'g', 15)); e.setAttribute("xTilt", QString::number(xTilt(), 'g', 15)); e.setAttribute("yTilt", QString::number(yTilt(), 'g', 15)); e.setAttribute("rotation", QString::number(rotation(), 'g', 15)); e.setAttribute("tangentialPressure", QString::number(tangentialPressure(), 'g', 15)); e.setAttribute("perspective", QString::number(perspective(), 'g', 15)); e.setAttribute("time", QString::number(d->time, 'g', 15)); e.setAttribute("speed", QString::number(d->speed, 'g', 15)); } KisPaintInformation KisPaintInformation::fromXML(const QDomElement& e) { qreal pointX = qreal(KisDomUtils::toDouble(e.attribute("pointX", "0.0"))); qreal pointY = qreal(KisDomUtils::toDouble(e.attribute("pointY", "0.0"))); qreal pressure = qreal(KisDomUtils::toDouble(e.attribute("pressure", "0.0"))); qreal rotation = qreal(KisDomUtils::toDouble(e.attribute("rotation", "0.0"))); qreal tangentialPressure = qreal(KisDomUtils::toDouble(e.attribute("tangentialPressure", "0.0"))); qreal perspective = qreal(KisDomUtils::toDouble(e.attribute("perspective", "0.0"))); qreal xTilt = qreal(KisDomUtils::toDouble(e.attribute("xTilt", "0.0"))); qreal yTilt = qreal(KisDomUtils::toDouble(e.attribute("yTilt", "0.0"))); qreal time = KisDomUtils::toDouble(e.attribute("time", "0")); qreal speed = KisDomUtils::toDouble(e.attribute("speed", "0")); return KisPaintInformation(QPointF(pointX, pointY), pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, time, speed); } const QPointF& KisPaintInformation::pos() const { return d->pos; } void KisPaintInformation::setPos(const QPointF& p) { d->pos = p; } qreal KisPaintInformation::pressure() const { return d->pressure; } void KisPaintInformation::setPressure(qreal p) { d->pressure = p; } qreal KisPaintInformation::xTilt() const { return d->xTilt; } qreal KisPaintInformation::yTilt() const { return d->yTilt; } void KisPaintInformation::overrideDrawingAngle(qreal angle) { d->drawingAngleOverride.reset(new qreal(angle)); } qreal KisPaintInformation::drawingAngleSafe(const KisDistanceInformation &distance) const { if (d->drawingAngleOverride) return *d->drawingAngleOverride; QVector2D diff(pos() - distance.lastPosition()); return atan2(diff.y(), diff.x()); } KisPaintInformation::DistanceInformationRegistrar KisPaintInformation::registerDistanceInformation(KisDistanceInformation *distance) { return DistanceInformationRegistrar(this, distance); } qreal KisPaintInformation::drawingAngle() const { if (d->drawingAngleOverride) return *d->drawingAngleOverride; if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) { warnKrita << "KisPaintInformation::drawingAngle()" << "Cannot access Distance Info last dab data"; return 0.0; } if (d->currentDistanceInfo->hasLockedDrawingAngle()) { return d->currentDistanceInfo->lockedDrawingAngle(); } QVector2D diff(pos() - d->currentDistanceInfo->lastPosition()); return atan2(diff.y(), diff.x()); } void KisPaintInformation::lockCurrentDrawingAngle(qreal alpha_unused) const { Q_UNUSED(alpha_unused); if (!d->currentDistanceInfo) { warnKrita << "KisPaintInformation::lockCurrentDrawingAngle()" << "Cannot access Distance Info last dab data"; return; } const QVector2D diff(pos() - d->currentDistanceInfo->lastPosition()); const qreal angle = atan2(diff.y(), diff.x()); qreal newAngle = angle; if (d->currentDistanceInfo->hasLockedDrawingAngle()) { const qreal stabilizingCoeff = 20.0; const qreal dist = stabilizingCoeff * d->currentDistanceInfo->currentSpacing().scalarApprox(); const qreal alpha = qMax(0.0, dist - d->currentDistanceInfo->scalarDistanceApprox()) / dist; const qreal oldAngle = d->currentDistanceInfo->lockedDrawingAngle(); if (shortestAngularDistance(oldAngle, newAngle) < M_PI / 6) { newAngle = (1.0 - alpha) * oldAngle + alpha * newAngle; } else { newAngle = oldAngle; } } d->currentDistanceInfo->setLockedDrawingAngle(newAngle); } QPointF KisPaintInformation::drawingDirectionVector() const { if (d->drawingAngleOverride) { qreal angle = *d->drawingAngleOverride; return QPointF(cos(angle), sin(angle)); } if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) { warnKrita << "KisPaintInformation::drawingDirectionVector()" << "Cannot access Distance Info last dab data"; return QPointF(1.0, 0.0); } QPointF diff(pos() - d->currentDistanceInfo->lastPosition()); return KisAlgebra2D::normalize(diff); } qreal KisPaintInformation::drawingDistance() const { if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) { warnKrita << "KisPaintInformation::drawingDistance()" << "Cannot access Distance Info last dab data"; return 1.0; } QVector2D diff(pos() - d->currentDistanceInfo->lastPosition()); qreal length = diff.length(); if (d->levelOfDetail) { length *= KisLodTransform::lodToInvScale(d->levelOfDetail); } return length; } qreal KisPaintInformation::drawingSpeed() const { return d->speed; } qreal KisPaintInformation::rotation() const { return d->rotation; } qreal KisPaintInformation::tangentialPressure() const { return d->tangentialPressure; } qreal KisPaintInformation::perspective() const { return d->perspective; } qreal KisPaintInformation::currentTime() const { return d->time; } KisRandomSourceSP KisPaintInformation::randomSource() const { if (!d->randomSource) { d->randomSource = new KisRandomSource(); } return d->randomSource; } void KisPaintInformation::setRandomSource(KisRandomSourceSP value) { d->randomSource = value; } void KisPaintInformation::setLevelOfDetail(int levelOfDetail) const { d->levelOfDetail = levelOfDetail; } QDebug operator<<(QDebug dbg, const KisPaintInformation &info) { #ifdef NDEBUG Q_UNUSED(info); #else dbg.nospace() << "Position: " << info.pos(); dbg.nospace() << ", Pressure: " << info.pressure(); dbg.nospace() << ", X Tilt: " << info.xTilt(); dbg.nospace() << ", Y Tilt: " << info.yTilt(); dbg.nospace() << ", Rotation: " << info.rotation(); dbg.nospace() << ", Tangential Pressure: " << info.tangentialPressure(); dbg.nospace() << ", Perspective: " << info.perspective(); dbg.nospace() << ", Drawing Angle: " << info.drawingAngle(); dbg.nospace() << ", Drawing Speed: " << info.drawingSpeed(); dbg.nospace() << ", Drawing Distance: " << info.drawingDistance(); dbg.nospace() << ", Time: " << info.currentTime(); #endif return dbg.space(); } KisPaintInformation KisPaintInformation::mixOnlyPosition(qreal t, const KisPaintInformation& mixedPi, const KisPaintInformation& basePi) { QPointF pt = (1 - t) * mixedPi.pos() + t * basePi.pos(); KisPaintInformation result(pt, basePi.pressure(), basePi.xTilt(), basePi.yTilt(), basePi.rotation(), basePi.tangentialPressure(), basePi.perspective(), basePi.currentTime(), basePi.drawingSpeed()); result.setRandomSource(basePi.randomSource()); return result; } KisPaintInformation KisPaintInformation::mix(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2) { QPointF pt = (1 - t) * pi1.pos() + t * pi2.pos(); return mix(pt, t, pi1, pi2); } KisPaintInformation KisPaintInformation::mix(const QPointF& p, qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2) { qreal pressure = (1 - t) * pi1.pressure() + t * pi2.pressure(); qreal xTilt = (1 - t) * pi1.xTilt() + t * pi2.xTilt(); qreal yTilt = (1 - t) * pi1.yTilt() + t * pi2.yTilt(); qreal rotation = pi1.rotation(); if (pi1.rotation() != pi2.rotation()) { qreal a1 = kisDegreesToRadians(pi1.rotation()); qreal a2 = kisDegreesToRadians(pi2.rotation()); qreal distance = shortestAngularDistance(a2, a1); rotation = kisRadiansToDegrees(incrementInDirection(a1, t * distance, a2)); } qreal tangentialPressure = (1 - t) * pi1.tangentialPressure() + t * pi2.tangentialPressure(); qreal perspective = (1 - t) * pi1.perspective() + t * pi2.perspective(); qreal time = (1 - t) * pi1.currentTime() + t * pi2.currentTime(); qreal speed = (1 - t) * pi1.drawingSpeed() + t * pi2.drawingSpeed(); KisPaintInformation result(p, pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, time, speed); KIS_ASSERT_RECOVER_NOOP(pi1.isHoveringMode() == pi2.isHoveringMode()); result.d->isHoveringMode = pi1.isHoveringMode(); result.d->levelOfDetail = pi1.d->levelOfDetail; result.d->randomSource = pi1.d->randomSource; result.d->canvasRotation = pi2.canvasRotation(); result.d->canvasMirroredH = pi2.canvasMirroredH(); return result; } qreal KisPaintInformation::tiltDirection(const KisPaintInformation& info, bool normalize) { qreal xTilt = info.xTilt(); qreal yTilt = info.yTilt(); // radians -PI, PI qreal tiltDirection = atan2(-xTilt, yTilt); // if normalize is true map to 0.0..1.0 return normalize ? (tiltDirection / (2 * M_PI) + 0.5) : tiltDirection; } qreal KisPaintInformation::tiltElevation(const KisPaintInformation& info, qreal maxTiltX, qreal maxTiltY, bool normalize) { qreal xTilt = qBound(qreal(-1.0), info.xTilt() / maxTiltX , qreal(1.0)); qreal yTilt = qBound(qreal(-1.0), info.yTilt() / maxTiltY , qreal(1.0)); qreal e; if (fabs(xTilt) > fabs(yTilt)) { e = sqrt(qreal(1.0) + yTilt * yTilt); } else { e = sqrt(qreal(1.0) + xTilt * xTilt); } qreal cosAlpha = sqrt(xTilt * xTilt + yTilt * yTilt) / e; qreal tiltElevation = acos(cosAlpha); // in radians in [0, 0.5 * PI] // mapping to 0.0..1.0 if normalize is true return normalize ? (tiltElevation / (M_PI * qreal(0.5))) : tiltElevation; } diff --git a/libs/image/brushengine/kis_paint_information.h b/libs/image/brushengine/kis_paint_information.h index 06073bbedc..81b453feda 100644 --- a/libs/image/brushengine/kis_paint_information.h +++ b/libs/image/brushengine/kis_paint_information.h @@ -1,271 +1,273 @@ /* * Copyright (c) 2004 Cyrille Berger * 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_PAINT_INFORMATION_ #define _KIS_PAINT_INFORMATION_ #include #include #include "kis_global.h" #include "kritaimage_export.h" #include #include "kis_random_source.h" class QDomDocument; class QDomElement; class KisDistanceInformation; /** * KisPaintInformation contains information about the input event that * causes the brush action to happen to the brush engine's paint * methods. * * XXX: we directly pass the KoPointerEvent x and y tilt to * KisPaintInformation, and their range is -60 to +60! * * @param pos: the position of the paint event in subpixel accuracy * @param pressure: the pressure of the stylus * @param xTilt: the angle between the device (a pen, for example) and * the perpendicular in the direction of the x axis. Positive values * are towards the bottom of the tablet. The angle is within the range * 0 to 1 * @param yTilt: the angle between the device (a pen, for example) and * the perpendicular in the direction of the y axis. Positive values * are towards the bottom of the tablet. The angle is within the range * 0 to . * @param movement: current position minus the last position of the call to paintAt * @param rotation * @param tangentialPressure * @param perspective **/ class KRITAIMAGE_EXPORT KisPaintInformation { public: /** * Note, that this class is relied on the compiler optimization * of the return value. So if it doesn't work for some reason, * please implement a proper copy c-tor */ class KRITAIMAGE_EXPORT DistanceInformationRegistrar { public: DistanceInformationRegistrar(KisPaintInformation *_p, KisDistanceInformation *distanceInfo); + DistanceInformationRegistrar(const DistanceInformationRegistrar &rhs) = delete; + DistanceInformationRegistrar(DistanceInformationRegistrar &&rhs); ~DistanceInformationRegistrar(); private: KisPaintInformation *p; }; public: /** * Create a new KisPaintInformation object. */ KisPaintInformation(const QPointF & pos, qreal pressure, qreal xTilt, qreal yTilt, qreal rotation, qreal tangentialPressure, qreal perspective, qreal time, qreal speed); KisPaintInformation(const QPointF & pos, qreal pressure, qreal xTilt, qreal yTilt, qreal rotation); KisPaintInformation(const QPointF & pos = QPointF(), qreal pressure = PRESSURE_DEFAULT); KisPaintInformation(const KisPaintInformation& rhs); void operator=(const KisPaintInformation& rhs); ~KisPaintInformation(); template void paintAt(PaintOp &op, KisDistanceInformation *distanceInfo) { KisSpacingInformation spacingInfo; { DistanceInformationRegistrar r = registerDistanceInformation(distanceInfo); spacingInfo = op.paintAt(*this); } distanceInfo->registerPaintedDab(*this, spacingInfo); } const QPointF& pos() const; void setPos(const QPointF& p); /// The pressure of the value (from 0.0 to 1.0) qreal pressure() const; /// Set the pressure void setPressure(qreal p); /// The tilt of the pen on the horizontal axis (from 0.0 to 1.0) qreal xTilt() const; /// The tilt of the pen on the vertical axis (from 0.0 to 1.0) qreal yTilt() const; /// XXX !!! :-| Please add dox! void overrideDrawingAngle(qreal angle); /// XXX !!! :-| Please add dox! qreal drawingAngleSafe(const KisDistanceInformation &distance) const; /// XXX !!! :-| Please add dox! DistanceInformationRegistrar registerDistanceInformation(KisDistanceInformation *distance); /** * Current brush direction computed from the cursor movement * * WARNING: this method is available *only* inside paintAt() call, * that is when the distance information is registered. */ qreal drawingAngle() const; /** * Lock current drawing angle for the rest of the stroke. If some * value has already been locked, \p alpha shown the coefficient * with which the new velue should be blended in. */ void lockCurrentDrawingAngle(qreal alpha) const; /** * Current brush direction vector computed from the cursor movement * * WARNING: this method is available *only* inside paintAt() call, * that is when the distance information is registered. */ QPointF drawingDirectionVector() const; /** * Current brush speed computed from the cursor movement * * WARNING: this method is available *only* inside paintAt() call, * that is when the distance information is registered. */ qreal drawingSpeed() const; /** * Current distance from the previous dab * * WARNING: this method is available *only* inside paintAt() call, * that is when the distance information is registered. */ qreal drawingDistance() const; /// rotation as given by the tablet event qreal rotation() const; /// tangential pressure (i.e., rate for an airbrush device) qreal tangentialPressure() const; /// reciprocal of distance on the perspective grid qreal perspective() const; /// Number of ms since the beginning of the stroke qreal currentTime() const; // random source for generating in-stroke effects KisRandomSourceSP randomSource() const; // the stroke should initialize random source of all the used // paint info objects, otherwise it shows a warning void setRandomSource(KisRandomSourceSP value); // set level of detail which info object has been generated for void setLevelOfDetail(int levelOfDetail) const; /** * The paint information may be generated not only during real * stroke when the actual painting is happening, but also when the * cursor is hovering the canvas. In this mode some of the sensors * work a bit differently. The most outstanding example is Fuzzy * sensor, which returns unit value in this mode, otherwise it is * too irritating for a user. * * This value is true only for paint information objects created with * createHoveringModeInfo() constructor. * * \see createHoveringModeInfo() */ bool isHoveringMode() const; /** * Create a fake info object with isHoveringMode() property set to * true. * * \see isHoveringMode() */ static KisPaintInformation createHoveringModeInfo(const QPointF &pos, qreal pressure = PRESSURE_DEFAULT, qreal xTilt = 0.0, qreal yTilt = 0.0, qreal rotation = 0.0, qreal tangentialPressure = 0.0, qreal perspective = 1.0, qreal speed = 0.0, int canvasrotation = 0, bool canvasMirroredH = false); /** *Returns the canvas rotation if that has been given to the kispaintinformation. */ int canvasRotation() const; /** *set the canvas rotation. */ void setCanvasRotation(int rotation); /* *Whether the canvas is mirrored for the paint-operation. */ bool canvasMirroredH() const; /* *Set whether the canvas is mirrored for the paint-operation. */ void setCanvasHorizontalMirrorState(bool mir); void toXML(QDomDocument&, QDomElement&) const; static KisPaintInformation fromXML(const QDomElement&); /// (1-t) * p1 + t * p2 static KisPaintInformation mixOnlyPosition(qreal t, const KisPaintInformation& mixedPi, const KisPaintInformation& basePi); static KisPaintInformation mix(const QPointF& p, qreal t, const KisPaintInformation& p1, const KisPaintInformation& p2); static KisPaintInformation mix(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2); static qreal tiltDirection(const KisPaintInformation& info, bool normalize = true); static qreal tiltElevation(const KisPaintInformation& info, qreal maxTiltX = 60.0, qreal maxTiltY = 60.0, bool normalize = true); private: struct Private; Private* const d; }; KRITAIMAGE_EXPORT QDebug operator<<(QDebug debug, const KisPaintInformation& info); #endif diff --git a/libs/image/kis_properties_configuration.cc b/libs/image/kis_properties_configuration.cc index e5a6baddf3..b966e11b8c 100644 --- a/libs/image/kis_properties_configuration.cc +++ b/libs/image/kis_properties_configuration.cc @@ -1,319 +1,334 @@ /* * Copyright (c) 2006 Boudewijn Rempt * Copyright (c) 2007,2010 Cyrille Berger * * 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_properties_configuration.h" #include #include #include #include "kis_image.h" #include "kis_transaction.h" #include "kis_undo_adapter.h" #include "kis_painter.h" #include "kis_selection.h" #include "KoID.h" #include "kis_types.h" #include #include struct Q_DECL_HIDDEN KisPropertiesConfiguration::Private { QMap properties; QStringList notSavedProperties; }; KisPropertiesConfiguration::KisPropertiesConfiguration() : d(new Private) { } KisPropertiesConfiguration::~KisPropertiesConfiguration() { delete d; } KisPropertiesConfiguration::KisPropertiesConfiguration(const KisPropertiesConfiguration& rhs) : KisSerializableConfiguration(rhs) , d(new Private(*rhs.d)) { } bool KisPropertiesConfiguration::fromXML(const QString & xml, bool clear) { if (clear) { clearProperties(); } QDomDocument doc; bool retval = doc.setContent(xml); if (retval) { QDomElement e = doc.documentElement(); fromXML(e); } return retval; } void KisPropertiesConfiguration::fromXML(const QDomElement& e) { QDomNode n = e.firstChild(); while (!n.isNull()) { // We don't nest elements in filter configuration. For now... QDomElement e = n.toElement(); if (!e.isNull()) { if (e.tagName() == "param") { // If the file contains the new type parameter introduced in Krita act on it // Else invoke old behaviour if(e.attributes().contains("type")) { QString type = e.attribute("type"); QString name = e.attribute("name"); QString value = e.text(); if(type == "bytearray") { d->properties[name] = QVariant(QByteArray::fromBase64(value.toLatin1())); - } - else + } else if (type == "bool") { + d->properties[name] = QVariant(value).toBool(); + } else if (type == "int") { + d->properties[name] = QVariant(value.toInt()); + } else if (type == "double") { + d->properties[name] = QVariant(value.toDouble()); + } else { d->properties[name] = value; + } } else d->properties[e.attribute("name")] = QVariant(e.text()); } } n = n.nextSibling(); } //dump(); } void KisPropertiesConfiguration::toXML(QDomDocument& doc, QDomElement& root) const { QMap::Iterator it; for (it = d->properties.begin(); it != d->properties.end(); ++it) { if(d->notSavedProperties.contains(it.key())) { continue; } QDomElement e = doc.createElement("param"); e.setAttribute("name", QString(it.key().toLatin1())); QString type = "string"; QVariant v = it.value(); QDomText text; if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId()) { text = doc.createCDATASection(v.value().toString()); } else if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId()) { QDomDocument doc = QDomDocument("color"); QDomElement root = doc.createElement("color"); doc.appendChild(root); v.value().toXML(doc, root); text = doc.createCDATASection(doc.toString()); type = "color"; } else if(v.type() == QVariant::String ) { text = doc.createCDATASection(v.toString()); // XXX: Unittest this! type = "string"; } else if(v.type() == QVariant::ByteArray ) { text = doc.createTextNode(QString::fromLatin1(v.toByteArray().toBase64())); // Arbitrary Data type = "bytearray"; + } else if (v.type() == QVariant::Bool) { + text = doc.createTextNode(v.toString()); + type = "bool"; + } else if (v.type() == QVariant::Int) { + text = doc.createTextNode(v.toString()); + type = "int"; + } else if (v.type() == QVariant::Double) { + text = doc.createTextNode(v.toString()); + type = "double"; } else { text = doc.createTextNode(v.toString()); type = "internal"; } e.setAttribute("type", type); e.appendChild(text); root.appendChild(e); } } QString KisPropertiesConfiguration::toXML() const { QDomDocument doc = QDomDocument("params"); QDomElement root = doc.createElement("params"); doc.appendChild(root); toXML(doc, root); return doc.toString(); } bool KisPropertiesConfiguration::hasProperty(const QString& name) const { return d->properties.contains(name); } void KisPropertiesConfiguration::setProperty(const QString & name, const QVariant & value) { if (d->properties.find(name) == d->properties.end()) { d->properties.insert(name, value); } else { d->properties[name] = value; } } bool KisPropertiesConfiguration::getProperty(const QString & name, QVariant & value) const { if (d->properties.find(name) == d->properties.end()) { return false; } else { value = d->properties[name]; return true; } } QVariant KisPropertiesConfiguration::getProperty(const QString & name) const { if (d->properties.find(name) == d->properties.end()) { return QVariant(); } else { return d->properties[name]; } } int KisPropertiesConfiguration::getInt(const QString & name, int def) const { QVariant v = getProperty(name); if (v.isValid()) return v.toInt(); else return def; } double KisPropertiesConfiguration::getDouble(const QString & name, double def) const { QVariant v = getProperty(name); if (v.isValid()) return v.toDouble(); else return def; } float KisPropertiesConfiguration::getFloat(const QString & name, float def) const { QVariant v = getProperty(name); if (v.isValid()) return (float)v.toDouble(); else return def; } bool KisPropertiesConfiguration::getBool(const QString & name, bool def) const { QVariant v = getProperty(name); if (v.isValid()) return v.toBool(); else return def; } QString KisPropertiesConfiguration::getString(const QString & name, const QString & def) const { QVariant v = getProperty(name); if (v.isValid()) return v.toString(); else return def; } KisCubicCurve KisPropertiesConfiguration::getCubicCurve(const QString & name, const KisCubicCurve & curve) const { QVariant v = getProperty(name); if (v.isValid()) { if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId()) { return v.value(); } else { KisCubicCurve c; c.fromString(v.toString()); return c; } } else return curve; } KoColor KisPropertiesConfiguration::getColor(const QString& name, const KoColor& color) const { QVariant v = getProperty(name); if (v.isValid()) { if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId()) { return v.value(); } else { QDomDocument doc; doc.setContent(v.toString()); QDomElement e = doc.documentElement().firstChild().toElement(); return KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } } else { return color; } } void KisPropertiesConfiguration::dump() const { QMap::Iterator it; for (it = d->properties.begin(); it != d->properties.end(); ++it) { dbgKrita << it.key() << " = " << it.value(); } } void KisPropertiesConfiguration::clearProperties() { d->properties.clear(); } void KisPropertiesConfiguration::setPropertyNotSaved(const QString& name) { d->notSavedProperties.append(name); } QMap KisPropertiesConfiguration::getProperties() const { return d->properties; } void KisPropertiesConfiguration::removeProperty(const QString & name) { d->properties.remove(name); } // --- factory --- struct Q_DECL_HIDDEN KisPropertiesConfigurationFactory::Private { }; KisPropertiesConfigurationFactory::KisPropertiesConfigurationFactory() : d(new Private) { } KisPropertiesConfigurationFactory::~KisPropertiesConfigurationFactory() { delete d; } KisSerializableConfigurationSP KisPropertiesConfigurationFactory::createDefault() { return new KisPropertiesConfiguration(); } KisSerializableConfigurationSP KisPropertiesConfigurationFactory::create(const QDomElement& e) { KisPropertiesConfigurationSP pc = new KisPropertiesConfiguration(); pc->fromXML(e); return pc; } diff --git a/libs/libkis/Document.cpp b/libs/libkis/Document.cpp index 5525ece21e..ed5c640b15 100644 --- a/libs/libkis/Document.cpp +++ b/libs/libkis/Document.cpp @@ -1,514 +1,514 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program 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 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 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. */ #include "Document.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 struct Document::Private { Private() {} QPointer document; }; Document::Document(KisDocument *document, QObject *parent) : QObject(parent) , d(new Private) { d->document = document; } Document::~Document() { delete d; } bool Document::operator==(const Document &other) const { return (d->document == other.d->document); } bool Document::operator!=(const Document &other) const { return !(operator==(other)); } bool Document::batchmode() const { if (!d->document) return false; return d->document->fileBatchMode(); } void Document::setBatchmode(bool value) { if (!d->document) return; d->document->setFileBatchMode(value); } Node *Document::activeNode() const { QList activeNodes; Q_FOREACH(QPointer view, KisPart::instance()->views()) { if (view && view->document() == d->document) { activeNodes << view->currentNode(); } } if (activeNodes.size() > 0) { return new Node(d->document->image(), activeNodes.first()); } return new Node(d->document->image(), d->document->image()->root()->firstChild()); } void Document::setActiveNode(Node* value) { if (!value->node()) return; KisMainWindow *mainWin = KisPart::instance()->currentMainwindow(); if (!mainWin) return; KisViewManager *viewManager = mainWin->viewManager(); if (!viewManager) return; if (viewManager->document() != d->document) return; KisNodeManager *nodeManager = viewManager->nodeManager(); if (!nodeManager) return; KisNodeSelectionAdapter *selectionAdapter = nodeManager->nodeSelectionAdapter(); if (!selectionAdapter) return; selectionAdapter->setActiveNode(value->node()); } QList Document::topLevelNodes() const { if (!d->document) return QList(); Node n(d->document->image(), d->document->image()->rootLayer()); return n.childNodes(); } Node *Document::nodeByName(const QString &name) const { if (!d->document) return 0; KisNodeSP node = d->document->image()->rootLayer()->findChildByName(name); return new Node(d->document->image(), node); } QString Document::colorDepth() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorDepthId().id(); } QString Document::colorModel() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorModelId().id(); } QString Document::colorProfile() const { if (!d->document) return ""; return d->document->image()->colorSpace()->profile()->name(); } bool Document::setColorProfile(const QString &value) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(value); if (!profile) return false; bool retval = d->document->image()->assignImageProfile(profile); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return retval; } bool Document::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile); if (!colorSpace) return false; d->document->image()->convertImageColorSpace(colorSpace, KoColorConversionTransformation::IntentPerceptual, KoColorConversionTransformation::HighQuality | KoColorConversionTransformation::NoOptimization); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return true; } QString Document::documentInfo() const { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = d->document->documentInfo()->save(doc); return doc.toString(); } void Document::setDocumentInfo(const QString &document) { - KoXmlDocument doc = KoXmlDocument(true); + KoXmlDocument doc = KoXmlDocument(); QString errorMsg; int errorLine, errorColumn; doc.setContent(document, &errorMsg, &errorLine, &errorColumn); d->document->documentInfo()->load(doc); } QString Document::fileName() const { if (!d->document) return QString::null; return d->document->url().toLocalFile(); } void Document::setFileName(QString value) { if (!d->document) return; d->document->setUrl(QUrl::fromLocalFile(value)); } int Document::height() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->height(); } void Document::setHeight(int value) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->width(), value); } QString Document::name() const { if (!d->document) return ""; return d->document->documentInfo()->aboutInfo("title"); } void Document::setName(QString value) { if (!d->document) return; d->document->documentInfo()->setAboutInfo("title", value); } int Document::resolution() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return qRound(d->document->image()->xRes() * 72); } void Document::setResolution(int value) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; d->document->image()->setResolution(value / 72.0, value / 72.0); } Node *Document::rootNode() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return new Node(image, image->root()); } Selection *Document::selection() const { if (!d->document) return 0; if (!d->document->image()) return 0; if (!d->document->image()->globalSelection()) return 0; return new Selection(d->document->image()->globalSelection()); } void Document::setSelection(Selection* value) { if (!d->document) return; if (!d->document->image()) return; if (value) { d->document->image()->setGlobalSelection(value->selection()); } else { d->document->image()->setGlobalSelection(0); } } int Document::width() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->width(); } void Document::setWidth(int value) { if (!d->document) return; if (!d->document->image()) return; resizeImage(value, d->document->image()->height()); } double Document::xRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->xRes(); } void Document::setXRes(double xRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(xRes, d->document->image()->yRes()); } double Document::yRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->yRes(); } void Document::setYRes(double yRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(d->document->image()->xRes(), yRes); } QByteArray Document::pixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->document) return ba; KisImageSP image = d->document->image(); if (!image) return ba; KisPaintDeviceSP dev = image->projection(); ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } bool Document::close() { bool retval = d->document->closeUrl(false); Q_FOREACH(KisView *view, KisPart::instance()->views()) { if (view->document() == d->document) { view->close(); view->deleteLater(); } } d->document->deleteLater(); d->document = 0; return retval; } void Document::crop(int x, int y, int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc(x, y, w, h); image->cropImage(rc); } bool Document::exportImage(const QString &filename, const InfoObject &exportConfiguration) { if (!d->document) return false; return d->document->exportDocument(QUrl::fromLocalFile(filename), exportConfiguration.configuration()); } void Document::flatten() { if (!d->document) return; if (!d->document->image()) return; d->document->image()->flatten(); } void Document::resizeImage(int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc = image->bounds(); rc.setWidth(w); rc.setHeight(h); image->resizeImage(rc); } bool Document::save() { if (!d->document) return false; return d->document->save(); } bool Document::saveAs(const QString &filename) { if (!d->document) return false; return d->document->saveAs(QUrl::fromLocalFile(filename)); } Node* Document::createNode(const QString &name, const QString &nodeType) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); Node *node = 0; if (nodeType == "paintlayer") { node = new Node(image, new KisPaintLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "grouplayer") { node = new Node(image, new KisGroupLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "filelayer") { node = new Node(image, new KisFileLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "filterlayer") { node = new Node(image, new KisAdjustmentLayer(image, name, 0, 0)); } else if (nodeType == "filllayer") { node = new Node(image, new KisGeneratorLayer(image, name, 0, 0)); } else if (nodeType == "clonelayer") { node = new Node(image, new KisCloneLayer(0, image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "vectorlayer") { node = new Node(image, new KisShapeLayer(d->document->shapeController(), image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "transparencymask") { node = new Node(image, new KisTransparencyMask()); } else if (nodeType == "filtermask") { node = new Node(image, new KisFilterMask()); } else if (nodeType == "transformmask") { node = new Node(image, new KisTransformMask()); } else if (nodeType == "selectionmask") { node = new Node(image, new KisSelectionMask(image)); } return node; } QImage Document::projection(int x, int y, int w, int h) const { if (!d->document || !d->document->image()) return QImage(); return d->document->image()->convertToQImage(x, y, w, h, 0); } QImage Document::thumbnail(int w, int h) const { if (!d->document || !d->document->image()) return QImage(); return d->document->generatePreview(QSize(w, h)).toImage(); } void Document::lock() { if (!d->document || !d->document->image()) return; d->document->image()->barrierLock(); } void Document::unlock() { if (!d->document || !d->document->image()) return; d->document->image()->unlock(); } void Document::waitForDone() { if (!d->document || !d->document->image()) return; d->document->image()->waitForDone(); } bool Document::tryBarrierLock() { if (!d->document || !d->document->image()) return false; return d->document->image()->tryBarrierLock(); } bool Document::isIdle() { if (!d->document || !d->document->image()) return false; return d->document->image()->isIdle(); } void Document::refreshProjection() { if (!d->document || !d->document->image()) return; d->document->image()->refreshGraph(); } QPointer Document::document() const { return d->document; } diff --git a/plugins/paintops/libpaintop/CMakeLists.txt b/libs/libpaintop/CMakeLists.txt similarity index 98% rename from plugins/paintops/libpaintop/CMakeLists.txt rename to libs/libpaintop/CMakeLists.txt index 153950f752..d2d40ea67d 100644 --- a/plugins/paintops/libpaintop/CMakeLists.txt +++ b/libs/libpaintop/CMakeLists.txt @@ -1,99 +1,103 @@ set(kritalibpaintop_LIB_SRCS kis_airbrush_option.cpp kis_auto_brush_widget.cpp kis_spacing_selection_widget.cpp kis_bidirectional_mixing_option.cpp kis_bidirectional_mixing_option_widget.cpp kis_brush_based_paintop.cpp kis_brush_chooser.cpp kis_brush_option_widget.cpp kis_brush_option.cpp kis_brush_selection_widget.cpp kis_color_option.cpp kis_color_source.cpp kis_color_source_option.cpp kis_color_source_option_widget.cpp kis_curve_option.cpp kis_curve_option_widget.cpp kis_curve_option_uniform_property.cpp kis_custom_brush_widget.cpp kis_clipboard_brush_widget.cpp kis_dynamic_sensor.cc kis_dab_cache.cpp kis_filter_option.cpp kis_multi_sensors_model_p.cpp kis_multi_sensors_selector.cpp kis_paint_action_type_option.cpp kis_precision_option.cpp kis_pressure_darken_option.cpp kis_pressure_hsv_option.cpp kis_pressure_opacity_option.cpp kis_pressure_flow_option.cpp kis_pressure_mirror_option.cpp kis_pressure_scatter_option.cpp kis_pressure_scatter_option_widget.cpp kis_pressure_sharpness_option.cpp kis_pressure_sharpness_option_widget.cpp kis_pressure_mirror_option_widget.cpp kis_pressure_rotation_option.cpp kis_pressure_size_option.cpp kis_pressure_spacing_option.cpp kis_pressure_softness_option.cpp kis_pressure_mix_option.cpp kis_pressure_gradient_option.cpp kis_pressure_flow_opacity_option.cpp kis_pressure_flow_opacity_option_widget.cpp kis_pressure_spacing_option_widget.cpp kis_pressure_ratio_option.cpp kis_current_outline_fetcher.cpp kis_text_brush_chooser.cpp kis_brush_based_paintop_options_widget.cpp kis_brush_based_paintop_settings.cpp kis_compositeop_option.cpp kis_texture_option.cpp kis_pressure_texture_strength_option.cpp kis_embedded_pattern_manager.cpp sensors/kis_dynamic_sensors.cc sensors/kis_dynamic_sensor_drawing_angle.cpp sensors/kis_dynamic_sensor_distance.cc sensors/kis_dynamic_sensor_time.cc sensors/kis_dynamic_sensor_fade.cpp sensors/kis_dynamic_sensor_fuzzy.cpp ) ki18n_wrap_ui(kritalibpaintop_LIB_SRCS forms/wdgautobrush.ui forms/wdgBrushSizeOptions.ui forms/wdgcurveoption.ui forms/wdgcustombrush.ui forms/wdgclipboardbrush.ui forms/wdgtextbrush.ui forms/wdgincremental.ui forms/wdgmultisensorsselector.ui forms/wdgairbrush.ui forms/wdgfilteroption.ui forms/wdgcoloroptions.ui forms/wdgbrushchooser.ui forms/wdgpredefinedbrushchooser.ui forms/wdgCompositeOpOption.ui forms/wdgflowopacityoption.ui sensors/SensorDistanceConfiguration.ui sensors/SensorTimeConfiguration.ui sensors/SensorFadeConfiguration.ui ) +include_Directories(SYSTEM + ${EIGEN3_INCLUDE_DIR} +) add_library(kritalibpaintop SHARED ${kritalibpaintop_LIB_SRCS} ) generate_export_header(kritalibpaintop BASE_NAME kritapaintop EXPORT_MACRO_NAME PAINTOP_EXPORT) + target_link_libraries(kritalibpaintop kritaui kritalibbrush kritawidgetutils) target_link_libraries(kritalibpaintop LINK_INTERFACE_LIBRARIES kritaui kritalibbrush) set_target_properties(kritalibpaintop PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritalibpaintop ${INSTALL_TARGETS_DEFAULT_ARGS}) add_subdirectory(tests) diff --git a/plugins/paintops/libpaintop/Mainpage.dox b/libs/libpaintop/Mainpage.dox similarity index 100% rename from plugins/paintops/libpaintop/Mainpage.dox rename to libs/libpaintop/Mainpage.dox diff --git a/plugins/paintops/libpaintop/forms/wdgBrushSizeOptions.ui b/libs/libpaintop/forms/wdgBrushSizeOptions.ui similarity index 100% rename from plugins/paintops/libpaintop/forms/wdgBrushSizeOptions.ui rename to libs/libpaintop/forms/wdgBrushSizeOptions.ui diff --git a/plugins/paintops/libpaintop/forms/wdgCompositeOpOption.ui b/libs/libpaintop/forms/wdgCompositeOpOption.ui similarity index 100% rename from plugins/paintops/libpaintop/forms/wdgCompositeOpOption.ui rename to libs/libpaintop/forms/wdgCompositeOpOption.ui diff --git a/plugins/paintops/libpaintop/forms/wdgairbrush.ui b/libs/libpaintop/forms/wdgairbrush.ui similarity index 100% rename from plugins/paintops/libpaintop/forms/wdgairbrush.ui rename to libs/libpaintop/forms/wdgairbrush.ui diff --git a/plugins/paintops/libpaintop/forms/wdgautobrush.ui b/libs/libpaintop/forms/wdgautobrush.ui similarity index 100% rename from plugins/paintops/libpaintop/forms/wdgautobrush.ui rename to libs/libpaintop/forms/wdgautobrush.ui diff --git a/plugins/paintops/libpaintop/forms/wdgbrushchooser.ui b/libs/libpaintop/forms/wdgbrushchooser.ui similarity index 100% rename from plugins/paintops/libpaintop/forms/wdgbrushchooser.ui rename to libs/libpaintop/forms/wdgbrushchooser.ui diff --git a/plugins/paintops/libpaintop/forms/wdgclipboardbrush.ui b/libs/libpaintop/forms/wdgclipboardbrush.ui similarity index 100% rename from plugins/paintops/libpaintop/forms/wdgclipboardbrush.ui rename to libs/libpaintop/forms/wdgclipboardbrush.ui diff --git a/plugins/paintops/libpaintop/forms/wdgcoloroptions.ui b/libs/libpaintop/forms/wdgcoloroptions.ui similarity index 100% rename from plugins/paintops/libpaintop/forms/wdgcoloroptions.ui rename to libs/libpaintop/forms/wdgcoloroptions.ui diff --git a/plugins/paintops/libpaintop/forms/wdgcurveoption.ui b/libs/libpaintop/forms/wdgcurveoption.ui similarity index 100% rename from plugins/paintops/libpaintop/forms/wdgcurveoption.ui rename to libs/libpaintop/forms/wdgcurveoption.ui diff --git a/plugins/paintops/libpaintop/forms/wdgcustombrush.ui b/libs/libpaintop/forms/wdgcustombrush.ui similarity index 100% rename from plugins/paintops/libpaintop/forms/wdgcustombrush.ui rename to libs/libpaintop/forms/wdgcustombrush.ui diff --git a/plugins/paintops/libpaintop/forms/wdgfilteroption.ui b/libs/libpaintop/forms/wdgfilteroption.ui similarity index 100% rename from plugins/paintops/libpaintop/forms/wdgfilteroption.ui rename to libs/libpaintop/forms/wdgfilteroption.ui diff --git a/plugins/paintops/libpaintop/forms/wdgflowopacityoption.ui b/libs/libpaintop/forms/wdgflowopacityoption.ui similarity index 100% rename from plugins/paintops/libpaintop/forms/wdgflowopacityoption.ui rename to libs/libpaintop/forms/wdgflowopacityoption.ui diff --git a/plugins/paintops/libpaintop/forms/wdgincremental.ui b/libs/libpaintop/forms/wdgincremental.ui similarity index 100% rename from plugins/paintops/libpaintop/forms/wdgincremental.ui rename to libs/libpaintop/forms/wdgincremental.ui diff --git a/plugins/paintops/libpaintop/forms/wdgmultisensorsselector.ui b/libs/libpaintop/forms/wdgmultisensorsselector.ui similarity index 100% rename from plugins/paintops/libpaintop/forms/wdgmultisensorsselector.ui rename to libs/libpaintop/forms/wdgmultisensorsselector.ui diff --git a/plugins/paintops/libpaintop/forms/wdgpredefinedbrushchooser.ui b/libs/libpaintop/forms/wdgpredefinedbrushchooser.ui similarity index 100% rename from plugins/paintops/libpaintop/forms/wdgpredefinedbrushchooser.ui rename to libs/libpaintop/forms/wdgpredefinedbrushchooser.ui diff --git a/plugins/paintops/libpaintop/forms/wdgtextbrush.ui b/libs/libpaintop/forms/wdgtextbrush.ui similarity index 100% rename from plugins/paintops/libpaintop/forms/wdgtextbrush.ui rename to libs/libpaintop/forms/wdgtextbrush.ui diff --git a/plugins/paintops/libpaintop/kis_airbrush_option.cpp b/libs/libpaintop/kis_airbrush_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_airbrush_option.cpp rename to libs/libpaintop/kis_airbrush_option.cpp diff --git a/plugins/paintops/libpaintop/kis_airbrush_option.h b/libs/libpaintop/kis_airbrush_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_airbrush_option.h rename to libs/libpaintop/kis_airbrush_option.h diff --git a/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp b/libs/libpaintop/kis_auto_brush_widget.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_auto_brush_widget.cpp rename to libs/libpaintop/kis_auto_brush_widget.cpp diff --git a/plugins/paintops/libpaintop/kis_auto_brush_widget.h b/libs/libpaintop/kis_auto_brush_widget.h similarity index 100% rename from plugins/paintops/libpaintop/kis_auto_brush_widget.h rename to libs/libpaintop/kis_auto_brush_widget.h diff --git a/plugins/paintops/libpaintop/kis_bidirectional_mixing_option.cpp b/libs/libpaintop/kis_bidirectional_mixing_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_bidirectional_mixing_option.cpp rename to libs/libpaintop/kis_bidirectional_mixing_option.cpp diff --git a/plugins/paintops/libpaintop/kis_bidirectional_mixing_option.h b/libs/libpaintop/kis_bidirectional_mixing_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_bidirectional_mixing_option.h rename to libs/libpaintop/kis_bidirectional_mixing_option.h diff --git a/plugins/paintops/libpaintop/kis_bidirectional_mixing_option_widget.cpp b/libs/libpaintop/kis_bidirectional_mixing_option_widget.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_bidirectional_mixing_option_widget.cpp rename to libs/libpaintop/kis_bidirectional_mixing_option_widget.cpp diff --git a/plugins/paintops/libpaintop/kis_bidirectional_mixing_option_widget.h b/libs/libpaintop/kis_bidirectional_mixing_option_widget.h similarity index 100% rename from plugins/paintops/libpaintop/kis_bidirectional_mixing_option_widget.h rename to libs/libpaintop/kis_bidirectional_mixing_option_widget.h diff --git a/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp b/libs/libpaintop/kis_brush_based_paintop.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_brush_based_paintop.cpp rename to libs/libpaintop/kis_brush_based_paintop.cpp diff --git a/plugins/paintops/libpaintop/kis_brush_based_paintop.h b/libs/libpaintop/kis_brush_based_paintop.h similarity index 100% rename from plugins/paintops/libpaintop/kis_brush_based_paintop.h rename to libs/libpaintop/kis_brush_based_paintop.h diff --git a/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.cpp b/libs/libpaintop/kis_brush_based_paintop_options_widget.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.cpp rename to libs/libpaintop/kis_brush_based_paintop_options_widget.cpp diff --git a/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.h b/libs/libpaintop/kis_brush_based_paintop_options_widget.h similarity index 100% rename from plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.h rename to libs/libpaintop/kis_brush_based_paintop_options_widget.h diff --git a/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp b/libs/libpaintop/kis_brush_based_paintop_settings.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp rename to libs/libpaintop/kis_brush_based_paintop_settings.cpp diff --git a/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.h b/libs/libpaintop/kis_brush_based_paintop_settings.h similarity index 100% rename from plugins/paintops/libpaintop/kis_brush_based_paintop_settings.h rename to libs/libpaintop/kis_brush_based_paintop_settings.h diff --git a/plugins/paintops/libpaintop/kis_brush_chooser.cpp b/libs/libpaintop/kis_brush_chooser.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_brush_chooser.cpp rename to libs/libpaintop/kis_brush_chooser.cpp diff --git a/plugins/paintops/libpaintop/kis_brush_chooser.h b/libs/libpaintop/kis_brush_chooser.h similarity index 100% rename from plugins/paintops/libpaintop/kis_brush_chooser.h rename to libs/libpaintop/kis_brush_chooser.h diff --git a/plugins/paintops/libpaintop/kis_brush_option.cpp b/libs/libpaintop/kis_brush_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_brush_option.cpp rename to libs/libpaintop/kis_brush_option.cpp diff --git a/plugins/paintops/libpaintop/kis_brush_option.h b/libs/libpaintop/kis_brush_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_brush_option.h rename to libs/libpaintop/kis_brush_option.h diff --git a/plugins/paintops/libpaintop/kis_brush_option_widget.cpp b/libs/libpaintop/kis_brush_option_widget.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_brush_option_widget.cpp rename to libs/libpaintop/kis_brush_option_widget.cpp diff --git a/plugins/paintops/libpaintop/kis_brush_option_widget.h b/libs/libpaintop/kis_brush_option_widget.h similarity index 100% rename from plugins/paintops/libpaintop/kis_brush_option_widget.h rename to libs/libpaintop/kis_brush_option_widget.h diff --git a/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp b/libs/libpaintop/kis_brush_selection_widget.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_brush_selection_widget.cpp rename to libs/libpaintop/kis_brush_selection_widget.cpp diff --git a/plugins/paintops/libpaintop/kis_brush_selection_widget.h b/libs/libpaintop/kis_brush_selection_widget.h similarity index 100% rename from plugins/paintops/libpaintop/kis_brush_selection_widget.h rename to libs/libpaintop/kis_brush_selection_widget.h diff --git a/plugins/paintops/libpaintop/kis_clipboard_brush_widget.cpp b/libs/libpaintop/kis_clipboard_brush_widget.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_clipboard_brush_widget.cpp rename to libs/libpaintop/kis_clipboard_brush_widget.cpp diff --git a/plugins/paintops/libpaintop/kis_clipboard_brush_widget.h b/libs/libpaintop/kis_clipboard_brush_widget.h similarity index 100% rename from plugins/paintops/libpaintop/kis_clipboard_brush_widget.h rename to libs/libpaintop/kis_clipboard_brush_widget.h diff --git a/plugins/paintops/libpaintop/kis_color_option.cpp b/libs/libpaintop/kis_color_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_color_option.cpp rename to libs/libpaintop/kis_color_option.cpp diff --git a/plugins/paintops/libpaintop/kis_color_option.h b/libs/libpaintop/kis_color_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_color_option.h rename to libs/libpaintop/kis_color_option.h diff --git a/plugins/paintops/libpaintop/kis_color_source.cpp b/libs/libpaintop/kis_color_source.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_color_source.cpp rename to libs/libpaintop/kis_color_source.cpp diff --git a/plugins/paintops/libpaintop/kis_color_source.h b/libs/libpaintop/kis_color_source.h similarity index 100% rename from plugins/paintops/libpaintop/kis_color_source.h rename to libs/libpaintop/kis_color_source.h diff --git a/plugins/paintops/libpaintop/kis_color_source_option.cpp b/libs/libpaintop/kis_color_source_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_color_source_option.cpp rename to libs/libpaintop/kis_color_source_option.cpp diff --git a/plugins/paintops/libpaintop/kis_color_source_option.h b/libs/libpaintop/kis_color_source_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_color_source_option.h rename to libs/libpaintop/kis_color_source_option.h diff --git a/plugins/paintops/libpaintop/kis_color_source_option_widget.cpp b/libs/libpaintop/kis_color_source_option_widget.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_color_source_option_widget.cpp rename to libs/libpaintop/kis_color_source_option_widget.cpp diff --git a/plugins/paintops/libpaintop/kis_color_source_option_widget.h b/libs/libpaintop/kis_color_source_option_widget.h similarity index 100% rename from plugins/paintops/libpaintop/kis_color_source_option_widget.h rename to libs/libpaintop/kis_color_source_option_widget.h diff --git a/plugins/paintops/libpaintop/kis_compositeop_option.cpp b/libs/libpaintop/kis_compositeop_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_compositeop_option.cpp rename to libs/libpaintop/kis_compositeop_option.cpp diff --git a/plugins/paintops/libpaintop/kis_compositeop_option.h b/libs/libpaintop/kis_compositeop_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_compositeop_option.h rename to libs/libpaintop/kis_compositeop_option.h diff --git a/plugins/paintops/libpaintop/kis_current_outline_fetcher.cpp b/libs/libpaintop/kis_current_outline_fetcher.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_current_outline_fetcher.cpp rename to libs/libpaintop/kis_current_outline_fetcher.cpp diff --git a/plugins/paintops/libpaintop/kis_current_outline_fetcher.h b/libs/libpaintop/kis_current_outline_fetcher.h similarity index 100% rename from plugins/paintops/libpaintop/kis_current_outline_fetcher.h rename to libs/libpaintop/kis_current_outline_fetcher.h diff --git a/plugins/paintops/libpaintop/kis_curve_label.cpp b/libs/libpaintop/kis_curve_label.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_curve_label.cpp rename to libs/libpaintop/kis_curve_label.cpp diff --git a/plugins/paintops/libpaintop/kis_curve_label.h b/libs/libpaintop/kis_curve_label.h similarity index 100% rename from plugins/paintops/libpaintop/kis_curve_label.h rename to libs/libpaintop/kis_curve_label.h diff --git a/plugins/paintops/libpaintop/kis_curve_option.cpp b/libs/libpaintop/kis_curve_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_curve_option.cpp rename to libs/libpaintop/kis_curve_option.cpp diff --git a/plugins/paintops/libpaintop/kis_curve_option.h b/libs/libpaintop/kis_curve_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_curve_option.h rename to libs/libpaintop/kis_curve_option.h diff --git a/plugins/paintops/libpaintop/kis_curve_option_uniform_property.cpp b/libs/libpaintop/kis_curve_option_uniform_property.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_curve_option_uniform_property.cpp rename to libs/libpaintop/kis_curve_option_uniform_property.cpp diff --git a/plugins/paintops/libpaintop/kis_curve_option_uniform_property.h b/libs/libpaintop/kis_curve_option_uniform_property.h similarity index 100% rename from plugins/paintops/libpaintop/kis_curve_option_uniform_property.h rename to libs/libpaintop/kis_curve_option_uniform_property.h diff --git a/plugins/paintops/libpaintop/kis_curve_option_widget.cpp b/libs/libpaintop/kis_curve_option_widget.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_curve_option_widget.cpp rename to libs/libpaintop/kis_curve_option_widget.cpp diff --git a/plugins/paintops/libpaintop/kis_curve_option_widget.h b/libs/libpaintop/kis_curve_option_widget.h similarity index 100% rename from plugins/paintops/libpaintop/kis_curve_option_widget.h rename to libs/libpaintop/kis_curve_option_widget.h diff --git a/plugins/paintops/libpaintop/kis_custom_brush_widget.cpp b/libs/libpaintop/kis_custom_brush_widget.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_custom_brush_widget.cpp rename to libs/libpaintop/kis_custom_brush_widget.cpp diff --git a/plugins/paintops/libpaintop/kis_custom_brush_widget.h b/libs/libpaintop/kis_custom_brush_widget.h similarity index 100% rename from plugins/paintops/libpaintop/kis_custom_brush_widget.h rename to libs/libpaintop/kis_custom_brush_widget.h diff --git a/plugins/paintops/libpaintop/kis_dab_cache.cpp b/libs/libpaintop/kis_dab_cache.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_dab_cache.cpp rename to libs/libpaintop/kis_dab_cache.cpp diff --git a/plugins/paintops/libpaintop/kis_dab_cache.h b/libs/libpaintop/kis_dab_cache.h similarity index 100% rename from plugins/paintops/libpaintop/kis_dab_cache.h rename to libs/libpaintop/kis_dab_cache.h diff --git a/plugins/paintops/libpaintop/kis_dynamic_sensor.cc b/libs/libpaintop/kis_dynamic_sensor.cc similarity index 100% rename from plugins/paintops/libpaintop/kis_dynamic_sensor.cc rename to libs/libpaintop/kis_dynamic_sensor.cc diff --git a/plugins/paintops/libpaintop/kis_dynamic_sensor.h b/libs/libpaintop/kis_dynamic_sensor.h similarity index 100% rename from plugins/paintops/libpaintop/kis_dynamic_sensor.h rename to libs/libpaintop/kis_dynamic_sensor.h diff --git a/plugins/paintops/libpaintop/kis_dynamic_sensors.cc b/libs/libpaintop/kis_dynamic_sensors.cc similarity index 100% rename from plugins/paintops/libpaintop/kis_dynamic_sensors.cc rename to libs/libpaintop/kis_dynamic_sensors.cc diff --git a/plugins/paintops/libpaintop/kis_dynamic_sensors.h b/libs/libpaintop/kis_dynamic_sensors.h similarity index 100% rename from plugins/paintops/libpaintop/kis_dynamic_sensors.h rename to libs/libpaintop/kis_dynamic_sensors.h diff --git a/plugins/paintops/libpaintop/kis_embedded_pattern_manager.cpp b/libs/libpaintop/kis_embedded_pattern_manager.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_embedded_pattern_manager.cpp rename to libs/libpaintop/kis_embedded_pattern_manager.cpp diff --git a/plugins/paintops/libpaintop/kis_embedded_pattern_manager.h b/libs/libpaintop/kis_embedded_pattern_manager.h similarity index 100% rename from plugins/paintops/libpaintop/kis_embedded_pattern_manager.h rename to libs/libpaintop/kis_embedded_pattern_manager.h diff --git a/plugins/paintops/libpaintop/kis_filter_option.cpp b/libs/libpaintop/kis_filter_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_filter_option.cpp rename to libs/libpaintop/kis_filter_option.cpp diff --git a/plugins/paintops/libpaintop/kis_filter_option.h b/libs/libpaintop/kis_filter_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_filter_option.h rename to libs/libpaintop/kis_filter_option.h diff --git a/plugins/paintops/libpaintop/kis_multi_sensors_model_p.cpp b/libs/libpaintop/kis_multi_sensors_model_p.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_multi_sensors_model_p.cpp rename to libs/libpaintop/kis_multi_sensors_model_p.cpp diff --git a/plugins/paintops/libpaintop/kis_multi_sensors_model_p.h b/libs/libpaintop/kis_multi_sensors_model_p.h similarity index 100% rename from plugins/paintops/libpaintop/kis_multi_sensors_model_p.h rename to libs/libpaintop/kis_multi_sensors_model_p.h diff --git a/plugins/paintops/libpaintop/kis_multi_sensors_selector.cpp b/libs/libpaintop/kis_multi_sensors_selector.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_multi_sensors_selector.cpp rename to libs/libpaintop/kis_multi_sensors_selector.cpp diff --git a/plugins/paintops/libpaintop/kis_multi_sensors_selector.h b/libs/libpaintop/kis_multi_sensors_selector.h similarity index 100% rename from plugins/paintops/libpaintop/kis_multi_sensors_selector.h rename to libs/libpaintop/kis_multi_sensors_selector.h diff --git a/plugins/paintops/libpaintop/kis_outline_generation_policy.h b/libs/libpaintop/kis_outline_generation_policy.h similarity index 100% rename from plugins/paintops/libpaintop/kis_outline_generation_policy.h rename to libs/libpaintop/kis_outline_generation_policy.h diff --git a/plugins/paintops/libpaintop/kis_paint_action_type_option.cpp b/libs/libpaintop/kis_paint_action_type_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_paint_action_type_option.cpp rename to libs/libpaintop/kis_paint_action_type_option.cpp diff --git a/plugins/paintops/libpaintop/kis_paint_action_type_option.h b/libs/libpaintop/kis_paint_action_type_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_paint_action_type_option.h rename to libs/libpaintop/kis_paint_action_type_option.h diff --git a/plugins/paintops/libpaintop/kis_precision_option.cpp b/libs/libpaintop/kis_precision_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_precision_option.cpp rename to libs/libpaintop/kis_precision_option.cpp diff --git a/plugins/paintops/libpaintop/kis_precision_option.h b/libs/libpaintop/kis_precision_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_precision_option.h rename to libs/libpaintop/kis_precision_option.h diff --git a/plugins/paintops/libpaintop/kis_pressure_darken_option.cpp b/libs/libpaintop/kis_pressure_darken_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_darken_option.cpp rename to libs/libpaintop/kis_pressure_darken_option.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_darken_option.h b/libs/libpaintop/kis_pressure_darken_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_darken_option.h rename to libs/libpaintop/kis_pressure_darken_option.h diff --git a/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.cpp b/libs/libpaintop/kis_pressure_flow_opacity_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.cpp rename to libs/libpaintop/kis_pressure_flow_opacity_option.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.h b/libs/libpaintop/kis_pressure_flow_opacity_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.h rename to libs/libpaintop/kis_pressure_flow_opacity_option.h diff --git a/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option_widget.cpp b/libs/libpaintop/kis_pressure_flow_opacity_option_widget.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_flow_opacity_option_widget.cpp rename to libs/libpaintop/kis_pressure_flow_opacity_option_widget.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option_widget.h b/libs/libpaintop/kis_pressure_flow_opacity_option_widget.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_flow_opacity_option_widget.h rename to libs/libpaintop/kis_pressure_flow_opacity_option_widget.h diff --git a/plugins/paintops/libpaintop/kis_pressure_flow_option.cpp b/libs/libpaintop/kis_pressure_flow_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_flow_option.cpp rename to libs/libpaintop/kis_pressure_flow_option.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_flow_option.h b/libs/libpaintop/kis_pressure_flow_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_flow_option.h rename to libs/libpaintop/kis_pressure_flow_option.h diff --git a/plugins/paintops/libpaintop/kis_pressure_gradient_option.cpp b/libs/libpaintop/kis_pressure_gradient_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_gradient_option.cpp rename to libs/libpaintop/kis_pressure_gradient_option.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_gradient_option.h b/libs/libpaintop/kis_pressure_gradient_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_gradient_option.h rename to libs/libpaintop/kis_pressure_gradient_option.h diff --git a/plugins/paintops/libpaintop/kis_pressure_hsv_option.cpp b/libs/libpaintop/kis_pressure_hsv_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_hsv_option.cpp rename to libs/libpaintop/kis_pressure_hsv_option.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_hsv_option.h b/libs/libpaintop/kis_pressure_hsv_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_hsv_option.h rename to libs/libpaintop/kis_pressure_hsv_option.h diff --git a/plugins/paintops/libpaintop/kis_pressure_mirror_option.cpp b/libs/libpaintop/kis_pressure_mirror_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_mirror_option.cpp rename to libs/libpaintop/kis_pressure_mirror_option.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_mirror_option.h b/libs/libpaintop/kis_pressure_mirror_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_mirror_option.h rename to libs/libpaintop/kis_pressure_mirror_option.h diff --git a/plugins/paintops/libpaintop/kis_pressure_mirror_option_widget.cpp b/libs/libpaintop/kis_pressure_mirror_option_widget.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_mirror_option_widget.cpp rename to libs/libpaintop/kis_pressure_mirror_option_widget.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_mirror_option_widget.h b/libs/libpaintop/kis_pressure_mirror_option_widget.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_mirror_option_widget.h rename to libs/libpaintop/kis_pressure_mirror_option_widget.h diff --git a/plugins/paintops/libpaintop/kis_pressure_mix_option.cpp b/libs/libpaintop/kis_pressure_mix_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_mix_option.cpp rename to libs/libpaintop/kis_pressure_mix_option.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_mix_option.h b/libs/libpaintop/kis_pressure_mix_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_mix_option.h rename to libs/libpaintop/kis_pressure_mix_option.h diff --git a/plugins/paintops/libpaintop/kis_pressure_opacity_option.cpp b/libs/libpaintop/kis_pressure_opacity_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_opacity_option.cpp rename to libs/libpaintop/kis_pressure_opacity_option.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_opacity_option.h b/libs/libpaintop/kis_pressure_opacity_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_opacity_option.h rename to libs/libpaintop/kis_pressure_opacity_option.h diff --git a/plugins/paintops/libpaintop/kis_pressure_ratio_option.cpp b/libs/libpaintop/kis_pressure_ratio_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_ratio_option.cpp rename to libs/libpaintop/kis_pressure_ratio_option.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_ratio_option.h b/libs/libpaintop/kis_pressure_ratio_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_ratio_option.h rename to libs/libpaintop/kis_pressure_ratio_option.h diff --git a/plugins/paintops/libpaintop/kis_pressure_rotation_option.cpp b/libs/libpaintop/kis_pressure_rotation_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_rotation_option.cpp rename to libs/libpaintop/kis_pressure_rotation_option.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_rotation_option.h b/libs/libpaintop/kis_pressure_rotation_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_rotation_option.h rename to libs/libpaintop/kis_pressure_rotation_option.h diff --git a/plugins/paintops/libpaintop/kis_pressure_scatter_option.cpp b/libs/libpaintop/kis_pressure_scatter_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_scatter_option.cpp rename to libs/libpaintop/kis_pressure_scatter_option.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_scatter_option.h b/libs/libpaintop/kis_pressure_scatter_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_scatter_option.h rename to libs/libpaintop/kis_pressure_scatter_option.h diff --git a/plugins/paintops/libpaintop/kis_pressure_scatter_option_widget.cpp b/libs/libpaintop/kis_pressure_scatter_option_widget.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_scatter_option_widget.cpp rename to libs/libpaintop/kis_pressure_scatter_option_widget.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_scatter_option_widget.h b/libs/libpaintop/kis_pressure_scatter_option_widget.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_scatter_option_widget.h rename to libs/libpaintop/kis_pressure_scatter_option_widget.h diff --git a/plugins/paintops/libpaintop/kis_pressure_sharpness_option.cpp b/libs/libpaintop/kis_pressure_sharpness_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_sharpness_option.cpp rename to libs/libpaintop/kis_pressure_sharpness_option.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_sharpness_option.h b/libs/libpaintop/kis_pressure_sharpness_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_sharpness_option.h rename to libs/libpaintop/kis_pressure_sharpness_option.h diff --git a/plugins/paintops/libpaintop/kis_pressure_sharpness_option_widget.cpp b/libs/libpaintop/kis_pressure_sharpness_option_widget.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_sharpness_option_widget.cpp rename to libs/libpaintop/kis_pressure_sharpness_option_widget.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_sharpness_option_widget.h b/libs/libpaintop/kis_pressure_sharpness_option_widget.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_sharpness_option_widget.h rename to libs/libpaintop/kis_pressure_sharpness_option_widget.h diff --git a/plugins/paintops/libpaintop/kis_pressure_size_option.cpp b/libs/libpaintop/kis_pressure_size_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_size_option.cpp rename to libs/libpaintop/kis_pressure_size_option.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_size_option.h b/libs/libpaintop/kis_pressure_size_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_size_option.h rename to libs/libpaintop/kis_pressure_size_option.h diff --git a/plugins/paintops/libpaintop/kis_pressure_softness_option.cpp b/libs/libpaintop/kis_pressure_softness_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_softness_option.cpp rename to libs/libpaintop/kis_pressure_softness_option.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_softness_option.h b/libs/libpaintop/kis_pressure_softness_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_softness_option.h rename to libs/libpaintop/kis_pressure_softness_option.h diff --git a/plugins/paintops/libpaintop/kis_pressure_spacing_option.cpp b/libs/libpaintop/kis_pressure_spacing_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_spacing_option.cpp rename to libs/libpaintop/kis_pressure_spacing_option.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_spacing_option.h b/libs/libpaintop/kis_pressure_spacing_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_spacing_option.h rename to libs/libpaintop/kis_pressure_spacing_option.h diff --git a/plugins/paintops/libpaintop/kis_pressure_spacing_option_widget.cpp b/libs/libpaintop/kis_pressure_spacing_option_widget.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_spacing_option_widget.cpp rename to libs/libpaintop/kis_pressure_spacing_option_widget.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_spacing_option_widget.h b/libs/libpaintop/kis_pressure_spacing_option_widget.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_spacing_option_widget.h rename to libs/libpaintop/kis_pressure_spacing_option_widget.h diff --git a/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.cpp b/libs/libpaintop/kis_pressure_texture_strength_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_texture_strength_option.cpp rename to libs/libpaintop/kis_pressure_texture_strength_option.cpp diff --git a/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.h b/libs/libpaintop/kis_pressure_texture_strength_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_pressure_texture_strength_option.h rename to libs/libpaintop/kis_pressure_texture_strength_option.h diff --git a/plugins/paintops/libpaintop/kis_simple_paintop_factory.h b/libs/libpaintop/kis_simple_paintop_factory.h similarity index 100% rename from plugins/paintops/libpaintop/kis_simple_paintop_factory.h rename to libs/libpaintop/kis_simple_paintop_factory.h diff --git a/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp b/libs/libpaintop/kis_spacing_selection_widget.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp rename to libs/libpaintop/kis_spacing_selection_widget.cpp diff --git a/plugins/paintops/libpaintop/kis_spacing_selection_widget.h b/libs/libpaintop/kis_spacing_selection_widget.h similarity index 100% rename from plugins/paintops/libpaintop/kis_spacing_selection_widget.h rename to libs/libpaintop/kis_spacing_selection_widget.h diff --git a/plugins/paintops/libpaintop/kis_text_brush_chooser.cpp b/libs/libpaintop/kis_text_brush_chooser.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_text_brush_chooser.cpp rename to libs/libpaintop/kis_text_brush_chooser.cpp diff --git a/plugins/paintops/libpaintop/kis_text_brush_chooser.h b/libs/libpaintop/kis_text_brush_chooser.h similarity index 100% rename from plugins/paintops/libpaintop/kis_text_brush_chooser.h rename to libs/libpaintop/kis_text_brush_chooser.h diff --git a/plugins/paintops/libpaintop/kis_texture_option.cpp b/libs/libpaintop/kis_texture_option.cpp similarity index 100% rename from plugins/paintops/libpaintop/kis_texture_option.cpp rename to libs/libpaintop/kis_texture_option.cpp diff --git a/plugins/paintops/libpaintop/kis_texture_option.h b/libs/libpaintop/kis_texture_option.h similarity index 100% rename from plugins/paintops/libpaintop/kis_texture_option.h rename to libs/libpaintop/kis_texture_option.h diff --git a/plugins/paintops/libpaintop/sensors/SensorDistanceConfiguration.ui b/libs/libpaintop/sensors/SensorDistanceConfiguration.ui similarity index 100% rename from plugins/paintops/libpaintop/sensors/SensorDistanceConfiguration.ui rename to libs/libpaintop/sensors/SensorDistanceConfiguration.ui diff --git a/plugins/paintops/libpaintop/sensors/SensorFadeConfiguration.ui b/libs/libpaintop/sensors/SensorFadeConfiguration.ui similarity index 100% rename from plugins/paintops/libpaintop/sensors/SensorFadeConfiguration.ui rename to libs/libpaintop/sensors/SensorFadeConfiguration.ui diff --git a/plugins/paintops/libpaintop/sensors/SensorTimeConfiguration.ui b/libs/libpaintop/sensors/SensorTimeConfiguration.ui similarity index 100% rename from plugins/paintops/libpaintop/sensors/SensorTimeConfiguration.ui rename to libs/libpaintop/sensors/SensorTimeConfiguration.ui diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.cc b/libs/libpaintop/sensors/kis_dynamic_sensor_distance.cc similarity index 100% rename from plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.cc rename to libs/libpaintop/sensors/kis_dynamic_sensor_distance.cc diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.h b/libs/libpaintop/sensors/kis_dynamic_sensor_distance.h similarity index 100% rename from plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.h rename to libs/libpaintop/sensors/kis_dynamic_sensor_distance.h diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_drawing_angle.cpp b/libs/libpaintop/sensors/kis_dynamic_sensor_drawing_angle.cpp similarity index 100% rename from plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_drawing_angle.cpp rename to libs/libpaintop/sensors/kis_dynamic_sensor_drawing_angle.cpp diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_drawing_angle.h b/libs/libpaintop/sensors/kis_dynamic_sensor_drawing_angle.h similarity index 100% rename from plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_drawing_angle.h rename to libs/libpaintop/sensors/kis_dynamic_sensor_drawing_angle.h diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.cpp b/libs/libpaintop/sensors/kis_dynamic_sensor_fade.cpp similarity index 100% rename from plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.cpp rename to libs/libpaintop/sensors/kis_dynamic_sensor_fade.cpp diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.h b/libs/libpaintop/sensors/kis_dynamic_sensor_fade.h similarity index 100% rename from plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.h rename to libs/libpaintop/sensors/kis_dynamic_sensor_fade.h diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fuzzy.cpp b/libs/libpaintop/sensors/kis_dynamic_sensor_fuzzy.cpp similarity index 100% rename from plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fuzzy.cpp rename to libs/libpaintop/sensors/kis_dynamic_sensor_fuzzy.cpp diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fuzzy.h b/libs/libpaintop/sensors/kis_dynamic_sensor_fuzzy.h similarity index 100% rename from plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fuzzy.h rename to libs/libpaintop/sensors/kis_dynamic_sensor_fuzzy.h diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.cc b/libs/libpaintop/sensors/kis_dynamic_sensor_time.cc similarity index 100% rename from plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.cc rename to libs/libpaintop/sensors/kis_dynamic_sensor_time.cc diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.h b/libs/libpaintop/sensors/kis_dynamic_sensor_time.h similarity index 100% rename from plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.h rename to libs/libpaintop/sensors/kis_dynamic_sensor_time.h diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensors.cc b/libs/libpaintop/sensors/kis_dynamic_sensors.cc similarity index 100% rename from plugins/paintops/libpaintop/sensors/kis_dynamic_sensors.cc rename to libs/libpaintop/sensors/kis_dynamic_sensors.cc diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensors.h b/libs/libpaintop/sensors/kis_dynamic_sensors.h similarity index 100% rename from plugins/paintops/libpaintop/sensors/kis_dynamic_sensors.h rename to libs/libpaintop/sensors/kis_dynamic_sensors.h diff --git a/plugins/paintops/libpaintop/tests/CMakeLists.txt b/libs/libpaintop/tests/CMakeLists.txt similarity index 95% rename from plugins/paintops/libpaintop/tests/CMakeLists.txt rename to libs/libpaintop/tests/CMakeLists.txt index d38c247878..afa68ec1bc 100644 --- a/plugins/paintops/libpaintop/tests/CMakeLists.txt +++ b/libs/libpaintop/tests/CMakeLists.txt @@ -1,13 +1,14 @@ ########### next target ############### include_directories( ${CMAKE_SOURCE_DIR}/.. ${CMAKE_SOURCE_DIR}/sdk/tests ) include(KritaAddBrokenUnitTest) +include(ECMAddTests) ecm_add_test(kis_sensors_test.cpp TEST_NAME krita-paintop-SensorsTest LINK_LIBRARIES kritaimage kritalibpaintop Qt5::Test) krita_add_broken_unit_test(kis_embedded_pattern_manager_test.cpp TEST_NAME krita-paintop-EmbeddedPatternManagerTest LINK_LIBRARIES kritaimage kritalibpaintop Qt5::Test) diff --git a/plugins/paintops/libpaintop/tests/kis_embedded_pattern_manager_test.cpp b/libs/libpaintop/tests/kis_embedded_pattern_manager_test.cpp similarity index 100% rename from plugins/paintops/libpaintop/tests/kis_embedded_pattern_manager_test.cpp rename to libs/libpaintop/tests/kis_embedded_pattern_manager_test.cpp diff --git a/plugins/paintops/libpaintop/tests/kis_embedded_pattern_manager_test.h b/libs/libpaintop/tests/kis_embedded_pattern_manager_test.h similarity index 100% rename from plugins/paintops/libpaintop/tests/kis_embedded_pattern_manager_test.h rename to libs/libpaintop/tests/kis_embedded_pattern_manager_test.h diff --git a/plugins/paintops/libpaintop/tests/kis_sensors_test.cpp b/libs/libpaintop/tests/kis_sensors_test.cpp similarity index 100% rename from plugins/paintops/libpaintop/tests/kis_sensors_test.cpp rename to libs/libpaintop/tests/kis_sensors_test.cpp diff --git a/plugins/paintops/libpaintop/tests/kis_sensors_test.h b/libs/libpaintop/tests/kis_sensors_test.h similarity index 100% rename from plugins/paintops/libpaintop/tests/kis_sensors_test.h rename to libs/libpaintop/tests/kis_sensors_test.h diff --git a/libs/odf/KoColumns.h b/libs/odf/KoColumns.h index dc6b4e829e..cf068a0cf9 100644 --- a/libs/odf/KoColumns.h +++ b/libs/odf/KoColumns.h @@ -1,137 +1,138 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright 2002, 2003 David Faure Copyright 2003 Nicolas GOUTTE Copyright 2007 Thomas Zander 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. */ #ifndef KOCOLUMNS_H #define KOCOLUMNS_H #include "kritaodf_export.h" #include #include +#include + class KoGenStyle; -class KoXmlElement; /** structure for columns */ struct KoColumns { enum SeparatorVerticalAlignment { AlignTop = Qt::AlignTop, AlignVCenter = Qt::AlignVCenter, AlignBottom = Qt::AlignBottom }; enum SeparatorStyle { None = Qt::NoPen, Solid = Qt::SolidLine, Dashed = Qt::DashLine, Dotted = Qt::DotLine, DotDashed = Qt::DashDotLine }; struct ColumnDatum { /** Left indent in points */ qreal leftMargin; /** Right indent in points */ qreal rightMargin; /** Top indent in points */ qreal topMargin; /** Bottom indent in points */ qreal bottomMargin; /** The relative width */ int relativeWidth; ColumnDatum() {} ColumnDatum(qreal lm, qreal rm, qreal tm, qreal bm, int rw) : leftMargin(lm), rightMargin(rm), topMargin(tm), bottomMargin(bm), relativeWidth(rw) {} bool operator==(const KoColumns::ColumnDatum &rhs) const { return (leftMargin == rhs.leftMargin) && (rightMargin == rhs.rightMargin) && (topMargin == rhs.topMargin) && (bottomMargin == rhs.bottomMargin) && (relativeWidth == rhs.relativeWidth); } }; /** Number of columns */ int count; /** Spacing between columns in points */ qreal gapWidth; SeparatorStyle separatorStyle; QColor separatorColor; SeparatorVerticalAlignment separatorVerticalAlignment; /** Width in pt */ qreal separatorWidth; /** Height in percent. Default is 100% */ unsigned int separatorHeight; /** data about the individual columns if there */ QList columnData; /** * Construct a columns with the default column count 1, * default margins (2 cm), and portrait orientation. */ KRITAODF_EXPORT KoColumns(); KRITAODF_EXPORT void reset(); KRITAODF_EXPORT bool operator==(const KoColumns &l) const; KRITAODF_EXPORT bool operator!=(const KoColumns &l) const; /** * Save this columns to ODF. */ KRITAODF_EXPORT void saveOdf(KoGenStyle &style) const; /** * Load this columns from ODF */ KRITAODF_EXPORT void loadOdf(const KoXmlElement &style); qreal totalRelativeWidth() const { qreal result = 0.0; Q_FOREACH (const ColumnDatum &c, columnData) { result += c.relativeWidth; } return result; } KRITAODF_EXPORT static const char * separatorStyleString(KoColumns::SeparatorStyle separatorStyle); KRITAODF_EXPORT static const char * separatorVerticalAlignmentString(KoColumns::SeparatorVerticalAlignment separatorVerticalAlignment); KRITAODF_EXPORT static KoColumns::SeparatorVerticalAlignment parseSeparatorVerticalAlignment(const QString &value); KRITAODF_EXPORT static QColor parseSeparatorColor(const QString &value); KRITAODF_EXPORT static int parseSeparatorHeight(const QString &value); KRITAODF_EXPORT static KoColumns::SeparatorStyle parseSeparatorStyle(const QString &value); KRITAODF_EXPORT static int parseRelativeWidth(const QString &value); }; #endif /* KOCOLUMNS_H */ diff --git a/libs/odf/KoOdfReadStore.cpp b/libs/odf/KoOdfReadStore.cpp index c02e88d0df..505dd7d138 100644 --- a/libs/odf/KoOdfReadStore.cpp +++ b/libs/odf/KoOdfReadStore.cpp @@ -1,145 +1,141 @@ /* This file is part of the KDE project Copyright (C) 2005 David Faure Copyright (C) 2007 Thorsten Zach3n 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 "KoOdfReadStore.h" #include #include #include #include #include "KoOdfStylesReader.h" #include class Q_DECL_HIDDEN KoOdfReadStore::Private { public: Private(KoStore *s) : store(s) { } KoStore * store; KoOdfStylesReader stylesReader; // it is needed to keep the stylesDoc around so that you can access the styles KoXmlDocument stylesDoc; KoXmlDocument contentDoc; KoXmlDocument settingsDoc; }; KoOdfReadStore::KoOdfReadStore(KoStore *store) : d(new Private(store)) { } KoOdfReadStore::~KoOdfReadStore() { delete d; } KoStore * KoOdfReadStore::store() const { return d->store; } KoOdfStylesReader &KoOdfReadStore::styles() { return d->stylesReader; } KoXmlDocument KoOdfReadStore::contentDoc() const { return d->contentDoc; } KoXmlDocument KoOdfReadStore::settingsDoc() const { return d->settingsDoc; } bool KoOdfReadStore::loadAndParse(QString &errorMessage) { if (!loadAndParse("content.xml", d->contentDoc, errorMessage)) { return false; } if (d->store->hasFile("styles.xml")) { if (!loadAndParse("styles.xml", d->stylesDoc, errorMessage)) { return false; } } // Load styles from style.xml d->stylesReader.createStyleMap(d->stylesDoc, true); // Also load styles from content.xml d->stylesReader.createStyleMap(d->contentDoc, false); if (d->store->hasFile("settings.xml")) { loadAndParse("settings.xml", d->settingsDoc, errorMessage); } return true; } bool KoOdfReadStore::loadAndParse(const QString &fileName, KoXmlDocument &doc, QString &errorMessage) { if (!d->store) { errorMessage = i18n("No store backend"); return false; } if (!d->store->isOpen()) { if (!d->store->open(fileName)) { debugOdf << "Entry " << fileName << " not found!"; // not a warning as embedded stores don't have to have all files errorMessage = i18n("Could not find %1", fileName); return false; } } bool ok = loadAndParse(d->store->device(), doc, errorMessage, fileName); d->store->close(); return ok; } bool KoOdfReadStore::loadAndParse(QIODevice *fileDevice, KoXmlDocument &doc, QString &errorMessage, const QString &fileName) { // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; if (!fileDevice->isOpen()) { fileDevice->open(QIODevice::ReadOnly); } - - QXmlStreamReader reader(fileDevice); - reader.setNamespaceProcessing(true); - - bool ok = doc.setContent(&reader, &errorMsg, &errorLine, &errorColumn); + bool ok = doc.setContent(fileDevice, true, &errorMsg, &errorLine, &errorColumn); if (!ok) { errorOdf << "Parsing error in " << fileName << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errorMessage = i18n("Parsing error in the main document at line %1, column %2\nError message: %3" , errorLine , errorColumn , errorMsg); } else { debugOdf << "File" << fileName << " loaded and parsed"; } return ok; } diff --git a/libs/odf/tests/TestXmlReaderWithoutSpaces.cpp b/libs/odf/tests/TestXmlReaderWithoutSpaces.cpp index 1d3449ed3e..e0b7107746 100644 --- a/libs/odf/tests/TestXmlReaderWithoutSpaces.cpp +++ b/libs/odf/tests/TestXmlReaderWithoutSpaces.cpp @@ -1,2696 +1,2700 @@ #include #include #include #include #include #include #include #include #include class TestXmlReaderWithoutSpaces : public QObject { Q_OBJECT private Q_SLOTS: +#ifndef KOXML_USE_QDOM void testNode(); void testElement(); void testAttributes(); void testText(); void testCDATA(); void testDocument(); void testDocumentType(); void testNamespace(); void testParseQString(); void testUnload(); void testSimpleXML(); void testRootError(); void testMismatchedTag(); void testConvertQDomDocument(); void testConvertQDomElement(); void testSimpleOpenDocumentText(); void testWhitespace(); void testSimpleOpenDocumentSpreadsheet(); void testSimpleOpenDocumentPresentation(); void testSimpleOpenDocumentFormula(); void testLargeOpenDocumentSpreadsheet(); void testExternalOpenDocumentSpreadsheet(const QString& filename); +#endif }; +#ifndef KOXML_USE_QDOM void TestXmlReaderWithoutSpaces::testNode() { QString errorMsg; int errorLine = 0; int errorColumn = 0; QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmldevice.close(); KoXmlDocument doc(false); QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); // null node KoXmlNode node1; QCOMPARE(node1.nodeName(), QString()); QCOMPARE(node1.isNull(), true); QCOMPARE(node1.isElement(), false); QCOMPARE(node1.isDocument(), false); QCOMPARE(node1.ownerDocument().isNull(), true); QCOMPARE(node1.parentNode().isNull(), true); QCOMPARE(node1.hasChildNodes(), false); QCOMPARE(KoXml::childNodesCount(node1), 0); QCOMPARE(node1.firstChild().isNull(), true); QCOMPARE(node1.lastChild().isNull(), true); QCOMPARE(node1.previousSibling().isNull(), true); QCOMPARE(node1.nextSibling().isNull(), true); // compare with another null node KoXmlNode node2; QCOMPARE(node2.isNull(), true); QCOMPARE(node1 == node2, true); QCOMPARE(node1 != node2, false); // a node which is a document KoXmlNode node3 = doc; QCOMPARE(node3.nodeName(), QString("#document")); QCOMPARE(node3.isNull(), false); QCOMPARE(node3.isElement(), false); QCOMPARE(node3.isText(), false); QCOMPARE(node3.isDocument(), true); QCOMPARE(node3.ownerDocument().isNull(), false); QCOMPARE(node3.ownerDocument() == doc, true); QCOMPARE(node3.toDocument() == doc, true); QCOMPARE(KoXml::childNodesCount(node3), 1); // convert to document and the compare KoXmlDocument doc2 = node3.toDocument(); QCOMPARE(doc2.nodeName(), QString("#document")); QCOMPARE(doc2.isNull(), false); QCOMPARE(doc2.isDocument(), true); QCOMPARE(node3 == doc2, true); QCOMPARE(KoXml::childNodesCount(doc2), 1); // a document is of course can't be converted to element KoXmlElement invalidElement = node3.toElement(); QCOMPARE(invalidElement.nodeName(), QString()); QCOMPARE(invalidElement.isNull(), true); QCOMPARE(invalidElement.isElement(), false); QCOMPARE(invalidElement.isText(), false); QCOMPARE(invalidElement.isDocument(), false); // clear() makes it a null node again node3.clear(); QCOMPARE(node3.isNull(), true); QCOMPARE(node3.nodeName(), QString()); QCOMPARE(node3.isElement(), false); QCOMPARE(node3.isText(), false); QCOMPARE(node3.isDocument(), false); QCOMPARE(node3.ownerDocument().isNull(), true); QCOMPARE(node1 == node3, true); QCOMPARE(node1 != node3, false); // a node which is an element for KoXmlNode node4 = doc.firstChild(); QCOMPARE(node4.isNull(), false); QCOMPARE(node4.isElement(), true); QCOMPARE(node4.isText(), false); QCOMPARE(node4.isDocument(), false); QCOMPARE(node4.hasChildNodes(), true); QCOMPARE(KoXml::childNodesCount(node4), 2); QCOMPARE(node4.ownerDocument() == doc, true); QCOMPARE(node4.toElement() == doc.firstChild().toElement(), true); // clear() makes it a null node again node4.clear(); QCOMPARE(node4.isNull(), true); QCOMPARE(node4.isElement(), false); QCOMPARE(node4.isText(), false); QCOMPARE(node4.isDocument(), false); QCOMPARE(node4 == node1, true); QCOMPARE(node4 != node1, false); QCOMPARE(KoXml::childNodesCount(node4), 0); // a node which is an element for KoXmlNode node5 = doc.firstChild().firstChild(); QCOMPARE(node5.nodeName(), QString("continents")); QCOMPARE(node5.isNull(), false); QCOMPARE(node5.isElement(), true); QCOMPARE(node5.isText(), false); QCOMPARE(node5.isDocument(), false); QCOMPARE(node5.hasChildNodes(), true); QCOMPARE(KoXml::childNodesCount(node5), 6); QCOMPARE(node5.ownerDocument() == doc, true); // convert to element and the compare KoXmlElement continentsElement = node5.toElement(); QCOMPARE(node5 == continentsElement, true); QCOMPARE(continentsElement.isNull(), false); QCOMPARE(continentsElement.isElement(), true); QCOMPARE(continentsElement.isText(), false); QCOMPARE(continentsElement.hasChildNodes(), true); QCOMPARE(KoXml::childNodesCount(continentsElement), 6); QCOMPARE(continentsElement.ownerDocument() == doc, true); // and it doesn't make sense to convert that node to document KoXmlDocument invalidDoc = node5.toDocument(); QCOMPARE(invalidDoc.isNull(), true); QCOMPARE(invalidDoc.isElement(), false); QCOMPARE(invalidDoc.isText(), false); QCOMPARE(invalidDoc.isDocument(), false); // node for using namedItem() function KoXmlNode europeNode = continentsElement.namedItem(QString("europe")); QCOMPARE(europeNode.nodeName(), QString("europe")); QCOMPARE(europeNode.isNull(), false); QCOMPARE(europeNode.isElement(), true); QCOMPARE(europeNode.isText(), false); QCOMPARE(europeNode.hasChildNodes(), false); QCOMPARE(KoXml::childNodesCount(europeNode), 0); QCOMPARE(europeNode.ownerDocument() == doc, true); // search non-existing node KoXmlNode fooNode = continentsElement.namedItem(QString("foobar")); QCOMPARE(fooNode.isNull(), true); QCOMPARE(fooNode.isElement(), false); QCOMPARE(fooNode.isText(), false); QCOMPARE(fooNode.isCDATASection(), false); QCOMPARE(KoXml::childNodesCount(fooNode), 0); } void TestXmlReaderWithoutSpaces::testElement() { QString errorMsg; int errorLine = 0; int errorColumn = 0; QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream << ""; xmlstream << ""; xmlstream << "

"; xmlstream << "Hello, world!"; xmlstream << "

"; xmlstream << ""; xmlstream << ""; xmldevice.close(); KoXmlDocument doc(false); QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); // element for KoXmlElement rootElement; rootElement = doc.documentElement(); QCOMPARE(rootElement.nodeName(), QString("html")); QCOMPARE(rootElement.isNull(), false); QCOMPARE(rootElement.isElement(), true); QCOMPARE(rootElement.isDocument(), false); QCOMPARE(rootElement.ownerDocument().isNull(), false); QCOMPARE(rootElement.ownerDocument() == doc, true); QCOMPARE(rootElement.parentNode().isNull(), false); QCOMPARE(rootElement.parentNode().toDocument() == doc, true); QCOMPARE(rootElement.hasChildNodes(), true); QCOMPARE(KoXml::childNodesCount(rootElement), 1); QCOMPARE(rootElement.tagName(), QString("html")); QCOMPARE(rootElement.prefix().isNull(), true); // element for KoXmlElement bodyElement; bodyElement = rootElement.firstChild().toElement(); QCOMPARE(bodyElement.nodeName(), QString("body")); QCOMPARE(bodyElement.isNull(), false); QCOMPARE(bodyElement.isElement(), true); QCOMPARE(bodyElement.isDocument(), false); QCOMPARE(bodyElement.ownerDocument().isNull(), false); QCOMPARE(bodyElement.ownerDocument() == doc, true); QCOMPARE(bodyElement.parentNode().isNull(), false); QCOMPARE(bodyElement.parentNode() == rootElement, true); QCOMPARE(bodyElement.hasChildNodes(), true); QCOMPARE(KoXml::childNodesCount(bodyElement), 1); QCOMPARE(bodyElement.tagName(), QString("body")); QCOMPARE(bodyElement.prefix().isNull(), true); QCOMPARE(bodyElement.hasAttribute("bgcolor"), true); QCOMPARE(bodyElement.attribute("bgcolor"), QString("#000")); // a shared copy of , will still have access to attribute bgcolor KoXmlElement body2Element; body2Element = bodyElement; QCOMPARE(body2Element.nodeName(), QString("body")); QCOMPARE(body2Element.isNull(), false); QCOMPARE(body2Element.isElement(), true); QCOMPARE(body2Element.isDocument(), false); QCOMPARE(body2Element.ownerDocument().isNull(), false); QCOMPARE(body2Element.ownerDocument() == doc, true); QCOMPARE(body2Element == bodyElement, true); QCOMPARE(body2Element != bodyElement, false); QCOMPARE(body2Element.hasChildNodes(), true); QCOMPARE(KoXml::childNodesCount(body2Element), 1); QCOMPARE(body2Element.tagName(), QString("body")); QCOMPARE(body2Element.prefix().isNull(), true); QCOMPARE(body2Element.hasAttribute("bgcolor"), true); QCOMPARE(body2Element.attribute("bgcolor"), QString("#000")); // empty element, by default constructor KoXmlElement testElement; QCOMPARE(testElement.nodeName(), QString()); QCOMPARE(testElement.isNull(), true); QCOMPARE(testElement.isElement(), false); QCOMPARE(testElement.isDocument(), false); QCOMPARE(testElement.ownerDocument().isNull(), true); QCOMPARE(testElement.ownerDocument() != doc, true); QCOMPARE(testElement == rootElement, false); QCOMPARE(testElement != rootElement, true); QCOMPARE(testElement.parentNode().isNull(), true); QCOMPARE(testElement.hasChildNodes(), false); QCOMPARE(KoXml::childNodesCount(testElement), 0); // check assignment operator testElement = rootElement; QCOMPARE(testElement.nodeName(), QString("html")); QCOMPARE(testElement.isNull(), false); QCOMPARE(testElement.isElement(), true); QCOMPARE(testElement.isDocument(), false); QCOMPARE(testElement == rootElement, true); QCOMPARE(testElement != rootElement, false); QCOMPARE(testElement.parentNode().isNull(), false); QCOMPARE(testElement.parentNode().toDocument() == doc, true); QCOMPARE(testElement.tagName(), QString("html")); QCOMPARE(testElement.prefix().isNull(), true); QCOMPARE(KoXml::childNodesCount(testElement), 1); // assigned from another empty element testElement = KoXmlElement(); QCOMPARE(testElement.isNull(), true); QCOMPARE(testElement != rootElement, true); // assigned from testElement = bodyElement; QCOMPARE(testElement.isNull(), false); QCOMPARE(testElement.isElement(), true); QCOMPARE(testElement.isDocument(), false); QCOMPARE(testElement.ownerDocument().isNull(), false); QCOMPARE(testElement.ownerDocument() == doc, true); QCOMPARE(testElement == bodyElement, true); QCOMPARE(testElement.parentNode().isNull(), false); QCOMPARE(testElement.tagName(), QString("body")); QCOMPARE(testElement.prefix().isNull(), true); QCOMPARE(testElement.hasChildNodes(), true); QCOMPARE(KoXml::childNodesCount(testElement), 1); // copy constructor KoXmlElement dummyElement(rootElement); QCOMPARE(dummyElement.isNull(), false); QCOMPARE(dummyElement.isElement(), true); QCOMPARE(dummyElement.isDocument(), false); QCOMPARE(dummyElement.ownerDocument().isNull(), false); QCOMPARE(dummyElement.ownerDocument() == doc, true); QCOMPARE(dummyElement == rootElement, true); QCOMPARE(dummyElement.parentNode().isNull(), false); QCOMPARE(dummyElement.hasChildNodes(), true); QCOMPARE(KoXml::childNodesCount(dummyElement), 1); QCOMPARE(dummyElement.tagName(), QString("html")); QCOMPARE(dummyElement.prefix().isNull(), true); // clear() turns element to null node dummyElement.clear(); QCOMPARE(dummyElement.isNull(), true); QCOMPARE(dummyElement.isElement(), false); QCOMPARE(dummyElement.isDocument(), false); QCOMPARE(dummyElement.ownerDocument().isNull(), true); QCOMPARE(dummyElement.ownerDocument() == doc, false); QCOMPARE(dummyElement.hasChildNodes(), false); QCOMPARE(KoXml::childNodesCount(dummyElement), 0); QCOMPARE(dummyElement == rootElement, false); QCOMPARE(dummyElement != rootElement, true); // check for plain null node converted to element KoXmlNode dummyNode; dummyElement = dummyNode.toElement(); QCOMPARE(dummyElement.isNull(), true); QCOMPARE(dummyElement.isElement(), false); QCOMPARE(dummyElement.isDocument(), false); QCOMPARE(dummyElement.ownerDocument().isNull(), true); QCOMPARE(dummyElement.hasChildNodes(), false); QCOMPARE(KoXml::childNodesCount(dummyElement), 0); QCOMPARE(dummyElement.ownerDocument() == doc, false); } void TestXmlReaderWithoutSpaces::testAttributes() { QString errorMsg; int errorLine = 0; int errorColumn = 0; QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream << "

"; xmlstream << ""; xmlstream << "

"; xmldevice.close(); KoXmlDocument doc(false); QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); KoXmlElement rootElement; rootElement = doc.documentElement(); QCOMPARE(rootElement.isNull(), false); QCOMPARE(rootElement.isElement(), true); QCOMPARE(rootElement.parentNode().isNull(), false); QCOMPARE(rootElement.parentNode().toDocument() == doc, true); QCOMPARE(rootElement.tagName(), QString("p")); QCOMPARE(rootElement.prefix().isNull(), true); QCOMPARE(KoXml::childNodesCount(rootElement), 1); KoXmlElement imgElement; imgElement = rootElement.firstChild().toElement(); QCOMPARE(imgElement.isNull(), false); QCOMPARE(imgElement.isElement(), true); QCOMPARE(imgElement.tagName(), QString("img")); QCOMPARE(imgElement.prefix().isNull(), true); QCOMPARE(KoXml::childNodesCount(imgElement), 0); QCOMPARE(imgElement.hasAttribute("src"), true); QCOMPARE(imgElement.hasAttribute("width"), true); QCOMPARE(imgElement.hasAttribute("height"), true); QCOMPARE(imgElement.hasAttribute("non-exist"), false); QCOMPARE(imgElement.hasAttribute("SRC"), false); QCOMPARE(imgElement.attribute("src"), QString("foo.png")); QCOMPARE(imgElement.attribute("width"), QString("300")); QCOMPARE(imgElement.attribute("width").toInt(), 300); QCOMPARE(imgElement.attribute("height"), QString("150")); QCOMPARE(imgElement.attribute("height").toInt(), 150); QCOMPARE(imgElement.attribute("border").isEmpty(), true); QCOMPARE(imgElement.attribute("border", "0").toInt(), 0); QCOMPARE(imgElement.attribute("border", "-1").toInt(), -1); QStringList list = KoXml::attributeNames(imgElement); QCOMPARE(list.count(), 3); QVERIFY(list.contains("src")); QVERIFY(list.contains("width")); QVERIFY(list.contains("height")); QVERIFY(! list.contains("border")); Q_FOREACH (QString a, list) { QCOMPARE(imgElement.hasAttribute(a), true); QCOMPARE(imgElement.attribute(a).isEmpty(), false); } } void TestXmlReaderWithoutSpaces::testText() { QString errorMsg; int errorLine = 0; int errorColumn = 0; QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream << "

"; xmlstream << "Hello "; xmlstream << "world"; xmlstream << "

"; xmldevice.close(); KoXmlDocument doc(false); QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); // element for

KoXmlElement parElement; parElement = doc.documentElement(); QCOMPARE(parElement.isNull(), false); QCOMPARE(parElement.isElement(), true); QCOMPARE(parElement.isText(), false); QCOMPARE(parElement.isDocument(), false); QCOMPARE(parElement.ownerDocument().isNull(), false); QCOMPARE(parElement.ownerDocument() == doc, true); QCOMPARE(parElement.parentNode().isNull(), false); QCOMPARE(parElement.parentNode().toDocument() == doc, true); QCOMPARE(parElement.hasChildNodes(), true); QCOMPARE(KoXml::childNodesCount(parElement), 2); // and text node "Hello " QCOMPARE(parElement.tagName(), QString("p")); QCOMPARE(parElement.prefix().isNull(), true); QCOMPARE(parElement.text(), QString("Hello world")); // node for "Hello" KoXmlNode helloNode; helloNode = parElement.firstChild(); QCOMPARE(helloNode.nodeName(), QString("#text")); QCOMPARE(helloNode.isNull(), false); QCOMPARE(helloNode.isElement(), false); QCOMPARE(helloNode.isText(), true); QCOMPARE(helloNode.isDocument(), false); QCOMPARE(helloNode.hasChildNodes(), false); QCOMPARE(KoXml::childNodesCount(helloNode), 0); // "Hello" text KoXmlText helloText; helloText = helloNode.toText(); QCOMPARE(helloText.nodeName(), QString("#text")); QCOMPARE(helloText.isNull(), false); QCOMPARE(helloText.isElement(), false); QCOMPARE(helloText.isText(), true); QCOMPARE(helloText.isDocument(), false); QCOMPARE(helloText.data(), QString("Hello ")); QCOMPARE(KoXml::childNodesCount(helloText), 0); // shared copy of the text KoXmlText hello2Text; hello2Text = helloText; QCOMPARE(hello2Text.isNull(), false); QCOMPARE(hello2Text.isElement(), false); QCOMPARE(hello2Text.isText(), true); QCOMPARE(hello2Text.isDocument(), false); QCOMPARE(hello2Text.data(), QString("Hello ")); QCOMPARE(KoXml::childNodesCount(hello2Text), 0); // element for KoXmlElement boldElement; boldElement = helloNode.nextSibling().toElement(); QCOMPARE(boldElement.isNull(), false); QCOMPARE(boldElement.isElement(), true); QCOMPARE(boldElement.isText(), false); QCOMPARE(boldElement.isDocument(), false); QCOMPARE(boldElement.ownerDocument().isNull(), false); QCOMPARE(boldElement.ownerDocument() == doc, true); QCOMPARE(boldElement.parentNode().isNull(), false); QCOMPARE(boldElement.hasChildNodes(), true); QCOMPARE(KoXml::childNodesCount(boldElement), 1); // text node "world" QCOMPARE(boldElement.tagName(), QString("b")); QCOMPARE(boldElement.prefix().isNull(), true); // "world" text KoXmlText worldText; worldText = boldElement.firstChild().toText(); QCOMPARE(worldText.isNull(), false); QCOMPARE(worldText.isElement(), false); QCOMPARE(worldText.isText(), true); QCOMPARE(worldText.isDocument(), false); QCOMPARE(worldText.data(), QString("world")); QCOMPARE(KoXml::childNodesCount(worldText), 0); } void TestXmlReaderWithoutSpaces::testCDATA() { QString errorMsg; int errorLine = 0; int errorColumn = 0; QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream << "

"; xmlstream << "Hello "; xmlstream << ""; xmlstream << "

"; xmldevice.close(); KoXmlDocument doc(false); QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); // element for

KoXmlElement parElement; parElement = doc.documentElement(); QCOMPARE(parElement.isNull(), false); QCOMPARE(parElement.isElement(), true); QCOMPARE(parElement.isText(), false); QCOMPARE(parElement.isDocument(), false); QCOMPARE(parElement.ownerDocument().isNull(), false); QCOMPARE(parElement.ownerDocument() == doc, true); QCOMPARE(parElement.parentNode().isNull(), false); QCOMPARE(parElement.parentNode().toDocument() == doc, true); QCOMPARE(parElement.hasChildNodes(), true); QCOMPARE(KoXml::childNodesCount(parElement), 2); QCOMPARE(parElement.tagName(), QString("p")); QCOMPARE(parElement.prefix().isNull(), true); QCOMPARE(parElement.text(), QString("Hello world")); // node for "Hello" KoXmlNode helloNode; helloNode = parElement.firstChild(); QCOMPARE(helloNode.isNull(), false); QCOMPARE(helloNode.isElement(), false); QCOMPARE(helloNode.isText(), true); QCOMPARE(helloNode.isDocument(), false); // "Hello" text KoXmlText helloText; helloText = helloNode.toText(); QCOMPARE(helloText.isNull(), false); QCOMPARE(helloText.isElement(), false); QCOMPARE(helloText.isText(), true); QCOMPARE(helloText.isDocument(), false); QCOMPARE(helloText.data(), QString("Hello ")); // node for CDATA "world!" // Note: isText() is also true for CDATA KoXmlNode worldNode; worldNode = helloNode.nextSibling(); QCOMPARE(worldNode.nodeName(), QString("#cdata-section")); QCOMPARE(worldNode.isNull(), false); QCOMPARE(worldNode.isElement(), false); QCOMPARE(worldNode.isText(), true); QCOMPARE(worldNode.isCDATASection(), true); QCOMPARE(worldNode.isDocument(), false); // CDATA section for "world!" // Note: isText() is also true for CDATA KoXmlCDATASection worldCDATA; worldCDATA = worldNode.toCDATASection(); QCOMPARE(worldCDATA.nodeName(), QString("#cdata-section")); QCOMPARE(worldCDATA.isNull(), false); QCOMPARE(worldCDATA.isElement(), false); QCOMPARE(worldCDATA.isText(), true); QCOMPARE(worldCDATA.isCDATASection(), true); QCOMPARE(worldCDATA.isDocument(), false); QCOMPARE(worldCDATA.data(), QString("world")); } void TestXmlReaderWithoutSpaces::testDocument() { QString errorMsg; int errorLine = 0; int errorColumn = 0; QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream << ""; xmlstream << "\n"; xmlstream << "\n"; xmlstream << "\n"; xmlstream << ""; xmldevice.close(); KoXmlDocument doc(false); // empty document QCOMPARE(doc.nodeName(), QString()); QCOMPARE(doc.isNull(), true); QCOMPARE(doc.isElement(), false); QCOMPARE(doc.isDocument(), false); QCOMPARE(doc.parentNode().isNull(), true); QCOMPARE(doc.firstChild().isNull(), true); QCOMPARE(doc.lastChild().isNull(), true); QCOMPARE(doc.previousSibling().isNull(), true); QCOMPARE(doc.nextSibling().isNull(), true); // now give something as the content QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); // this document has something already QCOMPARE(doc.nodeName(), QString("#document")); QCOMPARE(doc.isNull(), false); QCOMPARE(doc.isElement(), false); QCOMPARE(doc.isDocument(), true); QCOMPARE(doc.parentNode().isNull(), true); QCOMPARE(doc.firstChild().isNull(), false); QCOMPARE(doc.lastChild().isNull(), false); QCOMPARE(doc.previousSibling().isNull(), true); QCOMPARE(doc.nextSibling().isNull(), true); // make sure its children are fine KoXmlElement rootElement; rootElement = doc.firstChild().toElement(); QCOMPARE(rootElement.isNull(), false); QCOMPARE(rootElement.isElement(), true); QCOMPARE(rootElement.isDocument(), false); QCOMPARE(rootElement.parentNode().isNull(), false); QCOMPARE(rootElement.parentNode().toDocument() == doc, true); rootElement = doc.lastChild().toElement(); QCOMPARE(rootElement.isNull(), false); QCOMPARE(rootElement.isElement(), true); QCOMPARE(rootElement.isDocument(), false); QCOMPARE(rootElement.parentNode().isNull(), false); QCOMPARE(rootElement.parentNode().toDocument() == doc, true); // clear() converts it into null node doc.clear(); QCOMPARE(doc.nodeName(), QString()); QCOMPARE(doc.isNull(), true); QCOMPARE(doc.isElement(), false); QCOMPARE(doc.isDocument(), false); QCOMPARE(doc.parentNode().isNull(), true); QCOMPARE(doc.firstChild().isNull(), true); QCOMPARE(doc.lastChild().isNull(), true); QCOMPARE(doc.previousSibling().isNull(), true); QCOMPARE(doc.nextSibling().isNull(), true); // assigned from another empty document doc = KoXmlDocument(); QCOMPARE(doc.nodeName(), QString()); QCOMPARE(doc.nodeName().isEmpty(), true); QCOMPARE(doc.isNull(), true); QCOMPARE(doc.isElement(), false); QCOMPARE(doc.isDocument(), false); QCOMPARE(doc.parentNode().isNull(), true); } void TestXmlReaderWithoutSpaces::testDocumentType() { QString errorMsg; int errorLine = 0; int errorColumn = 0; QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << "

"; xmlstream << "

"; xmlstream << ""; xmldevice.close(); // empty document KoXmlDocument doc(false); QCOMPARE(doc.nodeName(), QString()); QCOMPARE(doc.isNull(), true); QCOMPARE(doc.isElement(), false); QCOMPARE(doc.isDocument(), false); QCOMPARE(doc.parentNode().isNull(), true); QCOMPARE(doc.firstChild().isNull(), true); QCOMPARE(doc.lastChild().isNull(), true); QCOMPARE(doc.previousSibling().isNull(), true); QCOMPARE(doc.nextSibling().isNull(), true); // has empty doctype KoXmlDocumentType doctype = doc.doctype(); QCOMPARE(doctype.nodeName(), QString()); QCOMPARE(doctype.isNull(), true); QCOMPARE(doctype.isElement(), false); QCOMPARE(doctype.isDocument(), false); QCOMPARE(doctype.isDocumentType(), false); QCOMPARE(doctype.parentNode().isNull(), true); QCOMPARE(doctype.firstChild().isNull(), true); QCOMPARE(doctype.lastChild().isNull(), true); QCOMPARE(doctype.previousSibling().isNull(), true); QCOMPARE(doctype.nextSibling().isNull(), true); // now give something as the content QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); // this document has something already QCOMPARE(doc.nodeName(), QString("#document")); QCOMPARE(doc.isNull(), false); QCOMPARE(doc.isElement(), false); QCOMPARE(doc.isDocument(), true); QCOMPARE(doc.parentNode().isNull(), true); QCOMPARE(doc.firstChild().isNull(), false); QCOMPARE(doc.lastChild().isNull(), false); QCOMPARE(doc.previousSibling().isNull(), true); QCOMPARE(doc.nextSibling().isNull(), true); // the doctype becomes a valid one doctype = doc.doctype(); QCOMPARE(doctype.nodeName(), QString("html")); QCOMPARE(doctype.name(), QString("html")); QCOMPARE(doctype.isNull(), false); QCOMPARE(doctype.isElement(), false); QCOMPARE(doctype.isDocument(), false); QCOMPARE(doctype.isDocumentType(), true); QCOMPARE(doctype.parentNode().isNull(), false); QCOMPARE(doctype.parentNode() == doc, true); QCOMPARE(doctype.firstChild().isNull(), true); QCOMPARE(doctype.lastChild().isNull(), true); QCOMPARE(doctype.previousSibling().isNull(), true); QCOMPARE(doctype.nextSibling().isNull(), true); } void TestXmlReaderWithoutSpaces::testNamespace() { QString errorMsg; int errorLine = 0; int errorColumn = 0; QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); // taken from example in Qt documentation (xml.html) xmlstream << ""; xmlstream << ""; xmlstream << "Practical XML"; xmlstream << ""; xmlstream << ""; xmlstream << "A Namespace Called fnord"; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmldevice.close(); KoXmlDocument doc(false); KoXmlElement rootElement; KoXmlElement bookElement; KoXmlElement bookTitleElement; KoXmlElement bookAuthorElement; // ------------- first without any namespace processing ----------- QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); rootElement = doc.documentElement(); QCOMPARE(rootElement.isNull(), false); QCOMPARE(rootElement.isElement(), true); QCOMPARE(rootElement.tagName(), QString("document")); QCOMPARE(rootElement.prefix().isNull(), true); bookElement = rootElement.firstChild().toElement(); QCOMPARE(bookElement.isNull(), false); QCOMPARE(bookElement.isElement(), true); QCOMPARE(bookElement.tagName(), QString("book")); QCOMPARE(bookElement.prefix().isNull(), true); QCOMPARE(bookElement.localName(), QString()); bookTitleElement = bookElement.firstChild().toElement(); QCOMPARE(bookTitleElement.isNull(), false); QCOMPARE(bookTitleElement.isElement(), true); QCOMPARE(bookTitleElement.tagName(), QString("book:title")); QCOMPARE(bookTitleElement.prefix().isNull(), true); QCOMPARE(bookTitleElement.localName(), QString()); bookAuthorElement = bookTitleElement.nextSibling().toElement(); QCOMPARE(bookAuthorElement.isNull(), false); QCOMPARE(bookAuthorElement.isElement(), true); QCOMPARE(bookAuthorElement.tagName(), QString("book:author")); QCOMPARE(bookAuthorElement.prefix().isNull(), true); QCOMPARE(bookAuthorElement.attribute("title"), QString("Ms")); QCOMPARE(bookAuthorElement.attribute("fnord:title"), QString("Goddess")); QCOMPARE(bookAuthorElement.attribute("name"), QString("Eris Kallisti")); // ------------- now with namespace processing ----------- xmldevice.seek(0); // just to rewind QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); const char* defaultNS = "http://trolltech.com/fnord/"; const char* bookNS = "http://trolltech.com/fnord/book/"; const char* fnordNS = "http://trolltech.com/fnord/"; // rootElement = doc.documentElement(); QCOMPARE(rootElement.isNull(), false); QCOMPARE(rootElement.isElement(), true); QCOMPARE(rootElement.tagName(), QString("document")); QCOMPARE(rootElement.prefix().isEmpty(), true); QCOMPARE(rootElement.namespaceURI(), QString(defaultNS)); QCOMPARE(rootElement.localName(), QString("document")); // bookElement = rootElement.firstChild().toElement(); QCOMPARE(bookElement.isNull(), false); QCOMPARE(bookElement.isElement(), true); QCOMPARE(bookElement.tagName(), QString("book")); QCOMPARE(bookElement.prefix().isEmpty(), true); QCOMPARE(bookElement.namespaceURI(), QString(defaultNS)); QCOMPARE(bookElement.localName(), QString("book")); // bookTitleElement = bookElement.firstChild().toElement(); QCOMPARE(bookTitleElement.isNull(), false); QCOMPARE(bookTitleElement.isElement(), true); QCOMPARE(bookTitleElement.tagName(), QString("title")); QCOMPARE(bookTitleElement.prefix(), QString("book")); QCOMPARE(bookTitleElement.namespaceURI(), QString(bookNS)); QCOMPARE(bookTitleElement.localName(), QString("title")); // another way, find it using namedItemNS() KoXmlElement book2TitleElement; book2TitleElement = KoXml::namedItemNS(rootElement.firstChild(), bookNS, "title"); //book2TitleElement = bookElement.namedItemNS( bookNS, "title" ).toElement(); QCOMPARE(book2TitleElement == bookTitleElement, true); QCOMPARE(book2TitleElement.isNull(), false); QCOMPARE(book2TitleElement.isElement(), true); QCOMPARE(book2TitleElement.tagName(), QString("title")); // bookAuthorElement = bookTitleElement.nextSibling().toElement(); QCOMPARE(bookAuthorElement.isNull(), false); QCOMPARE(bookAuthorElement.isElement(), true); QCOMPARE(bookAuthorElement.tagName(), QString("author")); QCOMPARE(bookAuthorElement.prefix(), QString("book")); QCOMPARE(bookAuthorElement.namespaceURI(), QString(bookNS)); QCOMPARE(bookAuthorElement.localName(), QString("author")); // another way, find it using namedItemNS() KoXmlElement book2AuthorElement; book2AuthorElement = KoXml::namedItemNS(bookElement, bookNS, "author"); //book2AuthorElement = bookElement.namedItemNS( bookNS, "author" ).toElement(); QCOMPARE(book2AuthorElement == bookAuthorElement, true); QCOMPARE(book2AuthorElement.isNull(), false); QCOMPARE(book2AuthorElement.isElement(), true); QCOMPARE(book2AuthorElement.tagName(), QString("author")); // attributes in // Note: with namespace processing, attribute's prefix is taken out and // hence "fnord:title" will simply override "title" // and searching attribute with prefix will give no result QCOMPARE(bookAuthorElement.hasAttribute("title"), true); QCOMPARE(bookAuthorElement.hasAttribute("fnord:title"), false); QCOMPARE(bookAuthorElement.hasAttribute("name"), true); QCOMPARE(bookAuthorElement.attribute("title"), QString("Goddess")); QCOMPARE(bookAuthorElement.attribute("fnord:title").isEmpty(), true); QCOMPARE(bookAuthorElement.attribute("name"), QString("Eris Kallisti")); // attributes in , with NS family of functions // those without prefix are not accessible at all, because they do not belong // to any namespace at all. // Note: default namespace does not apply to attribute names! QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "title"), true); QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "title"), false); QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "title"), true); QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "title", ""), QString("Goddess")); QCOMPARE(bookAuthorElement.attributeNS(bookNS, "title", ""), QString()); QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "title", ""), QString("Goddess")); QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "fnord:title"), false); QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "fnord:title"), false); QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "fnord:title"), false); QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "name"), false); QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "name"), false); QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "name"), false); QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "name", QString()).isEmpty(), true); QCOMPARE(bookAuthorElement.attributeNS(bookNS, "name", QString()).isEmpty(), true); QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "name", QString()).isEmpty(), true); } // mostly similar to testNamespace above, but parse from a QString void TestXmlReaderWithoutSpaces::testParseQString() { QString errorMsg; int errorLine = 0; int errorColumn = 0; QString xmlText; xmlText += ""; xmlText += ""; xmlText += "Practical XML"; xmlText += ""; xmlText += ""; xmlText += "A Namespace Called fnord"; xmlText += ""; xmlText += ""; xmlText += ""; KoXmlDocument doc(false); KoXmlElement rootElement; KoXmlElement bookElement; KoXmlElement bookTitleElement; KoXmlElement bookAuthorElement; QCOMPARE(doc.setContent(xmlText, true, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); const char* defaultNS = "http://trolltech.com/fnord/"; const char* bookNS = "http://trolltech.com/fnord/book/"; const char* fnordNS = "http://trolltech.com/fnord/"; // rootElement = doc.documentElement(); QCOMPARE(rootElement.isNull(), false); QCOMPARE(rootElement.isElement(), true); QCOMPARE(rootElement.tagName(), QString("document")); QCOMPARE(rootElement.prefix().isEmpty(), true); QCOMPARE(rootElement.namespaceURI(), QString(defaultNS)); QCOMPARE(rootElement.localName(), QString("document")); // bookElement = rootElement.firstChild().toElement(); QCOMPARE(bookElement.isNull(), false); QCOMPARE(bookElement.isElement(), true); QCOMPARE(bookElement.tagName(), QString("book")); QCOMPARE(bookElement.prefix().isEmpty(), true); QCOMPARE(bookElement.namespaceURI(), QString(defaultNS)); QCOMPARE(bookElement.localName(), QString("book")); // bookTitleElement = bookElement.firstChild().toElement(); QCOMPARE(bookTitleElement.isNull(), false); QCOMPARE(bookTitleElement.isElement(), true); QCOMPARE(bookTitleElement.tagName(), QString("title")); QCOMPARE(bookTitleElement.prefix(), QString("book")); QCOMPARE(bookTitleElement.namespaceURI(), QString(bookNS)); QCOMPARE(bookTitleElement.localName(), QString("title")); // another way, find it using namedItemNS() KoXmlElement book2TitleElement; book2TitleElement = KoXml::namedItemNS(rootElement.firstChild(), bookNS, "title"); //book2TitleElement = bookElement.namedItemNS( bookNS, "title" ).toElement(); QCOMPARE(book2TitleElement == bookTitleElement, true); QCOMPARE(book2TitleElement.isNull(), false); QCOMPARE(book2TitleElement.isElement(), true); QCOMPARE(book2TitleElement.tagName(), QString("title")); // bookAuthorElement = bookTitleElement.nextSibling().toElement(); QCOMPARE(bookAuthorElement.isNull(), false); QCOMPARE(bookAuthorElement.isElement(), true); QCOMPARE(bookAuthorElement.tagName(), QString("author")); QCOMPARE(bookAuthorElement.prefix(), QString("book")); QCOMPARE(bookAuthorElement.namespaceURI(), QString(bookNS)); QCOMPARE(bookAuthorElement.localName(), QString("author")); // another way, find it using namedItemNS() KoXmlElement book2AuthorElement; book2AuthorElement = KoXml::namedItemNS(bookElement, bookNS, "author"); //book2AuthorElement = bookElement.namedItemNS( bookNS, "author" ).toElement(); QCOMPARE(book2AuthorElement == bookAuthorElement, true); QCOMPARE(book2AuthorElement.isNull(), false); QCOMPARE(book2AuthorElement.isElement(), true); QCOMPARE(book2AuthorElement.tagName(), QString("author")); // attributes in // Note: with namespace processing, attribute's prefix is taken out and // hence "fnord:title" will simply override "title" // and searching attribute with prefix will give no result QCOMPARE(bookAuthorElement.hasAttribute("title"), true); QCOMPARE(bookAuthorElement.hasAttribute("fnord:title"), false); QCOMPARE(bookAuthorElement.hasAttribute("name"), true); QCOMPARE(bookAuthorElement.attribute("title"), QString("Goddess")); QCOMPARE(bookAuthorElement.attribute("fnord:title").isEmpty(), true); QCOMPARE(bookAuthorElement.attribute("name"), QString("Eris Kallisti")); // attributes in , with NS family of functions // those without prefix are not accessible at all, because they do not belong // to any namespace at all. // Note: default namespace does not apply to attribute names! QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "title"), true); QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "title"), false); QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "title"), true); QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "title", ""), QString("Goddess")); QCOMPARE(bookAuthorElement.attributeNS(bookNS, "title", ""), QString()); QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "title", ""), QString("Goddess")); QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "fnord:title"), false); QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "fnord:title"), false); QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "fnord:title"), false); QCOMPARE(bookAuthorElement.hasAttributeNS(defaultNS, "name"), false); QCOMPARE(bookAuthorElement.hasAttributeNS(bookNS, "name"), false); QCOMPARE(bookAuthorElement.hasAttributeNS(fnordNS, "name"), false); QCOMPARE(bookAuthorElement.attributeNS(defaultNS, "name", QString()).isEmpty(), true); QCOMPARE(bookAuthorElement.attributeNS(bookNS, "name", QString()).isEmpty(), true); QCOMPARE(bookAuthorElement.attributeNS(fnordNS, "name", QString()).isEmpty(), true); } void TestXmlReaderWithoutSpaces::testUnload() { QString errorMsg; int errorLine = 0; int errorColumn = 0; QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmldevice.close(); KoXmlDocument doc(false); QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); KoXmlElement earthElement; earthElement = doc.documentElement().toElement(); QCOMPARE(earthElement.isNull(), false); QCOMPARE(earthElement.isElement(), true); QCOMPARE(earthElement.parentNode().isNull(), false); QCOMPARE(earthElement.hasChildNodes(), true); QCOMPARE(KoXml::childNodesCount(earthElement), 2); QCOMPARE(earthElement.tagName(), QString("earth")); QCOMPARE(earthElement.prefix().isNull(), true); // this ensures that all child nodes of are loaded earthElement.firstChild(); // explicitly unload all child nodes of KoXml::unload(earthElement); // we should get the correct first child KoXmlElement continentsElement = earthElement.firstChild().toElement(); QCOMPARE(continentsElement.nodeName(), QString("continents")); QCOMPARE(continentsElement.isNull(), false); QCOMPARE(continentsElement.isElement(), true); QCOMPARE(continentsElement.isText(), false); QCOMPARE(continentsElement.isDocument(), false); QCOMPARE(continentsElement.hasChildNodes(), true); QCOMPARE(KoXml::childNodesCount(continentsElement), 6); // let us unload everything again KoXml::unload(earthElement); // we should get the correct last child KoXmlElement oceansElement = earthElement.lastChild().toElement(); QCOMPARE(oceansElement.nodeName(), QString("oceans")); QCOMPARE(oceansElement.isNull(), false); QCOMPARE(oceansElement.isElement(), true); QCOMPARE(oceansElement.isText(), false); QCOMPARE(oceansElement.isDocument(), false); QCOMPARE(oceansElement.hasChildNodes(), true); QCOMPARE(KoXml::childNodesCount(continentsElement), 6); } void TestXmlReaderWithoutSpaces::testSimpleXML() { QString errorMsg; int errorLine = 0; int errorColumn = 0; QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmldevice.close(); KoXmlDocument doc(false); QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); // KoXmlElement rootElement; rootElement = doc.documentElement(); QCOMPARE(rootElement.isNull(), false); QCOMPARE(rootElement.isElement(), true); QCOMPARE(rootElement.parentNode().isNull(), false); QCOMPARE(rootElement.hasChildNodes(), true); QCOMPARE(KoXml::childNodesCount(rootElement), 5); QCOMPARE(rootElement.tagName(), QString("solarsystem")); QCOMPARE(rootElement.prefix().isNull(), true); // node KoXmlNode firstPlanetNode; firstPlanetNode = rootElement.firstChild(); QCOMPARE(firstPlanetNode.isNull(), false); QCOMPARE(firstPlanetNode.isElement(), true); QCOMPARE(firstPlanetNode.nextSibling().isNull(), false); QCOMPARE(firstPlanetNode.previousSibling().isNull(), true); QCOMPARE(firstPlanetNode.parentNode().isNull(), false); QCOMPARE(firstPlanetNode.parentNode() == rootElement, true); QCOMPARE(firstPlanetNode.parentNode() != rootElement, false); QCOMPARE(firstPlanetNode.hasChildNodes(), false); QCOMPARE(KoXml::childNodesCount(firstPlanetNode), 0); QCOMPARE(firstPlanetNode.firstChild().isNull(), true); QCOMPARE(firstPlanetNode.lastChild().isNull(), true); // element KoXmlElement firstPlanetElement; firstPlanetElement = firstPlanetNode.toElement(); QCOMPARE(firstPlanetElement.isNull(), false); QCOMPARE(firstPlanetElement.isElement(), true); QCOMPARE(firstPlanetElement.parentNode().isNull(), false); QCOMPARE(firstPlanetElement.parentNode() == rootElement, true); QCOMPARE(firstPlanetElement.hasChildNodes(), false); QCOMPARE(KoXml::childNodesCount(firstPlanetNode), 0); QCOMPARE(firstPlanetElement.firstChild().isNull(), true); QCOMPARE(firstPlanetElement.lastChild().isNull(), true); QCOMPARE(firstPlanetElement.tagName(), QString("mercurius")); QCOMPARE(firstPlanetElement.prefix().isNull(), true); // node KoXmlNode secondPlanetNode; secondPlanetNode = firstPlanetNode.nextSibling(); QCOMPARE(secondPlanetNode.isNull(), false); QCOMPARE(secondPlanetNode.isElement(), true); QCOMPARE(secondPlanetNode.nextSibling().isNull(), false); QCOMPARE(secondPlanetNode.previousSibling().isNull(), false); QCOMPARE(secondPlanetNode.previousSibling() == firstPlanetNode, true); QCOMPARE(secondPlanetNode.previousSibling() == firstPlanetElement, true); QCOMPARE(secondPlanetNode.parentNode().isNull(), false); QCOMPARE(secondPlanetNode.parentNode() == rootElement, true); QCOMPARE(secondPlanetNode.parentNode() != rootElement, false); QCOMPARE(secondPlanetNode.hasChildNodes(), false); QCOMPARE(KoXml::childNodesCount(secondPlanetNode), 0); QCOMPARE(secondPlanetNode.firstChild().isNull(), true); QCOMPARE(secondPlanetNode.lastChild().isNull(), true); // element KoXmlElement secondPlanetElement; secondPlanetElement = secondPlanetNode.toElement(); QCOMPARE(secondPlanetElement.isNull(), false); QCOMPARE(secondPlanetElement.isElement(), true); QCOMPARE(secondPlanetElement.nextSibling().isNull(), false); QCOMPARE(secondPlanetElement.previousSibling().isNull(), false); QCOMPARE(secondPlanetElement.previousSibling() == firstPlanetNode, true); QCOMPARE(secondPlanetElement.previousSibling() == firstPlanetElement, true); QCOMPARE(secondPlanetElement.parentNode().isNull(), false); QCOMPARE(secondPlanetElement.parentNode() == rootElement, true); QCOMPARE(secondPlanetElement.parentNode() != rootElement, false); QCOMPARE(secondPlanetElement.hasChildNodes(), false); QCOMPARE(KoXml::childNodesCount(secondPlanetNode), 0); QCOMPARE(secondPlanetElement.firstChild().isNull(), true); QCOMPARE(secondPlanetElement.lastChild().isNull(), true); QCOMPARE(secondPlanetElement.tagName(), QString("venus")); QCOMPARE(secondPlanetElement.prefix().isNull(), true); } void TestXmlReaderWithoutSpaces::testRootError() { QString errorMsg; int errorLine = 0; int errorColumn = 0; // multiple root nodes are not valid ! QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream << ""; xmldevice.close(); KoXmlDocument doc(false); QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), false); QCOMPARE(errorMsg.isEmpty(), false); QCOMPARE(errorMsg, QString("Extra content at end of document.")); QCOMPARE(errorLine, 1); QCOMPARE(errorColumn, 21); } void TestXmlReaderWithoutSpaces::testMismatchedTag() { QString errorMsg; int errorLine = 0; int errorColumn = 0; QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream << ""; xmldevice.close(); KoXmlDocument doc(false); QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), false); QCOMPARE(errorMsg.isEmpty(), false); QCOMPARE(errorMsg, QString("Opening and ending tag mismatch.")); QCOMPARE(errorLine, 1); QCOMPARE(errorColumn, 11); } static void dumpNodes(const KoXmlNode &node, int level=0) { QString indent = QString("%1").arg("", level*3); if (node.isNull()) { qDebug()<"; xmlstream << ""; xmlstream << ""; xmlstream << "

The best place

"; xmlstream << ""; xmlstream << "
"; xmlstream << ""; xmlstream << ""; xmlstream << "
"; xmldevice.close(); KoXmlDocument doc(false); QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); dumpNodes(doc); // KoXmlElement rootElement; rootElement = doc.documentElement(); QCOMPARE(rootElement.isNull(), false); QCOMPARE(rootElement.isElement(), true); QCOMPARE(rootElement.parentNode().isNull(), false); QCOMPARE(rootElement.hasChildNodes(), true); QCOMPARE(KoXml::childNodesCount(rootElement), 5); QCOMPARE(rootElement.tagName(), QString("solarsystem")); QCOMPARE(rootElement.prefix().isNull(), true); // now test converting KoXmlDocument to QDomDocument QDomDocument universeDoc = KoXml::asQDomDocument(doc); // QDomElement solarSystemElement = universeDoc.documentElement(); QCOMPARE(solarSystemElement.isNull(), false); QCOMPARE(solarSystemElement.isElement(), true); QCOMPARE(solarSystemElement.parentNode().isNull(), false); QCOMPARE(solarSystemElement.hasChildNodes(), true); QCOMPARE(solarSystemElement.tagName(), QString("solarsystem")); QCOMPARE(solarSystemElement.prefix().isNull(), true); // QDomElement earthElement = solarSystemElement.namedItem("earth").toElement(); QCOMPARE(earthElement.isNull(), false); QCOMPARE(earthElement.isElement(), true); QCOMPARE(earthElement.parentNode().isNull(), false); QCOMPARE(earthElement.hasAttribute("habitable"), true); QCOMPARE(earthElement.hasChildNodes(), true); QCOMPARE(earthElement.tagName(), QString("earth")); QCOMPARE(earthElement.prefix().isNull(), true); //

in QDomNode placeNode = earthElement.firstChild(); qDebug()<<"placeNode"<"; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << "

The best place

"; xmlstream << ""; xmlstream << "
"; xmlstream << ""; xmlstream << ""; xmlstream << "
"; xmlstream << ""; xmldevice.close(); KoXmlDocument doc(false); QCOMPARE(doc.setContent(&xmldevice, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); // KoXmlElement rootElement; rootElement = doc.documentElement(); QCOMPARE(rootElement.isNull(), false); QCOMPARE(rootElement.isElement(), true); QCOMPARE(rootElement.parentNode().isNull(), false); QCOMPARE(rootElement.hasChildNodes(), true); QCOMPARE(KoXml::childNodesCount(rootElement), 1); QCOMPARE(rootElement.tagName(), QString("universe")); QCOMPARE(rootElement.prefix().isNull(), true); // now test converting KoXmlElement to QDomElement QDomDocument solarDoc; KoXml::asQDomElement(solarDoc, rootElement.firstChild().toElement()); // QDomElement solarSystemElement = solarDoc.documentElement(); QCOMPARE(solarSystemElement.isNull(), false); QCOMPARE(solarSystemElement.isElement(), true); QCOMPARE(solarSystemElement.parentNode().isNull(), false); QCOMPARE(solarSystemElement.hasChildNodes(), true); QCOMPARE(solarSystemElement.tagName(), QString("solarsystem")); QCOMPARE(solarSystemElement.prefix().isNull(), true); // QDomElement earthElement = solarSystemElement.namedItem("earth").toElement(); QCOMPARE(earthElement.isNull(), false); QCOMPARE(earthElement.isElement(), true); QCOMPARE(earthElement.parentNode().isNull(), false); QCOMPARE(earthElement.hasAttribute("habitable"), true); QCOMPARE(earthElement.hasChildNodes(), true); QCOMPARE(earthElement.tagName(), QString("earth")); QCOMPARE(earthElement.prefix().isNull(), true); //

in QDomNode placeNode = earthElement.firstChild(); QCOMPARE(placeNode.isNull(), false); QCOMPARE(placeNode.isElement(), true); QCOMPARE(placeNode.toElement().text(), QString("The best place")); QCOMPARE(placeNode.nextSibling().isNull(), false); QCOMPARE(placeNode.previousSibling().isNull(), true); QCOMPARE(placeNode.parentNode().isNull(), false); QCOMPARE(placeNode.parentNode() == earthElement, true); QCOMPARE(placeNode.hasChildNodes(), true); QCOMPARE(placeNode.childNodes().count(), 1); //printf("Result:\n%s\n\n", qPrintable(universeDoc.toString())); } void TestXmlReaderWithoutSpaces::testSimpleOpenDocumentText() { QString errorMsg; int errorLine = 0; int errorColumn = 0; QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); // content.xml from a simple OpenDocument text // it has only paragraph "Hello, world!" // automatic styles, declarations and unnecessary namespaces are omitted. xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << "Hello, world!"; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmldevice.close(); KoXmlDocument doc(false); QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); const char* officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; const char* textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; // KoXmlElement contentElement; contentElement = doc.documentElement(); QCOMPARE(contentElement.isNull(), false); QCOMPARE(contentElement.isElement(), true); QCOMPARE(contentElement.parentNode().isNull(), false); QCOMPARE(contentElement.parentNode().toDocument() == doc, true); QCOMPARE(KoXml::childNodesCount(contentElement), 2); QCOMPARE(contentElement.firstChild().isNull(), false); QCOMPARE(contentElement.lastChild().isNull(), false); QCOMPARE(contentElement.previousSibling().isNull(), false); QCOMPARE(contentElement.nextSibling().isNull(), true); QCOMPARE(contentElement.localName(), QString("document-content")); QCOMPARE(contentElement.hasAttributeNS(officeNS, "version"), true); QCOMPARE(contentElement.attributeNS(officeNS, "version", ""), QString("1.0")); // KoXmlElement stylesElement; stylesElement = KoXml::namedItemNS(contentElement, officeNS, "automatic-styles"); QCOMPARE(stylesElement.isNull(), false); QCOMPARE(stylesElement.isElement(), true); QCOMPARE(stylesElement.parentNode().isNull(), false); QCOMPARE(stylesElement.parentNode() == contentElement, true); QCOMPARE(KoXml::childNodesCount(stylesElement), 0); QCOMPARE(stylesElement.firstChild().isNull(), true); QCOMPARE(stylesElement.lastChild().isNull(), true); QCOMPARE(stylesElement.previousSibling().isNull(), true); QCOMPARE(stylesElement.nextSibling().isNull(), false); QCOMPARE(stylesElement.localName(), QString("automatic-styles")); // also same , but without namedItemNS KoXmlNode styles2Element; styles2Element = contentElement.firstChild().toElement(); QCOMPARE(styles2Element.isNull(), false); QCOMPARE(styles2Element.isElement(), true); QCOMPARE(styles2Element.parentNode().isNull(), false); QCOMPARE(styles2Element.parentNode() == contentElement, true); QCOMPARE(KoXml::childNodesCount(styles2Element), 0); QCOMPARE(styles2Element.firstChild().isNull(), true); QCOMPARE(styles2Element.lastChild().isNull(), true); QCOMPARE(styles2Element.previousSibling().isNull(), true); QCOMPARE(styles2Element.nextSibling().isNull(), false); QCOMPARE(styles2Element.localName(), QString("automatic-styles")); // KoXmlElement bodyElement; bodyElement = KoXml::namedItemNS(contentElement, officeNS, "body"); QCOMPARE(bodyElement.isNull(), false); QCOMPARE(bodyElement.isElement(), true); QCOMPARE(bodyElement.parentNode().isNull(), false); QCOMPARE(bodyElement.parentNode() == contentElement, true); QCOMPARE(KoXml::childNodesCount(bodyElement), 1); QCOMPARE(bodyElement.firstChild().isNull(), false); QCOMPARE(bodyElement.lastChild().isNull(), false); QCOMPARE(bodyElement.previousSibling().isNull(), false); QCOMPARE(bodyElement.nextSibling().isNull(), true); QCOMPARE(bodyElement.localName(), QString("body")); // also same , but without namedItemNS KoXmlElement body2Element; body2Element = stylesElement.nextSibling().toElement(); QCOMPARE(body2Element.isNull(), false); QCOMPARE(body2Element.isElement(), true); QCOMPARE(body2Element.parentNode().isNull(), false); QCOMPARE(body2Element.parentNode() == contentElement, true); QCOMPARE(KoXml::childNodesCount(body2Element), 1); QCOMPARE(body2Element.firstChild().isNull(), false); QCOMPARE(body2Element.lastChild().isNull(), false); QCOMPARE(body2Element.previousSibling().isNull(), false); QCOMPARE(body2Element.nextSibling().isNull(), true); QCOMPARE(body2Element.localName(), QString("body")); // KoXmlElement textElement; textElement = KoXml::namedItemNS(bodyElement, officeNS, "text"); QCOMPARE(textElement.isNull(), false); QCOMPARE(textElement.isElement(), true); QCOMPARE(textElement.parentNode().isNull(), false); QCOMPARE(textElement.parentNode() == bodyElement, true); QCOMPARE(KoXml::childNodesCount(textElement), 1); QCOMPARE(textElement.firstChild().isNull(), false); QCOMPARE(textElement.lastChild().isNull(), false); QCOMPARE(textElement.previousSibling().isNull(), true); QCOMPARE(textElement.nextSibling().isNull(), true); QCOMPARE(textElement.localName(), QString("text")); // the same , but without namedItemNS KoXmlElement text2Element; text2Element = bodyElement.firstChild().toElement(); QCOMPARE(text2Element.isNull(), false); QCOMPARE(text2Element.isElement(), true); QCOMPARE(text2Element.parentNode().isNull(), false); QCOMPARE(text2Element.parentNode() == bodyElement, true); QCOMPARE(KoXml::childNodesCount(text2Element), 1); QCOMPARE(text2Element.firstChild().isNull(), false); QCOMPARE(text2Element.lastChild().isNull(), false); QCOMPARE(text2Element.previousSibling().isNull(), true); QCOMPARE(text2Element.nextSibling().isNull(), true); QCOMPARE(text2Element.localName(), QString("text")); // KoXmlElement parElement; parElement = textElement.firstChild().toElement(); QCOMPARE(parElement.isNull(), false); QCOMPARE(parElement.isElement(), true); QCOMPARE(parElement.parentNode().isNull(), false); QCOMPARE(parElement.parentNode() == textElement, true); QCOMPARE(KoXml::childNodesCount(parElement), 1); QCOMPARE(parElement.firstChild().isNull(), false); QCOMPARE(parElement.lastChild().isNull(), false); QCOMPARE(parElement.previousSibling().isNull(), true); QCOMPARE(parElement.nextSibling().isNull(), true); QCOMPARE(parElement.tagName(), QString("p")); QCOMPARE(parElement.text(), QString("Hello, world!")); QCOMPARE(parElement.attributeNS(QString(textNS), "style-name", ""), QString("Standard")); } void TestXmlReaderWithoutSpaces::testWhitespace() { QString errorMsg; int errorLine = 0; int errorColumn = 0; QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); // content.xml for testing paragraphs with whitespace /* The list of elements for which whitespace should be preserved can be obtained from the Relax NG schema with these commands: cat OpenDocument-schema-v1.0-os.rng| xmlstarlet sel \ -N s="http://relaxng.org/ns/structure/1.0" -t -m '//s:text' \ -v '../@name' -n |grep : cat OpenDocument-schema-v1.0-os.rng| xmlstarlet sel \ -N s="http://relaxng.org/ns/structure/1.0" \ -t -m "//s:ref[@name='paragraph-content']" -v '../../@name' -n |grep : */ xmlstream << ""; xmlstream << ""; xmlstream << " "; xmlstream << " "; xmlstream << ""; xmldevice.close(); KoXmlDocument doc(false); QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); KoXmlElement p1; p1 = doc.documentElement().firstChild().toElement(); QCOMPARE(p1.isNull(), false); QCOMPARE(p1.isElement(), true); QCOMPARE(KoXml::childNodesCount(p1), 1); KoXmlElement p2; p2 = p1.nextSibling().toElement(); QCOMPARE(p2.isNull(), false); QCOMPARE(p2.isElement(), true); QCOMPARE(KoXml::childNodesCount(p2), 3); } void TestXmlReaderWithoutSpaces::testSimpleOpenDocumentSpreadsheet() { QString errorMsg; int errorLine = 0; int errorColumn = 0; QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); // content.xml from a simple OpenDocument spreadsheet // the document has three worksheets, the last two are empty. // on the first sheet, cell A1 contains the text "Hello, world". // automatic styles, font declarations and unnecessary namespaces are omitted. xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << "Hello, world"; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmldevice.close(); KoXmlDocument doc(false); QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); QString officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; QString tableNS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"; QString textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; // KoXmlElement contentElement; contentElement = doc.documentElement(); QCOMPARE(contentElement.isNull(), false); QCOMPARE(contentElement.isElement(), true); QCOMPARE(contentElement.parentNode().isNull(), false); QCOMPARE(contentElement.parentNode().toDocument() == doc, true); QCOMPARE(KoXml::childNodesCount(contentElement), 1); QCOMPARE(contentElement.firstChild().isNull(), false); QCOMPARE(contentElement.lastChild().isNull(), false); QCOMPARE(contentElement.previousSibling().isNull(), false); QCOMPARE(contentElement.nextSibling().isNull(), true); QCOMPARE(contentElement.localName(), QString("document-content")); // KoXmlElement bodyElement; bodyElement = contentElement.firstChild().toElement(); QCOMPARE(bodyElement.isNull(), false); QCOMPARE(bodyElement.isElement(), true); QCOMPARE(bodyElement.parentNode().isNull(), false); QCOMPARE(bodyElement.parentNode() == contentElement, true); QCOMPARE(KoXml::childNodesCount(bodyElement), 1); QCOMPARE(bodyElement.firstChild().isNull(), false); QCOMPARE(bodyElement.lastChild().isNull(), false); QCOMPARE(bodyElement.previousSibling().isNull(), true); QCOMPARE(bodyElement.nextSibling().isNull(), true); QCOMPARE(bodyElement.localName(), QString("body")); // KoXmlElement spreadsheetElement; spreadsheetElement = bodyElement.firstChild().toElement(); QCOMPARE(spreadsheetElement.isNull(), false); QCOMPARE(spreadsheetElement.isElement(), true); QCOMPARE(spreadsheetElement.parentNode().isNull(), false); QCOMPARE(spreadsheetElement.parentNode() == bodyElement, true); QCOMPARE(KoXml::childNodesCount(spreadsheetElement), 3); QCOMPARE(spreadsheetElement.firstChild().isNull(), false); QCOMPARE(spreadsheetElement.lastChild().isNull(), false); QCOMPARE(spreadsheetElement.previousSibling().isNull(), true); QCOMPARE(spreadsheetElement.nextSibling().isNull(), true); QCOMPARE(spreadsheetElement.localName(), QString("spreadsheet")); // for Sheet1 KoXmlElement sheet1Element; sheet1Element = spreadsheetElement.firstChild().toElement(); QCOMPARE(sheet1Element.isNull(), false); QCOMPARE(sheet1Element.isElement(), true); QCOMPARE(sheet1Element.parentNode().isNull(), false); QCOMPARE(sheet1Element.parentNode() == spreadsheetElement, true); QCOMPARE(KoXml::childNodesCount(sheet1Element), 2); QCOMPARE(sheet1Element.firstChild().isNull(), false); QCOMPARE(sheet1Element.lastChild().isNull(), false); QCOMPARE(sheet1Element.previousSibling().isNull(), true); QCOMPARE(sheet1Element.nextSibling().isNull(), false); QCOMPARE(sheet1Element.tagName(), QString("table")); QCOMPARE(sheet1Element.hasAttributeNS(tableNS, "name"), true); QCOMPARE(sheet1Element.attributeNS(tableNS, "name", ""), QString("Sheet1")); QCOMPARE(sheet1Element.attributeNS(tableNS, "style-name", ""), QString("ta1")); QCOMPARE(sheet1Element.attributeNS(tableNS, "print", ""), QString("false")); // KoXml::load( sheet1Element, 100 ); // KoXmlElement columnElement; columnElement = sheet1Element.firstChild().toElement(); QCOMPARE(columnElement.isNull(), false); QCOMPARE(columnElement.isElement(), true); QCOMPARE(columnElement.parentNode().isNull(), false); QCOMPARE(columnElement.parentNode() == sheet1Element, true); QCOMPARE(KoXml::childNodesCount(columnElement), 0); QCOMPARE(columnElement.firstChild().isNull(), true); QCOMPARE(columnElement.lastChild().isNull(), true); QCOMPARE(columnElement.previousSibling().isNull(), true); QCOMPARE(columnElement.nextSibling().isNull(), false); QCOMPARE(columnElement.tagName(), QString("table-column")); QCOMPARE(columnElement.attributeNS(tableNS, "style-name", ""), QString("co1")); QCOMPARE(columnElement.attributeNS(tableNS, "default-cell-style-name", ""), QString("Default")); // KoXmlElement rowElement; rowElement = columnElement.nextSibling().toElement(); QCOMPARE(rowElement.isNull(), false); QCOMPARE(rowElement.isElement(), true); QCOMPARE(rowElement.parentNode().isNull(), false); QCOMPARE(rowElement.parentNode() == sheet1Element, true); QCOMPARE(KoXml::childNodesCount(rowElement), 1); QCOMPARE(rowElement.firstChild().isNull(), false); QCOMPARE(rowElement.lastChild().isNull(), false); QCOMPARE(rowElement.previousSibling().isNull(), false); QCOMPARE(rowElement.nextSibling().isNull(), true); QCOMPARE(rowElement.tagName(), QString("table-row")); QCOMPARE(rowElement.attributeNS(tableNS, "style-name", ""), QString("ro1")); // KoXmlElement cellElement; cellElement = rowElement.firstChild().toElement(); QCOMPARE(cellElement.isNull(), false); QCOMPARE(cellElement.isElement(), true); QCOMPARE(cellElement.parentNode().isNull(), false); QCOMPARE(cellElement.parentNode() == rowElement, true); QCOMPARE(KoXml::childNodesCount(cellElement), 1); QCOMPARE(cellElement.firstChild().isNull(), false); QCOMPARE(cellElement.lastChild().isNull(), false); QCOMPARE(cellElement.previousSibling().isNull(), true); QCOMPARE(cellElement.nextSibling().isNull(), true); QCOMPARE(cellElement.tagName(), QString("table-cell")); QCOMPARE(cellElement.attributeNS(officeNS, "value-type", ""), QString("string")); // KoXmlElement parElement; parElement = cellElement.firstChild().toElement(); QCOMPARE(parElement.isNull(), false); QCOMPARE(parElement.isElement(), true); QCOMPARE(parElement.parentNode().isNull(), false); QCOMPARE(parElement.parentNode() == cellElement, true); QCOMPARE(KoXml::childNodesCount(parElement), 1); QCOMPARE(parElement.firstChild().isNull(), false); QCOMPARE(parElement.lastChild().isNull(), false); QCOMPARE(parElement.previousSibling().isNull(), true); QCOMPARE(parElement.nextSibling().isNull(), true); QCOMPARE(parElement.tagName(), QString("p")); QCOMPARE(parElement.text(), QString("Hello, world")); // for Sheet2 KoXmlElement sheet2Element; sheet2Element = sheet1Element.nextSibling().toElement(); QCOMPARE(sheet2Element.isNull(), false); QCOMPARE(sheet2Element.isElement(), true); QCOMPARE(sheet2Element.parentNode().isNull(), false); QCOMPARE(sheet2Element.parentNode() == spreadsheetElement, true); QCOMPARE(KoXml::childNodesCount(sheet2Element), 2); QCOMPARE(sheet2Element.firstChild().isNull(), false); QCOMPARE(sheet2Element.lastChild().isNull(), false); QCOMPARE(sheet2Element.previousSibling().isNull(), false); QCOMPARE(sheet2Element.nextSibling().isNull(), false); QCOMPARE(sheet2Element.tagName(), QString("table")); // for Sheet3 KoXmlElement sheet3Element; sheet3Element = sheet2Element.nextSibling().toElement(); QCOMPARE(sheet3Element.isNull(), false); QCOMPARE(sheet3Element.isElement(), true); QCOMPARE(sheet3Element.parentNode().isNull(), false); QCOMPARE(sheet3Element.parentNode() == spreadsheetElement, true); QCOMPARE(KoXml::childNodesCount(sheet3Element), 2); QCOMPARE(sheet3Element.firstChild().isNull(), false); QCOMPARE(sheet3Element.lastChild().isNull(), false); QCOMPARE(sheet3Element.previousSibling().isNull(), false); QCOMPARE(sheet3Element.nextSibling().isNull(), true); QCOMPARE(sheet3Element.tagName(), QString("table")); } void TestXmlReaderWithoutSpaces::testSimpleOpenDocumentPresentation() { QString errorMsg; int errorLine = 0; int errorColumn = 0; QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); // content.xml from a simple OpenDocument presentation // styles, declarations and unnecessary namespaces are omitted // the first page is "Title" and has two text boxes // the second page is xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << "Foobar"; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << "Foo"; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmldevice.close(); KoXmlDocument doc(false); QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); const char* officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; const char* drawNS = "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"; const char* textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; const char* presentationNS = "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0"; const char* svgNS = "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"; // KoXmlElement contentElement; contentElement = doc.documentElement(); QCOMPARE(contentElement.isNull(), false); QCOMPARE(contentElement.isElement(), true); QCOMPARE(contentElement.parentNode().isNull(), false); QCOMPARE(contentElement.parentNode().toDocument() == doc, true); QCOMPARE(KoXml::childNodesCount(contentElement), 3); QCOMPARE(contentElement.firstChild().isNull(), false); QCOMPARE(contentElement.lastChild().isNull(), false); QCOMPARE(contentElement.previousSibling().isNull(), false); QCOMPARE(contentElement.nextSibling().isNull(), true); QCOMPARE(contentElement.localName(), QString("document-content")); QCOMPARE(contentElement.hasAttributeNS(officeNS, "version"), true); QCOMPARE(contentElement.attributeNS(officeNS, "version", ""), QString("1.0")); // KoXmlElement scriptsElement; scriptsElement = KoXml::namedItemNS(contentElement, officeNS, "scripts"); QCOMPARE(scriptsElement.isNull(), false); QCOMPARE(scriptsElement.isElement(), true); QCOMPARE(scriptsElement.parentNode().isNull(), false); QCOMPARE(scriptsElement.parentNode() == contentElement, true); QCOMPARE(KoXml::childNodesCount(scriptsElement), 0); QCOMPARE(scriptsElement.firstChild().isNull(), true); QCOMPARE(scriptsElement.lastChild().isNull(), true); QCOMPARE(scriptsElement.previousSibling().isNull(), true); QCOMPARE(scriptsElement.nextSibling().isNull(), false); QCOMPARE(scriptsElement.localName(), QString("scripts")); // KoXmlElement stylesElement; stylesElement = KoXml::namedItemNS(contentElement, officeNS, "automatic-styles"); QCOMPARE(stylesElement.isNull(), false); QCOMPARE(stylesElement.isElement(), true); QCOMPARE(stylesElement.parentNode().isNull(), false); QCOMPARE(stylesElement.parentNode() == contentElement, true); QCOMPARE(KoXml::childNodesCount(stylesElement), 0); QCOMPARE(stylesElement.firstChild().isNull(), true); QCOMPARE(stylesElement.lastChild().isNull(), true); QCOMPARE(stylesElement.previousSibling().isNull(), false); QCOMPARE(stylesElement.nextSibling().isNull(), false); QCOMPARE(stylesElement.localName(), QString("automatic-styles")); // also same , but without namedItemNS KoXmlNode styles2Element; styles2Element = scriptsElement.nextSibling().toElement(); QCOMPARE(styles2Element.isNull(), false); QCOMPARE(styles2Element.isElement(), true); QCOMPARE(styles2Element.parentNode().isNull(), false); QCOMPARE(styles2Element.parentNode() == contentElement, true); QCOMPARE(KoXml::childNodesCount(styles2Element), 0); QCOMPARE(styles2Element.firstChild().isNull(), true); QCOMPARE(styles2Element.lastChild().isNull(), true); QCOMPARE(styles2Element.previousSibling().isNull(), false); QCOMPARE(styles2Element.nextSibling().isNull(), false); QCOMPARE(styles2Element.localName(), QString("automatic-styles")); // KoXmlElement bodyElement; bodyElement = KoXml::namedItemNS(contentElement, officeNS, "body"); QCOMPARE(bodyElement.isNull(), false); QCOMPARE(bodyElement.isElement(), true); QCOMPARE(bodyElement.parentNode().isNull(), false); QCOMPARE(bodyElement.parentNode() == contentElement, true); QCOMPARE(KoXml::childNodesCount(bodyElement), 1); QCOMPARE(bodyElement.firstChild().isNull(), false); QCOMPARE(bodyElement.lastChild().isNull(), false); QCOMPARE(bodyElement.previousSibling().isNull(), false); QCOMPARE(bodyElement.nextSibling().isNull(), true); QCOMPARE(bodyElement.localName(), QString("body")); // also same , but without namedItemNS KoXmlElement body2Element; body2Element = stylesElement.nextSibling().toElement(); QCOMPARE(body2Element.isNull(), false); QCOMPARE(body2Element.isElement(), true); QCOMPARE(body2Element.parentNode().isNull(), false); QCOMPARE(body2Element.parentNode() == contentElement, true); QCOMPARE(KoXml::childNodesCount(body2Element), 1); QCOMPARE(body2Element.firstChild().isNull(), false); QCOMPARE(body2Element.lastChild().isNull(), false); QCOMPARE(body2Element.previousSibling().isNull(), false); QCOMPARE(body2Element.nextSibling().isNull(), true); QCOMPARE(body2Element.localName(), QString("body")); // KoXmlElement presentationElement; presentationElement = KoXml::namedItemNS(bodyElement, officeNS, "presentation"); QCOMPARE(presentationElement.isNull(), false); QCOMPARE(presentationElement.isElement(), true); QCOMPARE(presentationElement.parentNode().isNull(), false); QCOMPARE(presentationElement.parentNode() == bodyElement, true); QCOMPARE(KoXml::childNodesCount(presentationElement), 2); QCOMPARE(presentationElement.firstChild().isNull(), false); QCOMPARE(presentationElement.lastChild().isNull(), false); QCOMPARE(presentationElement.previousSibling().isNull(), true); QCOMPARE(presentationElement.nextSibling().isNull(), true); QCOMPARE(presentationElement.localName(), QString("presentation")); // the same , but without namedItemNS KoXmlElement presentation2Element; presentation2Element = bodyElement.firstChild().toElement(); QCOMPARE(presentation2Element.isNull(), false); QCOMPARE(presentation2Element.isElement(), true); QCOMPARE(presentation2Element.parentNode().isNull(), false); QCOMPARE(presentation2Element.parentNode() == bodyElement, true); QCOMPARE(KoXml::childNodesCount(presentation2Element), 2); QCOMPARE(presentation2Element.firstChild().isNull(), false); QCOMPARE(presentation2Element.lastChild().isNull(), false); QCOMPARE(presentation2Element.previousSibling().isNull(), true); QCOMPARE(presentation2Element.nextSibling().isNull(), true); QCOMPARE(presentation2Element.localName(), QString("presentation")); // for "Title" KoXmlElement titlePageElement; titlePageElement = presentationElement.firstChild().toElement(); QCOMPARE(titlePageElement.isNull(), false); QCOMPARE(titlePageElement.isElement(), true); QCOMPARE(titlePageElement.parentNode().isNull(), false); QCOMPARE(titlePageElement.parentNode() == presentationElement, true); QCOMPARE(KoXml::childNodesCount(titlePageElement), 3); QCOMPARE(titlePageElement.firstChild().isNull(), false); QCOMPARE(titlePageElement.lastChild().isNull(), false); QCOMPARE(titlePageElement.previousSibling().isNull(), true); QCOMPARE(titlePageElement.nextSibling().isNull(), false); QCOMPARE(titlePageElement.localName(), QString("page")); QCOMPARE(titlePageElement.attributeNS(drawNS, "name", ""), QString("Title")); QCOMPARE(titlePageElement.attributeNS(drawNS, "style-name", ""), QString("dp1")); QCOMPARE(titlePageElement.attributeNS(drawNS, "master-page-name", ""), QString("lyt-cool")); QCOMPARE(titlePageElement.attributeNS(presentationNS, "presentation-page-layout-name", ""), QString("AL1T0")); // for the title frame KoXmlElement titleFrameElement; titleFrameElement = titlePageElement.firstChild().toElement(); QCOMPARE(titleFrameElement.isNull(), false); QCOMPARE(titleFrameElement.isElement(), true); QCOMPARE(titleFrameElement.parentNode().isNull(), false); QCOMPARE(titleFrameElement.parentNode() == titlePageElement, true); QCOMPARE(KoXml::childNodesCount(titleFrameElement), 1); QCOMPARE(titleFrameElement.firstChild().isNull(), false); QCOMPARE(titleFrameElement.lastChild().isNull(), false); QCOMPARE(titleFrameElement.previousSibling().isNull(), true); QCOMPARE(titleFrameElement.nextSibling().isNull(), false); QCOMPARE(titleFrameElement.localName(), QString("frame")); QCOMPARE(titleFrameElement.attributeNS(presentationNS, "style-name", ""), QString("pr1")); QCOMPARE(titleFrameElement.attributeNS(presentationNS, "class", ""), QString("title")); QCOMPARE(titleFrameElement.attributeNS(presentationNS, "user-transformed", ""), QString("true")); QCOMPARE(titleFrameElement.attributeNS(drawNS, "text-style-name", ""), QString("P2")); QCOMPARE(titleFrameElement.attributeNS(drawNS, "layer", ""), QString("layout")); QCOMPARE(titleFrameElement.attributeNS(svgNS, "width", ""), QString("23.912cm")); QCOMPARE(titleFrameElement.attributeNS(svgNS, "height", ""), QString("3.508cm")); QCOMPARE(titleFrameElement.attributeNS(svgNS, "x", ""), QString("2.058cm")); QCOMPARE(titleFrameElement.attributeNS(svgNS, "y", ""), QString("1.543cm")); // of the title frame KoXmlElement titleBoxElement; titleBoxElement = titleFrameElement.firstChild().toElement(); QCOMPARE(titleBoxElement.isNull(), false); QCOMPARE(titleBoxElement.isElement(), true); QCOMPARE(titleBoxElement.parentNode().isNull(), false); QCOMPARE(titleBoxElement.parentNode() == titleFrameElement, true); QCOMPARE(KoXml::childNodesCount(titleBoxElement), 1); QCOMPARE(titleBoxElement.firstChild().isNull(), false); QCOMPARE(titleBoxElement.lastChild().isNull(), false); QCOMPARE(titleBoxElement.previousSibling().isNull(), true); QCOMPARE(titleBoxElement.nextSibling().isNull(), true); QCOMPARE(titleBoxElement.localName(), QString("text-box")); // for the title text-box KoXmlElement titleParElement; titleParElement = titleBoxElement.firstChild().toElement(); QCOMPARE(titleParElement.isNull(), false); QCOMPARE(titleParElement.isElement(), true); QCOMPARE(titleParElement.parentNode().isNull(), false); QCOMPARE(titleParElement.parentNode() == titleBoxElement, true); QCOMPARE(KoXml::childNodesCount(titleParElement), 1); QCOMPARE(titleParElement.firstChild().isNull(), false); QCOMPARE(titleParElement.lastChild().isNull(), false); QCOMPARE(titleParElement.previousSibling().isNull(), true); QCOMPARE(titleParElement.nextSibling().isNull(), true); QCOMPARE(titleParElement.localName(), QString("p")); QCOMPARE(titleParElement.attributeNS(textNS, "style-name", ""), QString("P1")); QCOMPARE(titleParElement.text(), QString("Foobar")); // for the subtitle frame KoXmlElement subtitleFrameElement; subtitleFrameElement = titleFrameElement.nextSibling().toElement(); QCOMPARE(subtitleFrameElement.isNull(), false); QCOMPARE(subtitleFrameElement.isElement(), true); QCOMPARE(subtitleFrameElement.parentNode().isNull(), false); QCOMPARE(subtitleFrameElement.parentNode() == titlePageElement, true); QCOMPARE(KoXml::childNodesCount(subtitleFrameElement), 1); QCOMPARE(subtitleFrameElement.firstChild().isNull(), false); QCOMPARE(subtitleFrameElement.lastChild().isNull(), false); QCOMPARE(subtitleFrameElement.previousSibling().isNull(), false); QCOMPARE(subtitleFrameElement.nextSibling().isNull(), false); QCOMPARE(subtitleFrameElement.localName(), QString("frame")); QCOMPARE(subtitleFrameElement.attributeNS(presentationNS, "style-name", ""), QString("pr2")); QCOMPARE(subtitleFrameElement.attributeNS(presentationNS, "class", ""), QString("subtitle")); QCOMPARE(subtitleFrameElement.hasAttributeNS(presentationNS, "user-transformed"), false); QCOMPARE(subtitleFrameElement.attributeNS(drawNS, "text-style-name", ""), QString("P3")); QCOMPARE(subtitleFrameElement.attributeNS(drawNS, "layer", ""), QString("layout")); QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "width", ""), QString("23.912cm")); QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "height", ""), QString("13.231cm")); QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "x", ""), QString("2.058cm")); QCOMPARE(subtitleFrameElement.attributeNS(svgNS, "y", ""), QString("5.838cm")); // of the subtitle frame KoXmlElement subtitleBoxElement; subtitleBoxElement = subtitleFrameElement.firstChild().toElement(); QCOMPARE(subtitleBoxElement.isNull(), false); QCOMPARE(subtitleBoxElement.isElement(), true); QCOMPARE(subtitleBoxElement.parentNode().isNull(), false); QCOMPARE(subtitleBoxElement.parentNode() == subtitleFrameElement, true); QCOMPARE(KoXml::childNodesCount(subtitleBoxElement), 1); QCOMPARE(subtitleBoxElement.firstChild().isNull(), false); QCOMPARE(subtitleBoxElement.lastChild().isNull(), false); QCOMPARE(subtitleBoxElement.previousSibling().isNull(), true); QCOMPARE(subtitleBoxElement.nextSibling().isNull(), true); QCOMPARE(subtitleBoxElement.localName(), QString("text-box")); // for the subtitle text-box KoXmlElement subtitleParElement; subtitleParElement = subtitleBoxElement.firstChild().toElement(); QCOMPARE(subtitleParElement.isNull(), false); QCOMPARE(subtitleParElement.isElement(), true); QCOMPARE(subtitleParElement.parentNode().isNull(), false); QCOMPARE(subtitleParElement.parentNode() == subtitleBoxElement, true); QCOMPARE(KoXml::childNodesCount(subtitleParElement), 1); QCOMPARE(subtitleParElement.firstChild().isNull(), false); QCOMPARE(subtitleParElement.lastChild().isNull(), false); QCOMPARE(subtitleParElement.previousSibling().isNull(), true); QCOMPARE(subtitleParElement.nextSibling().isNull(), true); QCOMPARE(subtitleParElement.localName(), QString("p")); QCOMPARE(subtitleParElement.attributeNS(textNS, "style-name", ""), QString("P3")); QCOMPARE(subtitleParElement.text(), QString("Foo")); } void TestXmlReaderWithoutSpaces::testSimpleOpenDocumentFormula() { QString errorMsg; int errorLine = 0; int errorColumn = 0; QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); // content.xml from a simple OpenDocument formula // this is essentially MathML xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << "E"; xmlstream << "="; xmlstream << ""; xmlstream << "mc"; xmlstream << "2"; xmlstream << ""; xmlstream << ""; xmlstream << "E = mc^2 "; xmlstream << ""; xmlstream << ""; xmldevice.close(); KoXmlDocument doc(false); QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); const char* mathNS = "http://www.w3.org/1998/Math/MathML"; // KoXmlElement mathElement; mathElement = doc.documentElement(); QCOMPARE(mathElement.isNull(), false); QCOMPARE(mathElement.isElement(), true); QCOMPARE(mathElement.parentNode().isNull(), false); QCOMPARE(mathElement.parentNode().toDocument() == doc, true); QCOMPARE(mathElement.firstChild().isNull(), false); QCOMPARE(mathElement.lastChild().isNull(), false); QCOMPARE(mathElement.previousSibling().isNull(), false); QCOMPARE(mathElement.nextSibling().isNull(), true); QCOMPARE(mathElement.localName(), QString("math")); // KoXmlElement semanticsElement; semanticsElement = KoXml::namedItemNS(mathElement, mathNS, "semantics"); QCOMPARE(semanticsElement.isNull(), false); QCOMPARE(semanticsElement.isElement(), true); QCOMPARE(semanticsElement.parentNode().isNull(), false); QCOMPARE(semanticsElement.parentNode().toElement() == mathElement, true); QCOMPARE(semanticsElement.firstChild().isNull(), false); QCOMPARE(semanticsElement.lastChild().isNull(), false); QCOMPARE(semanticsElement.previousSibling().isNull(), true); QCOMPARE(semanticsElement.nextSibling().isNull(), true); QCOMPARE(semanticsElement.localName(), QString("semantics")); // the same but without namedItemNS KoXmlElement semantics2Element; semantics2Element = mathElement.firstChild().toElement(); QCOMPARE(semantics2Element.isNull(), false); QCOMPARE(semantics2Element.isElement(), true); QCOMPARE(semantics2Element.parentNode().isNull(), false); QCOMPARE(semantics2Element.parentNode().toElement() == mathElement, true); QCOMPARE(semantics2Element.firstChild().isNull(), false); QCOMPARE(semantics2Element.lastChild().isNull(), false); QCOMPARE(semantics2Element.previousSibling().isNull(), true); QCOMPARE(semantics2Element.nextSibling().isNull(), true); QCOMPARE(semantics2Element.localName(), QString("semantics")); // KoXmlElement mrowElement; mrowElement = semanticsElement.firstChild().toElement(); QCOMPARE(mrowElement.isNull(), false); QCOMPARE(mrowElement.isElement(), true); QCOMPARE(mrowElement.parentNode().isNull(), false); QCOMPARE(mrowElement.parentNode().toElement() == semanticsElement, true); QCOMPARE(mrowElement.firstChild().isNull(), false); QCOMPARE(mrowElement.lastChild().isNull(), false); QCOMPARE(mrowElement.previousSibling().isNull(), true); QCOMPARE(mrowElement.nextSibling().isNull(), false); QCOMPARE(mrowElement.localName(), QString("mrow")); // for "E" KoXmlElement miElement; miElement = mrowElement.firstChild().toElement(); QCOMPARE(miElement.isNull(), false); QCOMPARE(miElement.isElement(), true); QCOMPARE(miElement.parentNode().isNull(), false); QCOMPARE(miElement.parentNode().toElement() == mrowElement, true); QCOMPARE(miElement.firstChild().isNull(), false); QCOMPARE(miElement.lastChild().isNull(), false); QCOMPARE(miElement.previousSibling().isNull(), true); QCOMPARE(miElement.nextSibling().isNull(), false); QCOMPARE(miElement.localName(), QString("mi")); // for "=" KoXmlElement moElement; moElement = miElement.nextSibling().toElement(); QCOMPARE(moElement.isNull(), false); QCOMPARE(moElement.isElement(), true); QCOMPARE(moElement.parentNode().isNull(), false); QCOMPARE(moElement.parentNode().toElement() == mrowElement, true); QCOMPARE(moElement.firstChild().isNull(), false); QCOMPARE(moElement.lastChild().isNull(), false); QCOMPARE(moElement.previousSibling().isNull(), false); QCOMPARE(moElement.nextSibling().isNull(), false); QCOMPARE(moElement.localName(), QString("mo")); QCOMPARE(moElement.attributeNS(mathNS, "stretchy", ""), QString("false")); // for "mc" and superscripted "2" KoXmlElement msupElement; msupElement = moElement.nextSibling().toElement(); QCOMPARE(msupElement.isNull(), false); QCOMPARE(msupElement.isElement(), true); QCOMPARE(msupElement.parentNode().isNull(), false); QCOMPARE(msupElement.parentNode().toElement() == mrowElement, true); QCOMPARE(msupElement.firstChild().isNull(), false); QCOMPARE(msupElement.lastChild().isNull(), false); QCOMPARE(msupElement.previousSibling().isNull(), false); QCOMPARE(msupElement.nextSibling().isNull(), true); QCOMPARE(msupElement.localName(), QString("msup")); // inside the for "mc" KoXmlElement mcElement; mcElement = msupElement.firstChild().toElement(); QCOMPARE(mcElement.isNull(), false); QCOMPARE(mcElement.isElement(), true); QCOMPARE(mcElement.parentNode().isNull(), false); QCOMPARE(mcElement.parentNode().toElement() == msupElement, true); QCOMPARE(mcElement.firstChild().isNull(), false); QCOMPARE(mcElement.lastChild().isNull(), false); QCOMPARE(mcElement.previousSibling().isNull(), true); QCOMPARE(mcElement.nextSibling().isNull(), false); QCOMPARE(mcElement.localName(), QString("mi")); QCOMPARE(mcElement.text(), QString("mc")); QCOMPARE(mcElement.attributeNS(mathNS, "fontstyle", ""), QString("italic")); // inside the for "2" (superscript) KoXmlElement mnElement; mnElement = mcElement.nextSibling().toElement(); QCOMPARE(mnElement.isNull(), false); QCOMPARE(mnElement.isElement(), true); QCOMPARE(mnElement.parentNode().isNull(), false); QCOMPARE(mnElement.parentNode().toElement() == msupElement, true); QCOMPARE(mnElement.firstChild().isNull(), false); QCOMPARE(mnElement.lastChild().isNull(), false); QCOMPARE(mnElement.previousSibling().isNull(), false); QCOMPARE(mnElement.nextSibling().isNull(), true); QCOMPARE(mnElement.localName(), QString("mn")); QCOMPARE(mnElement.text(), QString("2")); // KoXmlElement annotationElement; annotationElement = semanticsElement.lastChild().toElement(); QCOMPARE(annotationElement.isNull(), false); QCOMPARE(annotationElement.isElement(), true); QCOMPARE(annotationElement.parentNode().isNull(), false); QCOMPARE(annotationElement.parentNode().toElement() == semanticsElement, true); QCOMPARE(annotationElement.firstChild().isNull(), false); QCOMPARE(annotationElement.lastChild().isNull(), false); QCOMPARE(annotationElement.previousSibling().isNull(), false); QCOMPARE(annotationElement.nextSibling().isNull(), true); QCOMPARE(annotationElement.localName(), QString("annotation")); QCOMPARE(annotationElement.text(), QString("E = mc^2 ")); QCOMPARE(annotationElement.attributeNS(mathNS, "encoding", ""), QString("StarMath 5.0")); } void TestXmlReaderWithoutSpaces::testLargeOpenDocumentSpreadsheet() { QString errorMsg; int errorLine = 0; int errorColumn = 0; int sheetCount = 4; int rowCount = 200; int colCount = 200 / 16; QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); // content.xml xmlstream << "\n"; xmlstream << ""; xmlstream << ""; xmlstream << ""; for (int i = 0; i < sheetCount; i++) { QString sheetName = QString("Sheet%1").arg(i + 1); xmlstream << ""; for (int j = 0; j < rowCount; j++) { xmlstream << ""; for (int k = 0; k < colCount; k++) { xmlstream << ""; xmlstream << "Hello, world"; xmlstream << ""; } xmlstream << ""; } xmlstream << ""; } xmlstream << ""; xmlstream << ""; xmlstream << ""; xmldevice.close(); printf("Raw XML size: %lld KB\n", xmldevice.size() / 1024); QTime timer; #if 0 // just to test parsing speed with plain dumb handler QXmlStreamReader *reader = new QXmlStreamReader(xmldevice); reader->setNamespaceProcessing(true); timer.start(); ParseError error = parseDocument(*reader, doc); printf("Large spreadsheet: QXmlStreamReader parsing time is %d ms\n", timer.elapsed()); delete reader; xmldevice.seek(0); #endif KoXmlDocument doc(false); timer.start(); QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); if (!errorMsg.isEmpty()) { qDebug("Error: %s", qPrintable(errorMsg)); return; } printf("Large spreadsheet: KoXmlDocument parsing time is %d ms\n", timer.elapsed()); // release memory taken by the XML document content //xmlstream.setDevice( 0 ); // namespaces that will be used QString officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; QString tableNS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"; QString textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; // KoXmlElement contentElement; contentElement = doc.documentElement(); QCOMPARE(contentElement.isNull(), false); QCOMPARE(contentElement.isElement(), true); QCOMPARE(contentElement.localName(), QString("document-content")); // KoXmlElement bodyElement; bodyElement = contentElement.firstChild().toElement(); QCOMPARE(bodyElement.isNull(), false); QCOMPARE(bodyElement.isElement(), true); QCOMPARE(bodyElement.localName(), QString("body")); // KoXmlElement spreadsheetElement; spreadsheetElement = bodyElement.firstChild().toElement(); QCOMPARE(spreadsheetElement.isNull(), false); QCOMPARE(spreadsheetElement.isElement(), true); QCOMPARE(spreadsheetElement.localName(), QString("spreadsheet")); // now we visit every sheet, every row, every cell timer.start(); KoXmlElement tableElement; tableElement = spreadsheetElement.firstChild().toElement(); for (int table = 0; table < sheetCount; table++) { QString tableName = QString("Sheet%1").arg(table + 1); QCOMPARE(tableElement.isNull(), false); QCOMPARE(tableElement.isElement(), true); QCOMPARE(tableElement.localName(), QString("table")); QCOMPARE(tableElement.hasAttributeNS(tableNS, "name"), true); QCOMPARE(tableElement.attributeNS(tableNS, "name", ""), tableName); QCOMPARE(tableElement.attributeNS(tableNS, "print", ""), QString("false")); // load everything for this table //KoXml::load( tableElement, 99 ); QCOMPARE(tableElement.parentNode().isNull(), false); QCOMPARE(tableElement.parentNode() == spreadsheetElement, true); QCOMPARE(tableElement.firstChild().isNull(), false); QCOMPARE(tableElement.lastChild().isNull(), false); KoXmlElement rowElement; rowElement = tableElement.firstChild().toElement(); for (int row = 0; row < rowCount; row++) { QCOMPARE(rowElement.isNull(), false); QCOMPARE(rowElement.isElement(), true); QCOMPARE(rowElement.localName(), QString("table-row")); QCOMPARE(rowElement.parentNode().isNull(), false); QCOMPARE(rowElement.parentNode() == tableElement, true); QCOMPARE(rowElement.firstChild().isNull(), false); QCOMPARE(rowElement.lastChild().isNull(), false); KoXmlElement cellElement; cellElement = rowElement.firstChild().toElement(); for (int col = 0; col < colCount; col++) { QCOMPARE(cellElement.isNull(), false); QCOMPARE(cellElement.isElement(), true); QCOMPARE(cellElement.localName(), QString("table-cell")); QCOMPARE(cellElement.text(), QString("Hello, world")); QCOMPARE(cellElement.hasAttributeNS(officeNS, "value-type"), true); QCOMPARE(cellElement.attributeNS(officeNS, "value-type", ""), QString("string")); QCOMPARE(cellElement.parentNode().isNull(), false); QCOMPARE(cellElement.parentNode() == rowElement, true); QCOMPARE(cellElement.firstChild().isNull(), false); QCOMPARE(cellElement.lastChild().isNull(), false); cellElement = cellElement.nextSibling().toElement(); } //KoXml::unload( rowElement ); rowElement = rowElement.nextSibling().toElement(); } KoXml::unload(tableElement); tableElement = tableElement.nextSibling().toElement(); } printf("Large spreadsheet: iterating time is %d ms\n", timer.elapsed()); } void TestXmlReaderWithoutSpaces::testExternalOpenDocumentSpreadsheet(const QString& filename) { QProcess unzip; QStringList arguments; arguments << "-o" << filename << "content.xml"; printf("Unzipping content.xml from %s...\n", qPrintable(filename)); unzip.start("unzip", arguments); if (!unzip.waitForStarted()) { printf("Error: can't invoke unzip. Check your PATH and installation!\n\n"); return; } if (!unzip.waitForFinished()) { printf("Error: unzip failed, can't continue!\n\n"); return; } printf("Procesing content.xml....\n"); QString errorMsg; int errorLine = 0; int errorColumn = 0; QFile xmlfile("content.xml"); if (!xmlfile.open(QFile::ReadOnly)) { printf("Can not open file '%s'\n", qPrintable(filename)); return; } printf("Test external file: %s %lld KB\n", qPrintable(filename), xmlfile.size() / 1024); QTime timer; timer.start(); KoXmlDocument doc(false); QCOMPARE(KoXml::setDocument(doc, &xmlfile, true, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); printf("External spreadsheet: parsing time is %d ms\n", timer.elapsed()); // namespaces that will be used QString officeNS = "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; QString tableNS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"; QString textNS = "urn:oasis:names:tc:opendocument:xmlns:text:1.0"; // KoXmlElement contentElement; contentElement = doc.documentElement(); QCOMPARE(contentElement.isNull(), false); QCOMPARE(contentElement.isElement(), true); QCOMPARE(contentElement.localName(), QString("document-content")); long totalCellCount = 0; KoXmlElement bodyElement; forEachElement(bodyElement, contentElement) { // if (bodyElement.localName() != QString("body")) continue; // now we iterate inside the body timer.start(); // KoXmlElement spreadsheetElement; spreadsheetElement = bodyElement.firstChild().toElement(); QCOMPARE(spreadsheetElement.isNull(), false); QCOMPARE(spreadsheetElement.isElement(), true); QCOMPARE(spreadsheetElement.localName(), QString("spreadsheet")); // now we visit every sheet long tableCount = -1; KoXmlElement tableElement; tableElement = spreadsheetElement.firstChild().toElement(); for (;;) { if (tableElement.isNull()) break; if (tableElement.localName() != QString("table")) { tableElement = tableElement.nextSibling().toElement(); continue; } QString tableName = tableElement.attributeNS(tableNS, "name", ""); tableCount++; printf(" sheet #%ld (%s): ", tableCount + 1, qPrintable(tableName)); // use to preload everything in this sheet, will slow it down! // KoXml::load( tableElement, 50 ); long rowCount = -1; long cellCount = -1; KoXmlElement rowElement; rowElement = tableElement.firstChild().toElement(); for (;;) { if (rowElement.isNull()) break; if (rowElement.localName() != QString("table-row")) { rowElement = rowElement.nextSibling().toElement(); continue; } rowCount++; KoXml::load(rowElement, 4); QCOMPARE(rowElement.isElement(), true); QCOMPARE(rowElement.localName(), QString("table-row")); QCOMPARE(rowElement.parentNode().isNull(), false); QCOMPARE(rowElement.parentNode() == tableElement, true); KoXmlElement cellElement; cellElement = rowElement.firstChild().toElement(); for (; ;) { if (cellElement.isNull()) break; if (cellElement.localName() != QString("table-cell")) { cellElement = cellElement.nextSibling().toElement(); continue; } cellCount++; QCOMPARE(cellElement.isNull(), false); QCOMPARE(cellElement.isElement(), true); QCOMPARE(cellElement.localName(), QString("table-cell")); QString text1 = cellElement.text(); QString text2 = cellElement.text(); QCOMPARE(text1, text2); QString type1 = cellElement.attributeNS(officeNS, "value-type", QString()); QString type2 = cellElement.attributeNS(officeNS, "value-type", QString()); QCOMPARE(type1, type2); QString style1 = cellElement.attributeNS(tableNS, "style-name", QString()); QString style2 = cellElement.attributeNS(tableNS, "style-name", QString()); QCOMPARE(style1, style2); QCOMPARE(cellElement.parentNode().isNull(), false); QCOMPARE(cellElement.parentNode() == rowElement, true); cellElement = cellElement.nextSibling().toElement(); } // better not to unload, freeing memory takes time KoXml::unload(rowElement); rowElement = rowElement.nextSibling().toElement(); } printf(" %ld rows, %ld cells\n", rowCount + 1, cellCount + 1); totalCellCount += (cellCount + 1); // IMPORTANT: helps minimizing memory usage !! // we do not need that element anymore, so just throw it away KoXml::unload(tableElement); tableElement = tableElement.nextSibling().toElement(); } KoXml::unload(spreadsheetElement); } printf("Total number of cells: %ld\n", totalCellCount); int elapsed = timer.elapsed(); printf("External spreadsheet: iterating time is %d ms\n", elapsed); if (elapsed > 0) printf(" approx. %ld cells/second\n", totalCellCount*1000 / elapsed); // uncomment to check the XML xmlfile.remove(); } +#endif QTEST_GUILESS_MAIN(TestXmlReaderWithoutSpaces) #include diff --git a/libs/store/KoXmlReader.cpp b/libs/store/KoXmlReader.cpp index 5e1d00c2fe..7a6c626992 100644 --- a/libs/store/KoXmlReader.cpp +++ b/libs/store/KoXmlReader.cpp @@ -1,2351 +1,2349 @@ /* This file is part of the KDE project Copyright (C) 2005-2006 Ariya Hidayat 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 "KoXmlReader.h" #include "KoXmlNS.h" /* This is a memory-efficient DOM implementation for Calligra. See the API documentation for details. IMPORTANT ! * When you change this stuff, make sure it DOES NOT BREAK the test suite. Build tests/koxmlreadertest.cpp and verify it. Many sleepless nights have been sacrificed for this piece of code, do not let those precious hours wasted! * Run koxmlreadertest.cpp WITH Valgrind and make sure NO illegal memory read/write and any type of leak occurs. If you are not familiar with Valgrind then RTFM first and come back again later on. * The public API shall remain as compatible as QDom. * All QDom-compatible methods should behave the same. All QDom-compatible functions should return the same result. In case of doubt, run koxmlreadertest.cpp but uncomment KOXML_USE_QDOM in koxmlreader.h so that the tests are performed with standard QDom. Some differences compared to QDom: - DOM tree in KoXmlDocument is read-only, you can not modify it. This is sufficient for Calligra since the tree is only accessed when loading a document to the application. For saving the document to XML file, use KoXmlWriter. - Because the dynamic loading and unloading, you have to use the nodes (and therefore also elements) carefully since the whole API (just like QDom) is reference-based, not pointer-based. If the parent node is unloaded from memory, the reference is not valid anymore and may give unpredictable result. The easiest way: use the node/element in very short time only. - Comment node (like QDomComment) is not implemented as comments are simply ignored. - DTD, entity and entity reference are not handled. Thus, the associated nodes (like QDomDocumentType, QDomEntity, QDomEntityReference) are also not implemented. - Attribute mapping node is not implemented. But of course, functions to query attributes of an element are available. */ #include #include +#include #ifndef KOXML_USE_QDOM #include #include -#include #include #include #include #include #include #include #include #include /* Use more compact representation of in-memory nodes. Advantages: faster iteration, can facilitate real-time compression. Disadvantages: still buggy, eat slightly more memory. */ #define KOXML_COMPACT /* Use real-time compression. Only works in conjuction with KOXML_COMPACT above because otherwise the non-compact layout will slow down everything. */ #define KOXML_COMPRESS // prevent mistake, see above #ifdef KOXML_COMPRESS #ifndef KOXML_COMPACT #error Please enable also KOXML_COMPACT #endif #endif // this is used to quickly get namespaced attribute(s) typedef QPair KoXmlStringPair; class KoQName { public: QString nsURI; QString name; explicit KoQName(const QString& nsURI_, const QString& name_) : nsURI(nsURI_), name(name_) {} bool operator==(const KoQName& qname) const { // local name is more likely to differ, so compare that first return name == qname.name && nsURI == qname.nsURI; } }; uint qHash(const KoQName& qname) { // possibly add a faster hash function that only includes some trailing // part of the nsURI // in case of doubt, use this: // return qHash(qname.nsURI)^qHash(qname.name); return qHash(qname.nsURI)^qHash(qname.name); } static inline bool operator==(const KoXmlStringPair &a, const KoXmlStringPair &b) { return a.second == b.second && a.first == b.first; } // Older versions of OpenOffice.org used different namespaces. This function // does translate the old namespaces into the new ones. static QString fixNamespace(const QString &nsURI) { static QString office = QString::fromLatin1("http://openoffice.org/2000/office"); static QString text = QString::fromLatin1("http://openoffice.org/2000/text"); static QString style = QString::fromLatin1("http://openoffice.org/2000/style"); static QString fo = QString::fromLatin1("http://www.w3.org/1999/XSL/Format"); static QString table = QString::fromLatin1("http://openoffice.org/2000/table"); static QString drawing = QString::fromLatin1("http://openoffice.org/2000/drawing"); static QString datastyle = QString::fromLatin1("http://openoffice.org/2000/datastyle"); static QString svg = QString::fromLatin1("http://www.w3.org/2000/svg"); static QString chart = QString::fromLatin1("http://openoffice.org/2000/chart"); static QString dr3d = QString::fromLatin1("http://openoffice.org/2000/dr3d"); static QString form = QString::fromLatin1("http://openoffice.org/2000/form"); static QString script = QString::fromLatin1("http://openoffice.org/2000/script"); static QString meta = QString::fromLatin1("http://openoffice.org/2000/meta"); static QString config = QString::fromLatin1("http://openoffice.org/2001/config"); static QString pres = QString::fromLatin1("http://openoffice.org/2000/presentation"); static QString manifest = QString::fromLatin1("http://openoffice.org/2001/manifest"); if (nsURI == text) return KoXmlNS::text; if (nsURI == style) return KoXmlNS::style; if (nsURI == office) return KoXmlNS::office; if (nsURI == fo) return KoXmlNS::fo; if (nsURI == table) return KoXmlNS::table; if (nsURI == drawing) return KoXmlNS::draw; if (nsURI == datastyle) return KoXmlNS::number; if (nsURI == svg) return KoXmlNS::svg; if (nsURI == chart) return KoXmlNS::chart; if (nsURI == dr3d) return KoXmlNS::dr3d; if (nsURI == form) return KoXmlNS::form; if (nsURI == script) return KoXmlNS::script; if (nsURI == meta) return KoXmlNS::meta; if (nsURI == config) return KoXmlNS::config; if (nsURI == pres) return KoXmlNS::presentation; if (nsURI == manifest) return KoXmlNS::manifest; return nsURI; } // ================================================================== // // KoXmlPackedItem // // ================================================================== // 12 bytes on most system 32 bit systems, 16 bytes on 64 bit systems class KoXmlPackedItem { public: bool attr: 1; KoXmlNode::NodeType type: 3; #ifdef KOXML_COMPACT quint32 childStart: 28; #else unsigned depth: 28; #endif unsigned qnameIndex; QString value; // it is important NOT to have a copy constructor, so that growth is optimal // see http://doc.trolltech.com/4.2/containers.html#growth-strategies #if 0 KoXmlPackedItem(): attr(false), type(KoXmlNode::NullNode), childStart(0), depth(0) {} #endif }; Q_DECLARE_TYPEINFO(KoXmlPackedItem, Q_MOVABLE_TYPE); #ifdef KOXML_COMPRESS static QDataStream& operator<<(QDataStream& s, const KoXmlPackedItem& item) { quint8 flag = item.attr ? 1 : 0; s << flag; s << (quint8) item.type; s << item.childStart; s << item.qnameIndex; s << item.value; return s; } static QDataStream& operator>>(QDataStream& s, KoXmlPackedItem& item) { quint8 flag; quint8 type; quint32 child; QString value; s >> flag; s >> type; s >> child; s >> item.qnameIndex; s >> value; item.attr = (flag != 0); item.type = (KoXmlNode::NodeType) type; item.childStart = child; item.value = value; return s; } #endif // ================================================================== // // KoXmlPackedDocument // // ================================================================== #ifdef KOXML_COMPRESS #include "KoXmlVector.h" // when number of buffered items reach this, compression will start // small value will give better memory usage at the cost of speed // bigger value will be better in term of speed, but use more memory #define ITEMS_FULL (1*256) typedef KoXmlVector KoXmlPackedGroup; #else typedef QVector KoXmlPackedGroup; #endif // growth strategy: increase every GROUP_GROW_SIZE items // this will override standard QVector's growth strategy #define GROUP_GROW_SHIFT 3 #define GROUP_GROW_SIZE (1 << GROUP_GROW_SHIFT) class KoXmlPackedDocument { public: bool processNamespace; #ifdef KOXML_COMPACT // map given depth to the list of items QHash groups; #else QVector items; #endif QList qnameList; QString docType; private: QHash qnameHash; unsigned cacheQName(const QString& name, const QString& nsURI) { KoQName qname(nsURI, name); const unsigned ii = qnameHash.value(qname, (unsigned)-1); if (ii != (unsigned)-1) return ii; // not yet declared, so we add it unsigned i = qnameList.count(); qnameList.append(qname); qnameHash.insert(qname, i); return i; } QHash valueHash; QStringList valueList; QString cacheValue(const QString& value) { if (value.isEmpty()) return 0; const unsigned& ii = valueHash[value]; if (ii > 0) return valueList[ii]; // not yet declared, so we add it unsigned i = valueList.count(); valueList.append(value); valueHash.insert(value, i); return valueList[i]; } #ifdef KOXML_COMPACT public: const KoXmlPackedItem& itemAt(unsigned depth, unsigned index) { const KoXmlPackedGroup& group = groups[depth]; return group[index]; } unsigned itemCount(unsigned depth) { const KoXmlPackedGroup& group = groups[depth]; return group.count(); } /* NOTE: Function clear, newItem, addElement, addAttribute, addText, addCData, addProcessing are all related. These are all necessary for stateful manipulation of the document. See also the calls to these function from parseDocument(). The state itself is defined by the member variables currentDepth and the groups (see above). */ unsigned currentDepth; KoXmlPackedItem& newItem(unsigned depth) { KoXmlPackedGroup& group = groups[depth]; #ifdef KOXML_COMPRESS KoXmlPackedItem& item = group.newItem(); #else // reserve up front if ((groups.size() % GROUP_GROW_SIZE) == 0) group.reserve(GROUP_GROW_SIZE * (1 + (groups.size() >> GROUP_GROW_SHIFT))); group.resize(group.count() + 1); KoXmlPackedItem& item = group[group.count()-1]; #endif // this is necessary, because intentionally we don't want to have // a constructor for KoXmlPackedItem item.attr = false; item.type = KoXmlNode::NullNode; item.qnameIndex = 0; item.childStart = itemCount(depth + 1); item.value.clear(); return item; } void clear() { currentDepth = 0; qnameHash.clear(); qnameList.clear(); valueHash.clear(); valueList.clear(); groups.clear(); docType.clear(); // first node is root KoXmlPackedItem& rootItem = newItem(0); rootItem.type = KoXmlNode::DocumentNode; } void finish() { // won't be needed anymore qnameHash.clear(); valueHash.clear(); valueList.clear(); // optimize, see documentation on QVector::squeeze for (int d = 0; d < groups.count(); ++d) { KoXmlPackedGroup& group = groups[d]; group.squeeze(); } } // in case namespace processing, 'name' contains the prefix already void addElement(const QString& name, const QString& nsURI) { KoXmlPackedItem& item = newItem(currentDepth + 1); item.type = KoXmlNode::ElementNode; item.qnameIndex = cacheQName(name, nsURI); ++currentDepth; } void closeElement() { --currentDepth; } void addDTD(const QString& dt) { docType = dt; } void addAttribute(const QString& name, const QString& nsURI, const QString& value) { KoXmlPackedItem& item = newItem(currentDepth + 1); item.attr = true; item.qnameIndex = cacheQName(name, nsURI); //item.value = cacheValue( value ); item.value = value; } void addText(const QString& text) { KoXmlPackedItem& item = newItem(currentDepth + 1); item.type = KoXmlNode::TextNode; item.value = text; } void addCData(const QString& text) { KoXmlPackedItem& item = newItem(currentDepth + 1); item.type = KoXmlNode::CDATASectionNode; item.value = text; } void addProcessingInstruction() { KoXmlPackedItem& item = newItem(currentDepth + 1); item.type = KoXmlNode::ProcessingInstructionNode; } public: KoXmlPackedDocument(): processNamespace(false), currentDepth(0) { clear(); } #else private: unsigned elementDepth; public: KoXmlPackedItem& newItem() { unsigned count = items.count() + 512; count = 1024 * (count >> 10); items.reserve(count); items.resize(items.count() + 1); // this is necessary, because intentionally we don't want to have // a constructor for KoXmlPackedItem KoXmlPackedItem& item = items[items.count()-1]; item.attr = false; item.type = KoXmlNode::NullNode; item.qnameIndex = 0; item.depth = 0; return item; } void addElement(const QString& name, const QString& nsURI) { // we are going one level deeper ++elementDepth; KoXmlPackedItem& item = newItem(); item.attr = false; item.type = KoXmlNode::ElementNode; item.depth = elementDepth; item.qnameIndex = cacheQName(name, nsURI); } void closeElement() { // we are going up one level --elementDepth; } void addDTD(const QString& dt) { docType = dt; } void addAttribute(const QString& name, const QString& nsURI, const QString& value) { KoXmlPackedItem& item = newItem(); item.attr = true; item.type = KoXmlNode::NullNode; item.depth = elementDepth; item.qnameIndex = cacheQName(name, nsURI); //item.value = cacheValue( value ); item.value = value; } void addText(const QString& str) { KoXmlPackedItem& item = newItem(); item.attr = false; item.type = KoXmlNode::TextNode; item.depth = elementDepth + 1; item.qnameIndex = 0; item.value = str; } void addCData(const QString& str) { KoXmlPackedItem& item = newItem(); item.attr = false; item.type = KoXmlNode::CDATASectionNode; item.depth = elementDepth + 1; item.qnameIndex = 0; item.value = str; } void addProcessingInstruction() { KoXmlPackedItem& item = newItem(); item.attr = false; item.type = KoXmlNode::ProcessingInstructionNode; item.depth = elementDepth + 1; item.qnameIndex = 0; item.value.clear(); } void clear() { qnameHash.clear(); qnameList.clear(); valueHash.clear(); valueList.clear(); items.clear(); elementDepth = 0; KoXmlPackedItem& rootItem = newItem(); rootItem.attr = false; rootItem.type = KoXmlNode::DocumentNode; rootItem.depth = 0; rootItem.qnameIndex = 0; } void finish() { qnameHash.clear(); valueList.clear(); valueHash.clear(); items.squeeze(); } KoXmlPackedDocument(): processNamespace(false), elementDepth(0) { } #endif }; namespace { class ParseError { public: QString errorMsg; int errorLine; int errorColumn; bool error; ParseError() :errorLine(-1), errorColumn(-1), error(false) {} }; void parseElement(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces = true); // parse one element as if this were a standalone xml document ParseError parseDocument(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces = true) { doc.clear(); ParseError error; xml.readNext(); while (!xml.atEnd() && xml.tokenType() != QXmlStreamReader::EndDocument && !xml.hasError()) { switch (xml.tokenType()) { case QXmlStreamReader::StartElement: parseElement(xml, doc, stripSpaces); break; case QXmlStreamReader::DTD: doc.addDTD(xml.dtdName().toString()); break; case QXmlStreamReader::StartDocument: if (!xml.documentEncoding().isEmpty() || !xml.documentVersion().isEmpty()) { doc.addProcessingInstruction(); } break; case QXmlStreamReader::ProcessingInstruction: doc.addProcessingInstruction(); break; default: break; } xml.readNext(); } if (xml.hasError()) { error.error = true; error.errorMsg = xml.errorString(); error.errorColumn = xml.columnNumber(); error.errorLine = xml.lineNumber(); } else { doc.finish(); } return error; } void parseElementContents(QXmlStreamReader &xml, KoXmlPackedDocument &doc) { xml.readNext(); QString ws; while (!xml.atEnd()) { switch (xml.tokenType()) { case QXmlStreamReader::EndElement: // if an element contains only whitespace, put it in the dom if (!ws.isEmpty()) { doc.addText(ws); } return; case QXmlStreamReader::StartElement: // The whitespaces between > and < are also a text element if (!ws.isEmpty()) { doc.addText(ws); ws.clear(); } // Do not strip spaces parseElement(xml, doc, false); break; case QXmlStreamReader::Characters: if (xml.isCDATA()) { doc.addCData(xml.text().toString()); } else if (!xml.isWhitespace()) { doc.addText(xml.text().toString()); } else { ws += xml.text(); } break; case QXmlStreamReader::ProcessingInstruction: doc.addProcessingInstruction(); break; default: break; } xml.readNext(); } } void parseElementContentsStripSpaces(QXmlStreamReader &xml, KoXmlPackedDocument &doc) { xml.readNext(); QString ws; bool sawElement = false; while (!xml.atEnd()) { switch (xml.tokenType()) { case QXmlStreamReader::EndElement: // if an element contains only whitespace, put it in the dom if (!ws.isEmpty() && !sawElement) { doc.addText(ws); } return; case QXmlStreamReader::StartElement: sawElement = true; // Do strip spaces parseElement(xml, doc, true); break; case QXmlStreamReader::Characters: if (xml.isCDATA()) { doc.addCData(xml.text().toString()); } else if (!xml.isWhitespace()) { doc.addText(xml.text().toString()); } else if (!sawElement) { ws += xml.text(); } break; case QXmlStreamReader::ProcessingInstruction: doc.addProcessingInstruction(); break; default: break; } xml.readNext(); } } void parseElement(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces) { // Unfortunately MSVC fails using QXmlStreamReader::const_iterator // so we apply a for loop instead. https://bugreports.qt.io/browse/QTBUG-45368 doc.addElement(xml.qualifiedName().toString(), fixNamespace(xml.namespaceUri().toString())); QXmlStreamAttributes attr = xml.attributes(); for (int a = 0; a < attr.count(); a++) { doc.addAttribute(attr[a].qualifiedName().toString(), attr[a].namespaceUri().toString(), attr[a].value().toString()); } if (stripSpaces) parseElementContentsStripSpaces(xml, doc); else parseElementContents(xml, doc); // reader.tokenType() is now QXmlStreamReader::EndElement doc.closeElement(); } } // ================================================================== // // KoXmlNodeData // // ================================================================== class KoXmlNodeData { public: explicit KoXmlNodeData(unsigned long initialRefCount = 1); ~KoXmlNodeData(); // generic properties KoXmlNode::NodeType nodeType; bool loaded; #ifdef KOXML_COMPACT unsigned nodeDepth; #endif QString tagName; QString namespaceURI; QString prefix; QString localName; void ref() { ++refCount; } void unref() { if (!--refCount) { delete this; } } // type information QString nodeName() const; // for tree and linked-list KoXmlNodeData* parent; KoXmlNodeData* prev; KoXmlNodeData* next; KoXmlNodeData* first; KoXmlNodeData* last; QString text(); // node manipulation void clear(); // attributes inline void setAttribute(const QString& name, const QString& value); inline QString attribute(const QString& name, const QString& def) const; inline bool hasAttribute(const QString& name) const; inline void setAttributeNS(const QString& nsURI, const QString& name, const QString& value); inline QString attributeNS(const QString& nsURI, const QString& name, const QString& def) const; inline bool hasAttributeNS(const QString& nsURI, const QString& name) const; inline void clearAttributes(); inline QStringList attributeNames() const; inline QList< QPair > attributeFullNames() const; // for text and CDATA QString data() const; // reference from within the packed doc KoXmlPackedDocument* packedDoc; unsigned long nodeIndex; // used when doing on-demand (re)parse void loadChildren(int depth = 1); void unloadChildren(); void dump(); static KoXmlNodeData null; // compatibility void asQDomNode(QDomDocument& ownerDoc) const; private: QHash attr; QHash attrNS; QString textData; // reference counting unsigned long refCount; - friend class KoXmlElement; + friend #include }; KoXmlNodeData KoXmlNodeData::null; KoXmlNodeData::KoXmlNodeData(unsigned long initialRefCount) : nodeType(KoXmlNode::NullNode) , loaded(false) #ifdef KOXML_COMPACT , nodeDepth(0) #endif , parent(0), prev(0), next(0), first(0), last(0) , packedDoc(0), nodeIndex(0) , refCount(initialRefCount) { } KoXmlNodeData::~KoXmlNodeData() { clear(); } void KoXmlNodeData::clear() { if (first) for (KoXmlNodeData* node = first; node ;) { KoXmlNodeData* next = node->next; node->unref(); node = next; } // only document can delete these // normal nodes don't "own" them if (nodeType == KoXmlNode::DocumentNode) delete packedDoc; nodeType = KoXmlNode::NullNode; tagName.clear(); prefix.clear(); namespaceURI.clear(); textData.clear(); packedDoc = 0; attr.clear(); attrNS.clear(); parent = 0; prev = next = 0; first = last = 0; loaded = false; } QString KoXmlNodeData::text() { QString t; loadChildren(); KoXmlNodeData* node = first; while (node) { switch (node->nodeType) { case KoXmlNode::ElementNode: t += node->text(); break; case KoXmlNode::TextNode: t += node->data(); break; case KoXmlNode::CDATASectionNode: t += node->data(); break; default: break; } node = node->next; } return t; } QString KoXmlNodeData::nodeName() const { switch (nodeType) { case KoXmlNode::ElementNode: { QString n(tagName); if (!prefix.isEmpty()) n.prepend(':').prepend(prefix); return n; } break; case KoXmlNode::TextNode: return QLatin1String("#text"); case KoXmlNode::CDATASectionNode: return QLatin1String("#cdata-section"); case KoXmlNode::DocumentNode: return QLatin1String("#document"); case KoXmlNode::DocumentTypeNode: return tagName; default: return QString(); break; } // should not happen return QString(); } void KoXmlNodeData::setAttribute(const QString& name, const QString& value) { attr.insert(name, value); } QString KoXmlNodeData::attribute(const QString& name, const QString& def) const { return attr.value(name, def); } bool KoXmlNodeData::hasAttribute(const QString& name) const { return attr.contains(name); } void KoXmlNodeData::setAttributeNS(const QString& nsURI, const QString& name, const QString& value) { int i = name.indexOf(':'); if (i != -1) { QString localName(name.mid(i + 1)); KoXmlStringPair key(nsURI, localName); attrNS.insert(key, value); } } QString KoXmlNodeData::attributeNS(const QString& nsURI, const QString& name, const QString& def) const { KoXmlStringPair key(nsURI, name); return attrNS.value(key, def); } bool KoXmlNodeData::hasAttributeNS(const QString& nsURI, const QString& name) const { KoXmlStringPair key(nsURI, name); return attrNS.contains(key); } void KoXmlNodeData::clearAttributes() { attr.clear(); attrNS.clear(); } // FIXME how about namespaced attributes ? QStringList KoXmlNodeData::attributeNames() const { QStringList result; result = attr.keys(); return result; } QList< QPair > KoXmlNodeData::attributeFullNames() const { QList< QPair > result; result = attrNS.keys(); return result; } QString KoXmlNodeData::data() const { return textData; } #ifdef KOXML_COMPACT void KoXmlNodeData::loadChildren(int depth) { // sanity check if (!packedDoc) return; // already loaded ? if (loaded && (depth <= 1)) return; // in case depth is different unloadChildren(); KoXmlNodeData* lastDat = 0; unsigned childStop = 0; if (nodeIndex == packedDoc->itemCount(nodeDepth) - 1) childStop = packedDoc->itemCount(nodeDepth + 1); else { const KoXmlPackedItem& next = packedDoc->itemAt(nodeDepth, nodeIndex + 1); childStop = next.childStart; } const KoXmlPackedItem& self = packedDoc->itemAt(nodeDepth, nodeIndex); for (unsigned i = self.childStart; i < childStop; ++i) { const KoXmlPackedItem& item = packedDoc->itemAt(nodeDepth + 1, i); bool textItem = (item.type == KoXmlNode::TextNode); textItem |= (item.type == KoXmlNode::CDATASectionNode); // attribute belongs to this node if (item.attr) { KoQName qname = packedDoc->qnameList[item.qnameIndex]; QString value = item.value; QString prefix; QString qName; // with prefix QString localName; // without prefix, i.e. local name localName = qName = qname.name; int i = qName.indexOf(':'); if (i != -1) prefix = qName.left(i); if (i != -1) localName = qName.mid(i + 1); if (packedDoc->processNamespace) { setAttributeNS(qname.nsURI, qName, value); setAttribute(localName, value); } else setAttribute(qName, value); } else { KoQName qname = packedDoc->qnameList[item.qnameIndex]; QString value = item.value; QString nodeName = qname.name; QString localName; QString prefix; if (packedDoc->processNamespace) { localName = qname.name; int di = qname.name.indexOf(':'); if (di != -1) { localName = qname.name.mid(di + 1); prefix = qname.name.left(di); } nodeName = localName; } // make a node out of this item KoXmlNodeData* dat = new KoXmlNodeData; dat->nodeIndex = i; dat->packedDoc = packedDoc; dat->nodeDepth = nodeDepth + 1; dat->nodeType = item.type; dat->tagName = nodeName; dat->localName = localName; dat->prefix = prefix; dat->namespaceURI = qname.nsURI; dat->parent = this; dat->prev = lastDat; dat->next = 0; dat->first = 0; dat->last = 0; dat->loaded = false; dat->textData = (textItem) ? value : QString(); // adjust our linked-list first = (first) ? first : dat; last = dat; if (lastDat) lastDat->next = dat; lastDat = dat; // recursive if (depth > 1) dat->loadChildren(depth - 1); } } loaded = true; } #else void KoXmlNodeData::loadChildren(int depth) { // sanity check if (!packedDoc) return; // already loaded ? if (loaded && (depth <= 1)) return; // cause we don't know how deep this node's children already loaded are unloadChildren(); KoXmlNodeData* lastDat = 0; int nodeDepth = packedDoc->items[nodeIndex].depth; for (int i = nodeIndex + 1; i < packedDoc->items.count(); ++i) { KoXmlPackedItem& item = packedDoc->items[i]; bool textItem = (item.type == KoXmlNode::TextNode); textItem |= (item.type == KoXmlNode::CDATASectionNode); // element already outside our depth if (!item.attr && (item.type == KoXmlNode::ElementNode)) if (item.depth <= (unsigned)nodeDepth) break; // attribute belongs to this node if (item.attr && (item.depth == (unsigned)nodeDepth)) { KoQName qname = packedDoc->qnameList[item.qnameIndex]; QString value = item.value; QString prefix; QString qName; // with prefix QString localName; // without prefix, i.e. local name localName = qName = qname.name; int i = qName.indexOf(':'); if (i != -1) prefix = qName.left(i); if (i != -1) localName = qName.mid(i + 1); if (packedDoc->processNamespace) { setAttributeNS(qname.nsURI, qName, value); setAttribute(localName, value); } else setAttribute(qname.name, value); } // the child node if (!item.attr) { bool instruction = (item.type == KoXmlNode::ProcessingInstructionNode); bool ok = (textItem || instruction) ? (item.depth == (unsigned)nodeDepth) : (item.depth == (unsigned)nodeDepth + 1); ok = (item.depth == (unsigned)nodeDepth + 1); if (ok) { KoQName qname = packedDoc->qnameList[item.qnameIndex]; QString value = item.value; QString nodeName = qname.name; QString localName; QString prefix; if (packedDoc->processNamespace) { localName = qname.name; int di = qname.name.indexOf(':'); if (di != -1) { localName = qname.name.mid(di + 1); prefix = qname.name.left(di); } nodeName = localName; } // make a node out of this item KoXmlNodeData* dat = new KoXmlNodeData; dat->nodeIndex = i; dat->packedDoc = packedDoc; dat->nodeType = item.type; dat->tagName = nodeName; dat->localName = localName; dat->prefix = prefix; dat->namespaceURI = qname.nsURI; dat->count = 1; dat->parent = this; dat->prev = lastDat; dat->next = 0; dat->first = 0; dat->last = 0; dat->loaded = false; dat->textData = (textItem) ? value : QString(); // adjust our linked-list first = (first) ? first : dat; last = dat; if (lastDat) lastDat->next = dat; lastDat = dat; // recursive if (depth > 1) dat->loadChildren(depth - 1); } } } loaded = true; } #endif void KoXmlNodeData::unloadChildren() { // sanity check if (!packedDoc) return; if (!loaded) return; if (first) for (KoXmlNodeData* node = first; node ;) { KoXmlNodeData* next = node->next; node->unloadChildren(); node->unref(); node = next; } clearAttributes(); loaded = false; first = last = 0; } #ifdef KOXML_COMPACT static void itemAsQDomNode(QDomDocument& ownerDoc, KoXmlPackedDocument* packedDoc, unsigned nodeDepth, unsigned nodeIndex, QDomNode parentNode = QDomNode()) { // sanity check if (!packedDoc) return; const KoXmlPackedItem& self = packedDoc->itemAt(nodeDepth, nodeIndex); unsigned childStop = 0; if (nodeIndex == packedDoc->itemCount(nodeDepth) - 1) childStop = packedDoc->itemCount(nodeDepth + 1); else { const KoXmlPackedItem& next = packedDoc->itemAt(nodeDepth, nodeIndex + 1); childStop = next.childStart; } // nothing to do here if (self.type == KoXmlNode::NullNode) return; // create the element properly if (self.type == KoXmlNode::ElementNode) { QDomElement element; KoQName qname = packedDoc->qnameList[self.qnameIndex]; qname.nsURI = fixNamespace(qname.nsURI); if (packedDoc->processNamespace) element = ownerDoc.createElementNS(qname.nsURI, qname.name); else element = ownerDoc.createElement(qname.name); if ( parentNode.isNull() ) { ownerDoc.appendChild( element ); } else { parentNode.appendChild( element ); } // check all subnodes for attributes for (unsigned i = self.childStart; i < childStop; ++i) { const KoXmlPackedItem& item = packedDoc->itemAt(nodeDepth + 1, i); bool textItem = (item.type == KoXmlNode::TextNode); textItem |= (item.type == KoXmlNode::CDATASectionNode); // attribute belongs to this node if (item.attr) { KoQName qname = packedDoc->qnameList[item.qnameIndex]; qname.nsURI = fixNamespace(qname.nsURI ); QString value = item.value; QString prefix; QString qName; // with prefix QString localName; // without prefix, i.e. local name localName = qName = qname.name; int i = qName.indexOf(':'); if (i != -1) prefix = qName.left(i); if (i != -1) localName = qName.mid(i + 1); if (packedDoc->processNamespace) { element.setAttributeNS(qname.nsURI, qName, value); element.setAttribute(localName, value); } else element.setAttribute(qname.name, value); } else { // add it recursively itemAsQDomNode(ownerDoc, packedDoc, nodeDepth + 1, i, element); } } return; } // create the text node if (self.type == KoXmlNode::TextNode) { QString text = self.value; // FIXME: choose CDATA when the value contains special characters QDomText textNode = ownerDoc.createTextNode(text); if ( parentNode.isNull() ) { ownerDoc.appendChild( textNode ); } else { parentNode.appendChild( textNode ); } return; } // nothing matches? strange... } void KoXmlNodeData::asQDomNode(QDomDocument& ownerDoc) const { itemAsQDomNode(ownerDoc, packedDoc, nodeDepth, nodeIndex); } #else static void itemAsQDomNode(QDomDocument& ownerDoc, KoXmlPackedDocument* packedDoc, unsigned nodeIndex, QDomNode parentNode = QDomNode()) { // sanity check if (!packedDoc) return; KoXmlPackedItem& item = packedDoc->items[nodeIndex]; // nothing to do here if (item.type == KoXmlNode::NullNode) return; // create the element properly if (item.type == KoXmlNode::ElementNode) { QDomElement element; KoQName qname = packedDoc->qnameList[item.qnameIndex]; qname.nsURI = fixNamespace(qname.nsURI); if (packedDoc->processNamespace) element = ownerDoc.createElementNS(qname.nsURI, qname.name); else element = ownerDoc.createElement(qname.name); if ( parentNode.isNull() ) { ownerDoc.appendChild( element ); } else { parentNode.appendChild( element ); } // check all subnodes for attributes int nodeDepth = item.depth; for (int i = nodeIndex + 1; i < packedDoc->items.count(); ++i) { KoXmlPackedItem& item = packedDoc->items[i]; bool textItem = (item.type == KoXmlNode::TextNode); textItem |= (item.type == KoXmlNode::CDATASectionNode); // element already outside our depth if (!item.attr && (item.type == KoXmlNode::ElementNode)) if (item.depth <= (unsigned)nodeDepth) break; // attribute belongs to this node if (item.attr && (item.depth == (unsigned)nodeDepth)) { KoQName qname = packedDoc->qnameList[item.qnameIndex]; qname.nsURI = fixNamespace(qname.nsURI); QString value = item.value; QString prefix; QString qName; // with prefix QString localName; // without prefix, i.e. local name localName = qName = qname.name; int i = qName.indexOf(':'); if (i != -1) prefix = qName.left(i); if (i != -1) localName = qName.mid(i + 1); if (packedDoc->processNamespace) { element.setAttributeNS(qname.nsURI, qName, value); element.setAttribute(localName, value); } else element.setAttribute(qname.name, value); } // direct child of this node if (!item.attr && (item.depth == (unsigned)nodeDepth + 1)) { // add it recursively itemAsQDomNode(ownerDoc, packedDoc, i, element); } } return; } // create the text node if (item.type == KoXmlNode::TextNode) { QString text = item.value; // FIXME: choose CDATA when the value contains special characters QDomText textNode = ownerDoc.createTextNode(text); if ( parentNode.isNull() ) { ownerDoc.appendChild( textNode ); } else { parentNode.appendChild( textNode ); } return; } // nothing matches? strange... } void KoXmlNodeData::asQDomNode(QDomDocument& ownerDoc) const { itemAsQDomNode(ownerDoc, packedDoc, nodeIndex); } #endif void KoXmlNodeData::dump() { printf("NodeData %p\n", (void*)this); printf(" nodeIndex: %d\n", (int)nodeIndex); printf(" packedDoc: %p\n", (void*)packedDoc); printf(" nodeType : %d\n", (int)nodeType); printf(" tagName: %s\n", qPrintable(tagName)); printf(" namespaceURI: %s\n", qPrintable(namespaceURI)); printf(" prefix: %s\n", qPrintable(prefix)); printf(" localName: %s\n", qPrintable(localName)); printf(" parent : %p\n", (void*)parent); printf(" prev : %p\n", (void*)prev); printf(" next : %p\n", (void*)next); printf(" first : %p\n", (void*)first); printf(" last : %p\n", (void*)last); printf(" refCount: %ld\n", refCount); if (loaded) printf(" loaded: TRUE\n"); else printf(" loaded: FALSE\n"); } // ================================================================== // // KoXmlNodeData // // ================================================================== class KoXmlDocumentData : public KoXmlNodeData { public: KoXmlDocumentData(unsigned long initialRefCount = 1); ~KoXmlDocumentData(); bool setContent(QXmlStreamReader *reader, QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); KoXmlDocumentType dt; bool emptyDocument :1; // to read the xml with or without spaces bool stripSpaces :1; }; #define KOXMLDOCDATA(d) static_cast(d) KoXmlDocumentData::KoXmlDocumentData(unsigned long initialRefCount) : KoXmlNodeData(initialRefCount) , emptyDocument(true) , stripSpaces(true) { } KoXmlDocumentData::~KoXmlDocumentData() { } bool KoXmlDocumentData::setContent(QXmlStreamReader* reader, QString* errorMsg, int* errorLine, int* errorColumn) { // sanity checks if (!reader) return false; if (nodeType != KoXmlNode::DocumentNode) return false; clear(); nodeType = KoXmlNode::DocumentNode; packedDoc = new KoXmlPackedDocument; packedDoc->processNamespace = reader->namespaceProcessing(); ParseError error = parseDocument(*reader, *packedDoc, stripSpaces); if (error.error) { // parsing error has occurred if (errorMsg) *errorMsg = error.errorMsg; if (errorLine) *errorLine = error.errorLine; if (errorColumn) *errorColumn = error.errorColumn; return false; } // initially load loadChildren(); KoXmlNodeData *typeData = new KoXmlNodeData(0); typeData->nodeType = KoXmlNode::DocumentTypeNode; typeData->tagName = packedDoc->docType; typeData->parent = this; dt = KoXmlDocumentType(typeData); return true; } // ================================================================== // // KoXmlNode // // ================================================================== // Creates a null node KoXmlNode::KoXmlNode() { d = &KoXmlNodeData::null; d->ref(); } // Destroys this node KoXmlNode::~KoXmlNode() { d->unref(); } // Creates a copy of another node KoXmlNode::KoXmlNode(const KoXmlNode& node) { d = node.d; d->ref(); } // Creates a node for specific implementation KoXmlNode::KoXmlNode(KoXmlNodeData* data) { d = data; data->ref(); } // Creates a shallow copy of another node KoXmlNode& KoXmlNode::operator=(const KoXmlNode & node) { if (this != &node) { d->unref(); d = node.d; d->ref(); } return *this; } // Note: two null nodes are always equal bool KoXmlNode::operator==(const KoXmlNode& node) const { if (isNull() && node.isNull()) return true; return(d == node.d); } // Note: two null nodes are always equal bool KoXmlNode::operator!=(const KoXmlNode& node) const { if (isNull() && !node.isNull()) return true; if (!isNull() && node.isNull()) return true; if (isNull() && node.isNull()) return false; return(d != node.d); } KoXmlNode::NodeType KoXmlNode::nodeType() const { return d->nodeType; } bool KoXmlNode::isNull() const { return d->nodeType == NullNode; } bool KoXmlNode::isElement() const { return d->nodeType == ElementNode; } bool KoXmlNode::isText() const { return (d->nodeType == TextNode) || isCDATASection(); } bool KoXmlNode::isCDATASection() const { return d->nodeType == CDATASectionNode; } bool KoXmlNode::isDocument() const { return d->nodeType == DocumentNode; } bool KoXmlNode::isDocumentType() const { return d->nodeType == DocumentTypeNode; } void KoXmlNode::clear() { d->unref(); d = new KoXmlNodeData; } QString KoXmlNode::nodeName() const { return d->nodeName(); } QString KoXmlNode::prefix() const { return isElement() ? d->prefix : QString(); } QString KoXmlNode::namespaceURI() const { return isElement() ? d->namespaceURI : QString(); } QString KoXmlNode::localName() const { return isElement() ? d->localName : QString(); } KoXmlDocument KoXmlNode::ownerDocument() const { KoXmlNodeData* node = d; while (node->parent) node = node->parent; if (node->nodeType == DocumentNode) { return KoXmlDocument(static_cast(node)); } return KoXmlDocument(); } KoXmlNode KoXmlNode::parentNode() const { return d->parent ? KoXmlNode(d->parent) : KoXmlNode(); } bool KoXmlNode::hasChildNodes() const { if (isText()) return false; if (!d->loaded) d->loadChildren(); return d->first != 0 ; } int KoXmlNode::childNodesCount() const { if (isText()) return 0; if (!d->loaded) d->loadChildren(); KoXmlNodeData* node = d->first; int count = 0; while (node) { ++count; node = node->next; } return count; } QStringList KoXmlNode::attributeNames() const { if (!d->loaded) d->loadChildren(); return d->attributeNames(); } QList< QPair > KoXmlNode::attributeFullNames() const { if (!d->loaded) d->loadChildren(); return d->attributeFullNames(); } KoXmlNode KoXmlNode::firstChild() const { if (!d->loaded) d->loadChildren(); return d->first ? KoXmlNode(d->first) : KoXmlNode(); } KoXmlElement KoXmlNode::firstChildElement() const { KoXmlElement element; forEachElement (element, (*this)) { return element; } return KoXmlElement(); } KoXmlNode KoXmlNode::lastChild() const { if (!d->loaded) d->loadChildren(); return d->last ? KoXmlNode(d->last) : KoXmlNode(); } KoXmlNode KoXmlNode::nextSibling() const { return d->next ? KoXmlNode(d->next) : KoXmlNode(); } KoXmlNode KoXmlNode::previousSibling() const { return d->prev ? KoXmlNode(d->prev) : KoXmlNode(); } KoXmlNode KoXmlNode::namedItem(const QString& name) const { if (!d->loaded) d->loadChildren(); for (KoXmlNodeData* node = d->first; node; node = node->next) { if (node->nodeName() == name) return KoXmlNode(node); } // not found return KoXmlNode(); } KoXmlNode KoXmlNode::namedItemNS(const QString& nsURI, const QString& name) const { if (!d->loaded) d->loadChildren(); for (KoXmlNodeData* node = d->first; node; node = node->next) { if (node->nodeType == KoXmlNode::ElementNode && node->localName == name && node->namespaceURI == nsURI ) { return KoXmlNode(node); } } // not found return KoXmlNode(); } KoXmlNode KoXmlNode::namedItemNS(const QString& nsURI, const QString& name, KoXmlNamedItemType type) const { if (!d->loaded) d->loadChildren(); for (KoXmlNodeData* node = d->first; node; node = node->next) { if (node->nodeType != KoXmlNode::ElementNode) continue; if (node->localName == name && node->namespaceURI == nsURI) { return KoXmlNode(node); } bool isPrelude = false; switch (type) { case KoXmlTextContentPrelude: isPrelude = (node->localName == "tracked-changes" && node->namespaceURI == KoXmlNS::text) || (node->localName == "variable-decls" && node->namespaceURI == KoXmlNS::text) || (node->localName == "user-field-decls" && node->namespaceURI == KoXmlNS::text) || (node->localName == "user-field-decl" && node->namespaceURI == KoXmlNS::text) || (node->localName == "sequence-decls" && node->namespaceURI == KoXmlNS::text) || (node->localName == "sequence-decl" && node->namespaceURI == KoXmlNS::text) || (node->localName == "dde-connection-decls" && node->namespaceURI == KoXmlNS::text) || (node->localName == "alphabetical-index-auto-mark-file" && node->namespaceURI == KoXmlNS::text) || (node->localName == "forms" && node->namespaceURI == KoXmlNS::office); break; } if (!isPrelude) { return KoXmlNode(); // no TextContentPrelude means it follows TextContentMain, so stop here. } } // not found return KoXmlNode(); } KoXmlElement KoXmlNode::toElement() const { return isElement() ? KoXmlElement(d) : KoXmlElement(); } KoXmlText KoXmlNode::toText() const { return isText() ? KoXmlText(d) : KoXmlText(); } KoXmlCDATASection KoXmlNode::toCDATASection() const { return isCDATASection() ? KoXmlCDATASection(d) : KoXmlCDATASection(); } KoXmlDocument KoXmlNode::toDocument() const { if (isDocument()) { return KoXmlDocument(static_cast(d)); } return KoXmlDocument(); } void KoXmlNode::load(int depth) { d->loadChildren(depth); } void KoXmlNode::unload() { d->unloadChildren(); } void KoXmlNode::asQDomNode(QDomDocument& ownerDoc) const { Q_ASSERT(!isDocument()); d->asQDomNode(ownerDoc); } // ================================================================== // // KoXmlElement // // ================================================================== // Creates an empty element KoXmlElement::KoXmlElement(): KoXmlNode() { } KoXmlElement::~KoXmlElement() { } // Creates a shallow copy of another element KoXmlElement::KoXmlElement(const KoXmlElement& element): KoXmlNode(element.d) { } KoXmlElement::KoXmlElement(KoXmlNodeData* data): KoXmlNode(data) { } // Copies another element KoXmlElement& KoXmlElement::operator=(const KoXmlElement & element) { KoXmlNode::operator=(element); return *this; } bool KoXmlElement::operator== (const KoXmlElement& element) const { if (isNull() || element.isNull()) return false; return (d == element.d); } bool KoXmlElement::operator!= (const KoXmlElement& element) const { if (isNull() && element.isNull()) return false; if (isNull() || element.isNull()) return true; return (d != element.d); } QString KoXmlElement::tagName() const { return isElement() ? d->tagName : QString(); } QString KoXmlElement::text() const { return d->text(); } QString KoXmlElement::attribute(const QString& name) const { if (!isElement()) return QString(); if (!d->loaded) d->loadChildren(); return d->attribute(name, QString()); } QString KoXmlElement::attribute(const QString& name, const QString& defaultValue) const { if (!isElement()) return defaultValue; if (!d->loaded) d->loadChildren(); return d->attribute(name, defaultValue); } QString KoXmlElement::attributeNS(const QString& namespaceURI, const QString& localName, const QString& defaultValue) const { if (!isElement()) return defaultValue; if (!d->loaded) d->loadChildren(); KoXmlStringPair key(namespaceURI, localName); return d->attrNS.value(key, defaultValue); // return d->attributeNS( namespaceURI, localName, defaultValue ); } bool KoXmlElement::hasAttribute(const QString& name) const { if (!d->loaded) d->loadChildren(); return isElement() ? d->hasAttribute(name) : false; } bool KoXmlElement::hasAttributeNS(const QString& namespaceURI, const QString& localName) const { if (!d->loaded) d->loadChildren(); return isElement() ? d->hasAttributeNS(namespaceURI, localName) : false; } // ================================================================== // // KoXmlText // // ================================================================== KoXmlText::KoXmlText(): KoXmlNode() { } KoXmlText::~KoXmlText() { } KoXmlText::KoXmlText(const KoXmlText& text): KoXmlNode(text.d) { } KoXmlText::KoXmlText(KoXmlNodeData* data): KoXmlNode(data) { } bool KoXmlText::isText() const { return true; } QString KoXmlText::data() const { return d->data(); } KoXmlText& KoXmlText::operator=(const KoXmlText & element) { KoXmlNode::operator=(element); return *this; } // ================================================================== // // KoXmlCDATASection // // ================================================================== KoXmlCDATASection::KoXmlCDATASection(): KoXmlText() { } KoXmlCDATASection::KoXmlCDATASection(const KoXmlCDATASection& cdata) : KoXmlText(cdata) { } KoXmlCDATASection::~KoXmlCDATASection() { } KoXmlCDATASection::KoXmlCDATASection(KoXmlNodeData* cdata): KoXmlText(cdata) { } bool KoXmlCDATASection::isCDATASection() const { return true; } KoXmlCDATASection& KoXmlCDATASection::operator=(const KoXmlCDATASection & cdata) { KoXmlNode::operator=(cdata); return *this; } // ================================================================== // // KoXmlDocumentType // // ================================================================== KoXmlDocumentType::KoXmlDocumentType(): KoXmlNode() { } KoXmlDocumentType::~KoXmlDocumentType() { } KoXmlDocumentType::KoXmlDocumentType(const KoXmlDocumentType& dt): KoXmlNode(dt.d) { } QString KoXmlDocumentType::name() const { return nodeName(); } KoXmlDocumentType::KoXmlDocumentType(KoXmlNodeData* dt): KoXmlNode(dt) { } KoXmlDocumentType& KoXmlDocumentType::operator=(const KoXmlDocumentType & dt) { KoXmlNode::operator=(dt); return *this; } // ================================================================== // // KoXmlDocument // // ================================================================== KoXmlDocument::KoXmlDocument(bool stripSpaces): KoXmlNode(new KoXmlDocumentData(0)) { KOXMLDOCDATA(d)->emptyDocument = false; KOXMLDOCDATA(d)->stripSpaces = stripSpaces; } KoXmlDocument::~KoXmlDocument() { } KoXmlDocument::KoXmlDocument(KoXmlDocumentData* data): KoXmlNode(data) { KOXMLDOCDATA(d)->emptyDocument = true; } // Creates a copy of another document KoXmlDocument::KoXmlDocument(const KoXmlDocument& doc): KoXmlNode(doc.d) { } // Creates a shallow copy of another document KoXmlDocument& KoXmlDocument::operator=(const KoXmlDocument & doc) { KoXmlNode::operator=(doc); return *this; } // Checks if this document and doc are equals bool KoXmlDocument::operator==(const KoXmlDocument& doc) const { return(d == doc.d); } // Checks if this document and doc are not equals bool KoXmlDocument::operator!=(const KoXmlDocument& doc) const { return(d != doc.d); } KoXmlElement KoXmlDocument::documentElement() const { if (!d->loaded) d->loadChildren(); for (KoXmlNodeData* node = d->first; node; node = node->next) { if (node->nodeType == KoXmlNode::ElementNode) { return KoXmlElement(node); } } return KoXmlElement(); } KoXmlDocumentType KoXmlDocument::doctype() const { return KOXMLDOCDATA(d)->dt; } QString KoXmlDocument::nodeName() const { return (KOXMLDOCDATA(d)->emptyDocument) ? QString::fromLatin1("#document") : QString(); } void KoXmlDocument::clear() { d->unref(); KoXmlDocumentData *dat = new KoXmlDocumentData; dat->emptyDocument = false; d = dat; } namespace { /* Use an entity resolver that ignores undefined entities and simply returns an empty string for them. */ class DumbEntityResolver : public QXmlStreamEntityResolver { public: QString resolveUndeclaredEntity ( const QString &) override { return ""; } }; } bool KoXmlDocument::setContent(QXmlStreamReader *reader, QString* errorMsg, int* errorLine, int* errorColumn) { if (d->nodeType != KoXmlNode::DocumentNode) { const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces; d->unref(); KoXmlDocumentData *dat = new KoXmlDocumentData; dat->nodeType = KoXmlNode::DocumentNode; dat->stripSpaces = stripSpaces; d = dat; } const bool result = KOXMLDOCDATA(d)->setContent(reader, errorMsg, errorLine, errorColumn); return result; } // no namespace processing bool KoXmlDocument::setContent(QIODevice* device, QString* errorMsg, int* errorLine, int* errorColumn) { return setContent(device, false, errorMsg, errorLine, errorColumn); } bool KoXmlDocument::setContent(QIODevice* device, bool namespaceProcessing, QString* errorMsg, int* errorLine, int* errorColumn) { if (d->nodeType != KoXmlNode::DocumentNode) { const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces; d->unref(); KoXmlDocumentData *dat = new KoXmlDocumentData; dat->nodeType = KoXmlNode::DocumentNode; dat->stripSpaces = stripSpaces; d = dat; } if (!device->isOpen()) device->open(QIODevice::ReadOnly); QXmlStreamReader reader(device); reader.setNamespaceProcessing(namespaceProcessing); DumbEntityResolver entityResolver; reader.setEntityResolver(&entityResolver); const bool result = KOXMLDOCDATA(d)->setContent(&reader, errorMsg, errorLine, errorColumn); return result; } bool KoXmlDocument::setContent(const QByteArray& text, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn) { QBuffer buffer; buffer.setData(text); return setContent(&buffer, namespaceProcessing, errorMsg, errorLine, errorColumn); } bool KoXmlDocument::setContent(const QString& text, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn) { if (d->nodeType != KoXmlNode::DocumentNode) { const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces; d->unref(); KoXmlDocumentData *dat = new KoXmlDocumentData; dat->nodeType = KoXmlNode::DocumentNode; dat->stripSpaces = stripSpaces; d = dat; } QXmlStreamReader reader(text); reader.setNamespaceProcessing(namespaceProcessing); DumbEntityResolver entityResolver; reader.setEntityResolver(&entityResolver); const bool result = KOXMLDOCDATA(d)->setContent(&reader, errorMsg, errorLine, errorColumn); return result; } bool KoXmlDocument::setContent(const QString& text, QString *errorMsg, int *errorLine, int *errorColumn) { return setContent(text, false, errorMsg, errorLine, errorColumn); } void KoXmlDocument::setWhitespaceStripping(bool stripSpaces) { KOXMLDOCDATA(d)->stripSpaces = stripSpaces; } #endif // ================================================================== // // functions in KoXml namespace // // ================================================================== KoXmlElement KoXml::namedItemNS(const KoXmlNode& node, const QString& nsURI, const QString& localName) { #ifdef KOXML_USE_QDOM // David's solution for namedItemNS, only for QDom stuff KoXmlNode n = node.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { if (n.isElement() && n.localName() == localName && n.namespaceURI() == nsURI) return n.toElement(); } return KoXmlElement(); #else return node.namedItemNS(nsURI, localName).toElement(); #endif } KoXmlElement KoXml::namedItemNS(const KoXmlNode& node, const QString& nsURI, const QString& localName, KoXmlNamedItemType type) { #ifdef KOXML_USE_QDOM -Q_ASSERT(false); + Q_UNUSED(type) return namedItemNS(node, nsURI, localName); #else return node.namedItemNS(nsURI, localName, type).toElement(); #endif } void KoXml::load(KoXmlNode& node, int depth) { #ifdef KOXML_USE_QDOM // do nothing, QDom has no on-demand loading Q_UNUSED(node); Q_UNUSED(depth); #else node.load(depth); #endif } void KoXml::unload(KoXmlNode& node) { #ifdef KOXML_USE_QDOM // do nothing, QDom has no on-demand unloading Q_UNUSED(node); #else node.unload(); #endif } int KoXml::childNodesCount(const KoXmlNode& node) { #ifdef KOXML_USE_QDOM return node.childNodes().count(); #else // compatibility function, because no need to implement // a class like QDomNodeList return node.childNodesCount(); #endif } QStringList KoXml::attributeNames(const KoXmlNode& node) { #ifdef KOXML_USE_QDOM QStringList result; QDomNamedNodeMap attrMap = node.attributes(); for (int i = 0; i < attrMap.count(); ++i) result += attrMap.item(i).toAttr().name(); return result; #else // compatibility function, because no need to implement // a class like QDomNamedNodeMap return node.attributeNames(); #endif } void KoXml::asQDomNode(QDomDocument& ownerDoc, const KoXmlNode& node) { Q_ASSERT(!node.isDocument()); #ifdef KOXML_USE_QDOM - ownerDoc.appendChild(ownerDoc.importNode(node)); + ownerDoc.appendChild(ownerDoc.importNode(node, true)); #else node.asQDomNode(ownerDoc); #endif } void KoXml::asQDomElement(QDomDocument &ownerDoc, const KoXmlElement& element) { KoXml::asQDomNode(ownerDoc, element); } QDomDocument KoXml::asQDomDocument(const KoXmlDocument& document) { #ifdef KOXML_USE_QDOM return document; #else QDomDocument qdoc( document.nodeName() ); if ( document.hasChildNodes() ) { for ( KoXmlNode n = document.firstChild(); ! n.isNull(); n = n.nextSibling() ) { KoXml::asQDomNode(qdoc, n); } } return qdoc; #endif } -bool KoXml::setDocument(KoXmlDocument& doc, QIODevice* device, - bool namespaceProcessing, QString* errorMsg, int* errorLine, - int* errorColumn) +bool KoXml::setDocument(KoXmlDocument& doc, QIODevice *device, + bool namespaceProcessing, + QString *errorMsg, int *errorLine, int *errorColumn) { - QXmlStreamReader reader(device); - reader.setNamespaceProcessing(namespaceProcessing); - bool result = doc.setContent(&reader, errorMsg, errorLine, errorColumn); + bool result = doc.setContent(device, namespaceProcessing, errorMsg, errorLine, errorColumn); return result; } diff --git a/libs/store/KoXmlReader.h b/libs/store/KoXmlReader.h index 0443dd8349..7827b6bbaf 100644 --- a/libs/store/KoXmlReader.h +++ b/libs/store/KoXmlReader.h @@ -1,451 +1,453 @@ /* This file is part of the KDE project Copyright (C) 2005-2006 Ariya Hidayat 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 KO_XMLREADER_H #define KO_XMLREADER_H // KOXML_USE_QDOM is defined there #include "KoXmlReaderForward.h" #include "kritastore_export.h" #include #include class QIODevice; -#ifndef KOXML_USE_QDOM - -class QXmlStreamReader; - -class KoXmlNodeData; -class KoXmlDocumentData; -class QDomDocument; -class QStringList; /** * The office-text-content-prelude type. */ enum KoXmlNamedItemType { KoXmlTextContentPrelude ///< office-text-content-prelude //KoXmlTextContentMain, ///< office-text-content-main //KoXmlTextContentEpilogue ///< office-text-content-epilogue }; +#ifndef KOXML_USE_QDOM + +class QXmlStreamReader; + +class KoXmlNodeData; +class KoXmlDocumentData; +class QDomDocument; +class QStringList; + + /** * KoXmlNode represents a node in a DOM tree. * * KoXmlNode is a base class for KoXmlElement, KoXmlText. * Often, these subclasses are used for getting the data instead of KoXmlNode. * However, as base class, KoXmlNode is very helpful when for example iterating * all child nodes within one parent node. * * KoXmlNode implements an explicit sharing, a node shares its data with * other copies (if exist). * * XXX: DO NOT ADD CONVENIENCE API HERE BECAUSE THIS CLASS MUST REMAIN COMPATIBLE WITH QDOMNODE! * * @author Ariya Hidayat */ class KRITASTORE_EXPORT KoXmlNode { public: enum NodeType { NullNode = 0, ElementNode, TextNode, CDATASectionNode, ProcessingInstructionNode, DocumentNode, DocumentTypeNode }; KoXmlNode(); KoXmlNode(const KoXmlNode& node); KoXmlNode& operator=(const KoXmlNode& node); bool operator== (const KoXmlNode&) const; bool operator!= (const KoXmlNode&) const; virtual ~KoXmlNode(); virtual KoXmlNode::NodeType nodeType() const; virtual bool isNull() const; virtual bool isElement() const; virtual bool isText() const; virtual bool isCDATASection() const; virtual bool isDocument() const; virtual bool isDocumentType() const; virtual void clear(); KoXmlElement toElement() const; KoXmlText toText() const; KoXmlCDATASection toCDATASection() const; KoXmlDocument toDocument() const; virtual QString nodeName() const; virtual QString namespaceURI() const; virtual QString prefix() const; virtual QString localName() const; KoXmlDocument ownerDocument() const; KoXmlNode parentNode() const; bool hasChildNodes() const; KoXmlNode firstChild() const; KoXmlNode lastChild() const; KoXmlNode nextSibling() const; KoXmlNode previousSibling() const; KoXmlElement firstChildElement() const; // equivalent to node.childNodes().count() if node is a QDomNode instance int childNodesCount() const; // workaround to get and iterate over all attributes QStringList attributeNames() const; QList< QPair > attributeFullNames() const; KoXmlNode namedItem(const QString& name) const; KoXmlNode namedItemNS(const QString& nsURI, const QString& name) const; KoXmlNode namedItemNS(const QString& nsURI, const QString& name, KoXmlNamedItemType type) const; /** * Loads all child nodes (if any) of this node. Normally you do not need * to call this function as the child nodes will be automatically * loaded when necessary. */ void load(int depth = 1); /** * Releases all child nodes of this node. */ void unload(); // compatibility /** * @internal do not call directly * Use KoXml::asQDomDocument(), KoXml::asQDomElement() or KoXml::asQDomNode() instead */ void asQDomNode(QDomDocument& ownerDoc) const; protected: KoXmlNodeData* d; explicit KoXmlNode(KoXmlNodeData*); }; /** * KoXmlElement represents a tag element in a DOM tree. * * KoXmlElement holds information about an XML tag, along with its attributes. * * @author Ariya Hidayat */ class KRITASTORE_EXPORT KoXmlElement: public KoXmlNode { public: KoXmlElement(); KoXmlElement(const KoXmlElement& element); KoXmlElement& operator=(const KoXmlElement& element); virtual ~KoXmlElement(); bool operator== (const KoXmlElement&) const; bool operator!= (const KoXmlElement&) const; QString tagName() const; QString text() const; QString attribute(const QString& name) const; QString attribute(const QString& name, const QString& defaultValue) const; QString attributeNS(const QString& namespaceURI, const QString& localName, const QString& defaultValue = QString()) const; bool hasAttribute(const QString& name) const; bool hasAttributeNS(const QString& namespaceURI, const QString& localName) const; private: friend class KoXmlNode; friend class KoXmlDocument; explicit KoXmlElement(KoXmlNodeData*); }; /** * KoXmlText represents a text in a DOM tree. * @author Ariya Hidayat */ class KRITASTORE_EXPORT KoXmlText: public KoXmlNode { public: KoXmlText(); KoXmlText(const KoXmlText& text); KoXmlText& operator=(const KoXmlText& text); virtual ~KoXmlText(); QString data() const; virtual bool isText() const; private: friend class KoXmlNode; friend class KoXmlCDATASection; friend class KoXmlDocument; explicit KoXmlText(KoXmlNodeData*); }; /** * KoXmlCDATASection represents a CDATA section in a DOM tree. * @author Ariya Hidayat */ class KRITASTORE_EXPORT KoXmlCDATASection: public KoXmlText { public: KoXmlCDATASection(); KoXmlCDATASection(const KoXmlCDATASection& cdata); KoXmlCDATASection& operator=(const KoXmlCDATASection& cdata); virtual ~KoXmlCDATASection(); virtual bool isCDATASection() const; private: friend class KoXmlNode; friend class KoXmlDocument; explicit KoXmlCDATASection(KoXmlNodeData*); }; /** * KoXmlDocumentType represents the DTD of the document. At the moment, * it can used only to get the document type, i.e. no support for * entities etc. * * @author Ariya Hidayat */ class KRITASTORE_EXPORT KoXmlDocumentType: public KoXmlNode { public: KoXmlDocumentType(); KoXmlDocumentType(const KoXmlDocumentType&); KoXmlDocumentType& operator=(const KoXmlDocumentType&); virtual ~KoXmlDocumentType(); QString name() const; private: friend class KoXmlNode; friend class KoXmlDocument; friend class KoXmlDocumentData; explicit KoXmlDocumentType(KoXmlNodeData*); }; /** * KoXmlDocument represents an XML document, structured in a DOM tree. * * KoXmlDocument is designed to be memory efficient. Unlike QDomDocument from * Qt's XML module, KoXmlDocument does not store all nodes in the DOM tree. * Some nodes will be loaded and parsed on-demand only. * * KoXmlDocument is read-only, you can not modify its content. * * @author Ariya Hidayat */ class KRITASTORE_EXPORT KoXmlDocument: public KoXmlNode { public: explicit KoXmlDocument(bool stripSpaces = false); KoXmlDocument(const KoXmlDocument& node); KoXmlDocument& operator=(const KoXmlDocument& node); bool operator==(const KoXmlDocument&) const; bool operator!=(const KoXmlDocument&) const; virtual ~KoXmlDocument(); KoXmlElement documentElement() const; KoXmlDocumentType doctype() const; virtual QString nodeName() const; virtual void clear(); bool setContent(QIODevice* device, bool namespaceProcessing, QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); bool setContent(QIODevice* device, QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); bool setContent(QXmlStreamReader *reader, QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); bool setContent(const QByteArray& text, bool namespaceProcessing, QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0); bool setContent(const QString& text, bool namespaceProcessing, QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0); // no namespace processing bool setContent(const QString& text, QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0); /** * Change the way an XMLDocument will be read: * if stripSpaces = true then a will only have one child * if stripSpaces = false then a will have 3 children. */ void setWhitespaceStripping(bool stripSpaces); private: friend class KoXmlNode; explicit KoXmlDocument(KoXmlDocumentData*); }; #endif // KOXML_USE_QDOM /** * This namespace contains a few convenience functions to simplify code using QDom * (when loading OASIS documents, in particular). * * To find the child element with a given name, use KoXml::namedItemNS. * * To find all child elements with a given name, use * QDomElement e; * forEachElement( e, parent ) * { * if ( e.localName() == "..." && e.namespaceURI() == KoXmlNS::... ) * { * ... * } * } * Note that this means you don't ever need to use QDomNode nor toElement anymore! * Also note that localName is the part without the prefix, this is the whole point * of namespace-aware methods. * * To find the attribute with a given name, use QDomElement::attributeNS. * * Do not use getElementsByTagNameNS, it's recursive (which is never needed in Calligra). * Do not use tagName() or nodeName() or prefix(), since the prefix isn't fixed. * * @author David Faure */ namespace KoXml { /** * A namespace-aware version of QDomNode::namedItem(), * which also takes care of casting to a QDomElement. * * Use this when a domelement is known to have only *one* child element * with a given tagname. * * Note: do *NOT* use getElementsByTagNameNS, it's recursive! */ KRITASTORE_EXPORT KoXmlElement namedItemNS(const KoXmlNode& node, const QString& nsURI, const QString& localName); /** * A namespace-aware version of QDomNode::namedItem(). * which also takes care of casting to a QDomElement. * * Use this when you like to return the first or an invalid * KoXmlElement with a known type. * * This is an optimized version of the namedItemNS above to * give fast access to certain sections of the document using * the office-text-content-prelude condition as @a KoXmlNamedItemType . */ KRITASTORE_EXPORT KoXmlElement namedItemNS(const KoXmlNode& node, const QString& nsURI, const QString& localName, KoXmlNamedItemType type); /** * Explicitly load child nodes of specified node, up to given depth. * This function has no effect if QDom is used. */ KRITASTORE_EXPORT void load(KoXmlNode& node, int depth = 1); /** * Unload child nodes of specified node. * This function has no effect if QDom is used. */ KRITASTORE_EXPORT void unload(KoXmlNode& node); /** * Get the number of child nodes of specified node. */ KRITASTORE_EXPORT int childNodesCount(const KoXmlNode& node); /** * Return the name of all attributes of specified node. */ KRITASTORE_EXPORT QStringList attributeNames(const KoXmlNode& node); /** * Convert KoXmlNode classes to the corresponding QDom classes, which has * @p ownerDoc as the owner document (QDomDocument instance). * The converted @p node (and its children) are added to ownerDoc. * * NOTE: * - If ownerDoc is not empty, this may fail, @see QDomDocument * - @p node must not be a KoXmlDocument, use asQDomDocument() - * + * * @see asQDomDocument, asQDomElement */ KRITASTORE_EXPORT void asQDomNode(QDomDocument& ownerDoc, const KoXmlNode& node); /** * Convert KoXmlNode classes to the corresponding QDom classes, which has * @p ownerDoc as the owner document (QDomDocument instance). * The converted @p element (and its children) is added to ownerDoc. - * + * * NOTE: If ownerDoc is not empty, this may fail, @see QDomDocument * */ KRITASTORE_EXPORT void asQDomElement(QDomDocument& ownerDoc, const KoXmlElement& element); /** * Converts the whole @p document into a QDomDocument * If KOXML_USE_QDOM is defined, just returns @p document */ KRITASTORE_EXPORT QDomDocument asQDomDocument(const KoXmlDocument& document); /* * Load an XML document from specified device to a document. You can of * course use it with QFile (which inherits QIODevice). * This is much more memory efficient than standard QDomDocument::setContent * because the data from the device is buffered, unlike * QDomDocument::setContent which just loads everything in memory. * * Note: it is assumed that the XML uses UTF-8 encoding. */ KRITASTORE_EXPORT bool setDocument(KoXmlDocument& doc, QIODevice* device, bool namespaceProcessing, QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); } /** * \def forEachElement( elem, parent ) * \brief Loop through all child elements of \parent. * This convenience macro is used to implement the forEachElement loop. * The \elem parameter is a name of a QDomElement variable and the \parent * is the name of the parent element. For example: * * QDomElement e; * forEachElement( e, parent ) * { * qDebug() << e.localName() << " element found."; * ... * } */ #define forEachElement( elem, parent ) \ for ( KoXmlNode _node = parent.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) \ if ( ( elem = _node.toElement() ).isNull() ) {} else #endif // KO_XMLREADER_H diff --git a/libs/store/KoXmlReaderForward.h b/libs/store/KoXmlReaderForward.h index f49a7c1cb9..9aa79ad0a8 100644 --- a/libs/store/KoXmlReaderForward.h +++ b/libs/store/KoXmlReaderForward.h @@ -1,49 +1,49 @@ /* This file is part of the KDE project Copyright (C) 2005-2006 Ariya Hidayat Copyright (C) 2007 Thorsten Zachmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KOXMLREADERFORWARD_H #define KOXMLREADERFORWARD_H // use standard QDom, useful to test KoXml classes against Qt's QDom -//#define KOXML_USE_QDOM +#define KOXML_USE_QDOM #ifdef KOXML_USE_QDOM #include typedef QDomNode KoXmlNode; typedef QDomElement KoXmlElement; typedef QDomText KoXmlText; typedef QDomCDATASection KoXmlCDATASection; typedef QDomDocumentType KoXmlDocumentType; typedef QDomDocument KoXmlDocument; #else class KoXmlElement; class KoXmlNode; class KoXmlText; class KoXmlCDATASection; class KoXmlDocumentType; class KoXmlDocument; #endif #endif // KOXMLREADERFORWARD_H diff --git a/libs/ui/flake/kis_shape_layer.cc b/libs/ui/flake/kis_shape_layer.cc index 157c517adb..5b5e628f11 100644 --- a/libs/ui/flake/kis_shape_layer.cc +++ b/libs/ui/flake/kis_shape_layer.cc @@ -1,629 +1,627 @@ /* * 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 #include #include #include #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 class ShapeLayerContainerModel : public SimpleShapeContainerModel { public: ShapeLayerContainerModel(KisShapeLayer *parent) : q(parent) {} void add(KoShape *child) override { SimpleShapeContainerModel::add(child); } void remove(KoShape *child) override { 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; KisShapeLayerCanvas * canvas; KoShapeBasedDocumentBase* controller; int x; int y; }; KisShapeLayer::KisShapeLayer(KoShapeBasedDocumentBase* 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, KoShapeBasedDocumentBase* controller) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _rhs 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(controller); Q_FOREACH (KoShape *shape, _rhs.shapes()) { addShape(shape->cloneShape()); } } 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); // copy in _rhs's shapes Q_FOREACH (KoShape *shape, _rhs.shapes()) { addShape(shape->cloneShape()); } // copy in _addShapes's shapes Q_FOREACH (KoShape *shape, _addShapes.shapes()) { addShape(shape->cloneShape()); } } 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(KoShapeBasedDocumentBase* controller) { setSupportsLodMoves(false); setShapeId(KIS_SHAPE_LAYER_ID); KIS_ASSERT_RECOVER_NOOP(this->image()); m_d->paintDevice = new KisPaintDevice(image()->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(this->image())); m_d->paintDevice->setParentNode(this); m_d->canvas = new KisShapeLayerCanvas(this, image()); m_d->canvas->setProjection(m_d->paintDevice); 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(const QPointF&)), SLOT(slotMoveShapes(const 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(); } 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) { KisExternalLayer::setVisible(visible, isLoading); } void KisShapeLayer::forceRepaint() { m_d->canvas->forceRepaint(); } #include "SvgWriter.h" #include "SvgParser.h" #include bool KisShapeLayer::saveShapesToStore(KoStore *store, QList shapes, const QSizeF &sizeInPt) { if (!store->open("content.svg")) { return false; } KoStoreDevice storeDev(store); storeDev.open(QIODevice::WriteOnly); qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes, sizeInPt); writer.save(storeDev); if (!store->close()) { return false; } return true; } QList KisShapeLayer::createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize) { - QXmlStreamReader reader(device); - reader.setNamespaceProcessing(false); QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc; - bool ok = doc.setContent(&reader, &errorMsg, &errorLine, &errorColumn); + bool ok = doc.setContent(device, false, &errorMsg, &errorLine, &errorColumn); if (!ok) { 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(); if (master) { const KoXmlElement *style = odfStore.styles().findStyle( master->attributeNS(KoXmlNS::style, "page-layout-name", QString())); KoPageLayout pageLayout; pageLayout.loadOdf(*style); setSize(QSizeF(pageLayout.width, pageLayout.height)); } // 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; } void KisShapeLayer::resetCache() { m_d->paintDevice->clear(); QList shapes = m_d->canvas->shapeManager()->shapes(); Q_FOREACH (const KoShape* shape, shapes) { shape->update(); } } KUndo2Command* KisShapeLayer::crop(const QRect & rect) { QPoint oldPos(x(), y()); QPoint newPos = oldPos - rect.topLeft(); return new KisNodeMoveCommand2(this, oldPos, newPos); } KUndo2Command* KisShapeLayer::transform(const QTransform &transform) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return 0; KisImageViewConverter *converter = dynamic_cast(this->converter()); QTransform realTransform = converter->documentToView() * transform * converter->viewToDocument(); QList oldTransformations; QList newTransformations; QList newShadows; const qreal transformBaseScale = KoUnit::approxTransformScale(transform); Q_FOREACH (const KoShape* shape, shapes) { QTransform oldTransform = shape->transformation(); oldTransformations.append(oldTransform); QTransform globalTransform = shape->absoluteTransformation(0); QTransform localTransform = globalTransform * realTransform * globalTransform.inverted(); newTransformations.append(localTransform * oldTransform); KoShapeShadow *shadow = 0; if (shape->shadow()) { shadow = new KoShapeShadow(*shape->shadow()); shadow->setOffset(transformBaseScale * shadow->offset()); shadow->setBlur(transformBaseScale * shadow->blur()); } newShadows.append(shadow); } KUndo2Command *parentCommand = new KUndo2Command(); new KoShapeTransformCommand(shapes, oldTransformations, newTransformations, parentCommand); new KoShapeShadowCommand(shapes, newShadows, parentCommand); return parentCommand; } diff --git a/plugins/flake/artistictextshape/ArtisticTextLoadingContext.h b/plugins/flake/artistictextshape/ArtisticTextLoadingContext.h index 2d34d2ea70..82889a3666 100644 --- a/plugins/flake/artistictextshape/ArtisticTextLoadingContext.h +++ b/plugins/flake/artistictextshape/ArtisticTextLoadingContext.h @@ -1,129 +1,129 @@ /* 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 SVGTEXTHELPER_H #define SVGTEXTHELPER_H #include #include #include #include typedef QList CharTransforms; -class KoXmlElement; +#include class SvgGraphicsContext; class ArtisticTextLoadingContext { public: enum OffsetType { None, Absolute, Relative }; ArtisticTextLoadingContext(); static QString simplifyText(const QString &text, bool preserveWhiteSpace = false); /// Parses current character transforms (x,y,dx,dy,rotate) void parseCharacterTransforms(const KoXmlElement &element, SvgGraphicsContext *gc); /// Pushes the current character transforms to the stack void pushCharacterTransforms(); /// Pops last character transforms from the stack void popCharacterTransforms(); /// Checks current x-offset type OffsetType xOffsetType() const; /// Checks current y-offset type OffsetType yOffsetType() const; /// Returns x-offsets from stack CharTransforms xOffsets(int count); /// Returns y-offsets from stack CharTransforms yOffsets(int count); /// Returns rotations from stack CharTransforms rotations(int count); /// Returns the text position QPointF textPosition() const; private: void printDebug(); struct CharTransformState { CharTransformState() : hasData(false), lastTransform(0.0) { } CharTransformState(const CharTransforms &initialData) : data(initialData), hasData(!initialData.isEmpty()) , lastTransform(initialData.isEmpty() ? 0.0 : initialData.last()) { } CharTransforms extract(int count) { const int copyCount = qMin(data.count(), count); CharTransforms extracted = data.mid(0, copyCount); data = data.mid(copyCount); return extracted; } CharTransforms data; bool hasData; qreal lastTransform; }; typedef QList CharTransformStack; enum ValueType { Number, XLength, YLength }; /// Parses offset values from the given string CharTransforms parseList(const QString &listString, SvgGraphicsContext *gc, ValueType type); /// Collects number of specified transforms values from the stack CharTransforms collectValues(int count, CharTransformState ¤t, CharTransformStack &stack); CharTransformState m_currentAbsolutePosX; ///< current absolute character x-positions CharTransformState m_currentAbsolutePosY; ///< current absolute character y-positions CharTransformState m_currentRelativePosX; ///< current relative character x-positions CharTransformState m_currentRelativePosY; ///< current relative character y-positions CharTransformState m_currentRotations; ///< current character rotations CharTransformStack m_absolutePosX; ///< stack of absolute character x-positions CharTransformStack m_absolutePosY; ///< stack of absolute character y-positions CharTransformStack m_relativePosX; ///< stack of relative character x-positions CharTransformStack m_relativePosY; ///< stack of relative character y-positions CharTransformStack m_rotations; ///< stack of character rotations QPointF m_textPosition; }; #endif // SVGTEXTHELPER_H diff --git a/plugins/flake/textshape/kotext/KoSection.h b/plugins/flake/textshape/kotext/KoSection.h index 7d4e29e9ea..12dd9c2ded 100644 --- a/plugins/flake/textshape/kotext/KoSection.h +++ b/plugins/flake/textshape/kotext/KoSection.h @@ -1,135 +1,135 @@ /* * Copyright (c) 2011 Boudewijn Rempt * 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 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 KOSECTION_H #define KOSECTION_H #include "kritatext_export.h" #include #include #include #include #include #include -class KoXmlElement; +#include class KoShapeSavingContext; class KoTextSharedLoadingData; class KoSectionEnd; class KoElementReference; class KoTextInlineRdf; class KoSectionPrivate; /** * Contains the information about the current text:section. * * The element has the following attributes: * *

* (odf spec v.12) */ class KRITATEXT_EXPORT KoSection { public: ~KoSection(); /// Returns section name QString name() const; /// Returns starting and ending position of section in QTextDocument QPair bounds() const; /// Returns section level. Root section has @c 0 level. int level() const; /** Returns inlineRdf associated with section * @return pointer to the KoTextInlineRdf for this section */ KoTextInlineRdf *inlineRdf() const; /** Sets KoTextInlineRdf for this section * @param inlineRdf pointer to KoTextInlineRdf to set */ void setInlineRdf(KoTextInlineRdf *inlineRdf); bool loadOdf(const KoXmlElement &element, KoTextSharedLoadingData *sharedData, bool stylesDotXml); void saveOdf(KoShapeSavingContext &context) const; protected: const QScopedPointer d_ptr; private: Q_DISABLE_COPY(KoSection) Q_DECLARE_PRIVATE(KoSection) explicit KoSection(const QTextCursor &cursor, const QString &name, KoSection *parent); /// Changes section's name to @param name void setName(const QString &name); /// Sets paired KoSectionsEnd for this section. void setSectionEnd(KoSectionEnd *sectionEnd); /** * Sets level of section in section tree. * Root sections have @c 0 level. */ void setLevel(int level); /// Returns a pointer to the parent of the section in tree. KoSection *parent() const; /// Returns a vector of pointers to the children of the section. QVector children() const; /** * Specifies if end bound of section should stay on place when inserting text. * Used by KoTextLoader on document loading. * @see QTextCursor::setKeepPositionOnInsert(bool) */ void setKeepEndBound(bool state); /** * Inserts @param section to position @param childIdx of children */ void insertChild(int childIdx, KoSection *section); /** * Removes child on position @param childIdx */ void removeChild(int childIdx); friend class KoSectionModel; friend class KoTextLoader; // accesses setKeepEndBound() function friend class KoSectionEnd; friend class TestKoTextEditor; // accesses setKeepEndBound() function }; Q_DECLARE_METATYPE(KoSection *) Q_DECLARE_METATYPE(QList) #endif // KOSECTION_H diff --git a/plugins/flake/textshape/kotext/changetracker/KoChangeTracker.cpp b/plugins/flake/textshape/kotext/changetracker/KoChangeTracker.cpp index 1a6658c4ad..fd0e3a84b2 100644 --- a/plugins/flake/textshape/kotext/changetracker/KoChangeTracker.cpp +++ b/plugins/flake/textshape/kotext/changetracker/KoChangeTracker.cpp @@ -1,739 +1,741 @@ /* This file is part of the KDE project * Copyright (C) 2008 Pierre Stirnweiss \pierre.stirnweiss_calligra@gadz.org> * Copyright (C) 2011 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 "KoChangeTracker.h" //Calligra includes #include "styles/KoCharacterStyle.h" #include "KoChangeTrackerElement.h" #include #include #include #include #include #include #include #include #include "KoFormatChangeInformation.h" #include //KDE includes #include "TextDebug.h" #include //Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoChangeTracker::Private { public: Private() : changeId(1), recordChanges(false), displayChanges(false), insertionBgColor(101,255,137), deletionBgColor(255,185,185), formatChangeBgColor(195,195,255), changeSaveFormat(UNKNOWN) { } ~Private() { } QMultiHash children; QMultiHash duplicateIds; QHash parents; QHash changes; QHash loadedChanges; QHash changeInformation; QList saveChanges; QList acceptedRejectedChanges; int changeId; bool recordChanges; bool displayChanges; QColor insertionBgColor, deletionBgColor, formatChangeBgColor; QString changeAuthorName; KoChangeTracker::ChangeSaveFormat changeSaveFormat; }; KoChangeTracker::KoChangeTracker(QObject *parent) : QObject(parent), d(new Private()) { d->changeId = 1; } KoChangeTracker::~KoChangeTracker() { delete d; } void KoChangeTracker::setRecordChanges(bool enabled) { d->recordChanges = enabled; } bool KoChangeTracker::recordChanges() const { return d->recordChanges; } void KoChangeTracker::setDisplayChanges(bool enabled) { d->displayChanges = enabled; } bool KoChangeTracker::displayChanges() const { return d->displayChanges; } QString KoChangeTracker::authorName() const { return d->changeAuthorName; } void KoChangeTracker::setAuthorName(const QString &authorName) { d->changeAuthorName = authorName; } KoChangeTracker::ChangeSaveFormat KoChangeTracker::saveFormat() const { return d->changeSaveFormat; } void KoChangeTracker::setSaveFormat(ChangeSaveFormat saveFormat) { d->changeSaveFormat = saveFormat; } int KoChangeTracker::getFormatChangeId(const KUndo2MagicString &title, const QTextFormat &format, const QTextFormat &prevFormat, int existingChangeId) { if ( existingChangeId ) { d->children.insert(existingChangeId, d->changeId); d->parents.insert(d->changeId, existingChangeId); } KoChangeTrackerElement *changeElement = new KoChangeTrackerElement(title, KoGenChange::FormatChange); changeElement->setChangeFormat(format); changeElement->setPrevFormat(prevFormat); QLocale l; changeElement->setDate(l.toString(QDateTime::currentDateTime()).replace(QLocale().decimalPoint(), QString("."))); changeElement->setCreator(d->changeAuthorName); changeElement->setEnabled(d->recordChanges); d->changes.insert(d->changeId, changeElement); return d->changeId++; } int KoChangeTracker::getInsertChangeId(const KUndo2MagicString &title, int existingChangeId) { if ( existingChangeId ) { d->children.insert(existingChangeId, d->changeId); d->parents.insert(d->changeId, existingChangeId); } KoChangeTrackerElement *changeElement = new KoChangeTrackerElement(title, KoGenChange::InsertChange); QLocale l; changeElement->setDate(l.toString(QDateTime::currentDateTime()).replace(QLocale().decimalPoint(), QString("."))); changeElement->setCreator(d->changeAuthorName); changeElement->setEnabled(d->recordChanges); d->changes.insert(d->changeId, changeElement); return d->changeId++; } int KoChangeTracker::getDeleteChangeId(const KUndo2MagicString &title, const QTextDocumentFragment &selection, int existingChangeId) { if ( existingChangeId ) { d->children.insert(existingChangeId, d->changeId); d->parents.insert(d->changeId, existingChangeId); } KoChangeTrackerElement *changeElement = new KoChangeTrackerElement(title, KoGenChange::DeleteChange); QLocale l; changeElement->setDate(l.toString(QDateTime::currentDateTime()).replace(QLocale().decimalPoint(), QString("."))); changeElement->setCreator(d->changeAuthorName); changeElement->setDeleteData(selection); changeElement->setEnabled(d->recordChanges); d->changes.insert(d->changeId, changeElement); return d->changeId++; } KoChangeTrackerElement* KoChangeTracker::elementById(int id) const { if (isDuplicateChangeId(id)) { id = originalChangeId(id); } return d->changes.value(id); } bool KoChangeTracker::removeById(int id, bool freeMemory) { if (freeMemory) { KoChangeTrackerElement *temp = d->changes.value(id); delete temp; } return d->changes.remove(id); } bool KoChangeTracker::containsInlineChanges(const QTextFormat &format) const { if (format.property(KoCharacterStyle::ChangeTrackerId).toInt()) return true; return false; } int KoChangeTracker::mergeableId(KoGenChange::Type type, const KUndo2MagicString &title, int existingId) const { if (!existingId || !d->changes.value(existingId)) return 0; if (d->changes.value(existingId)->getChangeType() == type && d->changes.value(existingId)->getChangeTitle() == title) { return existingId; } else { if (d->parents.contains(existingId)) { return mergeableId(type, title, d->parents.value(existingId)); } else { return 0; } } } int KoChangeTracker::split(int changeId) { KoChangeTrackerElement *element = new KoChangeTrackerElement(*d->changes.value(changeId)); d->changes.insert(d->changeId, element); return d->changeId++; } bool KoChangeTracker::isParent(int testedParentId, int testedChildId) const { if ((testedParentId == testedChildId) && !d->acceptedRejectedChanges.contains(testedParentId)) return true; else if (d->parents.contains(testedChildId)) return isParent(testedParentId, d->parents.value(testedChildId)); else return false; } void KoChangeTracker::setParent(int child, int parent) { if (!d->children.values(parent).contains(child)) { d->children.insert(parent, child); } if (!d->parents.contains(child)) { d->parents.insert(child, parent); } } int KoChangeTracker::parent(int changeId) const { if (!d->parents.contains(changeId)) return 0; if (d->acceptedRejectedChanges.contains(d->parents.value(changeId))) return parent(d->parents.value(changeId)); return d->parents.value(changeId); } int KoChangeTracker::createDuplicateChangeId(int existingChangeId) { int duplicateChangeId = d->changeId; d->changeId++; d->duplicateIds.insert(existingChangeId, duplicateChangeId); return duplicateChangeId; } bool KoChangeTracker::isDuplicateChangeId(int duplicateChangeId) const { return d->duplicateIds.values().contains(duplicateChangeId); } int KoChangeTracker::originalChangeId(int duplicateChangeId) const { int originalChangeId = 0; QMultiHash::const_iterator i = d->duplicateIds.constBegin(); while (i != d->duplicateIds.constEnd()) { if (duplicateChangeId == i.value()) { originalChangeId = i.key(); break; } ++i; } return originalChangeId; } void KoChangeTracker::acceptRejectChange(int changeId, bool set) { if (set) { if (!d->acceptedRejectedChanges.contains(changeId)) d->acceptedRejectedChanges.append(changeId); } else { if (d->acceptedRejectedChanges.contains(changeId)) d->acceptedRejectedChanges.removeAll(changeId); } d->changes.value(changeId)->setAcceptedRejected(set); } bool KoChangeTracker::saveInlineChange(int changeId, KoGenChange &change) { if (!d->changes.contains(changeId)) return false; change.setType(d->changes.value(changeId)->getChangeType()); change.addChangeMetaData("dc-creator", d->changes.value(changeId)->getCreator()); change.addChangeMetaData("dc-date", d->changes.value(changeId)->getDate()); if (d->changes.value(changeId)->hasExtraMetaData()) change.addChildElement("changeMetaData", d->changes.value(changeId)->getExtraMetaData()); return true; } QMap KoChangeTracker::saveInlineChanges(QMap changeTransTable, KoGenChanges &genChanges) { foreach (int changeId, d->changes.keys()) { // return if the id we find in the changetranstable already has a length. if (changeTransTable.value(changeId).length()) { continue; } if ((elementById(changeId)->getChangeType() == KoGenChange::DeleteChange) && (saveFormat() == KoChangeTracker::ODF_1_2)) { continue; } KoGenChange change; if (saveFormat() == KoChangeTracker::ODF_1_2) { change.setChangeFormat(KoGenChange::ODF_1_2); } else { change.setChangeFormat(KoGenChange::DELTAXML); } saveInlineChange(changeId, change); QString changeName = genChanges.insert(change); changeTransTable.insert(changeId, changeName); } return changeTransTable; } void KoChangeTracker::setFormatChangeInformation(int formatChangeId, KoFormatChangeInformation *formatInformation) { d->changeInformation.insert(formatChangeId, formatInformation); } KoFormatChangeInformation *KoChangeTracker::formatChangeInformation(int formatChangeId) const { return d->changeInformation.value(formatChangeId); } void KoChangeTracker::loadOdfChanges(const KoXmlElement& element) { +#ifndef KOXML_USE_QDOM if (element.namespaceURI() == KoXmlNS::text) { KoXmlElement tag; forEachElement(tag, element) { if (! tag.isNull()) { const QString localName = tag.localName(); if (localName == "changed-region") { KoChangeTrackerElement *changeElement = 0; KoXmlElement region; forEachElement(region, tag) { if (!region.isNull()) { if (region.localName() == "insertion") { changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::text,"id")),KoGenChange::InsertChange); } else if (region.localName() == "format-change") { changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::text,"id")),KoGenChange::FormatChange); } else if (region.localName() == "deletion") { changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::text,"id")),KoGenChange::DeleteChange); } KoXmlElement metadata = region.namedItemNS(KoXmlNS::office,"change-info").toElement(); if (!metadata.isNull()) { KoXmlElement date = metadata.namedItem("dc:date").toElement(); if (!date.isNull()) { changeElement->setDate(date.text()); } KoXmlElement creator = metadata.namedItem("dc:creator").toElement(); if (!date.isNull()) { changeElement->setCreator(creator.text()); } //TODO load comments /* KoXmlElement extra = metadata.namedItem("dc-").toElement(); if (!date.isNull()) { debugText << "creator: " << creator.text(); changeElement->setCreator(creator.text()); }*/ } changeElement->setEnabled(d->recordChanges); d->changes.insert( d->changeId, changeElement); d->loadedChanges.insert(tag.attributeNS(KoXmlNS::text,"id"), d->changeId++); } } } } } } else { //This is the ODF 1.2 Change Format KoXmlElement tag; forEachElement(tag, element) { if (! tag.isNull()) { const QString localName = tag.localName(); if (localName == "change-transaction") { KoChangeTrackerElement *changeElement = 0; //Set the change element as an insertion element for now //Will be changed to the correct type when actual changes referencing this change-id are encountered changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::delta,"change-id")),KoGenChange::InsertChange); KoXmlElement metadata = tag.namedItemNS(KoXmlNS::delta,"change-info").toElement(); if (!metadata.isNull()) { KoXmlElement date = metadata.namedItem("dc:date").toElement(); if (!date.isNull()) { changeElement->setDate(date.text()); } KoXmlElement creator = metadata.namedItem("dc:creator").toElement(); if (!creator.isNull()) { changeElement->setCreator(creator.text()); } } changeElement->setEnabled(d->recordChanges); d->changes.insert( d->changeId, changeElement); d->loadedChanges.insert(tag.attributeNS(KoXmlNS::delta,"change-id"), d->changeId++); } } } } +#endif } int KoChangeTracker::getLoadedChangeId(const QString &odfId) const { return d->loadedChanges.value(odfId); } int KoChangeTracker::getDeletedChanges(QVector& deleteVector) const { int numAppendedItems = 0; foreach (KoChangeTrackerElement *element, d->changes.values()) { if(element->getChangeType() == KoGenChange::DeleteChange && !element->acceptedRejected()) { deleteVector << element; numAppendedItems++; } } return numAppendedItems; } QColor KoChangeTracker::getInsertionBgColor() const { return d->insertionBgColor; } QColor KoChangeTracker::getDeletionBgColor() const { return d->deletionBgColor; } QColor KoChangeTracker::getFormatChangeBgColor() const { return d->formatChangeBgColor; } void KoChangeTracker::setInsertionBgColor(const QColor& bgColor) { d->insertionBgColor = bgColor; } void KoChangeTracker::setDeletionBgColor(const QColor& bgColor) { d->deletionBgColor = bgColor; } void KoChangeTracker::setFormatChangeBgColor(const QColor& bgColor) { d->formatChangeBgColor = bgColor; } ////A convenience function to get a ListIdType from a format //static KoListStyle::ListIdType ListId(const QTextListFormat &format) //{ // KoListStyle::ListIdType listId; // if (sizeof(KoListStyle::ListIdType) == sizeof(uint)) { // listId = format.property(KoListStyle::ListId).toUInt(); // } // else { // listId = format.property(KoListStyle::ListId).toULongLong(); // } // return listId; //} QTextDocumentFragment KoChangeTracker::generateDeleteFragment(const QTextCursor &cursor) { QTextCursor editCursor(cursor); QTextDocument *document = cursor.document(); QTextDocument deletedDocument; QTextDocument deleteCursor(&deletedDocument); KoInlineTextObjectManager *textObjectManager = KoTextDocument(document).inlineTextObjectManager(); if (textObjectManager) { for (int i = cursor.anchor();i <= cursor.position(); i++) { if (document->characterAt(i) == QChar::ObjectReplacementCharacter) { editCursor.setPosition(i+1); - } + } } } QTextBlock currentBlock = document->findBlock(cursor.anchor()); QTextBlock startBlock = currentBlock; QTextBlock endBlock = document->findBlock(cursor.position()).next(); currentBlock = document->findBlock(cursor.anchor()); startBlock = currentBlock; endBlock = document->findBlock(cursor.position()).next(); for (;currentBlock != endBlock; currentBlock = currentBlock.next()) { editCursor.setPosition(currentBlock.position()); if (editCursor.currentTable()) { QTextTableFormat tableFormat = editCursor.currentTable()->format(); editCursor.currentTable()->setFormat(tableFormat); } if (currentBlock != startBlock) { QTextBlockFormat blockFormat; editCursor.mergeBlockFormat(blockFormat); } } return cursor.selection(); } bool KoChangeTracker::checkListDeletion(const QTextList &list, const QTextCursor &cursor) { int startOfList = (list.item(0).position() - 1); int endOfList = list.item(list.count() -1).position() + list.item(list.count() -1).length() - 1; if ((cursor.anchor() <= startOfList) && (cursor.position() >= endOfList)) return true; else { /***************************************************************************************************/ /* Qt Quirk Work-Around */ /***************************************************************************************************/ if ((cursor.anchor() == (startOfList + 1)) && (cursor.position() > endOfList)) { return true; /***************************************************************************************************/ } else if((cursor.anchor() <= startOfList) && (list.count() == 1)) { return true; } else { return false; } } } void KoChangeTracker::insertDeleteFragment(QTextCursor &cursor) { QTextDocument tempDoc; QTextCursor tempCursor(&tempDoc); bool deletedListItem = false; for (QTextBlock currentBlock = tempDoc.begin(); currentBlock != tempDoc.end(); currentBlock = currentBlock.next()) { //This condition is for the work-around for a Qt behaviour //Even if a delete ends at the end of a table, the fragment will have an empty block after the table //If such a block is detected then, just ignore it if ((currentBlock.next() == tempDoc.end()) && (currentBlock.text().length() == 0) && (QTextCursor(currentBlock.previous()).currentTable())) { continue; } tempCursor.setPosition(currentBlock.position()); QTextList *textList = tempCursor.currentList(); int outlineLevel = currentBlock.blockFormat().property(KoParagraphStyle::OutlineLevel).toInt(); KoList *currentList = KoTextDocument(cursor.document()).list(cursor.block()); int docOutlineLevel = cursor.block().blockFormat().property(KoParagraphStyle::OutlineLevel).toInt(); if (docOutlineLevel) { //Even though we got a list, it is actually a list for storing headings. So don't consider it currentList = 0; } QTextList *previousTextList = currentBlock.previous().isValid() ? QTextCursor(currentBlock.previous()).currentList():0; if (textList && previousTextList && (textList != previousTextList) && (KoList::level(currentBlock) == KoList::level(currentBlock.previous()))) { //Even though we are already in a list, the QTextList* of the current block is differnt from that of the previous block //Also the levels of the list-items ( previous and current ) are the same. //This can happen only when two lists are merged together without any intermediate content. //So we need to create a new list. currentList = 0; } if (textList) { if (deletedListItem && currentBlock != tempDoc.begin()) { // Found a deleted list item in the fragment. So insert a new list-item int deletedListItemLevel = KoList::level(currentBlock); if (!(QTextCursor(currentBlock.previous()).currentTable())) { cursor.insertBlock(currentBlock.blockFormat(), currentBlock.charFormat()); } else { cursor.mergeBlockFormat(currentBlock.blockFormat()); } if(!currentList) { if (!outlineLevel) { //This happens when a part of a paragraph and a succeeding list-item are deleted together //So go to the next block and insert it in the list there. QTextCursor tmp(cursor); tmp.setPosition(tmp.block().next().position()); currentList = KoTextDocument(tmp.document()).list(tmp.block()); } else { // This is a heading. So find the KoList for heading and add the block there KoList *headingList = KoTextDocument(cursor.document()).headingList(); currentList = headingList; } } currentList->add(cursor.block(), deletedListItemLevel); } } else if (tempCursor.currentTable()) { QTextTable *deletedTable = tempCursor.currentTable(); int numRows = deletedTable->rows(); int numColumns = deletedTable->columns(); QTextTable *insertedTable = cursor.insertTable(numRows, numColumns, deletedTable->format()); for (int i=0; icellAt(i,j).firstCursorPosition().position()); tempCursor.setPosition(deletedTable->cellAt(i,j).lastCursorPosition().position(), QTextCursor::KeepAnchor); insertedTable->cellAt(i,j).setFormat(deletedTable->cellAt(i,j).format().toTableCellFormat()); cursor.setPosition(insertedTable->cellAt(i,j).firstCursorPosition().position()); cursor.insertFragment(tempCursor.selection()); } } tempCursor.setPosition(deletedTable->cellAt(numRows-1,numColumns-1).lastCursorPosition().position()); currentBlock = tempCursor.block(); //Move the cursor outside of table cursor.setPosition(cursor.position() + 1); continue; } else { // This block does not contain a list. So no special work here. if ((currentBlock != tempDoc.begin()) && !(QTextCursor(currentBlock.previous()).currentTable())) { cursor.insertBlock(currentBlock.blockFormat(), currentBlock.charFormat()); } if (QTextCursor(currentBlock.previous()).currentTable()) { cursor.mergeBlockFormat(currentBlock.blockFormat()); } } /********************************************************************************************************************/ /*This section of code is a work-around for a bug in the Qt. This work-around is safe. If and when the bug is fixed */ /*the if condition would never be true and the code would never get executed */ /********************************************************************************************************************/ if ((KoList::level(cursor.block()) != KoList::level(currentBlock)) && currentBlock.text().length()) { if (!currentList) { QTextCursor tmp(cursor); tmp.setPosition(tmp.block().previous().position()); currentList = KoTextDocument(tmp.document()).list(tmp.block()); } currentList->add(cursor.block(), KoList::level(currentBlock)); } /********************************************************************************************************************/ // Finally insert all the contents of the block into the main document. QTextBlock::iterator it; for (it = currentBlock.begin(); !(it.atEnd()); ++it) { QTextFragment currentFragment = it.fragment(); if (currentFragment.isValid()) { cursor.insertText(currentFragment.text(), currentFragment.charFormat()); } } } } int KoChangeTracker::fragmentLength(const QTextDocumentFragment &fragment) { QTextDocument tempDoc; QTextCursor tempCursor(&tempDoc); tempCursor.insertFragment(fragment); int length = 0; bool deletedListItem = false; for (QTextBlock currentBlock = tempDoc.begin(); currentBlock != tempDoc.end(); currentBlock = currentBlock.next()) { tempCursor.setPosition(currentBlock.position()); if (tempCursor.currentList()) { if (currentBlock != tempDoc.begin() && deletedListItem) length += 1; //For the Block separator } else if (tempCursor.currentTable()) { QTextTable *deletedTable = tempCursor.currentTable(); int numRows = deletedTable->rows(); int numColumns = deletedTable->columns(); for (int i=0; icellAt(i,j).lastCursorPosition().position() - deletedTable->cellAt(i,j).firstCursorPosition().position()); } } tempCursor.setPosition(deletedTable->cellAt(numRows-1,numColumns-1).lastCursorPosition().position()); currentBlock = tempCursor.block(); length += 1; continue; } else { if ((currentBlock != tempDoc.begin()) && !(QTextCursor(currentBlock.previous()).currentTable())) length += 1; //For the Block Separator } QTextBlock::iterator it; for (it = currentBlock.begin(); !(it.atEnd()); ++it) { QTextFragment currentFragment = it.fragment(); if (currentFragment.isValid()) length += currentFragment.text().length(); } } return length; } diff --git a/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp b/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp index 0f4e92a38a..a78a2c79d2 100644 --- a/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp +++ b/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp @@ -1,1143 +1,1148 @@ /* * Copyright (c) 2010 Boudewijn Rempt * Copyright (c) 2014 Denis Kuplyakov * * 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 "KoTextWriter_p.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 "TextDebug.h" #include #include // A convenience function to get a listId from a list-format static KoListStyle::ListIdType ListId(const QTextListFormat &format) { KoListStyle::ListIdType listId; if (sizeof(KoListStyle::ListIdType) == sizeof(uint)) listId = format.property(KoListStyle::ListId).toUInt(); else listId = format.property(KoListStyle::ListId).toULongLong(); return listId; } typedef QPair Attribute; KoTextWriter::Private::Private(KoShapeSavingContext &context) : rdfData(0) , sharedData(0) , styleManager(0) , document(0) , writer(0) , context(context) { currentPairedInlineObjectsStack = new QStack(); writer = &context.xmlWriter(); } void KoTextWriter::Private::writeBlocks(QTextDocument *document, int from, int to, QHash &listStyles, QTextTable *currentTable, QTextList *currentList) { pairedInlineObjectsStackStack.push(currentPairedInlineObjectsStack); currentPairedInlineObjectsStack = new QStack(); QTextBlock block = document->findBlock(from); // Here we are going to detect all sections that // are positioned entirely inside selection. // They will stay untouched, and others will be omitted. // So we are using stack to detect them, by going through // the selection and finding open/close pairs. QSet entireWithinSectionNames; QStack sectionNamesStack; QTextCursor cur(document); cur.setPosition(from); while (to == -1 || cur.position() <= to) { if (cur.block().position() >= from) { // Begin of the block is inside selection. foreach (const KoSection *sec, KoSectionUtils::sectionStartings(cur.blockFormat())) { sectionNamesStack.push_back(sec->name()); } } if (to == -1 || cur.block().position() + cur.block().length() - 1 <= to) { // End of the block is inside selection. foreach (const KoSectionEnd *sec, KoSectionUtils::sectionEndings(cur.blockFormat())) { if (!sectionNamesStack.empty() && sectionNamesStack.top() == sec->name()) { sectionNamesStack.pop(); entireWithinSectionNames.insert(sec->name()); } } } if (!KoSectionUtils::getNextBlock(cur)) { break; } } while (block.isValid() && ((to == -1) || (block.position() <= to))) { QTextCursor cursor(block); int frameType = cursor.currentFrame()->format().intProperty(KoText::SubFrameType); if (frameType == KoText::AuxillaryFrameType) { break; // we've reached the "end" (end/footnotes saved by themselves) // note how NoteFrameType passes through here so the notes can // call writeBlocks to save their contents. } QTextBlockFormat format = block.blockFormat(); foreach (const KoSection *section, KoSectionUtils::sectionStartings(format)) { // We are writing in only sections, that are completely inside selection. if (entireWithinSectionNames.contains(section->name())) { section->saveOdf(context); } } if (format.hasProperty(KoParagraphStyle::HiddenByTable)) { block = block.next(); continue; } if (format.hasProperty(KoParagraphStyle::TableOfContentsData)) { saveTableOfContents(document, listStyles, block); block = block.next(); continue; } if (format.hasProperty(KoParagraphStyle::BibliographyData)) { saveBibliography(document, listStyles, block); block = block.next(); continue; } if (cursor.currentTable() && cursor.currentTable() != currentTable) { // Call the code to save the table.... saveTable(cursor.currentTable(), listStyles, from, to); // We skip to the end of the table. block = cursor.currentTable()->lastCursorPosition().block(); block = block.next(); continue; } if (cursor.currentList() && cursor.currentList() != currentList) { int previousBlockNumber = block.blockNumber(); block = saveList(block, listStyles, 1, currentTable); int blockNumberToProcess = block.blockNumber(); if (blockNumberToProcess != previousBlockNumber) continue; } saveParagraph(block, from, to); foreach (const KoSectionEnd *sectionEnd, KoSectionUtils::sectionEndings(format)) { // We are writing in only sections, that are completely inside selection. if (entireWithinSectionNames.contains(sectionEnd->name())) { sectionEnd->saveOdf(context); } } block = block.next(); } // while Q_ASSERT(!pairedInlineObjectsStackStack.isEmpty()); delete currentPairedInlineObjectsStack; currentPairedInlineObjectsStack = pairedInlineObjectsStackStack.pop(); } QHash KoTextWriter::Private::saveListStyles(QTextBlock block, int to) { QHash generatedLists; QHash listStyles; for (;block.isValid() && ((to == -1) || (block.position() < to)); block = block.next()) { QTextList *textList = block.textList(); if (!textList) continue; KoListStyle::ListIdType listId = ListId(textList->format()); if (KoList *list = KoTextDocument(document).list(listId)) { if (generatedLists.contains(list)) { if (!listStyles.contains(textList)) listStyles.insert(textList, generatedLists.value(list)); continue; } KoListStyle *listStyle = list->style(); if (!listStyle || listStyle->isOulineStyle()) continue; bool automatic = listStyle->styleId() == 0; KoGenStyle style(automatic ? KoGenStyle::ListAutoStyle : KoGenStyle::ListStyle); if (automatic && context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); listStyle->saveOdf(style, context); QString generatedName = context.mainStyles().insert(style, listStyle->name(), listStyle->isNumberingStyle() ? KoGenStyles::AllowDuplicates : KoGenStyles::DontAddNumberToName); listStyles[textList] = generatedName; generatedLists.insert(list, generatedName); } else { if (listStyles.contains(textList)) continue; KoListLevelProperties llp = KoListLevelProperties::fromTextList(textList); KoGenStyle style(KoGenStyle::ListAutoStyle); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); KoListStyle listStyle; listStyle.setLevelProperties(llp); if (listStyle.isOulineStyle()) { continue; } listStyle.saveOdf(style, context); QString generatedName = context.mainStyles().insert(style, listStyle.name()); listStyles[textList] = generatedName; } } return listStyles; } //---------------------------- PRIVATE ----------------------------------------------------------- void KoTextWriter::Private::openTagRegion(ElementType elementType, TagInformation& tagInformation) { //debugText << "tag:" << tagInformation.name() << openedTagStack.size(); if (tagInformation.name()) { writer->startElement(tagInformation.name(), elementType != ParagraphOrHeader); foreach (const Attribute &attribute, tagInformation.attributes()) { writer->addAttribute(attribute.first.toLocal8Bit(), attribute.second); } } openedTagStack.push(tagInformation.name()); //debugText << "stack" << openedTagStack.size(); } void KoTextWriter::Private::closeTagRegion() { // the tag needs to be closed even if there is no change tracking //debugText << "stack" << openedTagStack.size(); const char *tagName = openedTagStack.pop(); //debugText << "tag:" << tagName << openedTagStack.size(); if (tagName) { writer->endElement(); // close the tag } } QString KoTextWriter::Private::saveParagraphStyle(const QTextBlock &block) { return KoTextWriter::saveParagraphStyle(block, styleManager, context); } QString KoTextWriter::Private::saveParagraphStyle(const QTextBlockFormat &blockFormat, const QTextCharFormat &charFormat) { return KoTextWriter::saveParagraphStyle(blockFormat, charFormat, styleManager, context); } QString KoTextWriter::Private::saveCharacterStyle(const QTextCharFormat &charFormat, const QTextCharFormat &blockCharFormat) { KoCharacterStyle *defaultCharStyle = styleManager->defaultCharacterStyle(); KoCharacterStyle *originalCharStyle = styleManager->characterStyle(charFormat.intProperty(KoCharacterStyle::StyleId)); if (!originalCharStyle) originalCharStyle = defaultCharStyle; QString generatedName; QString displayName = originalCharStyle->name(); QString internalName = QString(QUrl::toPercentEncoding(displayName, "", " ")).replace('%', '_'); KoCharacterStyle *autoStyle = originalCharStyle->autoStyle(charFormat, blockCharFormat); if (autoStyle->isEmpty()) { // This is the real, unmodified character style. if (originalCharStyle != defaultCharStyle) { KoGenStyle style(KoGenStyle::TextStyle, "text"); originalCharStyle->saveOdf(style); generatedName = context.mainStyles().insert(style, internalName, KoGenStyles::DontAddNumberToName); } } else { // There are manual changes... We'll have to store them then KoGenStyle style(KoGenStyle::TextAutoStyle, "text", originalCharStyle != defaultCharStyle ? internalName : "" /*parent*/); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); autoStyle->saveOdf(style); generatedName = context.mainStyles().insert(style, "T"); } delete autoStyle; return generatedName; } QString KoTextWriter::Private::saveTableStyle(const QTextTable& table) { KoTableStyle *originalTableStyle = styleManager->tableStyle(table.format().intProperty(KoTableStyle::StyleId)); QString generatedName; QString internalName; if (originalTableStyle) { internalName = QString(QUrl::toPercentEncoding(originalTableStyle->name(), "", " ")).replace('%', '_'); } KoTableStyle tableStyle(table.format()); if ((originalTableStyle) && (*originalTableStyle == tableStyle)) { // This is the real unmodified table style KoGenStyle style(KoGenStyle::TableStyle, "table"); originalTableStyle->saveOdf(style); generatedName = context.mainStyles().insert(style, internalName, KoGenStyles::DontAddNumberToName); } else { // There are manual changes... We'll have to store them then KoGenStyle style(KoGenStyle::TableAutoStyle, "table", internalName); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); if (originalTableStyle) tableStyle.removeDuplicates(*originalTableStyle); if (!tableStyle.isEmpty()) { tableStyle.saveOdf(style); generatedName = context.mainStyles().insert(style, "Table"); } } return generatedName; } QString KoTextWriter::Private::saveTableColumnStyle(const KoTableColumnStyle& tableColumnStyle, int columnNumber, const QString& tableStyleName) { // 26*26 columns should be enough for everyone QString columnName = QChar('A' + int(columnNumber % 26)); if (columnNumber > 25) columnName.prepend(QChar('A' + int(columnNumber/26))); QString generatedName = tableStyleName + '.' + columnName; KoGenStyle style(KoGenStyle::TableColumnAutoStyle, "table-column"); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); tableColumnStyle.saveOdf(style); generatedName = context.mainStyles().insert(style, generatedName, KoGenStyles::DontAddNumberToName); return generatedName; } QString KoTextWriter::Private::saveTableRowStyle(const KoTableRowStyle& tableRowStyle, int rowNumber, const QString& tableStyleName) { QString generatedName = tableStyleName + '.' + QString::number(rowNumber + 1); KoGenStyle style(KoGenStyle::TableRowAutoStyle, "table-row"); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); tableRowStyle.saveOdf(style); generatedName = context.mainStyles().insert(style, generatedName, KoGenStyles::DontAddNumberToName); return generatedName; } QString KoTextWriter::Private::saveTableCellStyle(const QTextTableCellFormat& cellFormat, int columnNumber, const QString& tableStyleName) { // 26*26 columns should be enough for everyone QString columnName = QChar('A' + int(columnNumber % 26)); if (columnNumber > 25) columnName.prepend(QChar('A' + int(columnNumber/26))); QString generatedName = tableStyleName + '.' + columnName; KoGenStyle style(KoGenStyle::TableCellAutoStyle, "table-cell"); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); KoTableCellStyle cellStyle(cellFormat); cellStyle.saveOdf(style, context); generatedName = context.mainStyles().insert(style, generatedName); return generatedName; } void KoTextWriter::Private::saveInlineRdf(KoTextInlineRdf* rdf, TagInformation* tagInfos) { +#ifndef KOXML_USE_QDOM QBuffer rdfXmlData; KoXmlWriter rdfXmlWriter(&rdfXmlData); rdfXmlWriter.startDocument("rdf"); rdfXmlWriter.startElement("rdf"); rdf->saveOdf(context, &rdfXmlWriter); rdfXmlWriter.endElement(); rdfXmlWriter.endDocument(); KoXmlDocument xmlReader; xmlReader.setContent(rdfXmlData.data(), true); KoXmlElement mainElement = xmlReader.documentElement(); foreach (const Attribute &attributeNameNS, mainElement.attributeFullNames()) { QString attributeName = QString("%1:%2").arg(KoXmlNS::nsURI2NS(attributeNameNS.first)) .arg(attributeNameNS.second); if (attributeName.startsWith(':')) attributeName.prepend("xml"); tagInfos->addAttribute(attributeName, mainElement.attribute(attributeNameNS.second)); } +#endif } /* Note on saving textranges: Start and end tags of textranges can appear on cursor positions in a text block. in front of the first text element, between the elements, or behind the last. A textblock is composed of no, one or many text fragments. If there is no fragment at all, the only possible cursor position is 0 (relative to the begin of the block). Example: ([] marks a block, {} a fragment) Three blocks, first with text fragments {AB} {C}, second empty, last with {DEF}. Possible positions are: [|{A|B}|{C}|] [|] [|{D|E|F}|] Start tags are ideally written in front of the content they are tagging, not behind the previous content. That way tags which are at the very begin of the complete document do not need special handling. End tags are ideally written directly behind the content, and not in front of the next content. That way end tags which are at the end of the complete document do not need special handling. Next there is the case of start tags which are at the final position of a text block: the content they belong to includes the block end/border, so they need to be written at the place of the last position. Then there is the case of end tags at the first position of a text block: the content they belong to includes the block start/border, so they need to be written at the place of the first position. Example: (< marks a start tag, > marks an end tag) [|>{}|{}|<] [|><] [|>{||}|<] */ void KoTextWriter::Private::saveParagraph(const QTextBlock &block, int from, int to) { QTextCursor cursor(block); QTextBlockFormat blockFormat = block.blockFormat(); const int outlineLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); TagInformation blockTagInformation; if (outlineLevel > 0) { blockTagInformation.setTagName("text:h"); blockTagInformation.addAttribute("text:outline-level", outlineLevel); if (blockFormat.boolProperty(KoParagraphStyle::IsListHeader) || blockFormat.boolProperty(KoParagraphStyle::UnnumberedListItem)) { blockTagInformation.addAttribute("text:is-list-header", "true"); } } else { blockTagInformation.setTagName("text:p"); } openTagRegion(KoTextWriter::Private::ParagraphOrHeader, blockTagInformation); QString styleName = saveParagraphStyle(block); if (!styleName.isEmpty()) writer->addAttribute("text:style-name", styleName); KoElementReference xmlid; xmlid.invalidate(); QTextBlock currentBlock = block; KoTextBlockData blockData(currentBlock); if (blockData.saveXmlID()) { xmlid = context.xmlid(&blockData); xmlid.saveOdf(writer, KoElementReference::TextId); } // Write the fragments and their formats QTextCharFormat blockCharFormat = cursor.blockCharFormat(); QTextCharFormat previousCharFormat; QTextBlock::iterator it; if (KoTextInlineRdf* inlineRdf = KoTextInlineRdf::tryToGetInlineRdf(blockCharFormat)) { // Write xml:id here for Rdf debugText << "have inline rdf xmlid:" << inlineRdf->xmlId() << "active xml id" << xmlid.toString(); inlineRdf->saveOdf(context, writer, xmlid); } const KoTextRangeManager *textRangeManager = KoTextDocument(block.document()).textRangeManager(); if (textRangeManager) { // write tags for ranges which end at the first position of the block const QHash endingTextRangesAtStart = textRangeManager->textRangesChangingWithin(block.document(), block.position(), block.position(), globalFrom, globalTo); foreach (const KoTextRange *range, endingTextRangesAtStart) { range->saveOdf(context, block.position(), KoTextRange::EndTag); } } QString previousFragmentLink; // stores the end position of the last fragment, is position of the block without any fragment at all int lastEndPosition = block.position(); for (it = block.begin(); !(it.atEnd()); ++it) { QTextFragment currentFragment = it.fragment(); const int fragmentStart = currentFragment.position(); const int fragmentEnd = fragmentStart + currentFragment.length(); if (to != -1 && fragmentStart >= to) break; if (currentFragment.isValid()) { QTextCharFormat charFormat = currentFragment.charFormat(); if ((!previousFragmentLink.isEmpty()) && (charFormat.anchorHref() != previousFragmentLink || !charFormat.isAnchor())) { // Close the current text:a closeTagRegion(); previousFragmentLink.clear(); } if (charFormat.isAnchor() && charFormat.anchorHref() != previousFragmentLink) { // Open a text:a previousFragmentLink = charFormat.anchorHref(); TagInformation linkTagInformation; if (charFormat.intProperty(KoCharacterStyle::AnchorType) == KoCharacterStyle::Bookmark) { linkTagInformation.setTagName("text:bookmark-ref"); QString href = previousFragmentLink.right(previousFragmentLink.size()-1); linkTagInformation.addAttribute("text:ref-name", href); //linkTagInformation.addAttribute("text:ref-format", add the style of the ref here); } else { linkTagInformation.setTagName("text:a"); linkTagInformation.addAttribute("xlink:type", "simple"); linkTagInformation.addAttribute("xlink:href", charFormat.anchorHref()); } if (KoTextInlineRdf* inlineRdf = KoTextInlineRdf::tryToGetInlineRdf(charFormat)) { // Write xml:id here for Rdf debugText << "have inline rdf xmlid:" << inlineRdf->xmlId(); saveInlineRdf(inlineRdf, &linkTagInformation); } openTagRegion(KoTextWriter::Private::Span, linkTagInformation); } KoInlineTextObjectManager *textObjectManager = KoTextDocument(document).inlineTextObjectManager(); KoInlineObject *inlineObject = textObjectManager ? textObjectManager->inlineTextObject(charFormat) : 0; // If we are in an inline object if (currentFragment.length() == 1 && inlineObject && currentFragment.text()[0].unicode() == QChar::ObjectReplacementCharacter) { bool saveInlineObject = true; if (KoTextMeta* z = dynamic_cast(inlineObject)) { if (z->position() < from) { // // This starts before the selection, default // to not saving it with special cases to allow saving // saveInlineObject = false; if (z->type() == KoTextMeta::StartBookmark) { if (z->endBookmark()->position() > from) { // // They have selected something starting after the // opening but before the // saveInlineObject = true; } } } } // get all text ranges which start before this inline object // or end directly after it (+1 to last position for that) const QHash textRanges = textRangeManager ? textRangeManager->textRangesChangingWithin(block.document(), currentFragment.position(), currentFragment.position()+1, globalFrom, (globalTo==-1)?-1:globalTo+1) : QHash(); // get all text ranges which start before this const QList textRangesBefore = textRanges.values(currentFragment.position()); // write tags for ranges which start before this content or at positioned at it foreach (const KoTextRange *range, textRangesBefore) { range->saveOdf(context, currentFragment.position(), KoTextRange::StartTag); } bool saveSpan = dynamic_cast(inlineObject) != 0; if (saveSpan) { QString styleName = saveCharacterStyle(charFormat, blockCharFormat); if (!styleName.isEmpty()) { writer->startElement("text:span", false); writer->addAttribute("text:style-name", styleName); } else { saveSpan = false; } } if (saveInlineObject) { inlineObject->saveOdf(context); } if (saveSpan) { writer->endElement(); } // write tags for ranges which end after this inline object const QList textRangesAfter = textRanges.values(currentFragment.position()+1); foreach (const KoTextRange *range, textRangesAfter) { range->saveOdf(context, currentFragment.position()+1, KoTextRange::EndTag); } // // Track the end marker for matched pairs so we produce valid // ODF // if (KoTextMeta* z = dynamic_cast(inlineObject)) { debugText << "found kometa, type:" << z->type(); if (z->type() == KoTextMeta::StartBookmark) currentPairedInlineObjectsStack->push(z->endBookmark()); if (z->type() == KoTextMeta::EndBookmark && !currentPairedInlineObjectsStack->isEmpty()) currentPairedInlineObjectsStack->pop(); }/* else if (KoBookmark* z = dynamic_cast(inlineObject)) { if (z->type() == KoBookmark::StartBookmark) currentPairedInlineObjectsStack->push(z->endBookmark()); if (z->type() == KoBookmark::EndBookmark && !currentPairedInlineObjectsStack->isEmpty()) currentPairedInlineObjectsStack->pop(); }*/ } else { // Normal block, easier to handle QString styleName = saveCharacterStyle(charFormat, blockCharFormat); TagInformation fragmentTagInformation; if (!styleName.isEmpty() /*&& !identical*/) { fragmentTagInformation.setTagName("text:span"); fragmentTagInformation.addAttribute("text:style-name", styleName); } openTagRegion(KoTextWriter::Private::Span, fragmentTagInformation); QString text = currentFragment.text(); int spanFrom = fragmentStart >= from ? fragmentStart : from; int spanTo = to == -1 ? fragmentEnd : (fragmentEnd > to ? to : fragmentEnd); // get all text ranges which change within this span // or end directly after it (+1 to last position to include those) const QHash textRanges = textRangeManager ? textRangeManager->textRangesChangingWithin(block.document(), spanFrom, spanTo, globalFrom, (globalTo==-1)?-1:globalTo+1) : QHash(); // avoid mid, if possible if (spanFrom != fragmentStart || spanTo != fragmentEnd || !textRanges.isEmpty()) { if (textRanges.isEmpty()) { writer->addTextSpan(text.mid(spanFrom - fragmentStart, spanTo - spanFrom)); } else { // split the fragment into subspans at the points of range starts/ends QList subSpanTos = textRanges.uniqueKeys(); qSort(subSpanTos); // ensure last subSpanTo to be at the end if (subSpanTos.last() != spanTo) { subSpanTos.append(spanTo); } // spanFrom should not need to be included if (subSpanTos.first() == spanFrom) { subSpanTos.removeOne(spanFrom); } int subSpanFrom = spanFrom; // for all subspans foreach (int subSpanTo, subSpanTos) { // write tags for text ranges which start before this subspan or are positioned at it const QList textRangesStartingBefore = textRanges.values(subSpanFrom); foreach (const KoTextRange *range, textRangesStartingBefore) { range->saveOdf(context, subSpanFrom, KoTextRange::StartTag); } // write subspan content writer->addTextSpan(text.mid(subSpanFrom - fragmentStart, subSpanTo - subSpanFrom)); // write tags for text ranges which end behind this subspan const QList textRangesEndingBehind = textRanges.values(subSpanTo); foreach (const KoTextRange *range, textRangesEndingBehind) { range->saveOdf(context, subSpanTo, KoTextRange::EndTag); } subSpanFrom = subSpanTo; } } } else { writer->addTextSpan(text); } closeTagRegion(); } // if (inlineObject) previousCharFormat = charFormat; lastEndPosition = fragmentEnd; } } if (!previousFragmentLink.isEmpty()) { writer->endElement(); } if (it.atEnd() && textRangeManager && ((to == -1) || (lastEndPosition <= to))) { // write tags for ranges which start at the last position of the block, // i.e. at the position after the last (text) fragment const QHash startingTextRangesAtEnd = textRangeManager->textRangesChangingWithin(block.document(), lastEndPosition, lastEndPosition, globalFrom, globalTo); foreach (const KoTextRange *range, startingTextRangesAtEnd) { range->saveOdf(context, lastEndPosition, KoTextRange::StartTag); } } QString text = block.text(); if (text.length() == 0 || text.at(text.length()-1) == QChar(0x2028)) { if (block.blockFormat().hasProperty(KoParagraphStyle::EndCharStyle)) { QVariant v = block.blockFormat().property(KoParagraphStyle::EndCharStyle); QSharedPointer endCharStyle = v.value< QSharedPointer >(); if (!endCharStyle.isNull()) { QTextCharFormat charFormat; endCharStyle->applyStyle(charFormat); QString styleName = saveCharacterStyle(charFormat, blockCharFormat); if (!styleName.isEmpty()) { writer->startElement("text:span", false); writer->addAttribute("text:style-name", styleName); writer->endElement(); } } } } if (to !=-1 && to < block.position() + block.length()) { foreach (KoInlineObject* inlineObject, *currentPairedInlineObjectsStack) { inlineObject->saveOdf(context); } } closeTagRegion(); } void KoTextWriter::Private::saveTable(QTextTable *table, QHash &listStyles, int from, int to) { KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(table); int numberHeadingRows = table->format().property(KoTableStyle::NumberHeadingRows).toInt(); TagInformation tableTagInformation; QString tableStyleName = saveTableStyle(*table); tableTagInformation.setTagName("table:table"); tableTagInformation.addAttribute("table:style-name", tableStyleName); if (table->format().boolProperty(KoTableStyle::TableIsProtected)) { tableTagInformation.addAttribute("table:protected", "true"); } if (table->format().hasProperty(KoTableStyle::TableTemplate)) { tableTagInformation.addAttribute("table:template-name", sharedData->styleName(table->format().intProperty(KoTableStyle::TableTemplate))); } if (table->format().boolProperty(KoTableStyle::UseBandingColumnStyles)) { tableTagInformation.addAttribute("table:use-banding-columns-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseBandingRowStyles)) { tableTagInformation.addAttribute("table:use-banding-rows-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseFirstColumnStyles)) { tableTagInformation.addAttribute("table:use-first-column-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseFirstRowStyles)) { tableTagInformation.addAttribute("table:use-first-row-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseLastColumnStyles)) { tableTagInformation.addAttribute("table:use-last-column-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseLastRowStyles)) { tableTagInformation.addAttribute("table:use-last-row-styles", "true"); } int firstColumn = 0; int lastColumn = table->columns() -1; int firstRow = 0; int lastRow = table->rows() -1; if (to != -1 && from >= table->firstPosition() && to <= table->lastPosition()) { firstColumn = table->cellAt(from).column(); firstRow = table->cellAt(from).row(); lastColumn = table->cellAt(to).column(); lastRow = table->cellAt(to).row(); if (firstColumn == lastColumn && firstRow == lastRow && from >= table->firstPosition()) { // we only selected something inside a single cell so don't save a table writeBlocks(table->document(), from, to, listStyles, table); return; } } openTagRegion(KoTextWriter::Private::Table, tableTagInformation); for (int c = firstColumn ; c <= lastColumn; c++) { KoTableColumnStyle columnStyle = tcarManager.columnStyle(c); int repetition = 0; for (; repetition <= (lastColumn - c) ; repetition++) { if (columnStyle != tcarManager.columnStyle(c + repetition + 1)) break; } TagInformation tableColumnInformation; tableColumnInformation.setTagName("table:table-column"); QString columnStyleName = saveTableColumnStyle(columnStyle, c, tableStyleName); tableColumnInformation.addAttribute("table:style-name", columnStyleName); if (repetition > 0) tableColumnInformation.addAttribute("table:number-columns-repeated", repetition + 1); openTagRegion(KoTextWriter::Private::TableColumn, tableColumnInformation); closeTagRegion(); c += repetition; } if (numberHeadingRows) writer->startElement("table:table-header-rows"); // TODO make work for copying part of table that has header rows - copy header rows additionally or not ? for (int r = firstRow; r <= lastRow; r++) { TagInformation tableRowInformation; tableRowInformation.setTagName("table:table-row"); KoTableRowStyle rowStyle = tcarManager.rowStyle(r); if (!rowStyle.isEmpty()) { QString rowStyleName = saveTableRowStyle(rowStyle, r, tableStyleName); tableRowInformation.addAttribute("table:style-name", rowStyleName); } openTagRegion(KoTextWriter::Private::TableRow, tableRowInformation); for (int c = firstColumn; c <= lastColumn; c++) { QTextTableCell cell = table->cellAt(r, c); TagInformation tableCellInformation; if ((cell.row() == r) && (cell.column() == c)) { tableCellInformation.setTagName("table:table-cell"); if (cell.rowSpan() > 1) tableCellInformation.addAttribute("table:number-rows-spanned", cell.rowSpan()); if (cell.columnSpan() > 1) tableCellInformation.addAttribute("table:number-columns-spanned", cell.columnSpan()); if (cell.format().boolProperty(KoTableCellStyle::CellIsProtected)) { tableCellInformation.addAttribute("table:protected", "true"); } // Save the Rdf for the table cell QTextTableCellFormat cellFormat = cell.format().toTableCellFormat(); QVariant v = cellFormat.property(KoTableCellStyle::InlineRdf); if (KoTextInlineRdf* inlineRdf = v.value()) { inlineRdf->saveOdf(context, writer); } QString cellStyleName = saveTableCellStyle(cellFormat, c, tableStyleName); tableCellInformation.addAttribute("table:style-name", cellStyleName); openTagRegion(KoTextWriter::Private::TableCell, tableCellInformation); writeBlocks(table->document(), cell.firstPosition(), cell.lastPosition(), listStyles, table); } else { tableCellInformation.setTagName("table:covered-table-cell"); if (cell.format().boolProperty(KoTableCellStyle::CellIsProtected)) { tableCellInformation.addAttribute("table:protected", "true"); } openTagRegion(KoTextWriter::Private::TableCell, tableCellInformation); } closeTagRegion(); } closeTagRegion(); if (r + 1 == numberHeadingRows) { writer->endElement(); // table:table-header-rows writer->startElement("table:table-rows"); } } if (numberHeadingRows) writer->endElement(); // table:table-rows closeTagRegion(); } void KoTextWriter::Private::saveTableOfContents(QTextDocument *document, QHash &listStyles, QTextBlock toc) { Q_UNUSED(document); writer->startElement("text:table-of-content"); KoTableOfContentsGeneratorInfo *info = toc.blockFormat().property(KoParagraphStyle::TableOfContentsData).value(); QTextDocument *tocDocument = toc.blockFormat().property(KoParagraphStyle::GeneratedDocument).value(); if (!info->m_styleName.isNull()) { writer->addAttribute("text:style-name",info->m_styleName); } writer->addAttribute("text:name",info->m_name); info->saveOdf(writer); writer->startElement("text:index-body"); // write the title (one p block) QTextCursor localBlock = tocDocument->rootFrame()->firstCursorPosition(); localBlock.movePosition(QTextCursor::NextBlock); int endTitle = localBlock.position(); writer->startElement("text:index-title"); writer->addAttribute("text:name", QString("%1_Head").arg(info->m_name)); writeBlocks(tocDocument, 0, endTitle, listStyles); writer->endElement(); // text:index-title writeBlocks(tocDocument, endTitle, -1, listStyles); writer->endElement(); // table:index-body writer->endElement(); // table:table-of-content } void KoTextWriter::Private::saveBibliography(QTextDocument *document, QHash &listStyles, QTextBlock bib) { Q_UNUSED(document); writer->startElement("text:bibliography"); KoBibliographyInfo *info = bib.blockFormat().property(KoParagraphStyle::BibliographyData).value(); QTextDocument *bibDocument = bib.blockFormat().property(KoParagraphStyle::GeneratedDocument).value(); if (!info->m_styleName.isNull()) { writer->addAttribute("text:style-name",info->m_styleName); } writer->addAttribute("text:name",info->m_name); info->saveOdf(writer); writer->startElement("text:index-body"); // write the title (one p block) QTextCursor localBlock = bibDocument->rootFrame()->firstCursorPosition(); localBlock.movePosition(QTextCursor::NextBlock); int endTitle = localBlock.position(); writer->startElement("text:index-title"); writeBlocks(bibDocument, 0, endTitle, listStyles); writer->endElement(); // text:index-title writeBlocks(bibDocument, endTitle, -1, listStyles); writer->endElement(); // table:index-body writer->endElement(); // table:bibliography } QTextBlock& KoTextWriter::Private::saveList(QTextBlock &block, QHash &listStyles, int level, QTextTable *currentTable) { QTextList *textList, *topLevelTextList; topLevelTextList = textList = block.textList(); int headingLevel = 0, numberedParagraphLevel = 0; QTextBlockFormat blockFormat = block.blockFormat(); headingLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); numberedParagraphLevel = blockFormat.intProperty(KoParagraphStyle::ListLevel); KoTextDocument textDocument(block.document()); KoList *list = textDocument.list(block); int topListLevel = KoList::level(block); bool listStarted = false; if (!headingLevel && !numberedParagraphLevel) { listStarted = true; TagInformation listTagInformation; listTagInformation.setTagName("text:list"); listTagInformation.addAttribute("text:style-name", listStyles[textList]); if (list && listXmlIds.contains(list->listContinuedFrom())) { listTagInformation.addAttribute("text:continue-list", listXmlIds.value(list->listContinuedFrom())); } QString listXmlId = QString("list-%1").arg(createXmlId()); listTagInformation.addAttribute("xml:id", listXmlId); if (! listXmlIds.contains(list)) { listXmlIds.insert(list, listXmlId); } openTagRegion(KoTextWriter::Private::List, listTagInformation); } if (!headingLevel) { do { if (numberedParagraphLevel) { TagInformation paraTagInformation; paraTagInformation.setTagName("text:numbered-paragraph"); paraTagInformation.addAttribute("text:level", numberedParagraphLevel); paraTagInformation.addAttribute("text:style-name", listStyles.value(textList)); QString listId = numberedParagraphListIds.value(list, QString("list-%1").arg(createXmlId())); numberedParagraphListIds.insert(list, listId); paraTagInformation.addAttribute("text:list-id", listId); openTagRegion(KoTextWriter::Private::NumberedParagraph, paraTagInformation); writeBlocks(textDocument.document(), block.position(), block.position() + block.length() - 1, listStyles, currentTable, textList); closeTagRegion(); } else { const bool listHeader = blockFormat.boolProperty(KoParagraphStyle::IsListHeader)|| blockFormat.boolProperty(KoParagraphStyle::UnnumberedListItem); TagInformation listItemTagInformation; listItemTagInformation.setTagName(listHeader ? "text:list-header" : "text:list-item"); if (block.blockFormat().hasProperty(KoParagraphStyle::ListStartValue)) { int startValue = block.blockFormat().intProperty(KoParagraphStyle::ListStartValue); listItemTagInformation.addAttribute("text:start-value", startValue); } if (textList == topLevelTextList) { openTagRegion(KoTextWriter::Private::ListItem, listItemTagInformation); } else { // This is a sub-list. So check for a list-change openTagRegion(KoTextWriter::Private::List, listItemTagInformation); } if (KoListStyle::isNumberingStyle(textList->format().style())) { KoTextBlockData blockData(block); writer->startElement("text:number", false); writer->addTextSpan(blockData.counterText()); writer->endElement(); } if (topListLevel == level && textList == topLevelTextList) { writeBlocks(textDocument.document(), block.position(), block.position() + block.length() - 1, listStyles, currentTable, textList); // we are generating a text:list-item. Look forward and generate unnumbered list items. while (true) { QTextBlock nextBlock = block.next(); if (!nextBlock.textList() || !nextBlock.blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) break; block = nextBlock; saveParagraph(block, block.position(), block.position() + block.length() - 1); } } else { //This is a sub-list while (KoList::level(block) >= (level + 1) && !(headingLevel || numberedParagraphLevel)) { block = saveList(block, listStyles, level + 1, currentTable); blockFormat = block.blockFormat(); headingLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); numberedParagraphLevel = blockFormat.intProperty(KoParagraphStyle::ListLevel); } //saveList will return a block one-past the last block of the list. //Since we are doing a block.next() below, we need to go one back. block = block.previous(); } closeTagRegion(); } block = block.next(); blockFormat = block.blockFormat(); headingLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); numberedParagraphLevel = blockFormat.intProperty(KoParagraphStyle::ListLevel); textList = block.textList(); } while ((textDocument.list(block) == list) && (KoList::level(block) >= topListLevel)); } if (listStarted) { closeTagRegion(); } return block; } void KoTextWriter::Private::addNameSpaceDefinitions(QString &generatedXmlString) { //Generate the name-space definitions so that it can be parsed. Like what is office:text, office:delta etc QString nameSpaceDefinitions; QTextStream nameSpacesStream(&nameSpaceDefinitions); nameSpacesStream.setCodec("UTF-8"); nameSpacesStream << ""; generatedXmlString.prepend(nameSpaceDefinitions); generatedXmlString.append(""); } void KoTextWriter::Private::writeAttributes(QTextStream &outputXmlStream, KoXmlElement &element) { +#ifndef KOXML_USE_QDOM + QList > attributes = element.attributeFullNames(); foreach (const Attribute &attributeNamePair, attributes) { if (attributeNamePair.first == KoXmlNS::text) { outputXmlStream << " text:" << attributeNamePair.second << "="; outputXmlStream << "\"" << element.attributeNS(KoXmlNS::text, attributeNamePair.second) << "\""; } else { //To Be Added when needed } } +#endif } void KoTextWriter::Private::writeNode(QTextStream &outputXmlStream, KoXmlNode &node, bool writeOnlyChildren) { if (node.isText()) { outputXmlStream << node.toText().data(); } else if (node.isElement()) { KoXmlElement element = node.toElement(); - if ((element.localName() == "removed-content") && !element.childNodesCount()) { + if ((element.localName() == "removed-content") && !KoXml::childNodesCount(element)) { return; } if (!writeOnlyChildren) { outputXmlStream << "<" << element.prefix() << ":" << element.localName(); writeAttributes(outputXmlStream,element); outputXmlStream << ">"; } for (KoXmlNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) { writeNode(outputXmlStream, node); } if (!writeOnlyChildren) { outputXmlStream << ""; } } } QString KoTextWriter::Private::createXmlId() { QString uuid = QUuid::createUuid().toString(); uuid.remove('{'); uuid.remove('}'); return uuid; } diff --git a/plugins/flake/textshape/kotext/styles/KoTableCellStyle.cpp b/plugins/flake/textshape/kotext/styles/KoTableCellStyle.cpp index 0005a3f5e9..51ee414ddd 100644 --- a/plugins/flake/textshape/kotext/styles/KoTableCellStyle.cpp +++ b/plugins/flake/textshape/kotext/styles/KoTableCellStyle.cpp @@ -1,1057 +1,1057 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Roopesh Chander * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2009 KO GmbH * 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 "KoTableCellStyle.h" #include "KoTableCellStyle_p.h" #include #include #include #include #include "KoParagraphStyle.h" #include #include #include #include #include #include #include #include #include "TextDebug.h" #include KoTableCellStyle::RotationAlignment rotationAlignmentFromString(const QString& align) { if (align == "bottom") return KoTableCellStyle::RAlignBottom; if (align == "center") return KoTableCellStyle::RAlignCenter; if (align == "top") return KoTableCellStyle::RAlignTop; - + return KoTableCellStyle::RAlignNone; } QString rotationAlignmentToString(KoTableCellStyle::RotationAlignment align) { if (align == KoTableCellStyle::RAlignBottom) return "bottom"; if (align == KoTableCellStyle::RAlignTop) return "top"; if (align == KoTableCellStyle::RAlignCenter) return "center"; return "none"; } KoTableCellStylePrivate::KoTableCellStylePrivate() : paragraphStyle(0) , parentStyle(0) , next(0) { } KoTableCellStylePrivate::~KoTableCellStylePrivate() { } void KoTableCellStylePrivate::setProperty(int key, const QVariant &value) { stylesPrivate.add(key, value); } KoTableCellStyle::KoTableCellStyle(QObject *parent) : QObject(parent) , d_ptr(new KoTableCellStylePrivate) { Q_D(KoTableCellStyle); d->paragraphStyle = new KoParagraphStyle(this); } KoTableCellStyle::KoTableCellStyle(const QTextTableCellFormat &format, QObject *parent) : QObject(parent) , d_ptr(new KoTableCellStylePrivate) { Q_D(KoTableCellStyle); d->stylesPrivate = format.properties(); d->paragraphStyle = new KoParagraphStyle(this); } KoTableCellStyle::KoTableCellStyle(const KoTableCellStyle &other) :QObject(other.parent()) , d_ptr(new KoTableCellStylePrivate) { Q_D(KoTableCellStyle); copyProperties(&other); d->paragraphStyle = other.paragraphStyle()->clone(this); } KoTableCellStyle& KoTableCellStyle::operator=(const KoTableCellStyle &other) { Q_D(KoTableCellStyle); if (this == &other) { return *this; } copyProperties(&other); d->paragraphStyle = other.paragraphStyle()->clone(this); return *this; } KoTableCellStyle::~KoTableCellStyle() { delete d_ptr; } KoTableCellStyle *KoTableCellStyle::fromTableCell(const QTextTableCell &tableCell, QObject *parent) { QTextTableCellFormat tableCellFormat = tableCell.format().toTableCellFormat(); return new KoTableCellStyle(tableCellFormat, parent); } QTextCharFormat KoTableCellStyle::cleanCharFormat(const QTextCharFormat &charFormat) { if (charFormat.isTableCellFormat()) { QTextTableCellFormat format; const QMap props = charFormat.properties(); QMap::const_iterator it = props.begin(); while (it != props.end()) { // lets save all Qt's table cell properties if (it.key()>=QTextFormat::TableCellRowSpan && it.key()=StyleId && it.key()parentStyle = parent; } void KoTableCellStyle::setLeftPadding(qreal padding) { setProperty(QTextFormat::TableCellLeftPadding, padding); } void KoTableCellStyle::setTopPadding(qreal padding) { setProperty(QTextFormat::TableCellTopPadding, padding); } void KoTableCellStyle::setRightPadding(qreal padding) { setProperty(QTextFormat::TableCellRightPadding, padding); } void KoTableCellStyle::setBottomPadding(qreal padding) { setProperty(QTextFormat::TableCellBottomPadding, padding); } qreal KoTableCellStyle::leftPadding() const { return propertyDouble(QTextFormat::TableCellLeftPadding); } qreal KoTableCellStyle::rightPadding() const { return propertyDouble(QTextFormat::TableCellRightPadding); } qreal KoTableCellStyle::topPadding() const { return propertyDouble(QTextFormat::TableCellTopPadding); } qreal KoTableCellStyle::bottomPadding() const { return propertyDouble(QTextFormat::TableCellBottomPadding); } void KoTableCellStyle::setPadding(qreal padding) { setBottomPadding(padding); setTopPadding(padding); setRightPadding(padding); setLeftPadding(padding); } KoParagraphStyle *KoTableCellStyle::paragraphStyle() const { Q_D(const KoTableCellStyle); return d->paragraphStyle; } bool KoTableCellStyle::shrinkToFit() const { return propertyBoolean(ShrinkToFit); } void KoTableCellStyle::setShrinkToFit(bool state) { setProperty(ShrinkToFit, state); } void KoTableCellStyle::setProperty(int key, const QVariant &value) { Q_D(KoTableCellStyle); if (d->parentStyle) { QVariant var = d->parentStyle->value(key); if (!var.isNull() && var == value) { // same as parent, so its actually a reset. d->stylesPrivate.remove(key); return; } } d->stylesPrivate.add(key, value); } void KoTableCellStyle::remove(int key) { Q_D(KoTableCellStyle); d->stylesPrivate.remove(key); } QVariant KoTableCellStyle::value(int key) const { Q_D(const KoTableCellStyle); QVariant var = d->stylesPrivate.value(key); if (var.isNull() && d->parentStyle) var = d->parentStyle->value(key); return var; } bool KoTableCellStyle::hasProperty(int key) const { Q_D(const KoTableCellStyle); return d->stylesPrivate.contains(key); } qreal KoTableCellStyle::propertyDouble(int key) const { QVariant variant = value(key); if (variant.isNull()) return 0.0; return variant.toDouble(); } QPen KoTableCellStyle::propertyPen(int key) const { const QVariant prop = value(key); if (prop.userType() != QVariant::Pen) return QPen(Qt::NoPen); return qvariant_cast(prop); } int KoTableCellStyle::propertyInt(int key) const { QVariant variant = value(key); if (variant.isNull()) return 0; return variant.toInt(); } bool KoTableCellStyle::propertyBoolean(int key) const { QVariant variant = value(key); if (variant.isNull()) return false; return variant.toBool(); } QColor KoTableCellStyle::propertyColor(int key) const { QVariant variant = value(key); if (variant.isNull()) { return QColor(); } return qvariant_cast(variant); } void KoTableCellStyle::applyStyle(QTextTableCellFormat &format) const { Q_D(const KoTableCellStyle); if (d->parentStyle) { d->parentStyle->applyStyle(format); } QList keys = d->stylesPrivate.keys(); for (int i = 0; i < keys.count(); i++) { QVariant variant = d->stylesPrivate.value(keys[i]); format.setProperty(keys[i], variant); } // Hack : build KoBorder here if (d->parentStyle && d->parentStyle->hasProperty(Borders) && this->hasProperty(Borders)) { KoBorder parentBorder = d->parentStyle->borders(); KoBorder childBorder = this->borders(); if (childBorder.hasBorder(KoBorder::LeftBorder)) parentBorder.setBorderData(KoBorder::LeftBorder, childBorder.borderData(KoBorder::LeftBorder)); if (childBorder.hasBorder(KoBorder::RightBorder)) parentBorder.setBorderData(KoBorder::RightBorder, childBorder.borderData(KoBorder::RightBorder)); if (childBorder.hasBorder(KoBorder::TopBorder)) parentBorder.setBorderData(KoBorder::TopBorder, childBorder.borderData(KoBorder::TopBorder)); if (childBorder.hasBorder(KoBorder::BottomBorder)) parentBorder.setBorderData(KoBorder::BottomBorder, childBorder.borderData(KoBorder::BottomBorder)); if (childBorder.hasBorder(KoBorder::BltrBorder)) parentBorder.setBorderData(KoBorder::BltrBorder, childBorder.borderData(KoBorder::BltrBorder)); if (childBorder.hasBorder(KoBorder::TlbrBorder)) parentBorder.setBorderData(KoBorder::TlbrBorder, childBorder.borderData(KoBorder::TlbrBorder)); format.setProperty(Borders, QVariant::fromValue(parentBorder)); } } void KoTableCellStyle::applyStyle(QTextTableCell &cell) const { Q_D(const KoTableCellStyle); QTextTableCellFormat format = cell.format().toTableCellFormat(); applyStyle(format); if (d->paragraphStyle) { d->paragraphStyle->KoCharacterStyle::applyStyle(format); } cell.setFormat(format); } void KoTableCellStyle::setBackground(const QBrush &brush) { setProperty(CellBackgroundBrush, brush); } void KoTableCellStyle::clearBackground() { Q_D(KoTableCellStyle); d->stylesPrivate.remove(CellBackgroundBrush); } QBrush KoTableCellStyle::background() const { Q_D(const KoTableCellStyle); QVariant variant = d->stylesPrivate.value(CellBackgroundBrush); if (variant.isNull()) { return QBrush(); } return qvariant_cast(variant); } void KoTableCellStyle::setWrap(bool state) { setProperty(Wrap, state); } bool KoTableCellStyle::wrap() const { return propertyBoolean(Wrap); } void KoTableCellStyle::setAlignment(Qt::Alignment alignment) { setProperty(VerticalAlignment, (int) alignment); } Qt::Alignment KoTableCellStyle::alignment() const { if (propertyInt(VerticalAlignment) == 0) return Qt::AlignTop; return static_cast(propertyInt(VerticalAlignment)); } KoTableCellStyle *KoTableCellStyle::parentStyle() const { Q_D(const KoTableCellStyle); return d->parentStyle; } QString KoTableCellStyle::name() const { Q_D(const KoTableCellStyle); return d->name; } void KoTableCellStyle::setName(const QString &name) { Q_D(KoTableCellStyle); if (name == d->name) return; d->name = name; emit nameChanged(name); } int KoTableCellStyle::styleId() const { return propertyInt(StyleId); } void KoTableCellStyle::setStyleId(int id) { Q_D(KoTableCellStyle); setProperty(StyleId, id); if (d->next == 0) d->next = id; } QString KoTableCellStyle::masterPageName() const { return value(MasterPageName).toString(); } void KoTableCellStyle::setMasterPageName(const QString &name) { setProperty(MasterPageName, name); } void KoTableCellStyle::setCellProtection(KoTableCellStyle::CellProtectionFlag protection) { setProperty(CellProtection, protection); } KoTableCellStyle::CellProtectionFlag KoTableCellStyle::cellProtection() const { return (CellProtectionFlag) propertyInt(CellProtection); } void KoTableCellStyle::setTextDirection(KoText::Direction value) { setProperty(TextWritingMode, value); } KoText::Direction KoTableCellStyle::textDirection() const { return (KoText::Direction) propertyInt(TextWritingMode); } bool KoTableCellStyle::printContent() const { return (hasProperty(PrintContent) && propertyBoolean(PrintContent)); } void KoTableCellStyle::setPrintContent(bool state) { setProperty(PrintContent, state); } bool KoTableCellStyle::repeatContent() const { return (hasProperty(RepeatContent) && propertyBoolean(RepeatContent)); } void KoTableCellStyle::setRepeatContent(bool state) { setProperty(RepeatContent, state); } int KoTableCellStyle::decimalPlaces() const { return propertyInt(DecimalPlaces); } void KoTableCellStyle::setDecimalPlaces(int places) { setProperty(DecimalPlaces, places); } bool KoTableCellStyle::alignFromType() const { return (hasProperty(AlignFromType) && propertyBoolean(AlignFromType)); } void KoTableCellStyle::setAlignFromType(bool state) { setProperty(AlignFromType, state); } qreal KoTableCellStyle::rotationAngle() const { return propertyDouble(RotationAngle); } void KoTableCellStyle::setRotationAngle(qreal value) { if (value >= 0) setProperty(RotationAngle, value); } void KoTableCellStyle::setVerticalGlyphOrientation(bool state) { setProperty(VerticalGlyphOrientation, state); } bool KoTableCellStyle::verticalGlyphOrientation() const { if (hasProperty(VerticalGlyphOrientation)) return propertyBoolean(VerticalGlyphOrientation); return true; } void KoTableCellStyle::setDirection(KoTableCellStyle::CellTextDirection direction) { setProperty(Direction, direction); } KoBorder KoTableCellStyle::borders() const { if (hasProperty(Borders)) return value(Borders).value(); return KoBorder(); } void KoTableCellStyle::setBorders(const KoBorder& borders) { setProperty(Borders, QVariant::fromValue(borders)); } KoShadowStyle KoTableCellStyle::shadow() const { if (hasProperty(Shadow)) return value(Shadow).value(); return KoShadowStyle(); } void KoTableCellStyle::setShadow(const KoShadowStyle& shadow) { setProperty(Shadow, QVariant::fromValue(shadow)); } KoTableCellStyle::RotationAlignment KoTableCellStyle::rotationAlignment() const { return static_cast(propertyInt(RotationAlign)); } void KoTableCellStyle::setRotationAlignment(KoTableCellStyle::RotationAlignment align) { setProperty(RotationAlign, align); } KoTableCellStyle::CellTextDirection KoTableCellStyle::direction() const { if (hasProperty(Direction)) return (KoTableCellStyle::CellTextDirection) propertyInt(Direction); return KoTableCellStyle::Default; } void KoTableCellStyle::loadOdf(const KoXmlElement *element, KoShapeLoadingContext &scontext) { KoOdfLoadingContext &context = scontext.odfLoadingContext(); Q_D(KoTableCellStyle); if (element->hasAttributeNS(KoXmlNS::style, "display-name")) d->name = element->attributeNS(KoXmlNS::style, "display-name", QString()); if (d->name.isEmpty()) // if no style:display-name is given us the style:name d->name = element->attributeNS(KoXmlNS::style, "name", QString()); QString masterPage = element->attributeNS(KoXmlNS::style, "master-page-name", QString()); if (! masterPage.isEmpty()) { setMasterPageName(masterPage); } paragraphStyle()->loadOdf(element, scontext, true); // load the par and char properties - +#ifndef KOXML_USE_QDOM // Borders - we don't handle inheritance unfortunately - hope it's not a big problem KoBorder borders = this->borders(); borders.loadOdf(element->namedItemNS(KoXmlNS::style, "table-cell-properties").toElement()); setBorders(borders); - +#endif context.styleStack().save(); QString family = element->attributeNS(KoXmlNS::style, "family", "table-cell"); context.addStyles(element, family.toLocal8Bit().constData()); // Load all parents - only because we don't support inheritance. context.styleStack().setTypeProperties("table-cell"); loadOdfProperties(scontext, context.styleStack()); context.styleStack().setTypeProperties("graphic"); loadOdfProperties(scontext, context.styleStack()); context.styleStack().restore(); } void KoTableCellStyle::loadOdfProperties(KoShapeLoadingContext &context, KoStyleStack &styleStack) { // Padding if (styleStack.hasProperty(KoXmlNS::fo, "padding-left")) setLeftPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-left"))); if (styleStack.hasProperty(KoXmlNS::fo, "padding-right")) setRightPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-right"))); if (styleStack.hasProperty(KoXmlNS::fo, "padding-top")) setTopPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-top"))); if (styleStack.hasProperty(KoXmlNS::fo, "padding-bottom")) setBottomPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-bottom"))); if (styleStack.hasProperty(KoXmlNS::fo, "padding")) setPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding"))); if (styleStack.hasProperty(KoXmlNS::style, "shadow")) { KoShadowStyle shadow; if (shadow.loadOdf(styleStack.property(KoXmlNS::style, "shadow"))) { setShadow(shadow); } } // The fo:background-color attribute specifies the background color of a cell. if (styleStack.hasProperty(KoXmlNS::fo, "background-color")) { const QString bgcolor = styleStack.property(KoXmlNS::fo, "background-color"); QBrush brush = background(); if (bgcolor == "transparent") setBackground(Qt::NoBrush); else { if (brush.style() == Qt::NoBrush) brush.setStyle(Qt::SolidPattern); brush.setColor(bgcolor); // #rrggbb format setBackground(brush); } } QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle == "solid" || fillStyle == "hatch") { styleStack.save(); QBrush brush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, fillStyle, context.odfLoadingContext().stylesReader()); setBackground(brush); styleStack.restore(); } if (styleStack.hasProperty(KoXmlNS::style, "shrink-to-fit")) { setShrinkToFit(styleStack.property(KoXmlNS::style, "shrink-to-fit") == "true"); } - + if (styleStack.hasProperty(KoXmlNS::style, "print-content")) { setPrintContent(styleStack.property(KoXmlNS::style, "print-content") == "true"); } - + if (styleStack.hasProperty(KoXmlNS::style, "repeat-content")) { setRepeatContent(styleStack.property(KoXmlNS::style, "repeat-content") == "true"); } - + if (styleStack.hasProperty(KoXmlNS::style, "repeat-content")) { setRepeatContent(styleStack.property(KoXmlNS::style, "repeat-content") == "true"); } - + if (styleStack.hasProperty(KoXmlNS::style, "decimal-places")) { bool ok; int value = styleStack.property(KoXmlNS::style, "decimal-places").toInt(&ok); if (ok) setDecimalPlaces(value); } - + if (styleStack.hasProperty(KoXmlNS::style, "rotation-angle")) { setRotationAngle(KoUnit::parseAngle(styleStack.property(KoXmlNS::style, "rotation-angle"))); } - + if (styleStack.hasProperty(KoXmlNS::style, "glyph-orientation-vertical")) { setVerticalGlyphOrientation(styleStack.property(KoXmlNS::style, "glyph-orientation-vertical") == "auto"); } - + if (styleStack.hasProperty(KoXmlNS::style, "direction")) { if (styleStack.property(KoXmlNS::style, "direction") == "ltr") setDirection(KoTableCellStyle::LeftToRight); else setDirection(KoTableCellStyle::TopToBottom); } - + if (styleStack.hasProperty(KoXmlNS::style, "rotation-align")) { setRotationAlignment(rotationAlignmentFromString(styleStack.property(KoXmlNS::style, "rotation-align"))); } - + if (styleStack.hasProperty(KoXmlNS::style, "text-align-source")) { setAlignFromType(styleStack.property(KoXmlNS::style, "text-align-source") == "value-type"); } - + if (styleStack.hasProperty(KoXmlNS::fo, "wrap-option")) { setWrap(styleStack.property(KoXmlNS::fo, "wrap-option") == "wrap"); } - + if (styleStack.hasProperty(KoXmlNS::style, "cell-protect")) { QString protection = styleStack.property(KoXmlNS::style, "cell-protect"); if (protection == "none") setCellProtection(NoProtection); else if (protection == "hidden-and-protected") setCellProtection(HiddenAndProtected); else if (protection == "protected") setCellProtection(Protected); else if (protection == "formula-hidden") setCellProtection(FormulaHidden); else if ((protection == "protected formula-hidden") || (protection == "formula-hidden protected")) setCellProtection(ProtectedAndFormulaHidden); } // Alignment const QString verticalAlign(styleStack.property(KoXmlNS::style, "vertical-align")); if (!verticalAlign.isEmpty()) { if (verticalAlign == "automatic") setAlignment((Qt::AlignmentFlag) 0); else setAlignment(KoText::valignmentFromString(verticalAlign)); } - + if (styleStack.hasProperty(KoXmlNS::style, "writing-mode")) setTextDirection(KoText::directionFromString(styleStack.property(KoXmlNS::style, "writing-mode"))); } void KoTableCellStyle::copyProperties(const KoTableCellStyle *style) { Q_D(KoTableCellStyle); const KoTableCellStylePrivate *styleD = static_cast(style->d_func()); d->stylesPrivate = styleD->stylesPrivate; setName(style->name()); // make sure we emit property change d->next = styleD->next; d->parentStyle = styleD->parentStyle; } KoTableCellStyle *KoTableCellStyle::clone(QObject *parent) { KoTableCellStyle *newStyle = new KoTableCellStyle(parent); newStyle->copyProperties(this); return newStyle; } bool KoTableCellStyle::operator==(const KoTableCellStyle &other) const { Q_D(const KoTableCellStyle); const KoTableCellStylePrivate *otherD = static_cast(other.d_func()); return otherD->stylesPrivate == d->stylesPrivate; } void KoTableCellStyle::removeDuplicates(const KoTableCellStyle &other) { Q_D(KoTableCellStyle); const KoTableCellStylePrivate *otherD = static_cast(other.d_func()); d->stylesPrivate.removeDuplicates(otherD->stylesPrivate); } void KoTableCellStyle::saveOdf(KoGenStyle &style, KoShapeSavingContext &context) { Q_D(KoTableCellStyle); QList keys = d->stylesPrivate.keys(); bool donePadding = false; - if (hasProperty(QTextFormat::TableCellLeftPadding) && - hasProperty(QTextFormat::TableCellRightPadding) && - hasProperty(QTextFormat::TableCellTopPadding) && - hasProperty(QTextFormat::TableCellBottomPadding) && + if (hasProperty(QTextFormat::TableCellLeftPadding) && + hasProperty(QTextFormat::TableCellRightPadding) && + hasProperty(QTextFormat::TableCellTopPadding) && + hasProperty(QTextFormat::TableCellBottomPadding) && leftPadding() == rightPadding() && topPadding() == bottomPadding() && topPadding() == leftPadding()) { donePadding = true; style.addPropertyPt("fo:padding", leftPadding(), KoGenStyle::TableCellType); } Q_FOREACH (int key, keys) { if (key == CellBackgroundBrush) { QBrush backBrush = background(); if (backBrush.style() != Qt::NoBrush) style.addProperty("fo:background-color", backBrush.color().name(), KoGenStyle::TableCellType); else style.addProperty("fo:background-color", "transparent", KoGenStyle::TableCellType); } else if (key == VerticalAlignment) { if (propertyInt(VerticalAlignment) == 0) style.addProperty("style:vertical-align", "automatic", KoGenStyle::TableCellType); else style.addProperty("style:vertical-align", KoText::valignmentToString(alignment()), KoGenStyle::TableCellType); } else if ((key == QTextFormat::TableCellLeftPadding) && (!donePadding)) { style.addPropertyPt("fo:padding-left", leftPadding(), KoGenStyle::TableCellType); } else if ((key == QTextFormat::TableCellRightPadding) && (!donePadding)) { style.addPropertyPt("fo:padding-right", rightPadding(), KoGenStyle::TableCellType); } else if ((key == QTextFormat::TableCellTopPadding) && (!donePadding)) { style.addPropertyPt("fo:padding-top", topPadding(), KoGenStyle::TableCellType); } else if ((key == QTextFormat::TableCellBottomPadding) && (!donePadding)) { style.addPropertyPt("fo:padding-bottom", bottomPadding(), KoGenStyle::TableCellType); } else if (key == ShrinkToFit) { style.addProperty("style:shrink-to-fit", shrinkToFit(), KoGenStyle::TableCellType); } else if (key == PrintContent) { style.addProperty("style:print-content", printContent(), KoGenStyle::TableCellType); } else if (key == RepeatContent) { style.addProperty("style:repeat-content", repeatContent(), KoGenStyle::TableCellType); } else if (key == DecimalPlaces) { style.addProperty("style:decimal-places", decimalPlaces(), KoGenStyle::TableCellType); } else if (key == RotationAngle) { style.addProperty("style:rotation-angle", QString::number(rotationAngle()), KoGenStyle::TableCellType); } else if (key == Wrap) { if (wrap()) style.addProperty("fo:wrap-option", "wrap", KoGenStyle::TableCellType); else style.addProperty("fo:wrap-option", "no-wrap", KoGenStyle::TableCellType); } else if (key == Direction) { if (direction() == LeftToRight) style.addProperty("style:direction", "ltr", KoGenStyle::TableCellType); else if (direction() == TopToBottom) style.addProperty("style:direction", "ttb", KoGenStyle::TableCellType); } else if (key == CellProtection) { if (cellProtection() == NoProtection) style.addProperty("style:cell-protect", "none", KoGenStyle::TableCellType); else if (cellProtection() == HiddenAndProtected) style.addProperty("style:cell-protect", "hidden-and-protected", KoGenStyle::TableCellType); else if (cellProtection() == Protected) style.addProperty("style:cell-protect", "protected", KoGenStyle::TableCellType); else if (cellProtection() == FormulaHidden) style.addProperty("style:cell-protect", "formula-hidden", KoGenStyle::TableCellType); else if (cellProtection() == ProtectedAndFormulaHidden) style.addProperty("style:cell-protect", "protected formula-hidden", KoGenStyle::TableCellType); } else if (key == AlignFromType) { if (alignFromType()) style.addProperty("style:text-align-source", "value-type", KoGenStyle::TableCellType); else style.addProperty("style:text-align-source", "fix", KoGenStyle::TableCellType); } else if (key == RotationAlign) { style.addProperty("style:rotation-align", rotationAlignmentToString(rotationAlignment()), KoGenStyle::TableCellType); } else if (key == TextWritingMode) { style.addProperty("style:writing-mode", KoText::directionToString(textDirection()), KoGenStyle::TableCellType); } else if (key == VerticalGlyphOrientation) { if (verticalGlyphOrientation()) style.addProperty("style:glyph-orientation-vertical", "auto", KoGenStyle::TableCellType); else style.addProperty("style:glyph-orientation-vertical", "0", KoGenStyle::TableCellType); } else if (key == Borders) { borders().saveOdf(style, KoGenStyle::TableCellType); } else if (key == Shadow) { style.addProperty("style:shadow", shadow().saveOdf()); } } if (d->paragraphStyle) { d->paragraphStyle->saveOdf(style, context); } } void KoTableCellStyle::setEdge(KoBorder::BorderSide side, KoBorder::BorderStyle style, qreal width, const QColor &color) { KoBorder::BorderData edge; 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 = 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.setColor(color); 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); QPen middlePen; middlePen = edge.outerPen; middlePen.setWidthF(middleWidth); setEdge(side, edge, style); } void KoTableCellStyle::setEdge(KoBorder::BorderSide side, const KoBorder::BorderData &edge, KoBorder::BorderStyle style) { KoBorder borders = this->borders(); KoBorder::BorderData edgeCopy(edge); edgeCopy.style = style; // Just for safety. borders.setBorderData(side, edgeCopy); setBorders(borders); } void KoTableCellStyle::setEdgeDoubleBorderValues(KoBorder::BorderSide side, qreal innerWidth, qreal space) { KoBorder::BorderData edge = getEdge(side); qreal totalWidth = edge.outerPen.widthF() + edge.spacing + edge.innerPen.widthF(); if (edge.innerPen.widthF() > 0.0) { edge.outerPen.setWidthF(totalWidth - innerWidth - space); edge.spacing = space; edge.innerPen.setWidthF(innerWidth); setEdge(side, edge, getBorderStyle(side)); } } bool KoTableCellStyle::hasBorders() const { return borders().hasBorder(); } qreal KoTableCellStyle::leftBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::LeftBorder); return edge.spacing + edge.innerPen.widthF() + edge.outerPen.widthF(); } qreal KoTableCellStyle::rightBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::RightBorder); return edge.spacing + edge.innerPen.widthF() + edge.outerPen.widthF(); } qreal KoTableCellStyle::topBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::TopBorder); return edge.spacing + edge.innerPen.widthF() + edge.outerPen.widthF(); } qreal KoTableCellStyle::bottomBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::BottomBorder); return edge.spacing + edge.innerPen.widthF() + edge.outerPen.widthF(); } qreal KoTableCellStyle::leftInnerBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::LeftBorder); return edge.innerPen.widthF(); } qreal KoTableCellStyle::rightInnerBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::RightBorder); return edge.innerPen.widthF(); } qreal KoTableCellStyle::topInnerBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::TopBorder); return edge.innerPen.widthF(); } qreal KoTableCellStyle::bottomInnerBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::BottomBorder); return edge.innerPen.widthF(); } qreal KoTableCellStyle::leftOuterBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::LeftBorder); return edge.outerPen.widthF(); } qreal KoTableCellStyle::rightOuterBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::RightBorder); return edge.outerPen.widthF(); } qreal KoTableCellStyle::topOuterBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::TopBorder); return edge.outerPen.widthF(); } qreal KoTableCellStyle::bottomOuterBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::BottomBorder); return edge.outerPen.widthF(); } KoBorder::BorderData KoTableCellStyle::getEdge(KoBorder::BorderSide side) const { KoBorder border = this->borders(); return border.borderData(side); } KoBorder::BorderStyle KoTableCellStyle::getBorderStyle(KoBorder::BorderSide side) const { KoBorder::BorderData edge = getEdge(side); return edge.style; } diff --git a/plugins/impex/kra/kra_converter.cpp b/plugins/impex/kra/kra_converter.cpp index 20dd1a6b9f..87a7b6d101 100644 --- a/plugins/impex/kra/kra_converter.cpp +++ b/plugins/impex/kra/kra_converter.cpp @@ -1,353 +1,361 @@ /* * Copyright (C) 2016 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 "kra_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char CURRENT_DTD_VERSION[] = "2.0"; KraConverter::KraConverter(KisDocument *doc) : m_doc(doc) , m_image(doc->savingImage()) { } KraConverter::~KraConverter() { delete m_store; delete m_kraSaver; delete m_kraLoader; } KisImageBuilder_Result KraConverter::buildImage(QIODevice *io) { m_store = KoStore::createStore(io, KoStore::Read, "", KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Not a valid Krita file")); return KisImageBuilder_RESULT_FAILURE; } bool success; { if (m_store->hasFile("root") || m_store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml) +#ifdef KOXML_USE_QDOM + KoXmlDocument doc; +#else KoXmlDocument doc = KoXmlDocument(true); +#endif bool ok = oldLoadAndParse(m_store, "root", doc); if (ok) ok = loadXML(doc, m_store); if (!ok) { return KisImageBuilder_RESULT_FAILURE; } } else { errUI << "ERROR: No maindoc.xml" << endl; m_doc->setErrorMessage(i18n("Invalid document: no file 'maindoc.xml'.")); return KisImageBuilder_RESULT_FAILURE; } if (m_store->hasFile("documentinfo.xml")) { +#ifdef KOXML_USE_QDOM + KoXmlDocument doc; +#else KoXmlDocument doc = KoXmlDocument(true); +#endif if (oldLoadAndParse(m_store, "documentinfo.xml", doc)) { m_doc->documentInfo()->load(doc); } } success = completeLoading(m_store); } return success ? KisImageBuilder_RESULT_OK : KisImageBuilder_RESULT_FAILURE; } KisImageSP KraConverter::image() { return m_image; } vKisNodeSP KraConverter::activeNodes() { return m_activeNodes; } QList KraConverter::assistants() { return m_assistants; } KisImageBuilder_Result KraConverter::buildFile(QIODevice *io) { m_store = KoStore::createStore(io, KoStore::Write, m_doc->nativeFormatMimeType(), KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Could not create the file for saving")); return KisImageBuilder_RESULT_FAILURE; } bool result = false; m_kraSaver = new KisKraSaver(m_doc); result = saveRootDocuments(m_store); if (!result) { return KisImageBuilder_RESULT_FAILURE; } result = m_kraSaver->saveKeyframes(m_store, m_doc->url().toLocalFile(), true); if (!result) { qWarning() << "saving key frames failed"; } result = m_kraSaver->saveBinaryData(m_store, m_image, m_doc->url().toLocalFile(), true, m_doc->isAutosaving()); if (!result) { qWarning() << "saving binary data failed"; } if (!m_store->finalize()) { return KisImageBuilder_RESULT_FAILURE; } if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); return KisImageBuilder_RESULT_FAILURE; } return KisImageBuilder_RESULT_OK; } bool KraConverter::saveRootDocuments(KoStore *store) { dbgFile << "Saving root"; if (store->open("root")) { KoStoreDevice dev(store); if (!saveToStream(&dev) || !store->close()) { dbgUI << "saveToStream failed"; return false; } } else { m_doc->setErrorMessage(i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml"))); return false; } bool success = false; if (store->open("documentinfo.xml")) { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = m_doc->documentInfo()->save(doc); KoStoreDevice dev(store); QByteArray s = doc.toByteArray(); // this is already Utf8! success = dev.write(s.data(), s.size()); store->close(); } if (store->open("preview.png")) { // ### TODO: missing error checking (The partition could be full!) savePreview(store); (void)store->close(); } dbgUI << "Saving done of url:" << m_doc->url().toLocalFile(); // Success return success; } bool KraConverter::saveToStream(QIODevice *dev) { QDomDocument doc = createDomDocument(); // Save to buffer QByteArray s = doc.toByteArray(); // utf8 already dev->open(QIODevice::WriteOnly); int nwritten = dev->write(s.data(), s.size()); if (nwritten != (int)s.size()) { warnUI << "wrote " << nwritten << "- expected" << s.size(); } return nwritten == (int)s.size(); } QDomDocument KraConverter::createDomDocument() { QDomDocument doc = m_doc->createDomDocument("DOC", CURRENT_DTD_VERSION); QDomElement root = doc.documentElement(); root.setAttribute("editor", "Krita"); root.setAttribute("syntaxVersion", "2"); root.setAttribute("kritaVersion", KritaVersionWrapper::versionString(false)); root.appendChild(m_kraSaver->saveXML(doc, m_image)); if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); } return doc; } bool KraConverter::savePreview(KoStore *store) { QPixmap pix = m_doc->generatePreview(QSize(256, 256)); QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly)); if (preview.size() == QSize(0,0)) { QSize newSize = m_doc->savingImage()->bounds().size(); newSize.scale(QSize(256, 256), Qt::KeepAspectRatio); preview = QImage(newSize, QImage::Format_ARGB32); preview.fill(QColor(0, 0, 0, 0)); } KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) { return false; } bool ret = preview.save(&io, "PNG"); io.close(); return ret; } bool KraConverter::oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc) { //dbgUI <<"Trying to open" << filename; if (!store->open(filename)) { warnUI << "Entry " << filename << " not found!"; m_doc->setErrorMessage(i18n("Could not find %1", filename)); return false; } // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; bool ok = xmldoc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn); store->close(); if (!ok) { errUI << "Parsing error in " << filename << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; m_doc->setErrorMessage(i18n("Parsing error in %1 at line %2, column %3\nError message: %4" , filename , errorLine, errorColumn , QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0, QCoreApplication::UnicodeUTF8))); return false; } dbgUI << "File" << filename << " loaded and parsed"; return true; } bool KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store) { Q_UNUSED(store); KoXmlElement root; KoXmlNode node; if (doc.doctype().name() != "DOC") { m_doc->setErrorMessage(i18n("The format is not supported or the file is corrupted")); return false; } root = doc.documentElement(); int syntaxVersion = root.attribute("syntaxVersion", "3").toInt(); if (syntaxVersion > 2) { m_doc->setErrorMessage(i18n("The file is too new for this version of Krita (%1).", syntaxVersion)); return false; } if (!root.hasChildNodes()) { m_doc->setErrorMessage(i18n("The file has no layers.")); return false; } m_kraLoader = new KisKraLoader(m_doc, syntaxVersion); // Legacy from the multi-image .kra file period. for (node = root.firstChild(); !node.isNull(); node = node.nextSibling()) { if (node.isElement()) { if (node.nodeName() == "IMAGE") { KoXmlElement elem = node.toElement(); if (!(m_image = m_kraLoader->loadXML(elem))) { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); } return false; } return true; } else { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("The file does not contain an image.")); } return false; } } } return false; } bool KraConverter::completeLoading(KoStore* store) { if (!m_image) { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); } return false; } m_image->blockUpdates(); m_kraLoader->loadBinaryData(store, m_image, m_doc->localFilePath(), true); m_image->unblockUpdates(); bool retval = true; if (!m_kraLoader->warningMessages().isEmpty()) { m_doc->setWarningMessage(m_kraLoader->warningMessages().join("\n")); retval = true; } if (retval) { m_activeNodes = m_kraLoader->selectedNodes(); m_assistants = m_kraLoader->assistants(); } return retval; } void KraConverter::cancel() { m_stop = true; } diff --git a/plugins/impex/libkra/kis_kra_loader.cpp b/plugins/impex/libkra/kis_kra_loader.cpp index d9fac5a1e8..23f9267e16 100644 --- a/plugins/impex/libkra/kis_kra_loader.cpp +++ b/plugins/impex/libkra/kis_kra_loader.cpp @@ -1,1153 +1,1153 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2007 * * 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 "kis_kra_loader.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 "lazybrush/kis_colorize_mask.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_resource_server_provider.h" #include "kis_keyframe_channel.h" #include #include "KisDocument.h" #include "kis_config.h" #include "kis_kra_tags.h" #include "kis_kra_utils.h" #include "kis_kra_load_visitor.h" #include "kis_dom_utils.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_config.h" #include "KisProofingConfiguration.h" #include "kis_layer_properties_icons.h" #include "kis_node_view_color_scheme.h" /* Color model id comparison through the ages: 2.4 2.5 2.6 ideal ALPHA ALPHA ALPHA ALPHAU8 CMYK CMYK CMYK CMYKAU8 CMYKAF32 CMYKAF32 CMYKA16 CMYKAU16 CMYKAU16 GRAYA GRAYA GRAYA GRAYAU8 GrayF32 GRAYAF32 GRAYAF32 GRAYA16 GRAYAU16 GRAYAU16 LABA LABA LABA LABAU16 LABAF32 LABAF32 LABAU8 LABAU8 RGBA RGBA RGBA RGBAU8 RGBA16 RGBA16 RGBA16 RGBAU16 RgbAF32 RGBAF32 RGBAF32 RgbAF16 RgbAF16 RGBAF16 XYZA16 XYZA16 XYZA16 XYZAU16 XYZA8 XYZA8 XYZAU8 XyzAF16 XyzAF16 XYZAF16 XyzAF32 XYZAF32 XYZAF32 YCbCrA YCBCRA8 YCBCRA8 YCBCRAU8 YCbCrAU16 YCBCRAU16 YCBCRAU16 YCBCRF32 YCBCRF32 */ using namespace KRA; struct KisKraLoader::Private { public: KisDocument* document; QString imageName; // used to be stored in the image, is now in the documentInfo block QString imageComment; // used to be stored in the image, is now in the documentInfo block QMap layerFilenames; // temp storage during loading int syntaxVersion; // version of the fileformat we are loading vKisNodeSP selectedNodes; // the nodes that were active when saving the document. QMap assistantsFilenames; QList assistants; QMap keyframeFilenames; QStringList errorMessages; QStringList warningMessages; }; void convertColorSpaceNames(QString &colorspacename, QString &profileProductName) { if (colorspacename == "Grayscale + Alpha") { colorspacename = "GRAYA"; profileProductName.clear(); } else if (colorspacename == "RgbAF32") { colorspacename = "RGBAF32"; profileProductName.clear(); } else if (colorspacename == "RgbAF16") { colorspacename = "RGBAF16"; profileProductName.clear(); } else if (colorspacename == "CMYKA16") { colorspacename = "CMYKAU16"; } else if (colorspacename == "GrayF32") { colorspacename = "GRAYAF32"; profileProductName.clear(); } else if (colorspacename == "GRAYA16") { colorspacename = "GRAYAU16"; } else if (colorspacename == "XyzAF16") { colorspacename = "XYZAF16"; profileProductName.clear(); } else if (colorspacename == "XyzAF32") { colorspacename = "XYZAF32"; profileProductName.clear(); } else if (colorspacename == "YCbCrA") { colorspacename = "YCBCRA8"; } else if (colorspacename == "YCbCrAU16") { colorspacename = "YCBCRAU16"; } } KisKraLoader::KisKraLoader(KisDocument * document, int syntaxVersion) : m_d(new Private()) { m_d->document = document; m_d->syntaxVersion = syntaxVersion; } KisKraLoader::~KisKraLoader() { delete m_d; } KisImageSP KisKraLoader::loadXML(const KoXmlElement& element) { QString attr; KisImageSP image = 0; QString name; qint32 width; qint32 height; QString profileProductName; double xres; double yres; QString colorspacename; const KoColorSpace * cs; if ((attr = element.attribute(MIME)) == NATIVE_MIMETYPE) { if ((m_d->imageName = element.attribute(NAME)).isNull()) { m_d->errorMessages << i18n("Image does not have a name."); return KisImageSP(0); } if ((attr = element.attribute(WIDTH)).isNull()) { m_d->errorMessages << i18n("Image does not specify a width."); return KisImageSP(0); } width = KisDomUtils::toInt(attr); if ((attr = element.attribute(HEIGHT)).isNull()) { m_d->errorMessages << i18n("Image does not specify a height."); return KisImageSP(0); } height = KisDomUtils::toInt(attr); m_d->imageComment = element.attribute(DESCRIPTION); xres = 100.0 / 72.0; if (!(attr = element.attribute(X_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { xres = value / 72.0; } } yres = 100.0 / 72.0; if (!(attr = element.attribute(Y_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { yres = value / 72.0; } } if ((colorspacename = element.attribute(COLORSPACE_NAME)).isNull()) { // An old file: take a reasonable default. // Krita didn't support anything else in those // days anyway. colorspacename = "RGBA"; } profileProductName = element.attribute(PROFILE); // A hack for an old colorspacename convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); if (profileProductName.isNull()) { // no mention of profile so get default profile"; cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, profileProductName); } if (cs == 0) { // try once more without the profile cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); if (cs == 0) { m_d->errorMessages << i18n("Image specifies an unsupported color model: %1.", colorspacename); return KisImageSP(0); } } KisImageConfig cfgImage; KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); if (!(attr = element.attribute(PROOFINGPROFILENAME)).isNull()) { proofingConfig->proofingProfile = attr; } if (!(attr = element.attribute(PROOFINGMODEL)).isNull()) { proofingConfig->proofingModel = attr; } if (!(attr = element.attribute(PROOFINGDEPTH)).isNull()) { proofingConfig->proofingDepth = attr; } if (!(attr = element.attribute(PROOFINGINTENT)).isNull()) { proofingConfig->intent = (KoColorConversionTransformation::Intent) KisDomUtils::toInt(attr); } if (!(attr = element.attribute(PROOFINGADAPTATIONSTATE)).isNull()) { proofingConfig->adaptationState = KisDomUtils::toDouble(attr); } if (m_d->document) { image = new KisImage(m_d->document->createUndoStore(), width, height, cs, name); } else { image = new KisImage(0, width, height, cs, name); } image->setResolution(xres, yres); loadNodes(element, image, const_cast(image->rootLayer().data())); KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == CANVASPROJECTIONCOLOR) { if (e.hasAttribute(COLORBYTEDATA)) { QByteArray colorData = QByteArray::fromBase64(e.attribute(COLORBYTEDATA).toLatin1()); KoColor color((const quint8*)colorData.data(), image->colorSpace()); image->setDefaultProjectionColor(color); } } if(e.tagName()== PROOFINGWARNINGCOLOR) { QDomDocument dom; KoXml::asQDomElement(dom, e); QDomElement eq = dom.firstChildElement(); proofingConfig->warningColor = KoColor::fromXML(eq.firstChildElement(), Integer8BitsColorDepthID.id()); } if (e.tagName().toLower() == "animation") { loadAnimationMetadata(e, image); } } image->setProofingConfiguration(proofingConfig); for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == "compositions") { loadCompositions(e, image); } } } KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if (e.tagName() == "grid") { loadGrid(e); } else if (e.tagName() == "guides") { loadGuides(e); } else if (e.tagName() == "assistants") { loadAssistantsList(e); } else if (e.tagName() == "audio") { loadAudio(e, image); } } return image; } void KisKraLoader::loadBinaryData(KoStore * store, KisImageSP image, const QString & uri, bool external) { // icc profile: if present, this overrides the profile product name loaded in loadXML. QString location = external ? QString() : uri; location += m_d->imageName + ICC_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray data; data.resize(store->size()); bool res = (store->read(data.data(), store->size()) > -1); store->close(); if (res) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(image->colorSpace()->colorModelId().id(), image->colorSpace()->colorDepthId().id(), data); if (profile && profile->valid()) { res = image->assignImageProfile(profile); } if (!res) { profile = KoColorSpaceRegistry::instance()->profileByName(KoColorSpaceRegistry::instance()->colorSpaceFactory(image->colorSpace()->id())->defaultProfile()); Q_ASSERT(profile && profile->valid()); image->assignImageProfile(profile); } } } } //load the embed proofing profile, it only needs to be loaded into Krita, not assigned. location = external ? QString() : uri; location += m_d->imageName + ICC_PROOFING_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray proofingData; proofingData.resize(store->size()); bool proofingProfileRes = (store->read(proofingData.data(), store->size())>-1); store->close(); if (proofingProfileRes) { const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->createColorProfile(image->proofingConfiguration()->proofingModel, image->proofingConfiguration()->proofingDepth, proofingData); if (proofingProfile->valid()){ //if (KoColorSpaceEngineRegistry::instance()->get("icc")) { // KoColorSpaceEngineRegistry::instance()->get("icc")->addProfile(proofingProfile->fileName()); //} KoColorSpaceRegistry::instance()->addProfile(proofingProfile); } } } } // Load the layers data: if there is a profile associated with a layer it will be set now. KisKraLoadVisitor visitor(image, store, m_d->layerFilenames, m_d->keyframeFilenames, m_d->imageName, m_d->syntaxVersion); if (external) { visitor.setExternalUri(uri); } image->rootLayer()->accept(visitor); if (!visitor.errorMessages().isEmpty()) { m_d->errorMessages.append(visitor.errorMessages()); } if (!visitor.warningMessages().isEmpty()) { m_d->warningMessages.append(visitor.warningMessages()); } // annotations // exif location = external ? QString() : uri; location += m_d->imageName + EXIF_PATH; if (store->hasFile(location)) { QByteArray data; store->open(location); data = store->read(store->size()); store->close(); image->addAnnotation(KisAnnotationSP(new KisAnnotation("exif", "", data))); } // layer styles location = external ? QString() : uri; location += m_d->imageName + LAYER_STYLES_PATH; if (store->hasFile(location)) { KisPSDLayerStyleCollectionResource *collection = new KisPSDLayerStyleCollectionResource("Embedded Styles.asl"); collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_d->imageName)); KIS_ASSERT_RECOVER_NOOP(!collection->valid()); store->open(location); { KoStoreDevice device(store); device.open(QIODevice::ReadOnly); /** * ASL loading code cannot work with non-sequential IO devices, * so convert the device beforehand! */ QByteArray buf = device.readAll(); QBuffer raDevice(&buf); raDevice.open(QIODevice::ReadOnly); collection->loadFromDevice(&raDevice); } store->close(); if (collection->valid()) { KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); server->addResource(collection, false); collection->assignAllLayerStyles(image->root()); } else { warnKrita << "WARNING: Couldn't load layer styles library from .kra!"; delete collection; } } if (m_d->document && m_d->document->documentInfo()->aboutInfo("title").isNull()) m_d->document->documentInfo()->setAboutInfo("title", m_d->imageName); if (m_d->document && m_d->document->documentInfo()->aboutInfo("comment").isNull()) m_d->document->documentInfo()->setAboutInfo("comment", m_d->imageComment); loadAssistants(store, uri, external); } vKisNodeSP KisKraLoader::selectedNodes() const { return m_d->selectedNodes; } QList KisKraLoader::assistants() const { return m_d->assistants; } QStringList KisKraLoader::errorMessages() const { return m_d->errorMessages; } QStringList KisKraLoader::warningMessages() const { return m_d->warningMessages; } void KisKraLoader::loadAssistants(KoStore *store, const QString &uri, bool external) { QString file_path; QString location; QMap handleMap; KisPaintingAssistant* assistant = 0; QMap::const_iterator loadedAssistant = m_d->assistantsFilenames.constBegin(); while (loadedAssistant != m_d->assistantsFilenames.constEnd()){ const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(loadedAssistant.value()); if (factory) { assistant = factory->createPaintingAssistant(); location = external ? QString() : uri; location += m_d->imageName + ASSISTANTS_PATH; file_path = location + loadedAssistant.key(); assistant->loadXml(store, handleMap, file_path); //If an assistant has too few handles than it should according to it's own setup, just don't load it// if (assistant->handles().size()==assistant->numHandles()){ m_d->assistants.append(toQShared(assistant)); } } loadedAssistant++; } } void KisKraLoader::loadAnimationMetadata(const KoXmlElement &element, KisImageSP image) { QDomDocument qDom; KoXml::asQDomElement(qDom, element); QDomElement qElement = qDom.firstChildElement(); float framerate; KisTimeRange range; int currentTime; KisImageAnimationInterface *animation = image->animationInterface(); if (KisDomUtils::loadValue(qElement, "framerate", &framerate)) { animation->setFramerate(framerate); } if (KisDomUtils::loadValue(qElement, "range", &range)) { animation->setFullClipRange(range); } if (KisDomUtils::loadValue(qElement, "currentTime", ¤tTime)) { animation->switchCurrentTimeAsync(currentTime); } } KisNodeSP KisKraLoader::loadNodes(const KoXmlElement& element, KisImageSP image, KisNodeSP parent) { KoXmlNode node = element.firstChild(); KoXmlNode child; if (!node.isNull()) { if (node.isElement()) { if (node.nodeName().toUpper() == LAYERS.toUpper() || node.nodeName().toUpper() == MASKS.toUpper()) { for (child = node.lastChild(); !child.isNull(); child = child.previousSibling()) { KisNodeSP node = loadNode(child.toElement(), image, parent); if (node) { image->nextLayerName(); // Make sure the nameserver is current with the number of nodes. image->addNode(node, parent); - if (node->inherits("KisLayer") && child.childNodesCount() > 0) { + if (node->inherits("KisLayer") && KoXml::childNodesCount(child) > 0) { loadNodes(child.toElement(), image, node); } } } } } } return parent; } KisNodeSP KisKraLoader::loadNode(const KoXmlElement& element, KisImageSP image, KisNodeSP parent) { // Nota bene: If you add new properties to layers, you should // ALWAYS define a default value in case the property is not // present in the layer definition: this helps a LOT with backward // compatibility. QString name = element.attribute(NAME, "No Name"); QUuid id = QUuid(element.attribute(UUID, QUuid().toString())); qint32 x = element.attribute(X, "0").toInt(); qint32 y = element.attribute(Y, "0").toInt(); qint32 opacity = element.attribute(OPACITY, QString::number(OPACITY_OPAQUE_U8)).toInt(); if (opacity < OPACITY_TRANSPARENT_U8) opacity = OPACITY_TRANSPARENT_U8; if (opacity > OPACITY_OPAQUE_U8) opacity = OPACITY_OPAQUE_U8; const KoColorSpace* colorSpace = 0; if ((element.attribute(COLORSPACE_NAME)).isNull()) { dbgFile << "No attribute color space for layer: " << name; colorSpace = image->colorSpace(); } else { QString colorspacename = element.attribute(COLORSPACE_NAME); QString profileProductName; convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); dbgFile << "Searching color space: " << colorspacename << colorspaceModel << colorspaceDepth << " for layer: " << name; // use default profile - it will be replaced later in completeLoading colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); dbgFile << "found colorspace" << colorSpace; if (!colorSpace) { m_d->warningMessages << i18n("Layer %1 specifies an unsupported color model: %2.", name, colorspacename); return 0; } } const bool visible = element.attribute(VISIBLE, "1") == "0" ? false : true; const bool locked = element.attribute(LOCKED, "0") == "0" ? false : true; const bool collapsed = element.attribute(COLLAPSED, "0") == "0" ? false : true; int colorLabelIndex = element.attribute(COLOR_LABEL, "0").toInt(); QVector labels = KisNodeViewColorScheme::instance()->allColorLabels(); if (colorLabelIndex >= labels.size()) { colorLabelIndex = labels.size() - 1; } // Now find out the layer type and do specific handling QString nodeType; if (m_d->syntaxVersion == 1) { nodeType = element.attribute("layertype"); if (nodeType.isEmpty()) { nodeType = PAINT_LAYER; } } else { nodeType = element.attribute(NODE_TYPE); } if (nodeType.isEmpty()) { m_d->warningMessages << i18n("Layer %1 has an unsupported type.", name); return 0; } KisNodeSP node = 0; if (nodeType == PAINT_LAYER) node = loadPaintLayer(element, image, name, colorSpace, opacity); else if (nodeType == GROUP_LAYER) node = loadGroupLayer(element, image, name, colorSpace, opacity); else if (nodeType == ADJUSTMENT_LAYER) node = loadAdjustmentLayer(element, image, name, colorSpace, opacity); else if (nodeType == SHAPE_LAYER) node = loadShapeLayer(element, image, name, colorSpace, opacity); else if (nodeType == GENERATOR_LAYER) node = loadGeneratorLayer(element, image, name, colorSpace, opacity); else if (nodeType == CLONE_LAYER) node = loadCloneLayer(element, image, name, colorSpace, opacity); else if (nodeType == FILTER_MASK) node = loadFilterMask(element, parent); else if (nodeType == TRANSFORM_MASK) node = loadTransformMask(element, parent); else if (nodeType == TRANSPARENCY_MASK) node = loadTransparencyMask(element, parent); else if (nodeType == SELECTION_MASK) node = loadSelectionMask(image, element, parent); else if (nodeType == COLORIZE_MASK) node = loadColorizeMask(image, element, parent, colorSpace); else if (nodeType == FILE_LAYER) { node = loadFileLayer(element, image, name, opacity); } else { m_d->warningMessages << i18n("Layer %1 has an unsupported type: %2.", name, nodeType); return 0; } // Loading the node went wrong. Return empty node and leave to // upstream to complain to the user if (!node) { m_d->warningMessages << i18n("Failure loading layer %1 of type: %2.", name, nodeType); return 0; } node->setVisible(visible, true); node->setUserLocked(locked); node->setCollapsed(collapsed); node->setColorLabelIndex(colorLabelIndex); node->setX(x); node->setY(y); node->setName(name); if (! id.isNull()) // if no uuid in file, new one has been generated already node->setUuid(id); if (node->inherits("KisLayer") || node->inherits("KisColorizeMask")) { QString compositeOpName = element.attribute(COMPOSITE_OP, "normal"); node->setCompositeOpId(compositeOpName); } if (node->inherits("KisLayer")) { KisLayer* layer = qobject_cast(node.data()); QBitArray channelFlags = stringToFlags(element.attribute(CHANNEL_FLAGS, ""), colorSpace->channelCount()); layer->setChannelFlags(channelFlags); if (element.hasAttribute(LAYER_STYLE_UUID)) { QString uuidString = element.attribute(LAYER_STYLE_UUID); QUuid uuid(uuidString); if (!uuid.isNull()) { KisPSDLayerStyleSP dumbLayerStyle(new KisPSDLayerStyle()); dumbLayerStyle->setUuid(uuid); layer->setLayerStyle(dumbLayerStyle); } else { warnKrita << "WARNING: Layer style for layer" << layer->name() << "contains invalid UUID" << uuidString; } } } if (node->inherits("KisGroupLayer")) { if (element.hasAttribute(PASS_THROUGH_MODE)) { bool value = element.attribute(PASS_THROUGH_MODE, "0") != "0"; KisGroupLayer *group = qobject_cast(node.data()); group->setPassThroughMode(value); } } if (node->inherits("KisPaintLayer")) { KisPaintLayer* layer = qobject_cast(node.data()); QBitArray channelLockFlags = stringToFlags(element.attribute(CHANNEL_LOCK_FLAGS, ""), colorSpace->channelCount()); layer->setChannelLockFlags(channelLockFlags); bool onionEnabled = element.attribute(ONION_SKIN_ENABLED, "0") == "0" ? false : true; layer->setOnionSkinEnabled(onionEnabled); bool timelineEnabled = element.attribute(VISIBLE_IN_TIMELINE, "0") == "0" ? false : true; layer->setUseInTimeline(timelineEnabled); } if (element.attribute(FILE_NAME).isNull()) { m_d->layerFilenames[node.data()] = name; } else { m_d->layerFilenames[node.data()] = element.attribute(FILE_NAME); } if (element.hasAttribute("selected") && element.attribute("selected") == "true") { m_d->selectedNodes.append(node); } if (element.hasAttribute(KEYFRAME_FILE)) { m_d->keyframeFilenames.insert(node.data(), element.attribute(KEYFRAME_FILE)); } return node; } KisNodeSP KisKraLoader::loadPaintLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); KisPaintLayer* layer; layer = new KisPaintLayer(image, name, opacity, cs); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadFileLayer(const KoXmlElement& element, KisImageSP image, const QString& name, quint32 opacity) { QString filename = element.attribute("source", QString()); if (filename.isNull()) return 0; bool scale = (element.attribute("scale", "true") == "true"); int scalingMethod = element.attribute("scalingmethod", "-1").toInt(); if (scalingMethod < 0) { if (scale) { scalingMethod = KisFileLayer::ToImagePPI; } else { scalingMethod = KisFileLayer::None; } } QString documentPath; if (m_d->document) { documentPath = m_d->document->url().toLocalFile(); } QFileInfo info(documentPath); QString basePath = info.absolutePath(); QString fullPath = basePath + QDir::separator() + filename; // Entering the event loop to show the messagebox will delete the image, so up the ref by one image->ref(); if (!QFileInfo(fullPath).exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "The file associated to a file layer with the name \"%1\" is not found.\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", name, fullPath); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import)); dialog.setDefaultDir(basePath); QString url = dialog.filename(); if (!QFileInfo(basePath).exists()) { filename = url; } else { QDir d(basePath); filename = d.relativeFilePath(url); } } qApp->restoreOverrideCursor(); } KisLayer *layer = new KisFileLayer(image, basePath, filename, (KisFileLayer::ScalingMethod)scalingMethod, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGroupLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; KisGroupLayer* layer; layer = new KisGroupLayer(image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadAdjustmentLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { // XXX: do something with filterversion? Q_UNUSED(cs); QString attr; KisAdjustmentLayer* layer; QString filtername; if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid adjustmentlayer! We should warn about it! warnFile << "No filter in adjustment layer"; return 0; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } KisFilterConfigurationSP kfc = f->defaultConfiguration(); // We'll load the configuration and the selection later. layer = new KisAdjustmentLayer(image, name, kfc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadShapeLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; KoShapeBasedDocumentBase * shapeController = 0; if (m_d->document) { shapeController = m_d->document->shapeController(); } KisShapeLayer* layer = new KisShapeLayer(shapeController, image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGeneratorLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); // XXX: do something with generator version? KisGeneratorLayer* layer; QString generatorname = element.attribute(GENERATOR_NAME); if (generatorname.isNull()) { // XXX: Invalid generator layer! We should warn about it! warnFile << "No generator in generator layer"; return 0; } KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorname); if (!generator) { warnFile << "No generator for generatorname" << generatorname << ""; return 0; // XXX: We don't have this generator. We should warn about it! } KisFilterConfigurationSP kgc = generator->defaultConfiguration(); // We'll load the configuration and the selection later. layer = new KisGeneratorLayer(image, name, kgc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadCloneLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); KisCloneLayerSP layer = new KisCloneLayer(0, image, name, opacity); KisCloneInfo info; if (! (element.attribute(CLONE_FROM_UUID)).isNull()) { info = KisCloneInfo(QUuid(element.attribute(CLONE_FROM_UUID))); } else { if ((element.attribute(CLONE_FROM)).isNull()) { return 0; } else { info = KisCloneInfo(element.attribute(CLONE_FROM)); } } layer->setCopyFromInfo(info); if ((element.attribute(CLONE_TYPE)).isNull()) { return 0; } else { layer->setCopyType((CopyLayerType) element.attribute(CLONE_TYPE).toInt()); } return layer; } KisNodeSP KisKraLoader::loadFilterMask(const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(parent); QString attr; KisFilterMask* mask; QString filtername; // XXX: should we check the version? if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid filter layer! We should warn about it! warnFile << "No filter in filter layer"; return 0; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } KisFilterConfigurationSP kfc = f->defaultConfiguration(); // We'll load the configuration and the selection later. mask = new KisFilterMask(); mask->setFilter(kfc); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransformMask(const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(element); Q_UNUSED(parent); KisTransformMask* mask; /** * We'll load the transform configuration later on a stage * of binary data loading */ mask = new KisTransformMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransparencyMask(const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(element); Q_UNUSED(parent); KisTransparencyMask* mask = new KisTransparencyMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadSelectionMask(KisImageSP image, const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(parent); KisSelectionMaskSP mask = new KisSelectionMask(image); bool active = element.attribute(ACTIVE, "1") == "0" ? false : true; mask->setActive(active); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadColorizeMask(KisImageSP image, const KoXmlElement& element, KisNodeSP parent, const KoColorSpace *colorSpace) { Q_UNUSED(parent); KisColorizeMaskSP mask = new KisColorizeMask(); bool editKeystrokes = element.attribute(COLORIZE_EDIT_KEYSTROKES, "1") == "0" ? false : true; bool showColoring = element.attribute(COLORIZE_SHOW_COLORING, "1") == "0" ? false : true; KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, editKeystrokes, image); KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, showColoring, image); delete mask->setColorSpace(colorSpace); mask->setImage(image); return mask; } void KisKraLoader::loadCompositions(const KoXmlElement& elem, KisImageSP image) { KoXmlNode child; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString name = e.attribute("name"); bool exportEnabled = e.attribute("exportEnabled", "1") == "0" ? false : true; KisLayerCompositionSP composition(new KisLayerComposition(image, name)); composition->setExportEnabled(exportEnabled); KoXmlNode value; for (value = child.lastChild(); !value.isNull(); value = value.previousSibling()) { KoXmlElement e = value.toElement(); QUuid uuid(e.attribute("uuid")); bool visible = e.attribute("visible", "1") == "0" ? false : true; composition->setVisible(uuid, visible); bool collapsed = e.attribute("collapsed", "1") == "0" ? false : true; composition->setCollapsed(uuid, collapsed); } image->addComposition(composition); } } void KisKraLoader::loadAssistantsList(const KoXmlElement &elem) { KoXmlNode child; int count = 0; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString type = e.attribute("type"); QString file_name = e.attribute("filename"); m_d->assistantsFilenames.insert(file_name,type); count++; } } void KisKraLoader::loadGrid(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGridConfig config; config.loadDynamicDataFromXml(domElement); config.loadStaticData(); m_d->document->setGridConfig(config); } void KisKraLoader::loadGuides(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGuidesConfig guides; guides.loadFromXml(domElement); m_d->document->setGuidesConfig(guides); } void KisKraLoader::loadAudio(const KoXmlElement& elem, KisImageSP image) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement qElement = dom.firstChildElement(); QString fileName; if (KisDomUtils::loadValue(qElement, "masterChannelPath", &fileName)) { fileName = QDir::toNativeSeparators(fileName); QDir baseDirectory = QFileInfo(m_d->document->localFilePath()).absoluteDir(); fileName = baseDirectory.absoluteFilePath(fileName); QFileInfo info(fileName); if (!info.exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "Audio channel file \"%1\" doesn't exist!\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", info.fileName(), info.absoluteFilePath()); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { info.setFile(KisImportExportManager::askForAudioFileName(info.absolutePath(), 0)); } qApp->restoreOverrideCursor(); } if (info.exists()) { image->animationInterface()->setAudioChannelFileName(info.absoluteFilePath()); } } bool audioMuted = false; if (KisDomUtils::loadValue(qElement, "audioMuted", &audioMuted)) { image->animationInterface()->setAudioMuted(audioMuted); } qreal audioVolume = 0.5; if (KisDomUtils::loadValue(qElement, "audioVolume", &audioVolume)) { image->animationInterface()->setAudioVolume(audioVolume); } } diff --git a/plugins/paintops/CMakeLists.txt b/plugins/paintops/CMakeLists.txt index 8fe2cc7786..dede3f0b4f 100644 --- a/plugins/paintops/CMakeLists.txt +++ b/plugins/paintops/CMakeLists.txt @@ -1,20 +1,18 @@ -include_directories(libpaintop) -add_subdirectory( libpaintop ) add_subdirectory( defaultpresets ) add_subdirectory( defaultpaintops ) add_subdirectory( hairy ) add_subdirectory( chalk ) add_subdirectory( dynadraw ) add_subdirectory( deform ) add_subdirectory( curvebrush ) add_subdirectory( spray ) add_subdirectory( filterop ) add_subdirectory( experiment ) add_subdirectory( particle ) add_subdirectory( gridbrush ) add_subdirectory( hatching) add_subdirectory( sketch ) add_subdirectory( colorsmudge ) add_subdirectory( roundmarker ) add_subdirectory( tangentnormal ) diff --git a/plugins/tools/karbonplugins/tools/CMakeLists.txt b/plugins/tools/karbonplugins/tools/CMakeLists.txt index 3f5610caee..f484728440 100644 --- a/plugins/tools/karbonplugins/tools/CMakeLists.txt +++ b/plugins/tools/karbonplugins/tools/CMakeLists.txt @@ -1,47 +1,48 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/filterEffectTool ) ########### next target ############### set(karbon_tools_SOURCES KarbonToolsPlugin.cpp KarbonCursor.cpp CalligraphyTool/KarbonCalligraphyTool.cpp CalligraphyTool/KarbonCalligraphyOptionWidget.cpp CalligraphyTool/KarbonCalligraphyToolFactory.cpp CalligraphyTool/KarbonCalligraphicShape.cpp CalligraphyTool/KarbonCalligraphicShapeFactory.cpp CalligraphyTool/KarbonSimplifyPath.cpp KarbonPatternTool.cpp KarbonPatternToolFactory.cpp KarbonPatternEditStrategy.cpp filterEffectTool/KarbonFilterEffectsTool.cpp filterEffectTool/KarbonFilterEffectsToolFactory.cpp filterEffectTool/FilterEffectEditWidget.cpp filterEffectTool/FilterEffectScene.cpp filterEffectTool/FilterEffectSceneItems.cpp filterEffectTool/FilterInputChangeCommand.cpp filterEffectTool/FilterAddCommand.cpp filterEffectTool/FilterRemoveCommand.cpp filterEffectTool/FilterStackSetCommand.cpp filterEffectTool/FilterRegionChangeCommand.cpp filterEffectTool/FilterEffectResource.cpp filterEffectTool/FilterResourceServerProvider.cpp filterEffectTool/FilterRegionEditStrategy.cpp KarbonPatternOptionsWidget.cpp ) ki18n_wrap_ui(karbon_tools_SOURCES filterEffectTool/FilterEffectEditWidget.ui KarbonPatternOptionsWidget.ui + CalligraphyTool/karboncalligraphytooloptions.ui ) qt5_add_resources(karbon_tools_SOURCES karbontools.qrc) add_library(krita_karbontools MODULE ${karbon_tools_SOURCES}) -target_link_libraries(krita_karbontools kritaui kritawidgets KF5::Completion) +target_link_libraries(krita_karbontools kritalibpaintop kritaui kritawidgets KF5::Completion) install(TARGETS krita_karbontools DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp index 7529c187a1..70b8bea80d 100644 --- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp +++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp @@ -1,433 +1,577 @@ /* This file is part of the KDE project Copyright (C) 2008 Fela Winkelmolen 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 "KarbonCalligraphicShape.h" #include #include #include "KarbonSimplifyPath.h" #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include #undef M_PI const qreal M_PI = 3.1415927; -KarbonCalligraphicShape::KarbonCalligraphicShape(qreal caps) +KarbonCalligraphicShape::KarbonCalligraphicShape(KisPropertiesConfigurationSP settings) : m_lastWasFlip(false) - , m_caps(caps) + , m_strokeConfig(settings) { setShapeId(KoPathShapeId); setFillRule(Qt::WindingFill); setBackground(QSharedPointer(new KoColorBackground(QColor(Qt::black)))); setStroke(KoShapeStrokeModelSP()); + m_sizeOption.readOptionSetting(settings); + m_rotationOption.readOptionSetting(settings); + m_sizeOption.resetAllSensors(); + m_rotationOption.resetAllSensors(); + } KarbonCalligraphicShape::KarbonCalligraphicShape(const KarbonCalligraphicShape &rhs) : KoParameterShape(new KoParameterShapePrivate(*rhs.d_func(), this)), m_points(rhs.m_points), m_lastWasFlip(rhs.m_lastWasFlip), - m_caps(rhs.m_caps) + m_strokeConfig(rhs.m_strokeConfig) { } KarbonCalligraphicShape::~KarbonCalligraphicShape() { } +KisPropertiesConfigurationSP KarbonCalligraphicShape::configuration() const +{ + return m_strokeConfig; +} + +void KarbonCalligraphicShape::setConfiguration(KisPropertiesConfigurationSP setting) +{ + m_strokeConfig = setting; + m_sizeOption = KisPressureSizeOption(); + m_sizeOption.readOptionSetting(setting); + m_rotationOption = KisPressureRotationOption(); + m_rotationOption.readOptionSetting(setting); + m_sizeOption.resetAllSensors(); + m_rotationOption.resetAllSensors(); + m_rotationOption.setChecked(true); + m_sizeOption.setChecked(true); + updatePath(this->size()); +} + KoShape *KarbonCalligraphicShape::cloneShape() const { return new KarbonCalligraphicShape(*this); } -void KarbonCalligraphicShape::appendPoint(const QPointF &point, qreal angle, qreal width) +void KarbonCalligraphicShape::appendPoint(KisPaintInformation &paintInfo) { // convert the point from canvas to shape coordinates - QPointF p = point - position(); + paintInfo.setPos(paintInfo.pos()-position()); KarbonCalligraphicPoint *calligraphicPoint = - new KarbonCalligraphicPoint(p, angle, width); + new KarbonCalligraphicPoint(paintInfo); QList handles = this->handles(); - handles.append(p); + handles.append(paintInfo.pos()); setHandles(handles); m_points.append(calligraphicPoint); - appendPointToPath(*calligraphicPoint); - - // make the angle of the first point more in line with the actual - // direction - if (m_points.count() == 4) { - m_points[0]->setAngle(angle); - m_points[1]->setAngle(angle); - m_points[2]->setAngle(angle); + + if (m_points.count()<2) { + appendPointToPath(m_points.count()-1); + } else { + updatePath(QSize()); } } -void KarbonCalligraphicShape::appendPointToPath(const KarbonCalligraphicPoint &p) +void KarbonCalligraphicShape::appendPointToPath(int index) { - qreal dx = std::cos(p.angle()) * p.width(); - qreal dy = std::sin(p.angle()) * p.width(); + KarbonCalligraphicPoint *p = m_points.at(index); + qreal width = calculateWidth(p->paintInfo()); + qreal angle = calculateAngle(p->paintInfo()); + qreal dx = std::cos(angle) * width; + qreal dy = std::sin(angle) * width; // find the outline points - QPointF p1 = p.point() - QPointF(dx / 2, dy / 2); - QPointF p2 = p.point() + QPointF(dx / 2, dy / 2); + QPointF p1 = p->point() - QPointF(dx / 2, dy / 2); + QPointF p2 = p->point() + QPointF(dx / 2, dy / 2); if (pointCount() == 0) { moveTo(p1); lineTo(p2); normalize(); return; } // pointCount > 0 - bool flip = (pointCount() >= 2) ? flipDetected(p1, p2) : false; + bool flip = false; + if (m_points.count()>2 && m_points.count()>index+1) { + QLineF line1(m_points.at(index-1)->point(), m_points.at(index)->point()); + QLineF line2(m_points.at(index)->point(), m_points.at(index+1)->point()); + qreal diffAngle = line1.angle(line2); + if (diffAngle>90.0 && diffAngle<270.0) { + flip = true; + } + } // if there was a flip add additional points if (flip) { - appendPointsToPathAux(p2, p1); - if (pointCount() > 4) { - smoothLastPoints(); + qreal pm1width = calculateWidth(m_points.at(index-1)->paintInfo()); + qreal pm1angle = calculateAngle(m_points.at(index-1)->paintInfo()); + QPointF pm1vec = QPointF(cos(pm1angle)*pm1width / 2, sin(pm1angle)*pm1width / 2); + QPointF p1m1 = m_points.at(index-1)->point() - pm1vec; + QPointF p2m1 = m_points.at(index-1)->point() + pm1vec; + qreal pp1width = calculateWidth(m_points.at(index+1)->paintInfo()); + qreal pp1angle = calculateAngle(m_points.at(index+1)->paintInfo()); + QPointF pp1vec = QPointF(cos(pp1angle)*pp1width / 2, sin(pp1angle)*pp1width / 2); + QPointF p1p1 = m_points.at(index+1)->point() - pp1vec; + QPointF p2p1 = m_points.at(index+1)->point() + pp1vec; + + QPointF intersect; + if (QLineF(p1, p1p1).intersect(QLineF(p2m1, p2), &intersect) == QLineF::BoundedIntersection) { + appendPointsToPathAux(p1, intersect); + appendPointsToPathAux(p2, intersect); + } else if (QLineF(p1m1, p1).intersect(QLineF(p2, p2p1), &intersect) == QLineF::BoundedIntersection) { + appendPointsToPathAux(intersect, p2); + appendPointsToPathAux(intersect, p1); + } else { + appendPointsToPathAux(p1, p2); } - } - appendPointsToPathAux(p1, p2); + } else { + appendPointsToPathAux(p1, p2); + } if (pointCount() > 4) { smoothLastPoints(); if (flip) { int index = pointCount() / 2; // find the last two points KoPathPoint *last1 = pointByIndex(KoPathPointIndex(0, index - 1)); KoPathPoint *last2 = pointByIndex(KoPathPointIndex(0, index)); last1->removeControlPoint1(); last1->removeControlPoint2(); last2->removeControlPoint1(); last2->removeControlPoint2(); m_lastWasFlip = true; } if (m_lastWasFlip) { int index = pointCount() / 2; // find the previous two points KoPathPoint *prev1 = pointByIndex(KoPathPointIndex(0, index - 2)); KoPathPoint *prev2 = pointByIndex(KoPathPointIndex(0, index + 1)); prev1->removeControlPoint1(); prev1->removeControlPoint2(); prev2->removeControlPoint1(); prev2->removeControlPoint2(); if (!flip) { m_lastWasFlip = false; } } } normalize(); // add initial cap if it's the fourth added point // this code is here because this function is called from different places // pointCount() == 8 may causes crashes because it doesn't take possible // flips into account - if (m_points.count() >= 4 && &p == m_points[3]) { + if (m_points.count() >= 4 && p == m_points[3] && configuration()->getFloat("capSize")>0) { addCap(3, 0, 0, true); // duplicate the last point to make the points remain "balanced" // needed to keep all indexes code (else I would need to change // everything in the code...) KoPathPoint *last = pointByIndex(KoPathPointIndex(0, pointCount() - 1)); KoPathPoint *newPoint = new KoPathPoint(this, last->point()); insertPoint(newPoint, KoPathPointIndex(0, pointCount())); close(); } } void KarbonCalligraphicShape::appendPointsToPathAux(const QPointF &p1, const QPointF &p2) { KoPathPoint *pathPoint1 = new KoPathPoint(this, p1); KoPathPoint *pathPoint2 = new KoPathPoint(this, p2); // calculate the index of the insertion position int index = pointCount() / 2; insertPoint(pathPoint2, KoPathPointIndex(0, index)); insertPoint(pathPoint1, KoPathPointIndex(0, index)); } +KarbonCalligraphicPoint *KarbonCalligraphicShape::lastPoint() +{ + return m_points.last(); +} + void KarbonCalligraphicShape::smoothLastPoints() { int index = pointCount() / 2; smoothPoint(index - 2); smoothPoint(index + 1); } void KarbonCalligraphicShape::smoothPoint(const int index) { if (pointCount() < index + 2) { return; } else if (index < 1) { return; } const KoPathPointIndex PREV(0, index - 1); const KoPathPointIndex INDEX(0, index); const KoPathPointIndex NEXT(0, index + 1); QPointF prev = pointByIndex(PREV)->point(); QPointF point = pointByIndex(INDEX)->point(); QPointF next = pointByIndex(NEXT)->point(); QPointF vector = next - prev; qreal dist = (QLineF(prev, next)).length(); // normalize the vector (make it's size equal to 1) if (!qFuzzyCompare(dist + 1, 1)) { vector /= dist; } qreal mult = 0.35; // found by trial and error, might not be perfect... // distance of the control points from the point qreal dist1 = (QLineF(point, prev)).length() * mult; qreal dist2 = (QLineF(point, next)).length() * mult; QPointF vector1 = vector * dist1; QPointF vector2 = vector * dist2; QPointF controlPoint1 = point - vector1; QPointF controlPoint2 = point + vector2; pointByIndex(INDEX)->setControlPoint1(controlPoint1); pointByIndex(INDEX)->setControlPoint2(controlPoint2); } const QRectF KarbonCalligraphicShape::lastPieceBoundingRect() { if (pointCount() < 6) { return QRectF(); } int index = pointCount() / 2; QPointF p1 = pointByIndex(KoPathPointIndex(0, index - 3))->point(); QPointF p2 = pointByIndex(KoPathPointIndex(0, index - 2))->point(); QPointF p3 = pointByIndex(KoPathPointIndex(0, index - 1))->point(); QPointF p4 = pointByIndex(KoPathPointIndex(0, index))->point(); QPointF p5 = pointByIndex(KoPathPointIndex(0, index + 1))->point(); QPointF p6 = pointByIndex(KoPathPointIndex(0, index + 2))->point(); // TODO: also take the control points into account QPainterPath p; p.moveTo(p1); p.lineTo(p2); p.lineTo(p3); p.lineTo(p4); p.lineTo(p5); p.lineTo(p6); return p.boundingRect().translated(position()); } bool KarbonCalligraphicShape::flipDetected(const QPointF &p1, const QPointF &p2) { // detect the flip caused by the angle changing 180 degrees // thus detect the boundary crossing int index = pointCount() / 2; QPointF last1 = pointByIndex(KoPathPointIndex(0, index - 1))->point(); QPointF last2 = pointByIndex(KoPathPointIndex(0, index))->point(); int sum1 = std::abs(ccw(p1, p2, last1) + ccw(p1, last2, last1)); int sum2 = std::abs(ccw(p2, p1, last2) + ccw(p2, last1, last2)); // if there was a flip return sum1 < 2 && sum2 < 2; } int KarbonCalligraphicShape::ccw(const QPointF &p1, const QPointF &p2,const QPointF &p3) { // calculate two times the area of the triangle fomed by the points given qreal area2 = (p2.x() - p1.x()) * (p3.y() - p1.y()) - (p2.y() - p1.y()) * (p3.x() - p1.x()); if (area2 > 0) { return +1; // the points are given in counterclockwise order } else if (area2 < 0) { return -1; // the points are given in clockwise order } else { return 0; // the points form a degenerate triangle } } void KarbonCalligraphicShape::setSize(const QSizeF &newSize) { - // QSizeF oldSize = size(); - // TODO: check + QTransform matrix(resizeMatrix(newSize)); + for (int i = 0; i < m_points.size(); ++i) { + m_points[i]->setPoint(matrix.map(m_points[i]->point())); + } + QPointF width(m_strokeConfig->getDouble("strokeWidth", 10.0), 0.0); + width = matrix.map(width); + //qreal finalWidth = qMax(width.x()+width.y()/2, 1.0); + m_strokeConfig->setProperty("strokeWidth", width.x()); KoParameterShape::setSize(newSize); } QPointF KarbonCalligraphicShape::normalize() { QPointF offset(KoParameterShape::normalize()); QTransform matrix; matrix.translate(-offset.x(), -offset.y()); for (int i = 0; i < m_points.size(); ++i) { m_points[i]->setPoint(matrix.map(m_points[i]->point())); } - + m_lastOffset = offset; return offset; } void KarbonCalligraphicShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); m_points[handleId]->setPoint(point); } void KarbonCalligraphicShape::updatePath(const QSizeF &size) { Q_UNUSED(size); - - QPointF pos = position(); - // remove all points clear(); - setPosition(QPoint(0, 0)); + //KarbonCalligraphicPoint *pLast = m_points.at(0); + m_strokeDistance = new KisDistanceInformation(QPoint(), 0.0); + for (int i=0; i< m_points.count(); i++) { + KarbonCalligraphicPoint *p = m_points.at(i); + { + KisPaintInformation::DistanceInformationRegistrar r = p->paintInfo()->registerDistanceInformation(m_strokeDistance); + // NOTE: only in this scope you can use all the methods of the painting information, including drawingAngle(), distance and speed. + appendPointToPath(i); + } - Q_FOREACH (KarbonCalligraphicPoint *p, m_points) { - appendPointToPath(*p); + // after the point is "painter" it should be added to the distance information as the "previous" point + m_strokeDistance->registerPaintedDab(*p->paintInfo(), KisSpacingInformation(1.0)); } simplifyPath(); QList handles; Q_FOREACH (KarbonCalligraphicPoint *p, m_points) { handles.append(p->point()); } setHandles(handles); - - setPosition(pos); } void KarbonCalligraphicShape::simplifyPath() { if (m_points.count() < 2) { return; } close(); // add final cap addCap(m_points.count() - 2, m_points.count() - 1, pointCount() / 2); // TODO: the error should be proportional to the width // and it shouldn't be a magic number karbonSimplifyPath(this, 0.3); } void KarbonCalligraphicShape::addCap(int index1, int index2, int pointIndex, bool inverted) { QPointF p1 = m_points[index1]->point(); QPointF p2 = m_points[index2]->point(); // TODO: review why spikes can appear with a lower limit QPointF delta = p2 - p1; if (delta.manhattanLength() < 1.0) { return; } QPointF direction = QLineF(QPointF(0, 0), delta).unitVector().p2(); - qreal width = m_points[index2]->width(); - QPointF p = p2 + direction * m_caps * width; + qreal width = calculateWidth(m_points[index2]->paintInfo()); + qreal capSize = configuration()->getFloat("capSize"); + QPointF p = p2 + direction * capSize * width; KoPathPoint *newPoint = new KoPathPoint(this, p); - qreal angle = m_points[index2]->angle(); + qreal angle = calculateAngle(m_points[index2]->paintInfo()); if (inverted) { angle += M_PI; } qreal dx = std::cos(angle) * width; qreal dy = std::sin(angle) * width; newPoint->setControlPoint1(QPointF(p.x() - dx / 2, p.y() - dy / 2)); newPoint->setControlPoint2(QPointF(p.x() + dx / 2, p.y() + dy / 2)); insertPoint(newPoint, KoPathPointIndex(0, pointIndex)); } +qreal KarbonCalligraphicShape::calculateWidth(KisPaintInformation *p) +{ + if (m_sizeOption.isCurveUsed()) { + return m_sizeOption.apply(*p)*configuration()->getDouble("strokeWidth", 10); + } + return configuration()->getDouble("strokeWidth", 10); +} + +qreal KarbonCalligraphicShape::calculateAngle(KisPaintInformation *p) +{ + if (m_rotationOption.isCurveUsed()) { + return (2*M_PI)-m_rotationOption.apply(*p); + } + return 0; +} + QString KarbonCalligraphicShape::pathShapeId() const { return KarbonCalligraphicShapeId; } +bool KarbonCalligraphicShape::saveSvg(SvgSavingContext &context) +{ + context.shapeWriter().startElement("path"); + context.shapeWriter().addAttribute("krita:type", "calligraphic-stroke"); + context.shapeWriter().addAttribute("id", context.getID(this)); + context.shapeWriter().addAttribute("transform", SvgUtil::transformToString(transformation())); + context.shapeWriter().addAttribute("d", this->toString(context.userSpaceTransform())); + SvgStyleWriter::saveSvgStyle(this, context); + QDomDocument doc= QDomDocument(); + QDomElement baseNode = doc.createElement("krita:calligraphic-stroke-data"); + baseNode.setAttribute("xmlns", KoXmlNS::krita); + Q_FOREACH (KarbonCalligraphicPoint *p, m_points) { + QDomElement infoElt = doc.createElement("point"); + p->paintInfo()->toXML(doc, infoElt); + baseNode.appendChild(infoElt); + } + QDomElement configElt = doc.createElement("config"); + configuration()->toXML(doc, configElt); + baseNode.appendChild(configElt); + doc.appendChild(baseNode); + context.shapeWriter().addCompleteElement(doc.toString().toUtf8()); + context.shapeWriter().endElement(); + return true; +} + +bool KarbonCalligraphicShape::loadSvg(const KoXmlElement &element, SvgLoadingContext &context) +{ + Q_UNUSED(context); + + const QString extendedNamespace = element.attribute("krita:type"); + + if (element.tagName() == "path" && !extendedNamespace.isEmpty()) { + + QDomDocument doc = QDomDocument(); + KoXml::asQDomElement(doc, element); + QDomElement root = doc.firstChildElement("path").firstChildElement("krita:calligraphic-stroke-data"); + + QDomElement configElt = root.firstChildElement("config"); + KisPropertiesConfigurationSP config(new KisPropertiesConfiguration()); + + config->fromXML(configElt); + + QDomElement infoElt = root.firstChildElement("point"); + while (!infoElt.isNull()) { + KisPaintInformation paintInfo = KisPaintInformation::fromXML(infoElt); + m_points.append(new KarbonCalligraphicPoint(paintInfo)); + infoElt = infoElt.nextSiblingElement("point"); + } + setConfiguration(config); + return true; + } + return false; +} + void KarbonCalligraphicShape::simplifyGuidePath() { // do not attempt to simplify if there are too few points if (m_points.count() < 3) { return; } QList points; Q_FOREACH (KarbonCalligraphicPoint *p, m_points) { points.append(p->point()); } // cumulative data used to determine if the point can be removed qreal widthChange = 0; qreal directionChange = 0; QList::iterator i = m_points.begin() + 2; while (i != m_points.end() - 1) { QPointF point = (*i)->point(); - qreal width = (*i)->width(); - qreal prevWidth = (*(i - 1))->width(); + qreal width = calculateWidth((*i)->paintInfo()); + qreal prevWidth = calculateWidth((*(i - 1))->paintInfo()); qreal widthDiff = width - prevWidth; widthDiff /= qMax(width, prevWidth); qreal directionDiff = 0; if ((i + 1) != m_points.end()) { QPointF prev = (*(i - 1))->point(); QPointF next = (*(i + 1))->point(); directionDiff = QLineF(prev, point).angleTo(QLineF(point, next)); if (directionDiff > 180) { directionDiff -= 360; } } if (directionChange * directionDiff >= 0 && qAbs(directionChange + directionDiff) < 20 && widthChange * widthDiff >= 0 && qAbs(widthChange + widthDiff) < 0.1) { // deleted point + //(*i)->paintInfo(); delete *i; i = m_points.erase(i); directionChange += directionDiff; widthChange += widthDiff; } else { // keep point directionChange = 0; widthChange = 0; ++i; } } updatePath(QSizeF()); } diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.h b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.h index 356dbcda67..e3326be7e4 100644 --- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.h +++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.h @@ -1,151 +1,183 @@ /* This file is part of the KDE project Copyright (C) 2008 Fela Winkelmolen 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 KARBONCALLIGRAPHICSHAPE_H #define KARBONCALLIGRAPHICSHAPE_H #include +#include +#include +#include +#include +#include #define KarbonCalligraphicShapeId "KarbonCalligraphicShape" class KarbonCalligraphicPoint { public: - KarbonCalligraphicPoint(const QPointF &point, qreal angle, qreal width) - : m_point(point), m_angle(angle), m_width(width) {} + KarbonCalligraphicPoint(KisPaintInformation &paintInfo) + : m_paintInfo(paintInfo) {} QPointF point() const { - return m_point; + return m_paintInfo.pos(); } - qreal angle() const + KisPaintInformation* paintInfo() { - return m_angle; + return &m_paintInfo; } - qreal width() const + + void setPaintInfo(const KisPaintInformation &paintInfo) { - return m_width; + m_paintInfo = paintInfo; } void setPoint(const QPointF &point) { - m_point = point; - } - void setAngle(qreal angle) - { - m_angle = angle; + m_paintInfo.setPos(point); } private: - QPointF m_point; // in shape coordinates - qreal m_angle; - qreal m_width; + KisPaintInformation m_paintInfo; }; /*class KarbonCalligraphicShape::Point { public: KoPainterPath(KoPathPoint *point) : m_prev(point), m_next(0) {} // calculates the effective point QPointF point() { if (m_next = 0) return m_prev.point(); // m_next != 0 qDebug() << "not implemented yet!!!!"; return QPointF(); } private: KoPainterPath m_prev; KoPainterPath m_next; qreal m_percentage; };*/ // the indexes of the path will be similar to: // 7--6--5--4 <- pointCount() / 2 // start | | end ==> (direction of the stroke) // 0--1--2--3 -class KarbonCalligraphicShape : public KoParameterShape +class KarbonCalligraphicShape : public KoParameterShape, public SvgShape { public: - explicit KarbonCalligraphicShape(qreal caps = 0.0); + explicit KarbonCalligraphicShape(KisPropertiesConfigurationSP settings); ~KarbonCalligraphicShape(); + /** + * @brief configuration holds the interpretation of the paintinfo, + * this is similar to a vector version of a paintop. + * @return the configuration that is currently held by the object. + */ + KisPropertiesConfigurationSP configuration() const; + + /** + * @brief setConfiguration + * Set the configuration of the paintinfo interpretation(the paintop, basically) + * This will update the full stroke. + * @param setting + */ + void setConfiguration(KisPropertiesConfigurationSP setting); + KoShape* cloneShape() const override; - void appendPoint(const QPointF &p1, qreal angle, qreal width); - void appendPointToPath(const KarbonCalligraphicPoint &p); + void appendPoint(KisPaintInformation &paintInfo); + void appendPointToPath(int index); + + KarbonCalligraphicPoint* lastPoint(); // returns the bounding rect of whan needs to be repainted // after new points are added const QRectF lastPieceBoundingRect(); void setSize(const QSizeF &newSize); //virtual QPointF normalize(); QPointF normalize(); void simplifyPath(); void simplifyGuidePath(); // reimplemented virtual QString pathShapeId() const; + bool saveSvg(SvgSavingContext &context); + bool loadSvg(const KoXmlElement &element, SvgLoadingContext &context); + protected: // reimplemented void moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers = Qt::NoModifier); // reimplemented void updatePath(const QSizeF &size); private: KarbonCalligraphicShape(const KarbonCalligraphicShape &rhs); // auxiliary function that actually insererts the points // without doing any additional checks // the points should be given in canvas coordinates void appendPointsToPathAux(const QPointF &p1, const QPointF &p2); // function to detect a flip, given the points being inserted bool flipDetected(const QPointF &p1, const QPointF &p2); void smoothLastPoints(); void smoothPoint(const int index); // determine whether the points given are in counterclockwise order or not // returns +1 if they are, -1 if they are given in clockwise order // and 0 if they form a degenerate triangle static int ccw(const QPointF &p1, const QPointF &p2, const QPointF &p3); // void addCap(int index1, int index2, int pointIndex, bool inverted = false); + /** + * @brief calculateWidth calculate the current width. + * @param p the point for which you wish to calculate the width. + * @return the width as modulated by the paintinfo. + */ + qreal calculateWidth(KisPaintInformation *p); + qreal calculateAngle(KisPaintInformation *p); + // the actual data then determines it's shape (guide path + data for points) + KisDistanceInformation *m_strokeDistance; QList m_points; bool m_lastWasFlip; - qreal m_caps; + KisPropertiesConfigurationSP m_strokeConfig; + QPointF m_lastOffset; + KisPressureSizeOption m_sizeOption; + KisPressureRotationOption m_rotationOption; }; #endif // KARBONCALLIGRAPHICSHAPE_H diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShapeFactory.cpp b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShapeFactory.cpp index b0209d5ded..9e36b4f34e 100644 --- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShapeFactory.cpp +++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShapeFactory.cpp @@ -1,55 +1,61 @@ /* This file is part of the KDE project * Copyright (C) 2008 Fela Winkelmolen * * 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 "KarbonCalligraphicShapeFactory.h" #include "KarbonCalligraphicShape.h" #include #include #include +#include KarbonCalligraphicShapeFactory::KarbonCalligraphicShapeFactory() : KoShapeFactoryBase(KarbonCalligraphicShapeId, i18n("A calligraphic shape")) { setToolTip(i18n("Calligraphic Shape")); setIconName(koIconNameCStr("calligraphy")); setLoadingPriority(1); setHidden(true); + + QList > elementNamesList; + elementNamesList.append(qMakePair(QString(KoXmlNS::svg), QStringList("krita:calligraphic-stroke"))); + setXmlElements(elementNamesList); } KarbonCalligraphicShapeFactory::~KarbonCalligraphicShapeFactory() { } KoShape *KarbonCalligraphicShapeFactory::createDefaultShape(KoDocumentResourceManager *) const { - KarbonCalligraphicShape *path = new KarbonCalligraphicShape(); + KisPropertiesConfigurationSP settings = new KisPropertiesConfiguration(); + KarbonCalligraphicShape *path = new KarbonCalligraphicShape(settings); // FIXME: add points path->setShapeId(KarbonCalligraphicShapeId); return path; } bool KarbonCalligraphicShapeFactory::supports(const KoXmlElement &e, KoShapeLoadingContext &context) const { Q_UNUSED(e); Q_UNUSED(context); return false; } diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyOptionWidget.cpp b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyOptionWidget.cpp index 3c453a2110..b12d5f2707 100644 --- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyOptionWidget.cpp +++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyOptionWidget.cpp @@ -1,614 +1,548 @@ /* This file is part of the KDE project Copyright (C) 2008 Fela Winkelmolen 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 "KarbonCalligraphyOptionWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include -#include +#include #include +#include + +#include #include "kis_double_parse_spin_box.h" #include "kis_int_parse_spin_box.h" - +#include "ui_karboncalligraphytooloptions.h" +#include "kis_slider_spin_box.h" +#include +#include +#include +#include /* Profiles are saved in karboncalligraphyrc In the group "General", profile is the name of profile used Every profile is described in a group, the name of which is "ProfileN" Starting to count from 0 onwards (NOTE: the index in profiles is different from the N) Default profiles are added by the function addDefaultProfiles(), once they have been added, the entry defaultProfilesAdded in the "General" group is set to true TODO: add a reset defaults option? */ // name of the configuration file const QString RCFILENAME = "karboncalligraphyrc"; +class KarbonCalligraphyToolOptions: public QWidget, public Ui::WdgCalligraphyToolOptions +{ +public: + KarbonCalligraphyToolOptions(QWidget *parent = 0) + : QWidget(parent) { + setupUi(this); + } +}; + KarbonCalligraphyOptionWidget::KarbonCalligraphyOptionWidget() : m_changingProfile(false) { - QGridLayout *layout = new QGridLayout(this); - layout->setContentsMargins(0, 0, 0, 0); - - m_comboBox = new KComboBox(this); - layout->addWidget(m_comboBox, 0, 0); - - m_saveButton = new QToolButton(this); - m_saveButton->setToolTip(i18n("Save profile as...")); - m_saveButton->setIcon(koIcon("document-save-as")); - layout->addWidget(m_saveButton, 0, 1); - - m_removeButton = new QToolButton(this); - m_removeButton->setToolTip(i18n("Remove profile")); - m_removeButton->setIcon(koIcon("list-remove")); - layout->addWidget(m_removeButton, 0, 2); - - QGridLayout *detailsLayout = new QGridLayout(); - detailsLayout->setContentsMargins(0, 0, 0, 0); - detailsLayout->setVerticalSpacing(0); - - m_usePath = new QCheckBox(i18n("&Follow selected path"), this); - detailsLayout->addWidget(m_usePath, 0, 0, 1, 4); - - m_usePressure = new QCheckBox(i18n("Use tablet &pressure"), this); - detailsLayout->addWidget(m_usePressure, 1, 0, 1, 4); - - QLabel *widthLabel = new QLabel(i18n("Width:"), this); - widthLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); - m_widthBox = new KisDoubleParseSpinBox(this); - m_widthBox->setRange(0.0, 999.0); - widthLabel->setBuddy(m_widthBox); - detailsLayout->addWidget(widthLabel, 2, 2); - detailsLayout->addWidget(m_widthBox, 2, 3); - - QLabel *thinningLabel = new QLabel(i18n("Thinning:"), this); - thinningLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); - m_thinningBox = new KisDoubleParseSpinBox(this); - m_thinningBox->setRange(-1.0, 1.0); - m_thinningBox->setSingleStep(0.1); - thinningLabel->setBuddy(m_thinningBox); - detailsLayout->addWidget(thinningLabel, 2, 0); - detailsLayout->addWidget(m_thinningBox, 2, 1); - - m_useAngle = new QCheckBox(i18n("Use tablet &angle"), this); - detailsLayout->addWidget(m_useAngle, 3, 0, 1, 4); - - QLabel *angleLabel = new QLabel(i18n("Angle:"), this); - angleLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); - m_angleBox = new KisIntParseSpinBox(this); - m_angleBox->setRange(0, 179); - m_angleBox->setWrapping(true); - angleLabel->setBuddy(m_angleBox); - detailsLayout->addWidget(angleLabel, 4, 0); - detailsLayout->addWidget(m_angleBox, 4, 1); - - QLabel *fixationLabel = new QLabel(i18n("Fixation:"), this); - fixationLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); - m_fixationBox = new KisDoubleParseSpinBox(this); - m_fixationBox->setRange(0.0, 1.0); - m_fixationBox->setSingleStep(0.1); - fixationLabel->setBuddy(m_fixationBox); - detailsLayout->addWidget(fixationLabel, 5, 0); - detailsLayout->addWidget(m_fixationBox, 5, 1); - - QLabel *capsLabel = new QLabel(i18n("Caps:"), this); - capsLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); - m_capsBox = new KisDoubleParseSpinBox(this); - m_capsBox->setRange(0.0, 2.0); - m_capsBox->setSingleStep(0.03); - capsLabel->setBuddy(m_capsBox); - detailsLayout->addWidget(capsLabel, 5, 2); - detailsLayout->addWidget(m_capsBox, 5, 3); - - QLabel *massLabel = new QLabel(i18n("Mass:"), this); - massLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); - m_massBox = new KisDoubleParseSpinBox(this); - m_massBox->setRange(0.0, 20.0); - m_massBox->setDecimals(1); - massLabel->setBuddy(m_massBox); - detailsLayout->addWidget(massLabel, 6, 0); - detailsLayout->addWidget(m_massBox, 6, 1); - - QLabel *dragLabel = new QLabel(i18n("Drag:"), this); - dragLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); - m_dragBox = new KisDoubleParseSpinBox(this); - m_dragBox->setRange(0.0, 1.0); - m_dragBox->setSingleStep(0.1); - dragLabel->setBuddy(m_dragBox); - detailsLayout->addWidget(dragLabel, 6, 2); - detailsLayout->addWidget(m_dragBox, 6, 3); - - layout->addLayout(detailsLayout, 1, 0, 1, 3); - layout->setRowStretch(2, 1); + m_options = new KarbonCalligraphyToolOptions(); + m_options->setupUi(this); + + m_options->sldDistanceInterval->setPrefix(i18n("Distance: ")); + //the distance is in SCREEN coordinates! + m_options->sldDistanceInterval->setSuffix(i18n("px")); + m_options->sldDistanceInterval->setRange(1, 1000, 2); + m_options->sldDistanceInterval->setSingleStep(1); + + m_options->sldTimeInterval->setPrefix(i18n("Time: ")); + m_options->sldTimeInterval->setSuffix(i18n("ms")); + m_options->sldTimeInterval->setRange(1, 1000, 2); + m_options->sldTimeInterval->setSingleStep(1); + + m_options->sldCaps->setPrefix(i18n("Caps: ")); + m_options->sldCaps->setRange(0.0, 2.0, 2); + m_options->sldCaps->setSingleStep(0.03); + + m_sizeOption = new KisCurveOptionWidget(new KisPressureSizeOption(), i18n("0%"), i18n("100%")); + m_rotationOption = new KisCurveOptionWidget(new KisPressureRotationOption(), i18n("-180°"), i18n("180°")); + //m_ratioOption = new KisCurveOptionWidget(new KisPressureRatioOption(), i18n("0%"), i18n("100%")); + m_rotationOption->setChecked(true); + m_sizeOption->setChecked(true); + + m_options->bnSize->setIcon(kisIcon("brush_size")); + m_options->bnSize->setPopupWidget(m_sizeOption->configurationPage()); + + m_options->bnRotation->setIcon(kisIcon("brush_rotation")); + m_options->bnRotation->setPopupWidget(m_rotationOption->configurationPage()); + + m_options->bnRatio->hide(); + //m_options->bnRatio->setIcon(kisIcon("brush_ratio")); + //m_options->bnRatio->setPopupWidget(m_ratioOption->configurationPage()); createConnections(); addDefaultProfiles(); // if they are already added does nothing loadProfiles(); } KarbonCalligraphyOptionWidget::~KarbonCalligraphyOptionWidget() { qDeleteAll(m_profiles); } void KarbonCalligraphyOptionWidget::emitAll() { - emit usePathChanged(m_usePath->isChecked()); - emit usePressureChanged(m_usePressure->isChecked()); - emit useAngleChanged(m_useAngle->isChecked()); - emit widthChanged(m_widthBox->value()); - emit thinningChanged(m_thinningBox->value()); - emit angleChanged(m_angleBox->value()); - emit fixationChanged(m_fixationBox->value()); - emit capsChanged(m_capsBox->value()); - emit massChanged(m_massBox->value()); - emit dragChanged(m_dragBox->value()); + emit usePathChanged(m_options->rdAdjustPath->isChecked()); + emit useAssistantChanged(m_options->rdAdjustAssistant->isChecked()); + emit useNoAdjustChanged(m_options->rdNoAdjust->isChecked()); + emit generateSettings(); + emit smoothTimeChanged(m_options->sldTimeInterval->value()); + emit smoothDistanceChanged(m_options->sldDistanceInterval->value()); } void KarbonCalligraphyOptionWidget::loadProfile(const QString &name) { if (m_changingProfile) { return; } // write the new profile in the config file KConfig config(RCFILENAME); KConfigGroup generalGroup(&config, "General"); generalGroup.writeEntry("profile", name); config.sync(); // and load it loadCurrentProfile(); // don't show Current if it isn't selected if (name != i18n("Current")) { removeProfile(i18n("Current")); } } void KarbonCalligraphyOptionWidget::updateCurrentProfile() { if (!m_changingProfile) { saveProfile("Current"); } } void KarbonCalligraphyOptionWidget::saveProfileAs() { QString name; // loop until a valid name is entered or the user cancelled while (1) { bool ok; name = QInputDialog::getText(this, i18n("Profile name"), i18n("Please insert the name by which " "you want to save this profile:"), QLineEdit::Normal, QString(), &ok); if (!ok) { return; } if (name.isEmpty() || name == i18n("Current")) { KMessageBox::sorry(this, i18n("Sorry, the name you entered is invalid."), i18nc("invalid profile name", "Invalid name.")); // try again saveProfileAs(); continue; // ask again } if (m_profiles.contains(name)) { int ret = KMessageBox::warningYesNo(this, i18n("A profile with that name already exists.\n" "Do you want to overwrite it?")); if (ret == KMessageBox::Yes) { break; // exit while loop (save profile) } // else ask again } else { // the name is valid break; // exit while loop (save profile) } } saveProfile(name); } void KarbonCalligraphyOptionWidget::removeProfile() { - removeProfile(m_comboBox->currentText()); + removeProfile(m_options->cmbProfiles->currentText()); } -void KarbonCalligraphyOptionWidget::toggleUseAngle(bool checked) +void KarbonCalligraphyOptionWidget::generateSettings() { - m_angleBox->setEnabled(! checked); -} - -void KarbonCalligraphyOptionWidget::increaseWidth() -{ - m_widthBox->setValue(m_widthBox->value() + 1); -} - -void KarbonCalligraphyOptionWidget::decreaseWidth() -{ - m_widthBox->setValue(m_widthBox->value() - 1); -} - -void KarbonCalligraphyOptionWidget::increaseAngle() -{ - m_angleBox->setValue((m_angleBox->value() + 3) % 180); -} - -void KarbonCalligraphyOptionWidget::decreaseAngle() -{ - m_angleBox->setValue((m_angleBox->value() - 3) % 180); + KisPropertiesConfigurationSP settings = new KisPropertiesConfiguration(); + settings->setProperty("capSize", m_options->sldCaps->value()); + m_sizeOption->writeOptionSetting(settings); + m_rotationOption->writeOptionSetting(settings); + emit settingsChanged(settings); } /****************************************************************************** ************************* Convenience Functions ****************************** ******************************************************************************/ void KarbonCalligraphyOptionWidget::createConnections() { - connect(m_comboBox, SIGNAL(currentIndexChanged(QString)), + connect(m_options->cmbProfiles, SIGNAL(currentIndexChanged(QString)), SLOT(loadProfile(QString))); // propagate changes - connect(m_usePath, SIGNAL(toggled(bool)), + connect(m_options->rdAdjustPath, SIGNAL(toggled(bool)), SIGNAL(usePathChanged(bool))); + connect(m_options->rdAdjustAssistant, SIGNAL(toggled(bool)), + SIGNAL(useAssistantChanged(bool))); + connect(m_options->rdNoAdjust, SIGNAL(toggled(bool)), + SIGNAL(useNoAdjustChanged(bool))); - connect(m_usePressure, SIGNAL(toggled(bool)), - SIGNAL(usePressureChanged(bool))); - - connect(m_useAngle, SIGNAL(toggled(bool)), - SIGNAL(useAngleChanged(bool))); + connect(m_options->sldCaps, SIGNAL(valueChanged(double)), + SLOT(generateSettings())); - connect(m_widthBox, SIGNAL(valueChanged(double)), - SIGNAL(widthChanged(double))); + connect(m_options->sldTimeInterval, SIGNAL(valueChanged(double)), + SIGNAL(smoothTimeChanged(double))); + connect(m_options->sldDistanceInterval, SIGNAL(valueChanged(double)), + SIGNAL(smoothDistanceChanged(double))); - connect(m_thinningBox, SIGNAL(valueChanged(double)), - SIGNAL(thinningChanged(double))); - - connect(m_angleBox, SIGNAL(valueChanged(int)), - SIGNAL(angleChanged(int))); - - connect(m_fixationBox, SIGNAL(valueChanged(double)), - SIGNAL(fixationChanged(double))); - - connect(m_capsBox, SIGNAL(valueChanged(double)), - SIGNAL(capsChanged(double))); - - connect(m_massBox, SIGNAL(valueChanged(double)), - SIGNAL(massChanged(double))); - - connect(m_dragBox, SIGNAL(valueChanged(double)), - SIGNAL(dragChanged(double))); + connect(m_sizeOption, SIGNAL(sigSettingChanged()), SLOT(generateSettings())); + connect(m_rotationOption, SIGNAL(sigSettingChanged()), SLOT(generateSettings())); // update profile - connect(m_usePath, SIGNAL(toggled(bool)), - SLOT(updateCurrentProfile())); - - connect(m_usePressure, SIGNAL(toggled(bool)), - SLOT(updateCurrentProfile())); - - connect(m_useAngle, SIGNAL(toggled(bool)), - SLOT(updateCurrentProfile())); - - connect(m_widthBox, SIGNAL(valueChanged(double)), - SLOT(updateCurrentProfile())); - - connect(m_thinningBox, SIGNAL(valueChanged(double)), + connect(m_options->rdAdjustPath, SIGNAL(toggled(bool)), SLOT(updateCurrentProfile())); - - connect(m_angleBox, SIGNAL(valueChanged(int)), + connect(m_options->rdAdjustAssistant, SIGNAL(toggled(bool)), SLOT(updateCurrentProfile())); - - connect(m_fixationBox, SIGNAL(valueChanged(double)), + connect(m_options->rdNoAdjust, SIGNAL(toggled(bool)), SLOT(updateCurrentProfile())); - - connect(m_capsBox, SIGNAL(valueChanged(double)), + connect(m_options->sldCaps, SIGNAL(valueChanged(double)), SLOT(updateCurrentProfile())); - - connect(m_massBox, SIGNAL(valueChanged(double)), + connect(m_options->sldTimeInterval, SIGNAL(valueChanged(double)), SLOT(updateCurrentProfile())); - - connect(m_dragBox, SIGNAL(valueChanged(double)), + connect(m_options->sldDistanceInterval, SIGNAL(valueChanged(double)), SLOT(updateCurrentProfile())); + connect(m_sizeOption, SIGNAL(sigSettingChanged()), SLOT(updateCurrentProfile())); + connect(m_rotationOption, SIGNAL(sigSettingChanged()), SLOT(updateCurrentProfile())); - connect(m_saveButton, SIGNAL(clicked()), SLOT(saveProfileAs())); - connect(m_removeButton, SIGNAL(clicked()), SLOT(removeProfile())); + connect(m_options->bnSaveProfile, SIGNAL(clicked()), SLOT(saveProfileAs())); + connect(m_options->bnRemoveProfile, SIGNAL(clicked()), SLOT(removeProfile())); // visualization - connect(m_useAngle, SIGNAL(toggled(bool)), SLOT(toggleUseAngle(bool))); + //connect(m_useAngle, SIGNAL(toggled(bool)), SLOT(toggleUseAngle(bool))); } void KarbonCalligraphyOptionWidget::addDefaultProfiles() { // check if the profiles where already added KConfig config(RCFILENAME); KConfigGroup generalGroup(&config, "General"); if (generalGroup.readEntry("defaultProfilesAdded", false)) { return; } KConfigGroup profile0(&config, "Profile0"); profile0.writeEntry("name", i18n("Mouse")); profile0.writeEntry("usePath", false); - profile0.writeEntry("usePressure", false); - profile0.writeEntry("useAngle", false); - profile0.writeEntry("width", 30.0); - profile0.writeEntry("thinning", 0.2); - profile0.writeEntry("angle", 30); - profile0.writeEntry("fixation", 1.0); + profile0.writeEntry("useAssistants", false); profile0.writeEntry("caps", 0.0); - profile0.writeEntry("mass", 3.0); - profile0.writeEntry("drag", 0.7); + profile0.writeEntry("timeInterval", 0); + profile0.writeEntry("distanceInterval", 0); + QString curveConfigMouse("\n\ntrue\n " + "true\n " + "\n\n]]>\n" + "false\n" + "true\n" + "1\n" + "" + "\n\n" + "0,0;1,1;\n\n]]>" + "false\n" + "true\n" + "1\n"); + profile0.writeEntry("curveConfig", curveConfigMouse); KConfigGroup profile1(&config, "Profile1"); - profile1.writeEntry("name", i18n("Graphics Pen")); - profile1.writeEntry("width", 50.0); + profile1.writeEntry("name", i18n("Brush")); profile1.writeEntry("usePath", false); - profile1.writeEntry("usePressure", false); - profile1.writeEntry("useAngle", false); - profile1.writeEntry("thinning", 0.2); - profile1.writeEntry("angle", 30); - profile1.writeEntry("fixation", 1.0); + profile1.writeEntry("useAssistants", false); profile1.writeEntry("caps", 0.0); - profile1.writeEntry("mass", 1.0); - profile1.writeEntry("drag", 0.9); - - generalGroup.writeEntry("profile", i18n("Mouse")); + profile1.writeEntry("timeInterval", 50); + profile1.writeEntry("distanceInterval", 15); + QString curveConfigBrush("\n\ntrue\n " + "true\n " + "\n\n]]>\n" + "true\n" + "true\n" + "1\n" + "" + "\n\n" + "0,0;0.375,0.25;0.625,0.75;1,1;\n\n]]>" + "true\n" + "true\n" + "1\n"); + profile1.writeEntry("curveConfig", curveConfigBrush); + + KConfigGroup profile2(&config, "Profile2"); + profile2.writeEntry("name", i18n("GPen")); + profile2.writeEntry("usePath", false); + profile2.writeEntry("useAssistants", false); + profile2.writeEntry("caps", 0.0); + profile2.writeEntry("timeInterval", 50); + profile2.writeEntry("distanceInterval", 15); + QString curveConfigGpen("\n\ntrue\n " + "true\n " + "\n\n]]>\n" + "true\n" + "true\n" + "1\n" + "" + "\n\n" + "0,0;0.625,0.375;1,1;\n\n]]>" + "true\n" + "true\n" + "1\n"); + profile2.writeEntry("curveConfig", curveConfigGpen); + + generalGroup.writeEntry("profile", i18n("Brush")); generalGroup.writeEntry("defaultProfilesAdded", true); config.sync(); } void KarbonCalligraphyOptionWidget::loadProfiles() { KConfig config(RCFILENAME); // load profiles as long as they are present int i = 0; while (1) { // forever KConfigGroup profileGroup(&config, "Profile" + QString::number(i)); // invalid profile, assume we reached the last one if (!profileGroup.hasKey("name")) { break; } Profile *profile = new Profile; profile->index = i; - profile->name = profileGroup.readEntry("name", QString()); - profile->usePath = profileGroup.readEntry("usePath", false); - profile->usePressure = profileGroup.readEntry("usePressure", false); - profile->useAngle = profileGroup.readEntry("useAngle", false); - profile->width = profileGroup.readEntry("width", 30.0); - profile->thinning = profileGroup.readEntry("thinning", 0.2); - profile->angle = profileGroup.readEntry("angle", 30); - profile->fixation = profileGroup.readEntry("fixation", 0.0); - profile->caps = profileGroup.readEntry("caps", 0.0); - profile->mass = profileGroup.readEntry("mass", 3.0); - profile->drag = profileGroup.readEntry("drag", 0.7); + profile->name = profileGroup.readEntry("name", QString()); + profile->usePath = profileGroup.readEntry("usePath", false); + profile->useAssistants = profileGroup.readEntry("useAssistants", false); + profile->caps = profileGroup.readEntry("caps", 0.0); + profile->timeInterval = profileGroup.readEntry("timeInterval", 0.0); + profile->distanceInterval = profileGroup.readEntry("distanceInterval", 0.0); + profile->curveConfig = new KisPropertiesConfiguration(); + profile->curveConfig->fromXML(profileGroup.readEntry("curveConfig", QString())); m_profiles.insert(profile->name, profile); ++i; } m_changingProfile = true; ProfileMap::const_iterator it = m_profiles.constBegin(); ProfileMap::const_iterator lastIt = m_profiles.constEnd(); for (; it != lastIt; ++it) { - m_comboBox->addItem(it.key()); + m_options->cmbProfiles->addItem(it.key()); } m_changingProfile = false; loadCurrentProfile(); } void KarbonCalligraphyOptionWidget::loadCurrentProfile() { KConfig config(RCFILENAME); KConfigGroup generalGroup(&config, "General"); QString currentProfile = generalGroup.readEntry("profile", QString()); // find the index needed by the comboBox int index = profilePosition(currentProfile); if (currentProfile.isEmpty() || index < 0) { return; } - m_comboBox->setCurrentIndex(index); + m_options->cmbProfiles->setCurrentIndex(index); Profile *profile = m_profiles[currentProfile]; m_changingProfile = true; - m_usePath->setChecked(profile->usePath); - m_usePressure->setChecked(profile->usePressure); - m_useAngle->setChecked(profile->useAngle); - m_widthBox->setValue(profile->width); - m_thinningBox->setValue(profile->thinning); - m_angleBox->setValue(profile->angle); - m_fixationBox->setValue(profile->fixation); - m_capsBox->setValue(profile->caps); - m_massBox->setValue(profile->mass); - m_dragBox->setValue(profile->drag); + m_options->rdAdjustPath->setChecked(profile->usePath); + m_options->rdAdjustAssistant->setChecked(profile->useAssistants); + if (profile->useAssistants == false && profile->usePath==false) { + m_options->rdNoAdjust->setChecked(true); + } + m_options->sldCaps->setValue(profile->caps); + m_options->sldTimeInterval->setValue(profile->timeInterval); + m_options->sldDistanceInterval->setValue(profile->distanceInterval); + m_sizeOption->readOptionSetting(profile->curveConfig); + m_rotationOption->readOptionSetting(profile->curveConfig); m_changingProfile = false; } void KarbonCalligraphyOptionWidget::saveProfile(const QString &name) { Profile *profile = new Profile; profile->name = name; - profile->usePath = m_usePath->isChecked(); - profile->usePressure = m_usePressure->isChecked(); - profile->useAngle = m_useAngle->isChecked(); - profile->width = m_widthBox->value(); - profile->thinning = m_thinningBox->value(); - profile->angle = m_angleBox->value(); - profile->fixation = m_fixationBox->value(); - profile->caps = m_capsBox->value(); - profile->mass = m_massBox->value(); - profile->drag = m_dragBox->value(); + profile->usePath = m_options->rdAdjustPath->isChecked(); + profile->caps = m_options->sldCaps->value(); + profile->useAssistants = m_options->rdAdjustAssistant->isChecked(); + profile->timeInterval = m_options->sldTimeInterval->value(); + profile->distanceInterval = m_options->sldDistanceInterval->value(); + profile->curveConfig = new KisPropertiesConfiguration(); + m_sizeOption->writeOptionSetting(profile->curveConfig); + m_rotationOption->writeOptionSetting(profile->curveConfig); if (m_profiles.contains(name)) { // there is already a profile with the same name, overwrite profile->index = m_profiles[name]->index; m_profiles.insert(name, profile); } else { // it is a new profile profile->index = m_profiles.count(); m_profiles.insert(name, profile); // add the profile to the combobox QString dbg; - for (int i = 0; i < m_comboBox->count(); ++i) { - dbg += m_comboBox->itemText(i) + ' '; + for (int i = 0; i < m_options->cmbProfiles->count(); ++i) { + dbg += m_options->cmbProfiles->itemText(i) + ' '; } int pos = profilePosition(name); m_changingProfile = true; - m_comboBox->insertItem(pos, name); + m_options->cmbProfiles->insertItem(pos, name); m_changingProfile = false; - for (int i = 0; i < m_comboBox->count(); ++i) { - dbg += m_comboBox->itemText(i) + ' '; + for (int i = 0; i < m_options->cmbProfiles->count(); ++i) { + dbg += m_options->cmbProfiles->itemText(i) + ' '; } } KConfig config(RCFILENAME); QString str = "Profile" + QString::number(profile->index); KConfigGroup profileGroup(&config, str); profileGroup.writeEntry("name", name); profileGroup.writeEntry("usePath", profile->usePath); - profileGroup.writeEntry("usePressure", profile->usePressure); - profileGroup.writeEntry("useAngle", profile->useAngle); - profileGroup.writeEntry("width", profile->width); - profileGroup.writeEntry("thinning", profile->thinning); - profileGroup.writeEntry("angle", profile->angle); - profileGroup.writeEntry("fixation", profile->fixation); + profileGroup.writeEntry("useAssistants", profile->useAssistants); profileGroup.writeEntry("caps", profile->caps); - profileGroup.writeEntry("mass", profile->mass); - profileGroup.writeEntry("drag", profile->drag); + profileGroup.writeEntry("timeInterval", profile->timeInterval); + profileGroup.writeEntry("distanceInterval", profile->distanceInterval); + profileGroup.writeEntry("curveConfig", profile->curveConfig->toXML()); KConfigGroup generalGroup(&config, "General"); generalGroup.writeEntry("profile", name); config.sync(); - m_comboBox->setCurrentIndex(profilePosition(name)); + m_options->cmbProfiles->setCurrentIndex(profilePosition(name)); } void KarbonCalligraphyOptionWidget::removeProfile(const QString &name) { int index = profilePosition(name); if (index < 0) { return; // no such profile } // remove the file from the config file KConfig config(RCFILENAME); int deletedIndex = m_profiles[name]->index; QString deletedGroup = "Profile" + QString::number(deletedIndex); config.deleteGroup(deletedGroup); config.sync(); // and from profiles m_profiles.remove(name); - m_comboBox->removeItem(index); + m_options->cmbProfiles->removeItem(index); // now in the config file there is value ProfileN missing, // where N = configIndex, so put the last one there if (m_profiles.isEmpty()) { return; } int lastN = -1; Profile *profile = 0; // profile to be moved, will be the last one Q_FOREACH (Profile *p, m_profiles) { if (p->index > lastN) { lastN = p->index; profile = p; } } Q_ASSERT(profile != 0); // do nothing if the deleted group was the last one if (deletedIndex > lastN) { return; } QString lastGroup = "Profile" + QString::number(lastN); config.deleteGroup(lastGroup); KConfigGroup profileGroup(&config, deletedGroup); - profileGroup.writeEntry("name", profile->name); + profileGroup.writeEntry("name", name); profileGroup.writeEntry("usePath", profile->usePath); - profileGroup.writeEntry("usePressure", profile->usePressure); - profileGroup.writeEntry("useAngle", profile->useAngle); - profileGroup.writeEntry("width", profile->width); - profileGroup.writeEntry("thinning", profile->thinning); - profileGroup.writeEntry("angle", profile->angle); - profileGroup.writeEntry("fixation", profile->fixation); + profileGroup.writeEntry("useAssistants", profile->useAssistants); profileGroup.writeEntry("caps", profile->caps); - profileGroup.writeEntry("mass", profile->mass); - profileGroup.writeEntry("drag", profile->drag); + profileGroup.writeEntry("timeInterval", profile->timeInterval); + profileGroup.writeEntry("distanceInterval", profile->distanceInterval); config.sync(); profile->index = deletedIndex; } int KarbonCalligraphyOptionWidget::profilePosition(const QString &profileName) { int res = 0; ProfileMap::const_iterator it = m_profiles.constBegin(); ProfileMap::const_iterator lastIt = m_profiles.constEnd(); for (; it != lastIt; ++it) { if (it.key() == profileName) { return res; } ++res; } return -1; } - -void KarbonCalligraphyOptionWidget::setUsePathEnabled(bool enabled) -{ - m_usePath->setEnabled(enabled); -} diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyOptionWidget.h b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyOptionWidget.h index db4d39aa0f..1ea88b5adb 100644 --- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyOptionWidget.h +++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyOptionWidget.h @@ -1,143 +1,129 @@ /* This file is part of the KDE project Copyright (C) 2008 Fela Winkelmolen 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 KARBONCALLIGRAPHYOPTIONWIDGET_H #define KARBONCALLIGRAPHYOPTIONWIDGET_H #include #include +#include class KComboBox; class QCheckBox; class QSpinBox; class QDoubleSpinBox; class QToolButton; +class KarbonCalligraphyToolOptions; +class KisCurveOptionWidget; class KarbonCalligraphyOptionWidget : public QWidget { Q_OBJECT public: explicit KarbonCalligraphyOptionWidget(); ~KarbonCalligraphyOptionWidget(); // emits all signals with the appropriate values // called once the signals are connected inside KarbonCalligraphyTool // to make sure all parameters are uptodate void emitAll(); Q_SIGNALS: // all the following signals emit user friendly values, not the internal // values which are instead computed directly by KarbonCalligraphyTool void usePathChanged(bool); - void usePressureChanged(bool); - void useAngleChanged(bool); - void widthChanged(double); - void thinningChanged(double); - void angleChanged(int); - void fixationChanged(double); - void capsChanged(double); + void useAssistantChanged(bool); + void useNoAdjustChanged(bool); + void settingsChanged(KisPropertiesConfigurationSP settings); void massChanged(double); void dragChanged(double); + void smoothTimeChanged(double); + void smoothDistanceChanged(double); public Q_SLOTS: // needed for the shortcuts - void increaseWidth(); - void decreaseWidth(); - void increaseAngle(); - void decreaseAngle(); + //void increaseWidth(); + //void decreaseWidth(); + //void increaseAngle(); + //void decreaseAngle(); private Q_SLOTS: void loadProfile(const QString &name); - void toggleUseAngle(bool checked); + //void toggleUseAngle(bool checked); void updateCurrentProfile(); void saveProfileAs(); void removeProfile(); - - void setUsePathEnabled(bool enabled); + void generateSettings(); private: // TODO: maybe make it a hash?? // is it needed al all?? struct Profile { QString name; int index; // index in the config file bool usePath; - bool usePressure; - bool useAngle; - qreal width; - qreal thinning; - int angle; - qreal fixation; + bool useAssistants; qreal caps; - qreal mass; - qreal drag; + qreal timeInterval; + qreal distanceInterval; + KisPropertiesConfigurationSP curveConfig; }; // convenience functions: // connects signals and slots void createConnections(); // if they aren't already added adds the default profiles // called by the ctor void addDefaultProfiles(); // laod the profiles from the configuration file void loadProfiles(); // loads the profile set as current profile in the configuration file void loadCurrentProfile(); // save a new profile using the values of the input boxes // if a profile with the same name already exists it will be overwritten void saveProfile(const QString &name); // removes the profile from the configuration file, from profiles // and from the combobox. // if the profile doesn't exist the function does nothing void removeProfile(const QString &name); // returns the position inside profiles of a certain profile // returns -1 if the profile is not found int profilePosition(const QString &profileName); private: typedef QMap ProfileMap; ProfileMap m_profiles; - KComboBox *m_comboBox; - QCheckBox *m_usePath; - QCheckBox *m_usePressure; - QCheckBox *m_useAngle; - QDoubleSpinBox *m_widthBox; - QDoubleSpinBox *m_thinningBox; - QSpinBox *m_angleBox; - QDoubleSpinBox *m_capsBox; - QDoubleSpinBox *m_fixationBox; - QDoubleSpinBox *m_massBox; - QDoubleSpinBox *m_dragBox; - - QToolButton *m_saveButton; - QToolButton *m_removeButton; + KarbonCalligraphyToolOptions *m_options; + KisCurveOptionWidget *m_sizeOption; + KisCurveOptionWidget *m_rotationOption; + KisCurveOptionWidget *m_ratioOption; // when true updateCurrentProfile() doesn't do anything bool m_changingProfile; }; #endif // KARBONCALLIGRAPHYOPTIONWIDGET_H diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp index 1f88b06b35..d9dbaa7cab 100644 --- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp +++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp @@ -1,516 +1,406 @@ /* This file is part of the KDE project * Copyright (C) 2008 Fela Winkelmolen * * 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 "KarbonCalligraphyTool.h" #include "KarbonCalligraphicShape.h" #include "KarbonCalligraphyOptionWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include +#include +#include #include #include #include #include #include #undef M_PI const qreal M_PI = 3.1415927; using std::pow; using std::sqrt; KarbonCalligraphyTool::KarbonCalligraphyTool(KoCanvasBase *canvas) - : KoToolBase(canvas) + : KisToolShape(canvas, QCursor(Qt::CrossCursor)) , m_shape(0) - , m_angle(0) , m_selectedPath(0) , m_isDrawing(false) , m_speed(0, 0) , m_lastShape(0) { connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), SLOT(updateSelectedPath())); - + m_infoBuilder = new KisPaintingInformationBuilder(); updateSelectedPath(); } KarbonCalligraphyTool::~KarbonCalligraphyTool() { } void KarbonCalligraphyTool::paint(QPainter &painter, const KoViewConverter &converter) { - if (m_selectedPath) { + if (m_selectedPath && m_usePath) { painter.save(); painter.setRenderHints(QPainter::Antialiasing, false); painter.setPen(Qt::red); // TODO make configurable QRectF rect = m_selectedPath->boundingRect(); QPointF p1 = converter.documentToView(rect.topLeft()); QPointF p2 = converter.documentToView(rect.bottomRight()); painter.drawRect(QRectF(p1, p2)); painter.restore(); } + if (!m_intervalStore.isEmpty() && m_shape) { + painter.save(); + painter.setPen(QColor(0, 200, 255)); + Q_FOREACH(KisPaintInformation p, m_intervalStore) { + painter.drawEllipse(converter.documentToView(p.pos()), 1, 1); + } + if (!m_intervalStoreOld.isEmpty()) { + Q_FOREACH(KisPaintInformation p, m_intervalStoreOld) { + painter.drawEllipse(converter.documentToView(p.pos()), 1, 1); + } + } + painter.restore(); + } + if (!m_shape) { return; } painter.save(); painter.setTransform(m_shape->absoluteTransformation(&converter) * painter.transform()); KoShapePaintingContext paintContext; //FIXME m_shape->paint(painter, converter, paintContext); painter.restore(); } void KarbonCalligraphyTool::mousePressEvent(KoPointerEvent *event) { if (m_isDrawing) { return; } m_lastPoint = event->point; m_speed = QPointF(0, 0); - m_isDrawing = true; m_pointCount = 0; - m_shape = new KarbonCalligraphicShape(m_caps); + m_intervalStore.clear(); + m_strokeTime.start(); + m_lastInfo = m_infoBuilder->startStroke(event, m_strokeTime.elapsed(), canvas()->resourceManager()); + if (!m_settings) { + m_settings = new KisPropertiesConfiguration(); + } + m_settings->setProperty("strokeWidth", currentStrokeWidth()); + m_shape = new KarbonCalligraphicShape(m_settings); m_shape->setBackground(QSharedPointer(new KoColorBackground(canvas()->resourceManager()->foregroundColor().toQColor()))); //addPoint( event ); } void KarbonCalligraphyTool::mouseMoveEvent(KoPointerEvent *event) { if (!m_isDrawing) { return; } addPoint(event); } void KarbonCalligraphyTool::mouseReleaseEvent(KoPointerEvent *event) { if (!m_isDrawing) { return; } if (m_pointCount == 0) { // handle click: select shape (if any) if (event->point == m_lastPoint) { KoShapeManager *shapeManager = canvas()->shapeManager(); KoShape *selectedShape = shapeManager->shapeAt(event->point); if (selectedShape != 0) { shapeManager->selection()->deselectAll(); shapeManager->selection()->select(selectedShape); } } delete m_shape; m_shape = 0; m_isDrawing = false; return; } else { m_endOfPath = false; // allow last point being added - addPoint(event); // add last point + addPoint(event, true); // add last point m_isDrawing = false; } - m_shape->simplifyGuidePath(); + //m_shape->simplifyGuidePath(); KUndo2Command *cmd = canvas()->shapeController()->addShape(m_shape); if (cmd) { m_lastShape = m_shape; canvas()->addCommand(cmd); canvas()->updateCanvas(m_shape->boundingRect()); } else { // don't leak shape when command could not be created delete m_shape; } m_shape = 0; } -void KarbonCalligraphyTool::addPoint(KoPointerEvent *event) +void KarbonCalligraphyTool::addPoint(KoPointerEvent *event, bool lastPoint) { if (m_pointCount == 0) { if (m_usePath && m_selectedPath) { m_selectedPathOutline = m_selectedPath->outline(); } m_pointCount = 1; m_endOfPath = false; m_followPathPosition = 0; m_lastMousePos = event->point; - m_lastPoint = calculateNewPoint(event->point, &m_speed); + m_firstPathPosition = event->point; m_deviceSupportsTilt = (event->xTilt() != 0 || event->yTilt() != 0); return; } if (m_endOfPath) { return; } ++m_pointCount; - setAngle(event); - - QPointF newSpeed; - QPointF newPoint = calculateNewPoint(event->point, &newSpeed); - qreal width = calculateWidth(event->pressure()); - qreal angle = calculateAngle(m_speed, newSpeed); + //setAngle(event); + //QPointF newSpeed; + //QPointF newPoint = calculateNewPoint(event->point, &newSpeed); // add the previous point - m_shape->appendPoint(m_lastPoint, angle, width); - - m_speed = newSpeed; - m_lastPoint = newPoint; - canvas()->updateCanvas(m_shape->lastPieceBoundingRect()); - - if (m_usePath && m_selectedPath) { - m_speed = QPointF(0, 0); // following path - } -} - -void KarbonCalligraphyTool::setAngle(KoPointerEvent *event) -{ - if (!m_useAngle) { - m_angle = (360 - m_customAngle + 90) / 180.0 * M_PI; - return; - } - - // setting m_angle to the angle of the device - if (event->xTilt() != 0 || event->yTilt() != 0) { - m_deviceSupportsTilt = false; - } - - if (m_deviceSupportsTilt) { - if (event->xTilt() == 0 && event->yTilt() == 0) { - return; // leave as is + KisPaintInformation paintInfo = m_infoBuilder->continueStroke(event, m_strokeTime.elapsed()); + //apply the path following: + paintInfo.setPos(calculateNewPoint(paintInfo.pos(), m_firstPathPosition)); + m_intervalStore.append(paintInfo); + + qreal timeDiff = paintInfo.currentTime() - m_lastInfo.currentTime(); + if (timeDiff>m_smoothIntervalTime) { + qreal distDiff = 0; + for (int i=0; ix() == 0) { - m_angle = M_PI / 2; - return; + distDiff = canvas()->viewConverter()->documentToView(QSizeF(distDiff, 0)).width(); + if (distDiff>m_smoothIntervalDistance) { + m_shape->appendPoint(m_intervalStore.first()); + m_intervalStoreOld = m_intervalStore; + m_intervalStore.clear(); + m_intervalStore.append(paintInfo); + m_lastInfo = paintInfo; } - - // y is inverted in qt painting - m_angle = std::atan(static_cast(-event->yTilt() / event->xTilt())) + M_PI / 2; - } else { - m_angle = event->rotation() + M_PI / 2; - qDebug() << "using rotation" << m_angle; - } -} - -QPointF KarbonCalligraphyTool::calculateNewPoint(const QPointF &mousePos, QPointF *speed) -{ - if (!m_usePath || !m_selectedPath) { // don't follow path - QPointF force = mousePos - m_lastPoint; - QPointF dSpeed = force / m_mass; - *speed = m_speed * (1.0 - m_drag) + dSpeed; - return m_lastPoint + *speed; - } - - QPointF sp = mousePos - m_lastMousePos; - m_lastMousePos = mousePos; - - // follow selected path - qreal step = QLineF(QPointF(0, 0), sp).length(); - m_followPathPosition += step; - - qreal t; - if (m_followPathPosition >= m_selectedPathOutline.length()) { - t = 1.0; - m_endOfPath = true; - } else { - t = m_selectedPathOutline.percentAtLength(m_followPathPosition); - } - - QPointF res = m_selectedPathOutline.pointAtPercent(t) - + m_selectedPath->position(); - *speed = res - m_lastPoint; - return res; -} - -qreal KarbonCalligraphyTool::calculateWidth(qreal pressure) -{ - // calculate the modulo of the speed - qreal speed = std::sqrt(pow(m_speed.x(), 2) + pow(m_speed.y(), 2)); - qreal thinning = m_thinning * (speed + 1) / 10.0; // can be negative - - if (thinning > 1) { - thinning = 1; - } - - if (!m_usePressure) { - pressure = 1.0; } - - qreal strokeWidth = m_strokeWidth * pressure * (1 - thinning); - - const qreal MINIMUM_STROKE_WIDTH = 1.0; - if (strokeWidth < MINIMUM_STROKE_WIDTH) { - strokeWidth = MINIMUM_STROKE_WIDTH; + if (lastPoint) { + qreal pressure = 0; + for (int j=0; jappendPoint(paintInfo); + m_intervalStore.count(); + m_intervalStore.clear(); + m_intervalStoreOld.clear(); } + //m_speed = newSpeed; + //m_lastPoint = newPoint; + canvas()->updateCanvas(m_shape->lastPieceBoundingRect()); - return strokeWidth; } -qreal KarbonCalligraphyTool::calculateAngle(const QPointF &oldSpeed, const QPointF &newSpeed) +QPointF KarbonCalligraphyTool::calculateNewPoint(const QPointF &mousePos, QPointF firstPathPosition) { - // calculate the avarage of the speed (sum of the normalized values) - qreal oldLength = QLineF(QPointF(0, 0), oldSpeed).length(); - qreal newLength = QLineF(QPointF(0, 0), newSpeed).length(); - QPointF oldSpeedNorm = !qFuzzyCompare(oldLength + 1, 1) ? - oldSpeed / oldLength : QPointF(0, 0); - QPointF newSpeedNorm = !qFuzzyCompare(newLength + 1, 1) ? - newSpeed / newLength : QPointF(0, 0); - QPointF speed = oldSpeedNorm + newSpeedNorm; - - // angle solely based on the speed - qreal speedAngle = 0; - if (speed.x() != 0) { // avoid division by zero - speedAngle = std::atan(speed.y() / speed.x()); - } else if (speed.y() > 0) { - // x == 0 && y != 0 - speedAngle = M_PI / 2; - } else if (speed.y() < 0) { - // x == 0 && y != 0 - speedAngle = -M_PI / 2; - } - if (speed.x() < 0) { - speedAngle += M_PI; - } - - // move 90 degrees - speedAngle += M_PI / 2; - - qreal fixedAngle = m_angle; - // check if the fixed angle needs to be flipped - qreal diff = fixedAngle - speedAngle; - while (diff >= M_PI) { // normalize diff between -180 and 180 - diff -= 2 * M_PI; - } - while (diff < -M_PI) { - diff += 2 * M_PI; - } - - if (std::abs(diff) > M_PI / 2) { // if absolute value < 90 - fixedAngle += M_PI; // += 180 - } - - qreal dAngle = speedAngle - fixedAngle; + QPointF res = mousePos; + if (m_usePath && m_selectedPath) { + QPointF sp = mousePos - m_lastMousePos; + m_lastMousePos = mousePos; + + // follow selected path + qreal step = QLineF(QPointF(0, 0), sp).length(); + m_followPathPosition += step; + + qreal t; + if (m_followPathPosition >= m_selectedPathOutline.length()) { + t = 1.0; + m_endOfPath = true; + } else { + t = m_selectedPathOutline.percentAtLength(m_followPathPosition); + } - // normalize dAngle between -90 and +90 - while (dAngle >= M_PI / 2) { - dAngle -= M_PI; - } - while (dAngle < -M_PI / 2) { - dAngle += M_PI; + res = m_selectedPathOutline.pointAtPercent(t) + + m_selectedPath->position(); + } else if (m_useAssistant) { + if (static_cast(canvas())->paintingAssistantsDecoration()) { + static_cast(canvas())->paintingAssistantsDecoration()->setOnlyOneAssistantSnap(false); + res = static_cast(canvas())->paintingAssistantsDecoration()->adjustPosition(mousePos, firstPathPosition); + //return (1.0 - m_magnetism) * point + m_magnetism * ap; + } } - - qreal angle = fixedAngle + dAngle * (1.0 - m_fixation); - - return angle; + return res; } - void KarbonCalligraphyTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); - useCursor(Qt::CrossCursor); + //useCursor(Qt::CrossCursor); m_lastShape = 0; } void KarbonCalligraphyTool::deactivate() { if (m_lastShape && canvas()->shapeManager()->shapes().contains(m_lastShape)) { KoSelection *selection = canvas()->shapeManager()->selection(); selection->deselectAll(); selection->select(m_lastShape); } KoToolBase::deactivate(); } QList > KarbonCalligraphyTool::createOptionWidgets() { // if the widget don't exists yet create it QList > widgets; - //KoFillConfigWidget *fillWidget = new KoFillConfigWidget(0); - //fillWidget->setWindowTitle(i18n("Fill")); - //widgets.append(fillWidget); - KarbonCalligraphyOptionWidget *widget = new KarbonCalligraphyOptionWidget; connect(widget, SIGNAL(usePathChanged(bool)), this, SLOT(setUsePath(bool))); + connect(widget, SIGNAL(useAssistantChanged(bool)), + this, SLOT(setUseAssistant(bool))); + connect(widget, SIGNAL(useNoAdjustChanged(bool)), + this, SLOT(setNoAdjust(bool))); - connect(widget, SIGNAL(usePressureChanged(bool)), - this, SLOT(setUsePressure(bool))); + connect(widget, SIGNAL(settingsChanged(KisPropertiesConfigurationSP)), + this, SLOT(setSettings(KisPropertiesConfigurationSP))); - connect(widget, SIGNAL(useAngleChanged(bool)), - this, SLOT(setUseAngle(bool))); + connect(widget, SIGNAL(smoothTimeChanged(double)), + this, SLOT(setSmoothIntervalTime(double))); - connect(widget, SIGNAL(widthChanged(double)), - this, SLOT(setStrokeWidth(double))); + connect(widget, SIGNAL(smoothDistanceChanged(double)), + this, SLOT(setSmoothIntervalDistance(double))); - connect(widget, SIGNAL(thinningChanged(double)), - this, SLOT(setThinning(double))); - - connect(widget, SIGNAL(angleChanged(int)), - this, SLOT(setAngle(int))); - - connect(widget, SIGNAL(fixationChanged(double)), - this, SLOT(setFixation(double))); - - connect(widget, SIGNAL(capsChanged(double)), - this, SLOT(setCaps(double))); - - connect(widget, SIGNAL(massChanged(double)), - this, SLOT(setMass(double))); - - connect(widget, SIGNAL(dragChanged(double)), - this, SLOT(setDrag(double))); - - connect(this, SIGNAL(pathSelectedChanged(bool)), - widget, SLOT(setUsePathEnabled(bool))); // add shortcuts - QAction *action = new QAction(i18n("Calligraphy: increase width"), this); - action->setShortcut(Qt::Key_Right); - connect(action, SIGNAL(triggered()), widget, SLOT(increaseWidth())); - addAction("calligraphy_increase_width", action); - - action = new QAction(i18n("Calligraphy: decrease width"), this); - action->setShortcut(Qt::Key_Left); - connect(action, SIGNAL(triggered()), widget, SLOT(decreaseWidth())); - addAction("calligraphy_decrease_width", action); - +/* action = new QAction(i18n("Calligraphy: increase angle"), this); action->setShortcut(Qt::Key_Up); connect(action, SIGNAL(triggered()), widget, SLOT(increaseAngle())); addAction("calligraphy_increase_angle", action); action = new QAction(i18n("Calligraphy: decrease angle"), this); action->setShortcut(Qt::Key_Down); connect(action, SIGNAL(triggered()), widget, SLOT(decreaseAngle())); addAction("calligraphy_decrease_angle", action); - +*/ // sync all parameters with the loaded profile widget->emitAll(); widget->setObjectName(i18n("Calligraphy")); widget->setWindowTitle(i18n("Calligraphy")); widgets.append(widget); return widgets; } -void KarbonCalligraphyTool::setStrokeWidth(double width) -{ - m_strokeWidth = width; -} - -void KarbonCalligraphyTool::setThinning(double thinning) -{ - m_thinning = thinning; -} - -void KarbonCalligraphyTool::setAngle(int angle) +void KarbonCalligraphyTool::setSmoothIntervalTime(double time) { - m_customAngle = angle; + m_smoothIntervalTime = time; } -void KarbonCalligraphyTool::setFixation(double fixation) +void KarbonCalligraphyTool::setSmoothIntervalDistance(double dist) { - m_fixation = fixation; -} - -void KarbonCalligraphyTool::setMass(double mass) -{ - m_mass = mass * mass + 1; -} - -void KarbonCalligraphyTool::setDrag(double drag) -{ - m_drag = drag; + m_smoothIntervalDistance = dist; } void KarbonCalligraphyTool::setUsePath(bool usePath) { m_usePath = usePath; + m_useAssistant = !usePath; } -void KarbonCalligraphyTool::setUsePressure(bool usePressure) +void KarbonCalligraphyTool::setUseAssistant(bool useAssistant) { - m_usePressure = usePressure; + m_usePath = !useAssistant; + m_useAssistant = useAssistant; } -void KarbonCalligraphyTool::setUseAngle(bool useAngle) +void KarbonCalligraphyTool::setNoAdjust(bool none) { - m_useAngle = useAngle; + if (none){ + m_usePath = false; + m_useAssistant = false; + } } -void KarbonCalligraphyTool::setCaps(double caps) +void KarbonCalligraphyTool::setSettings(KisPropertiesConfigurationSP settings) { - m_caps = caps; + settings->setProperty("strokeWidth", currentStrokeWidth()); + m_settings = settings; } void KarbonCalligraphyTool::updateSelectedPath() { KoPathShape *oldSelectedPath = m_selectedPath; // save old value KoSelection *selection = canvas()->shapeManager()->selection(); if (selection) { // null pointer if it the selection isn't a KoPathShape // or if the selection is empty m_selectedPath = dynamic_cast(selection->firstSelectedShape()); // or if it's a KoPathShape but with no or more than one subpaths if (m_selectedPath && m_selectedPath->subpathCount() != 1) { m_selectedPath = 0; } // or if there ora none or more than 1 shapes selected if (selection->count() != 1) { m_selectedPath = 0; } // emit signal it there wasn't a selected path and now there is // or the other way around if ((m_selectedPath != 0) != (oldSelectedPath != 0)) { emit pathSelectedChanged(m_selectedPath != 0); } } } diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.h b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.h index 2b810a2862..ce5a9f1344 100644 --- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.h +++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.h @@ -1,110 +1,133 @@ /* This file is part of the KDE project * Copyright (C) 2008 Fela Winkelmolen * * 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 KARBONCALLIGRAPHYTOOL_H #define KARBONCALLIGRAPHYTOOL_H #include +#include #include #include +#include +#include +#include +#include class KoPathShape; class KarbonCalligraphicShape; -class KarbonCalligraphyTool : public KoToolBase +class KarbonCalligraphyTool : public KisToolShape { Q_OBJECT public: explicit KarbonCalligraphyTool(KoCanvasBase *canvas); ~KarbonCalligraphyTool(); void paint(QPainter &painter, const KoViewConverter &converter); + /** + * @brief configuration holds the interpretation of the paintinfo, + * this is similar to a vector version of a paintop. + * @return the configuration that is currently held by the object. + */ + KisPropertiesConfigurationSP configuration(); + void mousePressEvent(KoPointerEvent *event); void mouseMoveEvent(KoPointerEvent *event); void mouseReleaseEvent(KoPointerEvent *event); QList > createOptionWidgets(); virtual void activate(ToolActivation activation, const QSet &shapes); void deactivate(); Q_SIGNALS: void pathSelectedChanged(bool selection); private Q_SLOTS: + /** + * @brief setConfiguration + * Set the configuration of the paintinfo interpretation(the paintop, basically) + * This will update the full stroke. + * @param setting + */ + //void setConfiguration(KisPropertiesConfigurationSP setting) const; void setUsePath(bool usePath); - void setUsePressure(bool usePressure); - void setUseAngle(bool useAngle); - void setStrokeWidth(double width); - void setThinning(double thinning); - void setAngle(int angle); // set theangle in degrees - void setFixation(double fixation); - void setCaps(double caps); - void setMass(double mass); // set the mass in user friendly format - void setDrag(double drag); + void setUseAssistant(bool useAssistant); + void setNoAdjust(bool none); + void setSettings(KisPropertiesConfigurationSP settings); + /** + * @brief setSmoothIntervalTime + * @param time in milliseconds. + */ + void setSmoothIntervalTime(double time); + void setSmoothIntervalDistance(double dist); void updateSelectedPath(); private: - void addPoint(KoPointerEvent *event); + void addPoint(KoPointerEvent *event, bool lastPoint = false); // auxiliary function that sets m_angle - void setAngle(KoPointerEvent *event); + //void setAngle(KoPointerEvent *event); // auxiliary functions to calculate the dynamic parameters // returns the new point and sets speed to the speed - QPointF calculateNewPoint(const QPointF &mousePos, QPointF *speed); - qreal calculateWidth(qreal pressure); - qreal calculateAngle(const QPointF &oldSpeed, const QPointF &newSpeed); + QPointF calculateNewPoint(const QPointF &mousePos, QPointF firstPathPosition); + //qreal calculateWidth(qreal pressure); + //qreal calculateAngle(const QPointF &oldSpeed, const QPointF &newSpeed); + + void smoothPoints(); QPointF m_lastPoint; KarbonCalligraphicShape *m_shape; // used to determine if the device supports tilt bool m_deviceSupportsTilt; bool m_usePath; // follow selected path - bool m_usePressure; // use tablet pressure - bool m_useAngle; // use tablet angle + bool m_useAssistant; qreal m_strokeWidth; - qreal m_lastWidth; - qreal m_customAngle; // angle set by the user - qreal m_angle; // angle to use, may use the device angle, in radians!!! - qreal m_fixation; - qreal m_thinning; - qreal m_caps; - qreal m_mass; // in raw format (not user friendly) - qreal m_drag; // from 0.0 to 1.0 + KisPropertiesConfigurationSP m_settings; + qreal m_smoothIntervalTime; + qreal m_smoothIntervalDistance; KoPathShape *m_selectedPath; QPainterPath m_selectedPathOutline; + QPointF m_firstPathPosition; qreal m_followPathPosition; bool m_endOfPath; QPointF m_lastMousePos; bool m_isDrawing; + QTime m_strokeTime; + KisPaintInformation m_lastInfo; + KisDistanceInformation m_currentDistance; + KisPaintingInformationBuilder *m_infoBuilder; int m_pointCount; + QList m_intervalStore; + QList m_intervalStoreOld; + // dynamic parameters QPointF m_speed; // used as a vector // last calligraphic shape drawn, if any KarbonCalligraphicShape *m_lastShape; }; #endif // KARBONCALLIGRAPHYTOOL_H diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/karboncalligraphytooloptions.ui b/plugins/tools/karbonplugins/tools/CalligraphyTool/karboncalligraphytooloptions.ui new file mode 100644 index 0000000000..feaa4a2ef9 --- /dev/null +++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/karboncalligraphytooloptions.ui @@ -0,0 +1,160 @@ + + + WdgCalligraphyToolOptions + + + + 0 + 0 + 400 + 459 + + + + Form + + + + + + Tool Presets + + + + + + + + + Save profile as... + + + ... + + + + .. + + + + + + + Remove profile + + + ... + + + + .. + + + + + + + + + + + + + Stroke configuration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Adjust Position + + + + + + No adjustment + + + + + + + Follow Selected Path + + + + + + + Follow Assistants + + + + + + + + + + Sampling Interval + + + + + + <html><head/><body><p>The Time Interval samples based on the time it takes to make the stroke. The value is in milliseconds.</p></body></html> + + + + + + + The Distance interval samples based on the length of the stroke in the coordinates of the screen. So if you zoom in, you can make preciser strokes despite having the same distance interval. + + + + + + + + + + + KisDoubleSliderSpinBox + QWidget +
kis_slider_spin_box.h
+ 1 +
+ + KisPopupButton + QPushButton +
kis_popup_button.h
+
+
+ + +