diff --git a/libs/flake/KoClipPath.cpp b/libs/flake/KoClipPath.cpp index 97e19d4211..803a860294 100644 --- a/libs/flake/KoClipPath.cpp +++ b/libs/flake/KoClipPath.cpp @@ -1,240 +1,240 @@ /* This file is part of the KDE project * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoClipPath.h" #include "KoPathShape.h" #include "KoViewConverter.h" #include "KoShapeGroup.h" #include #include #include #include #include QTransform scaleToPercent(const QSizeF &size) { const qreal w = qMax(static_cast(1e-5), size.width()); const qreal h = qMax(static_cast(1e-5), size.height()); return QTransform().scale(1.0/w, 1.0/h); } QTransform scaleFromPercent(const QSizeF &size) { const qreal w = qMax(static_cast(1e-5), size.width()); const qreal h = qMax(static_cast(1e-5), size.height()); return QTransform().scale(w/1.0, h/1.0); } class Q_DECL_HIDDEN KoClipPath::Private { public: Private() {} Private(const Private &rhs) : clipPath(rhs.clipPath), clipRule(rhs.clipRule), coordinates(rhs.coordinates), initialTransformToShape(rhs.initialTransformToShape), initialShapeSize(rhs.initialShapeSize) { Q_FOREACH (KoShape *shape, rhs.shapes) { KoShape *clonedShape = shape->cloneShape(); KIS_ASSERT_RECOVER(clonedShape) { continue; } shapes.append(clonedShape); } } ~Private() { qDeleteAll(shapes); shapes.clear(); } void collectShapePath(QPainterPath *result, const KoShape *shape) { if (const KoPathShape *pathShape = dynamic_cast(shape)) { // different shapes add up to the final path using Windind Fill rule (acc. to SVG 1.1) QTransform t = pathShape->absoluteTransformation(0); result->addPath(t.map(pathShape->outline())); } else if (const KoShapeGroup *groupShape = dynamic_cast(shape)) { QList shapes = groupShape->shapes(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (const KoShape *child, shapes) { collectShapePath(result, child); } } } void compileClipPath() { QList clipShapes = this->shapes; if (clipShapes.isEmpty()) return; clipPath = QPainterPath(); clipPath.setFillRule(Qt::WindingFill); - qSort(clipShapes.begin(), clipShapes.end(), KoShape::compareShapeZIndex); + std::sort(clipShapes.begin(), clipShapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape *path, clipShapes) { if (!path) continue; collectShapePath(&clipPath, path); } } QList shapes; QPainterPath clipPath; ///< the compiled clip path in shape coordinates of the clipped shape Qt::FillRule clipRule = Qt::WindingFill; KoFlake::CoordinateSystem coordinates = KoFlake::ObjectBoundingBox; QTransform initialTransformToShape; ///< initial transformation to shape coordinates of the clipped shape QSizeF initialShapeSize; ///< initial size of clipped shape }; KoClipPath::KoClipPath(QList clipShapes, KoFlake::CoordinateSystem coordinates) : d(new Private()) { d->shapes = clipShapes; d->coordinates = coordinates; d->compileClipPath(); } KoClipPath::KoClipPath(const KoClipPath &rhs) : d(new Private(*rhs.d)) { } KoClipPath::~KoClipPath() { } KoClipPath *KoClipPath::clone() const { return new KoClipPath(*this); } void KoClipPath::setClipRule(Qt::FillRule clipRule) { d->clipRule = clipRule; } Qt::FillRule KoClipPath::clipRule() const { return d->clipRule; } KoFlake::CoordinateSystem KoClipPath::coordinates() const { return d->coordinates; } void KoClipPath::applyClipping(KoShape *clippedShape, QPainter &painter, const KoViewConverter &converter) { QPainterPath clipPath; KoShape *shape = clippedShape; while (shape) { if (shape->clipPath()) { QPainterPath path = shape->clipPath()->path(); QTransform t; if (shape->clipPath()->coordinates() == KoFlake::ObjectBoundingBox) { const QRectF shapeLocalBoundingRect = shape->outline().boundingRect(); t = KisAlgebra2D::mapToRect(shapeLocalBoundingRect) * shape->absoluteTransformation(0); } else { t = shape->absoluteTransformation(0); } path = t.map(path); if (clipPath.isEmpty()) { clipPath = path; } else { clipPath &= path; } } shape = shape->parent(); } if (!clipPath.isEmpty()) { QTransform viewMatrix; qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); viewMatrix.scale(zoomX, zoomY); painter.setClipPath(viewMatrix.map(clipPath), Qt::IntersectClip); } } QPainterPath KoClipPath::path() const { return d->clipPath; } QPainterPath KoClipPath::pathForSize(const QSizeF &size) const { return scaleFromPercent(size).map(d->clipPath); } QList KoClipPath::clipPathShapes() const { // TODO: deprecate this method! QList shapes; Q_FOREACH (KoShape *shape, d->shapes) { KoPathShape *pathShape = dynamic_cast(shape); if (pathShape) { shapes << pathShape; } } return shapes; } QList KoClipPath::clipShapes() const { return d->shapes; } QTransform KoClipPath::clipDataTransformation(KoShape *clippedShape) const { if (!clippedShape) return d->initialTransformToShape; // the current transformation of the clipped shape QTransform currentShapeTransform = clippedShape->absoluteTransformation(0); // calculate the transformation which represents any resizing of the clipped shape const QSizeF currentShapeSize = clippedShape->outline().boundingRect().size(); const qreal sx = currentShapeSize.width() / d->initialShapeSize.width(); const qreal sy = currentShapeSize.height() / d->initialShapeSize.height(); QTransform scaleTransform = QTransform().scale(sx, sy); // 1. transform to initial clipped shape coordinates // 2. apply resizing transfromation // 3. convert to current clipped shape document coordinates return d->initialTransformToShape * scaleTransform * currentShapeTransform; } diff --git a/libs/flake/KoDrag.cpp b/libs/flake/KoDrag.cpp index a510f16e5c..1120e2e539 100644 --- a/libs/flake/KoDrag.cpp +++ b/libs/flake/KoDrag.cpp @@ -1,113 +1,113 @@ /* This file is part of the KDE project * Copyright (C) 2007-2008 Thorsten Zachmann * Copyright (C) 2009 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoDrag.h" #include "KoDragOdfSaveHelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoShapeSavingContext.h" #include #include #include class KoDragPrivate { public: KoDragPrivate() : mimeData(0) { } ~KoDragPrivate() { delete mimeData; } QMimeData *mimeData; }; KoDrag::KoDrag() : d(new KoDragPrivate()) { } KoDrag::~KoDrag() { delete d; } bool KoDrag::setSvg(const QList originalShapes) { QRectF boundingRect; QList shapes; Q_FOREACH (KoShape *shape, originalShapes) { boundingRect |= shape->boundingRect(); shapes.append(shape->cloneShape()); } - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QBuffer buffer; QLatin1String mimeType("image/svg+xml"); buffer.open(QIODevice::WriteOnly); const QSizeF pageSize(boundingRect.right(), boundingRect.bottom()); SvgWriter writer(shapes); writer.save(buffer, pageSize); buffer.close(); qDeleteAll(shapes); setData(mimeType, buffer.data()); return true; } void KoDrag::setData(const QString &mimeType, const QByteArray &data) { if (d->mimeData == 0) { d->mimeData = new QMimeData(); } d->mimeData->setData(mimeType, data); } void KoDrag::addToClipboard() { if (d->mimeData) { QApplication::clipboard()->setMimeData(d->mimeData); d->mimeData = 0; } } QMimeData * KoDrag::mimeData() { QMimeData *mimeData = d->mimeData; d->mimeData = 0; return mimeData; } diff --git a/libs/flake/KoPathPoint.cpp b/libs/flake/KoPathPoint.cpp index 765207d566..bc00ba7a3e 100644 --- a/libs/flake/KoPathPoint.cpp +++ b/libs/flake/KoPathPoint.cpp @@ -1,405 +1,405 @@ /* This file is part of the KDE project Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2006-2008 Jan Hambrecht Copyright (C) 2007 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 "KoPathPoint.h" #include "KoPathShape.h" #include #include #include #include #include #include // for qIsNaN static bool qIsNaNPoint(const QPointF &p) { return qIsNaN(p.x()) || qIsNaN(p.y()); } class Q_DECL_HIDDEN KoPathPoint::Private { public: Private() : shape(0), properties(Normal) , activeControlPoint1(false), activeControlPoint2(false) {} KoPathShape * shape; QPointF point; QPointF controlPoint1; QPointF controlPoint2; PointProperties properties; bool activeControlPoint1; bool activeControlPoint2; }; KoPathPoint::KoPathPoint(const KoPathPoint &pathPoint) : d(new Private()) { d->shape = 0; d->point = pathPoint.d->point; d->controlPoint1 = pathPoint.d->controlPoint1; d->controlPoint2 = pathPoint.d->controlPoint2; d->properties = pathPoint.d->properties; d->activeControlPoint1 = pathPoint.d->activeControlPoint1; d->activeControlPoint2 = pathPoint.d->activeControlPoint2; } KoPathPoint::KoPathPoint(const KoPathPoint &pathPoint, KoPathShape *newParent) : KoPathPoint(pathPoint) { d->shape = newParent; } KoPathPoint::KoPathPoint() : d(new Private()) { } KoPathPoint::KoPathPoint(KoPathShape * path, const QPointF &point, PointProperties properties) : d(new Private()) { d->shape = path; d->point = point; d->controlPoint1 = point; d->controlPoint2 = point; d->properties = properties; } KoPathPoint::~KoPathPoint() { delete d; } KoPathPoint &KoPathPoint::operator=(const KoPathPoint &rhs) { if (this == &rhs) return (*this); d->shape = rhs.d->shape; d->point = rhs.d->point; d->controlPoint1 = rhs.d->controlPoint1; d->controlPoint2 = rhs.d->controlPoint2; d->properties = rhs.d->properties; d->activeControlPoint1 = rhs.d->activeControlPoint1; d->activeControlPoint2 = rhs.d->activeControlPoint2; return (*this); } bool KoPathPoint::operator == (const KoPathPoint &rhs) const { if (d->point != rhs.d->point) return false; if (d->controlPoint1 != rhs.d->controlPoint1) return false; if (d->controlPoint2 != rhs.d->controlPoint2) return false; if (d->properties != rhs.d->properties) return false; if (d->activeControlPoint1 != rhs.d->activeControlPoint1) return false; if (d->activeControlPoint2 != rhs.d->activeControlPoint2) return false; return true; } void KoPathPoint::setPoint(const QPointF &point) { d->point = point; if (d->shape) d->shape->notifyChanged(); } void KoPathPoint::setControlPoint1(const QPointF &point) { if (qIsNaNPoint(point)) return; d->controlPoint1 = point; d->activeControlPoint1 = true; if (d->shape) d->shape->notifyChanged(); } void KoPathPoint::setControlPoint2(const QPointF &point) { if (qIsNaNPoint(point)) return; d->controlPoint2 = point; d->activeControlPoint2 = true; if (d->shape) d->shape->notifyChanged(); } void KoPathPoint::removeControlPoint1() { d->activeControlPoint1 = false; d->properties &= ~IsSmooth; d->properties &= ~IsSymmetric; if (d->shape) d->shape->notifyChanged(); } void KoPathPoint::removeControlPoint2() { d->activeControlPoint2 = false; d->properties &= ~IsSmooth; d->properties &= ~IsSymmetric; if (d->shape) d->shape->notifyChanged(); } void KoPathPoint::setProperties(PointProperties properties) { d->properties = properties; // CloseSubpath only allowed with StartSubpath or StopSubpath if ((d->properties & StartSubpath) == 0 && (d->properties & StopSubpath) == 0) d->properties &= ~CloseSubpath; if (! activeControlPoint1() || ! activeControlPoint2()) { // strip smooth and symmetric flags if point has not two control points d->properties &= ~IsSmooth; d->properties &= ~IsSymmetric; } if (d->shape) d->shape->notifyChanged(); } void KoPathPoint::setProperty(PointProperty property) { switch (property) { case StartSubpath: case StopSubpath: case CloseSubpath: // nothing special to do here break; case IsSmooth: d->properties &= ~IsSymmetric; break; case IsSymmetric: d->properties &= ~IsSmooth; break; default: return; } d->properties |= property; if (! activeControlPoint1() || ! activeControlPoint2()) { // strip smooth and symmetric flags if point has not two control points d->properties &= ~IsSymmetric; d->properties &= ~IsSmooth; } } void KoPathPoint::unsetProperty(PointProperty property) { switch (property) { case StartSubpath: if (d->properties & StartSubpath && (d->properties & StopSubpath) == 0) d->properties &= ~CloseSubpath; break; case StopSubpath: if (d->properties & StopSubpath && (d->properties & StartSubpath) == 0) d->properties &= ~CloseSubpath; break; case CloseSubpath: if (d->properties & StartSubpath || d->properties & StopSubpath) { d->properties &= ~IsSmooth; d->properties &= ~IsSymmetric; } break; case IsSmooth: case IsSymmetric: // no others depend on these break; default: return; } d->properties &= ~property; } bool KoPathPoint::activeControlPoint1() const { // only start point on closed subpaths can have a controlPoint1 if ((d->properties & StartSubpath) && (d->properties & CloseSubpath) == 0) return false; return d->activeControlPoint1; } bool KoPathPoint::activeControlPoint2() const { // only end point on closed subpaths can have a controlPoint2 if ((d->properties & StopSubpath) && (d->properties & CloseSubpath) == 0) return false; return d->activeControlPoint2; } void KoPathPoint::map(const QTransform &matrix) { d->point = matrix.map(d->point); d->controlPoint1 = matrix.map(d->controlPoint1); d->controlPoint2 = matrix.map(d->controlPoint2); if (d->shape) d->shape->notifyChanged(); } void KoPathPoint::paint(KisHandlePainterHelper &handlesHelper, PointTypes types, bool active) { bool drawControlPoint1 = types & ControlPoint1 && (!active || activeControlPoint1()); bool drawControlPoint2 = types & ControlPoint2 && (!active || activeControlPoint2()); // draw lines at the bottom if (drawControlPoint2) { handlesHelper.drawConnectionLine(point(), controlPoint2()); } if (drawControlPoint1) { handlesHelper.drawConnectionLine(point(), controlPoint1()); } // the point is lowest if (types & Node) { if (properties() & IsSmooth) { handlesHelper.drawHandleRect(point()); } else if (properties() & IsSymmetric) { handlesHelper.drawGradientHandle(point()); } else { handlesHelper.drawHandleCircle(point()); } } // then comes control point 2 if (drawControlPoint2) { handlesHelper.drawHandleSmallCircle(controlPoint2()); } // then comes control point 1 if (drawControlPoint1) { handlesHelper.drawHandleSmallCircle(controlPoint1()); } } void KoPathPoint::setParent(KoPathShape* parent) { // don't set to zero //Q_ASSERT(parent); d->shape = parent; } QRectF KoPathPoint::boundingRect(bool active) const { QRectF rect(d->point, QSize(1, 1)); if (!active && activeControlPoint1()) { QRectF r1(d->point, QSize(1, 1)); r1.setBottomRight(d->controlPoint1); rect = rect.united(r1); } if (!active && activeControlPoint2()) { QRectF r2(d->point, QSize(1, 1)); r2.setBottomRight(d->controlPoint2); rect = rect.united(r2); } if (d->shape) return d->shape->shapeToDocument(rect); else return rect; } void KoPathPoint::reverse() { - qSwap(d->controlPoint1, d->controlPoint2); - qSwap(d->activeControlPoint1, d->activeControlPoint2); + std::swap(d->controlPoint1, d->controlPoint2); + std::swap(d->activeControlPoint1, d->activeControlPoint2); PointProperties newProps = Normal; newProps |= d->properties & IsSmooth; newProps |= d->properties & IsSymmetric; newProps |= d->properties & StartSubpath; newProps |= d->properties & StopSubpath; newProps |= d->properties & CloseSubpath; d->properties = newProps; } bool KoPathPoint::isSmooth(KoPathPoint * prev, KoPathPoint * next) const { QPointF t1, t2; if (activeControlPoint1()) { t1 = point() - controlPoint1(); } else { // we need the previous path point but there is none provided if (! prev) return false; if (prev->activeControlPoint2()) t1 = point() - prev->controlPoint2(); else t1 = point() - prev->point(); } if (activeControlPoint2()) { t2 = controlPoint2() - point(); } else { // we need the next path point but there is none provided if (! next) return false; if (next->activeControlPoint1()) t2 = next->controlPoint1() - point(); else t2 = next->point() - point(); } // normalize tangent vectors qreal l1 = sqrt(t1.x() * t1.x() + t1.y() * t1.y()); qreal l2 = sqrt(t2.x() * t2.x() + t2.y() * t2.y()); if (qFuzzyCompare(l1 + 1, qreal(1.0)) || qFuzzyCompare(l2 + 1, qreal(1.0))) return true; t1 /= l1; t2 /= l2; qreal scalar = t1.x() * t2.x() + t1.y() * t2.y(); // tangents are parallel if t1*t2 = |t1|*|t2| return qFuzzyCompare(scalar, qreal(1.0)); } KoPathPoint::PointProperties KoPathPoint::properties() const { return d->properties; } QPointF KoPathPoint::point() const { return d->point; } QPointF KoPathPoint::controlPoint1() const { return d->controlPoint1; } QPointF KoPathPoint::controlPoint2() const { return d->controlPoint2; } KoPathShape * KoPathPoint::parent() const { return d->shape; } diff --git a/libs/flake/KoShapeGroup.cpp b/libs/flake/KoShapeGroup.cpp index 0d9b11ed24..3eb0259a5e 100644 --- a/libs/flake/KoShapeGroup.cpp +++ b/libs/flake/KoShapeGroup.cpp @@ -1,286 +1,286 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeGroup.h" #include "KoShapeContainerModel.h" #include "KoShapeContainer_p.h" #include "KoShapeLayer.h" #include "SimpleShapeContainerModel.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoXmlWriter.h" #include "KoXmlReader.h" #include "KoShapeRegistry.h" #include "KoShapeStrokeModel.h" #include "KoShapeShadow.h" #include "KoInsets.h" #include #include class ShapeGroupContainerModel : public SimpleShapeContainerModel { public: ShapeGroupContainerModel(KoShapeGroup *group) : m_group(group) {} ~ShapeGroupContainerModel() override {} ShapeGroupContainerModel(const ShapeGroupContainerModel &rhs, KoShapeGroup *group) : SimpleShapeContainerModel(rhs), m_group(group) { } void add(KoShape *child) override { SimpleShapeContainerModel::add(child); m_group->invalidateSizeCache(); } void remove(KoShape *child) override { SimpleShapeContainerModel::remove(child); m_group->invalidateSizeCache(); } void childChanged(KoShape *shape, KoShape::ChangeType type) override { SimpleShapeContainerModel::childChanged(shape, type); //debugFlake << type; switch (type) { case KoShape::PositionChanged: case KoShape::RotationChanged: case KoShape::ScaleChanged: case KoShape::ShearChanged: case KoShape::SizeChanged: case KoShape::GenericMatrixChange: case KoShape::ParameterChanged: case KoShape::ClipPathChanged : m_group->invalidateSizeCache(); break; default: break; } } private: // members KoShapeGroup * m_group; }; class KoShapeGroupPrivate : public KoShapeContainerPrivate { public: KoShapeGroupPrivate(KoShapeGroup *q) : KoShapeContainerPrivate(q) { model = new ShapeGroupContainerModel(q); } KoShapeGroupPrivate(const KoShapeGroupPrivate &rhs, KoShapeGroup *q) : KoShapeContainerPrivate(rhs, q) { ShapeGroupContainerModel *otherModel = dynamic_cast(rhs.model); KIS_ASSERT_RECOVER_RETURN(otherModel); model = new ShapeGroupContainerModel(*otherModel, q); } ~KoShapeGroupPrivate() override { } mutable QRectF savedOutlineRect; mutable bool sizeCached = false; void tryUpdateCachedSize() const; Q_DECLARE_PUBLIC(KoShapeGroup) }; KoShapeGroup::KoShapeGroup() : KoShapeContainer(new KoShapeGroupPrivate(this)) { } KoShapeGroup::KoShapeGroup(const KoShapeGroup &rhs) : KoShapeContainer(new KoShapeGroupPrivate(*rhs.d_func(), this)) { } KoShapeGroup::~KoShapeGroup() { } KoShape *KoShapeGroup::cloneShape() const { return new KoShapeGroup(*this); } void KoShapeGroup::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); } bool KoShapeGroup::hitTest(const QPointF &position) const { Q_UNUSED(position); return false; } void KoShapeGroupPrivate::tryUpdateCachedSize() const { Q_Q(const KoShapeGroup); if (!sizeCached) { QRectF bound; Q_FOREACH (KoShape *shape, q->shapes()) { bound |= shape->transformation().mapRect(shape->outlineRect()); } savedOutlineRect = bound; size = bound.size(); sizeCached = true; } } QSizeF KoShapeGroup::size() const { Q_D(const KoShapeGroup); d->tryUpdateCachedSize(); return d->size; } void KoShapeGroup::setSize(const QSizeF &size) { QSizeF oldSize = this->size(); if (!shapeCount() || oldSize.isNull()) return; const QTransform scale = QTransform::fromScale(size.width() / oldSize.width(), size.height() / oldSize.height()); setTransformation(scale * transformation()); KoShapeContainer::setSize(size); } QRectF KoShapeGroup::outlineRect() const { Q_D(const KoShapeGroup); d->tryUpdateCachedSize(); return d->savedOutlineRect; } QRectF KoShapeGroup::boundingRect() const { QRectF groupBound = KoShape::boundingRect(shapes()); if (shadow()) { KoInsets insets; shadow()->insets(insets); groupBound.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } return groupBound; } void KoShapeGroup::saveOdf(KoShapeSavingContext & context) const { context.xmlWriter().startElement("draw:g"); saveOdfAttributes(context, (OdfMandatories ^ (OdfLayer | OdfZIndex)) | OdfAdditionalAttributes); context.xmlWriter().addAttributePt("svg:y", position().y()); QList shapes = this->shapes(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape* shape, shapes) { shape->saveOdf(context); } saveOdfCommonChildElements(context); context.xmlWriter().endElement(); } bool KoShapeGroup::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { Q_D(KoShapeGroup); loadOdfAttributes(element, context, OdfMandatories | OdfStyle | OdfAdditionalAttributes | OdfCommonChildElements); KoXmlElement child; QMap usedLayers; forEachElement(child, element) { KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, context); if (shape) { KoShapeLayer *layer = dynamic_cast(shape->parent()); if (layer) { usedLayers[layer]++; } addShape(shape); } } KoShapeLayer *parent = 0; int maxUseCount = 0; // find most used layer and use this as parent for the group for (QMap::const_iterator it(usedLayers.constBegin()); it != usedLayers.constEnd(); ++it) { if (it.value() > maxUseCount) { maxUseCount = it.value(); parent = it.key(); } } setParent(parent); QRectF bound; bool boundInitialized = false; Q_FOREACH (KoShape * shape, shapes()) { if (! boundInitialized) { bound = shape->boundingRect(); boundInitialized = true; } else bound = bound.united(shape->boundingRect()); } setSize(bound.size()); d->sizeCached = true; setPosition(bound.topLeft()); Q_FOREACH (KoShape * shape, shapes()) shape->setAbsolutePosition(shape->absolutePosition() - bound.topLeft()); return true; } void KoShapeGroup::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(shape); KoShapeContainer::shapeChanged(type, shape); switch (type) { case KoShape::StrokeChanged: break; default: break; } invalidateSizeCache(); } void KoShapeGroup::invalidateSizeCache() { Q_D(KoShapeGroup); d->sizeCached = false; } diff --git a/libs/flake/KoShapeLayer.cpp b/libs/flake/KoShapeLayer.cpp index 8a241cea5a..181a347adc 100644 --- a/libs/flake/KoShapeLayer.cpp +++ b/libs/flake/KoShapeLayer.cpp @@ -1,81 +1,81 @@ /* This file is part of the KDE project Copyright (C) 2006-2007 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeLayer.h" #include #include "SimpleShapeContainerModel.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include #include #include KoShapeLayer::KoShapeLayer() : KoShapeContainer(new SimpleShapeContainerModel()) { setSelectable(false); } KoShapeLayer::KoShapeLayer(KoShapeContainerModel *model) : KoShapeContainer(model) { setSelectable(false); } bool KoShapeLayer::hitTest(const QPointF &position) const { Q_UNUSED(position); return false; } QRectF KoShapeLayer::boundingRect() const { return KoShape::boundingRect(shapes()); } void KoShapeLayer::saveOdf(KoShapeSavingContext & context) const { QList shapes = this->shapes(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape* shape, shapes) { shape->saveOdf(context); } } bool KoShapeLayer::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { // set layer name setName(element.attributeNS(KoXmlNS::draw, "name")); // layer locking setGeometryProtected(element.attributeNS(KoXmlNS::draw, "protected", "false") == "true"); // layer visibility setVisible(element.attributeNS(KoXmlNS::draw, "display", "false") != "none"); // add layer by name into shape context context.addLayer(this, name()); return true; } void KoShapeLayer::paintComponent(QPainter &, const KoViewConverter &, KoShapePaintingContext &) { } diff --git a/libs/flake/KoShapeManager.cpp b/libs/flake/KoShapeManager.cpp index 2a6289b5ea..84825d1dcc 100644 --- a/libs/flake/KoShapeManager.cpp +++ b/libs/flake/KoShapeManager.cpp @@ -1,607 +1,607 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2009-2010 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 "KoShapeManager.h" #include "KoShapeManager_p.h" #include "KoSelection.h" #include "KoToolManager.h" #include "KoPointerEvent.h" #include "KoShape.h" #include "KoShape_p.h" #include "KoCanvasBase.h" #include "KoShapeContainer.h" #include "KoShapeStrokeModel.h" #include "KoShapeGroup.h" #include "KoToolProxy.h" #include "KoShapeShadow.h" #include "KoShapeLayer.h" #include "KoFilterEffect.h" #include "KoFilterEffectStack.h" #include "KoFilterEffectRenderContext.h" #include "KoShapeBackground.h" #include #include "KoClipPath.h" #include "KoClipMaskPainter.h" #include "KoShapePaintingContext.h" #include "KoViewConverter.h" #include "KisQPainterStateSaver.h" #include #include #include #include "kis_painting_tweaks.h" bool KoShapeManager::Private::shapeUsedInRenderingTree(KoShape *shape) { return !dynamic_cast(shape) && !dynamic_cast(shape); } void KoShapeManager::Private::updateTree() { // for detecting collisions between shapes. DetectCollision detector; bool selectionModified = false; bool anyModified = false; Q_FOREACH (KoShape *shape, aggregate4update) { if (shapeIndexesBeforeUpdate.contains(shape)) detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]); selectionModified = selectionModified || selection->isSelected(shape); anyModified = true; } foreach (KoShape *shape, aggregate4update) { if (!shapeUsedInRenderingTree(shape)) continue; tree.remove(shape); QRectF br(shape->boundingRect()); tree.insert(br, shape); } // do it again to see which shapes we intersect with _after_ moving. foreach (KoShape *shape, aggregate4update) { detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]); } aggregate4update.clear(); shapeIndexesBeforeUpdate.clear(); detector.fireSignals(); if (selectionModified) { emit q->selectionContentChanged(); } if (anyModified) { emit q->contentChanged(); } } void KoShapeManager::Private::paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { QList shapes = group->shapes(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape *child, shapes) { // we paint recursively here, so we do not have to check recursively for visibility if (!child->isVisible()) continue; KoShapeGroup *childGroup = dynamic_cast(child); if (childGroup) { paintGroup(childGroup, painter, converter, paintContext); } else { painter.save(); KoShapeManager::renderSingleShape(child, painter, converter, paintContext); painter.restore(); } } } KoShapeManager::KoShapeManager(KoCanvasBase *canvas, const QList &shapes) : d(new Private(this, canvas)) { Q_ASSERT(d->canvas); // not optional. connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); setShapes(shapes); } KoShapeManager::KoShapeManager(KoCanvasBase *canvas) : d(new Private(this, canvas)) { Q_ASSERT(d->canvas); // not optional. connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); } KoShapeManager::~KoShapeManager() { Q_FOREACH (KoShape *shape, d->shapes) { shape->priv()->removeShapeManager(this); } Q_FOREACH (KoShape *shape, d->additionalShapes) { shape->priv()->removeShapeManager(this); } delete d; } void KoShapeManager::setShapes(const QList &shapes, Repaint repaint) { //clear selection d->selection->deselectAll(); Q_FOREACH (KoShape *shape, d->shapes) { shape->priv()->removeShapeManager(this); } d->aggregate4update.clear(); d->tree.clear(); d->shapes.clear(); Q_FOREACH (KoShape *shape, shapes) { addShape(shape, repaint); } } void KoShapeManager::addShape(KoShape *shape, Repaint repaint) { if (d->shapes.contains(shape)) return; shape->priv()->addShapeManager(this); d->shapes.append(shape); if (d->shapeUsedInRenderingTree(shape)) { QRectF br(shape->boundingRect()); d->tree.insert(br, shape); } if (repaint == PaintShapeOnAdd) { shape->update(); } // add the children of a KoShapeContainer KoShapeContainer *container = dynamic_cast(shape); if (container) { foreach (KoShape *containerShape, container->shapes()) { addShape(containerShape, repaint); } } Private::DetectCollision detector; detector.detect(d->tree, shape, shape->zIndex()); detector.fireSignals(); } void KoShapeManager::remove(KoShape *shape) { Private::DetectCollision detector; detector.detect(d->tree, shape, shape->zIndex()); detector.fireSignals(); shape->update(); shape->priv()->removeShapeManager(this); d->selection->deselect(shape); d->aggregate4update.remove(shape); if (d->shapeUsedInRenderingTree(shape)) { d->tree.remove(shape); } d->shapes.removeAll(shape); // remove the children of a KoShapeContainer KoShapeContainer *container = dynamic_cast(shape); if (container) { foreach (KoShape *containerShape, container->shapes()) { remove(containerShape); } } } KoShapeManager::ShapeInterface::ShapeInterface(KoShapeManager *_q) : q(_q) { } void KoShapeManager::ShapeInterface::notifyShapeDestructed(KoShape *shape) { q->d->selection->deselect(shape); q->d->aggregate4update.remove(shape); // we cannot access RTTI of the semi-destructed shape, so just // unlink it lazily if (q->d->tree.contains(shape)) { q->d->tree.remove(shape); } q->d->shapes.removeAll(shape); } KoShapeManager::ShapeInterface *KoShapeManager::shapeInterface() { return &d->shapeInterface; } void KoShapeManager::paint(QPainter &painter, const KoViewConverter &converter, bool forPrint) { d->updateTree(); painter.setPen(Qt::NoPen); // painters by default have a black stroke, lets turn that off. painter.setBrush(Qt::NoBrush); QList unsortedShapes; if (painter.hasClipping()) { QRectF rect = converter.viewToDocument(KisPaintingTweaks::safeClipBoundingRect(painter)); unsortedShapes = d->tree.intersects(rect); } else { unsortedShapes = shapes(); warnFlake << "KoShapeManager::paint Painting with a painter that has no clipping will lead to too much being painted!"; } // filter all hidden shapes from the list // also filter shapes with a parent which has filter effects applied QList sortedShapes; foreach (KoShape *shape, unsortedShapes) { if (!shape->isVisible(true)) continue; bool addShapeToList = true; // check if one of the shapes ancestors have filter effects KoShapeContainer *parent = shape->parent(); while (parent) { // parent must be part of the shape manager to be taken into account if (!d->shapes.contains(parent)) break; if (parent->filterEffectStack() && !parent->filterEffectStack()->isEmpty()) { addShapeToList = false; break; } parent = parent->parent(); } if (addShapeToList) { sortedShapes.append(shape); } else if (parent) { sortedShapes.append(parent); } } - qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); + std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME foreach (KoShape *shape, sortedShapes) { renderSingleShape(shape, painter, converter, paintContext); } #ifdef CALLIGRA_RTREE_DEBUG // paint tree qreal zx = 0; qreal zy = 0; converter.zoom(&zx, &zy); painter.save(); painter.scale(zx, zy); d->tree.paint(painter); painter.restore(); #endif if (! forPrint) { KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME d->selection->paint(painter, converter, paintContext); } } void KoShapeManager::renderSingleShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { KisQPainterStateSaver saver(&painter); // apply shape clipping KoClipPath::applyClipping(shape, painter, converter); // apply transformation painter.setTransform(shape->absoluteTransformation(&converter) * painter.transform()); // paint the shape paintShape(shape, painter, converter, paintContext); } void KoShapeManager::paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { qreal transparency = shape->transparency(true); if (transparency > 0.0) { painter.setOpacity(1.0-transparency); } if (shape->shadow()) { painter.save(); shape->shadow()->paint(shape, painter, converter); painter.restore(); } if (!shape->filterEffectStack() || shape->filterEffectStack()->isEmpty()) { QScopedPointer clipMaskPainter; QPainter *shapePainter = &painter; KoClipMask *clipMask = shape->clipMask(); if (clipMask) { clipMaskPainter.reset(new KoClipMaskPainter(&painter, shape->boundingRect())); shapePainter = clipMaskPainter->shapePainter(); } shapePainter->save(); shape->paint(*shapePainter, converter, paintContext); shapePainter->restore(); if (shape->stroke()) { shapePainter->save(); shape->stroke()->paint(shape, *shapePainter, converter); shapePainter->restore(); } if (clipMask) { shape->clipMask()->drawMask(clipMaskPainter->maskPainter(), shape); clipMaskPainter->renderOnGlobalPainter(); } } else { // TODO: clipping mask is not implemented for this case! // There are filter effects, then we need to prerender the shape on an image, to filter it QRectF shapeBound(QPointF(), shape->size()); // First step, compute the rectangle used for the image QRectF clipRegion = shape->filterEffectStack()->clipRectForBoundingRect(shapeBound); // convert clip region to view coordinates QRectF zoomedClipRegion = converter.documentToView(clipRegion); // determine the offset of the clipping rect from the shapes origin QPointF clippingOffset = zoomedClipRegion.topLeft(); // Initialize the buffer image QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied); sourceGraphic.fill(qRgba(0,0,0,0)); QHash imageBuffers; QSet requiredStdInputs = shape->filterEffectStack()->requiredStandarsInputs(); if (requiredStdInputs.contains("SourceGraphic") || requiredStdInputs.contains("SourceAlpha")) { // Init the buffer painter QPainter imagePainter(&sourceGraphic); imagePainter.translate(-1.0f*clippingOffset); imagePainter.setPen(Qt::NoPen); imagePainter.setBrush(Qt::NoBrush); imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing)); // Paint the shape on the image KoShapeGroup *group = dynamic_cast(shape); if (group) { // the childrens matrix contains the groups matrix as well // so we have to compensate for that before painting the children imagePainter.setTransform(group->absoluteTransformation(&converter).inverted(), true); Private::paintGroup(group, imagePainter, converter, paintContext); } else { imagePainter.save(); shape->paint(imagePainter, converter, paintContext); imagePainter.restore(); if (shape->stroke()) { imagePainter.save(); shape->stroke()->paint(shape, imagePainter, converter); imagePainter.restore(); } imagePainter.end(); } } if (requiredStdInputs.contains("SourceAlpha")) { QImage sourceAlpha = sourceGraphic; sourceAlpha.fill(qRgba(0,0,0,255)); sourceAlpha.setAlphaChannel(sourceGraphic.alphaChannel()); imageBuffers.insert("SourceAlpha", sourceAlpha); } if (requiredStdInputs.contains("FillPaint")) { QImage fillPaint = sourceGraphic; if (shape->background()) { QPainter fillPainter(&fillPaint); QPainterPath fillPath; fillPath.addRect(fillPaint.rect().adjusted(-1,-1,1,1)); shape->background()->paint(fillPainter, converter, paintContext, fillPath); } else { fillPaint.fill(qRgba(0,0,0,0)); } imageBuffers.insert("FillPaint", fillPaint); } imageBuffers.insert("SourceGraphic", sourceGraphic); imageBuffers.insert(QString(), sourceGraphic); KoFilterEffectRenderContext renderContext(converter); renderContext.setShapeBoundingBox(shapeBound); QImage result; QList filterEffects = shape->filterEffectStack()->filterEffects(); // Filter foreach (KoFilterEffect *filterEffect, filterEffects) { QRectF filterRegion = filterEffect->filterRectForBoundingRect(shapeBound); filterRegion = converter.documentToView(filterRegion); QRect subRegion = filterRegion.translated(-clippingOffset).toRect(); // set current filter region renderContext.setFilterRegion(subRegion & sourceGraphic.rect()); if (filterEffect->maximalInputCount() <= 1) { QList inputs = filterEffect->inputs(); QString input = inputs.count() ? inputs.first() : QString(); // get input image from image buffers and apply the filter effect QImage image = imageBuffers.value(input); if (!image.isNull()) { result = filterEffect->processImage(imageBuffers.value(input), renderContext); } } else { QList inputImages; Q_FOREACH (const QString &input, filterEffect->inputs()) { QImage image = imageBuffers.value(input); if (!image.isNull()) inputImages.append(imageBuffers.value(input)); } // apply the filter effect if (filterEffect->inputs().count() == inputImages.count()) result = filterEffect->processImages(inputImages, renderContext); } // store result of effect imageBuffers.insert(filterEffect->output(), result); } KoFilterEffect *lastEffect = filterEffects.last(); // Paint the result painter.save(); painter.drawImage(clippingOffset, imageBuffers.value(lastEffect->output())); painter.restore(); } } KoShape *KoShapeManager::shapeAt(const QPointF &position, KoFlake::ShapeSelection selection, bool omitHiddenShapes) { d->updateTree(); QList sortedShapes(d->tree.contains(position)); - qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); + std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); KoShape *firstUnselectedShape = 0; for (int count = sortedShapes.count() - 1; count >= 0; count--) { KoShape *shape = sortedShapes.at(count); if (omitHiddenShapes && ! shape->isVisible(true)) continue; if (! shape->hitTest(position)) continue; switch (selection) { case KoFlake::ShapeOnTop: if (shape->isSelectable()) return shape; case KoFlake::Selected: if (d->selection->isSelected(shape)) return shape; break; case KoFlake::Unselected: if (! d->selection->isSelected(shape)) return shape; break; case KoFlake::NextUnselected: // we want an unselected shape if (d->selection->isSelected(shape)) continue; // memorize the first unselected shape if (! firstUnselectedShape) firstUnselectedShape = shape; // check if the shape above is selected if (count + 1 < sortedShapes.count() && d->selection->isSelected(sortedShapes.at(count + 1))) return shape; break; } } // if we want the next unselected below a selected but there was none selected, // return the first found unselected shape if (selection == KoFlake::NextUnselected && firstUnselectedShape) return firstUnselectedShape; if (d->selection->hitTest(position)) return d->selection; return 0; // missed everything } QList KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes, bool containedMode) { d->updateTree(); QList shapes(containedMode ? d->tree.contained(rect) : d->tree.intersects(rect)); for (int count = shapes.count() - 1; count >= 0; count--) { KoShape *shape = shapes.at(count); if (omitHiddenShapes && !shape->isVisible(true)) { shapes.removeAt(count); } else { const QPainterPath outline = shape->absoluteTransformation(0).map(shape->outline()); if (!containedMode && !outline.intersects(rect) && !outline.contains(rect)) { shapes.removeAt(count); } else if (containedMode) { QPainterPath containingPath; containingPath.addRect(rect); if (!containingPath.contains(outline)) { shapes.removeAt(count); } } } } return shapes; } void KoShapeManager::update(const QRectF &rect, const KoShape *shape, bool selectionHandles) { d->canvas->updateCanvas(rect); if (selectionHandles && d->selection->isSelected(shape)) { if (d->canvas->toolProxy()) d->canvas->toolProxy()->repaintDecorations(); } } void KoShapeManager::notifyShapeChanged(KoShape *shape) { Q_ASSERT(shape); if (d->aggregate4update.contains(shape) || d->additionalShapes.contains(shape)) { return; } const bool wasEmpty = d->aggregate4update.isEmpty(); d->aggregate4update.insert(shape); d->shapeIndexesBeforeUpdate.insert(shape, shape->zIndex()); KoShapeContainer *container = dynamic_cast(shape); if (container) { Q_FOREACH (KoShape *child, container->shapes()) notifyShapeChanged(child); } if (wasEmpty && !d->aggregate4update.isEmpty()) QTimer::singleShot(100, this, SLOT(updateTree())); emit shapeChanged(shape); } QList KoShapeManager::shapes() const { return d->shapes; } QList KoShapeManager::topLevelShapes() const { QList shapes; // get all toplevel shapes Q_FOREACH (KoShape *shape, d->shapes) { if (shape->parent() == 0) { shapes.append(shape); } } return shapes; } KoSelection *KoShapeManager::selection() const { return d->selection; } KoCanvasBase *KoShapeManager::canvas() { return d->canvas; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoShapeManager.cpp" diff --git a/libs/flake/KoShapeOdfSaveHelper.cpp b/libs/flake/KoShapeOdfSaveHelper.cpp index 83b7a1dd32..6ce57c4b2d 100644 --- a/libs/flake/KoShapeOdfSaveHelper.cpp +++ b/libs/flake/KoShapeOdfSaveHelper.cpp @@ -1,59 +1,59 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeOdfSaveHelper.h" #include "KoDragOdfSaveHelper_p.h" #include #include #include class KoShapeOdfSaveHelperPrivate : public KoDragOdfSaveHelperPrivate { public: KoShapeOdfSaveHelperPrivate(const QList &shapes) : shapes(shapes) {} QList shapes; }; KoShapeOdfSaveHelper::KoShapeOdfSaveHelper(const QList &shapes) : KoDragOdfSaveHelper(*(new KoShapeOdfSaveHelperPrivate(shapes))) { } bool KoShapeOdfSaveHelper::writeBody() { Q_D(KoShapeOdfSaveHelper); d->context->addOption(KoShapeSavingContext::DrawId); KoXmlWriter &bodyWriter = d->context->xmlWriter(); bodyWriter.startElement("office:body"); bodyWriter.startElement(KoOdf::bodyContentElement(KoOdf::Text, true)); - qSort(d->shapes.begin(), d->shapes.end(), KoShape::compareShapeZIndex); + std::sort(d->shapes.begin(), d->shapes.end(), KoShape::compareShapeZIndex); foreach (KoShape *shape, d->shapes) { shape->saveOdf(*d->context); } bodyWriter.endElement(); // office:element bodyWriter.endElement(); // office:body return true; } diff --git a/libs/flake/commands/KoPathBreakAtPointCommand.cpp b/libs/flake/commands/KoPathBreakAtPointCommand.cpp index 894f451a36..c887945329 100644 --- a/libs/flake/commands/KoPathBreakAtPointCommand.cpp +++ b/libs/flake/commands/KoPathBreakAtPointCommand.cpp @@ -1,157 +1,157 @@ /* This file is part of the KDE project * Copyright (C) 2006 Jan Hambrecht * Copyright (C) 2006,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. */ #include "KoPathBreakAtPointCommand.h" #include "KoPathPoint.h" #include /* * The algorithm to break a multiple open or closed subpaths is: * Subpath is closed * - open behind the last point in the subpath * - go on like as described in Not closed * Not closed * - break from the back of the subpath */ KoPathBreakAtPointCommand::KoPathBreakAtPointCommand(const QList & pointDataList, KUndo2Command *parent) : KUndo2Command(parent) , m_deletePoints(true) { QList sortedPointDataList(pointDataList); - qSort(sortedPointDataList); + std::sort(sortedPointDataList.begin(), sortedPointDataList.end()); setText(kundo2_i18n("Break subpath at points")); QList::const_iterator it(sortedPointDataList.constBegin()); for (; it != sortedPointDataList.constEnd(); ++it) { KoPathShape * pathShape = it->pathShape; KoPathPoint * point = pathShape->pointByIndex(it->pointIndex); if(! point) continue; // check if subpath is closed and the point is start or end point of the subpath if(! pathShape->isClosedSubpath(it->pointIndex.first)) { if(it->pointIndex.second == 0 || it->pointIndex.second == pathShape->subpathPointCount(it->pointIndex.first)) { continue; } } m_pointDataList.append(*it); m_points.push_back(new KoPathPoint(*point)); m_closedIndex.push_back(KoPathPointIndex(-1, 0)); } KoPathPointData last(0, KoPathPointIndex(-1, -1)); for (int i = m_pointDataList.size() - 1; i >= 0; --i) { const KoPathPointData ¤t = m_pointDataList.at(i); if (last.pathShape != current.pathShape || last.pointIndex.first != current.pointIndex.first) { last = current; if (current.pathShape->isClosedSubpath(current.pointIndex.first)) { // the break will happen before the inserted point so we have to increment by 1 m_closedIndex[i] = current.pointIndex; ++m_closedIndex[i].second; } } } } KoPathBreakAtPointCommand::~KoPathBreakAtPointCommand() { if (m_deletePoints) { qDeleteAll(m_points); } } void KoPathBreakAtPointCommand::redo() { KUndo2Command::redo(); KoPathPointData last(0, KoPathPointIndex(-1, -1)); // offset, needed when path was opened int offset = 0; for (int i = m_pointDataList.size() - 1; i >= 0; --i) { const KoPathPointData & pd = m_pointDataList.at(i); KoPathShape * pathShape = pd.pathShape; KoPathPointIndex pointIndex = pd.pointIndex; if (last.pathShape != pathShape || last.pointIndex.first != pointIndex.first) { offset = 0; } pointIndex.second = pointIndex.second + offset + 1; pathShape->insertPoint(m_points[i], pointIndex); if (m_closedIndex.at(i).first != -1) { m_closedIndex[i] = pathShape->openSubpath(m_closedIndex.at(i)); offset = m_closedIndex.at(i).second; } else { KoPathPointIndex breakIndex = pd.pointIndex; breakIndex.second += offset; pathShape->breakAfter(breakIndex); m_closedIndex[i].second = offset; } if (last.pathShape != pathShape) { if (last.pathShape) { last.pathShape->update(); } last = pd; } } if (last.pathShape) { last.pathShape->update(); } m_deletePoints = false; } void KoPathBreakAtPointCommand::undo() { KUndo2Command::undo(); KoPathShape * lastPathShape = 0; for (int i = 0; i < m_pointDataList.size(); ++i) { const KoPathPointData & pd = m_pointDataList.at(i); KoPathShape * pathShape = pd.pathShape; KoPathPointIndex pointIndex = pd.pointIndex; ++pointIndex.second; if (m_closedIndex.at(i).first != -1) { m_closedIndex[i] = pathShape->closeSubpath(m_closedIndex.at(i)); } else { pointIndex.second = pointIndex.second + m_closedIndex.at(i).second; pathShape->join(pd.pointIndex.first); } m_points[i] = pathShape->removePoint(pointIndex); if (lastPathShape != pathShape) { if (lastPathShape) { lastPathShape->update(); } lastPathShape = pathShape; } } if (lastPathShape) { lastPathShape->update(); } m_deletePoints = true; } diff --git a/libs/flake/commands/KoPathPointInsertCommand.cpp b/libs/flake/commands/KoPathPointInsertCommand.cpp index 04cbabf9a1..aa2790c787 100644 --- a/libs/flake/commands/KoPathPointInsertCommand.cpp +++ b/libs/flake/commands/KoPathPointInsertCommand.cpp @@ -1,154 +1,154 @@ /* This file is part of the KDE project * Copyright (C) 2006,2008 Jan Hambrecht * Copyright (C) 2006,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. */ #include "KoPathPointInsertCommand.h" #include "KoPathPoint.h" #include #include class KoPathPointInsertCommandPrivate { public: KoPathPointInsertCommandPrivate() : deletePoints(true) { } ~KoPathPointInsertCommandPrivate() { if (deletePoints) qDeleteAll(points); } QList pointDataList; QList points; QList > controlPoints; bool deletePoints; }; KoPathPointInsertCommand::KoPathPointInsertCommand(const QList &pointDataList, qreal insertPosition, KUndo2Command *parent) : KUndo2Command(parent), d(new KoPathPointInsertCommandPrivate()) { if (insertPosition < 0) insertPosition = 0; if (insertPosition > 1) insertPosition = 1; //TODO the list needs to be sorted QList::const_iterator it(pointDataList.begin()); for (; it != pointDataList.end(); ++it) { KoPathShape * pathShape = it->pathShape; KoPathSegment segment = pathShape->segmentByIndex(it->pointIndex); // should not happen but to be sure if (! segment.isValid()) continue; d->pointDataList.append(*it); QPair splitSegments = segment.splitAt(insertPosition); KoPathPoint * split1 = splitSegments.first.second(); KoPathPoint * split2 = splitSegments.second.first(); KoPathPoint * splitPoint = new KoPathPoint(pathShape, split1->point()); if(split1->activeControlPoint1()) splitPoint->setControlPoint1(split1->controlPoint1()); if(split2->activeControlPoint2()) splitPoint->setControlPoint2(split2->controlPoint2()); d->points.append(splitPoint); QPointF cp1 = splitSegments.first.first()->controlPoint2(); QPointF cp2 = splitSegments.second.second()->controlPoint1(); d->controlPoints.append(QPair(cp1, cp2)); } setText(kundo2_i18n("Insert points")); } KoPathPointInsertCommand::~KoPathPointInsertCommand() { delete d; } void KoPathPointInsertCommand::redo() { KUndo2Command::redo(); for (int i = d->pointDataList.size() - 1; i >= 0; --i) { KoPathPointData pointData = d->pointDataList.at(i); KoPathShape * pathShape = pointData.pathShape; KoPathSegment segment = pathShape->segmentByIndex(pointData.pointIndex); ++pointData.pointIndex.second; if (segment.first()->activeControlPoint2()) { QPointF controlPoint2 = segment.first()->controlPoint2(); - qSwap(controlPoint2, d->controlPoints[i].first); + std::swap(controlPoint2, d->controlPoints[i].first); segment.first()->setControlPoint2(controlPoint2); } if (segment.second()->activeControlPoint1()) { QPointF controlPoint1 = segment.second()->controlPoint1(); - qSwap(controlPoint1, d->controlPoints[i].second); + std::swap(controlPoint1, d->controlPoints[i].second); segment.second()->setControlPoint1(controlPoint1); } pathShape->insertPoint(d->points.at(i), pointData.pointIndex); pathShape->update(); } d->deletePoints = false; } void KoPathPointInsertCommand::undo() { KUndo2Command::undo(); for (int i = 0; i < d->pointDataList.size(); ++i) { const KoPathPointData &pdBefore = d->pointDataList.at(i); KoPathShape * pathShape = pdBefore.pathShape; KoPathPointIndex piAfter = pdBefore.pointIndex; ++piAfter.second; KoPathPoint * before = pathShape->pointByIndex(pdBefore.pointIndex); d->points[i] = pathShape->removePoint(piAfter); if (d->points[i]->properties() & KoPathPoint::CloseSubpath) { piAfter.second = 0; } KoPathPoint * after = pathShape->pointByIndex(piAfter); if (before->activeControlPoint2()) { QPointF controlPoint2 = before->controlPoint2(); - qSwap(controlPoint2, d->controlPoints[i].first); + std::swap(controlPoint2, d->controlPoints[i].first); before->setControlPoint2(controlPoint2); } if (after->activeControlPoint1()) { QPointF controlPoint1 = after->controlPoint1(); - qSwap(controlPoint1, d->controlPoints[i].second); + std::swap(controlPoint1, d->controlPoints[i].second); after->setControlPoint1(controlPoint1); } pathShape->update(); } d->deletePoints = true; } QList KoPathPointInsertCommand::insertedPoints() const { return d->points; } diff --git a/libs/flake/commands/KoPathPointMergeCommand.cpp b/libs/flake/commands/KoPathPointMergeCommand.cpp index e0bbeae528..bd3ee55603 100644 --- a/libs/flake/commands/KoPathPointMergeCommand.cpp +++ b/libs/flake/commands/KoPathPointMergeCommand.cpp @@ -1,262 +1,262 @@ /* This file is part of the KDE project * Copyright (C) 2009 Jan Hambrecht * Copyright (C) 2006,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. */ #include "KoPathPointMergeCommand.h" #include "KoPathPoint.h" #include "KoPathPointData.h" #include "KoPathShape.h" #include #include #include "kis_assert.h" class Q_DECL_HIDDEN KoPathPointMergeCommand::Private { public: Private(const KoPathPointData &pointData1, const KoPathPointData &pointData2) : pathShape(pointData1.pathShape) , endPoint(pointData1.pointIndex) , startPoint(pointData2.pointIndex) , splitIndex(KoPathPointIndex(-1, -1)) , removedPoint(0) , mergedPointIndex(-1, -1) , reverse(ReverseNone) { } ~Private() { delete removedPoint; } KoPathPoint * mergePoints(KoPathPoint * p1, KoPathPoint * p2) { QPointF mergePosition = 0.5 * (p1->point() + p2->point()); QPointF mergeControlPoint1 = mergePosition + (p1->controlPoint1() - p1->point()); QPointF mergeControlPoint2 = mergePosition + (p2->controlPoint2() - p2->point()); // change position and control points of first merged point p1->setPoint(mergePosition); if (p1->activeControlPoint1()) { p1->setControlPoint1(mergeControlPoint1); } if (p2->activeControlPoint2()) { p1->setControlPoint2(mergeControlPoint2); } // remove the second merged point KoPathPointIndex removeIndex = pathShape->pathPointIndex(p2); return pathShape->removePoint(removeIndex); } void resetPoints(KoPathPointIndex index1, KoPathPointIndex index2) { KoPathPoint * p1 = pathShape->pointByIndex(index1); KoPathPoint * p2 = pathShape->pointByIndex(index2); p1->setPoint(pathShape->documentToShape(oldNodePoint1)); p2->setPoint(pathShape->documentToShape(oldNodePoint2)); if (p1->activeControlPoint1()) { p1->setControlPoint1(pathShape->documentToShape(oldControlPoint1)); } if (p2->activeControlPoint2()) { p2->setControlPoint2(pathShape->documentToShape(oldControlPoint2)); } } KoPathShape * pathShape; KoPathPointIndex endPoint; KoPathPointIndex startPoint; KoPathPointIndex splitIndex; // the control points have to be stored in document positions QPointF oldNodePoint1; QPointF oldControlPoint1; QPointF oldNodePoint2; QPointF oldControlPoint2; KoPathPoint * removedPoint; KoPathPointIndex mergedPointIndex; enum Reverse { ReverseNone = 0, ReverseFirst = 1, ReverseSecond = 2 }; int reverse; }; /** * How does is work: * * The goal is to merge the point that is ending an open subpath with the one * starting the same or another open subpath. */ KoPathPointMergeCommand::KoPathPointMergeCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2, KUndo2Command *parent) : KUndo2Command(parent), d(new Private(pointData1, pointData2)) { KIS_ASSERT(pointData1.pathShape == pointData2.pathShape); KIS_ASSERT(d->pathShape); KIS_ASSERT(!d->pathShape->isClosedSubpath(d->endPoint.first)); KIS_ASSERT(!d->pathShape->isClosedSubpath(d->startPoint.first)); KIS_ASSERT(d->endPoint.second == 0 || d->endPoint.second == d->pathShape->subpathPointCount(d->endPoint.first) - 1); KIS_ASSERT(d->startPoint.second == 0 || d->startPoint.second == d->pathShape->subpathPointCount(d->startPoint.first) - 1); KIS_ASSERT(d->startPoint != d->endPoint); // if we have two different subpaths we might need to reverse them if (d->endPoint.first != d->startPoint.first) { // sort by point index if (d->startPoint < d->endPoint) - qSwap(d->endPoint, d->startPoint); + std::swap(d->endPoint, d->startPoint); // mark first subpath to be reversed if first point starts a subpath with more than one point if (d->endPoint.second == 0 && d->pathShape->subpathPointCount(d->endPoint.first) > 1) d->reverse |= Private::ReverseFirst; // mark second subpath to be reversed if second point does not start a subpath with more than one point if (d->startPoint.second != 0 && d->pathShape->subpathPointCount(d->startPoint.first) > 1) d->reverse |= Private::ReverseSecond; } else { Q_ASSERT(d->endPoint.second != d->startPoint.second); if (d->endPoint < d->startPoint) - qSwap(d->endPoint, d->startPoint); + std::swap(d->endPoint, d->startPoint); } KoPathPoint * p1 = d->pathShape->pointByIndex(d->endPoint); KoPathPoint * p2 = d->pathShape->pointByIndex(d->startPoint); d->oldNodePoint1 = d->pathShape->shapeToDocument(p1->point()); if (d->reverse & Private::ReverseFirst) { d->oldControlPoint1 = d->pathShape->shapeToDocument(p1->controlPoint2()); } else { d->oldControlPoint1 = d->pathShape->shapeToDocument(p1->controlPoint1()); } d->oldNodePoint2 = d->pathShape->shapeToDocument(p2->point()); if (d->reverse & Private::ReverseSecond) { d->oldControlPoint2 = d->pathShape->shapeToDocument(p2->controlPoint1()); } else { d->oldControlPoint2 = d->pathShape->shapeToDocument(p2->controlPoint2()); } setText(kundo2_i18n("Merge points")); } KoPathPointMergeCommand::~KoPathPointMergeCommand() { delete d; } void KoPathPointMergeCommand::redo() { KUndo2Command::redo(); if (d->removedPoint) return; d->pathShape->update(); KoPathPoint * endPoint = d->pathShape->pointByIndex(d->endPoint); KoPathPoint * startPoint = d->pathShape->pointByIndex(d->startPoint); // are we just closing a single subpath ? if (d->endPoint.first == d->startPoint.first) { // change the endpoint of the subpath d->removedPoint = d->mergePoints(endPoint, startPoint); // set endpoint of subpath to close the subpath endPoint->setProperty(KoPathPoint::CloseSubpath); // set new startpoint of subpath to close the subpath KoPathPointIndex newStartIndex(d->startPoint.first,0); d->pathShape->pointByIndex(newStartIndex)->setProperty(KoPathPoint::CloseSubpath); d->mergedPointIndex = d->pathShape->pathPointIndex(endPoint); } else { // first revert subpaths if needed if (d->reverse & Private::ReverseFirst) { d->pathShape->reverseSubpath(d->endPoint.first); } if (d->reverse & Private::ReverseSecond) { d->pathShape->reverseSubpath(d->startPoint.first); } // move the subpaths so the second is directly after the first d->pathShape->moveSubpath(d->startPoint.first, d->endPoint.first + 1); d->splitIndex = d->pathShape->pathPointIndex(endPoint); // join both subpaths d->pathShape->join(d->endPoint.first); // change the first point of the points to merge d->removedPoint = d->mergePoints(endPoint, startPoint); d->mergedPointIndex = d->pathShape->pathPointIndex(endPoint); } d->pathShape->normalize(); d->pathShape->update(); } void KoPathPointMergeCommand::undo() { KUndo2Command::undo(); if (!d->removedPoint) return; d->pathShape->update(); // check if we just have closed a single subpath if (d->endPoint.first == d->startPoint.first) { // open the subpath at the old/new first point d->pathShape->openSubpath(d->startPoint); // reinsert the old first point d->pathShape->insertPoint(d->removedPoint, d->startPoint); // reposition the points d->resetPoints(d->endPoint, d->startPoint); } else { // break merged subpaths apart d->pathShape->breakAfter(d->splitIndex); // reinsert the old second point d->pathShape->insertPoint(d->removedPoint, KoPathPointIndex(d->splitIndex.first+1,0)); // reposition the first point d->resetPoints(d->splitIndex, KoPathPointIndex(d->splitIndex.first+1,0)); // move second subpath to its old position d->pathShape->moveSubpath(d->splitIndex.first+1, d->startPoint.first); // undo the reversion of the subpaths if (d->reverse & Private::ReverseFirst) { d->pathShape->reverseSubpath(d->endPoint.first); } if (d->reverse & Private::ReverseSecond) { d->pathShape->reverseSubpath(d->startPoint.first); } } d->pathShape->normalize(); d->pathShape->update(); // reset the removed point d->removedPoint = 0; d->mergedPointIndex = KoPathPointIndex(-1,-1); } KoPathPointData KoPathPointMergeCommand::mergedPointData() const { return KoPathPointData(d->pathShape, d->mergedPointIndex); } diff --git a/libs/flake/commands/KoPathPointRemoveCommand.cpp b/libs/flake/commands/KoPathPointRemoveCommand.cpp index cacb0a6662..1045b0df29 100644 --- a/libs/flake/commands/KoPathPointRemoveCommand.cpp +++ b/libs/flake/commands/KoPathPointRemoveCommand.cpp @@ -1,199 +1,199 @@ /* This file is part of the KDE project * Copyright (C) 2006,2008 Jan Hambrecht * Copyright (C) 2006,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. */ #include "KoPathPointRemoveCommand.h" #include "KoSubpathRemoveCommand.h" #include "KoShapeController.h" #include "KoPathPoint.h" #include class KoPathPointRemoveCommandPrivate { public: KoPathPointRemoveCommandPrivate() : deletePoints(false) { } ~KoPathPointRemoveCommandPrivate() { if (deletePoints) qDeleteAll(points); } QList pointDataList; QList points; bool deletePoints; }; KUndo2Command *KoPathPointRemoveCommand::createCommand( const QList &pointDataList, KoShapeController *shapeController, KUndo2Command *parent) { /* * We want to decide if we have to: * 1. delete only some points of a path or * 2. delete one or more complete subpath or * 3. delete a complete path */ QList sortedPointData(pointDataList); - qSort(sortedPointData); + std::sort(sortedPointData.begin(), sortedPointData.end()); KoPathPointData last(0, KoPathPointIndex(-1, -1)); // add last at the end so that the point date before last will also be put in // the right places. sortedPointData.append(last); QList pointsOfSubpath; // points of current subpath QList subpathsOfPath; // subpaths of current path QList pointsToDelete; // single points to delete QList subpathToDelete; // single subpaths to delete QList shapesToDelete; // single paths to delete last = sortedPointData.first(); QList::const_iterator it(sortedPointData.constBegin()); for (; it != sortedPointData.constEnd(); ++it) { // check if we have come to the next subpath of the same or another path if (last.pathShape != it->pathShape || last.pointIndex.first != it->pointIndex.first) { // check if all points of the last subpath should be deleted if (last.pathShape->subpathPointCount(last.pointIndex.first) == pointsOfSubpath.size()) { // all points of subpath to be deleted -> mark subpath as to be deleted subpathsOfPath.append(pointsOfSubpath.first()); } else { // not all points of subpath to be deleted -> add them to the delete point list pointsToDelete += pointsOfSubpath; } // clear the suboath point list pointsOfSubpath.clear(); } // check if we have come to the next shape if (last.pathShape != it->pathShape) { // check if all subpath of the shape should be deleted if (last.pathShape->subpathCount() == subpathsOfPath.size()) { // all subpaths of path to be deleted -> add shape to delete shape list shapesToDelete.append(last.pathShape); } else { // not all subpaths of path to be deleted -> add them to delete subpath list subpathToDelete += subpathsOfPath; } subpathsOfPath.clear(); } if (! it->pathShape) continue; // keep reference to last point last = *it; // add this point to the current subpath point list pointsOfSubpath.append(*it); } KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Remove points"), parent); if (pointsToDelete.size() > 0) { new KoPathPointRemoveCommand(pointsToDelete, cmd); } Q_FOREACH (const KoPathPointData & pd, subpathToDelete) { new KoSubpathRemoveCommand(pd.pathShape, pd.pointIndex.first, cmd); } if (shapesToDelete.size() > 0) { shapeController->removeShapes(shapesToDelete, cmd); } return cmd; } KoPathPointRemoveCommand::KoPathPointRemoveCommand(const QList & pointDataList, KUndo2Command *parent) : KUndo2Command(parent), d(new KoPathPointRemoveCommandPrivate()) { QList::const_iterator it(pointDataList.begin()); for (; it != pointDataList.end(); ++it) { KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex); if (point) { d->pointDataList.append(*it); d->points.append(0); } } - qSort(d->pointDataList); + std::sort(d->pointDataList.begin(), d->pointDataList.end()); setText(kundo2_i18n("Remove points")); } KoPathPointRemoveCommand::~KoPathPointRemoveCommand() { delete d; } void KoPathPointRemoveCommand::redo() { KUndo2Command::redo(); KoPathShape * lastPathShape = 0; int updateBefore = d->pointDataList.size(); for (int i = d->pointDataList.size() - 1; i >= 0; --i) { const KoPathPointData &pd = d->pointDataList.at(i); pd.pathShape->update(); d->points[i] = pd.pathShape->removePoint(pd.pointIndex); if (lastPathShape != pd.pathShape) { if (lastPathShape) { QPointF offset = lastPathShape->normalize(); QTransform matrix; matrix.translate(-offset.x(), -offset.y()); for (int j = i + 1; j < updateBefore; ++j) { d->points.at(j)->map(matrix); } lastPathShape->update(); updateBefore = i + 1; } lastPathShape = pd.pathShape; } } if (lastPathShape) { QPointF offset = lastPathShape->normalize(); QTransform matrix; matrix.translate(-offset.x(), -offset.y()); for (int j = 0; j < updateBefore; ++j) { d->points.at(j)->map(matrix); } lastPathShape->update(); } d->deletePoints = true; } void KoPathPointRemoveCommand::undo() { KUndo2Command::undo(); KoPathShape * lastPathShape = 0; for (int i = 0; i < d->pointDataList.size(); ++i) { const KoPathPointData &pd = d->pointDataList.at(i); if (lastPathShape && lastPathShape != pd.pathShape) { lastPathShape->normalize(); lastPathShape->update(); } pd.pathShape->insertPoint(d->points[i], pd.pointIndex); lastPathShape = pd.pathShape; } if (lastPathShape) { lastPathShape->normalize(); lastPathShape->update(); } d->deletePoints = false; } diff --git a/libs/flake/commands/KoShapeGroupCommand.cpp b/libs/flake/commands/KoShapeGroupCommand.cpp index 2fb56c05ca..06643e776b 100644 --- a/libs/flake/commands/KoShapeGroupCommand.cpp +++ b/libs/flake/commands/KoShapeGroupCommand.cpp @@ -1,228 +1,228 @@ /* This file is part of the KDE project * Copyright (C) 2006,2010 Thomas Zander * Copyright (C) 2006,2007 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeGroupCommand.h" #include "KoShapeGroupCommand_p.h" #include "KoShape.h" #include "KoShapeGroup.h" #include "KoShapeContainer.h" #include // static KoShapeGroupCommand * KoShapeGroupCommand::createCommand(KoShapeGroup *container, const QList &shapes, KUndo2Command *parent) { QList orderedShapes(shapes); - qSort(orderedShapes.begin(), orderedShapes.end(), KoShape::compareShapeZIndex); + std::sort(orderedShapes.begin(), orderedShapes.end(), KoShape::compareShapeZIndex); if (!orderedShapes.isEmpty()) { KoShape * top = orderedShapes.last(); container->setParent(top->parent()); container->setZIndex(top->zIndex()); } return new KoShapeGroupCommand(container, orderedShapes, parent); } KoShapeGroupCommandPrivate::KoShapeGroupCommandPrivate(KoShapeContainer *c, const QList &s, const QList &clip, const QList &it, bool _shouldNormalize) : shapes(s), clipped(clip), inheritTransform(it), shouldNormalize(_shouldNormalize), container(c) { } KoShapeGroupCommand::KoShapeGroupCommand(KoShapeContainer *container, const QList &shapes, const QList &clipped, const QList &inheritTransform, KUndo2Command *parent) : KUndo2Command(parent), d(new KoShapeGroupCommandPrivate(container,shapes, clipped, inheritTransform, true)) { Q_ASSERT(d->clipped.count() == d->shapes.count()); Q_ASSERT(d->inheritTransform.count() == d->shapes.count()); d->init(this); } KoShapeGroupCommand::KoShapeGroupCommand(KoShapeGroup *container, const QList &shapes, KUndo2Command *parent) : KUndo2Command(parent), d(new KoShapeGroupCommandPrivate(container,shapes, QList(), QList(), true)) { for (int i = 0; i < shapes.count(); ++i) { d->clipped.append(false); d->inheritTransform.append(false); } d->init(this); } KoShapeGroupCommand::KoShapeGroupCommand(KoShapeContainer *container, const QList &shapes, bool clipped, bool inheritTransform, bool shouldNormalize, KUndo2Command *parent) : KUndo2Command(parent), d(new KoShapeGroupCommandPrivate(container,shapes, QList(), QList(), shouldNormalize)) { for (int i = 0; i < shapes.count(); ++i) { d->clipped.append(clipped); d->inheritTransform.append(inheritTransform); } d->init(this); } KoShapeGroupCommand::~KoShapeGroupCommand() { delete d; } KoShapeGroupCommand::KoShapeGroupCommand(KoShapeGroupCommandPrivate &dd, KUndo2Command *parent) : KUndo2Command(parent), d(&dd) { } void KoShapeGroupCommandPrivate::init(KUndo2Command *q) { Q_FOREACH (KoShape* shape, shapes) { oldParents.append(shape->parent()); oldClipped.append(shape->parent() && shape->parent()->isClipped(shape)); oldInheritTransform.append(shape->parent() && shape->parent()->inheritsTransform(shape)); oldZIndex.append(shape->zIndex()); } if (container->shapes().isEmpty()) { q->setText(kundo2_i18n("Group shapes")); } else { q->setText(kundo2_i18n("Add shapes to group")); } } void KoShapeGroupCommand::redo() { KUndo2Command::redo(); if (d->shouldNormalize && dynamic_cast(d->container)) { QRectF bound = d->containerBoundingRect(); QPointF oldGroupPosition = d->container->absolutePosition(KoFlake::TopLeft); d->container->setAbsolutePosition(bound.topLeft(), KoFlake::TopLeft); d->container->setSize(bound.size()); if (d->container->shapeCount() > 0) { // the group has changed position and so have the group child shapes // -> we need compensate the group position change QPointF positionOffset = oldGroupPosition - bound.topLeft(); Q_FOREACH (KoShape * child, d->container->shapes()) child->setAbsolutePosition(child->absolutePosition() + positionOffset); } } QTransform groupTransform = d->container->absoluteTransformation(0).inverted(); int zIndex=0; QList shapes(d->container->shapes()); if (!shapes.isEmpty()) { - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); zIndex = shapes.last()->zIndex(); } uint shapeCount = d->shapes.count(); for (uint i = 0; i < shapeCount; ++i) { KoShape * shape = d->shapes[i]; shape->setZIndex(zIndex++); if(d->inheritTransform[i]) { shape->applyAbsoluteTransformation(groupTransform); } else { QSizeF containerSize = d->container->size(); QPointF containerPos = d->container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height()); QTransform matrix; matrix.translate(containerPos.x(), containerPos.y()); shape->applyAbsoluteTransformation(matrix.inverted()); } d->container->addShape(shape); d->container->setClipped(shape, d->clipped[i]); d->container->setInheritsTransform(shape, d->inheritTransform[i]); } } void KoShapeGroupCommand::undo() { KUndo2Command::undo(); QTransform ungroupTransform = d->container->absoluteTransformation(0); for (int i = 0; i < d->shapes.count(); i++) { KoShape * shape = d->shapes[i]; const bool inheritedTransform = d->container->inheritsTransform(shape); d->container->removeShape(shape); if (d->oldParents.at(i)) { d->oldParents.at(i)->addShape(shape); d->oldParents.at(i)->setClipped(shape, d->oldClipped.at(i)); d->oldParents.at(i)->setInheritsTransform(shape, d->oldInheritTransform.at(i)); } if(inheritedTransform) { shape->applyAbsoluteTransformation(ungroupTransform); } else { QSizeF containerSize = d->container->size(); QPointF containerPos = d->container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height()); QTransform matrix; matrix.translate(containerPos.x(), containerPos.y()); shape->applyAbsoluteTransformation(matrix); } shape->setZIndex(d->oldZIndex[i]); } if (d->shouldNormalize && dynamic_cast(d->container)) { QPointF oldGroupPosition = d->container->absolutePosition(KoFlake::TopLeft); if (d->container->shapeCount() > 0) { bool boundingRectInitialized = false; QRectF bound; Q_FOREACH (KoShape * shape, d->container->shapes()) { if (! boundingRectInitialized) { bound = shape->boundingRect(); boundingRectInitialized = true; } else bound = bound.united(shape->boundingRect()); } // the group has changed position and so have the group child shapes // -> we need compensate the group position change QPointF positionOffset = oldGroupPosition - bound.topLeft(); Q_FOREACH (KoShape * child, d->container->shapes()) child->setAbsolutePosition(child->absolutePosition() + positionOffset); d->container->setAbsolutePosition(bound.topLeft(), KoFlake::TopLeft); d->container->setSize(bound.size()); } } } QRectF KoShapeGroupCommandPrivate::containerBoundingRect() { QRectF bound; if (container->shapeCount() > 0) { bound = container->absoluteTransformation(0).mapRect(container->outlineRect()); } Q_FOREACH (KoShape *shape, shapes) { bound |= shape->absoluteTransformation(0).mapRect(shape->outlineRect()); } return bound; } diff --git a/libs/flake/commands/KoShapeReorderCommand.cpp b/libs/flake/commands/KoShapeReorderCommand.cpp index 4a1d37471c..9fda9d4e5b 100644 --- a/libs/flake/commands/KoShapeReorderCommand.cpp +++ b/libs/flake/commands/KoShapeReorderCommand.cpp @@ -1,220 +1,220 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeReorderCommand.h" #include "KoShape.h" #include "KoShape_p.h" #include "KoShapeManager.h" #include "KoShapeContainer.h" #include #include #include class KoShapeReorderCommandPrivate { public: KoShapeReorderCommandPrivate(const QList &s, QList &ni) : shapes(s), newIndexes(ni) { } QList shapes; QList previousIndexes; QList newIndexes; }; KoShapeReorderCommand::KoShapeReorderCommand(const QList &shapes, QList &newIndexes, KUndo2Command *parent) : KUndo2Command(parent), d(new KoShapeReorderCommandPrivate(shapes, newIndexes)) { Q_ASSERT(shapes.count() == newIndexes.count()); foreach (KoShape *shape, shapes) d->previousIndexes.append(shape->zIndex()); setText(kundo2_i18n("Reorder shapes")); } KoShapeReorderCommand::~KoShapeReorderCommand() { delete d; } void KoShapeReorderCommand::redo() { KUndo2Command::redo(); for (int i = 0; i < d->shapes.count(); i++) { d->shapes.at(i)->update(); d->shapes.at(i)->setZIndex(d->newIndexes.at(i)); d->shapes.at(i)->update(); } } void KoShapeReorderCommand::undo() { KUndo2Command::undo(); for (int i = 0; i < d->shapes.count(); i++) { d->shapes.at(i)->update(); d->shapes.at(i)->setZIndex(d->previousIndexes.at(i)); d->shapes.at(i)->update(); } } static void prepare(KoShape *s, QMap > &newOrder, KoShapeManager *manager, KoShapeReorderCommand::MoveShapeType move) { KoShapeContainer *parent = s->parent(); QMap >::iterator it(newOrder.find(parent)); if (it == newOrder.end()) { QList children; if (parent != 0) { children = parent->shapes(); } else { // get all toplevel shapes children = manager->topLevelShapes(); } - qSort(children.begin(), children.end(), KoShape::compareShapeZIndex); + std::sort(children.begin(), children.end(), KoShape::compareShapeZIndex); // the append and prepend are needed so that the raise/lower of all shapes works as expected. children.append(0); children.prepend(0); it = newOrder.insert(parent, children); } QList & shapes(newOrder[parent]); int index = shapes.indexOf(s); if (index != -1) { shapes.removeAt(index); switch (move) { case KoShapeReorderCommand::BringToFront: index = shapes.size(); break; case KoShapeReorderCommand::RaiseShape: if (index < shapes.size()) { ++index; } break; case KoShapeReorderCommand::LowerShape: if (index > 0) { --index; } break; case KoShapeReorderCommand::SendToBack: index = 0; break; } shapes.insert(index,s); } } // static KoShapeReorderCommand *KoShapeReorderCommand::createCommand(const QList &shapes, KoShapeManager *manager, MoveShapeType move, KUndo2Command *parent) { QList newIndexes; QList changedShapes; QMap > newOrder; QList sortedShapes(shapes); - qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); + std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); if (move == BringToFront || move == LowerShape) { for (int i = 0; i < sortedShapes.size(); ++i) { prepare(sortedShapes.at(i), newOrder, manager, move); } } else { for (int i = sortedShapes.size() - 1; i >= 0; --i) { prepare(sortedShapes.at(i), newOrder, manager, move); } } QMap >::iterator newIt(newOrder.begin()); for (; newIt!= newOrder.end(); ++newIt) { QList order(newIt.value()); order.removeAll(0); int index = -KoShapePrivate::MaxZIndex - 1; // set minimum zIndex int pos = 0; for (; pos < order.size(); ++pos) { if (order[pos]->zIndex() > index) { index = order[pos]->zIndex(); } else { break; } } if (pos == order.size()) { //nothing needs to be done continue; } else if (pos <= order.size() / 2) { // new index for the front int startIndex = order[pos]->zIndex() - pos; for (int i = 0; i < pos; ++i) { changedShapes.append(order[i]); newIndexes.append(startIndex++); } } else { //new index for the end for (int i = pos; i < order.size(); ++i) { changedShapes.append(order[i]); newIndexes.append(++index); } } } Q_ASSERT(changedShapes.count() == newIndexes.count()); return changedShapes.isEmpty() ? 0: new KoShapeReorderCommand(changedShapes, newIndexes, parent); } KoShapeReorderCommand *KoShapeReorderCommand::mergeInShape(QList shapes, KoShape *newShape, KUndo2Command *parent) { - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QList reindexedShapes; QList reindexedIndexes; const int originalShapeZIndex = newShape->zIndex(); int newShapeZIndex = originalShapeZIndex; int lastOccupiedShapeZIndex = originalShapeZIndex + 1; Q_FOREACH (KoShape *shape, shapes) { if (shape == newShape) continue; const int zIndex = shape->zIndex(); if (newShapeZIndex == originalShapeZIndex) { if (zIndex == originalShapeZIndex) { newShapeZIndex = originalShapeZIndex + 1; lastOccupiedShapeZIndex = newShapeZIndex; reindexedShapes << newShape; reindexedIndexes << newShapeZIndex; } } else { if (newShapeZIndex != originalShapeZIndex && zIndex >= newShapeZIndex && zIndex <= lastOccupiedShapeZIndex) { lastOccupiedShapeZIndex = zIndex + 1; reindexedShapes << shape; reindexedIndexes << lastOccupiedShapeZIndex; } } } return !reindexedShapes.isEmpty() ? new KoShapeReorderCommand(reindexedShapes, reindexedIndexes, parent) : 0; } diff --git a/libs/flake/commands/KoShapeUngroupCommand.cpp b/libs/flake/commands/KoShapeUngroupCommand.cpp index a2afe90448..90b3d69bd8 100644 --- a/libs/flake/commands/KoShapeUngroupCommand.cpp +++ b/libs/flake/commands/KoShapeUngroupCommand.cpp @@ -1,84 +1,84 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2006 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeUngroupCommand.h" #include "KoShapeGroupCommand_p.h" #include "KoShapeContainer.h" #include KoShapeUngroupCommand::KoShapeUngroupCommand(KoShapeContainer *container, const QList &shapes, const QList &topLevelShapes, KUndo2Command *parent) : KoShapeGroupCommand(*(new KoShapeGroupCommandPrivate(container, shapes, QList(), QList(), false)), parent) { QList orderdShapes(shapes); - qSort(orderdShapes.begin(), orderdShapes.end(), KoShape::compareShapeZIndex); + std::sort(orderdShapes.begin(), orderdShapes.end(), KoShape::compareShapeZIndex); d->shapes = orderdShapes; QList ancestors = d->container->parent()? d->container->parent()->shapes(): topLevelShapes; if (ancestors.count()) { - qSort(ancestors.begin(), ancestors.end(), KoShape::compareShapeZIndex); - QList::const_iterator it(qFind(ancestors, d->container)); + std::sort(ancestors.begin(), ancestors.end(), KoShape::compareShapeZIndex); + QList::const_iterator it(std::find(ancestors.constBegin(), ancestors.constEnd(), d->container)); Q_ASSERT(it != ancestors.constEnd()); for (; it != ancestors.constEnd(); ++it) { d->oldAncestorsZIndex.append(QPair(*it, (*it)->zIndex())); } } int zIndex = d->container->zIndex(); Q_FOREACH (KoShape *shape, d->shapes) { d->clipped.append(d->container->isClipped(shape)); d->oldParents.append(d->container->parent()); d->oldClipped.append(d->container->isClipped(shape)); d->oldInheritTransform.append(shape->parent() && shape->parent()->inheritsTransform(shape)); d->inheritTransform.append(d->container->inheritsTransform(shape)); // TODO this might also need to change the children of the parent but that is very problematic if the parent is 0 d->oldZIndex.append(zIndex++); } d->shouldNormalize = false; setText(kundo2_i18n("Ungroup shapes")); } void KoShapeUngroupCommand::redo() { KoShapeGroupCommand::undo(); if (d->oldAncestorsZIndex.count()) { int zIndex = d->container->zIndex() + d->oldZIndex.count() - 1; for (QList >::const_iterator it(d->oldAncestorsZIndex.constBegin()); it != d->oldAncestorsZIndex.constEnd(); ++it) { it->first->setZIndex(zIndex++); } } } void KoShapeUngroupCommand::undo() { KoShapeGroupCommand::redo(); if (d->oldAncestorsZIndex.count()) { for (QList >::const_iterator it(d->oldAncestorsZIndex.constBegin()); it != d->oldAncestorsZIndex.constEnd(); ++it) { it->first->setZIndex(it->second); } } } diff --git a/libs/flake/commands/KoSubpathJoinCommand.cpp b/libs/flake/commands/KoSubpathJoinCommand.cpp index 2791a1ccd6..2ea3d3655f 100644 --- a/libs/flake/commands/KoSubpathJoinCommand.cpp +++ b/libs/flake/commands/KoSubpathJoinCommand.cpp @@ -1,149 +1,149 @@ /* This file is part of the KDE project * Copyright (C) 2006 Jan Hambrecht * Copyright (C) 2006,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. */ #include "KoSubpathJoinCommand.h" #include KoSubpathJoinCommand::KoSubpathJoinCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2, KUndo2Command *parent) : KUndo2Command(parent) , m_pointData1(pointData1) , m_pointData2(pointData2) , m_splitIndex(KoPathPointIndex(-1, -1)) , m_oldProperties1(KoPathPoint::Normal) , m_oldProperties2(KoPathPoint::Normal) , m_reverse(0) { Q_ASSERT(m_pointData1.pathShape == m_pointData2.pathShape); KoPathShape * pathShape = m_pointData1.pathShape; Q_ASSERT(!pathShape->isClosedSubpath(m_pointData1.pointIndex.first)); Q_ASSERT(m_pointData1.pointIndex.second == 0 || m_pointData1.pointIndex.second == pathShape->subpathPointCount(m_pointData1.pointIndex.first) - 1); Q_ASSERT(!pathShape->isClosedSubpath(m_pointData2.pointIndex.first)); Q_ASSERT(m_pointData2.pointIndex.second == 0 || m_pointData2.pointIndex.second == pathShape->subpathPointCount(m_pointData2.pointIndex.first) - 1); //TODO check that points are not the same if (m_pointData2 < m_pointData1) - qSwap(m_pointData1, m_pointData2); + std::swap(m_pointData1, m_pointData2); if (m_pointData1.pointIndex.first != m_pointData2.pointIndex.first) { if (m_pointData1.pointIndex.second == 0 && pathShape->subpathPointCount(m_pointData1.pointIndex.first) > 1) m_reverse |= ReverseFirst; if (m_pointData2.pointIndex.second != 0) m_reverse |= ReverseSecond; setText(kundo2_i18n("Close subpath")); } else { setText(kundo2_i18n("Join subpaths")); } KoPathPoint * point1 = pathShape->pointByIndex(m_pointData1.pointIndex); KoPathPoint * point2 = pathShape->pointByIndex(m_pointData2.pointIndex); m_oldControlPoint1 = QPointF(pathShape->shapeToDocument(m_reverse & 1 ? point1->controlPoint1() : point1->controlPoint2())); m_oldControlPoint2 = QPointF(pathShape->shapeToDocument(m_reverse & 2 ? point2->controlPoint1() : point2->controlPoint2())); m_oldProperties1 = point1->properties(); m_oldProperties2 = point2->properties(); } KoSubpathJoinCommand::~KoSubpathJoinCommand() { } void KoSubpathJoinCommand::redo() { KUndo2Command::redo(); KoPathShape * pathShape = m_pointData1.pathShape; bool closeSubpath = m_pointData1.pointIndex.first == m_pointData2.pointIndex.first; KoPathPoint * point1 = pathShape->pointByIndex(m_pointData1.pointIndex); KoPathPoint * point2 = pathShape->pointByIndex(m_pointData2.pointIndex); // if the endpoint is has a control point create a contol point for the new segment to be // at the symetric position to the exiting one if (m_reverse & ReverseFirst || closeSubpath) { if (point1->activeControlPoint2()) point1->setControlPoint1(2.0 * point1->point() - point1->controlPoint2()); } else if (point1->activeControlPoint1()) point1->setControlPoint2(2.0 * point1->point() - point1->controlPoint1()); if (m_reverse & ReverseSecond || closeSubpath) { if (point2->activeControlPoint1()) point2->setControlPoint2(2.0 * point2->point() - point2->controlPoint1()); } else if (point2->activeControlPoint2()) point2->setControlPoint1(2.0 * point2->point() - point2->controlPoint2()); if (closeSubpath) { pathShape->closeSubpath(m_pointData1.pointIndex); } else { if (m_reverse & ReverseFirst) { pathShape->reverseSubpath(m_pointData1.pointIndex.first); } if (m_reverse & ReverseSecond) { pathShape->reverseSubpath(m_pointData2.pointIndex.first); } pathShape->moveSubpath(m_pointData2.pointIndex.first, m_pointData1.pointIndex.first + 1); m_splitIndex = m_pointData1.pointIndex; m_splitIndex.second = pathShape->subpathPointCount(m_pointData1.pointIndex.first) - 1; pathShape->join(m_pointData1.pointIndex.first); } pathShape->normalize(); pathShape->update(); } void KoSubpathJoinCommand::undo() { KUndo2Command::undo(); KoPathShape * pathShape = m_pointData1.pathShape; pathShape->update(); if (m_pointData1.pointIndex.first == m_pointData2.pointIndex.first) { pathShape->openSubpath(m_pointData1.pointIndex); } else { pathShape->breakAfter(m_splitIndex); pathShape->moveSubpath(m_pointData1.pointIndex.first + 1, m_pointData2.pointIndex.first); if (m_reverse & ReverseSecond) { pathShape->reverseSubpath(m_pointData2.pointIndex.first); } if (m_reverse & ReverseFirst) { pathShape->reverseSubpath(m_pointData1.pointIndex.first); } } KoPathPoint * point1 = pathShape->pointByIndex(m_pointData1.pointIndex); KoPathPoint * point2 = pathShape->pointByIndex(m_pointData2.pointIndex); // restore the old end points if (m_reverse & ReverseFirst) point1->setControlPoint1(pathShape->documentToShape(m_oldControlPoint1)); else point1->setControlPoint2(pathShape->documentToShape(m_oldControlPoint1)); point1->setProperties(m_oldProperties1); if (m_reverse & ReverseSecond) point2->setControlPoint1(pathShape->documentToShape(m_oldControlPoint2)); else point2->setControlPoint2(pathShape->documentToShape(m_oldControlPoint2)); point2->setProperties(m_oldProperties2); pathShape->normalize(); pathShape->update(); } diff --git a/libs/flake/resources/KoSvgSymbolCollectionResource.cpp b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp index 9b8e88433a..aad020dbeb 100644 --- a/libs/flake/resources/KoSvgSymbolCollectionResource.cpp +++ b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp @@ -1,253 +1,253 @@ /* This file is part of the KDE project Copyright (c) 2017 L. E. Segovia 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; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include #include #include #include #include #include struct KoSvgSymbolCollectionResource::Private { QVector symbols; QString title; QString description; }; KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource(const QString& filename) : KoResource(filename) , d(new Private()) { } KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource() : KoResource(QString()) , d(new Private()) { } KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource(const KoSvgSymbolCollectionResource& rhs) : QObject(0) , KoResource(QString()) , d(new Private()) { setFilename(rhs.filename()); d->symbols = rhs.d->symbols; setValid(true); } KoSvgSymbolCollectionResource::~KoSvgSymbolCollectionResource() { } bool KoSvgSymbolCollectionResource::load() { QFile file(filename()); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { return false; } bool res = loadFromDevice(&file); file.close(); return res; } void paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { QList shapes = group->shapes(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape *child, shapes) { // we paint recursively here, so we do not have to check recursively for visibility if (!child->isVisible()) continue; KoShapeGroup *childGroup = dynamic_cast(child); if (childGroup) { paintGroup(childGroup, painter, converter, paintContext); } else { painter.save(); KoShapeManager::renderSingleShape(child, painter, converter, paintContext); painter.restore(); } } } bool KoSvgSymbolCollectionResource::loadFromDevice(QIODevice *dev) { if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); KoXmlDocument doc; QString errorMsg; int errorLine = 0; int errorColumn; bool ok = doc.setContent(dev->readAll(), false, &errorMsg, &errorLine, &errorColumn); if (!ok) { errKrita << "Parsing error in " << filename() << "! 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 false; } KoDocumentResourceManager manager; SvgParser parser(&manager); parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values QSizeF fragmentSize; // We're not interested in the shapes themselves qDeleteAll(parser.parseSvg(doc.documentElement(), &fragmentSize)); d->symbols = parser.takeSymbols(); // qDebug() << "Loaded" << filename() << "\n\t" // << "Title" << parser.documentTitle() << "\n\t" // << "Description" << parser.documentDescription() // << "\n\tgot" << d->symbols.size() << "symbols" // << d->symbols[0]->shape->outlineRect() // << d->symbols[0]->shape->size(); d->title = parser.documentTitle(); setName(d->title); d->description = parser.documentDescription(); if (d->symbols.size() < 1) { setValid(false); return false; } setValid(true);; for(int i = 0; i < d->symbols.size(); ++i) { KoSvgSymbol *symbol = d->symbols[i]; KoShapeGroup *group = dynamic_cast(symbol->shape); Q_ASSERT(group); QRectF rc = group->boundingRect().normalized(); QImage image(rc.width(), rc.height(), QImage::Format_ARGB32_Premultiplied); QPainter gc(&image); image.fill(Qt::gray); KoViewConverter vc; KoShapePaintingContext ctx; // qDebug() << "Going to render. Original bounding rect:" << group->boundingRect() // << "Normalized: " << rc // << "Scale W" << 256 / rc.width() << "Scale H" << 256 / rc.height(); gc.translate(-rc.x(), -rc.y()); paintGroup(group, gc, vc, ctx); gc.end(); d->symbols[i]->icon = image.scaled(500, 500, Qt::KeepAspectRatio); } setImage(d->symbols[0]->icon); return true; } bool KoSvgSymbolCollectionResource::save() { QFile file(filename()); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return false; } saveToDevice(&file); file.close(); return true; } bool KoSvgSymbolCollectionResource::saveToDevice(QIODevice *dev) const { bool res = false; // XXX if (res) { KoResource::saveToDevice(dev); } return res; } QString KoSvgSymbolCollectionResource::defaultFileExtension() const { return QString(".svg"); } QString KoSvgSymbolCollectionResource::title() const { return d->title; } QString KoSvgSymbolCollectionResource::description() const { return d->description; } QString KoSvgSymbolCollectionResource::creator() const { return ""; } QString KoSvgSymbolCollectionResource::rights() const { return ""; } QString KoSvgSymbolCollectionResource::language() const { return ""; } QStringList KoSvgSymbolCollectionResource::subjects() const { return QStringList(); } QString KoSvgSymbolCollectionResource::license() const { return ""; } QStringList KoSvgSymbolCollectionResource::permits() const { return QStringList(); } QVector KoSvgSymbolCollectionResource::symbols() const { return d->symbols; } diff --git a/libs/flake/svg/SvgWriter.cpp b/libs/flake/svg/SvgWriter.cpp index f4c18bc95e..1ffceec5e9 100644 --- a/libs/flake/svg/SvgWriter.cpp +++ b/libs/flake/svg/SvgWriter.cpp @@ -1,272 +1,272 @@ /* This file is part of the KDE project Copyright (C) 2002 Lars Siebold Copyright (C) 2002-2003,2005 Rob Buis Copyright (C) 2002,2005-2006 David Faure Copyright (C) 2002 Werner Trobin Copyright (C) 2002 Lennart Kudling Copyright (C) 2004 Nicolas Goutte Copyright (C) 2005 Boudewijn Rempt Copyright (C) 2005 Raphael Langerhorst Copyright (C) 2005 Thomas Zander Copyright (C) 2005,2007-2008 Jan Hambrecht Copyright (C) 2006 Inge Wallin Copyright (C) 2006 Martin Pfeiffer Copyright (C) 2006 Gábor Lehel Copyright (C) 2006 Laurent Montel Copyright (C) 2006 Christian Mueller Copyright (C) 2006 Ariya Hidayat Copyright (C) 2010 Thorsten Zachmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SvgWriter.h" #include "SvgUtil.h" #include "SvgSavingContext.h" #include "SvgShape.h" #include "SvgStyleWriter.h" #include #include #include #include #include #include #include #include #include #include #include #include SvgWriter::SvgWriter(const QList &layers) : m_writeInlineImages(true) { Q_FOREACH (KoShapeLayer *layer, layers) m_toplevelShapes.append(layer); } SvgWriter::SvgWriter(const QList &toplevelShapes) : m_toplevelShapes(toplevelShapes) , m_writeInlineImages(true) { } SvgWriter::~SvgWriter() { } bool SvgWriter::save(const QString &filename, const QSizeF &pageSize, bool writeInlineImages) { QFile fileOut(filename); if (!fileOut.open(QIODevice::WriteOnly)) return false; m_writeInlineImages = writeInlineImages; const bool success = save(fileOut, pageSize); m_writeInlineImages = true; fileOut.close(); return success; } bool SvgWriter::save(QIODevice &outputDevice, const QSizeF &pageSize) { if (m_toplevelShapes.isEmpty()) return false; QTextStream svgStream(&outputDevice); svgStream.setCodec("UTF-8"); // standard header: svgStream << "" << endl; svgStream << "" << endl; // add some PR. one line is more than enough. svgStream << "" << endl; svgStream << "" << endl; { SvgSavingContext savingContext(outputDevice, m_writeInlineImages); saveShapes(m_toplevelShapes, savingContext); } // end tag: svgStream << endl << "" << endl; return true; } bool SvgWriter::saveDetached(QIODevice &outputDevice) { if (m_toplevelShapes.isEmpty()) return false; SvgSavingContext savingContext(outputDevice, m_writeInlineImages); saveShapes(m_toplevelShapes, savingContext); return true; } void SvgWriter::saveShapes(const QList shapes, SvgSavingContext &savingContext) { // top level shapes Q_FOREACH (KoShape *shape, shapes) { KoShapeLayer *layer = dynamic_cast(shape); if(layer) { saveLayer(layer, savingContext); } else { KoShapeGroup *group = dynamic_cast(shape); if (group) saveGroup(group, savingContext); else saveShape(shape, savingContext); } } } void SvgWriter::saveLayer(KoShapeLayer *layer, SvgSavingContext &context) { context.shapeWriter().startElement("g"); context.shapeWriter().addAttribute("id", context.getID(layer)); QList sortedShapes = layer->shapes(); - qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); + std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape * shape, sortedShapes) { KoShapeGroup * group = dynamic_cast(shape); if (group) saveGroup(group, context); else saveShape(shape, context); } context.shapeWriter().endElement(); } void SvgWriter::saveGroup(KoShapeGroup * group, SvgSavingContext &context) { context.shapeWriter().startElement("g"); context.shapeWriter().addAttribute("id", context.getID(group)); context.shapeWriter().addAttribute("transform", SvgUtil::transformToString(group->transformation())); SvgStyleWriter::saveSvgStyle(group, context); QList sortedShapes = group->shapes(); - qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); + std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); Q_FOREACH (KoShape * shape, sortedShapes) { KoShapeGroup * childGroup = dynamic_cast(shape); if (childGroup) saveGroup(childGroup, context); else saveShape(shape, context); } context.shapeWriter().endElement(); } void SvgWriter::saveShape(KoShape *shape, SvgSavingContext &context) { SvgShape *svgShape = dynamic_cast(shape); if (svgShape && svgShape->saveSvg(context)) return; KoPathShape * path = dynamic_cast(shape); if (path) { savePath(path, context); } else { // generic saving of shape via a switch element saveGeneric(shape, context); } } void SvgWriter::savePath(KoPathShape *path, SvgSavingContext &context) { context.shapeWriter().startElement("path"); context.shapeWriter().addAttribute("id", context.getID(path)); context.shapeWriter().addAttribute("transform", SvgUtil::transformToString(path->transformation())); SvgStyleWriter::saveSvgStyle(path, context); context.shapeWriter().addAttribute("d", path->toString(context.userSpaceTransform())); context.shapeWriter().endElement(); } void SvgWriter::saveGeneric(KoShape *shape, SvgSavingContext &context) { const QRectF bbox = shape->boundingRect(); // paint shape to the image KoShapePainter painter; painter.setShapes(QList()<< shape); // generate svg from shape QBuffer svgBuffer; QSvgGenerator svgGenerator; svgGenerator.setOutputDevice(&svgBuffer); QPainter svgPainter; svgPainter.begin(&svgGenerator); painter.paint(svgPainter, SvgUtil::toUserSpace(bbox).toRect(), bbox); svgPainter.end(); // remove anything before the start of the svg element from the buffer int startOfContent = svgBuffer.buffer().indexOf("0) { svgBuffer.buffer().remove(0, startOfContent); } // check if painting to svg produced any output if (svgBuffer.buffer().isEmpty()) { // prepare a transparent image, make it twice as big as the original size QImage image(2*bbox.size().toSize(), QImage::Format_ARGB32); image.fill(0); painter.paint(image); context.shapeWriter().startElement("image"); context.shapeWriter().addAttribute("id", context.getID(shape)); context.shapeWriter().addAttributePt("x", bbox.x()); context.shapeWriter().addAttributePt("y", bbox.y()); context.shapeWriter().addAttributePt("width", bbox.width()); context.shapeWriter().addAttributePt("height", bbox.height()); context.shapeWriter().addAttribute("xlink:href", context.saveImage(image)); context.shapeWriter().endElement(); // image } else { context.shapeWriter().addCompleteElement(&svgBuffer); } // TODO: once we support saving single (flat) odf files // we can embed these here to have full support for generic shapes } diff --git a/libs/flake/tests/TestPathShape.cpp b/libs/flake/tests/TestPathShape.cpp index 9d489247fe..b627e82c43 100644 --- a/libs/flake/tests/TestPathShape.cpp +++ b/libs/flake/tests/TestPathShape.cpp @@ -1,785 +1,785 @@ /* This file is part of the KDE project Copyright (C) 2006 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 "TestPathShape.h" #include #include "KoPathShape.h" #include "KoPathPoint.h" #include "KoPathPointData.h" #include "KoPathSegment.h" #include void TestPathShape::close() { KoPathShape path; path.lineTo(QPointF(10, 0)); path.lineTo(QPointF(10, 10)); QPainterPath ppath(QPointF(0, 0)); ppath.lineTo(QPointF(10, 0)); ppath.lineTo(10, 10); QVERIFY(ppath == path.outline()); path.close(); ppath.closeSubpath(); QVERIFY(ppath == path.outline()); path.lineTo(QPointF(0, 10)); ppath.lineTo(0, 10); QVERIFY(ppath == path.outline()); } void TestPathShape::moveTo() { KoPathShape path; path.moveTo(QPointF(10, 10)); QPainterPath ppath(QPointF(10, 10)); path.lineTo(QPointF(20, 20)); ppath.lineTo(20, 20); QVERIFY(ppath == path.outline()); path.moveTo(QPointF(30, 30)); ppath.moveTo(30, 30); path.lineTo(QPointF(40, 40)); ppath.lineTo(QPointF(40, 40)); QVERIFY(ppath == path.outline()); } void TestPathShape::normalize() { KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 20)); path.normalize(); QPainterPath ppath(QPointF(0, 0)); ppath.lineTo(10, 10); QVERIFY(ppath == path.outline()); } void TestPathShape::pathPointIndex() { KoPathShape path; KoPathPoint * point1 = path.moveTo(QPointF(10, 10)); KoPathPointIndex p1Index(0, 0); KoPathPoint * point2 = path.lineTo(QPointF(20, 20)); KoPathPointIndex p2Index(0, 1); KoPathPoint * point3 = path.moveTo(QPointF(30, 30)); KoPathPointIndex p3Index(1, 0); KoPathPoint * point4 = path.lineTo(QPointF(40, 40)); KoPathPointIndex p4Index(1, 1); KoPathPoint * point5 = 0; KoPathPointIndex p5Index(-1, -1); QCOMPARE(p1Index, path.pathPointIndex(point1)); QCOMPARE(p2Index, path.pathPointIndex(point2)); QCOMPARE(p3Index, path.pathPointIndex(point3)); QCOMPARE(p4Index, path.pathPointIndex(point4)); QCOMPARE(p5Index, path.pathPointIndex(point5)); QVERIFY(p1Index == path.pathPointIndex(point1)); QVERIFY(p2Index == path.pathPointIndex(point2)); QVERIFY(p3Index == path.pathPointIndex(point3)); QVERIFY(p4Index == path.pathPointIndex(point4)); QVERIFY(p5Index == path.pathPointIndex(point5)); } void TestPathShape::pointByIndex() { KoPathShape path; KoPathPoint * point1 = path.moveTo(QPointF(10, 10)); KoPathPoint * point2 = path.lineTo(QPointF(20, 20)); KoPathPoint * point3 = path.moveTo(QPointF(30, 30)); KoPathPoint * point4 = path.lineTo(QPointF(40, 40)); KoPathPoint * point5 = 0; QVERIFY(point1 == path.pointByIndex(path.pathPointIndex(point1))); QVERIFY(point2 == path.pointByIndex(path.pathPointIndex(point2))); QVERIFY(point3 == path.pointByIndex(path.pathPointIndex(point3))); QVERIFY(point4 == path.pointByIndex(path.pathPointIndex(point4))); QVERIFY(point5 == path.pointByIndex(path.pathPointIndex(point5))); } void TestPathShape::segmentByIndex() { KoPathShape path; KoPathPoint * point1 = path.moveTo(QPointF(20, 20)); KoPathPoint * point2 = path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.close(); path.moveTo(QPointF(20, 30)); KoPathPoint * point3 = path.lineTo(QPointF(20, 30)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); KoPathPoint * point4 = path.moveTo(QPointF(50, 50)); path.lineTo(QPointF(60, 50)); path.lineTo(QPointF(60, 60)); KoPathPoint * point5 = path.curveTo(QPointF(60, 65), QPointF(50, 65), QPointF(50, 60)); path.close(); QVERIFY(KoPathSegment(point1, point2) == path.segmentByIndex(path.pathPointIndex(point1))); // test last point in a open path QVERIFY(KoPathSegment(0, 0) == path.segmentByIndex(path.pathPointIndex(point3))); // test last point in a closed path QVERIFY(KoPathSegment(point5, point4) == path.segmentByIndex(path.pathPointIndex(point5))); // test out of bounds QVERIFY(KoPathSegment(0, 0) == path.segmentByIndex(KoPathPointIndex(3, 4))); QVERIFY(KoPathSegment(0, 0) == path.segmentByIndex(KoPathPointIndex(4, 0))); } void TestPathShape::pointCount() { KoPathShape path; path.moveTo(QPointF(20, 20)); path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.close(); QVERIFY(path.pointCount() == 3); path.moveTo(QPointF(20, 30)); path.lineTo(QPointF(20, 30)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); QVERIFY(path.pointCount() == 9); path.moveTo(QPointF(50, 50)); path.lineTo(QPointF(60, 50)); path.lineTo(QPointF(60, 60)); path.curveTo(QPointF(60, 65), QPointF(50, 65), QPointF(50, 60)); path.close(); QVERIFY(path.pointCount() == 13); } void TestPathShape::subpathPointCount() { KoPathShape path; path.moveTo(QPointF(20, 20)); path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.close(); path.moveTo(QPointF(20, 30)); path.lineTo(QPointF(20, 30)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.moveTo(QPointF(50, 50)); path.lineTo(QPointF(60, 50)); path.lineTo(QPointF(60, 60)); path.curveTo(QPointF(60, 65), QPointF(50, 65), QPointF(50, 60)); path.close(); QVERIFY(path.subpathPointCount(0) == 3); QVERIFY(path.subpathPointCount(1) == 2); QVERIFY(path.subpathPointCount(2) == 4); QVERIFY(path.subpathPointCount(3) == 4); QVERIFY(path.subpathPointCount(4) == -1); } void TestPathShape::isClosedSubpath() { KoPathShape path; path.moveTo(QPointF(20, 20)); path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.close(); path.moveTo(QPointF(20, 30)); path.lineTo(QPointF(20, 30)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); path.moveTo(QPointF(50, 50)); path.lineTo(QPointF(60, 50)); path.lineTo(QPointF(60, 60)); path.curveTo(QPointF(60, 65), QPointF(50, 65), QPointF(50, 60)); path.close(); QVERIFY(path.isClosedSubpath(0) == true); QVERIFY(path.isClosedSubpath(1) == false); QVERIFY(path.isClosedSubpath(2) == true); QVERIFY(path.isClosedSubpath(3) == true); } void TestPathShape::insertPoint() { KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 20)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 40)); path.close(); // add before the first point of a open subpath KoPathPoint *point1 = new KoPathPoint(&path, QPointF(5, 5), KoPathPoint::Normal); KoPathPointIndex p1Index(0, 0); QVERIFY(path.insertPoint(point1, p1Index) == true); QVERIFY(point1->parent() == &path); KoPathPoint *point2 = new KoPathPoint(&path, QPointF(15, 15), KoPathPoint::Normal); KoPathPointIndex p2Index(0, 2); QVERIFY(path.insertPoint(point2, p2Index) == true); QVERIFY(point2->parent() == &path); // add after last point of a open subpath KoPathPoint *point3 = new KoPathPoint(&path, QPointF(25, 25), KoPathPoint::Normal); KoPathPointIndex p3Index(0, 4); QVERIFY(path.insertPoint(point3, p3Index) == true); QVERIFY(point3->parent() == &path); KoPathPoint *point4 = new KoPathPoint(&path, QPointF(40, 30), KoPathPoint::Normal); KoPathPointIndex p4Index(1, 1); QVERIFY(path.insertPoint(point4, p4Index) == true); QVERIFY(point4->parent() == &path); // add before the first point of a closed subpath KoPathPoint *point5 = new KoPathPoint(&path, QPointF(30, 35), KoPathPoint::Normal); KoPathPointIndex p5Index(1, 0); QVERIFY(path.insertPoint(point5, p5Index) == true); QVERIFY(point5->parent() == &path); // add after last point of a closed subpath KoPathPoint *point6 = new KoPathPoint(&path, QPointF(35, 40), KoPathPoint::Normal); KoPathPointIndex p6Index(1, 4); QVERIFY(path.insertPoint(point6, p6Index) == true); QVERIFY(point6->parent() == &path); // test out of bounds KoPathPoint *point7 = new KoPathPoint(&path, QPointF(0, 0), KoPathPoint::Normal); // subpath index out of bounds KoPathPointIndex p7Index(2, 0); QVERIFY(path.insertPoint(point7, p7Index) == false); // position in subpath out of bounds p7Index.second = 6; QVERIFY(path.insertPoint(point7, p7Index) == false); QPainterPath ppath(QPointF(5, 5)); ppath.lineTo(10, 10); ppath.lineTo(15, 15); ppath.lineTo(20, 20); ppath.lineTo(25, 25); ppath.moveTo(30, 35); ppath.lineTo(30, 30); ppath.lineTo(40, 30); ppath.lineTo(40, 40); ppath.lineTo(35, 40); ppath.closeSubpath(); QVERIFY(ppath == path.outline()); KoPathShape path2; path2.moveTo(QPointF(0, 0)); KoPathPoint * p = new KoPathPoint(0, QPointF(100, 100)); QVERIFY(path2.insertPoint(p, KoPathPointIndex(0, 1)) == true); QVERIFY(p->parent() == &path2); } void TestPathShape::removePoint() { KoPathShape path; KoPathPoint *point1 = path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 10)); KoPathPoint *point3 = path.lineTo(QPointF(20, 20)); path.lineTo(QPointF(15, 25)); KoPathPoint *point5 = path.lineTo(QPointF(10, 20)); KoPathPoint *point6 = path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); KoPathPoint *point8 = path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); KoPathPoint *point10 = path.lineTo(QPointF(30, 35)); path.close(); // remove from beginning of a open subpath QVERIFY(path.removePoint(path.pathPointIndex(point1)) == point1); // remove from middle of a open subpath QVERIFY(path.removePoint(path.pathPointIndex(point3)) == point3); // remove from end of a open subpath QVERIFY(path.removePoint(path.pathPointIndex(point5)) == point5); // remove from beginning of a closed subpath QVERIFY(path.removePoint(path.pathPointIndex(point6)) == point6); // remove from middle of a closed subpath QVERIFY(path.removePoint(path.pathPointIndex(point8)) == point8); // remove from end of a closed subpath QVERIFY(path.removePoint(path.pathPointIndex(point10)) == point10); QPainterPath ppath(QPointF(20, 10)); ppath.lineTo(15, 25); ppath.moveTo(40, 30); ppath.quadTo(30, 45, 30, 40); ppath.closeSubpath(); QVERIFY(ppath == path.outline()); } void TestPathShape::splitAfter() { KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 10)); path.lineTo(QPointF(20, 20)); path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); QVERIFY(path.breakAfter(KoPathPointIndex(0, 1)) == true); // try to break at the last point in the subpath QVERIFY(path.breakAfter(KoPathPointIndex(1, 2)) == false); // try to break a closed subpath QVERIFY(path.breakAfter(KoPathPointIndex(2, 1)) == false); QPainterPath ppath(QPointF(10, 10)); ppath.lineTo(20, 10); ppath.moveTo(20, 20); ppath.lineTo(15, 25); ppath.lineTo(10, 20); ppath.moveTo(30, 30); ppath.lineTo(40, 30); ppath.lineTo(40, 40); ppath.cubicTo(40, 45, 30, 45, 30, 40); ppath.closeSubpath(); QVERIFY(ppath == path.outline()); } void TestPathShape::join() { KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 10)); path.moveTo(QPointF(20, 20)); path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); path.moveTo(QPointF(50, 50)); path.lineTo(QPointF(60, 60)); QVERIFY(path.join(0) == true); // try to join to a closed subpath QVERIFY(path.join(0) == false); // try to join from a closed subpath QVERIFY(path.join(1) == false); // try to join last subpath QVERIFY(path.join(2) == false); QPainterPath ppath(QPointF(10, 10)); ppath.lineTo(20, 10); ppath.lineTo(20, 20); ppath.lineTo(15, 25); ppath.lineTo(10, 20); ppath.moveTo(30, 30); ppath.lineTo(40, 30); ppath.lineTo(40, 40); ppath.cubicTo(40, 45, 30, 45, 30, 40); ppath.closeSubpath(); ppath.moveTo(50, 50); ppath.lineTo(60, 60); QVERIFY(ppath == path.outline()); } void TestPathShape::moveSubpath() { KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 10)); path.moveTo(QPointF(20, 20)); path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); QVERIFY(path.moveSubpath(0, 1) == true); QVERIFY(path.moveSubpath(1, 0) == true); QVERIFY(path.moveSubpath(2, 1) == true); QVERIFY(path.moveSubpath(0, 2) == true); QVERIFY(path.moveSubpath(3, 1) == false); QVERIFY(path.moveSubpath(1, 3) == false); QPainterPath ppath(QPointF(30, 30)); ppath.lineTo(40, 30); ppath.lineTo(40, 40); ppath.cubicTo(40, 45, 30, 45, 30, 40); ppath.closeSubpath(); ppath.moveTo(20, 20); ppath.lineTo(15, 25); ppath.lineTo(10, 20); ppath.moveTo(10, 10); ppath.lineTo(20, 10); QVERIFY(ppath == path.outline()); } void TestPathShape::openSubpath() { KoPathShape path; path.moveTo(QPointF(20, 20)); KoPathPoint *point1 = path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.close(); KoPathPoint *point2 = path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); path.moveTo(QPointF(50, 50)); path.lineTo(QPointF(60, 50)); path.lineTo(QPointF(60, 60)); KoPathPoint *point3 = path.curveTo(QPointF(60, 65), QPointF(50, 65), QPointF(50, 60)); path.close(); KoPathPoint *point4 = path.moveTo(QPointF(100, 100)); point4->setControlPoint2(QPointF(120, 120)); path.lineTo(QPointF(140, 140)); KoPathPoint *point5 = path.lineTo(QPointF(140, 100)); path.close(); // open at middle point in subpath QVERIFY(path.openSubpath(path.pathPointIndex(point1)) == KoPathPointIndex(0, 2)); QVERIFY(path.pointByIndex(KoPathPointIndex(0,0))->properties() & KoPathPoint::StartSubpath); QVERIFY(path.pointByIndex(KoPathPointIndex(0,2))->properties() & KoPathPoint::StopSubpath); // open at first point in subpath QVERIFY(path.openSubpath(path.pathPointIndex(point2)) == KoPathPointIndex(1, 0)); QVERIFY(path.pointByIndex(KoPathPointIndex(1,0))->properties() & KoPathPoint::StartSubpath); QVERIFY(path.pointByIndex(KoPathPointIndex(1,3))->properties() & KoPathPoint::StopSubpath); // open at last point in subpath QVERIFY(path.openSubpath(path.pathPointIndex(point3)) == KoPathPointIndex(2, 1)); QVERIFY(path.pointByIndex(KoPathPointIndex(2,0))->properties() & KoPathPoint::StartSubpath); QVERIFY(path.pointByIndex(KoPathPointIndex(2,3))->properties() & KoPathPoint::StopSubpath); // try to open open subpath QVERIFY(path.openSubpath(path.pathPointIndex(point3)) == KoPathPointIndex(-1, -1)); // open if the first path is a curve QVERIFY(path.openSubpath(path.pathPointIndex(point5)) == KoPathPointIndex(3, 1)); QVERIFY(path.pointByIndex(KoPathPointIndex(3,0))->properties() & KoPathPoint::StartSubpath); QVERIFY(path.pointByIndex(KoPathPointIndex(3,2))->properties() & KoPathPoint::StopSubpath); // try to open none existing subpath QVERIFY(path.openSubpath(KoPathPointIndex(4, 1)) == KoPathPointIndex(-1, -1)); QPainterPath ppath(QPointF(15, 25)); ppath.lineTo(10, 20); ppath.lineTo(20, 20); ppath.moveTo(30, 30); ppath.lineTo(40, 30); ppath.lineTo(40, 40); ppath.cubicTo(40, 45, 30, 45, 30, 40); ppath.moveTo(50, 60); ppath.lineTo(50, 50); ppath.lineTo(60, 50); ppath.lineTo(60, 60); ppath.moveTo(140, 100); ppath.lineTo(100, 100); ppath.quadTo(120, 120, 140, 140); QVERIFY(ppath == path.outline()); } void TestPathShape::closeSubpath() { KoPathShape path; path.moveTo(QPointF(20, 20)); KoPathPoint *point1 = path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); KoPathPoint *point2 = path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.moveTo(QPointF(50, 50)); path.lineTo(QPointF(60, 50)); path.lineTo(QPointF(60, 60)); KoPathPoint *point3 = path.curveTo(QPointF(60, 65), QPointF(50, 65), QPointF(50, 60)); // open at middle point in subpath QVERIFY(path.closeSubpath(path.pathPointIndex(point1)) == KoPathPointIndex(0, 2)); // open at first point in subpath QVERIFY(path.closeSubpath(path.pathPointIndex(point2)) == KoPathPointIndex(1, 0)); // open at last point in subpath QVERIFY(path.closeSubpath(path.pathPointIndex(point3)) == KoPathPointIndex(2, 1)); // try to close a closed subpath QVERIFY(path.closeSubpath(path.pathPointIndex(point3)) == KoPathPointIndex(-1, -1)); // try to close a none existing subpath QVERIFY(path.closeSubpath(KoPathPointIndex(3, 1)) == KoPathPointIndex(-1, -1)); // try to close at a none existing position in a subpath QVERIFY(path.closeSubpath(KoPathPointIndex(2, 4)) == KoPathPointIndex(-1, -1)); QPainterPath ppath(QPointF(15, 25)); ppath.lineTo(10, 20); ppath.lineTo(20, 20); ppath.closeSubpath(); ppath.moveTo(30, 30); ppath.lineTo(40, 30); ppath.lineTo(40, 40); ppath.cubicTo(40, 45, 30, 45, 30, 40); ppath.closeSubpath(); ppath.moveTo(50, 60); ppath.lineTo(50, 50); ppath.lineTo(60, 50); ppath.lineTo(60, 60); ppath.cubicTo(60, 65, 50, 65, 50, 60); ppath.closeSubpath(); QVERIFY(ppath == path.outline()); } void TestPathShape::openCloseSubpath() { KoPathShape path; path.moveTo(QPointF(20, 20)); KoPathPoint *point1 = path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.close(); KoPathPoint *point2 = path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); KoPathPointIndex p1Index = path.pathPointIndex(point1); KoPathPointIndex p1OldIndex = path.openSubpath(p1Index); QVERIFY(path.closeSubpath(p1OldIndex) == p1Index); KoPathPointIndex p2Index = path.pathPointIndex(point2); KoPathPointIndex p2OldIndex = path.closeSubpath(p2Index); QVERIFY(path.openSubpath(p2OldIndex) == p2Index); QPainterPath ppath(QPointF(20, 20)); ppath.lineTo(15, 25); ppath.lineTo(10, 20); ppath.closeSubpath(); ppath.moveTo(30, 30); ppath.lineTo(40, 30); ppath.lineTo(40, 40); ppath.cubicTo(40, 45, 30, 45, 30, 40); QVERIFY(ppath == path.outline()); } void TestPathShape::reverseSubpath() { KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 10)); path.moveTo(QPointF(20, 20)); path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); QVERIFY(path.reverseSubpath(0) == true); QVERIFY(path.reverseSubpath(1) == true); QVERIFY(path.reverseSubpath(1) == true); QVERIFY(path.reverseSubpath(2) == true); QVERIFY(path.reverseSubpath(3) == false); QPainterPath ppath(QPointF(20, 10)); ppath.lineTo(10, 10); ppath.moveTo(20, 20); ppath.lineTo(15, 25); ppath.lineTo(10, 20); ppath.moveTo(30, 40); ppath.cubicTo(30, 45, 40, 45, 40, 40); ppath.lineTo(40, 30); ppath.lineTo(30, 30); ppath.closeSubpath(); QVERIFY(ppath == path.outline()); QVERIFY(path.reverseSubpath(2) == true); QVERIFY(path.reverseSubpath(2) == true); QVERIFY(ppath == path.outline()); } void TestPathShape::removeSubpath() { #if 0 // enable again when point groups work KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 10)); path.lineTo(QPointF(20, 20)); path.close(); path.lineTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); QVERIFY(path.removeSubpath(0) != 0); QVERIFY(path.removeSubpath(1) != 0); QVERIFY(path.removeSubpath(1) == 0); QPainterPath ppath(QPointF(10, 20)); ppath.lineTo(15, 25); ppath.lineTo(10, 20); path.debugPath(); QVERIFY(ppath == path.outline()); #endif KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 10)); path.lineTo(QPointF(20, 20)); path.close(); path.moveTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); QVERIFY(path.removeSubpath(0) != 0); QVERIFY(path.removeSubpath(1) != 0); QVERIFY(path.removeSubpath(1) == 0); QPainterPath ppath(QPointF(15, 25)); ppath.lineTo(10, 20); QVERIFY(ppath == path.outline()); } void TestPathShape::addSubpath() { KoPathShape path; path.moveTo(QPointF(10, 10)); path.lineTo(QPointF(20, 10)); path.lineTo(QPointF(20, 20)); path.close(); path.moveTo(QPointF(15, 25)); path.lineTo(QPointF(10, 20)); path.moveTo(QPointF(30, 30)); path.lineTo(QPointF(40, 30)); path.lineTo(QPointF(40, 40)); path.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path.close(); KoSubpath * sp1 = path.removeSubpath(0); QVERIFY(path.addSubpath(sp1, 0) == true); KoSubpath * sp2 = path.removeSubpath(1); QVERIFY(path.addSubpath(sp2, 1) == true); QVERIFY(path.addSubpath(sp2, 4) == false); QPainterPath ppath(QPointF(10, 10)); ppath.lineTo(20, 10); ppath.lineTo(20, 20); ppath.closeSubpath(); ppath.moveTo(15, 25); ppath.lineTo(10, 20); ppath.moveTo(30, 30); ppath.lineTo(40, 30); ppath.lineTo(40, 40); ppath.cubicTo(40, 45, 30, 45, 30, 40); ppath.closeSubpath(); QVERIFY(ppath == path.outline()); } void TestPathShape::koPathPointDataLess() { QList v; v.push_back(KoPathPointData((KoPathShape*)1, KoPathPointIndex(1, 1))); v.push_back(KoPathPointData((KoPathShape*)1, KoPathPointIndex(1, 2))); v.push_back(KoPathPointData((KoPathShape*)1, KoPathPointIndex(1, 3))); v.push_back(KoPathPointData((KoPathShape*)1, KoPathPointIndex(1, 6))); v.push_back(KoPathPointData((KoPathShape*)2, KoPathPointIndex(2, 1))); v.push_back(KoPathPointData((KoPathShape*)2, KoPathPointIndex(2, 3))); v.push_back(KoPathPointData((KoPathShape*)2, KoPathPointIndex(3, 3))); v.push_back(KoPathPointData((KoPathShape*)3, KoPathPointIndex(1, 1))); v.push_back(KoPathPointData((KoPathShape*)3, KoPathPointIndex(1, 2))); QList l; l.push_back(v[8]); l.push_back(v[0]); l.push_back(v[1]); l.push_back(v[7]); l.push_back(v[6]); l.push_back(v[2]); l.push_back(v[5]); l.push_back(v[3]); l.push_back(v[4]); - qSort(l.begin(), l.end()); + std::sort(l.begin(), l.end()); for (int i = 0; i < v.size(); ++i) { KoPathPointData ld = l.at(i); KoPathPointData vd = v[i]; QVERIFY(ld.pathShape == vd.pathShape); QVERIFY(ld.pointIndex.first == vd.pointIndex.first); QVERIFY(ld.pointIndex.second == vd.pointIndex.second); } } void TestPathShape::closeMerge() { KoPathShape path; KoPathPoint *p1 = path.moveTo(QPointF(0, 0)); KoPathPoint *p2 = path.curveTo(QPointF(50, 0), QPointF(100, 50), QPointF(100, 100)); KoPathPoint *p3 = path.curveTo(QPointF(50, 100), QPointF(0, 50), QPointF(0, 0)); QVERIFY(p1->properties() & KoPathPoint::StartSubpath); QVERIFY((p1->properties() & KoPathPoint::CloseSubpath) == 0); QVERIFY(p1->activeControlPoint1() == false); QVERIFY(p1->activeControlPoint2()); QVERIFY(p2->activeControlPoint1()); QVERIFY(p2->activeControlPoint2()); QVERIFY((p3->properties() & KoPathPoint::CloseSubpath) == 0); QVERIFY(p3->activeControlPoint1()); QCOMPARE(path.pointCount(), 3); path.closeMerge(); QCOMPARE(path.pointCount(), 2); QVERIFY(p1->properties() & KoPathPoint::CloseSubpath); QVERIFY(p1->activeControlPoint1()); QVERIFY(p2->properties() & KoPathPoint::CloseSubpath); QVERIFY(p2->activeControlPoint2()); QPainterPath ppath(QPointF(0, 0)); ppath.cubicTo(50, 0, 100, 50, 100, 100); ppath.cubicTo(50, 100, 0, 50, 0, 0); QVERIFY(path.outline() == ppath); } QTEST_MAIN(TestPathShape) diff --git a/libs/flake/tests/TestPathTool.cpp b/libs/flake/tests/TestPathTool.cpp index de66de6d5d..a190cbe0f1 100644 --- a/libs/flake/tests/TestPathTool.cpp +++ b/libs/flake/tests/TestPathTool.cpp @@ -1,91 +1,91 @@ /* This file is part of the KDE project Copyright (C) 2007 Thorsten Zachmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TestPathTool.h" #include #include "../KoPathShape.h" #include "../tools/KoPathTool.h" #include "../tools/KoPathToolSelection.h" #include "../KoPathPointData.h" #include #include void TestPathTool::koPathPointSelection_selectedSegmentsData() { KoPathShape path1; KoPathPoint *point11 = path1.moveTo(QPointF(10, 10)); KoPathPoint *point12 = path1.lineTo(QPointF(20, 10)); KoPathPoint *point13 = path1.lineTo(QPointF(20, 20)); KoPathPoint *point14 = path1.lineTo(QPointF(15, 25)); path1.lineTo(QPointF(10, 20)); KoPathPoint *point16 = path1.moveTo(QPointF(30, 30)); path1.lineTo(QPointF(40, 30)); KoPathPoint *point18 = path1.lineTo(QPointF(40, 40)); KoPathPoint *point19 = path1.curveTo(QPointF(40, 45), QPointF(30, 45), QPointF(30, 40)); path1.close(); KoPathShape path2; KoPathPoint *point21 = path2.moveTo(QPointF(100, 100)); KoPathPoint *point22 = path2.lineTo(QPointF(110, 100)); KoPathPoint *point23 = path2.lineTo(QPointF(110, 110)); KoPathShape path3; KoPathPoint *point31 = path3.moveTo(QPointF(200, 220)); KoPathPoint *point32 = path3.lineTo(QPointF(210, 220)); KoPathPoint *point33 = path3.lineTo(QPointF(220, 220)); path3.close(); MockCanvas canvas; KoPathTool tool(&canvas); QVERIFY(1 == 1); KoPathToolSelection pps(&tool); pps.add(point11, false); pps.add(point12, false); pps.add(point13, false); pps.add(point14, false); pps.add(point16, false); pps.add(point18, false); pps.add(point19, false); pps.add(point21, false); pps.add(point22, false); pps.add(point23, false); pps.add(point31, false); pps.add(point32, false); pps.add(point33, false); QList pd2; pd2.append(KoPathPointData(&path1, path1.pathPointIndex(point11))); pd2.append(KoPathPointData(&path1, path1.pathPointIndex(point12))); pd2.append(KoPathPointData(&path1, path1.pathPointIndex(point13))); pd2.append(KoPathPointData(&path1, path1.pathPointIndex(point18))); pd2.append(KoPathPointData(&path1, path1.pathPointIndex(point19))); pd2.append(KoPathPointData(&path2, path2.pathPointIndex(point21))); pd2.append(KoPathPointData(&path2, path2.pathPointIndex(point22))); pd2.append(KoPathPointData(&path3, path3.pathPointIndex(point31))); pd2.append(KoPathPointData(&path3, path3.pathPointIndex(point32))); pd2.append(KoPathPointData(&path3, path3.pathPointIndex(point33))); - qSort(pd2); + std::sort(pd2.begin(), pd2.end()); QList pd1(pps.selectedSegmentsData()); QVERIFY(pd1 == pd2); } QTEST_MAIN(TestPathTool) diff --git a/libs/flake/tests/TestShapeGroupCommand.cpp b/libs/flake/tests/TestShapeGroupCommand.cpp index 9411dea3af..6018d6055b 100644 --- a/libs/flake/tests/TestShapeGroupCommand.cpp +++ b/libs/flake/tests/TestShapeGroupCommand.cpp @@ -1,281 +1,281 @@ /* This file is part of the KDE project * Copyright (C) 2007 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TestShapeGroupCommand.h" #include "MockShapes.h" #include #include #include #include #include #include "kis_pointer_utils.h" #include TestShapeGroupCommand::TestShapeGroupCommand() : toplevelGroup(0), sublevelGroup(0), strokeGroup(0) , cmd1(0), cmd2(0), strokeCmd(0) , toplevelShape1(0), toplevelShape2(0) , sublevelShape1(0), sublevelShape2(0) , extraShape1(0), extraShape2(0) , strokeShape1(0), strokeShape2(0) { } TestShapeGroupCommand::~TestShapeGroupCommand() { } void TestShapeGroupCommand::init() { toplevelShape1 = new MockShape(); toplevelShape1->setPosition(QPointF(50, 50)); toplevelShape1->setSize(QSize(50, 50)); toplevelShape2 = new MockShape(); toplevelShape2->setPosition(QPointF(50, 150)); toplevelShape2->setSize(QSize(50, 50)); sublevelShape1 = new MockShape(); sublevelShape1->setPosition(QPointF(150, 150)); sublevelShape1->setSize(QSize(50, 50)); sublevelShape2 = new MockShape(); sublevelShape2->setPosition(QPointF(250, 150)); sublevelShape2->setSize(QSize(50, 50)); extraShape1 = new MockShape(); extraShape1->setPosition(QPointF(150, 50)); extraShape1->setSize(QSize(50, 50)); extraShape2 = new MockShape(); extraShape2->setPosition(QPointF(250, 50)); extraShape2->setSize(QSize(50, 50)); toplevelGroup = new KoShapeGroup(); sublevelGroup = new KoShapeGroup(); strokeShape1 = new MockShape(); strokeShape1->setSize( QSizeF(50,50) ); strokeShape1->setPosition( QPointF(0,0) ); strokeShape2 = new MockShape(); strokeShape2->setSize( QSizeF(50,50) ); strokeShape2->setPosition( QPointF(25,25) ); strokeGroup = new KoShapeGroup(); strokeGroup->setStroke( toQShared(new KoShapeStroke( 2.0f )) ); strokeGroup->setShadow( new KoShapeShadow() ); } void TestShapeGroupCommand::cleanup() { if (toplevelShape1->parent() == 0) { delete toplevelShape1; } toplevelShape1 = 0; if (toplevelShape2->parent() == 0) { delete toplevelShape2; } toplevelShape2 = 0; if (sublevelShape1->parent() == 0) { delete sublevelShape1; } sublevelShape1 = 0; if (sublevelShape2->parent() == 0) { delete sublevelShape2; } sublevelShape2 = 0; if (extraShape1->parent() == 0) { delete extraShape1; } extraShape1 = 0; if (extraShape2->parent() == 0) { delete extraShape2; } extraShape2 = 0; if (strokeShape1->parent() == 0) { delete strokeShape1; } strokeShape1 = 0; if (strokeShape2->parent() == 0) { delete strokeShape2; } strokeShape2 = 0; if (sublevelGroup->parent() == 0) { delete sublevelGroup; } sublevelGroup = 0; if (strokeGroup->parent() == 0) { delete strokeGroup; strokeGroup = 0; } delete toplevelGroup; toplevelGroup = 0; delete cmd1; cmd1 = 0; delete cmd2; cmd2 = 0; delete strokeCmd; strokeCmd = 0; } void TestShapeGroupCommand::testToplevelGroup() { QList toplevelShapes; toplevelShapes << toplevelShape1 << toplevelShape2; cmd1 = KoShapeGroupCommand::createCommand(toplevelGroup, toplevelShapes); cmd1->redo(); QCOMPARE(toplevelShape1->parent(), toplevelGroup); QCOMPARE(toplevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(50, 50)); QCOMPARE(toplevelShape1->position(), QPointF(0, 0)); QCOMPARE(toplevelShape2->parent(), toplevelGroup); QCOMPARE(toplevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(50, 150)); QCOMPARE(toplevelShape2->position(), QPointF(0, 100)); QCOMPARE(toplevelGroup->position(), QPointF(50, 50)); cmd1->undo(); QVERIFY(toplevelShape1->parent() == 0); QCOMPARE(toplevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(50, 50)); QCOMPARE(toplevelShape1->position(), QPointF(50, 50)); QVERIFY(toplevelShape2->parent() == 0); QCOMPARE(toplevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(50, 150)); QCOMPARE(toplevelShape2->position(), QPointF(50, 150)); } void TestShapeGroupCommand::testSublevelGroup() { QList toplevelShapes; toplevelShapes << toplevelShape1 << toplevelShape2; cmd1 = KoShapeGroupCommand::createCommand(toplevelGroup, toplevelShapes); QList sublevelShapes; sublevelShapes << sublevelShape1 << sublevelShape2; sublevelShape1->setZIndex(1); sublevelShape2->setZIndex(2); sublevelShape2->setParent(strokeGroup); strokeGroup->setZIndex(-1); KoShapeGroupCommand::createCommand(sublevelGroup, sublevelShapes, cmd1); KoShapeGroupCommand::createCommand(toplevelGroup, QList() << sublevelGroup, cmd1); cmd1->redo(); QCOMPARE(toplevelShape1->parent(), toplevelGroup); QCOMPARE(toplevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(50, 50)); QCOMPARE(toplevelShape1->position(), QPointF(0, 0)); QCOMPARE(toplevelShape2->parent(), toplevelGroup); QCOMPARE(toplevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(50, 150)); QCOMPARE(toplevelShape2->position(), QPointF(0, 100)); QCOMPARE(toplevelGroup->position(), QPointF(50, 50)); QCOMPARE(sublevelShape1->parent(), sublevelGroup); QCOMPARE(sublevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(150, 150)); QCOMPARE(sublevelShape1->position(), QPointF(0, 0)); QCOMPARE(sublevelShape2->parent(), sublevelGroup); QCOMPARE(sublevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(250, 150)); QCOMPARE(sublevelShape2->position(), QPointF(100, 0)); QCOMPARE(sublevelGroup->absolutePosition(KoFlake::TopLeft), QPointF(150, 150)); QCOMPARE(sublevelGroup->position(), QPointF(100, 100)); // check that the shapes are added in the correct order QList childOrder(sublevelGroup->shapes()); - qSort(childOrder.begin(), childOrder.end(), KoShape::compareShapeZIndex); + std::sort(childOrder.begin(), childOrder.end(), KoShape::compareShapeZIndex); QList expectedOrder; expectedOrder << sublevelShape2 << sublevelShape1; QCOMPARE(childOrder, expectedOrder); // check that the group has the zIndex/parent of its added top shape QCOMPARE(toplevelGroup->parent(), static_cast(0)); QCOMPARE(toplevelGroup->zIndex(), 1); } void TestShapeGroupCommand::testAddToToplevelGroup() { QList toplevelShapes; toplevelShapes << toplevelShape1 << toplevelShape2; cmd1 = KoShapeGroupCommand::createCommand(toplevelGroup, toplevelShapes); cmd1->redo(); cmd2 = KoShapeGroupCommand::createCommand(toplevelGroup, QList() << extraShape1); cmd2->redo(); QVERIFY(extraShape1->parent() == toplevelGroup); QCOMPARE(extraShape1->absolutePosition(KoFlake::TopLeft), QPointF(150, 50)); QCOMPARE(extraShape1->position(), QPointF(100, 0)); QCOMPARE(toplevelGroup->position(), QPointF(50, 50)); cmd2->undo(); QVERIFY(extraShape1->parent() == 0); QCOMPARE(extraShape1->absolutePosition(KoFlake::TopLeft), QPointF(150, 50)); QCOMPARE(extraShape1->position(), QPointF(150, 50)); QCOMPARE(toplevelGroup->position(), QPointF(50, 50)); } void TestShapeGroupCommand::testAddToSublevelGroup() { QList toplevelShapes; toplevelShapes << toplevelShape1 << toplevelShape2; cmd1 = new KoShapeGroupCommand(toplevelGroup, toplevelShapes); QList sublevelShapes; sublevelShapes << sublevelShape1 << sublevelShape2; new KoShapeGroupCommand(sublevelGroup, sublevelShapes, cmd1); new KoShapeGroupCommand(toplevelGroup, QList() << sublevelGroup, cmd1); cmd1->redo(); cmd2 = new KoShapeGroupCommand(sublevelGroup, QList() << extraShape2); cmd2->redo(); QVERIFY(extraShape2->parent() == sublevelGroup); QCOMPARE(extraShape2->absolutePosition(KoFlake::TopLeft), QPointF(250, 50)); QCOMPARE(extraShape2->position(), QPointF(100, 0)); QCOMPARE(sublevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(150, 150)); QCOMPARE(sublevelShape1->position(), QPointF(0, 100)); QCOMPARE(sublevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(250, 150)); QCOMPARE(sublevelShape2->position(), QPointF(100, 100)); QCOMPARE(sublevelGroup->absolutePosition(KoFlake::TopLeft), QPointF(150, 50)); QCOMPARE(sublevelGroup->position(), QPointF(100, 0)); cmd2->undo(); QVERIFY(extraShape2->parent() == 0); QCOMPARE(extraShape2->absolutePosition(KoFlake::TopLeft), QPointF(250, 50)); QCOMPARE(extraShape2->position(), QPointF(250, 50)); QCOMPARE(sublevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(150, 150)); QCOMPARE(sublevelShape1->position(), QPointF(0, 0)); QCOMPARE(sublevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(250, 150)); QCOMPARE(sublevelShape2->position(), QPointF(100, 0)); QCOMPARE(sublevelGroup->absolutePosition(KoFlake::TopLeft), QPointF(150, 150)); QCOMPARE(sublevelGroup->position(), QPointF(100, 100)); } void TestShapeGroupCommand::testGroupStrokeShapes() { QList strokeShapes; strokeShapes << strokeShape2 << strokeShape1; strokeCmd = new KoShapeGroupCommand(strokeGroup, strokeShapes); strokeCmd->redo(); QCOMPARE(strokeShape1->size(), QSizeF(50, 50)); QCOMPARE(strokeShape2->size(), QSizeF(50, 50)); } QTEST_MAIN(TestShapeGroupCommand) diff --git a/libs/flake/tests/TestShapePainting.cpp b/libs/flake/tests/TestShapePainting.cpp index b74c4e0a67..295fe355bd 100644 --- a/libs/flake/tests/TestShapePainting.cpp +++ b/libs/flake/tests/TestShapePainting.cpp @@ -1,325 +1,325 @@ /* * This file is part of Calligra tests * * Copyright (C) 2006-2010 Thomas Zander * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "TestShapePainting.h" #include #include "KoShapeContainer.h" #include "KoShapeManager.h" #include "KoShapePaintingContext.h" #include "KoViewConverter.h" #include #include void TestShapePainting::testPaintShape() { MockShape *shape1 = new MockShape(); MockShape *shape2 = new MockShape(); MockContainer *container = new MockContainer(); container->addShape(shape1); container->addShape(shape2); QCOMPARE(shape1->parent(), container); QCOMPARE(shape2->parent(), container); container->setClipped(shape1, false); container->setClipped(shape2, false); QCOMPARE(container->isClipped(shape1), false); QCOMPARE(container->isClipped(shape2), false); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(container); QCOMPARE(manager.shapes().count(), 3); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); KoViewConverter vc; manager.paint(painter, vc, false); // with the shape not being clipped, the shapeManager will paint it for us. QCOMPARE(shape1->paintedCount, 1); QCOMPARE(shape2->paintedCount, 1); QCOMPARE(container->paintedCount, 1); // the container should thus not paint the shape shape1->paintedCount = 0; shape2->paintedCount = 0; container->paintedCount = 0; KoShapePaintingContext paintContext; container->paint(painter, vc, paintContext); QCOMPARE(shape1->paintedCount, 0); QCOMPARE(shape2->paintedCount, 0); QCOMPARE(container->paintedCount, 1); container->setClipped(shape1, false); container->setClipped(shape2, true); QCOMPARE(container->isClipped(shape1), false); QCOMPARE(container->isClipped(shape2), true); shape1->paintedCount = 0; shape2->paintedCount = 0; container->paintedCount = 0; manager.paint(painter, vc, false); // with this shape not being clipped, the shapeManager will paint the container and this shape QCOMPARE(shape1->paintedCount, 1); // with this shape being clipped, the container will paint it for us. QCOMPARE(shape2->paintedCount, 1); QCOMPARE(container->paintedCount, 1); delete container; } void TestShapePainting::testPaintHiddenShape() { MockShape *shape = new MockShape(); MockContainer *fourth = new MockContainer(); MockContainer *thirth = new MockContainer(); MockContainer *second = new MockContainer(); MockContainer *top = new MockContainer(); top->addShape(second); second->addShape(thirth); thirth->addShape(fourth); fourth->addShape(shape); second->setVisible(false); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(top); QCOMPARE(manager.shapes().count(), 5); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); KoViewConverter vc; manager.paint(painter, vc, false); QCOMPARE(top->paintedCount, 1); QCOMPARE(second->paintedCount, 0); QCOMPARE(thirth->paintedCount, 0); QCOMPARE(fourth->paintedCount, 0); QCOMPARE(shape->paintedCount, 0); delete top; } void TestShapePainting::testPaintOrder() { // the stacking order determines the painting order so things on top // get their paint called last. // Each shape has a zIndex and within the children a container has // it determines the stacking order. Its important to realize that // the zIndex is thus local to a container, if you have layer1 and layer2 // with both various child shapes the stacking order of the layer shapes // is most important, then within this the child shape index is used. class OrderedMockShape : public MockShape { public: OrderedMockShape(QList &list) : order(list) {} void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override { order.append(this); MockShape::paint(painter, converter, paintcontext); } QList ℴ }; QList order; MockContainer *top = new MockContainer(); top->setZIndex(2); OrderedMockShape *shape1 = new OrderedMockShape(order); shape1->setZIndex(5); OrderedMockShape *shape2 = new OrderedMockShape(order); shape2->setZIndex(0); top->addShape(shape1); top->addShape(shape2); MockContainer *bottom = new MockContainer(); bottom->setZIndex(1); OrderedMockShape *shape3 = new OrderedMockShape(order); shape3->setZIndex(-1); OrderedMockShape *shape4 = new OrderedMockShape(order); shape4->setZIndex(9); bottom->addShape(shape3); bottom->addShape(shape4); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(top); manager.addShape(bottom); QCOMPARE(manager.shapes().count(), 6); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); KoViewConverter vc; manager.paint(painter, vc, false); QCOMPARE(top->paintedCount, 1); QCOMPARE(bottom->paintedCount, 1); QCOMPARE(shape1->paintedCount, 1); QCOMPARE(shape2->paintedCount, 1); QCOMPARE(shape3->paintedCount, 1); QCOMPARE(shape4->paintedCount, 1); QCOMPARE(order.count(), 4); QVERIFY(order[0] == shape3); // lowest first QVERIFY(order[1] == shape4); QVERIFY(order[2] == shape2); QVERIFY(order[3] == shape1); // again, with clipping. order.clear(); painter.setClipRect(0, 0, 100, 100); manager.paint(painter, vc, false); QCOMPARE(top->paintedCount, 2); QCOMPARE(bottom->paintedCount, 2); QCOMPARE(shape1->paintedCount, 2); QCOMPARE(shape2->paintedCount, 2); QCOMPARE(shape3->paintedCount, 2); QCOMPARE(shape4->paintedCount, 2); QCOMPARE(order.count(), 4); QVERIFY(order[0] == shape3); // lowest first QVERIFY(order[1] == shape4); QVERIFY(order[2] == shape2); QVERIFY(order[3] == shape1); order.clear(); MockContainer *root = new MockContainer(); root->setZIndex(0); MockContainer *branch1 = new MockContainer(); branch1->setZIndex(1); OrderedMockShape *child1_1 = new OrderedMockShape(order); child1_1->setZIndex(1); OrderedMockShape *child1_2 = new OrderedMockShape(order); child1_2->setZIndex(2); branch1->addShape(child1_1); branch1->addShape(child1_2); MockContainer *branch2 = new MockContainer(); branch2->setZIndex(2); OrderedMockShape *child2_1 = new OrderedMockShape(order); child2_1->setZIndex(1); OrderedMockShape *child2_2 = new OrderedMockShape(order); child2_2->setZIndex(2); branch2->addShape(child2_1); branch2->addShape(child2_2); root->addShape(branch1); root->addShape(branch2); QList sortedShapes; sortedShapes.append(root); sortedShapes.append(branch1); sortedShapes.append(branch2); sortedShapes.append(branch1->shapes()); sortedShapes.append(branch2->shapes()); - qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); + std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); QCOMPARE(sortedShapes.count(), 7); QVERIFY(sortedShapes[0] == root); QVERIFY(sortedShapes[1] == branch1); QVERIFY(sortedShapes[2] == child1_1); QVERIFY(sortedShapes[3] == child1_2); QVERIFY(sortedShapes[4] == branch2); QVERIFY(sortedShapes[5] == child2_1); QVERIFY(sortedShapes[6] == child2_2); delete top; delete bottom; delete root; } #include #include #include #include #include "kis_debug.h" void TestShapePainting::testGroupUngroup() { MockShape *shape1 = new MockShape(); MockShape *shape2 = new MockShape(); shape1->setName("shape1"); shape2->setName("shape2"); QList groupedShapes = {shape1, shape2}; MockShapeController controller; MockCanvas canvas(&controller); KoShapeManager *manager = canvas.shapeManager(); controller.addShape(shape1); controller.addShape(shape2); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); painter.setClipRect(image.rect()); KoViewConverter vc; KoShapeGroup *group = 0; for (int i = 0; i < 3; i++) { { group = new KoShapeGroup(); group->setName("group"); KUndo2Command groupingCommand; canvas.shapeController()->addShapeDirect(group, &groupingCommand); new KoShapeGroupCommand(group, groupedShapes, false, true, true, &groupingCommand); groupingCommand.redo(); manager->paint(painter, vc, false); QCOMPARE(shape1->paintedCount, 2 * i + 1); QCOMPARE(shape2->paintedCount, 2 * i + 1); QCOMPARE(manager->shapes().size(), 3); } { KUndo2Command ungroupingCommand; new KoShapeUngroupCommand(group, group->shapes(), QList(), &ungroupingCommand); canvas.shapeController()->removeShape(group, &ungroupingCommand); ungroupingCommand.redo(); manager->paint(painter, vc, false); QCOMPARE(shape1->paintedCount, 2 * i + 2); QCOMPARE(shape2->paintedCount, 2 * i + 2); QCOMPARE(manager->shapes().size(), 2); group = 0; } } } QTEST_MAIN(TestShapePainting) diff --git a/libs/flake/tests/TestShapeReorderCommand.cpp b/libs/flake/tests/TestShapeReorderCommand.cpp index b8d173a657..831e45828c 100644 --- a/libs/flake/tests/TestShapeReorderCommand.cpp +++ b/libs/flake/tests/TestShapeReorderCommand.cpp @@ -1,608 +1,608 @@ /* This file is part of the KDE project * Copyright (C) 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. */ #include "TestShapeReorderCommand.h" #include #include #include #include TestShapeReorderCommand::TestShapeReorderCommand() { } TestShapeReorderCommand::~TestShapeReorderCommand() { } void TestShapeReorderCommand::testZIndexSorting() { MockShape shape1; MockShape shape2; MockShape shape3; MockShape shape4; MockShape shape5; shape1.setZIndex(-2); shape2.setZIndex(5); shape3.setZIndex(0); shape4.setZIndex(9999); shape5.setZIndex(-9999); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); shapes.append(&shape4); shapes.append(&shape5); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape1), 1); QCOMPARE(shapes.indexOf(&shape2), 3); QCOMPARE(shapes.indexOf(&shape3), 2); QCOMPARE(shapes.indexOf(&shape4), 4); QCOMPARE(shapes.indexOf(&shape5), 0); } void TestShapeReorderCommand::testRunThroughSorting() { MockShape shape1; MockShape shape2; MockShape shape3; MockShape shape4; MockShape shape5; shape1.setZIndex(-2); shape2.setZIndex(5); shape3.setZIndex(0); shape4.setZIndex(9999); shape5.setZIndex(-9999); shape2.setTextRunAroundSide(KoShape::RunThrough, KoShape::Background); shape3.setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); shapes.append(&shape4); shapes.append(&shape5); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape1), 2); QCOMPARE(shapes.indexOf(&shape2), 0); QCOMPARE(shapes.indexOf(&shape3), 4); QCOMPARE(shapes.indexOf(&shape4), 3); QCOMPARE(shapes.indexOf(&shape5), 1); } void TestShapeReorderCommand::testParentChildSorting() { MockShape *shape1 = new MockShape(); MockShape *shape2 = new MockShape(); MockShape *shape3 = new MockShape(); MockShape *shape4 = new MockShape(); MockShape *shape5 = new MockShape(); MockShape *shape6 = new MockShape(); MockShape *shape7 = new MockShape(); MockContainer *container1 = new MockContainer(); MockContainer *container2 = new MockContainer(); MockContainer *container3 = new MockContainer(); shape1->setZIndex(-2); shape2->setZIndex(5); shape3->setZIndex(0); shape4->setZIndex(9999); shape5->setZIndex(-9999); shape6->setZIndex(3); shape7->setZIndex(7); container1->setZIndex(-55); container2->setZIndex(57); shape2->setTextRunAroundSide(KoShape::RunThrough, KoShape::Background); shape3->setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground); container1->setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground); container1->addShape(shape1); //container1.addShape(&shape2); //we shouldn't parent combine fg and bg container2->addShape(shape4); container2->addShape(shape5); container1->addShape(container2); container1->addShape(container3); QList shapes; shapes.append(shape1); shapes.append(shape2); shapes.append(shape3); shapes.append(shape4); shapes.append(shape5); shapes.append(shape6); shapes.append(shape7); shapes.append(container1); shapes.append(container2); shapes.append(container3); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); /* This is the expected result s3 0 fg s4 9999 s5 -9999 c2 57 c3 0 s1 -2 c1 -55 fg s7 7 s6 3 s2 5 bg */ QCOMPARE(shapes.indexOf(shape1), 4); QCOMPARE(shapes.indexOf(shape2), 0); QCOMPARE(shapes.indexOf(shape3), 9); QCOMPARE(shapes.indexOf(shape4), 8); QCOMPARE(shapes.indexOf(shape5), 7); QCOMPARE(shapes.indexOf(shape6), 1); QCOMPARE(shapes.indexOf(shape7), 2); QCOMPARE(shapes.indexOf(container1), 3); QCOMPARE(shapes.indexOf(container2), 6); QCOMPARE(shapes.indexOf(container3), 5); delete container1; delete shape2; delete shape3; delete shape6; delete shape7; } void TestShapeReorderCommand::testBringToFront() { MockShape shape1, shape2, shape3; shape1.setSize(QSizeF(100, 100)); shape1.setZIndex(1); shape2.setSize(QSizeF(100, 100)); shape2.setZIndex(2); shape3.setSize(QSizeF(100, 100)); shape3.setZIndex(3); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); MockCanvas canvas; KoShapeManager manager(&canvas, shapes); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape1), 0); QCOMPARE(shapes.indexOf(&shape2), 1); QCOMPARE(shapes.indexOf(&shape3), 2); QList selectedShapes; selectedShapes.append(&shape1); KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::BringToFront); cmd->redo(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape2), 0); QCOMPARE(shapes.indexOf(&shape3), 1); QCOMPARE(shapes.indexOf(&shape1), 2); delete cmd; } void TestShapeReorderCommand::testSendToBack() { MockShape shape1, shape2, shape3; shape1.setSize(QSizeF(100, 100)); shape1.setZIndex(1); shape2.setSize(QSizeF(100, 100)); shape2.setZIndex(2); shape3.setSize(QSizeF(100, 100)); shape3.setZIndex(3); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); MockCanvas canvas; KoShapeManager manager(&canvas, shapes); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape1), 0); QCOMPARE(shapes.indexOf(&shape2), 1); QCOMPARE(shapes.indexOf(&shape3), 2); QList selectedShapes; selectedShapes.append(&shape3); KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack); cmd->redo(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape3), 0); QCOMPARE(shapes.indexOf(&shape1), 1); QCOMPARE(shapes.indexOf(&shape2), 2); delete cmd; } void TestShapeReorderCommand::testMoveUp() { MockShape shape1, shape2, shape3; shape1.setSize(QSizeF(100, 100)); shape1.setZIndex(1); shape2.setSize(QSizeF(100, 100)); shape2.setZIndex(2); shape3.setSize(QSizeF(100, 100)); shape3.setZIndex(3); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); MockCanvas canvas; KoShapeManager manager(&canvas, shapes); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape1), 0); QCOMPARE(shapes.indexOf(&shape2), 1); QCOMPARE(shapes.indexOf(&shape3), 2); QList selectedShapes; selectedShapes.append(&shape1); KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::RaiseShape); cmd->redo(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape2), 0); QCOMPARE(shapes.indexOf(&shape1), 1); QCOMPARE(shapes.indexOf(&shape3), 2); delete cmd; } void TestShapeReorderCommand::testMoveDown() { MockShape shape1, shape2, shape3; shape1.setSize(QSizeF(100, 100)); shape1.setZIndex(1); shape2.setSize(QSizeF(100, 100)); shape2.setZIndex(2); shape3.setSize(QSizeF(100, 100)); shape3.setZIndex(3); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); MockCanvas canvas; KoShapeManager manager(&canvas, shapes); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape1), 0); QCOMPARE(shapes.indexOf(&shape2), 1); QCOMPARE(shapes.indexOf(&shape3), 2); QList selectedShapes; selectedShapes.append(&shape2); KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::LowerShape); cmd->redo(); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape2), 0); QCOMPARE(shapes.indexOf(&shape1), 1); QCOMPARE(shapes.indexOf(&shape3), 2); delete cmd; } void TestShapeReorderCommand::testMoveUpOverlapping() { MockShape shape1, shape2, shape3, shape4, shape5; shape1.setSize(QSizeF(100, 100)); shape1.setZIndex(1); shape2.setSize(QSizeF(100, 100)); shape2.setZIndex(2); shape3.setSize(QSizeF(300, 300)); shape3.setZIndex(3); shape4.setSize(QSizeF(100, 100)); shape4.setPosition(QPointF(200,200)); shape4.setZIndex(4); shape5.setSize(QSizeF(100, 100)); shape5.setPosition(QPointF(200,200)); shape5.setZIndex(5); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); shapes.append(&shape4); shapes.append(&shape5); MockCanvas canvas; KoShapeManager manager(&canvas, shapes); QVERIFY(shape1.zIndex() < shape2.zIndex()); QVERIFY(shape2.zIndex() < shape3.zIndex()); QVERIFY(shape3.zIndex() < shape4.zIndex()); QVERIFY(shape4.zIndex() < shape5.zIndex()); QList selectedShapes; selectedShapes.append(&shape1); KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::RaiseShape); cmd->redo(); delete cmd; QVERIFY(shape1.zIndex() > shape2.zIndex()); QVERIFY(shape2.zIndex() < shape3.zIndex()); QVERIFY(shape1.zIndex() < shape3.zIndex()); QVERIFY(shape3.zIndex() < shape4.zIndex()); QVERIFY(shape4.zIndex() < shape5.zIndex()); } void TestShapeReorderCommand::testMoveDownOverlapping() { #if 0 // disable a current alogrithm does not yet support this MockShape shape1, shape2, shape3, shape4, shape5; shape1.setSize(QSizeF(100, 100)); shape1.setZIndex(1); shape2.setSize(QSizeF(100, 100)); shape2.setZIndex(2); shape3.setSize(QSizeF(300, 300)); shape3.setZIndex(3); shape4.setSize(QSizeF(100, 100)); shape4.setPosition(QPointF(200,200)); shape4.setZIndex(4); shape5.setSize(QSizeF(100, 100)); shape5.setPosition(QPointF(200,200)); shape5.setZIndex(5); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); shapes.append(&shape4); shapes.append(&shape5); MockCanvas canvas; KoShapeManager manager(&canvas, shapes); QVERIFY(shape1.zIndex() < shape2.zIndex()); QVERIFY(shape2.zIndex() < shape3.zIndex()); QVERIFY(shape3.zIndex() < shape4.zIndex()); QVERIFY(shape4.zIndex() < shape5.zIndex()); QList selectedShapes; selectedShapes.append(&shape5); KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::LowerShape); cmd->redo(); delete cmd; QVERIFY(shape1.zIndex() < shape2.zIndex()); QVERIFY(shape2.zIndex() < shape3.zIndex()); QVERIFY(shape3.zIndex() < shape4.zIndex()); QVERIFY(shape4.zIndex() > shape5.zIndex()); QVERIFY(shape3.zIndex() > shape5.zIndex()); #endif } void TestShapeReorderCommand::testSendToBackChildren() { MockShape *shape1 = new MockShape(); MockShape *shape2 = new MockShape(); MockShape *shape3 = new MockShape(); shape1->setSize(QSizeF(100, 100)); shape1->setZIndex(1); shape2->setSize(QSizeF(100, 100)); shape2->setZIndex(2); shape3->setSize(QSizeF(100, 100)); shape3->setZIndex(3); MockContainer *container = new MockContainer(); container->addShape(shape1); container->addShape(shape2); container->addShape(shape3); QList shapes; shapes.append(shape1); shapes.append(shape2); shapes.append(shape3); shapes.append(container); MockCanvas canvas; KoShapeManager manager(&canvas, shapes); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(container), 0); // atm the parent is always lower than its children QCOMPARE(shapes.indexOf(shape1), 1); QCOMPARE(shapes.indexOf(shape2), 2); QCOMPARE(shapes.indexOf(shape3), 3); QList selectedShapes; selectedShapes.append(shape3); KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack); cmd->redo(); delete cmd; - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(container), 0); // atm the parent is always lower than its children QCOMPARE(shapes.indexOf(shape3), 1); QVERIFY(shape3->zIndex() < shape1->zIndex()); QCOMPARE(shapes.indexOf(shape1), 2); QVERIFY(shape1->zIndex() < shape2->zIndex()); QCOMPARE(shapes.indexOf(shape2), 3); selectedShapes.clear(); selectedShapes.append(shape2); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack); cmd->redo(); delete cmd; - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(container), 0); // atm the parent is always lower than its children QCOMPARE(shapes.indexOf(shape2), 1); QVERIFY(shape2->zIndex() < shape3->zIndex()); QCOMPARE(shapes.indexOf(shape3), 2); QVERIFY(shape3->zIndex() < shape1->zIndex()); QCOMPARE(shapes.indexOf(shape1), 3); selectedShapes.clear(); selectedShapes.append(shape1); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack); cmd->redo(); delete cmd; - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(container), 0); // atm the parent is always lower than its children QCOMPARE(shapes.indexOf(shape1), 1); QVERIFY(shape1->zIndex() < shape2->zIndex()); QCOMPARE(shapes.indexOf(shape2), 2); QVERIFY(shape2->zIndex() < shape3->zIndex()); QCOMPARE(shapes.indexOf(shape3), 3); delete container; } void TestShapeReorderCommand::testNoCommand() { MockShape shape1, shape2, shape3; shape1.setSize(QSizeF(100, 100)); shape1.setZIndex(1); shape2.setSize(QSizeF(100, 100)); shape2.setZIndex(2); shape3.setSize(QSizeF(100, 100)); shape3.setZIndex(3); QList shapes; shapes.append(&shape1); shapes.append(&shape2); shapes.append(&shape3); MockCanvas canvas; KoShapeManager manager(&canvas, shapes); - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); QCOMPARE(shapes.indexOf(&shape1), 0); QCOMPARE(shapes.indexOf(&shape2), 1); QCOMPARE(shapes.indexOf(&shape3), 2); QList selectedShapes; selectedShapes.append(&shape3); KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::BringToFront); QVERIFY(cmd == 0); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::RaiseShape); QVERIFY(cmd == 0); selectedShapes.append(&shape1); selectedShapes.append(&shape2); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::BringToFront); QVERIFY(cmd == 0); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::RaiseShape); QVERIFY(cmd == 0); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::LowerShape); QVERIFY(cmd == 0); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack); QVERIFY(cmd == 0); selectedShapes.clear(); selectedShapes.append(&shape1); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack); QVERIFY(cmd == 0); cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::LowerShape); QVERIFY(cmd == 0); } #include #include void testMergeInShapeImpl(const QVector indexesProfile, int newShapeIndex, const QVector expectedIndexes) { KIS_ASSERT(indexesProfile.size() == expectedIndexes.size()); QVector shapesStore(indexesProfile.size()); QList managedShapes; for (int i = 0; i < shapesStore.size(); i++) { shapesStore[i].setSize(QSizeF(100,100)); shapesStore[i].setZIndex(indexesProfile[i]); managedShapes << &shapesStore[i]; } QScopedPointer cmd( KoShapeReorderCommand::mergeInShape(managedShapes, &shapesStore[newShapeIndex])); cmd->redo(); for (int i = 0; i < shapesStore.size(); i++) { //qDebug() << ppVar(i) << ppVar(shapesStore[i].zIndex()); QCOMPARE(shapesStore[i].zIndex(), expectedIndexes[i]); } } void TestShapeReorderCommand::testMergeInShape() { QVector indexesProfile({1,1,2,2,2,3,3,4,5,6}); int newShapeIndex = 3; QVector expectedIndexes({1,1,2,3,2,4,4,5,6,7}); testMergeInShapeImpl(indexesProfile, newShapeIndex, expectedIndexes); } void TestShapeReorderCommand::testMergeInShapeDistant() { QVector indexesProfile({1,1,2,2,2,4,4,5,6,7}); int newShapeIndex = 3; QVector expectedIndexes({1,1,2,3,2,4,4,5,6,7}); testMergeInShapeImpl(indexesProfile, newShapeIndex, expectedIndexes); } QTEST_MAIN(TestShapeReorderCommand) diff --git a/libs/flake/tools/KoInteractionTool.cpp b/libs/flake/tools/KoInteractionTool.cpp index ee0971ed59..38bcbf5d7c 100644 --- a/libs/flake/tools/KoInteractionTool.cpp +++ b/libs/flake/tools/KoInteractionTool.cpp @@ -1,218 +1,218 @@ /* This file is part of the KDE project Copyright (C) 2006-2007, 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. */ #include "KoInteractionTool.h" #include "KoInteractionTool_p.h" #include "KoToolBase_p.h" #include "KoPointerEvent.h" #include "KoCanvasBase.h" #include "kis_global.h" #include "kis_assert.h" KoInteractionTool::KoInteractionTool(KoCanvasBase *canvas) : KoToolBase(*(new KoInteractionToolPrivate(this, canvas))) { } KoInteractionTool::~KoInteractionTool() { } void KoInteractionTool::paint(QPainter &painter, const KoViewConverter &converter) { Q_D(KoInteractionTool); if (d->currentStrategy) { d->currentStrategy->paint(painter, converter); } else { Q_FOREACH (KoInteractionStrategyFactorySP factory, d->interactionFactories) { // skip the rest of rendering if the factory asks for it if (factory->paintOnHover(painter, converter)) break; } } } void KoInteractionTool::mousePressEvent(KoPointerEvent *event) { Q_D(KoInteractionTool); if (d->currentStrategy) { // possible if the user presses an extra mouse button cancelCurrentStrategy(); return; } d->currentStrategy = createStrategyBase(event); if (d->currentStrategy == 0) event->ignore(); } void KoInteractionTool::mouseMoveEvent(KoPointerEvent *event) { Q_D(KoInteractionTool); d->lastPoint = event->point; if (d->currentStrategy) d->currentStrategy->handleMouseMove(d->lastPoint, event->modifiers()); else { Q_FOREACH (KoInteractionStrategyFactorySP factory, d->interactionFactories) { // skip the rest of rendering if the factory asks for it if (factory->hoverEvent(event)) return; } event->ignore(); } } void KoInteractionTool::mouseReleaseEvent(KoPointerEvent *event) { Q_D(KoInteractionTool); if (d->currentStrategy) { d->currentStrategy->finishInteraction(event->modifiers()); KUndo2Command *command = d->currentStrategy->createCommand(); if (command) d->canvas->addCommand(command); delete d->currentStrategy; d->currentStrategy = 0; repaintDecorations(); } else event->ignore(); } void KoInteractionTool::keyPressEvent(QKeyEvent *event) { Q_D(KoInteractionTool); event->ignore(); if (d->currentStrategy && (event->key() == Qt::Key_Control || event->key() == Qt::Key_Alt || event->key() == Qt::Key_Shift || event->key() == Qt::Key_Meta)) { d->currentStrategy->handleMouseMove(d->lastPoint, event->modifiers()); event->accept(); } } void KoInteractionTool::keyReleaseEvent(QKeyEvent *event) { Q_D(KoInteractionTool); if (!d->currentStrategy) { KoToolBase::keyReleaseEvent(event); return; } if (event->key() == Qt::Key_Escape) { cancelCurrentStrategy(); event->accept(); } else if (event->key() == Qt::Key_Control || event->key() == Qt::Key_Alt || event->key() == Qt::Key_Shift || event->key() == Qt::Key_Meta) { d->currentStrategy->handleMouseMove(d->lastPoint, event->modifiers()); } } KoInteractionStrategy *KoInteractionTool::currentStrategy() { Q_D(KoInteractionTool); return d->currentStrategy; } void KoInteractionTool::cancelCurrentStrategy() { Q_D(KoInteractionTool); if (d->currentStrategy) { d->currentStrategy->cancelInteraction(); delete d->currentStrategy; d->currentStrategy = 0; } } KoInteractionStrategy *KoInteractionTool::createStrategyBase(KoPointerEvent *event) { Q_D(KoInteractionTool); Q_FOREACH (KoInteractionStrategyFactorySP factory, d->interactionFactories) { KoInteractionStrategy *strategy = factory->createStrategy(event); if (strategy) { return strategy; } } return createStrategy(event); } void KoInteractionTool::addInteractionFactory(KoInteractionStrategyFactory *factory) { Q_D(KoInteractionTool); Q_FOREACH (auto f, d->interactionFactories) { KIS_SAFE_ASSERT_RECOVER_RETURN(f->id() != factory->id()); } d->interactionFactories.append(toQShared(factory)); - qSort(d->interactionFactories.begin(), + std::sort(d->interactionFactories.begin(), d->interactionFactories.end(), KoInteractionStrategyFactory::compareLess); } void KoInteractionTool::removeInteractionFactory(const QString &id) { Q_D(KoInteractionTool); QList::iterator it = d->interactionFactories.begin(); while (it != d->interactionFactories.end()) { if ((*it)->id() == id) { it = d->interactionFactories.erase(it); } else { ++it; } } } bool KoInteractionTool::hasInteractioFactory(const QString &id) { Q_D(KoInteractionTool); Q_FOREACH (auto f, d->interactionFactories) { if (f->id() == id) { return true; } } return false; } bool KoInteractionTool::tryUseCustomCursor() { Q_D(KoInteractionTool); Q_FOREACH (auto f, d->interactionFactories) { if (f->tryUseCustomCursor()) { return true; } } return false; } KoInteractionTool::KoInteractionTool(KoInteractionToolPrivate &dd) : KoToolBase(dd) { } diff --git a/libs/flake/tools/KoPathToolSelection.cpp b/libs/flake/tools/KoPathToolSelection.cpp index 45dea7a181..419a4fc8e9 100644 --- a/libs/flake/tools/KoPathToolSelection.cpp +++ b/libs/flake/tools/KoPathToolSelection.cpp @@ -1,244 +1,244 @@ /* This file is part of the KDE project * Copyright (C) 2006,2008 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 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 "KoPathToolSelection.h" #include "KoPathTool.h" #include #include #include #include #include #include #include #include #include KoPathToolSelection::KoPathToolSelection(KoPathTool * tool) : m_tool(tool) { } KoPathToolSelection::~KoPathToolSelection() { } void KoPathToolSelection::paint(QPainter &painter, const KoViewConverter &converter, qreal handleRadius) { PathShapePointMap::iterator it(m_shapePointMap.begin()); for (; it != m_shapePointMap.end(); ++it) { KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, it.key(), converter, handleRadius); helper.setHandleStyle(KisHandleStyle::selectedPrimaryHandles()); Q_FOREACH (KoPathPoint *p, it.value()) { p->paint(helper, KoPathPoint::All); } } } void KoPathToolSelection::add(KoPathPoint * point, bool clear) { if(! point) return; bool allreadyIn = false; if (clear) { if (size() == 1 && m_selectedPoints.contains(point)) { allreadyIn = true; } else { this->clear(); } } else { allreadyIn = m_selectedPoints.contains(point); } if (!allreadyIn) { m_selectedPoints.insert(point); KoPathShape * pathShape = point->parent(); PathShapePointMap::iterator it(m_shapePointMap.find(pathShape)); if (it == m_shapePointMap.end()) { it = m_shapePointMap.insert(pathShape, QSet()); } it.value().insert(point); m_tool->repaint(point->boundingRect()); emit selectionChanged(); } } void KoPathToolSelection::remove(KoPathPoint * point) { if (m_selectedPoints.remove(point)) { KoPathShape * pathShape = point->parent(); m_shapePointMap[pathShape].remove(point); if (m_shapePointMap[pathShape].size() == 0) { m_shapePointMap.remove(pathShape); } emit selectionChanged(); } m_tool->repaint(point->boundingRect()); } void KoPathToolSelection::clear() { repaint(); m_selectedPoints.clear(); m_shapePointMap.clear(); emit selectionChanged(); } void KoPathToolSelection::selectPoints(const QRectF &rect, bool clearSelection) { if (clearSelection) { clear(); } blockSignals(true); Q_FOREACH (KoPathShape* shape, m_selectedShapes) { KoParameterShape *parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) continue; Q_FOREACH (KoPathPoint* point, shape->pointsAt(shape->documentToShape(rect))) add(point, false); } blockSignals(false); emit selectionChanged(); } int KoPathToolSelection::objectCount() const { return m_shapePointMap.size(); } int KoPathToolSelection::size() const { return m_selectedPoints.size(); } bool KoPathToolSelection::contains(KoPathPoint * point) { return m_selectedPoints.contains(point); } const QSet & KoPathToolSelection::selectedPoints() const { return m_selectedPoints; } QList KoPathToolSelection::selectedPointsData() const { QList pointData; Q_FOREACH (KoPathPoint* p, m_selectedPoints) { KoPathShape * pathShape = p->parent(); pointData.append(KoPathPointData(pathShape, pathShape->pathPointIndex(p))); } return pointData; } QList KoPathToolSelection::selectedSegmentsData() const { QList pointData; QList pd(selectedPointsData()); - qSort(pd); + std::sort(pd.begin(), pd.end()); KoPathPointData last(0, KoPathPointIndex(-1, -1)); KoPathPointData lastSubpathStart(0, KoPathPointIndex(-1, -1)); QList::const_iterator it(pd.constBegin()); for (; it != pd.constEnd(); ++it) { if (it->pointIndex.second == 0) lastSubpathStart = *it; if (last.pathShape == it->pathShape && last.pointIndex.first == it->pointIndex.first && last.pointIndex.second + 1 == it->pointIndex.second) { pointData.append(last); } if (lastSubpathStart.pathShape == it->pathShape && it->pathShape->pointByIndex(it->pointIndex)->properties() & KoPathPoint::CloseSubpath && (it->pathShape->pointByIndex(it->pointIndex)->properties() & KoPathPoint::StartSubpath) == 0) { pointData.append(*it); } last = *it; } return pointData; } QList KoPathToolSelection::selectedShapes() const { return m_selectedShapes; } void KoPathToolSelection::setSelectedShapes(const QList shapes) { m_selectedShapes = shapes; } void KoPathToolSelection::repaint() { update(); Q_FOREACH (KoPathPoint *p, m_selectedPoints) { m_tool->repaint(p->boundingRect(false)); } } void KoPathToolSelection::update() { bool selectionHasChanged = false; PathShapePointMap::iterator it(m_shapePointMap.begin()); while (it != m_shapePointMap.end()) { KoParameterShape *parameterShape = dynamic_cast(it.key()); bool isParametricShape = parameterShape && parameterShape->isParametricShape(); if (! m_selectedShapes.contains(it.key()) || isParametricShape) { QSet::iterator pointIt(it.value().begin()); for (; pointIt != it.value().end(); ++pointIt) { m_selectedPoints.remove(*pointIt); } it = m_shapePointMap.erase(it); selectionHasChanged = true; } else { QSet::iterator pointIt(it.value().begin()); while (pointIt != it.value().end()) { if ((*pointIt)->parent()->pathPointIndex(*pointIt) == KoPathPointIndex(-1, -1)) { m_selectedPoints.remove(*pointIt); pointIt = it.value().erase(pointIt); selectionHasChanged = true; } else { ++pointIt; } } ++it; } } if (selectionHasChanged) emit selectionChanged(); } bool KoPathToolSelection::hasSelection() { return !m_selectedPoints.isEmpty(); } diff --git a/libs/image/kis_cubic_curve.cpp b/libs/image/kis_cubic_curve.cpp index 43ca6f5d54..10ffa92bfd 100644 --- a/libs/image/kis_cubic_curve.cpp +++ b/libs/image/kis_cubic_curve.cpp @@ -1,477 +1,477 @@ /* * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * Copyright (c) 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_cubic_curve.h" #include #include #include #include #include "kis_dom_utils.h" template class KisTridiagonalSystem { /* * e.g. * |b0 c0 0 0 0| |x0| |f0| * |a0 b1 c1 0 0| |x1| |f1| * |0 a1 b2 c2 0|*|x2|=|f2| * |0 0 a2 b3 c3| |x3| |f3| * |0 0 0 a3 b4| |x4| |f4| */ public: /** * @return - vector that is storing x[] */ static QVector calculate(QList &a, QList &b, QList &c, QList &f) { QVector x; QVector alpha; QVector beta; int i; int size = b.size(); Q_ASSERT(a.size() == size - 1 && c.size() == size - 1 && f.size() == size); x.resize(size); /** * Check for special case when * order of the matrix is equal to 1 */ if (size == 1) { x[0] = f[0] / b[0]; return x; } /** * Common case */ alpha.resize(size); beta.resize(size); alpha[1] = -c[0] / b[0]; beta[1] = f[0] / b[0]; for (i = 1; i < size - 1; i++) { alpha[i+1] = -c[i] / (a[i-1] * alpha[i] + b[i]); beta[i+1] = (f[i] - a[i-1] * beta[i]) / (a[i-1] * alpha[i] + b[i]); } x.last() = (f.last() - a.last() * beta.last()) / (b.last() + a.last() * alpha.last()); for (i = size - 2; i >= 0; i--) x[i] = alpha[i+1] * x[i+1] + beta[i+1]; return x; } }; template class KisCubicSpline { /** * s[i](x)=a[i] + * b[i] * (x-x[i]) + * 1/2 * c[i] * (x-x[i])^2 + * 1/6 * d[i] * (x-x[i])^3 * * h[i]=x[i+1]-x[i] * */ protected: QList m_a; QVector m_b; QVector m_c; QVector m_d; QVector m_h; T m_begin; T m_end; int m_intervals; public: KisCubicSpline() {} KisCubicSpline(const QList &a) { createSpline(a); } /** * Create new spline and precalculate some values * for future * * @a - base points of the spline */ void createSpline(const QList &a) { int intervals = m_intervals = a.size() - 1; int i; m_begin = a.first().x(); m_end = a.last().x(); m_a.clear(); m_b.resize(intervals); m_c.clear(); m_d.resize(intervals); m_h.resize(intervals); for (i = 0; i < intervals; i++) { m_h[i] = a[i+1].x() - a[i].x(); m_a.append(a[i].y()); } m_a.append(a.last().y()); QList tri_b; QList tri_f; QList tri_a; /* equals to @tri_c */ for (i = 0; i < intervals - 1; i++) { tri_b.append(2.*(m_h[i] + m_h[i+1])); tri_f.append(6.*((m_a[i+2] - m_a[i+1]) / m_h[i+1] - (m_a[i+1] - m_a[i]) / m_h[i])); } for (i = 1; i < intervals - 1; i++) tri_a.append(m_h[i]); if (intervals > 1) { m_c = KisTridiagonalSystem::calculate(tri_a, tri_b, tri_a, tri_f); } m_c.prepend(0); m_c.append(0); for (i = 0; i < intervals; i++) m_d[i] = (m_c[i+1] - m_c[i]) / m_h[i]; for (i = 0; i < intervals; i++) m_b[i] = -0.5 * (m_c[i] * m_h[i]) - (1 / 6.0) * (m_d[i] * m_h[i] * m_h[i]) + (m_a[i+1] - m_a[i]) / m_h[i]; } /** * Get value of precalculated spline in the point @x */ T getValue(T x) const { T x0; int i = findRegion(x, x0); /* TODO: check for asm equivalent */ return m_a[i] + m_b[i] *(x - x0) + 0.5 * m_c[i] *(x - x0) *(x - x0) + (1 / 6.0)* m_d[i] *(x - x0) *(x - x0) *(x - x0); } T begin() const { return m_begin; } T end() const { return m_end; } protected: /** * findRegion - Searches for the region containing @x * @x0 - out parameter, containing beginning of the region * @return - index of the region */ int findRegion(T x, T &x0) const { int i; x0 = m_begin; for (i = 0; i < m_intervals; i++) { if (x >= x0 && x < x0 + m_h[i]) return i; x0 += m_h[i]; } if (x >= x0) { x0 -= m_h[m_intervals-1]; return m_intervals - 1; } qDebug("X value: %f\n", x); qDebug("m_begin: %f\n", m_begin); qDebug("m_end : %f\n", m_end); Q_ASSERT_X(0, "findRegion", "X value is outside regions"); /* **never reached** */ return -1; } }; static bool pointLessThan(const QPointF &a, const QPointF &b) { return a.x() < b.x(); } struct Q_DECL_HIDDEN KisCubicCurve::Data : public QSharedData { Data() { init(); } Data(const Data& data) : QSharedData() { init(); points = data.points; name = data.name; } void init() { validSpline = false; validU16Transfer = false; validFTransfer = false; } ~Data() { } mutable QString name; mutable KisCubicSpline spline; QList points; mutable bool validSpline; mutable QVector u8Transfer; mutable bool validU8Transfer; mutable QVector u16Transfer; mutable bool validU16Transfer; mutable QVector fTransfer; mutable bool validFTransfer; void updateSpline(); void keepSorted(); qreal value(qreal x); void invalidate(); template void updateTransfer(QVector<_T_>* transfer, bool& valid, _T2_ min, _T2_ max, int size); }; void KisCubicCurve::Data::updateSpline() { if (validSpline) return; validSpline = true; spline.createSpline(points); } void KisCubicCurve::Data::invalidate() { validSpline = false; validFTransfer = false; validU16Transfer = false; } void KisCubicCurve::Data::keepSorted() { - qSort(points.begin(), points.end(), pointLessThan); + std::sort(points.begin(), points.end(), pointLessThan); } qreal KisCubicCurve::Data::value(qreal x) { updateSpline(); /* Automatically extend non-existing parts of the curve * (e.g. before the first point) and cut off big y-values */ x = qBound(spline.begin(), x, spline.end()); qreal y = spline.getValue(x); return qBound(qreal(0.0), y, qreal(1.0)); } template void KisCubicCurve::Data::updateTransfer(QVector<_T_>* transfer, bool& valid, _T2_ min, _T2_ max, int size) { if (!valid || transfer->size() != size) { if (transfer->size() != size) { transfer->resize(size); } qreal end = 1.0 / (size - 1); for (int i = 0; i < size; ++i) { /* Direct uncached version */ _T2_ val = value(i * end ) * max; val = qBound(min, val, max); (*transfer)[i] = val; } valid = true; } } struct Q_DECL_HIDDEN KisCubicCurve::Private { QSharedDataPointer data; }; KisCubicCurve::KisCubicCurve() : d(new Private) { d->data = new Data; QPointF p; p.rx() = 0.0; p.ry() = 0.0; d->data->points.append(p); p.rx() = 1.0; p.ry() = 1.0; d->data->points.append(p); } KisCubicCurve::KisCubicCurve(const QList& points) : d(new Private) { d->data = new Data; d->data->points = points; d->data->keepSorted(); } KisCubicCurve::KisCubicCurve(const KisCubicCurve& curve) : d(new Private(*curve.d)) { } KisCubicCurve::~KisCubicCurve() { delete d; } KisCubicCurve& KisCubicCurve::operator=(const KisCubicCurve & curve) { if (&curve != this) { *d = *curve.d; } return *this; } bool KisCubicCurve::operator==(const KisCubicCurve& curve) const { if (d->data == curve.d->data) return true; return d->data->points == curve.d->data->points; } qreal KisCubicCurve::value(qreal x) const { qreal value = d->data->value(x); return value; } QList KisCubicCurve::points() const { return d->data->points; } void KisCubicCurve::setPoints(const QList& points) { d->data.detach(); d->data->points = points; d->data->invalidate(); } void KisCubicCurve::setPoint(int idx, const QPointF& point) { d->data.detach(); d->data->points[idx] = point; d->data->keepSorted(); d->data->invalidate(); } int KisCubicCurve::addPoint(const QPointF& point) { d->data.detach(); d->data->points.append(point); d->data->keepSorted(); d->data->invalidate(); return d->data->points.indexOf(point); } void KisCubicCurve::removePoint(int idx) { d->data.detach(); d->data->points.removeAt(idx); d->data->invalidate(); } bool KisCubicCurve::isNull() const { const QList &points = d->data->points; Q_FOREACH (const QPointF &pt, points) { if (!qFuzzyCompare(pt.x(), pt.y())) { return false; } } return true; } const QString& KisCubicCurve::name() const { return d->data->name; } void KisCubicCurve::setName(const QString& name) { d->data->name = name; } QString KisCubicCurve::toString() const { QString sCurve; if(d->data->points.count() < 1) return sCurve; Q_FOREACH (const QPointF & pair, d->data->points) { sCurve += QString::number(pair.x()); sCurve += ','; sCurve += QString::number(pair.y()); sCurve += ';'; } return sCurve; } void KisCubicCurve::fromString(const QString& string) { QStringList data = string.split(';'); QList points; Q_FOREACH (const QString & pair, data) { if (pair.indexOf(',') > -1) { QPointF p; p.rx() = KisDomUtils::toDouble(pair.section(',', 0, 0)); p.ry() = KisDomUtils::toDouble(pair.section(',', 1, 1)); points.append(p); } } setPoints(points); } const QVector KisCubicCurve::uint16Transfer(int size) const { d->data->updateTransfer(&d->data->u16Transfer, d->data->validU16Transfer, 0x0, 0xFFFF, size); return d->data->u16Transfer; } const QVector KisCubicCurve::floatTransfer(int size) const { d->data->updateTransfer(&d->data->fTransfer, d->data->validFTransfer, 0.0, 1.0, size); return d->data->fTransfer; } diff --git a/libs/image/kis_layer_utils.cpp b/libs/image/kis_layer_utils.cpp index f4f49e53d2..2f13b369e1 100644 --- a/libs/image/kis_layer_utils.cpp +++ b/libs/image/kis_layer_utils.cpp @@ -1,1350 +1,1350 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_utils.h" #include #include #include #include "kis_painter.h" #include "kis_image.h" #include "kis_node.h" #include "kis_layer.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_group_layer.h" #include "kis_selection.h" #include "kis_selection_mask.h" #include "kis_meta_data_merge_strategy.h" #include #include "commands/kis_image_layer_add_command.h" #include "commands/kis_image_layer_remove_command.h" #include "commands/kis_image_layer_move_command.h" #include "commands/kis_image_change_layers_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "kis_abstract_projection_plane.h" #include "kis_processing_applicator.h" #include "kis_image_animation_interface.h" #include "kis_keyframe_channel.h" #include "kis_command_utils.h" #include "kis_processing_applicator.h" #include "commands_new/kis_change_projection_color_command.h" #include "kis_layer_properties_icons.h" #include "lazybrush/kis_colorize_mask.h" #include "commands/kis_node_property_list_command.h" #include namespace KisLayerUtils { void fetchSelectionMasks(KisNodeList mergedNodes, QVector &selectionMasks) { foreach (KisNodeSP node, mergedNodes) { KisLayerSP layer = qobject_cast(node.data()); KisSelectionMaskSP mask; if (layer && (mask = layer->selectionMask())) { selectionMasks.append(mask); } } } struct MergeDownInfoBase { MergeDownInfoBase(KisImageSP _image) : image(_image), storage(new SwitchFrameCommand::SharedStorage()) { } virtual ~MergeDownInfoBase() {} KisImageWSP image; QVector selectionMasks; KisNodeSP dstNode; SwitchFrameCommand::SharedStorageSP storage; QSet frames; virtual KisNodeList allSrcNodes() = 0; KisLayerSP dstLayer() { return qobject_cast(dstNode.data()); } }; struct MergeDownInfo : public MergeDownInfoBase { MergeDownInfo(KisImageSP _image, KisLayerSP _prevLayer, KisLayerSP _currLayer) : MergeDownInfoBase(_image), prevLayer(_prevLayer), currLayer(_currLayer) { frames = fetchLayerFramesRecursive(prevLayer) | fetchLayerFramesRecursive(currLayer); } KisLayerSP prevLayer; KisLayerSP currLayer; KisNodeList allSrcNodes() override { KisNodeList mergedNodes; mergedNodes << currLayer; mergedNodes << prevLayer; return mergedNodes; } }; struct MergeMultipleInfo : public MergeDownInfoBase { MergeMultipleInfo(KisImageSP _image, KisNodeList _mergedNodes) : MergeDownInfoBase(_image), mergedNodes(_mergedNodes) { foreach (KisNodeSP node, mergedNodes) { frames |= fetchLayerFramesRecursive(node); } } KisNodeList mergedNodes; KisNodeList allSrcNodes() override { return mergedNodes; } }; typedef QSharedPointer MergeDownInfoBaseSP; typedef QSharedPointer MergeDownInfoSP; typedef QSharedPointer MergeMultipleInfoSP; struct FillSelectionMasks : public KUndo2Command { FillSelectionMasks(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { fetchSelectionMasks(m_info->allSrcNodes(), m_info->selectionMasks); } private: MergeDownInfoBaseSP m_info; }; struct DisableColorizeKeyStrokes : public KisCommandUtils::AggregateCommand { DisableColorizeKeyStrokes(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { recursiveApplyNodes(node, [this] (KisNodeSP node) { if (dynamic_cast(node.data()) && KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false); addCommand(new KisNodePropertyListCommand(node, props)); } }); } } private: MergeDownInfoBaseSP m_info; }; struct DisableOnionSkins : public KisCommandUtils::AggregateCommand { DisableOnionSkins(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { recursiveApplyNodes(node, [this] (KisNodeSP node) { if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::onionSkins, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::onionSkins, false); addCommand(new KisNodePropertyListCommand(node, props)); } }); } } private: MergeDownInfoBaseSP m_info; }; struct DisablePassThroughForHeadsOnly : public KisCommandUtils::AggregateCommand { DisablePassThroughForHeadsOnly(MergeDownInfoBaseSP info, bool skipIfDstIsGroup = false) : m_info(info), m_skipIfDstIsGroup(skipIfDstIsGroup) { } void populateChildCommands() override { if (m_skipIfDstIsGroup && m_info->dstLayer() && m_info->dstLayer()->inherits("KisGroupLayer")) { return; } Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) { if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::passThrough, false).toBool()) { KisBaseNode::PropertyList props = node->sectionModelProperties(); KisLayerPropertiesIcons::setNodeProperty(&props, KisLayerPropertiesIcons::passThrough, false); addCommand(new KisNodePropertyListCommand(node, props)); } } } private: MergeDownInfoBaseSP m_info; bool m_skipIfDstIsGroup; }; struct RefreshHiddenAreas : public KUndo2Command { RefreshHiddenAreas(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { KisImageAnimationInterface *interface = m_info->image->animationInterface(); const QRect preparedRect = !interface->externalFrameActive() ? m_info->image->bounds() : QRect(); foreach (KisNodeSP node, m_info->allSrcNodes()) { refreshHiddenAreaAsync(node, preparedRect); } } private: QRect realNodeExactBounds(KisNodeSP rootNode, QRect currentRect = QRect()) { KisNodeSP node = rootNode->firstChild(); while(node) { currentRect |= realNodeExactBounds(node, currentRect); node = node->nextSibling(); } // TODO: it would be better to count up changeRect inside // node's extent() method currentRect |= rootNode->projectionPlane()->changeRect(rootNode->exactBounds()); return currentRect; } void refreshHiddenAreaAsync(KisNodeSP rootNode, const QRect &preparedArea) { QRect realNodeRect = realNodeExactBounds(rootNode); if (!preparedArea.contains(realNodeRect)) { QRegion dirtyRegion = realNodeRect; dirtyRegion -= preparedArea; foreach(const QRect &rc, dirtyRegion.rects()) { m_info->image->refreshGraphAsync(rootNode, rc, realNodeRect); } } } private: MergeDownInfoBaseSP m_info; }; struct RefreshDelayedUpdateLayers : public KUndo2Command { RefreshDelayedUpdateLayers(MergeDownInfoBaseSP info) : m_info(info) {} void redo() override { foreach (KisNodeSP node, m_info->allSrcNodes()) { recursiveApplyNodes(node, [] (KisNodeSP node) { KisDelayedUpdateNodeInterface *delayedUpdate = dynamic_cast(node.data()); if (delayedUpdate) { delayedUpdate->forceUpdateTimedNode(); } }); } } private: MergeDownInfoBaseSP m_info; }; struct KeepMergedNodesSelected : public KisCommandUtils::AggregateCommand { KeepMergedNodesSelected(MergeDownInfoSP info, bool finalizing) : m_singleInfo(info), m_finalizing(finalizing) {} KeepMergedNodesSelected(MergeMultipleInfoSP info, KisNodeSP putAfter, bool finalizing) : m_multipleInfo(info), m_finalizing(finalizing), m_putAfter(putAfter) {} void populateChildCommands() override { KisNodeSP prevNode; KisNodeSP nextNode; KisNodeList prevSelection; KisNodeList nextSelection; KisImageSP image; if (m_singleInfo) { prevNode = m_singleInfo->currLayer; nextNode = m_singleInfo->dstNode; image = m_singleInfo->image; } else if (m_multipleInfo) { prevNode = m_putAfter; nextNode = m_multipleInfo->dstNode; prevSelection = m_multipleInfo->allSrcNodes(); image = m_multipleInfo->image; } if (!m_finalizing) { addCommand(new KeepNodesSelectedCommand(prevSelection, KisNodeList(), prevNode, KisNodeSP(), image, false)); } else { addCommand(new KeepNodesSelectedCommand(KisNodeList(), nextSelection, KisNodeSP(), nextNode, image, true)); } } private: MergeDownInfoSP m_singleInfo; MergeMultipleInfoSP m_multipleInfo; bool m_finalizing; KisNodeSP m_putAfter; }; struct CreateMergedLayer : public KisCommandUtils::AggregateCommand { CreateMergedLayer(MergeDownInfoSP info) : m_info(info) {} void populateChildCommands() override { // actual merging done by KisLayer::createMergedLayer (or specialized decendant) m_info->dstNode = m_info->currLayer->createMergedLayerTemplate(m_info->prevLayer); if (m_info->frames.size() > 0) { m_info->dstNode->enableAnimation(); m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); } } private: MergeDownInfoSP m_info; }; struct CreateMergedLayerMultiple : public KisCommandUtils::AggregateCommand { CreateMergedLayerMultiple(MergeMultipleInfoSP info, const QString name = QString() ) : m_info(info), m_name(name) {} void populateChildCommands() override { QString mergedLayerName; if (m_name.isEmpty()){ const QString mergedLayerSuffix = i18n("Merged"); mergedLayerName = m_info->mergedNodes.first()->name(); if (!mergedLayerName.endsWith(mergedLayerSuffix)) { mergedLayerName = QString("%1 %2") .arg(mergedLayerName).arg(mergedLayerSuffix); } } else { mergedLayerName = m_name; } m_info->dstNode = new KisPaintLayer(m_info->image, mergedLayerName, OPACITY_OPAQUE_U8); if (m_info->frames.size() > 0) { m_info->dstNode->enableAnimation(); m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); } QString compositeOpId; QBitArray channelFlags; bool compositionVaries = false; foreach (KisNodeSP node, m_info->allSrcNodes()) { if (compositeOpId.isEmpty()) { compositeOpId = node->compositeOpId(); } else if (compositeOpId != node->compositeOpId()) { compositionVaries = true; break; } KisLayerSP layer = qobject_cast(node.data()); if (layer && layer->layerStyle()) { compositionVaries = true; break; } } if (!compositionVaries) { if (!compositeOpId.isEmpty()) { m_info->dstNode->setCompositeOpId(compositeOpId); } if (m_info->dstLayer() && !channelFlags.isEmpty()) { m_info->dstLayer()->setChannelFlags(channelFlags); } } } private: MergeMultipleInfoSP m_info; QString m_name; }; struct MergeLayers : public KisCommandUtils::AggregateCommand { MergeLayers(MergeDownInfoSP info) : m_info(info) {} void populateChildCommands() override { // actual merging done by KisLayer::createMergedLayer (or specialized decendant) m_info->currLayer->fillMergedLayerTemplate(m_info->dstLayer(), m_info->prevLayer); } private: MergeDownInfoSP m_info; }; struct MergeLayersMultiple : public KisCommandUtils::AggregateCommand { MergeLayersMultiple(MergeMultipleInfoSP info) : m_info(info) {} void populateChildCommands() override { KisPainter gc(m_info->dstNode->paintDevice()); foreach (KisNodeSP node, m_info->allSrcNodes()) { QRect rc = node->exactBounds() | m_info->image->bounds(); node->projectionPlane()->apply(&gc, rc); } } private: MergeMultipleInfoSP m_info; }; struct MergeMetaData : public KUndo2Command { MergeMetaData(MergeDownInfoSP info, const KisMetaData::MergeStrategy* strategy) : m_info(info), m_strategy(strategy) {} void redo() override { QRect layerProjectionExtent = m_info->currLayer->projection()->extent(); QRect prevLayerProjectionExtent = m_info->prevLayer->projection()->extent(); int prevLayerArea = prevLayerProjectionExtent.width() * prevLayerProjectionExtent.height(); int layerArea = layerProjectionExtent.width() * layerProjectionExtent.height(); QList scores; double norm = qMax(prevLayerArea, layerArea); scores.append(prevLayerArea / norm); scores.append(layerArea / norm); QList srcs; srcs.append(m_info->prevLayer->metaData()); srcs.append(m_info->currLayer->metaData()); m_strategy->merge(m_info->dstLayer()->metaData(), srcs, scores); } private: MergeDownInfoSP m_info; const KisMetaData::MergeStrategy *m_strategy; }; KeepNodesSelectedCommand::KeepNodesSelectedCommand(const KisNodeList &selectedBefore, const KisNodeList &selectedAfter, KisNodeSP activeBefore, KisNodeSP activeAfter, KisImageSP image, bool finalize, KUndo2Command *parent) : FlipFlopCommand(finalize, parent), m_selectedBefore(selectedBefore), m_selectedAfter(selectedAfter), m_activeBefore(activeBefore), m_activeAfter(activeAfter), m_image(image) { } void KeepNodesSelectedCommand::end() { KisImageSignalType type; if (isFinalizing()) { type = ComplexNodeReselectionSignal(m_activeAfter, m_selectedAfter); } else { type = ComplexNodeReselectionSignal(m_activeBefore, m_selectedBefore); } m_image->signalRouter()->emitNotification(type); } KisLayerSP constructDefaultLayer(KisImageSP image) { return new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace()); } RemoveNodeHelper::~RemoveNodeHelper() { } /** * The removal of two nodes in one go may be a bit tricky, because one * of them may be the clone of another. If we remove the source of a * clone layer, it will reincarnate into a paint layer. In this case * the pointer to the second layer will be lost. * * That's why we need to care about the order of the nodes removal: * the clone --- first, the source --- last. */ void RemoveNodeHelper::safeRemoveMultipleNodes(KisNodeList nodes, KisImageSP image) { const bool lastLayer = scanForLastLayer(image, nodes); while (!nodes.isEmpty()) { KisNodeList::iterator it = nodes.begin(); while (it != nodes.end()) { if (!checkIsSourceForClone(*it, nodes)) { KisNodeSP node = *it; addCommandImpl(new KisImageLayerRemoveCommand(image, node, false, true)); it = nodes.erase(it); } else { ++it; } } } if (lastLayer) { KisLayerSP newLayer = constructDefaultLayer(image); addCommandImpl(new KisImageLayerAddCommand(image, newLayer, image->root(), KisNodeSP(), false, false)); } } bool RemoveNodeHelper::checkIsSourceForClone(KisNodeSP src, const KisNodeList &nodes) { foreach (KisNodeSP node, nodes) { if (node == src) continue; KisCloneLayer *clone = dynamic_cast(node.data()); if (clone && KisNodeSP(clone->copyFrom()) == src) { return true; } } return false; } bool RemoveNodeHelper::scanForLastLayer(KisImageWSP image, KisNodeList nodesToRemove) { bool removeLayers = false; Q_FOREACH(KisNodeSP nodeToRemove, nodesToRemove) { if (qobject_cast(nodeToRemove.data())) { removeLayers = true; break; } } if (!removeLayers) return false; bool lastLayer = true; KisNodeSP node = image->root()->firstChild(); while (node) { if (!nodesToRemove.contains(node) && qobject_cast(node.data())) { lastLayer = false; break; } node = node->nextSibling(); } return lastLayer; } SimpleRemoveLayers::SimpleRemoveLayers(const KisNodeList &nodes, KisImageSP image) : m_nodes(nodes), m_image(image) { } void SimpleRemoveLayers::populateChildCommands() { if (m_nodes.isEmpty()) return; safeRemoveMultipleNodes(m_nodes, m_image); } void SimpleRemoveLayers::addCommandImpl(KUndo2Command *cmd) { addCommand(cmd); } struct InsertNode : public KisCommandUtils::AggregateCommand { InsertNode(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter) {} void populateChildCommands() override { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, m_putAfter->parent(), m_putAfter, true, false)); } private: virtual void addCommandImpl(KUndo2Command *cmd) { addCommand(cmd); } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; struct CleanUpNodes : private RemoveNodeHelper, public KisCommandUtils::AggregateCommand { CleanUpNodes(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter) {} static void findPerfectParent(KisNodeList nodesToDelete, KisNodeSP &putAfter, KisNodeSP &parent) { if (!putAfter) { putAfter = nodesToDelete.last(); } // Add the new merged node on top of the active node -- checking // whether the parent is going to be deleted parent = putAfter->parent(); while (parent && nodesToDelete.contains(parent)) { parent = parent->parent(); } } void populateChildCommands() override { KisNodeList nodesToDelete = m_info->allSrcNodes(); KisNodeSP parent; findPerfectParent(nodesToDelete, m_putAfter, parent); if (!parent) { KisNodeSP oldRoot = m_info->image->root(); KisNodeSP newRoot(new KisGroupLayer(m_info->image, "root", OPACITY_OPAQUE_U8)); addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, newRoot, KisNodeSP(), true, false)); addCommand(new KisImageChangeLayersCommand(m_info->image, oldRoot, newRoot)); } else { if (parent == m_putAfter->parent()) { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, parent, m_putAfter, true, false)); } else { addCommand(new KisImageLayerAddCommand(m_info->image, m_info->dstNode, parent, parent->lastChild(), true, false)); } /** * We can merge selection masks, in this case dstLayer is not defined! */ if (m_info->dstLayer()) { reparentSelectionMasks(m_info->image, m_info->dstLayer(), m_info->selectionMasks); } safeRemoveMultipleNodes(m_info->allSrcNodes(), m_info->image); } } private: void addCommandImpl(KUndo2Command *cmd) override { addCommand(cmd); } void reparentSelectionMasks(KisImageSP image, KisLayerSP newLayer, const QVector &selectionMasks) { KIS_SAFE_ASSERT_RECOVER_RETURN(newLayer); foreach (KisSelectionMaskSP mask, selectionMasks) { addCommand(new KisImageLayerMoveCommand(image, mask, newLayer, newLayer->lastChild())); addCommand(new KisActivateSelectionMaskCommand(mask, false)); } } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; SwitchFrameCommand::SharedStorage::~SharedStorage() { } SwitchFrameCommand::SwitchFrameCommand(KisImageSP image, int time, bool finalize, SharedStorageSP storage) : FlipFlopCommand(finalize), m_image(image), m_newTime(time), m_storage(storage) {} SwitchFrameCommand::~SwitchFrameCommand() {} void SwitchFrameCommand::init() { KisImageAnimationInterface *interface = m_image->animationInterface(); const int currentTime = interface->currentTime(); if (currentTime == m_newTime) { m_storage->value = m_newTime; return; } interface->image()->disableUIUpdates(); interface->saveAndResetCurrentTime(m_newTime, &m_storage->value); } void SwitchFrameCommand::end() { KisImageAnimationInterface *interface = m_image->animationInterface(); const int currentTime = interface->currentTime(); if (currentTime == m_storage->value) { return; } interface->restoreCurrentTime(&m_storage->value); interface->image()->enableUIUpdates(); } struct AddNewFrame : public KisCommandUtils::AggregateCommand { AddNewFrame(MergeDownInfoBaseSP info, int frame) : m_info(info), m_frame(frame) {} void populateChildCommands() override { KUndo2Command *cmd = new KisCommandUtils::SkipFirstRedoWrapper(); KisKeyframeChannel *channel = m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id()); channel->addKeyframe(m_frame, cmd); addCommand(cmd); } private: MergeDownInfoBaseSP m_info; int m_frame; }; QSet fetchLayerFrames(KisNodeSP node) { KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!channel) return QSet(); return channel->allKeyframeIds(); } QSet fetchLayerFramesRecursive(KisNodeSP rootNode) { QSet frames = fetchLayerFrames(rootNode); KisNodeSP node = rootNode->firstChild(); while(node) { frames |= fetchLayerFramesRecursive(node); node = node->nextSibling(); } return frames; } void updateFrameJobs(FrameJobs *jobs, KisNodeSP node) { QSet frames = fetchLayerFrames(node); if (frames.isEmpty()) { (*jobs)[0].insert(node); } else { foreach (int frame, frames) { (*jobs)[frame].insert(node); } } } void updateFrameJobsRecursive(FrameJobs *jobs, KisNodeSP rootNode) { updateFrameJobs(jobs, rootNode); KisNodeSP node = rootNode->firstChild(); while(node) { updateFrameJobsRecursive(jobs, node); node = node->nextSibling(); } } void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { if (!layer->prevSibling()) return; // XXX: this breaks if we allow free mixing of masks and layers KisLayerSP prevLayer = qobject_cast(layer->prevSibling().data()); if (!prevLayer) return; if (!layer->visible() && !prevLayer->visible()) { return; } KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, kundo2_i18n("Merge Down")); if (layer->visible() && prevLayer->visible()) { MergeDownInfoSP info(new MergeDownInfo(image, prevLayer, layer)); // disable key strokes on all colorize masks, all onion skins on // paint layers and wait until update is finished with a barrier applicator.applyCommand(new DisableColorizeKeyStrokes(info)); applicator.applyCommand(new DisableOnionSkins(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); applicator.applyCommand(new KeepMergedNodesSelected(info, false)); applicator.applyCommand(new FillSelectionMasks(info)); applicator.applyCommand(new CreateMergedLayer(info), KisStrokeJobData::BARRIER); // in two-layer mode we disable pass trhough only when the destination layer // is not a group layer applicator.applyCommand(new DisablePassThroughForHeadsOnly(info, true)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); if (info->frames.size() > 0) { foreach (int frame, info->frames) { applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage)); applicator.applyCommand(new AddNewFrame(info, frame)); applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage)); } } else { applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER); } applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER); applicator.applyCommand(new CleanUpNodes(info, layer), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepMergedNodesSelected(info, true)); } else if (layer->visible()) { applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), layer, KisNodeSP(), image, false)); applicator.applyCommand( new SimpleRemoveLayers(KisNodeList() << prevLayer, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), KisNodeSP(), layer, image, true)); } else if (prevLayer->visible()) { applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), layer, KisNodeSP(), image, false)); applicator.applyCommand( new SimpleRemoveLayers(KisNodeList() << layer, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(), KisNodeSP(), prevLayer, image, true)); } applicator.end(); } bool checkIsChildOf(KisNodeSP node, const KisNodeList &parents) { KisNodeList nodeParents; KisNodeSP parent = node->parent(); while (parent) { nodeParents << parent; parent = parent->parent(); } foreach(KisNodeSP perspectiveParent, parents) { if (nodeParents.contains(perspectiveParent)) { return true; } } return false; } bool checkIsCloneOf(KisNodeSP node, const KisNodeList &nodes) { bool result = false; KisCloneLayer *clone = dynamic_cast(node.data()); if (clone) { KisNodeSP cloneSource = KisNodeSP(clone->copyFrom()); Q_FOREACH(KisNodeSP subtree, nodes) { result = recursiveFindNode(subtree, [cloneSource](KisNodeSP node) -> bool { return node == cloneSource; }); if (!result) { result = checkIsCloneOf(cloneSource, nodes); } if (result) { break; } } } return result; } void filterMergableNodes(KisNodeList &nodes, bool allowMasks) { KisNodeList::iterator it = nodes.begin(); while (it != nodes.end()) { if ((!allowMasks && !qobject_cast(it->data())) || checkIsChildOf(*it, nodes)) { qDebug() << "Skipping node" << ppVar((*it)->name()); it = nodes.erase(it); } else { ++it; } } } void sortMergableNodes(KisNodeSP root, KisNodeList &inputNodes, KisNodeList &outputNodes) { KisNodeList::iterator it = std::find(inputNodes.begin(), inputNodes.end(), root); if (it != inputNodes.end()) { outputNodes << *it; inputNodes.erase(it); } if (inputNodes.isEmpty()) { return; } KisNodeSP child = root->firstChild(); while (child) { sortMergableNodes(child, inputNodes, outputNodes); child = child->nextSibling(); } /** * By the end of recursion \p inputNodes must be empty */ KIS_ASSERT_RECOVER_NOOP(root->parent() || inputNodes.isEmpty()); } KisNodeList sortMergableNodes(KisNodeSP root, KisNodeList nodes) { KisNodeList result; sortMergableNodes(root, nodes, result); return result; } KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks) { KIS_ASSERT_RECOVER(!nodes.isEmpty()) { return nodes; } KisNodeSP root; Q_FOREACH(KisNodeSP node, nodes) { KisNodeSP localRoot = node; while (localRoot->parent()) { localRoot = localRoot->parent(); } if (!root) { root = localRoot; } KIS_ASSERT_RECOVER(root == localRoot) { return nodes; } } KisNodeList result; sortMergableNodes(root, nodes, result); filterMergableNodes(result, allowMasks); return result; } void addCopyOfNameTag(KisNodeSP node) { const QString prefix = i18n("Copy of"); QString newName = node->name(); if (!newName.startsWith(prefix)) { newName = QString("%1 %2").arg(prefix).arg(newName); node->setName(newName); } } KisNodeList findNodesWithProps(KisNodeSP root, const KoProperties &props, bool excludeRoot) { KisNodeList nodes; if ((!excludeRoot || root->parent()) && root->check(props)) { nodes << root; } KisNodeSP node = root->firstChild(); while (node) { nodes += findNodesWithProps(node, props, excludeRoot); node = node->nextSibling(); } return nodes; } KisNodeList filterInvisibleNodes(const KisNodeList &nodes, KisNodeList *invisibleNodes, KisNodeSP *putAfter) { KIS_ASSERT_RECOVER(invisibleNodes) { return nodes; } KIS_ASSERT_RECOVER(putAfter) { return nodes; } KisNodeList visibleNodes; int putAfterIndex = -1; Q_FOREACH(KisNodeSP node, nodes) { if (node->visible()) { visibleNodes << node; } else { *invisibleNodes << node; if (node == *putAfter) { putAfterIndex = visibleNodes.size() - 1; } } } if (!visibleNodes.isEmpty() && putAfterIndex >= 0) { putAfterIndex = qBound(0, putAfterIndex, visibleNodes.size() - 1); *putAfter = visibleNodes[putAfterIndex]; } return visibleNodes; } void changeImageDefaultProjectionColor(KisImageSP image, const KoColor &color) { KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, image->root(), KisProcessingApplicator::RECURSIVE, emitSignals, kundo2_i18n("Change projection color"), 0, 142857 + 1); applicator.applyCommand(new KisChangeProjectionColorCommand(image, color), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); applicator.end(); } void mergeMultipleLayersImpl(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter, bool flattenSingleLayer, const KUndo2MagicString &actionName, bool cleanupNodes = true, const QString layerName = QString()) { if (!putAfter) { putAfter = mergedNodes.first(); } filterMergableNodes(mergedNodes); { KisNodeList tempNodes; - qSwap(mergedNodes, tempNodes); + std::swap(mergedNodes, tempNodes); sortMergableNodes(image->root(), tempNodes, mergedNodes); } if (mergedNodes.size() <= 1 && (!flattenSingleLayer && mergedNodes.size() == 1)) return; KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; emitSignals << ComplexNodeReselectionSignal(KisNodeSP(), KisNodeList(), KisNodeSP(), mergedNodes); KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, actionName); KisNodeList originalNodes = mergedNodes; KisNodeList invisibleNodes; mergedNodes = filterInvisibleNodes(originalNodes, &invisibleNodes, &putAfter); if (!invisibleNodes.isEmpty()) { applicator.applyCommand( new SimpleRemoveLayers(invisibleNodes, image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } if (mergedNodes.size() > 1 || invisibleNodes.isEmpty()) { MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes)); // disable key strokes on all colorize masks, all onion skins on // paint layers and wait until update is finished with a barrier applicator.applyCommand(new DisableColorizeKeyStrokes(info)); applicator.applyCommand(new DisableOnionSkins(info)); applicator.applyCommand(new DisablePassThroughForHeadsOnly(info)); applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER); applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, false)); applicator.applyCommand(new FillSelectionMasks(info)); applicator.applyCommand(new CreateMergedLayerMultiple(info, layerName), KisStrokeJobData::BARRIER); if (info->frames.size() > 0) { foreach (int frame, info->frames) { applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage)); applicator.applyCommand(new AddNewFrame(info, frame)); applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage)); } } else { applicator.applyCommand(new RefreshHiddenAreas(info)); applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER); applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER); } //applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER); if (cleanupNodes){ applicator.applyCommand(new CleanUpNodes(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } else { applicator.applyCommand(new InsertNode(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, true)); } applicator.end(); } void mergeMultipleLayers(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter) { mergeMultipleLayersImpl(image, mergedNodes, putAfter, false, kundo2_i18n("Merge Selected Nodes")); } void newLayerFromVisible(KisImageSP image, KisNodeSP putAfter) { KisNodeList mergedNodes; mergedNodes << image->root(); mergeMultipleLayersImpl(image, mergedNodes, putAfter, true, kundo2_i18n("New From Visible"), false, i18nc("New layer created from all the visible layers", "Visible")); } struct MergeSelectionMasks : public KisCommandUtils::AggregateCommand { MergeSelectionMasks(MergeDownInfoBaseSP info, KisNodeSP putAfter) : m_info(info), m_putAfter(putAfter){} void populateChildCommands() override { KisNodeSP parent; CleanUpNodes::findPerfectParent(m_info->allSrcNodes(), m_putAfter, parent); KisLayerSP parentLayer; do { parentLayer = qobject_cast(parent.data()); parent = parent->parent(); } while(!parentLayer && parent); KisSelectionSP selection = new KisSelection(); foreach (KisNodeSP node, m_info->allSrcNodes()) { KisMaskSP mask = dynamic_cast(node.data()); if (!mask) continue; selection->pixelSelection()->applySelection( mask->selection()->pixelSelection(), SELECTION_ADD); } KisSelectionMaskSP mergedMask = new KisSelectionMask(m_info->image); mergedMask->initSelection(parentLayer); mergedMask->setSelection(selection); m_info->dstNode = mergedMask; } private: MergeDownInfoBaseSP m_info; KisNodeSP m_putAfter; }; struct ActivateSelectionMask : public KisCommandUtils::AggregateCommand { ActivateSelectionMask(MergeDownInfoBaseSP info) : m_info(info) {} void populateChildCommands() override { KisSelectionMaskSP mergedMask = dynamic_cast(m_info->dstNode.data()); addCommand(new KisActivateSelectionMaskCommand(mergedMask, true)); } private: MergeDownInfoBaseSP m_info; }; bool tryMergeSelectionMasks(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter) { QList selectionMasks; for (auto it = mergedNodes.begin(); it != mergedNodes.end(); /*noop*/) { KisSelectionMaskSP mask = dynamic_cast(it->data()); if (!mask) { it = mergedNodes.erase(it); } else { selectionMasks.append(mask); ++it; } } if (mergedNodes.isEmpty()) return false; KisLayerSP parentLayer = qobject_cast(selectionMasks.first()->parent().data()); KIS_ASSERT_RECOVER(parentLayer) { return 0; } KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, emitSignals, kundo2_i18n("Merge Selection Masks")); MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes)); applicator.applyCommand(new MergeSelectionMasks(info, putAfter)); applicator.applyCommand(new CleanUpNodes(info, putAfter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new ActivateSelectionMask(info)); applicator.end(); return true; } void flattenLayer(KisImageSP image, KisLayerSP layer) { if (!layer->childCount() && !layer->layerStyle()) return; KisNodeList mergedNodes; mergedNodes << layer; mergeMultipleLayersImpl(image, mergedNodes, layer, true, kundo2_i18n("Flatten Layer")); } void flattenImage(KisImageSP image) { KisNodeList mergedNodes; mergedNodes << image->root(); mergeMultipleLayersImpl(image, mergedNodes, 0, true, kundo2_i18n("Flatten Image")); } KisSimpleUpdateCommand::KisSimpleUpdateCommand(KisNodeList nodes, bool finalize, KUndo2Command *parent) : FlipFlopCommand(finalize, parent), m_nodes(nodes) { } void KisSimpleUpdateCommand::end() { updateNodes(m_nodes); } void KisSimpleUpdateCommand::updateNodes(const KisNodeList &nodes) { Q_FOREACH(KisNodeSP node, nodes) { node->setDirty(node->extent()); } } void recursiveApplyNodes(KisNodeSP node, std::function func) { func(node); node = node->firstChild(); while (node) { recursiveApplyNodes(node, func); node = node->nextSibling(); } } KisNodeSP recursiveFindNode(KisNodeSP node, std::function func) { if (func(node)) { return node; } node = node->firstChild(); while (node) { KisNodeSP resultNode = recursiveFindNode(node, func); if (resultNode) { return resultNode; } node = node->nextSibling(); } return 0; } KisNodeSP findNodeByUuid(KisNodeSP root, const QUuid &uuid) { return recursiveFindNode(root, [uuid] (KisNodeSP node) { return node->uuid() == uuid; }); } } diff --git a/libs/image/kis_paint_device.cc b/libs/image/kis_paint_device.cc index c248ecf4e1..85da26f27e 100644 --- a/libs/image/kis_paint_device.cc +++ b/libs/image/kis_paint_device.cc @@ -1,2119 +1,2119 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_paint_device.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_random_sub_accessor.h" #include "kis_selection.h" #include "kis_node.h" #include "kis_datamanager.h" #include "kis_paint_device_writer.h" #include "kis_selection_component.h" #include "kis_pixel_selection.h" #include "kis_repeat_iterators_pixel.h" #include "kis_fixed_paint_device.h" #include "tiles3/kis_hline_iterator.h" #include "tiles3/kis_vline_iterator.h" #include "tiles3/kis_random_accessor.h" #include "kis_default_bounds.h" #include "kis_lod_transform.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_cache.h" #include "kis_paint_device_data.h" #include "kis_paint_device_frames_interface.h" #include "kis_transform_worker.h" #include "kis_filter_strategy.h" #include "krita_utils.h" struct KisPaintDeviceSPStaticRegistrar { KisPaintDeviceSPStaticRegistrar() { qRegisterMetaType("KisPaintDeviceSP"); } }; static KisPaintDeviceSPStaticRegistrar __registrar; struct KisPaintDevice::Private { /** * Used when the paint device is loading to ensure no lod/animation * interferes the process. */ static const KisDefaultBoundsSP transitionalDefaultBounds; public: class KisPaintDeviceStrategy; class KisPaintDeviceWrappedStrategy; Private(KisPaintDevice *paintDevice); ~Private(); KisPaintDevice *q; KisNodeWSP parent; QScopedPointer contentChannel; KisDefaultBoundsBaseSP defaultBounds; QScopedPointer basicStrategy; QScopedPointer wrappedStrategy; QScopedPointer framesInterface; bool isProjectionDevice; KisPaintDeviceStrategy* currentStrategy(); void init(const KoColorSpace *cs, const quint8 *defaultPixel); KUndo2Command* convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); bool assignProfile(const KoColorProfile * profile); inline const KoColorSpace* colorSpace() const { return currentData()->colorSpace(); } inline KisDataManagerSP dataManager() const { return currentData()->dataManager(); } inline qint32 x() const { return currentData()->x(); } inline qint32 y() const { return currentData()->y(); } inline void setX(qint32 x) { currentData()->setX(x); } inline void setY(qint32 y) { currentData()->setY(y); } inline KisPaintDeviceCache* cache() { return currentData()->cache(); } inline KisIteratorCompleteListener* cacheInvalidator() { return currentData()->cacheInvalidator(); } void cloneAllDataObjects(Private *rhs, bool copyFrames) { m_lodData.reset(); m_externalFrameData.reset(); if (!m_frames.isEmpty()) { m_frames.clear(); } if (!copyFrames) { if (m_data) { m_data->prepareClone(rhs->currentNonLodData(), true); } else { m_data = toQShared(new KisPaintDeviceData(rhs->currentNonLodData(), true)); } } else { if (m_data && !rhs->m_data) { m_data.clear(); } else if (!m_data && rhs->m_data) { m_data = toQShared(new KisPaintDeviceData(rhs->m_data.data(), true)); } else if (m_data && rhs->m_data) { m_data->prepareClone(rhs->m_data.data(), true); } if (!rhs->m_frames.isEmpty()) { FramesHash::const_iterator it = rhs->m_frames.constBegin(); FramesHash::const_iterator end = rhs->m_frames.constEnd(); for (; it != end; ++it) { DataSP data = toQShared(new KisPaintDeviceData(it.value().data(), true)); m_frames.insert(it.key(), data); } } m_nextFreeFrameId = rhs->m_nextFreeFrameId; } if (rhs->m_lodData) { m_lodData.reset(new KisPaintDeviceData(rhs->m_lodData.data(), true)); } } void prepareClone(KisPaintDeviceSP src) { prepareCloneImpl(src, src->m_d->currentData()); Q_ASSERT(fastBitBltPossible(src)); } bool fastBitBltPossible(KisPaintDeviceSP src) { return fastBitBltPossibleImpl(src->m_d->currentData()); } int currentFrameId() const { KIS_ASSERT_RECOVER(contentChannel) { return -1; } return !defaultBounds->currentLevelOfDetail() ? contentChannel->frameIdAt(defaultBounds->currentTime()) : -1; } KisDataManagerSP frameDataManager(int frameId) const { DataSP data = m_frames[frameId]; return data->dataManager(); } void invalidateFrameCache(int frameId) { DataSP data = m_frames[frameId]; return data->cache()->invalidate(); } private: typedef KisPaintDeviceData Data; typedef QSharedPointer DataSP; typedef QHash FramesHash; class FrameInsertionCommand : public KUndo2Command { public: FrameInsertionCommand(FramesHash *hash, DataSP data, int frameId, bool insert, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_hash(hash), m_data(data), m_frameId(frameId), m_insert(insert) { } void redo() override { doSwap(m_insert); } void undo() override { doSwap(!m_insert); } private: void doSwap(bool insert) { if (insert) { m_hash->insert(m_frameId, m_data); } else { DataSP deletedData = m_hash->take(m_frameId); } } private: FramesHash *m_hash; DataSP m_data; int m_frameId; bool m_insert; }; public: int getNextFrameId() { int frameId = 0; while (m_frames.contains(frameId = m_nextFreeFrameId++)); KIS_SAFE_ASSERT_RECOVER_NOOP(!m_frames.contains(frameId)); return frameId; } int createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER(parentCommand) { return -1; } DataSP data; bool initialFrame = false; if (m_frames.isEmpty()) { /** * Here we move the contents of the paint device to the * new frame and clear m_data to make the "background" for * the areas where there is no frame at all. */ data = toQShared(new Data(m_data.data(), true)); m_data->dataManager()->clear(); m_data->cache()->invalidate(); initialFrame = true; } else if (copy) { DataSP srcData = m_frames[copySrc]; data = toQShared(new Data(srcData.data(), true)); } else { DataSP srcData = m_frames.begin().value(); data = toQShared(new Data(srcData.data(), false)); } if (!initialFrame && !copy) { data->setX(offset.x()); data->setY(offset.y()); } int frameId = getNextFrameId(); KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, data, frameId, true, parentCommand); cmd->redo(); return frameId; } void deleteFrame(int frame, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER_RETURN(m_frames.contains(frame)); KIS_ASSERT_RECOVER_RETURN(parentCommand); DataSP deletedData = m_frames[frame]; KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, deletedData, frame, false, parentCommand); cmd->redo(); } QRect frameBounds(int frameId) { DataSP data = m_frames[frameId]; QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); return extent; } QPoint frameOffset(int frameId) const { DataSP data = m_frames[frameId]; return QPoint(data->x(), data->y()); } void setFrameOffset(int frameId, const QPoint &offset) { DataSP data = m_frames[frameId]; data->setX(offset.x()); data->setY(offset.y()); } const QList frameIds() const { return m_frames.keys(); } bool readFrame(QIODevice *stream, int frameId) { bool retval = false; DataSP data = m_frames[frameId]; retval = data->dataManager()->read(stream); data->cache()->invalidate(); return retval; } bool writeFrame(KisPaintDeviceWriter &store, int frameId) { DataSP data = m_frames[frameId]; return data->dataManager()->write(store); } void setFrameDefaultPixel(const KoColor &defPixel, int frameId) { DataSP data = m_frames[frameId]; KoColor color(defPixel); color.convertTo(data->colorSpace()); data->dataManager()->setDefaultPixel(color.data()); } KoColor frameDefaultPixel(int frameId) const { DataSP data = m_frames[frameId]; return KoColor(data->dataManager()->defaultPixel(), data->colorSpace()); } void fetchFrame(int frameId, KisPaintDeviceSP targetDevice); void uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrameData(DataSP srcData, DataSP dstData); struct LodDataStructImpl; LodDataStruct* createLodDataStruct(int lod); void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect); void uploadLodDataStruct(LodDataStruct *dst); QRegion regionForLodSyncing() const; void tesingFetchLodDevice(KisPaintDeviceSP targetDevice); private: QRegion syncWholeDevice(Data *srcData); inline DataSP currentFrameData() const { DataSP data; const int numberOfFrames = contentChannel->keyframeCount(); if (numberOfFrames > 1) { int frameId = contentChannel->frameIdAt(defaultBounds->currentTime()); if (frameId == -1) { data = m_data; } else { KIS_ASSERT_RECOVER(m_frames.contains(frameId)) { return m_frames.begin().value(); } data = m_frames[frameId]; } } else if (numberOfFrames == 1) { data = m_frames.begin().value(); } else { data = m_data; } return data; } inline Data* currentNonLodData() const { Data *data = m_data.data(); if (contentChannel) { data = currentFrameData().data(); } else if (isProjectionDevice && defaultBounds->externalFrameActive()) { if (!m_externalFrameData) { QMutexLocker l(&m_dataSwitchLock); if (!m_externalFrameData) { m_externalFrameData.reset(new Data(m_data.data(), false)); } } data = m_externalFrameData.data(); } return data; } inline void ensureLodDataPresent() const { if (!m_lodData) { Data *srcData = currentNonLodData(); QMutexLocker l(&m_dataSwitchLock); if (!m_lodData) { m_lodData.reset(new Data(srcData, false)); } } } inline Data* currentData() const { Data *data; if (defaultBounds->currentLevelOfDetail()) { ensureLodDataPresent(); data = m_lodData.data(); } else { data = currentNonLodData(); } return data; } void prepareCloneImpl(KisPaintDeviceSP src, Data *srcData) { currentData()->prepareClone(srcData); q->setDefaultPixel(KoColor(srcData->dataManager()->defaultPixel(), colorSpace())); q->setDefaultBounds(src->defaultBounds()); } bool fastBitBltPossibleImpl(Data *srcData) { return x() == srcData->x() && y() == srcData->y() && *colorSpace() == *srcData->colorSpace(); } QList allDataObjects() const { QList dataObjects; if (m_frames.isEmpty()) { dataObjects << m_data.data(); } dataObjects << m_lodData.data(); dataObjects << m_externalFrameData.data(); Q_FOREACH (DataSP value, m_frames.values()) { dataObjects << value.data(); } return dataObjects; } void transferFromData(Data *data, KisPaintDeviceSP targetDevice); struct Q_DECL_HIDDEN StrategyPolicy; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialConstIterator; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialIterator; private: friend class KisPaintDeviceFramesInterface; private: DataSP m_data; mutable QScopedPointer m_lodData; mutable QScopedPointer m_externalFrameData; mutable QMutex m_dataSwitchLock; FramesHash m_frames; int m_nextFreeFrameId; }; const KisDefaultBoundsSP KisPaintDevice::Private::transitionalDefaultBounds = new KisDefaultBounds(); #include "kis_paint_device_strategies.h" KisPaintDevice::Private::Private(KisPaintDevice *paintDevice) : q(paintDevice), basicStrategy(new KisPaintDeviceStrategy(paintDevice, this)), isProjectionDevice(false), m_data(new Data(paintDevice)), m_nextFreeFrameId(0) { } KisPaintDevice::Private::~Private() { m_frames.clear(); } KisPaintDevice::Private::KisPaintDeviceStrategy* KisPaintDevice::Private::currentStrategy() { if (!defaultBounds->wrapAroundMode()) { return basicStrategy.data(); } QRect wrapRect = defaultBounds->bounds(); if (!wrappedStrategy || wrappedStrategy->wrapRect() != wrapRect) { wrappedStrategy.reset(new KisPaintDeviceWrappedStrategy(wrapRect, q, this)); } return wrappedStrategy.data(); } struct KisPaintDevice::Private::StrategyPolicy { StrategyPolicy(KisPaintDevice::Private::KisPaintDeviceStrategy *strategy, KisDataManager *dataManager, qint32 offsetX, qint32 offsetY) : m_strategy(strategy), m_dataManager(dataManager), m_offsetX(offsetX), m_offsetY(offsetY) { } KisHLineConstIteratorSP createConstIterator(const QRect &rect) { return m_strategy->createHLineConstIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } KisHLineIteratorSP createIterator(const QRect &rect) { return m_strategy->createHLineIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } int pixelSize() const { return m_dataManager->pixelSize(); } KisPaintDeviceStrategy *m_strategy; KisDataManager *m_dataManager; int m_offsetX; int m_offsetY; }; struct KisPaintDevice::Private::LodDataStructImpl : public KisPaintDevice::LodDataStruct { LodDataStructImpl(Data *_lodData) : lodData(_lodData) {} QScopedPointer lodData; }; QRegion KisPaintDevice::Private::regionForLodSyncing() const { Data *srcData = currentNonLodData(); return srcData->dataManager()->region().translated(srcData->x(), srcData->y()); } KisPaintDevice::LodDataStruct* KisPaintDevice::Private::createLodDataStruct(int newLod) { Data *srcData = currentNonLodData(); Data *lodData = new Data(srcData, false); LodDataStruct *lodStruct = new LodDataStructImpl(lodData); int expectedX = KisLodTransform::coordToLodCoord(srcData->x(), newLod); int expectedY = KisLodTransform::coordToLodCoord(srcData->y(), newLod); /** * We compare color spaces as pure pointers, because they must be * exactly the same, since they come from the common source. */ if (lodData->levelOfDetail() != newLod || lodData->colorSpace() != srcData->colorSpace() || lodData->x() != expectedX || lodData->y() != expectedY) { lodData->prepareClone(srcData); lodData->setLevelOfDetail(newLod); lodData->setX(expectedX); lodData->setY(expectedY); // FIXME: different kind of synchronization } //QRegion dirtyRegion = syncWholeDevice(srcData); lodData->cache()->invalidate(); return lodStruct; } void KisPaintDevice::Private::updateLodDataStruct(LodDataStruct *_dst, const QRect &originalRect) { LodDataStructImpl *dst = dynamic_cast(_dst); KIS_SAFE_ASSERT_RECOVER_RETURN(dst); Data *lodData = dst->lodData.data(); Data *srcData = currentNonLodData(); const int lod = lodData->levelOfDetail(); const int srcStepSize = 1 << lod; KIS_ASSERT_RECOVER_RETURN(lod > 0); const QRect srcRect = KisLodTransform::alignedRect(originalRect, lod); const QRect dstRect = KisLodTransform::scaledRect(srcRect, lod); if (!srcRect.isValid() || !dstRect.isValid()) return; KIS_ASSERT_RECOVER_NOOP(srcRect.width() / srcStepSize == dstRect.width()); const int pixelSize = srcData->dataManager()->pixelSize(); int rowsAccumulated = 0; int columnsAccumulated = 0; KoMixColorsOp *mixOp = colorSpace()->mixColorsOp(); QScopedArrayPointer blendData(new quint8[srcStepSize * srcRect.width() * pixelSize]); quint8 *blendDataPtr = blendData.data(); int blendDataOffset = 0; const int srcCellSize = srcStepSize * srcStepSize; const int srcCellStride = srcCellSize * pixelSize; const int srcStepStride = srcStepSize * pixelSize; const int srcColumnStride = (srcStepSize - 1) * srcStepStride; QScopedArrayPointer weights(new qint16[srcCellSize]); { const qint16 averageWeight = qCeil(255.0 / srcCellSize); const qint16 extraWeight = averageWeight * srcCellSize - 255; KIS_ASSERT_RECOVER_NOOP(extraWeight == 1); for (int i = 0; i < srcCellSize - 1; i++) { weights[i] = averageWeight; } weights[srcCellSize - 1] = averageWeight - extraWeight; } InternalSequentialConstIterator srcIntIt(StrategyPolicy(currentStrategy(), srcData->dataManager().data(), srcData->x(), srcData->y()), srcRect); InternalSequentialIterator dstIntIt(StrategyPolicy(currentStrategy(), lodData->dataManager().data(), lodData->x(), lodData->y()), dstRect); int rowsRemaining = srcRect.height(); while (rowsRemaining > 0) { int colsRemaining = srcRect.width(); while (colsRemaining > 0) { memcpy(blendDataPtr, srcIntIt.rawDataConst(), pixelSize); blendDataPtr += pixelSize; columnsAccumulated++; if (columnsAccumulated >= srcStepSize) { blendDataPtr += srcColumnStride; columnsAccumulated = 0; } srcIntIt.nextPixel(); colsRemaining--; } rowsAccumulated++; if (rowsAccumulated >= srcStepSize) { // blend and write the final data blendDataPtr = blendData.data(); for (int i = 0; i < dstRect.width(); i++) { mixOp->mixColors(blendDataPtr, weights.data(), srcCellSize, dstIntIt.rawData()); blendDataPtr += srcCellStride; dstIntIt.nextPixel(); } // reset counters rowsAccumulated = 0; blendDataPtr = blendData.data(); blendDataOffset = 0; } else { blendDataOffset += srcStepStride; blendDataPtr = blendData.data() + blendDataOffset; } rowsRemaining--; } } void KisPaintDevice::Private::uploadLodDataStruct(LodDataStruct *_dst) { LodDataStructImpl *dst = dynamic_cast(_dst); KIS_SAFE_ASSERT_RECOVER_RETURN(dst); KIS_SAFE_ASSERT_RECOVER_RETURN( dst->lodData->levelOfDetail() == defaultBounds->currentLevelOfDetail()); ensureLodDataPresent(); m_lodData->prepareClone(dst->lodData.data()); m_lodData->dataManager()->bitBltRough(dst->lodData->dataManager(), dst->lodData->dataManager()->extent()); } void KisPaintDevice::Private::transferFromData(Data *data, KisPaintDeviceSP targetDevice) { QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); targetDevice->m_d->prepareCloneImpl(q, data); targetDevice->m_d->currentStrategy()->fastBitBltRough(data->dataManager(), extent); } void KisPaintDevice::Private::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { DataSP data = m_frames[frameId]; transferFromData(data.data(), targetDevice); } void KisPaintDevice::Private::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_frames[srcFrameId]; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_data; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrameData(DataSP srcData, DataSP dstData) { if (srcData->colorSpace() != dstData->colorSpace() && *srcData->colorSpace() != *dstData->colorSpace()) { KUndo2Command tempCommand; srcData = toQShared(new Data(srcData.data(), true)); srcData->convertDataColorSpace(dstData->colorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags(), &tempCommand); } dstData->dataManager()->clear(); dstData->cache()->invalidate(); const QRect rect = srcData->dataManager()->extent(); dstData->dataManager()->bitBltRough(srcData->dataManager(), rect); dstData->setX(srcData->x()); dstData->setY(srcData->y()); } void KisPaintDevice::Private::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { Data *data = m_lodData.data(); Q_ASSERT(data); transferFromData(data, targetDevice); } KUndo2Command* KisPaintDevice::Private::convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { class DeviceChangeColorSpaceCommand : public KUndo2Command { public: DeviceChangeColorSpaceCommand(KisPaintDeviceSP device) : m_firstRun(true), m_device(device) { } void emitNotifications() { m_device->emitColorSpaceChanged(); m_device->setDirty(); } void redo() override { KUndo2Command::redo(); if (!m_firstRun) { m_firstRun = false; return; } emitNotifications(); } void undo() override { KUndo2Command::undo(); emitNotifications(); } private: bool m_firstRun; KisPaintDeviceSP m_device; }; KUndo2Command *parentCommand = new DeviceChangeColorSpaceCommand(q); QList dataObjects = allDataObjects();; Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->convertDataColorSpace(dstColorSpace, renderingIntent, conversionFlags, parentCommand); } if (!parentCommand->childCount()) { delete parentCommand; parentCommand = 0; } else { q->emitColorSpaceChanged(); } return parentCommand; } bool KisPaintDevice::Private::assignProfile(const KoColorProfile * profile) { if (!profile) return false; const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); if (!dstColorSpace) return false; QList dataObjects = allDataObjects();; Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->assignColorSpace(dstColorSpace); } q->emitProfileChanged(); // no undo information is provided here return true; } void KisPaintDevice::Private::init(const KoColorSpace *cs, const quint8 *defaultPixel) { QList dataObjects = allDataObjects();; Q_FOREACH (Data *data, dataObjects) { if (!data) continue; KisDataManagerSP dataManager = new KisDataManager(cs->pixelSize(), defaultPixel); data->init(cs, dataManager); } } KisPaintDevice::KisPaintDevice(const KoColorSpace * colorSpace, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, new KisDefaultBounds(), 0, name); } KisPaintDevice::KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, defaultBounds, parent, name); } void KisPaintDevice::init(const KoColorSpace *colorSpace, KisDefaultBoundsBaseSP defaultBounds, KisNodeWSP parent, const QString& name) { Q_ASSERT(colorSpace); setObjectName(name); // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; if (!defaultBounds) { // Reuse transitionalDefaultBounds here. Change if you change // semantics of transitionalDefaultBounds defaultBounds = m_d->transitionalDefaultBounds; } QScopedArrayPointer defaultPixel(new quint8[colorSpace->pixelSize()]); colorSpace->fromQColor(Qt::transparent, defaultPixel.data()); m_d->init(colorSpace, defaultPixel.data()); Q_ASSERT(m_d->colorSpace()); setDefaultBounds(defaultBounds); setParentNode(parent); } KisPaintDevice::KisPaintDevice(const KisPaintDevice& rhs, bool copyFrames, KisNode *newParentNode) : QObject() , KisShared() , m_d(new Private(this)) { if (this != &rhs) { // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; // copy data objects with or without frames m_d->cloneAllDataObjects(rhs.m_d, copyFrames); if (copyFrames) { KIS_ASSERT_RECOVER_RETURN(rhs.m_d->framesInterface); KIS_ASSERT_RECOVER_RETURN(rhs.m_d->contentChannel); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); m_d->contentChannel.reset(new KisRasterKeyframeChannel(*rhs.m_d->contentChannel.data(), newParentNode, this)); } setDefaultBounds(rhs.m_d->defaultBounds); setParentNode(0); } } KisPaintDevice::~KisPaintDevice() { delete m_d; } void KisPaintDevice::setProjectionDevice(bool value) { m_d->isProjectionDevice = value; } void KisPaintDevice::prepareClone(KisPaintDeviceSP src) { m_d->prepareClone(src); Q_ASSERT(fastBitBltPossible(src)); } void KisPaintDevice::makeCloneFrom(KisPaintDeviceSP src, const QRect &rect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = rect & src->extent(); fastBitBlt(src, optimizedRect); } void KisPaintDevice::makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = minimalRect & src->extent(); fastBitBltRough(src, optimizedRect); } void KisPaintDevice::setDirty() { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(); } void KisPaintDevice::setDirty(const QRect & rc) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rc); } void KisPaintDevice::setDirty(const QRegion & region) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(region); } void KisPaintDevice::setDirty(const QVector rects) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rects); } void KisPaintDevice::requestTimeSwitch(int time) { if (m_d->parent.isValid()) { m_d->parent->requestTimeSwitch(time); } } int KisPaintDevice::sequenceNumber() const { return m_d->cache()->sequenceNumber(); } void KisPaintDevice::setParentNode(KisNodeWSP parent) { m_d->parent = parent; } // for testing purposes only KisNodeWSP KisPaintDevice::parentNode() const { return m_d->parent; } void KisPaintDevice::setDefaultBounds(KisDefaultBoundsBaseSP defaultBounds) { m_d->defaultBounds = defaultBounds; m_d->cache()->invalidate(); } KisDefaultBoundsBaseSP KisPaintDevice::defaultBounds() const { return m_d->defaultBounds; } void KisPaintDevice::moveTo(const QPoint &pt) { m_d->currentStrategy()->move(pt); m_d->cache()->invalidate(); } QPoint KisPaintDevice::offset() const { return QPoint(x(), y()); } void KisPaintDevice::moveTo(qint32 x, qint32 y) { moveTo(QPoint(x, y)); } void KisPaintDevice::setX(qint32 x) { moveTo(QPoint(x, m_d->y())); } void KisPaintDevice::setY(qint32 y) { moveTo(QPoint(m_d->x(), y)); } qint32 KisPaintDevice::x() const { return m_d->x(); } qint32 KisPaintDevice::y() const { return m_d->y(); } void KisPaintDevice::extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const { QRect rc = extent(); x = rc.x(); y = rc.y(); w = rc.width(); h = rc.height(); } QRect KisPaintDevice::extent() const { return m_d->currentStrategy()->extent(); } QRegion KisPaintDevice::region() const { return m_d->currentStrategy()->region(); } QRect KisPaintDevice::nonDefaultPixelArea() const { return m_d->cache()->nonDefaultPixelArea(); } QRect KisPaintDevice::exactBounds() const { return m_d->cache()->exactBounds(); } QRect KisPaintDevice::exactBoundsAmortized() const { return m_d->cache()->exactBoundsAmortized(); } namespace Impl { struct CheckFullyTransparent { CheckFullyTransparent(const KoColorSpace *colorSpace) : m_colorSpace(colorSpace) { } bool isPixelEmpty(const quint8 *pixelData) { return m_colorSpace->opacityU8(pixelData) == OPACITY_TRANSPARENT_U8; } private: const KoColorSpace *m_colorSpace; }; struct CheckNonDefault { CheckNonDefault(int pixelSize, const quint8 *defaultPixel) : m_pixelSize(pixelSize), m_defaultPixel(defaultPixel) { } bool isPixelEmpty(const quint8 *pixelData) { return memcmp(m_defaultPixel, pixelData, m_pixelSize) == 0; } private: int m_pixelSize; const quint8 *m_defaultPixel; }; template QRect calculateExactBoundsImpl(const KisPaintDevice *device, const QRect &startRect, const QRect &endRect, ComparePixelOp compareOp) { if (startRect == endRect) return startRect; // Solution n°2 int x, y, w, h; int boundLeft, boundTop, boundRight, boundBottom; int endDirN, endDirE, endDirS, endDirW; startRect.getRect(&x, &y, &w, &h); if (endRect.isEmpty()) { endDirS = startRect.bottom(); endDirN = startRect.top(); endDirE = startRect.right(); endDirW = startRect.left(); startRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } else { endDirS = endRect.top() - 1; endDirN = endRect.bottom() + 1; endDirE = endRect.left() - 1; endDirW = endRect.right() + 1; endRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } // XXX: a small optimization is possible by using H/V line iterators in the first // and third cases, at the cost of making the code a bit more complex KisRandomConstAccessorSP accessor = device->createRandomConstAccessorNG(x, y); bool found = false; { for (qint32 y2 = y; y2 <= endDirS; ++y2) { for (qint32 x2 = x; x2 < x + w || found; ++ x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundTop = y2; found = true; break; } } if (found) break; } } /** * If the first pass hasn't found any opaque pixel, there is no * reason to check that 3 more times. They will not appear in the * meantime. Just return an empty bounding rect. */ if (!found && endRect.isEmpty()) { return QRect(); } found = false; for (qint32 y2 = y + h - 1; y2 >= endDirN ; --y2) { for (qint32 x2 = x + w - 1; x2 >= x || found; --x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundBottom = y2; found = true; break; } } if (found) break; } found = false; { for (qint32 x2 = x; x2 <= endDirE ; ++x2) { for (qint32 y2 = y; y2 < y + h || found; ++y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundLeft = x2; found = true; break; } } if (found) break; } } found = false; // Look for right edge ) { for (qint32 x2 = x + w - 1; x2 >= endDirW; --x2) { for (qint32 y2 = y + h - 1; y2 >= y || found; --y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundRight = x2; found = true; break; } } if (found) break; } } return QRect(boundLeft, boundTop, boundRight - boundLeft + 1, boundBottom - boundTop + 1); } } QRect KisPaintDevice::calculateExactBounds(bool nonDefaultOnly) const { QRect startRect = extent(); QRect endRect; quint8 defaultOpacity = defaultPixel().opacityU8(); if (defaultOpacity != OPACITY_TRANSPARENT_U8) { if (!nonDefaultOnly) { /** * We will calculate exact bounds only outside of the * image bounds, and that'll be nondefault area only. */ endRect = defaultBounds()->bounds(); nonDefaultOnly = true; } else { startRect = region().boundingRect(); } } if (nonDefaultOnly) { const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } else { Impl::CheckFullyTransparent compareOp(m_d->colorSpace()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } return endRect; } QRegion KisPaintDevice::regionExact() const { QRegion resultRegion; QVector rects = region().rects(); const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); Q_FOREACH (const QRect &rc1, rects) { const int patchSize = 64; QVector smallerRects = KritaUtils::splitRectIntoPatches(rc1, QSize(patchSize, patchSize)); Q_FOREACH (const QRect &rc2, smallerRects) { const QRect result = Impl::calculateExactBoundsImpl(this, rc2, QRect(), compareOp); if (!result.isEmpty()) { resultRegion += result; } } } return resultRegion; } void KisPaintDevice::crop(qint32 x, qint32 y, qint32 w, qint32 h) { crop(QRect(x, y, w, h)); } void KisPaintDevice::crop(const QRect &rect) { m_d->currentStrategy()->crop(rect); } void KisPaintDevice::purgeDefaultPixels() { KisDataManagerSP dm = m_d->dataManager(); dm->purge(dm->extent()); } void KisPaintDevice::setDefaultPixel(const KoColor &defPixel) { KoColor color(defPixel); color.convertTo(colorSpace()); m_d->dataManager()->setDefaultPixel(color.data()); m_d->cache()->invalidate(); } KoColor KisPaintDevice::defaultPixel() const { return KoColor(m_d->dataManager()->defaultPixel(), colorSpace()); } void KisPaintDevice::clear() { m_d->dataManager()->clear(); m_d->cache()->invalidate(); } void KisPaintDevice::clear(const QRect & rc) { m_d->currentStrategy()->clear(rc); } void KisPaintDevice::fill(const QRect & rc, const KoColor &color) { Q_ASSERT(*color.colorSpace() == *colorSpace()); m_d->currentStrategy()->fill(rc, color.data()); } void KisPaintDevice::fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel) { m_d->currentStrategy()->fill(QRect(x, y, w, h), fillPixel); } bool KisPaintDevice::write(KisPaintDeviceWriter &store) { return m_d->dataManager()->write(store); } bool KisPaintDevice::read(QIODevice *stream) { bool retval; retval = m_d->dataManager()->read(stream); m_d->cache()->invalidate(); return retval; } void KisPaintDevice::emitColorSpaceChanged() { emit colorSpaceChanged(m_d->colorSpace()); } void KisPaintDevice::emitProfileChanged() { emit profileChanged(m_d->colorSpace()->profile()); } KUndo2Command* KisPaintDevice::convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { KUndo2Command *command = m_d->convertColorSpace(dstColorSpace, renderingIntent, conversionFlags); return command; } bool KisPaintDevice::setProfile(const KoColorProfile * profile) { return m_d->assignProfile(profile); } KisDataManagerSP KisPaintDevice::dataManager() const { return m_d->dataManager(); } void KisPaintDevice::convertFromQImage(const QImage& _image, const KoColorProfile *profile, qint32 offsetX, qint32 offsetY) { QImage image = _image; if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } // Don't convert if not no profile is given and both paint dev and qimage are rgba. if (!profile && colorSpace()->id() == "RGBA") { writeBytes(image.constBits(), offsetX, offsetY, image.width(), image.height()); } else { try { quint8 * dstData = new quint8[image.width() * image.height() * pixelSize()]; KoColorSpaceRegistry::instance() ->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), profile) ->convertPixelsTo(image.constBits(), dstData, colorSpace(), image.width() * image.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); writeBytes(dstData, offsetX, offsetY, image.width(), image.height()); delete[] dstData; } catch (std::bad_alloc) { warnKrita << "KisPaintDevice::convertFromQImage: Could not allocate" << image.width() * image.height() * pixelSize() << "bytes"; return; } } m_d->cache()->invalidate(); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { qint32 x1; qint32 y1; qint32 w; qint32 h; QRect rc = exactBounds(); x1 = rc.x(); y1 = rc.y(); w = rc.width(); h = rc.height(); return convertToQImage(dstProfile, x1, y1, w, h, renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, const QRect &rc, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { return convertToQImage(dstProfile, rc.x(), rc.y(), rc.width(), rc.height(), renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, qint32 x1, qint32 y1, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (w < 0) return QImage(); if (h < 0) return QImage(); quint8 *data = 0; try { data = new quint8 [w * h * pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPaintDevice::convertToQImage std::bad_alloc for " << w << " * " << h << " * " << pixelSize(); //delete[] data; // data is not allocated, so don't free it return QImage(); } Q_CHECK_PTR(data); // XXX: Is this really faster than converting line by line and building the QImage directly? // This copies potentially a lot of data. readBytes(data, x1, y1, w, h); QImage image = colorSpace()->convertToQImage(data, w, h, dstProfile, renderingIntent, conversionFlags); delete[] data; return image; } inline bool moveBy(KisSequentialConstIterator& iter, int numPixels) { int pos = 0; while (pos < numPixels) { int step = std::min(iter.nConseqPixels(), numPixels - pos); if (!iter.nextPixels(step)) return false; pos += step; } return true; } static KisPaintDeviceSP createThumbnailDeviceInternal(const KisPaintDevice* srcDev, qint32 srcX0, qint32 srcY0, qint32 srcWidth, qint32 srcHeight, qint32 w, qint32 h, QRect outputRect) { KisPaintDeviceSP thumbnail = new KisPaintDevice(srcDev->colorSpace()); qint32 pixelSize = srcDev->pixelSize(); KisRandomConstAccessorSP srcIter = srcDev->createRandomConstAccessorNG(0, 0); KisRandomAccessorSP dstIter = thumbnail->createRandomAccessorNG(0, 0); for (qint32 y = outputRect.y(); y < outputRect.y() + outputRect.height(); ++y) { qint32 iY = srcY0 + (y * srcHeight) / h; for (qint32 x = outputRect.x(); x < outputRect.x() + outputRect.width(); ++x) { qint32 iX = srcX0 + (x * srcWidth) / w; srcIter->moveTo(iX, iY); dstIter->moveTo(x, y); memcpy(dstIter->rawData(), srcIter->rawDataConst(), pixelSize); } } return thumbnail; } QSize fixThumbnailSize(QSize size) { if (!size.width() && size.height()) { size.setWidth(1); } if (size.width() && !size.height()) { size.setHeight(1); } return size; } KisPaintDeviceSP KisPaintDevice::createThumbnailDevice(qint32 w, qint32 h, QRect rect, QRect outputRect) const { QSize thumbnailSize(w, h); QRect imageRect = rect.isValid() ? rect : extent(); if ((thumbnailSize.width() > imageRect.width()) || (thumbnailSize.height() > imageRect.height())) { thumbnailSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailSize = fixThumbnailSize(thumbnailSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } int srcWidth, srcHeight; int srcX0, srcY0; imageRect.getRect(&srcX0, &srcY0, &srcWidth, &srcHeight); if (!outputRect.isValid()) { outputRect = QRect(0, 0, w, h); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailSize.width(), thumbnailSize.height(), outputRect); return thumbnail; } KisPaintDeviceSP KisPaintDevice::createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect, QRect outputTileRect) const { QSize thumbnailSize(w, h); qreal oversampleAdjusted = qMax(oversample, 1.); QSize thumbnailOversampledSize = oversampleAdjusted * thumbnailSize; QRect outputRect; QRect imageRect = rect.isValid() ? rect : extent(); qint32 hstart = thumbnailOversampledSize.height(); if ((thumbnailOversampledSize.width() > imageRect.width()) || (thumbnailOversampledSize.height() > imageRect.height())) { thumbnailOversampledSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailOversampledSize = fixThumbnailSize(thumbnailOversampledSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty() || thumbnailOversampledSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } oversampleAdjusted *= (hstart > 0) ? ((qreal)thumbnailOversampledSize.height() / hstart) : 1.; //readjusting oversample ratio, given that we had to adjust thumbnail size outputRect = QRect(0, 0, thumbnailOversampledSize.width(), thumbnailOversampledSize.height()); if (outputTileRect.isValid()) { //compensating output rectangle for oversampling outputTileRect = QRect(oversampleAdjusted * outputTileRect.topLeft(), oversampleAdjusted * outputTileRect.bottomRight()); outputRect = outputRect.intersected(outputTileRect); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailOversampledSize.width(), thumbnailOversampledSize.height(), outputRect); if (oversample != 1. && oversampleAdjusted != 1.) { KoDummyUpdater updater; KisTransformWorker worker(thumbnail, 1 / oversampleAdjusted, 1 / oversampleAdjusted, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, &updater, KisFilterStrategyRegistry::instance()->value("Bilinear")); worker.run(); } return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, QRect rect, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); KisPaintDeviceSP dev = createThumbnailDeviceOversampled(size.width(), size.height(), oversample, rect); QImage thumbnail = dev->convertToQImage(KoColorSpaceRegistry::instance()->rgb8()->profile(), 0, 0, w, h, renderingIntent, conversionFlags); return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); return m_d->cache()->createThumbnail(size.width(), size.height(), oversample, renderingIntent, conversionFlags); } KisHLineIteratorSP KisPaintDevice::createHLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createHLineIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisHLineConstIteratorSP KisPaintDevice::createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisVLineIteratorSP KisPaintDevice::createVLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createVLineIteratorNG(x, y, w); } KisVLineConstIteratorSP KisPaintDevice::createVLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createVLineConstIteratorNG(x, y, w); } KisRepeatHLineConstIteratorSP KisPaintDevice::createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const { return new KisRepeatHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRepeatVLineConstIteratorSP KisPaintDevice::createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const { return new KisRepeatVLineConstIteratorNG(m_d->dataManager().data(), x, y, h, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRandomAccessorSP KisPaintDevice::createRandomAccessorNG(qint32 x, qint32 y) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createRandomAccessorNG(x, y); } KisRandomConstAccessorSP KisPaintDevice::createRandomConstAccessorNG(qint32 x, qint32 y) const { return m_d->currentStrategy()->createRandomConstAccessorNG(x, y); } KisRandomSubAccessorSP KisPaintDevice::createRandomSubAccessor() const { KisPaintDevice* pd = const_cast(this); return new KisRandomSubAccessor(pd); } void KisPaintDevice::clearSelection(KisSelectionSP selection) { const KoColorSpace *colorSpace = m_d->colorSpace(); QRect r = selection->selectedExactRect() & m_d->defaultBounds->bounds(); if (r.isValid()) { KisHLineIteratorSP devIt = createHLineIteratorNG(r.x(), r.y(), r.width()); KisHLineConstIteratorSP selectionIt = selection->projection()->createHLineConstIteratorNG(r.x(), r.y(), r.width()); const KoColor defaultPixel = this->defaultPixel(); bool transparentDefault = (defaultPixel.opacityU8() == OPACITY_TRANSPARENT_U8); for (qint32 y = 0; y < r.height(); y++) { do { // XXX: Optimize by using stretches colorSpace->applyInverseAlphaU8Mask(devIt->rawData(), selectionIt->rawDataConst(), 1); if (transparentDefault && colorSpace->opacityU8(devIt->rawData()) == OPACITY_TRANSPARENT_U8) { memcpy(devIt->rawData(), defaultPixel.data(), colorSpace->pixelSize()); } } while (devIt->nextPixel() && selectionIt->nextPixel()); devIt->nextRow(); selectionIt->nextRow(); } m_d->dataManager()->purge(r.translated(-m_d->x(), -m_d->y())); setDirty(r); } } bool KisPaintDevice::pixel(qint32 x, qint32 y, QColor *c) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; colorSpace()->toQColor(pix, c); return true; } bool KisPaintDevice::pixel(qint32 x, qint32 y, KoColor * kc) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; kc->setColor(pix, m_d->colorSpace()); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const QColor& c) { KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); colorSpace()->fromQColor(c, iter->rawData()); m_d->cache()->invalidate(); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const KoColor& kc) { const quint8 * pix; KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); if (kc.colorSpace() != m_d->colorSpace()) { KoColor kc2(kc, m_d->colorSpace()); pix = kc2.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } else { pix = kc.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } m_d->cache()->invalidate(); return true; } bool KisPaintDevice::fastBitBltPossible(KisPaintDeviceSP src) { return m_d->fastBitBltPossible(src); } void KisPaintDevice::fastBitBlt(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBlt(src, rect); } void KisPaintDevice::fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltOldData(src, rect); } void KisPaintDevice::fastBitBltRough(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRough(src, rect); } void KisPaintDevice::fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRoughOldData(src, rect); } void KisPaintDevice::readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const { readBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::readBytes(quint8 *data, const QRect &rect) const { m_d->currentStrategy()->readBytes(data, rect); } void KisPaintDevice::writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h) { writeBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::writeBytes(const quint8 *data, const QRect &rect) { m_d->currentStrategy()->writeBytes(data, rect); } QVector KisPaintDevice::readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const { return m_d->currentStrategy()->readPlanarBytes(x, y, w, h); } void KisPaintDevice::writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h) { m_d->currentStrategy()->writePlanarBytes(planes, x, y, w, h); } quint32 KisPaintDevice::pixelSize() const { quint32 _pixelSize = m_d->colorSpace()->pixelSize(); Q_ASSERT(_pixelSize > 0); return _pixelSize; } quint32 KisPaintDevice::channelCount() const { quint32 _channelCount = m_d->colorSpace()->channelCount(); Q_ASSERT(_channelCount > 0); return _channelCount; } KisRasterKeyframeChannel *KisPaintDevice::createKeyframeChannel(const KoID &id) { Q_ASSERT(!m_d->framesInterface); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); Q_ASSERT(!m_d->contentChannel); m_d->contentChannel.reset(new KisRasterKeyframeChannel(id, this, m_d->defaultBounds)); // Raster channels always have at least one frame (representing a static image) KUndo2Command tempParentCommand; m_d->contentChannel->addKeyframe(0, &tempParentCommand); return m_d->contentChannel.data(); } KisRasterKeyframeChannel* KisPaintDevice::keyframeChannel() const { Q_ASSERT(m_d->contentChannel); return m_d->contentChannel.data(); } const KoColorSpace* KisPaintDevice::colorSpace() const { Q_ASSERT(m_d->colorSpace() != 0); return m_d->colorSpace(); } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice() const { KisPaintDeviceSP device = new KisPaintDevice(compositionSourceColorSpace()); device->setDefaultBounds(defaultBounds()); return device; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const { KisPaintDeviceSP clone = new KisPaintDevice(*cloneSource); clone->setDefaultBounds(defaultBounds()); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const { KisPaintDeviceSP clone = new KisPaintDevice(colorSpace()); clone->setDefaultBounds(defaultBounds()); clone->makeCloneFromRough(cloneSource, roughRect); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisFixedPaintDeviceSP KisPaintDevice::createCompositionSourceDeviceFixed() const { return new KisFixedPaintDevice(compositionSourceColorSpace()); } const KoColorSpace* KisPaintDevice::compositionSourceColorSpace() const { return colorSpace(); } QVector KisPaintDevice::channelSizes() const { QVector sizes; QList channels = colorSpace()->channels(); - qSort(channels); + std::sort(channels.begin(), channels.end()); Q_FOREACH (KoChannelInfo * channelInfo, channels) { sizes.append(channelInfo->size()); } return sizes; } KisPaintDevice::MemoryReleaseObject::~MemoryReleaseObject() { KisDataManager::releaseInternalPools(); } KisPaintDevice::MemoryReleaseObject* KisPaintDevice::createMemoryReleaseObject() { return new MemoryReleaseObject(); } KisPaintDevice::LodDataStruct::~LodDataStruct() { } QRegion KisPaintDevice::regionForLodSyncing() const { return m_d->regionForLodSyncing(); } KisPaintDevice::LodDataStruct* KisPaintDevice::createLodDataStruct(int lod) { return m_d->createLodDataStruct(lod); } void KisPaintDevice::updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect) { m_d->updateLodDataStruct(dst, srcRect); } void KisPaintDevice::uploadLodDataStruct(LodDataStruct *dst) { m_d->uploadLodDataStruct(dst); } KisPaintDeviceFramesInterface* KisPaintDevice::framesInterface() { return m_d->framesInterface.data(); } /******************************************************************/ /* KisPaintDeviceFramesInterface */ /******************************************************************/ KisPaintDeviceFramesInterface::KisPaintDeviceFramesInterface(KisPaintDevice *parentDevice) : q(parentDevice) { } QList KisPaintDeviceFramesInterface::frames() { return q->m_d->frameIds(); } int KisPaintDeviceFramesInterface::createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { return q->m_d->createFrame(copy, copySrc, offset, parentCommand); } void KisPaintDeviceFramesInterface::deleteFrame(int frame, KUndo2Command *parentCommand) { return q->m_d->deleteFrame(frame, parentCommand); } void KisPaintDeviceFramesInterface::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { q->m_d->fetchFrame(frameId, targetDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(srcFrameId, dstFrameId, srcDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(dstFrameId, srcDevice); } QRect KisPaintDeviceFramesInterface::frameBounds(int frameId) { return q->m_d->frameBounds(frameId); } QPoint KisPaintDeviceFramesInterface::frameOffset(int frameId) const { return q->m_d->frameOffset(frameId); } void KisPaintDeviceFramesInterface::setFrameDefaultPixel(const KoColor &defPixel, int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); q->m_d->setFrameDefaultPixel(defPixel, frameId); } KoColor KisPaintDeviceFramesInterface::frameDefaultPixel(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return KoColor(Qt::red, q->m_d->colorSpace()); } return q->m_d->frameDefaultPixel(frameId); } bool KisPaintDeviceFramesInterface::writeFrame(KisPaintDeviceWriter &store, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->writeFrame(store, frameId); } bool KisPaintDeviceFramesInterface::readFrame(QIODevice *stream, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->readFrame(stream, frameId); } int KisPaintDeviceFramesInterface::currentFrameId() const { return q->m_d->currentFrameId(); } KisDataManagerSP KisPaintDeviceFramesInterface::frameDataManager(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return q->m_d->dataManager(); } return q->m_d->frameDataManager(frameId); } void KisPaintDeviceFramesInterface::invalidateFrameCache(int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->invalidateFrameCache(frameId); } void KisPaintDeviceFramesInterface::setFrameOffset(int frameId, const QPoint &offset) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->setFrameOffset(frameId, offset); } KisPaintDeviceFramesInterface::TestingDataObjects KisPaintDeviceFramesInterface::testingGetDataObjects() const { TestingDataObjects objects; objects.m_data = q->m_d->m_data.data(); objects.m_lodData = q->m_d->m_lodData.data(); objects.m_externalFrameData = q->m_d->m_externalFrameData.data(); typedef KisPaintDevice::Private::FramesHash FramesHash; FramesHash::const_iterator it = q->m_d->m_frames.constBegin(); FramesHash::const_iterator end = q->m_d->m_frames.constEnd(); for (; it != end; ++it) { objects.m_frames.insert(it.key(), it.value().data()); } objects.m_currentData = q->m_d->currentData(); return objects; } QList KisPaintDeviceFramesInterface::testingGetDataObjectsList() const { return q->m_d->allDataObjects(); } void KisPaintDevice::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { m_d->tesingFetchLodDevice(targetDevice); } diff --git a/libs/image/kis_painter.cc b/libs/image/kis_painter.cc index 0343f393fd..5ade111ff6 100644 --- a/libs/image/kis_painter.cc +++ b/libs/image/kis_painter.cc @@ -1,2915 +1,2915 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2008-2010 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara Toloza * Copyright (c) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_painter.h" #include #include #include #include #include #ifndef Q_OS_WIN #include #endif #include #include #include #include #include #include #include #include #include #include #include "kis_image.h" #include "filter/kis_filter.h" #include "kis_layer.h" #include "kis_paint_device.h" #include "kis_fixed_paint_device.h" #include "kis_transaction.h" #include "kis_vec.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "kis_paintop.h" #include "kis_selection.h" #include "kis_fill_painter.h" #include "filter/kis_filter_configuration.h" #include "kis_pixel_selection.h" #include #include "kis_paintop_registry.h" #include "kis_perspective_math.h" #include "tiles3/kis_random_accessor.h" #include #include #include "kis_lod_transform.h" #include "kis_algebra_2d.h" // Maximum distance from a Bezier control point to the line through the start // and end points for the curve to be considered flat. #define BEZIER_FLATNESS_THRESHOLD 0.5 #define trunc(x) ((int)(x)) #ifndef Q_OS_WIN #endif struct Q_DECL_HIDDEN KisPainter::Private { Private(KisPainter *_q) : q(_q) {} Private(KisPainter *_q, const KoColorSpace *cs) : q(_q), paintColor(cs), backgroundColor(cs) {} KisPainter *q; KisPaintDeviceSP device; KisSelectionSP selection; KisTransaction* transaction; KoUpdater* progressUpdater; QVector dirtyRects; KisPaintOp* paintOp; KoColor paintColor; KoColor backgroundColor; KoColor customColor; KisFilterConfigurationSP generator; KisPaintLayer* sourceLayer; FillStyle fillStyle; StrokeStyle strokeStyle; bool antiAliasPolygonFill; const KoPattern* pattern; QPointF duplicateOffset; quint32 pixelSize; const KoColorSpace* colorSpace; KoColorProfile* profile; const KoCompositeOp* compositeOp; const KoAbstractGradient* gradient; KisPaintOpPresetSP paintOpPreset; QImage polygonMaskImage; QPainter* maskPainter; KisFillPainter* fillPainter; KisPaintDeviceSP polygon; qint32 maskImageWidth; qint32 maskImageHeight; QPointF axesCenter; bool mirrorHorizontally; bool mirrorVertically; bool isOpacityUnit; // TODO: move into ParameterInfo KoCompositeOp::ParameterInfo paramInfo; KoColorConversionTransformation::Intent renderingIntent; KoColorConversionTransformation::ConversionFlags conversionFlags; bool tryReduceSourceRect(const KisPaintDevice *srcDev, QRect *srcRect, qint32 *srcX, qint32 *srcY, qint32 *srcWidth, qint32 *srcHeight, qint32 *dstX, qint32 *dstY); void fillPainterPathImpl(const QPainterPath& path, const QRect &requestedRect); }; KisPainter::KisPainter() : d(new Private(this)) { init(); } KisPainter::KisPainter(KisPaintDeviceSP device) : d(new Private(this, device->colorSpace())) { init(); Q_ASSERT(device); begin(device); } KisPainter::KisPainter(KisPaintDeviceSP device, KisSelectionSP selection) : d(new Private(this, device->colorSpace())) { init(); Q_ASSERT(device); begin(device); d->selection = selection; } void KisPainter::init() { d->selection = 0 ; d->transaction = 0; d->paintOp = 0; d->pattern = 0; d->sourceLayer = 0; d->fillStyle = FillStyleNone; d->strokeStyle = StrokeStyleBrush; d->antiAliasPolygonFill = true; d->progressUpdater = 0; d->gradient = 0; d->maskPainter = 0; d->fillPainter = 0; d->maskImageWidth = 255; d->maskImageHeight = 255; d->mirrorHorizontally = false; d->mirrorVertically = false; d->isOpacityUnit = true; d->paramInfo = KoCompositeOp::ParameterInfo(); d->renderingIntent = KoColorConversionTransformation::internalRenderingIntent(); d->conversionFlags = KoColorConversionTransformation::internalConversionFlags(); } KisPainter::~KisPainter() { // TODO: Maybe, don't be that strict? // deleteTransaction(); end(); delete d->paintOp; delete d->maskPainter; delete d->fillPainter; delete d; } template void copyAreaOptimizedImpl(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { const QRect dstRect(dstPt, srcRect.size()); const bool srcEmpty = (src->extent() & srcRect).isEmpty(); const bool dstEmpty = (dst->extent() & dstRect).isEmpty(); if (!srcEmpty || !dstEmpty) { if (srcEmpty) { dst->clear(dstRect); } else { KisPainter gc(dst); gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY)); if (useOldData) { gc.bitBltOldData(dstRect.topLeft(), src, srcRect); } else { gc.bitBlt(dstRect.topLeft(), src, srcRect); } } } } void KisPainter::copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { copyAreaOptimizedImpl(dstPt, src, dst, srcRect); } void KisPainter::copyAreaOptimizedOldData(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &srcRect) { copyAreaOptimizedImpl(dstPt, src, dst, srcRect); } void KisPainter::copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect, KisSelectionSP selection) { if (!selection) { copyAreaOptimized(dstPt, src, dst, originalSrcRect); return; } const QRect selectionRect = selection->selectedRect(); const QRect srcRect = originalSrcRect & selectionRect; const QPoint dstOffset = srcRect.topLeft() - originalSrcRect.topLeft(); const QRect dstRect = QRect(dstPt + dstOffset, srcRect.size()); const bool srcEmpty = (src->extent() & srcRect).isEmpty(); const bool dstEmpty = (dst->extent() & dstRect).isEmpty(); if (!srcEmpty || !dstEmpty) { //if (srcEmpty) { // doesn't support dstRect // dst->clearSelection(selection); // } else */ { KisPainter gc(dst); gc.setSelection(selection); gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY)); gc.bitBlt(dstRect.topLeft(), src, srcRect); } } } KisPaintDeviceSP KisPainter::convertToAlphaAsAlpha(KisPaintDeviceSP src) { const KoColorSpace *srcCS = src->colorSpace(); const QRect processRect = src->extent(); KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8())); KisSequentialConstIterator srcIt(src, processRect); KisSequentialIterator dstIt(dst, processRect); do { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *alpha8Ptr = dstIt.rawData(); const quint8 white = srcCS->intensity8(srcPtr); const quint8 alpha = srcCS->opacityU8(srcPtr); *alpha8Ptr = KoColorSpaceMaths::multiply(alpha, KoColorSpaceMathsTraits::unitValue - white); } while (srcIt.nextPixel() && dstIt.nextPixel()); return dst; } KisPaintDeviceSP KisPainter::convertToAlphaAsGray(KisPaintDeviceSP src) { const KoColorSpace *srcCS = src->colorSpace(); const QRect processRect = src->extent(); KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8())); KisSequentialConstIterator srcIt(src, processRect); KisSequentialIterator dstIt(dst, processRect); do { const quint8 *srcPtr = srcIt.rawDataConst(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->intensity8(srcPtr); } while (srcIt.nextPixel() && dstIt.nextPixel()); return dst; } bool KisPainter::checkDeviceHasTransparency(KisPaintDeviceSP dev) { const QRect deviceBounds = dev->exactBounds(); const QRect imageBounds = dev->defaultBounds()->bounds(); if (deviceBounds.isEmpty() || (deviceBounds & imageBounds) != imageBounds) { return true; } const KoColorSpace *cs = dev->colorSpace(); KisSequentialConstIterator it(dev, deviceBounds); do { if (cs->opacityU8(it.rawDataConst()) != OPACITY_OPAQUE_U8) { return true; } } while(it.nextPixel()); return false; } void KisPainter::begin(KisPaintDeviceSP device) { begin(device, d->selection); } void KisPainter::begin(KisPaintDeviceSP device, KisSelectionSP selection) { if (!device) return; d->selection = selection; Q_ASSERT(device->colorSpace()); end(); d->device = device; d->colorSpace = device->colorSpace(); d->compositeOp = d->colorSpace->compositeOp(COMPOSITE_OVER); d->pixelSize = device->pixelSize(); } void KisPainter::end() { Q_ASSERT_X(!d->transaction, "KisPainter::end()", "end() was called for the painter having a transaction. " "Please use end/deleteTransaction() instead"); } void KisPainter::beginTransaction(const KUndo2MagicString& transactionName,int timedID) { Q_ASSERT_X(!d->transaction, "KisPainter::beginTransaction()", "You asked for a new transaction while still having " "another one. Please finish the first one with " "end/deleteTransaction() first"); d->transaction = new KisTransaction(transactionName, d->device); Q_CHECK_PTR(d->transaction); d->transaction->undoCommand()->setTimedID(timedID); } void KisPainter::revertTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::revertTransaction()", "No transaction is in progress"); d->transaction->revert(); delete d->transaction; d->transaction = 0; } void KisPainter::endTransaction(KisUndoAdapter *undoAdapter) { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); d->transaction->commit(undoAdapter); delete d->transaction; d->transaction = 0; } void KisPainter::endTransaction(KisPostExecutionUndoAdapter *undoAdapter) { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); d->transaction->commit(undoAdapter); delete d->transaction; d->transaction = 0; } KUndo2Command* KisPainter::endAndTakeTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()", "No transaction is in progress"); KUndo2Command *transactionData = d->transaction->endAndTake(); delete d->transaction; d->transaction = 0; return transactionData; } void KisPainter::deleteTransaction() { if (!d->transaction) return; delete d->transaction; d->transaction = 0; } void KisPainter::putTransaction(KisTransaction* transaction) { Q_ASSERT_X(!d->transaction, "KisPainter::putTransaction()", "You asked for a new transaction while still having " "another one. Please finish the first one with " "end/deleteTransaction() first"); d->transaction = transaction; } KisTransaction* KisPainter::takeTransaction() { Q_ASSERT_X(d->transaction, "KisPainter::takeTransaction()", "No transaction is in progress"); KisTransaction *temp = d->transaction; d->transaction = 0; return temp; } QVector KisPainter::takeDirtyRegion() { QVector vrect = d->dirtyRects; d->dirtyRects.clear(); return vrect; } void KisPainter::addDirtyRect(const QRect & rc) { QRect r = rc.normalized(); if (r.isValid()) { d->dirtyRects.append(rc); } } inline bool KisPainter::Private::tryReduceSourceRect(const KisPaintDevice *srcDev, QRect *srcRect, qint32 *srcX, qint32 *srcY, qint32 *srcWidth, qint32 *srcHeight, qint32 *dstX, qint32 *dstY) { /** * In case of COMPOSITE_COPY and Wrap Around Mode even the pixels * outside the device extent matter, because they will be either * directly copied (former case) or cloned from another area of * the image. */ if (compositeOp->id() != COMPOSITE_COPY && compositeOp->id() != COMPOSITE_DESTINATION_IN && compositeOp->id() != COMPOSITE_DESTINATION_ATOP && !srcDev->defaultBounds()->wrapAroundMode()) { /** * If srcDev->extent() (the area of the tiles containing * srcDev) is smaller than srcRect, then shrink srcRect to * that size. This is done as a speed optimization, useful for * stack recomposition in KisImage. srcRect won't grow if * srcDev->extent() is larger. */ *srcRect &= srcDev->extent(); if (srcRect->isEmpty()) return true; // Readjust the function paramenters to the new dimensions. *dstX += srcRect->x() - *srcX; // This will only add, not subtract *dstY += srcRect->y() - *srcY; // Idem srcRect->getRect(srcX, srcY, srcWidth, srcHeight); } return false; } void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { // TODO: get selX and selY working as intended /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; // Check that selection has an alpha colorspace, crash if false Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8()); QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect selRect = QRect(selX, selY, srcWidth, srcHeight); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize YET as it would obfuscate the mistake. */ Q_ASSERT(selection->bounds().contains(selRect)); Q_UNUSED(selRect); // only used by the above Q_ASSERT /** * An optimization, which crops the source rect by the bounds of * the source device when it is possible */ if (d->tryReduceSourceRect(srcDev, &srcRect, &srcX, &srcY, &srcWidth, &srcHeight, &dstX, &dstY)) return; /* Create an intermediate byte array to hold information before it is written to the current paint device (d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "dst bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); // Copy the relevant bytes of raw data from srcDev quint8* srcBytes = 0; try { srcBytes = new quint8[srcWidth * srcHeight * srcDev->pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "src bytes"; return; } srcDev->readBytes(srcBytes, srcX, srcY, srcWidth, srcHeight); QRect selBounds = selection->bounds(); const quint8 *selRowStart = selection->data() + (selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize(); /* * This checks whether there is nothing selected. */ if (!d->selection) { /* As there's nothing selected, blit to dstBytes (intermediary bit array), ignoring d->selection (the user selection)*/ d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcBytes; d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize(); d->paramInfo.maskRowStart = selRowStart; d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } else { /* Read the user selection (d->selection) bytes into an array, ready to merge in the next block*/ quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize(); quint8* mergedSelectionBytes = 0; try { mergedSelectionBytes = new quint8[ totalBytes ]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight); // Merge selections here by multiplying them - compositeOP(COMPOSITE_MULT) d->paramInfo.dstRowStart = mergedSelectionBytes; d->paramInfo.dstRowStride = srcWidth * selection->pixelSize(); d->paramInfo.srcRowStart = selRowStart; d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo); // Blit to dstBytes (intermediary bit array) d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcBytes; d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize(); d->paramInfo.maskRowStart = mergedSelectionBytes; d->paramInfo.maskRowStride = srcWidth * selection->pixelSize(); d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); delete[] mergedSelectionBytes; } d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] dstBytes; delete[] srcBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 srcWidth, qint32 srcHeight) { bitBltWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight); } template void KisPainter::bitBltImpl(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); if (d->compositeOp->id() == COMPOSITE_COPY) { if(!d->selection && d->isOpacityUnit && srcX == dstX && srcY == dstY && d->device->fastBitBltPossible(srcDev)) { if(useOldSrcData) { d->device->fastBitBltOldData(srcDev, srcRect); } else { d->device->fastBitBlt(srcDev, srcRect); } addDirtyRect(srcRect); return; } } else { /** * An optimization, which crops the source rect by the bounds of * the source device when it is possible */ if (d->tryReduceSourceRect(srcDev, &srcRect, &srcX, &srcY, &srcWidth, &srcHeight, &dstX, &dstY)) return; } qint32 dstY_ = dstY; qint32 srcY_ = srcY; qint32 rowsRemaining = srcHeight; // Read below KisRandomConstAccessorSP srcIt = srcDev->createRandomConstAccessorNG(srcX, srcY); KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(dstX, dstY); /* Here be a huge block of verbose code that does roughly the same than the other bit blit operations. This one is longer than the rest in an effort to optimize speed and memory use */ if (d->selection) { KisPaintDeviceSP selectionProjection(d->selection->projection()); KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(dstX, dstY); while (rowsRemaining > 0) { qint32 dstX_ = dstX; qint32 srcX_ = srcX; qint32 columnsRemaining = srcWidth; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_); qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_); qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY_); qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows); rows = qMin(rows, numContiguousSelRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_); qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_); qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX_); qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns); columns = qMin(columns, numContiguousSelColumns); columns = qMin(columns, columnsRemaining); qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_); srcIt->moveTo(srcX_, srcY_); qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_); dstIt->moveTo(dstX_, dstY_); qint32 maskRowStride = maskIt->rowStride(dstX_, dstY_); maskIt->moveTo(dstX_, dstY_); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; // if we don't use the oldRawData, we need to access the rawData of the source device. d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast(srcIt.data())->rawData(); d->paramInfo.srcRowStride = srcRowStride; d->paramInfo.maskRowStart = static_cast(maskIt.data())->rawData(); d->paramInfo.maskRowStride = maskRowStride; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); srcX_ += columns; dstX_ += columns; columnsRemaining -= columns; } srcY_ += rows; dstY_ += rows; rowsRemaining -= rows; } } else { while (rowsRemaining > 0) { qint32 dstX_ = dstX; qint32 srcX_ = srcX; qint32 columnsRemaining = srcWidth; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_); qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_); qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_); qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_); qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns); columns = qMin(columns, columnsRemaining); qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_); srcIt->moveTo(srcX_, srcY_); qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_); dstIt->moveTo(dstX_, dstY_); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; // if we don't use the oldRawData, we need to access the rawData of the source device. d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast(srcIt.data())->rawData(); d->paramInfo.srcRowStride = srcRowStride; d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); srcX_ += columns; dstX_ += columns; columnsRemaining -= columns; } srcY_ += rows; dstY_ += rows; rowsRemaining -= rows; } } addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { bitBltImpl(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight); } void KisPainter::bitBlt(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect) { bitBlt(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::bitBltOldData(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { bitBltImpl(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight); } void KisPainter::bitBltOldData(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect) { bitBltOldData(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::fill(qint32 x, qint32 y, qint32 width, qint32 height, const KoColor& color) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just * initializing they perform some dummy passes with those parameters, and it must not crash */ if(width == 0 || height == 0 || d->device.isNull()) return; KoColor srcColor(color, d->device->compositionSourceColorSpace()); qint32 dstY = y; qint32 rowsRemaining = height; KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(x, y); if(d->selection) { KisPaintDeviceSP selectionProjection(d->selection->projection()); KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(x, y); while(rowsRemaining > 0) { qint32 dstX = x; qint32 columnsRemaining = width; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY); qint32 rows = qMin(numContiguousDstRows, numContiguousSelRows); rows = qMin(rows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX); qint32 columns = qMin(numContiguousDstColumns, numContiguousSelColumns); columns = qMin(columns, columnsRemaining); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); qint32 maskRowStride = maskIt->rowStride(dstX, dstY); maskIt->moveTo(dstX, dstY); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; d->paramInfo.srcRowStart = srcColor.data(); d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel d->paramInfo.maskRowStart = maskIt->oldRawData(); d->paramInfo.maskRowStride = maskRowStride; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } else { while(rowsRemaining > 0) { qint32 dstX = x; qint32 columnsRemaining = width; qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY); qint32 rows = qMin(numContiguousDstRows, rowsRemaining); while(columnsRemaining > 0) { qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX); qint32 columns = qMin(numContiguousDstColumns, columnsRemaining); qint32 dstRowStride = dstIt->rowStride(dstX, dstY); dstIt->moveTo(dstX, dstY); d->paramInfo.dstRowStart = dstIt->rawData(); d->paramInfo.dstRowStride = dstRowStride; d->paramInfo.srcRowStart = srcColor.data(); d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = rows; d->paramInfo.cols = columns; d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); dstX += columns; columnsRemaining -= columns; } dstY += rows; rowsRemaining -= rows; } } addDirtyRect(QRect(x, y, width, height)); } void KisPainter::bltFixed(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight) { /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect srcBounds = srcDev->bounds(); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */ Q_ASSERT(srcBounds.contains(srcRect)); Q_UNUSED(srcRect); // only used in above assertion /* Create an intermediate byte array to hold information before it is written to the current paint device (aka: d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bltFixed std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); const quint8 *srcRowStart = srcDev->data() + (srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize(); d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; if (d->selection) { /* d->selection is a KisPaintDevice, so first a readBytes is performed to get the area of interest... */ KisPaintDeviceSP selectionProjection(d->selection->projection()); quint8* selBytes = 0; try { selBytes = new quint8[srcWidth * srcHeight * selectionProjection->pixelSize()]; } catch (std::bad_alloc) { delete[] dstBytes; return; } selectionProjection->readBytes(selBytes, dstX, dstY, srcWidth, srcHeight); d->paramInfo.maskRowStart = selBytes; d->paramInfo.maskRowStride = srcWidth * selectionProjection->pixelSize(); } // ...and then blit. d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] d->paramInfo.maskRowStart; delete[] dstBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bltFixed(const QPoint & pos, const KisFixedPaintDeviceSP srcDev, const QRect & srcRect) { bltFixed(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height()); } void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, qint32 selX, qint32 selY, qint32 srcX, qint32 srcY, quint32 srcWidth, quint32 srcHeight) { // TODO: get selX and selY working as intended /* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just initializing they perform some dummy passes with those parameters, and it must not crash */ if (srcWidth == 0 || srcHeight == 0) return; if (srcDev.isNull()) return; if (d->device.isNull()) return; // Check that selection has an alpha colorspace, crash if false Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8()); QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight); QRect selRect = QRect(selX, selY, srcWidth, srcHeight); QRect srcBounds = srcDev->bounds(); QRect selBounds = selection->bounds(); /* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done, so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */ Q_ASSERT(srcBounds.contains(srcRect)); Q_UNUSED(srcRect); // only used in above assertion Q_ASSERT(selBounds.contains(selRect)); Q_UNUSED(selRect); // only used in above assertion /* Create an intermediate byte array to hold information before it is written to the current paint device (aka: d->device) */ quint8* dstBytes = 0; try { dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes"; return; } d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); const quint8 *srcRowStart = srcDev->data() + (srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize(); const quint8 *selRowStart = selection->data() + (selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize(); if (!d->selection) { /* As there's nothing selected, blit to dstBytes (intermediary bit array), ignoring d->selection (the user selection)*/ d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = selRowStart; d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } else { /* Read the user selection (d->selection) bytes into an array, ready to merge in the next block*/ quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize(); quint8 * mergedSelectionBytes = 0; try { mergedSelectionBytes = new quint8[ totalBytes ]; } catch (std::bad_alloc) { warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << totalBytes << "total bytes"; delete[] dstBytes; return; } d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight); // Merge selections here by multiplying them - compositeOp(COMPOSITE_MULT) d->paramInfo.dstRowStart = mergedSelectionBytes; d->paramInfo.dstRowStride = srcWidth * selection->pixelSize(); d->paramInfo.srcRowStart = selRowStart; d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize(); d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = srcHeight; d->paramInfo.cols = srcWidth; KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo); // Blit to dstBytes (intermediary bit array) d->paramInfo.dstRowStart = dstBytes; d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize(); d->paramInfo.srcRowStart = srcRowStart; d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize(); d->paramInfo.maskRowStart = mergedSelectionBytes; d->paramInfo.maskRowStride = srcWidth * selection->pixelSize(); d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); delete[] mergedSelectionBytes; } d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight); delete[] dstBytes; addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight)); } void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY, const KisFixedPaintDeviceSP srcDev, const KisFixedPaintDeviceSP selection, quint32 srcWidth, quint32 srcHeight) { bltFixedWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight); } void KisPainter::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { if (d->device && d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintLine(pi1, pi2, currentDistance); } } void KisPainter::paintPolyline(const vQPointF &points, int index, int numPoints) { if (index >= (int) points.count()) return; if (numPoints < 0) numPoints = points.count(); if (index + numPoints > (int) points.count()) numPoints = points.count() - index; if (numPoints > 1) { KisDistanceInformation saveDist(points[0], 0.0, KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0)); for (int i = index; i < index + numPoints - 1; i++) { paintLine(points [i], points [i + 1], &saveDist); } } } static void getBezierCurvePoints(const KisVector2D &pos1, const KisVector2D &control1, const KisVector2D &control2, const KisVector2D &pos2, vQPointF& points) { LineEquation line = LineEquation::Through(pos1, pos2); qreal d1 = line.absDistance(control1); qreal d2 = line.absDistance(control2); if (d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) { points.push_back(toQPointF(pos1)); } else { // Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508 KisVector2D l2 = (pos1 + control1) / 2; KisVector2D h = (control1 + control2) / 2; KisVector2D l3 = (l2 + h) / 2; KisVector2D r3 = (control2 + pos2) / 2; KisVector2D r2 = (h + r3) / 2; KisVector2D l4 = (l3 + r2) / 2; getBezierCurvePoints(pos1, l2, l3, l4, points); getBezierCurvePoints(l4, r2, r3, pos2, points); } } void KisPainter::getBezierCurvePoints(const QPointF &pos1, const QPointF &control1, const QPointF &control2, const QPointF &pos2, vQPointF& points) const { ::getBezierCurvePoints(toKisVector2D(pos1), toKisVector2D(control1), toKisVector2D(control2), toKisVector2D(pos2), points); } void KisPainter::paintBezierCurve(const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { if (d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintBezierCurve(pi1, control1, control2, pi2, currentDistance); } } void KisPainter::paintRect(const QRectF &rect) { QRectF normalizedRect = rect.normalized(); vQPointF points; points.push_back(normalizedRect.topLeft()); points.push_back(normalizedRect.bottomLeft()); points.push_back(normalizedRect.bottomRight()); points.push_back(normalizedRect.topRight()); paintPolygon(points); } void KisPainter::paintRect(const qreal x, const qreal y, const qreal w, const qreal h) { paintRect(QRectF(x, y, w, h)); } void KisPainter::paintEllipse(const QRectF &rect) { QRectF r = rect.normalized(); // normalize before checking as negative width and height are empty too if (r.isEmpty()) return; // See http://www.whizkidtech.redprince.net/bezier/circle/ for explanation. // kappa = (4/3*(sqrt(2)-1)) const qreal kappa = 0.5522847498; const qreal lx = (r.width() / 2) * kappa; const qreal ly = (r.height() / 2) * kappa; QPointF center = r.center(); QPointF p0(r.left(), center.y()); QPointF p1(r.left(), center.y() - ly); QPointF p2(center.x() - lx, r.top()); QPointF p3(center.x(), r.top()); vQPointF points; getBezierCurvePoints(p0, p1, p2, p3, points); QPointF p4(center.x() + lx, r.top()); QPointF p5(r.right(), center.y() - ly); QPointF p6(r.right(), center.y()); getBezierCurvePoints(p3, p4, p5, p6, points); QPointF p7(r.right(), center.y() + ly); QPointF p8(center.x() + lx, r.bottom()); QPointF p9(center.x(), r.bottom()); getBezierCurvePoints(p6, p7, p8, p9, points); QPointF p10(center.x() - lx, r.bottom()); QPointF p11(r.left(), center.y() + ly); getBezierCurvePoints(p9, p10, p11, p0, points); paintPolygon(points); } void KisPainter::paintEllipse(const qreal x, const qreal y, const qreal w, const qreal h) { paintEllipse(QRectF(x, y, w, h)); } void KisPainter::paintAt(const KisPaintInformation& pi, KisDistanceInformation *savedDist) { if (d->paintOp && d->paintOp->canPaint()) { d->paintOp->paintAt(pi, savedDist); } } void KisPainter::fillPolygon(const vQPointF& points, FillStyle fillStyle) { if (points.count() < 3) { return; } if (fillStyle == FillStyleNone) { return; } QPainterPath polygonPath; polygonPath.moveTo(points.at(0)); for (int pointIndex = 1; pointIndex < points.count(); pointIndex++) { polygonPath.lineTo(points.at(pointIndex)); } polygonPath.closeSubpath(); d->fillStyle = fillStyle; fillPainterPath(polygonPath); } void KisPainter::paintPolygon(const vQPointF& points) { if (d->fillStyle != FillStyleNone) { fillPolygon(points, d->fillStyle); } if (d->strokeStyle != StrokeStyleNone) { if (points.count() > 1) { KisDistanceInformation distance(points[0], 0.0, KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0)); for (int i = 0; i < points.count() - 1; i++) { paintLine(KisPaintInformation(points[i]), KisPaintInformation(points[i + 1]), &distance); } paintLine(points[points.count() - 1], points[0], &distance); } } } void KisPainter::paintPainterPath(const QPainterPath& path) { if (d->fillStyle != FillStyleNone) { fillPainterPath(path); } if (d->strokeStyle == StrokeStyleNone) return; QPointF lastPoint, nextPoint; int elementCount = path.elementCount(); KisDistanceInformation saveDist; for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = path.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: lastPoint = QPointF(element.x, element.y); break; case QPainterPath::LineToElement: nextPoint = QPointF(element.x, element.y); paintLine(KisPaintInformation(lastPoint), KisPaintInformation(nextPoint), &saveDist); lastPoint = nextPoint; break; case QPainterPath::CurveToElement: nextPoint = QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y); paintBezierCurve(KisPaintInformation(lastPoint), QPointF(path.elementAt(i).x, path.elementAt(i).y), QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y), KisPaintInformation(nextPoint), &saveDist); lastPoint = nextPoint; break; default: continue; } } } void KisPainter::fillPainterPath(const QPainterPath& path) { fillPainterPath(path, QRect()); } void KisPainter::fillPainterPath(const QPainterPath& path, const QRect &requestedRect) { if (d->mirrorHorizontally || d->mirrorVertically) { KisLodTransform lod(d->device); QPointF effectiveAxesCenter = lod.map(d->axesCenter); QTransform C1 = QTransform::fromTranslate(-effectiveAxesCenter.x(), -effectiveAxesCenter.y()); QTransform C2 = QTransform::fromTranslate(effectiveAxesCenter.x(), effectiveAxesCenter.y()); QTransform t; QPainterPath newPath; QRect newRect; if (d->mirrorHorizontally) { t = C1 * QTransform::fromScale(-1,1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } if (d->mirrorVertically) { t = C1 * QTransform::fromScale(1,-1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } if (d->mirrorHorizontally && d->mirrorVertically) { t = C1 * QTransform::fromScale(-1,-1) * C2; newPath = t.map(path); newRect = t.mapRect(requestedRect); d->fillPainterPathImpl(newPath, newRect); } } d->fillPainterPathImpl(path, requestedRect); } void KisPainter::Private::fillPainterPathImpl(const QPainterPath& path, const QRect &requestedRect) { if (fillStyle == FillStyleNone) { return; } // Fill the polygon bounding rectangle with the required contents then we'll // create a mask for the actual polygon coverage. if (!fillPainter) { polygon = device->createCompositionSourceDevice(); fillPainter = new KisFillPainter(polygon); } else { polygon->clear(); } Q_CHECK_PTR(polygon); QRectF boundingRect = path.boundingRect(); QRect fillRect = boundingRect.toAlignedRect(); // Expand the rectangle to allow for anti-aliasing. fillRect.adjust(-1, -1, 1, 1); if (requestedRect.isValid()) { fillRect &= requestedRect; } switch (fillStyle) { default: // Fall through case FillStyleGradient: // Currently unsupported, fall through case FillStyleStrokes: // Currently unsupported, fall through warnImage << "Unknown or unsupported fill style in fillPolygon\n"; case FillStyleForegroundColor: fillPainter->fillRect(fillRect, q->paintColor(), OPACITY_OPAQUE_U8); break; case FillStyleBackgroundColor: fillPainter->fillRect(fillRect, q->backgroundColor(), OPACITY_OPAQUE_U8); break; case FillStylePattern: if (pattern) { // if the user hasn't got any patterns installed, we shouldn't crash... fillPainter->fillRect(fillRect, pattern); } break; case FillStyleGenerator: if (generator) { // if the user hasn't got any generators, we shouldn't crash... fillPainter->fillRect(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), q->generator()); } break; } if (polygonMaskImage.isNull() || (maskPainter == 0)) { polygonMaskImage = QImage(maskImageWidth, maskImageHeight, QImage::Format_ARGB32_Premultiplied); maskPainter = new QPainter(&polygonMaskImage); maskPainter->setRenderHint(QPainter::Antialiasing, q->antiAliasPolygonFill()); } // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. const QColor black(Qt::black); const QBrush brush(Qt::white); for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += maskImageWidth) { for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += maskImageHeight) { polygonMaskImage.fill(black.rgb()); maskPainter->translate(-x, -y); maskPainter->fillPath(path, brush); maskPainter->translate(x, y); qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, maskImageWidth); qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, maskImageHeight); KisHLineIteratorSP lineIt = polygon->createHLineIteratorNG(x, y, rectWidth); quint8 tmp; for (int row = y; row < y + rectHeight; row++) { QRgb* line = reinterpret_cast(polygonMaskImage.scanLine(row - y)); do { tmp = qRed(line[lineIt->x() - x]); polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1); } while (lineIt->nextPixel()); lineIt->nextRow(); } } } QRect bltRect = !requestedRect.isEmpty() ? requestedRect : fillRect; q->bitBlt(bltRect.x(), bltRect.y(), polygon, bltRect.x(), bltRect.y(), bltRect.width(), bltRect.height()); } void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen) { drawPainterPath(path, pen, QRect()); } void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen, const QRect &requestedRect) { // we are drawing mask, it has to be white // color of the path is given by paintColor() Q_ASSERT(pen.color() == Qt::white); if (!d->fillPainter) { d->polygon = d->device->createCompositionSourceDevice(); d->fillPainter = new KisFillPainter(d->polygon); } else { d->polygon->clear(); } Q_CHECK_PTR(d->polygon); QRectF boundingRect = path.boundingRect(); QRect fillRect = boundingRect.toAlignedRect(); // take width of the pen into account int penWidth = qRound(pen.widthF()); fillRect.adjust(-penWidth, -penWidth, penWidth, penWidth); // Expand the rectangle to allow for anti-aliasing. fillRect.adjust(-1, -1, 1, 1); if (!requestedRect.isNull()) { fillRect &= requestedRect; } d->fillPainter->fillRect(fillRect, paintColor(), OPACITY_OPAQUE_U8); if (d->polygonMaskImage.isNull() || (d->maskPainter == 0)) { d->polygonMaskImage = QImage(d->maskImageWidth, d->maskImageHeight, QImage::Format_ARGB32_Premultiplied); d->maskPainter = new QPainter(&d->polygonMaskImage); d->maskPainter->setRenderHint(QPainter::Antialiasing, antiAliasPolygonFill()); } // Break the mask up into chunks so we don't have to allocate a potentially very large QImage. const QColor black(Qt::black); QPen oldPen = d->maskPainter->pen(); d->maskPainter->setPen(pen); for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += d->maskImageWidth) { for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += d->maskImageHeight) { d->polygonMaskImage.fill(black.rgb()); d->maskPainter->translate(-x, -y); d->maskPainter->drawPath(path); d->maskPainter->translate(x, y); qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, d->maskImageWidth); qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, d->maskImageHeight); KisHLineIteratorSP lineIt = d->polygon->createHLineIteratorNG(x, y, rectWidth); quint8 tmp; for (int row = y; row < y + rectHeight; row++) { QRgb* line = reinterpret_cast(d->polygonMaskImage.scanLine(row - y)); do { tmp = qRed(line[lineIt->x() - x]); d->polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1); } while (lineIt->nextPixel()); lineIt->nextRow(); } } } d->maskPainter->setPen(oldPen); QRect r = d->polygon->extent(); bitBlt(r.x(), r.y(), d->polygon, r.x(), r.y(), r.width(), r.height()); } inline void KisPainter::compositeOnePixel(quint8 *dst, const KoColor &color) { d->paramInfo.dstRowStart = dst; d->paramInfo.dstRowStride = 0; d->paramInfo.srcRowStart = color.data(); d->paramInfo.srcRowStride = 0; d->paramInfo.maskRowStart = 0; d->paramInfo.maskRowStride = 0; d->paramInfo.rows = 1; d->paramInfo.cols = 1; d->colorSpace->bitBlt(color.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags); } /**/ void KisPainter::drawLine(const QPointF& start, const QPointF& end, qreal width, bool antialias){ int x1 = start.x(); int y1 = start.y(); int x2 = end.x(); int y2 = end.y(); if ((x2 == x1 ) && (y2 == y1)) return; int dstX = x2-x1; int dstY = y2-y1; qreal uniC = dstX*y1 - dstY*x1; qreal projectionDenominator = 1.0 / (pow((double)dstX, 2) + pow((double)dstY, 2)); qreal subPixel; if (qAbs(dstX) > qAbs(dstY)){ subPixel = start.x() - x1; }else{ subPixel = start.y() - y1; } qreal halfWidth = width * 0.5 + subPixel; int W_ = qRound(halfWidth) + 1; // save the state int X1_ = x1; int Y1_ = y1; int X2_ = x2; int Y2_ = y2; - if (x2device->createRandomAccessorNG(x1, y1); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1); } for (int y = y1-W_; y < y2+W_ ; y++){ for (int x = x1-W_; x < x2+W_; x++){ projection = ( (x-X1_)* dstX + (y-Y1_)*dstY ) * projectionDenominator; scanX = X1_ + projection * dstX; scanY = Y1_ + projection * dstY; if (((scanX < x1) || (scanX > x2)) || ((scanY < y1) || (scanY > y2))) { AA_ = qMin( sqrt( pow((double)x - X1_, 2) + pow((double)y - Y1_, 2) ), sqrt( pow((double)x - X2_, 2) + pow((double)y - Y2_, 2) )); }else{ AA_ = qAbs(dstY*x - dstX*y + uniC) * denominator; } if (AA_>halfWidth) { continue; } accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x,y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { KoColor mycolor = d->paintColor; if (antialias && AA_ > halfWidth-1.0) { mycolor.colorSpace()->multiplyAlpha(mycolor.data(), 1.0 - (AA_-(halfWidth-1.0)), 1); } compositeOnePixel(accessor->rawData(), mycolor); } } } } /**/ void KisPainter::drawLine(const QPointF & start, const QPointF & end) { drawThickLine(start, end, 1, 1); } void KisPainter::drawDDALine(const QPointF & start, const QPointF & end) { int x = int(start.x()); int y = int(start.y()); int x2 = int(end.x()); int y2 = int(end.y()); // Width and height of the line int xd = x2 - x; int yd = y2 - y; float m = (float)yd / (float)xd; float fx = x; float fy = y; int inc; KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x, y); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x, y); } accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x,y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } if (fabs(m) > 1.0f) { inc = (yd > 0) ? 1 : -1; m = 1.0f / m; m *= inc; while (y != y2) { y = y + inc; fx = fx + m; x = qRound(fx); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } } } else { inc = (xd > 0) ? 1 : -1; m *= inc; while (x != x2) { x = x + inc; fy = fy + m; y = qRound(fy); accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), d->paintColor); } } } } void KisPainter::drawWobblyLine(const QPointF & start, const QPointF & end) { KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y()); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y()); } KoColor mycolor(d->paintColor); int x1 = start.x(); int y1 = start.y(); int x2 = end.x(); int y2 = end.y(); // Width and height of the line int xd = (x2 - x1); int yd = (y2 - y1); int x; int y; float fx = (x = x1); float fy = (y = y1); float m = (float)yd / (float)xd; int inc; if (fabs(m) > 1) { inc = (yd > 0) ? 1 : -1; m = 1.0f / m; m *= inc; while (y != y2) { fx = fx + m; y = y + inc; x = qRound(fx); float br1 = int(fx + 1) - fx; float br2 = fx - (int)fx; accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br1)); compositeOnePixel(accessor->rawData(), mycolor); } accessor->moveTo(x + 1, y); if (selectionAccessor) selectionAccessor->moveTo(x + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br2)); compositeOnePixel(accessor->rawData(), mycolor); } } } else { inc = (xd > 0) ? 1 : -1; m *= inc; while (x != x2) { fy = fy + m; x = x + inc; y = qRound(fy); float br1 = int(fy + 1) - fy; float br2 = fy - (int)fy; accessor->moveTo(x, y); if (selectionAccessor) selectionAccessor->moveTo(x, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br1)); compositeOnePixel(accessor->rawData(), mycolor); } accessor->moveTo(x, y + 1); if (selectionAccessor) selectionAccessor->moveTo(x, y + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { mycolor.setOpacity((quint8)(255*br2)); compositeOnePixel(accessor->rawData(), mycolor); } } } } void KisPainter::drawWuLine(const QPointF & start, const QPointF & end) { KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y()); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y()); } KoColor lineColor(d->paintColor); int x1 = start.x(); int y1 = start.y(); int x2 = end.x(); int y2 = end.y(); float grad, xd, yd; float xgap, ygap, xend, yend, yf, xf; float brightness1, brightness2; int ix1, ix2, iy1, iy2; quint8 c1, c2; // gradient of line xd = (x2 - x1); yd = (y2 - y1); if (yd == 0) { /* Horizontal line */ int incr = (x1 < x2) ? 1 : -1; ix1 = (int)x1; ix2 = (int)x2; iy1 = (int)y1; while (ix1 != ix2) { ix1 = ix1 + incr; accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), lineColor); } } return; } if (xd == 0) { /* Vertical line */ int incr = (y1 < y2) ? 1 : -1; iy1 = (int)y1; iy2 = (int)y2; ix1 = (int)x1; while (iy1 != iy2) { iy1 = iy1 + incr; accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), lineColor); } } return; } if (fabs(xd) > fabs(yd)) { // horizontal line // line have to be paint from left to right if (x1 > x2) { float tmp; tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; xd = (x2 - x1); yd = (y2 - y1); } grad = yd / xd; // nearest X,Y interger coordinates xend = static_cast(x1 + 0.5f); yend = y1 + grad * (xend - x1); xgap = invertFrac(x1 + 0.5f); ix1 = static_cast(xend); iy1 = static_cast(yend); // calc the intensity of the other end point pixel pair. brightness1 = invertFrac(yend) * xgap; brightness2 = frac(yend) * xgap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix1, iy1 + 1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1 + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // calc first Y-intersection for main loop yf = yend + grad; xend = trunc(x2 + 0.5f); yend = y2 + grad * (xend - x2); xgap = invertFrac(x2 - 0.5f); ix2 = static_cast(xend); iy2 = static_cast(yend); brightness1 = invertFrac(yend) * xgap; brightness2 = frac(yend) * xgap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix2, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix2, iy2 + 1); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2 + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // main loop for (int x = ix1 + 1; x <= ix2 - 1; x++) { brightness1 = invertFrac(yf); brightness2 = frac(yf); c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(x, int (yf)); if (selectionAccessor) selectionAccessor->moveTo(x, int (yf)); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(x, int (yf) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, int (yf) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } yf = yf + grad; } } else { //vertical // line have to be painted from left to right if (y1 > y2) { float tmp; tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; xd = (x2 - x1); yd = (y2 - y1); } grad = xd / yd; // nearest X,Y interger coordinates yend = static_cast(y1 + 0.5f); xend = x1 + grad * (yend - y1); ygap = invertFrac(y1 + 0.5f); ix1 = static_cast(xend); iy1 = static_cast(yend); // calc the intensity of the other end point pixel pair. brightness1 = invertFrac(xend) * ygap; brightness2 = frac(xend) * ygap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix1, iy1); if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(x1 + 1, y1); if (selectionAccessor) selectionAccessor->moveTo(x1 + 1, y1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // calc first Y-intersection for main loop xf = xend + grad; yend = trunc(y2 + 0.5f); xend = x2 + grad * (yend - y2); ygap = invertFrac(y2 - 0.5f); ix2 = static_cast(xend); iy2 = static_cast(yend); brightness1 = invertFrac(xend) * ygap; brightness2 = frac(xend) * ygap; c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(ix2, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(ix2 + 1, iy2); if (selectionAccessor) selectionAccessor->moveTo(ix2 + 1, iy2); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } // main loop for (int y = iy1 + 1; y <= iy2 - 1; y++) { brightness1 = invertFrac(xf); brightness2 = frac(xf); c1 = (int)(brightness1 * OPACITY_OPAQUE_U8); c2 = (int)(brightness2 * OPACITY_OPAQUE_U8); accessor->moveTo(int (xf), y); if (selectionAccessor) selectionAccessor->moveTo(int (xf), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c1); compositeOnePixel(accessor->rawData(), lineColor); } accessor->moveTo(int (xf) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(int (xf) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { lineColor.setOpacity(c2); compositeOnePixel(accessor->rawData(), lineColor); } xf = xf + grad; } }//end-of-else } void KisPainter::drawThickLine(const QPointF & start, const QPointF & end, int startWidth, int endWidth) { KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y()); KisRandomConstAccessorSP selectionAccessor; if (d->selection) { selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y()); } const KoColorSpace *cs = d->device->colorSpace(); KoColor c1(d->paintColor); KoColor c2(d->paintColor); KoColor c3(d->paintColor); KoColor col1(c1); KoColor col2(c1); float grada, gradb, dxa, dxb, dya, dyb, fraca, fracb, xfa, yfa, xfb, yfb, b1a, b2a, b1b, b2b, dstX, dstY; int x, y, ix1, ix2, iy1, iy2; int x0a, y0a, x1a, y1a, x0b, y0b, x1b, y1b; int tp0, tn0, tp1, tn1; int horizontal = 0; float opacity = 1.0; tp0 = startWidth / 2; tn0 = startWidth / 2; if (startWidth % 2 == 0) // even width startWidth tn0--; tp1 = endWidth / 2; tn1 = endWidth / 2; if (endWidth % 2 == 0) // even width endWidth tn1--; int x0 = qRound(start.x()); int y0 = qRound(start.y()); int x1 = qRound(end.x()); int y1 = qRound(end.y()); dstX = x1 - x0; // run of general line dstY = y1 - y0; // rise of general line if (dstY < 0) dstY = -dstY; if (dstX < 0) dstX = -dstX; if (dstX > dstY) { // horizontalish horizontal = 1; x0a = x0; y0a = y0 - tn0; x0b = x0; y0b = y0 + tp0; x1a = x1; y1a = y1 - tn1; x1b = x1; y1b = y1 + tp1; } else { x0a = x0 - tn0; y0a = y0; x0b = x0 + tp0; y0b = y0; x1a = x1 - tn1; y1a = y1; x1b = x1 + tp1; y1b = y1; } if (horizontal) { // draw endpoints for (int i = y0a; i <= y0b; i++) { accessor->moveTo(x0, i); if (selectionAccessor) selectionAccessor->moveTo(x0, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } for (int i = y1a; i <= y1b; i++) { accessor->moveTo(x1, i); if (selectionAccessor) selectionAccessor->moveTo(x1, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } } else { for (int i = x0a; i <= x0b; i++) { accessor->moveTo(i, y0); if (selectionAccessor) selectionAccessor->moveTo(i, y0); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } for (int i = x1a; i <= x1b; i++) { accessor->moveTo(i, y1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c1); } } } //antialias endpoints if (x1 != x0 && y1 != y0) { if (horizontal) { accessor->moveTo(x0a, y0a - 1); if (selectionAccessor) selectionAccessor->moveTo(x0a, y0a - 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c1.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } accessor->moveTo(x1b, y1b + 1); if (selectionAccessor) selectionAccessor->moveTo(x1b, y1b + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c2.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } else { accessor->moveTo(x0a - 1, y0a); if (selectionAccessor) selectionAccessor->moveTo(x0a - 1, y0a); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c1.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } accessor->moveTo(x1b + 1, y1b); if (selectionAccessor) selectionAccessor->moveTo(x1b + 1, y1b); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = .25 * c2.opacityF() + (1 - .25) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } } dxa = x1a - x0a; // run of a dya = y1a - y0a; // rise of a dxb = x1b - x0b; // run of b dyb = y1b - y0b; // rise of b if (horizontal) { // horizontal-ish lines if (x1 < x0) { int xt, yt, wt; KoColor tmp; xt = x1a; x1a = x0a; x0a = xt; yt = y1a; y1a = y0a; y0a = yt; xt = x1b; x1b = x0b; x0b = xt; yt = y1b; y1b = y0b; y0b = yt; xt = x1; x1 = x0; x0 = xt; yt = y1; y1 = y0; y0 = yt; tmp = c1; c1 = c2; c2 = tmp; wt = startWidth; startWidth = endWidth; endWidth = wt; } grada = dya / dxa; gradb = dyb / dxb; ix1 = x0; iy1 = y0; ix2 = x1; iy2 = y1; yfa = y0a + grada; yfb = y0b + gradb; for (x = ix1 + 1; x <= ix2 - 1; x++) { fraca = yfa - int (yfa); b1a = 1 - fraca; b2a = fraca; fracb = yfb - int (yfb); b1b = 1 - fracb; b2b = fracb; // color first pixel of bottom line opacity = ((x - ix1) / dstX) * c2.opacityF() + (1 - (x - ix1) / dstX) * c1.opacityF(); c3.setOpacity(opacity); accessor->moveTo(x, (int)yfa); if (selectionAccessor) selectionAccessor->moveTo(x, (int)yfa); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1a * c3.opacityF() + (1 - b1a) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } // color first pixel of top line if (!(startWidth == 1 && endWidth == 1)) { accessor->moveTo(x, (int)yfb); if (selectionAccessor) selectionAccessor->moveTo(x, (int)yfb); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1b * c3.opacityF() + (1 - b1b) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } // color second pixel of bottom line if (grada != 0 && grada != 1) { // if not flat or exact diagonal accessor->moveTo(x, int (yfa) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, int (yfa) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2a * c3.opacityF() + (1 - b2a) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // color second pixel of top line if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) { accessor->moveTo(x, int (yfb) + 1); if (selectionAccessor) selectionAccessor->moveTo(x, int (yfb) + 1); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2b * c3.opacityF() + (1 - b2b) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // fill remaining pixels if (!(startWidth == 1 && endWidth == 1)) { if (yfa < yfb) for (int i = yfa + 1; i <= yfb; i++) { accessor->moveTo(x, i); if (selectionAccessor) selectionAccessor->moveTo(x, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } else for (int i = yfa + 1; i >= yfb; i--) { accessor->moveTo(x, i); if (selectionAccessor) selectionAccessor->moveTo(x, i); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } } yfa += grada; yfb += gradb; } } else { // vertical-ish lines if (y1 < y0) { int xt, yt, wt; xt = x1a; x1a = x0a; x0a = xt; yt = y1a; y1a = y0a; y0a = yt; xt = x1b; x1b = x0b; x0b = xt; yt = y1b; y1b = y0b; y0b = yt; xt = x1; x1 = x0; x0 = xt; yt = y1; y1 = y0; y0 = yt; KoColor tmp; tmp = c1; c1 = c2; c2 = tmp; wt = startWidth; startWidth = endWidth; endWidth = wt; } grada = dxa / dya; gradb = dxb / dyb; ix1 = x0; iy1 = y0; ix2 = x1; iy2 = y1; xfa = x0a + grada; xfb = x0b + gradb; for (y = iy1 + 1; y <= iy2 - 1; y++) { fraca = xfa - int (xfa); b1a = 1 - fraca; b2a = fraca; fracb = xfb - int (xfb); b1b = 1 - fracb; b2b = fracb; // color first pixel of left line opacity = ((y - iy1) / dstY) * c2.opacityF() + (1 - (y - iy1) / dstY) * c1.opacityF(); c3.setOpacity(opacity); accessor->moveTo(int (xfa), y); if (selectionAccessor) selectionAccessor->moveTo(int (xfa), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1a * c3.opacityF() + (1 - b1a) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } // color first pixel of right line if (!(startWidth == 1 && endWidth == 1)) { accessor->moveTo(int(xfb), y); if (selectionAccessor) selectionAccessor->moveTo(int(xfb), y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b1b * c3.opacityF() + (1 - b1b) * alpha; col1.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col1); } } // color second pixel of left line if (grada != 0 && grada != 1) { // if not flat or exact diagonal accessor->moveTo(int(xfa) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(int(xfa) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2a * c3.opacityF() + (1 - b2a) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // color second pixel of right line if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) { accessor->moveTo(int(xfb) + 1, y); if (selectionAccessor) selectionAccessor->moveTo(int(xfb) + 1, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { qreal alpha = cs->opacityF(accessor->rawData()); opacity = b2b * c3.opacityF() + (1 - b2b) * alpha; col2.setOpacity(opacity); compositeOnePixel(accessor->rawData(), col2); } } // fill remaining pixels between current xfa,xfb if (!(startWidth == 1 && endWidth == 1)) { if (xfa < xfb) for (int i = (int) xfa + 1; i <= (int) xfb; i++) { accessor->moveTo(i, y); if (selectionAccessor) selectionAccessor->moveTo(i, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } else for (int i = (int) xfb; i <= (int) xfa + 1; i++) { accessor->moveTo(i, y); if (selectionAccessor) selectionAccessor->moveTo(i, y); if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) { compositeOnePixel(accessor->rawData(), c3); } } } xfa += grada; xfb += gradb; } } } void KisPainter::setProgress(KoUpdater * progressUpdater) { d->progressUpdater = progressUpdater; } const KisPaintDeviceSP KisPainter::device() const { return d->device; } KisPaintDeviceSP KisPainter::device() { return d->device; } void KisPainter::setChannelFlags(QBitArray channelFlags) { Q_ASSERT(channelFlags.isEmpty() || quint32(channelFlags.size()) == d->colorSpace->channelCount()); // Now, if all bits in the channelflags are true, pass an empty channel flags bitarray // because otherwise the compositeops cannot optimize. d->paramInfo.channelFlags = channelFlags; if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { d->paramInfo.channelFlags = QBitArray(); } } QBitArray KisPainter::channelFlags() { return d->paramInfo.channelFlags; } void KisPainter::setPattern(const KoPattern * pattern) { d->pattern = pattern; } const KoPattern * KisPainter::pattern() const { return d->pattern; } void KisPainter::setPaintColor(const KoColor& color) { d->paintColor = color; if (d->device) { d->paintColor.convertTo(d->device->compositionSourceColorSpace()); } } const KoColor &KisPainter::paintColor() const { return d->paintColor; } void KisPainter::setBackgroundColor(const KoColor& color) { d->backgroundColor = color; if (d->device) { d->backgroundColor.convertTo(d->device->compositionSourceColorSpace()); } } const KoColor &KisPainter::backgroundColor() const { return d->backgroundColor; } void KisPainter::setGenerator(KisFilterConfigurationSP generator) { d->generator = generator; } const KisFilterConfigurationSP KisPainter::generator() const { return d->generator; } void KisPainter::setFillStyle(FillStyle fillStyle) { d->fillStyle = fillStyle; } KisPainter::FillStyle KisPainter::fillStyle() const { return d->fillStyle; } void KisPainter::setAntiAliasPolygonFill(bool antiAliasPolygonFill) { d->antiAliasPolygonFill = antiAliasPolygonFill; } bool KisPainter::antiAliasPolygonFill() { return d->antiAliasPolygonFill; } void KisPainter::setStrokeStyle(KisPainter::StrokeStyle strokeStyle) { d->strokeStyle = strokeStyle; } KisPainter::StrokeStyle KisPainter::strokeStyle() const { return d->strokeStyle; } void KisPainter::setFlow(quint8 flow) { d->paramInfo.flow = float(flow) / 255.0f; } quint8 KisPainter::flow() const { return quint8(d->paramInfo.flow * 255.0f); } void KisPainter::setOpacityUpdateAverage(quint8 opacity) { d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8; d->paramInfo.updateOpacityAndAverage(float(opacity) / 255.0f); } void KisPainter::setOpacity(quint8 opacity) { d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8; d->paramInfo.opacity = float(opacity) / 255.0f; } quint8 KisPainter::opacity() const { return quint8(d->paramInfo.opacity * 255.0f); } void KisPainter::setCompositeOp(const KoCompositeOp * op) { d->compositeOp = op; } const KoCompositeOp * KisPainter::compositeOp() { return d->compositeOp; } /** * TODO: Rename this setCompositeOpId(). See KoCompositeOpRegistry.h */ void KisPainter::setCompositeOp(const QString& op) { d->compositeOp = d->colorSpace->compositeOp(op); } void KisPainter::setSelection(KisSelectionSP selection) { d->selection = selection; } KisSelectionSP KisPainter::selection() { return d->selection; } KoUpdater * KisPainter::progressUpdater() { return d->progressUpdater; } void KisPainter::setGradient(const KoAbstractGradient* gradient) { d->gradient = gradient; } const KoAbstractGradient* KisPainter::gradient() const { return d->gradient; } void KisPainter::setPaintOpPreset(KisPaintOpPresetSP preset, KisNodeSP node, KisImageSP image) { d->paintOpPreset = preset; KisPaintOp *paintop = KisPaintOpRegistry::instance()->paintOp(preset, this, node, image); Q_ASSERT(paintop); if (paintop) { delete d->paintOp; d->paintOp = paintop; } else { warnKrita << "Could not create paintop for preset " << preset->name(); } } KisPaintOpPresetSP KisPainter::preset() const { return d->paintOpPreset; } KisPaintOp* KisPainter::paintOp() const { return d->paintOp; } void KisPainter::setMirrorInformation(const QPointF& axesCenter, bool mirrorHorizontally, bool mirrorVertically) { d->axesCenter = axesCenter; d->mirrorHorizontally = mirrorHorizontally; d->mirrorVertically = mirrorVertically; } void KisPainter::copyMirrorInformation(KisPainter* painter) { painter->setMirrorInformation(d->axesCenter, d->mirrorHorizontally, d->mirrorVertically); } bool KisPainter::hasMirroring() const { return d->mirrorHorizontally || d->mirrorVertically; } void KisPainter::setMaskImageSize(qint32 width, qint32 height) { d->maskImageWidth = qBound(1, width, 256); d->maskImageHeight = qBound(1, height, 256); d->fillPainter = 0; d->polygonMaskImage = QImage(); } //void KisPainter::setLockAlpha(bool protect) //{ // if(d->paramInfo.channelFlags.isEmpty()) { // d->paramInfo.channelFlags = d->colorSpace->channelFlags(true, true); // } // QBitArray switcher = // d->colorSpace->channelFlags(protect, !protect); // if(protect) { // d->paramInfo.channelFlags &= switcher; // } // else { // d->paramInfo.channelFlags |= switcher; // } // Q_ASSERT(quint32(d->paramInfo.channelFlags.size()) == d->colorSpace->channelCount()); //} //bool KisPainter::alphaLocked() const //{ // QBitArray switcher = d->colorSpace->channelFlags(false, true); // return !(d->paramInfo.channelFlags & switcher).count(true); //} void KisPainter::setRenderingIntent(KoColorConversionTransformation::Intent intent) { d->renderingIntent = intent; } void KisPainter::setColorConversionFlags(KoColorConversionTransformation::ConversionFlags conversionFlags) { d->conversionFlags = conversionFlags; } void KisPainter::renderMirrorMaskSafe(QRect rc, KisFixedPaintDeviceSP dab, bool preserveDab) { if (!d->mirrorHorizontally && !d->mirrorVertically) return; KisFixedPaintDeviceSP dabToProcess = dab; if (preserveDab) { dabToProcess = new KisFixedPaintDevice(*dab); } renderMirrorMask(rc, dabToProcess); } void KisPainter::renderMirrorMaskSafe(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask, bool preserveMask) { if (!d->mirrorHorizontally && !d->mirrorVertically) return; KisFixedPaintDeviceSP maskToProcess = mask; if (preserveMask) { maskToProcess = new KisFixedPaintDevice(*mask); } renderMirrorMask(rc, dab, sx, sy, maskToProcess); } void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab) { int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPointF effectiveAxesCenter = t.map(d->axesCenter); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); if (d->mirrorHorizontally && d->mirrorVertically){ dab->mirror(true, false); bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height()); dab->mirror(false,true); bltFixed(mirrorX, mirrorY, dab, 0,0,rc.width(),rc.height()); dab->mirror(true, false); bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height()); } else if (d->mirrorHorizontally){ dab->mirror(true, false); bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height()); } else if (d->mirrorVertically){ dab->mirror(false, true); bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height()); } } void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab, KisFixedPaintDeviceSP mask) { int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPointF effectiveAxesCenter = t.map(d->axesCenter); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); if (d->mirrorHorizontally && d->mirrorVertically){ dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() ); dab->mirror(false,true); mask->mirror(false, true); bltFixedWithFixedSelection(mirrorX,mirrorY, dab, mask, rc.width() ,rc.height() ); dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() ); }else if (d->mirrorHorizontally){ dab->mirror(true, false); mask->mirror(true, false); bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() ); }else if (d->mirrorVertically){ dab->mirror(false, true); mask->mirror(false, true); bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() ); } } void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab){ if (d->mirrorHorizontally || d->mirrorVertically){ KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace())); QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) ); mirrorDab->setRect(dabRc); mirrorDab->initialize(); dab->readBytes(mirrorDab->data(),rc); renderMirrorMask( QRect(rc.topLeft(),dabRc.size()), mirrorDab); } } void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask) { if (d->mirrorHorizontally || d->mirrorVertically){ KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace())); QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) ); mirrorDab->setRect(dabRc); mirrorDab->initialize(); dab->readBytes(mirrorDab->data(),QRect(QPoint(sx,sy),rc.size())); renderMirrorMask(rc, mirrorDab, mask); } } void KisPainter::renderDabWithMirroringNonIncremental(QRect rc, KisPaintDeviceSP dab) { QVector rects; int x = rc.topLeft().x(); int y = rc.topLeft().y(); KisLodTransform t(d->device); QPointF effectiveAxesCenter = t.map(d->axesCenter); int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x(); int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y(); rects << rc; if (d->mirrorHorizontally && d->mirrorVertically){ rects << QRect(mirrorX, y, rc.width(), rc.height()); rects << QRect(mirrorX, mirrorY, rc.width(), rc.height()); rects << QRect(x, mirrorY, rc.width(), rc.height()); } else if (d->mirrorHorizontally) { rects << QRect(mirrorX, y, rc.width(), rc.height()); } else if (d->mirrorVertically) { rects << QRect(x, mirrorY, rc.width(), rc.height()); } Q_FOREACH (const QRect &rc, rects) { d->device->clear(rc); } QRect resultRect = dab->extent() | rc; bool intersects = false; for (int i = 1; i < rects.size(); i++) { if (rects[i].intersects(resultRect)) { intersects = true; break; } } /** * If there are no cross-intersections, we can use a fast path * and do no cycling recompositioning */ if (!intersects) { rects.resize(1); } Q_FOREACH (const QRect &rc, rects) { bitBlt(rc.topLeft(), dab, rc); } Q_FOREACH (const QRect &rc, rects) { renderMirrorMask(rc, dab); } } diff --git a/libs/image/tests/kis_image_test.cpp b/libs/image/tests/kis_image_test.cpp index a51bbe0954..026a30d32b 100644 --- a/libs/image/tests/kis_image_test.cpp +++ b/libs/image/tests/kis_image_test.cpp @@ -1,1159 +1,1159 @@ /* * Copyright (c) 2005 Adrian Page * * 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_image_test.h" #include #include #include #include #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_image.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_adjustment_layer.h" #include "kis_selection.h" #include #include #include "kis_keyframe_channel.h" #include "kis_selection_mask.h" #include "kis_layer_utils.h" #include "kis_annotation.h" #include "KisProofingConfiguration.h" #include "kis_undo_stores.h" #define IMAGE_WIDTH 128 #define IMAGE_HEIGHT 128 void KisImageTest::layerTests() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer 1", OPACITY_OPAQUE_U8); image->addNode(layer); QVERIFY(image->rootLayer()->firstChild()->objectName() == layer->objectName()); } void KisImageTest::benchmarkCreation() { const QRect imageRect(0,0,3000,2000); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QList images; QList stores; QBENCHMARK { for (int i = 0; i < 10; i++) { stores << new KisSurrogateUndoStore(); } for (int i = 0; i < 10; i++) { KisImageSP image = new KisImage(stores.takeLast(), imageRect.width(), imageRect.height(), cs, "test image"); images << image; } } } #include "testutil.h" #include "kis_stroke_strategy.h" #include class ForbiddenLodStrokeStrategy : public KisStrokeStrategy { public: ForbiddenLodStrokeStrategy(std::function lodCallback) : m_lodCallback(lodCallback) { } KisStrokeStrategy* createLodClone(int levelOfDetail) override { Q_UNUSED(levelOfDetail); m_lodCallback(); return 0; } private: std::function m_lodCallback; }; void notifyVar(bool *value) { *value = true; } void KisImageTest::testBlockLevelOfDetail() { TestUtil::MaskParent p; QCOMPARE(p.image->currentLevelOfDetail(), 0); p.image->setDesiredLevelOfDetail(1); p.image->waitForDone(); QCOMPARE(p.image->currentLevelOfDetail(), 0); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } p.image->setLevelOfDetailBlocked(true); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(!lodCreated); } p.image->setLevelOfDetailBlocked(false); p.image->setDesiredLevelOfDetail(1); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } } void KisImageTest::testConvertImageColorSpace() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); KisPaintDeviceSP device1 = new KisPaintDevice(cs8); KisLayerSP paint1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration = filter->defaultConfiguration(); Q_ASSERT(configuration); KisLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0); image->addNode(paint1, image->root()); image->addNode(blur1, image->root()); image->refreshGraph(); const KoColorSpace *cs16 = KoColorSpaceRegistry::instance()->rgb16(); image->lock(); image->convertImageColorSpace(cs16, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image->unlock(); QVERIFY(*cs16 == *image->colorSpace()); QVERIFY(*cs16 == *image->root()->colorSpace()); QVERIFY(*cs16 == *paint1->colorSpace()); QVERIFY(*cs16 == *blur1->colorSpace()); QVERIFY(!image->root()->compositeOp()); QVERIFY(*cs16 == *paint1->compositeOp()->colorSpace()); QVERIFY(*cs16 == *blur1->compositeOp()->colorSpace()); image->refreshGraph(); } void KisImageTest::testGlobalSelection() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 0U); KisSelectionSP selection1 = new KisSelection(new KisDefaultBounds(image)); KisSelectionSP selection2 = new KisSelection(new KisDefaultBounds(image)); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->setGlobalSelection(selection2); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->reselectGlobalSelection(); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); // mixed deselecting/setting/reselecting image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); } void KisImageTest::testCloneImage() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisAnnotationSP annotation = new KisAnnotation("mytype", "mydescription", QByteArray()); image->addAnnotation(annotation); QVERIFY(image->annotation("mytype")); KisProofingConfigurationSP proofing = toQShared(new KisProofingConfiguration()); image->setProofingConfiguration(proofing); QVERIFY(image->proofingConfiguration()); const KoColor defaultColor(Qt::green, image->colorSpace()); image->setDefaultProjectionColor(defaultColor); QCOMPARE(image->defaultProjectionColor(), defaultColor); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); QVERIFY(TestUtil::findNode(image->root(), "layer1")); QVERIFY(TestUtil::findNode(image->root(), "layer2")); QUuid uuid1 = layer->uuid(); QUuid uuid2 = layer2->uuid(); { KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() != uuid1); QVERIFY(newLayer2->uuid() != uuid2); KisAnnotationSP newAnnotation = newImage->annotation("mytype"); QVERIFY(newAnnotation); QVERIFY(newAnnotation != annotation); KisProofingConfigurationSP newProofing = newImage->proofingConfiguration(); QVERIFY(newProofing); QVERIFY(newProofing != proofing); QCOMPARE(newImage->defaultProjectionColor(), defaultColor); } { KisImageSP newImage = image->clone(true); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() == uuid1); QVERIFY(newLayer2->uuid() == uuid2); } } void KisImageTest::testLayerComposition() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisLayerComposition comp(image, "comp 1"); comp.store(); layer2->setVisible(false); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); KisLayerComposition comp2(image, "comp 2"); comp2.store(); KisLayerCompositionSP comp3 = toQShared(new KisLayerComposition(image, "comp 3")); comp3->store(); image->addComposition(comp3); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); comp2.apply(); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp1(comp, newImage); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp2(comp2, newImage); newComp2.apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); QVERIFY(!newImage->compositions().isEmpty()); KisLayerCompositionSP newComp3 = newImage->compositions().first(); newComp3->apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); } #include "testutil.h" #include "kis_group_layer.h" #include "kis_transparency_mask.h" #include "kis_psd_layer_style.h" struct FlattenTestImage { FlattenTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; layer1 = p.layer; layer5 = new KisPaintLayer(p.image, "paint5", 0.4 * OPACITY_OPAQUE_U8); layer5->disableAlphaChannel(true); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); tmask = new KisTransparencyMask(); // check channel flags // make addition composite op group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); layer4 = new KisPaintLayer(p.image, "paint4", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); layer7 = new KisPaintLayer(p.image, "paint7", OPACITY_OPAQUE_U8); layer8 = new KisPaintLayer(p.image, "paint8", OPACITY_OPAQUE_U8); layer7->setCompositeOpId(COMPOSITE_ADD); layer8->setCompositeOpId(COMPOSITE_ADD); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect tmaskRect(200,200,100,100); QRect rect3(400, 100, 100, 100); QRect rect4(500, 100, 100, 100); QRect rect5(50, 50, 100, 100); QRect rect6(50, 250, 100, 100); QRect rect7(50, 350, 50, 50); QRect rect8(50, 400, 50, 50); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, p.image->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, p.image->colorSpace())); tmask->testingInitSelection(tmaskRect, layer2); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, p.image->colorSpace())); layer4->paintDevice()->fill(rect4, KoColor(Qt::yellow, p.image->colorSpace())); layer5->paintDevice()->fill(rect5, KoColor(Qt::green, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::cyan, p.image->colorSpace())); layer7->paintDevice()->fill(rect7, KoColor(Qt::red, p.image->colorSpace())); layer8->paintDevice()->fill(rect8, KoColor(Qt::green, p.image->colorSpace())); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setDistance(10.0); style->dropShadow()->setSpread(80.0); style->dropShadow()->setSize(10); style->dropShadow()->setNoise(0); style->dropShadow()->setKnocksOut(false); style->dropShadow()->setOpacity(80.0); layer2->setLayerStyle(style); layer2->setCompositeOpId(COMPOSITE_ADD); group1->setCompositeOpId(COMPOSITE_ADD); p.image->addNode(layer5); p.image->addNode(layer2); p.image->addNode(tmask, layer2); p.image->addNode(group1); p.image->addNode(layer3, group1); p.image->addNode(layer4, group1); p.image->addNode(layer6); p.image->addNode(layer7); p.image->addNode(layer8); p.image->initialRefreshGraph(); // dbgKrita << ppVar(layer1->exactBounds()); // dbgKrita << ppVar(layer5->exactBounds()); // dbgKrita << ppVar(layer2->exactBounds()); // dbgKrita << ppVar(group1->exactBounds()); // dbgKrita << ppVar(layer3->exactBounds()); // dbgKrita << ppVar(layer4->exactBounds()); TestUtil::ExternalImageChecker chk("flatten", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisTransparencyMaskSP tmask; KisGroupLayerSP group1; KisPaintLayerSP layer3; KisPaintLayerSP layer4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; KisPaintLayerSP layer7; KisPaintLayerSP layer8; }; template KisLayerSP flattenLayerHelper(ContainerTest &p, KisLayerSP layer, bool nothingHappens = false) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); //p.image->flattenLayer(layer); KisLayerUtils::flattenLayer(p.image, layer); p.image->waitForDone(); if (nothingHappens) { Q_ASSERT(!spy.count()); return layer; } Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); KisLayerSP newLayer = qobject_cast(newNode.data()); return newLayer; } void KisImageTest::testFlattenLayer() { FlattenTestImage p; TestUtil::ExternalImageChecker chk("flatten", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.layer2); //KisLayerSP newLayer = p.image->flattenLayer(p.layer2); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); //KisLayerSP newLayer = p.image->flattenLayer(p.group1); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(400, 100, 200, 100)); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.layer5, true); //KisLayerSP newLayer = p.image->flattenLayer(p.layer5); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 50, 100, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), true); } } #include template KisLayerSP mergeHelper(ContainerTest &p, KisLayerSP layer) { KisNodeSP parent = layer->parent(); const int newIndex = parent->index(layer) - 1; p.image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); //KisLayerUtils::mergeDown(p.image, layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); KisLayerSP newLayer = qobject_cast(parent->at(newIndex).data()); return newLayer; } void KisImageTest::testMergeDown() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_simple", "imagetest"); { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 213, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_group1_mergedown_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 500, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationInheritsAlpha() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_dst_inheritsalpha", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); // WARN: this check is suspicious! QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_proj_merged_layer2_over_layer5_IA")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50,50, 263, 267)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationCustomCompositeOp() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_dst_customop", "imagetest"); { QCOMPARE(p.layer6->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer6->alphaChannelDisabled(), false); QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer6); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer6_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOpLayerStyle() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_sameop_ls", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(197, 100, 403, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOp() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_sameop_fastpath", "imagetest"); { QCOMPARE(p.layer8->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer8->alphaChannelDisabled(), false); QCOMPARE(p.layer7->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer7->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer8); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(50, 350, 50, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } #include "kis_image_animation_interface.h" void KisImageTest::testMergeDownMultipleFrames() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergedown_simple", "imagetest"); QSet initialFrames; { KisLayerSP l = p.layer5; l->enableAnimation(); KisKeyframeChannel *channel = l->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); channel->addKeyframe(10); channel->addKeyframe(20); channel->addKeyframe(30); QCOMPARE(channel->keyframeCount(), 4); initialFrames = KisLayerUtils::fetchLayerFramesRecursive(l); QCOMPARE(initialFrames.size(), 4); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); QVERIFY(newLayer->isAnimated()); QSet newFrames = KisLayerUtils::fetchLayerFramesRecursive(newLayer); QCOMPARE(newFrames, initialFrames); foreach (int frame, newFrames) { KisImageAnimationInterface *interface = p.image->animationInterface(); int savedSwitchedTime = 0; interface->saveAndResetCurrentTime(frame, &savedSwitchedTime); QCOMPARE(newLayer->exactBounds(), QRect(100,100,100,100)); interface->restoreCurrentTime(&savedSwitchedTime); } p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } template KisNodeSP mergeMultipleHelper(ContainerTest &p, QList selectedNodes, KisNodeSP putAfter) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); p.image->mergeMultipleLayers(selectedNodes, putAfter); //KisLayerUtils::mergeMultipleLayers(p.image, selectedNodes, putAfter); p.image->waitForDone(); Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); return newNode; } void KisImageTest::testMergeMultiple() { FlattenTestImage p; TestUtil::ExternalImageChecker img("flatten", "imagetest"); TestUtil::ExternalImageChecker chk("mergemultiple", "imagetest"); { QList selectedNodes; selectedNodes << p.layer2 << p.group1 << p.layer6; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } p.p.undoStore->undo(); p.image->waitForDone(); // Test reversed order, the result must be the same { QList selectedNodes; selectedNodes << p.layer6 << p.group1 << p.layer2; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } } void testMergeCrossColorSpaceImpl(bool useProjectionColorSpace, bool swapSpaces) { QRect refRect; TestUtil::MaskParent p; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; const KoColorSpace *cs2 = useProjectionColorSpace ? p.image->colorSpace() : KoColorSpaceRegistry::instance()->lab16(); const KoColorSpace *cs3 = KoColorSpaceRegistry::instance()->rgb16(); if (swapSpaces) { - qSwap(cs2, cs3); + std::swap(cs2, cs3); } dbgKrita << "Testing testMergeCrossColorSpaceImpl:"; dbgKrita << " " << ppVar(cs2); dbgKrita << " " << ppVar(cs3); layer1 = p.layer; layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8, cs2); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8, cs3); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(250, 250, 200, 200); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, layer2->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, layer3->colorSpace())); p.image->addNode(layer2); p.image->addNode(layer3); p.image->initialRefreshGraph(); { KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } { layer2->disableAlphaChannel(true); KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } } void KisImageTest::testMergeCrossColorSpace() { testMergeCrossColorSpaceImpl(true, false); testMergeCrossColorSpaceImpl(true, true); testMergeCrossColorSpaceImpl(false, false); testMergeCrossColorSpaceImpl(false, true); } void KisImageTest::testMergeSelectionMasks() { QRect refRect; TestUtil::MaskParent p; QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(50, 50, 100, 100); KisPaintLayerSP layer1 = p.layer; layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); p.image->initialRefreshGraph(); KisSelectionSP sel = new KisSelection(layer1->paintDevice()->defaultBounds()); sel->pixelSelection()->select(rect2, MAX_SELECTED); KisSelectionMaskSP mask1 = new KisSelectionMask(p.image); mask1->initSelection(sel, layer1); p.image->addNode(mask1, layer1); QVERIFY(!layer1->selection()); mask1->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); sel->pixelSelection()->select(rect3, MAX_SELECTED); KisSelectionMaskSP mask2 = new KisSelectionMask(p.image); mask2->initSelection(sel, layer1); p.image->addNode(mask2, layer1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); mask2->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); QList selectedNodes; selectedNodes << mask2 << mask1; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); QCOMPARE(newLayer->parent(), KisNodeSP(layer1)); QCOMPARE((int)layer1->childCount(), 1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); } } void KisImageTest::testFlattenImage() { FlattenTestImage p; KisImageSP image = p.image; TestUtil::ExternalImageChecker img("flatten", "imagetest"); { KisLayerUtils::flattenImage(p.image); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } struct FlattenPassThroughTestImage { FlattenPassThroughTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); group4 = new KisGroupLayer(p.image, "group4", OPACITY_OPAQUE_U8); layer5 = new KisPaintLayer(p.image, "paint5", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); QRect rect2(100, 100, 100, 100); QRect rect3(150, 150, 100, 100); QRect rect5(200, 200, 100, 100); QRect rect6(250, 250, 100, 100); group1->setPassThroughMode(true); layer2->paintDevice()->fill(rect2, KoColor(Qt::red, p.image->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::green, p.image->colorSpace())); group4->setPassThroughMode(true); layer5->paintDevice()->fill(rect5, KoColor(Qt::blue, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::yellow, p.image->colorSpace())); p.image->addNode(group1); p.image->addNode(layer2, group1); p.image->addNode(layer3, group1); p.image->addNode(group4); p.image->addNode(layer5, group4); p.image->addNode(layer6, group4); p.image->initialRefreshGraph(); TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisGroupLayerSP group1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; KisGroupLayerSP group4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; }; void KisImageTest::testFlattenPassThroughLayer() { FlattenPassThroughTestImage p; TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergeTwoPassThroughLayers() { FlattenPassThroughTestImage p; TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisGroupLayer")); } } void KisImageTest::testMergePaintOverPassThroughLayer() { FlattenPassThroughTestImage p; TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, newLayer); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergePassThroughOverPaintLayer() { FlattenPassThroughTestImage p; TestUtil::ExternalImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } QTEST_MAIN(KisImageTest) diff --git a/libs/libkis/Krita.cpp b/libs/libkis/Krita.cpp index 7a2e47490c..055a29d82a 100644 --- a/libs/libkis/Krita.cpp +++ b/libs/libkis/Krita.cpp @@ -1,394 +1,394 @@ /* * 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 "Krita.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 "View.h" #include "Document.h" #include "Window.h" #include "Extension.h" #include "DockWidgetFactoryBase.h" #include "Filter.h" #include "InfoObject.h" #include "Resource.h" Krita* Krita::s_instance = 0; struct Krita::Private { Private() {} QList extensions; bool batchMode {false}; Notifier *notifier{new Notifier()}; }; Krita::Krita(QObject *parent) : QObject(parent) , d(new Private) { qRegisterMetaType(); } Krita::~Krita() { qDeleteAll(d->extensions); delete d->notifier; delete d; } QList Krita::actions() const { QList actionList; KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return actionList; } KActionCollection *actionCollection = mainWindow->actionCollection(); Q_FOREACH(QAction *action, actionCollection->actions()) { actionList << new Action(action->objectName(), action); } return actionList; } Action *Krita::action(const QString &name) const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } KActionCollection *actionCollection = mainWindow->actionCollection(); QAction *action = actionCollection->action(name); if (action) { return new Action(name, action); } return 0; } Document* Krita::activeDocument() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } KisView *view = mainWindow->activeView(); if (!view) { return 0; } KisDocument *document = view->document(); return new Document(document); } void Krita::setActiveDocument(Document* value) { Q_FOREACH(KisView *view, KisPart::instance()->views()) { if (view->document() == value->document().data()) { view->activateWindow(); break; } } } bool Krita::batchmode() const { return d->batchMode; } void Krita::setBatchmode(bool value) { d->batchMode = value; } QList Krita::documents() const { QList ret; foreach(QPointer doc, KisPart::instance()->documents()) { ret << new Document(doc); } return ret; } QStringList Krita::filters() const { QStringList ls = KisFilterRegistry::instance()->keys(); - qSort(ls); + std::sort(ls.begin(), ls.end()); return ls; } Filter *Krita::filter(const QString &name) const { if (!filters().contains(name)) return 0; Filter *filter = new Filter(); filter->setName(name); KisFilterSP f = KisFilterRegistry::instance()->value(name); KisFilterConfigurationSP fc = f->defaultConfiguration(); InfoObject *info = new InfoObject(fc); filter->setConfiguration(info); return filter; } QStringList Krita::filterStrategies() const { return KisFilterStrategyRegistry::instance()->keys(); } QStringList Krita::profiles(const QString &colorModel, const QString &colorDepth) const { QSet profileNames; QString id = KoColorSpaceRegistry::instance()->colorSpaceId(colorModel, colorDepth); QList profiles = KoColorSpaceRegistry::instance()->profilesFor(id); Q_FOREACH(const KoColorProfile *profile, profiles) { profileNames << profile->name(); } return profileNames.toList(); } bool Krita::addProfile(const QString &profilePath) { KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); return iccEngine->addProfile(profilePath); } Notifier* Krita::notifier() const { return d->notifier; } QString Krita::version() const { return KritaVersionWrapper::versionString(true); } QList Krita::views() const { QList ret; foreach(QPointer view, KisPart::instance()->views()) { ret << new View(view); } return ret; } Window *Krita::activeWindow() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } return new Window(mainWindow); } QList Krita::windows() const { QList ret; foreach(QPointer mainWin, KisPart::instance()->mainWindows()) { ret << new Window(mainWin); } return ret; } QMap Krita::resources(const QString &type) const { QMap resources = QMap (); if (type == "pattern") { KoResourceServer* server = KoResourceServerProvider::instance()->patternServer(); Q_FOREACH (KoResource *res, server->resources()) { resources[res->name()] = new Resource(res); } } else if (type == "gradient") { KoResourceServer* server = KoResourceServerProvider::instance()->gradientServer(); Q_FOREACH (KoResource *res, server->resources()) { resources[res->name()] = new Resource(res); } } else if (type == "brush") { KisBrushResourceServer* server = KisBrushServer::instance()->brushServer(); Q_FOREACH (KisBrushSP res, server->resources()) { resources[res->name()] = new Resource(res.data()); } } else if (type == "preset") { KisPaintOpPresetResourceServer* server = KisResourceServerProvider::instance()->paintOpPresetServer(); Q_FOREACH (KisPaintOpPresetSP res, server->resources()) { resources[res->name()] = new Resource(res.data()); } } else if (type == "palette") { KoResourceServer* server = KoResourceServerProvider::instance()->paletteServer(); Q_FOREACH (KoResource *res, server->resources()) { resources[res->name()] = new Resource(res); } } else if (type == "workspace") { KoResourceServer< KisWorkspaceResource >* server = KisResourceServerProvider::instance()->workspaceServer(); Q_FOREACH (KoResource *res, server->resources()) { resources[res->name()] = new Resource(res); } } return resources; } Document* Krita::createDocument(int width, int height, const QString &name, const QString &colorModel, const QString &colorDepth, const QString &profile) { KisDocument *document = KisPart::instance()->createDocument(); KisPart::instance()->addDocument(document); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, profile); Q_ASSERT(cs); QColor qc(Qt::white); qc.setAlpha(0); KoColor bgColor(qc, cs); if (!document->newImage(name, width, height, cs, bgColor, true, 1, "", 100.0)) { qDebug() << "Could not create a new image"; return 0; } Q_ASSERT(document->image()); qDebug() << document->image()->objectName(); return new Document(document); } Document* Krita::openDocument(const QString &filename) { KisDocument *document = KisPart::instance()->createDocument(); KisPart::instance()->addDocument(document); document->openUrl(QUrl::fromLocalFile(filename), KisDocument::OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES); return new Document(document); } Window* Krita::openWindow() { KisMainWindow *mw = KisPart::instance()->createMainWindow(); return new Window(mw); } Action *Krita::createAction(const QString &id, const QString &text) { KisAction *action = new KisAction(text, this); action->setObjectName(id); KisActionRegistry *actionRegistry = KisActionRegistry::instance(); actionRegistry->propertizeAction(action->objectName(), action); bool ok; // We will skip this check int activationFlags = actionRegistry->getActionProperty(id, "activationFlags").toInt(&ok, 2); int activationConditions = actionRegistry->getActionProperty(id, "activationConditions").toInt(&ok, 2); action->setActivationFlags((KisAction::ActivationFlags) activationFlags); action->setActivationConditions((KisAction::ActivationConditions) activationConditions); KisPart::instance()->addScriptAction(action); return new Action(action->objectName(), action); } void Krita::addExtension(Extension* extension) { d->extensions.append(extension); } QList< Extension* > Krita::extensions() { return d->extensions; } void Krita::writeSetting(const QString &group, const QString &name, const QString &value) { KConfigGroup grp = KSharedConfig::openConfig()->group(group); grp.writeEntry(name, value); } QString Krita::readSetting(const QString &group, const QString &name, const QString &defaultValue) { KConfigGroup grp = KSharedConfig::openConfig()->group(group); return grp.readEntry(name, defaultValue); } void Krita::addDockWidgetFactory(DockWidgetFactoryBase* factory) { KoDockRegistry::instance()->add(factory); } Krita* Krita::instance() { if (!s_instance) { s_instance = new Krita; } return s_instance; } /** * Scripter.fromVariant(variant) * variant is a QVariant * returns instance of QObject-subclass * * This is a helper method for PyQt because PyQt cannot cast a variant to a QObject or QWidget */ QObject *Krita::fromVariant(const QVariant& v) { if (v.canConvert< QWidget* >()) { QObject* obj = qvariant_cast< QWidget* >(v); return obj; } else if (v.canConvert< QObject* >()) { QObject* obj = qvariant_cast< QObject* >(v); return obj; } else return 0; } diff --git a/libs/pigment/KoCompositeOpRegistry.cpp b/libs/pigment/KoCompositeOpRegistry.cpp index d2ac141e2a..76af0764a5 100644 --- a/libs/pigment/KoCompositeOpRegistry.cpp +++ b/libs/pigment/KoCompositeOpRegistry.cpp @@ -1,215 +1,215 @@ /* * Copyright (c) 2005 Adrian Page * * 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 "KoCompositeOpRegistry.h" #include #include #include #include #include "KoCompositeOp.h" #include "KoColorSpace.h" Q_GLOBAL_STATIC(KoCompositeOpRegistry, registry) KoCompositeOpRegistry::KoCompositeOpRegistry() { m_categories << KoID("arithmetic", i18n("Arithmetic")) << KoID("dark" , i18n("Darken")) << KoID("light" , i18n("Lighten")) << KoID("negative" , i18n("Negative")) << KoID("mix" , i18n("Mix")) << KoID("misc" , i18n("Misc")) << KoID("hsy" , i18n("HSY")) << KoID("hsi" , i18n("HSI")) << KoID("hsl" , i18n("HSL")) << KoID("hsv" , i18n("HSV")); m_map.insert(m_categories[0], KoID(COMPOSITE_ADD , i18n("Addition"))); m_map.insert(m_categories[0], KoID(COMPOSITE_SUBTRACT , i18n("Subtract"))); m_map.insert(m_categories[0], KoID(COMPOSITE_MULT , i18n("Multiply"))); m_map.insert(m_categories[0], KoID(COMPOSITE_DIVIDE , i18n("Divide"))); m_map.insert(m_categories[0], KoID(COMPOSITE_INVERSE_SUBTRACT, i18n("Inverse Subtract"))); m_map.insert(m_categories[1], KoID(COMPOSITE_BURN , i18n("Burn"))); m_map.insert(m_categories[1], KoID(COMPOSITE_LINEAR_BURN, i18n("Linear Burn"))); m_map.insert(m_categories[1], KoID(COMPOSITE_DARKEN , i18n("Darken"))); m_map.insert(m_categories[1], KoID(COMPOSITE_GAMMA_DARK , i18n("Gamma Dark"))); m_map.insert(m_categories[1], KoID(COMPOSITE_DARKER_COLOR , i18n("Darker Color"))); m_map.insert(m_categories[2], KoID(COMPOSITE_DODGE , i18n("Color Dodge"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LINEAR_DODGE, i18n("Linear Dodge"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LIGHTEN , i18n("Lighten"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LINEAR_LIGHT, i18n("Linear Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_SCREEN , i18n("Screen"))); m_map.insert(m_categories[2], KoID(COMPOSITE_PIN_LIGHT , i18n("Pin Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_VIVID_LIGHT , i18n("Vivid Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_HARD_LIGHT , i18n("Hard Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_SOFT_LIGHT_PHOTOSHOP, i18n("Soft Light (Photoshop)"))); m_map.insert(m_categories[2], KoID(COMPOSITE_SOFT_LIGHT_SVG, i18n("Soft Light (SVG)"))); m_map.insert(m_categories[2], KoID(COMPOSITE_GAMMA_LIGHT , i18n("Gamma Light"))); m_map.insert(m_categories[2], KoID(COMPOSITE_LIGHTER_COLOR , i18n("Lighter Color"))); m_map.insert(m_categories[3], KoID(COMPOSITE_DIFF , i18n("Difference"))); m_map.insert(m_categories[3], KoID(COMPOSITE_EQUIVALENCE , i18n("Equivalence"))); m_map.insert(m_categories[3], KoID(COMPOSITE_ADDITIVE_SUBTRACTIVE, i18n("Additive Subtractive"))); m_map.insert(m_categories[3], KoID(COMPOSITE_EXCLUSION , i18n("Exclusion"))); m_map.insert(m_categories[3], KoID(COMPOSITE_ARC_TANGENT , i18n("Arcus Tangent"))); m_map.insert(m_categories[4], KoID(COMPOSITE_OVER , i18n("Normal"))); m_map.insert(m_categories[4], KoID(COMPOSITE_BEHIND , i18n("Behind"))); m_map.insert(m_categories[4], KoID(COMPOSITE_GREATER , i18n("Greater"))); m_map.insert(m_categories[4], KoID(COMPOSITE_OVERLAY , i18n("Overlay"))); m_map.insert(m_categories[4], KoID(COMPOSITE_ERASE , i18n("Erase"))); m_map.insert(m_categories[4], KoID(COMPOSITE_ALPHA_DARKEN , i18n("Alpha Darken"))); m_map.insert(m_categories[4], KoID(COMPOSITE_HARD_MIX , i18n("Hard Mix"))); m_map.insert(m_categories[4], KoID(COMPOSITE_GRAIN_MERGE , i18n("Grain Merge"))); m_map.insert(m_categories[4], KoID(COMPOSITE_GRAIN_EXTRACT , i18n("Grain Extract"))); m_map.insert(m_categories[4], KoID(COMPOSITE_PARALLEL , i18n("Parallel"))); m_map.insert(m_categories[4], KoID(COMPOSITE_ALLANON , i18n("Allanon"))); m_map.insert(m_categories[4], KoID(COMPOSITE_GEOMETRIC_MEAN , i18n("Geometric Mean"))); m_map.insert(m_categories[4], KoID(COMPOSITE_DESTINATION_ATOP, i18n("Destination Atop"))); m_map.insert(m_categories[4], KoID(COMPOSITE_DESTINATION_IN , i18n("Destination In"))); m_map.insert(m_categories[4], KoID(COMPOSITE_HARD_OVERLAY , i18n("Hard Overlay"))); m_map.insert(m_categories[5], KoID(COMPOSITE_BUMPMAP , i18n("Bumpmap"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COMBINE_NORMAL, i18n("Combine Normal Map"))); m_map.insert(m_categories[5], KoID(COMPOSITE_DISSOLVE , i18n("Dissolve"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COPY_RED , i18n("Copy Red"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COPY_GREEN, i18n("Copy Green"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COPY_BLUE , i18n("Copy Blue"))); m_map.insert(m_categories[5], KoID(COMPOSITE_COPY , i18n("Copy"))); m_map.insert(m_categories[5], KoID(COMPOSITE_TANGENT_NORMALMAP, i18n("Tangent Normalmap"))); m_map.insert(m_categories[6], KoID(COMPOSITE_COLOR , i18n("Color"))); m_map.insert(m_categories[6], KoID(COMPOSITE_HUE , i18n("Hue"))); m_map.insert(m_categories[6], KoID(COMPOSITE_SATURATION , i18n("Saturation"))); m_map.insert(m_categories[6], KoID(COMPOSITE_LUMINIZE , i18n("Luminosity"))); m_map.insert(m_categories[6], KoID(COMPOSITE_DEC_SATURATION, i18n("Decrease Saturation"))); m_map.insert(m_categories[6], KoID(COMPOSITE_INC_SATURATION, i18n("Increase Saturation"))); m_map.insert(m_categories[6], KoID(COMPOSITE_DEC_LUMINOSITY, i18n("Decrease Luminosity"))); m_map.insert(m_categories[6], KoID(COMPOSITE_INC_LUMINOSITY, i18n("Increase Luminosity"))); m_map.insert(m_categories[7], KoID(COMPOSITE_COLOR_HSI , i18n("Color HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_HUE_HSI , i18n("Hue HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_SATURATION_HSI , i18n("Saturation HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_INTENSITY , i18n("Intensity"))); m_map.insert(m_categories[7], KoID(COMPOSITE_DEC_SATURATION_HSI, i18n("Decrease Saturation HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_INC_SATURATION_HSI, i18n("Increase Saturation HSI"))); m_map.insert(m_categories[7], KoID(COMPOSITE_DEC_INTENSITY , i18n("Decrease Intensity"))); m_map.insert(m_categories[7], KoID(COMPOSITE_INC_INTENSITY , i18n("Increase Intensity"))); m_map.insert(m_categories[8], KoID(COMPOSITE_COLOR_HSL , i18n("Color HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_HUE_HSL , i18n("Hue HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_SATURATION_HSL , i18n("Saturation HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_LIGHTNESS , i18n("Lightness"))); m_map.insert(m_categories[8], KoID(COMPOSITE_DEC_SATURATION_HSL, i18n("Decrease Saturation HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_INC_SATURATION_HSL, i18n("Increase Saturation HSL"))); m_map.insert(m_categories[8], KoID(COMPOSITE_DEC_LIGHTNESS , i18n("Decrease Lightness"))); m_map.insert(m_categories[8], KoID(COMPOSITE_INC_LIGHTNESS , i18n("Increase Lightness"))); m_map.insert(m_categories[9], KoID(COMPOSITE_COLOR_HSV , i18n("Color HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_HUE_HSV , i18n("Hue HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_SATURATION_HSV , i18n("Saturation HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_VALUE , i18n("Value"))); m_map.insert(m_categories[9], KoID(COMPOSITE_DEC_SATURATION_HSV, i18n("Decrease Saturation HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_INC_SATURATION_HSV, i18n("Increase Saturation HSV"))); m_map.insert(m_categories[9], KoID(COMPOSITE_DEC_VALUE , i18n("Decrease Value"))); m_map.insert(m_categories[9], KoID(COMPOSITE_INC_VALUE , i18n("Increase Value"))); } const KoCompositeOpRegistry& KoCompositeOpRegistry::instance() { return *registry; } KoID KoCompositeOpRegistry::getDefaultCompositeOp() const { return KoID(COMPOSITE_OVER, i18n("Normal")); } KoID KoCompositeOpRegistry::getKoID(const QString& compositeOpID) const { - KoIDMap::const_iterator itr = qFind(m_map.begin(), m_map.end(), KoID(compositeOpID)); + KoIDMap::const_iterator itr = std::find(m_map.begin(), m_map.end(), KoID(compositeOpID)); return (itr != m_map.end()) ? *itr : KoID(); } KoCompositeOpRegistry::KoIDMap KoCompositeOpRegistry::getCompositeOps() const { return m_map; } KoCompositeOpRegistry::KoIDList KoCompositeOpRegistry::getCategories() const { return m_categories; } KoCompositeOpRegistry::KoIDList KoCompositeOpRegistry::getCompositeOps(const KoID& category, const KoColorSpace* colorSpace) const { qint32 num = m_map.count(category); KoIDMap::const_iterator beg = m_map.find(category); KoIDMap::const_iterator end = beg + num; KoIDList list; list.reserve(num); if(colorSpace) { for(; beg!=end; ++beg){ if(colorSpace->hasCompositeOp(beg->id())) list.push_back(*beg); } } else { for(; beg!=end; ++beg) list.push_back(*beg); } return list; } KoCompositeOpRegistry::KoIDList KoCompositeOpRegistry::getCompositeOps(const KoColorSpace* colorSpace) const { KoIDMap::const_iterator beg = m_map.begin(); KoIDMap::const_iterator end = m_map.end(); KoIDList list; list.reserve(m_map.size()); if(colorSpace) { for(; beg!=end; ++beg){ if(colorSpace->hasCompositeOp(beg->id())) list.push_back(*beg); } } else { for(; beg!=end; ++beg) list.push_back(*beg); } return list; } bool KoCompositeOpRegistry::colorSpaceHasCompositeOp(const KoColorSpace* colorSpace, const KoID& compositeOp) const { return colorSpace ? colorSpace->hasCompositeOp(compositeOp.id()) : false; } diff --git a/libs/pigment/resources/KoSegmentGradient.cpp b/libs/pigment/resources/KoSegmentGradient.cpp index 8d1cd78a7c..82459de74b 100644 --- a/libs/pigment/resources/KoSegmentGradient.cpp +++ b/libs/pigment/resources/KoSegmentGradient.cpp @@ -1,978 +1,978 @@ /* Copyright (c) 2000 Matthias Elter 2001 John Califf 2004 Boudewijn Rempt 2004 Adrian Page 2004, 2007 Sven Langkamp 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; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColorSpace.h" #include "KoMixColorsOp.h" #include #include #include KoGradientSegment::RGBColorInterpolationStrategy *KoGradientSegment::RGBColorInterpolationStrategy::m_instance = 0; KoGradientSegment::HSVCWColorInterpolationStrategy *KoGradientSegment::HSVCWColorInterpolationStrategy::m_instance = 0; KoGradientSegment::HSVCCWColorInterpolationStrategy *KoGradientSegment::HSVCCWColorInterpolationStrategy::m_instance = 0; KoGradientSegment::LinearInterpolationStrategy *KoGradientSegment::LinearInterpolationStrategy::m_instance = 0; KoGradientSegment::CurvedInterpolationStrategy *KoGradientSegment::CurvedInterpolationStrategy::m_instance = 0; KoGradientSegment::SineInterpolationStrategy *KoGradientSegment::SineInterpolationStrategy::m_instance = 0; KoGradientSegment::SphereIncreasingInterpolationStrategy *KoGradientSegment::SphereIncreasingInterpolationStrategy::m_instance = 0; KoGradientSegment::SphereDecreasingInterpolationStrategy *KoGradientSegment::SphereDecreasingInterpolationStrategy::m_instance = 0; KoSegmentGradient::KoSegmentGradient(const QString& file) : KoAbstractGradient(file) { } KoSegmentGradient::~KoSegmentGradient() { for (int i = 0; i < m_segments.count(); i++) { delete m_segments[i]; m_segments[i] = 0; } } KoSegmentGradient::KoSegmentGradient(const KoSegmentGradient &rhs) : KoAbstractGradient(rhs) { Q_FOREACH (KoGradientSegment *segment, rhs.m_segments) { pushSegment(new KoGradientSegment(*segment)); } } KoAbstractGradient* KoSegmentGradient::clone() const { return new KoSegmentGradient(*this); } bool KoSegmentGradient::load() { QFile file(filename()); if (!file.open(QIODevice::ReadOnly)) { warnPigment << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&file); file.close(); return res; } bool KoSegmentGradient::loadFromDevice(QIODevice *dev) { QByteArray data = dev->readAll(); QTextStream fileContent(data, QIODevice::ReadOnly); fileContent.setAutoDetectUnicode(true); QString header = fileContent.readLine(); if (header != "GIMP Gradient") { return false; } QString nameDefinition = fileContent.readLine(); QString numSegmentsText; if (nameDefinition.startsWith("Name: ")) { QString nameText = nameDefinition.right(nameDefinition.length() - 6); setName(nameText); numSegmentsText = fileContent.readLine(); } else { // Older format without name. numSegmentsText = nameDefinition; } dbgPigment << "Loading gradient: " << name(); int numSegments; bool ok; numSegments = numSegmentsText.toInt(&ok); if (!ok || numSegments < 1) { return false; } dbgPigment << "Number of segments = " << numSegments; const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); for (int i = 0; i < numSegments; i++) { QString segmentText = fileContent.readLine(); QTextStream segmentFields(&segmentText); QStringList values = segmentText.split(' '); qreal leftOffset = values[0].toDouble(); qreal middleOffset = values[1].toDouble(); qreal rightOffset = values[2].toDouble(); qreal leftRed = values[3].toDouble(); qreal leftGreen = values[4].toDouble(); qreal leftBlue = values[5].toDouble(); qreal leftAlpha = values[6].toDouble(); qreal rightRed = values[7].toDouble(); qreal rightGreen = values[8].toDouble(); qreal rightBlue = values[9].toDouble(); qreal rightAlpha = values[10].toDouble(); int interpolationType = values[11].toInt(); int colorInterpolationType = values[12].toInt(); quint8 data[4]; data[2] = static_cast(leftRed * 255 + 0.5); data[1] = static_cast(leftGreen * 255 + 0.5); data[0] = static_cast(leftBlue * 255 + 0.5); data[3] = static_cast(leftAlpha * OPACITY_OPAQUE_U8 + 0.5); KoColor leftColor(data, rgbColorSpace); data[2] = static_cast(rightRed * 255 + 0.5); data[1] = static_cast(rightGreen * 255 + 0.5); data[0] = static_cast(rightBlue * 255 + 0.5); data[3] = static_cast(rightAlpha * OPACITY_OPAQUE_U8 + 0.5); KoColor rightColor(data, rgbColorSpace); KoGradientSegment *segment = new KoGradientSegment(interpolationType, colorInterpolationType, leftOffset, middleOffset, rightOffset, leftColor, rightColor); Q_CHECK_PTR(segment); if (!segment -> isValid()) { delete segment; return false; } m_segments.push_back(segment); } if (!m_segments.isEmpty()) { updatePreview(); setValid(true); return true; } else { return false; } } bool KoSegmentGradient::save() { QFile file(filename()); if (!file.open(QIODevice::WriteOnly)) { return false; } saveToDevice(&file); file.close(); return true; } bool KoSegmentGradient::saveToDevice(QIODevice *dev) const { QTextStream fileContent(dev); fileContent << "GIMP Gradient\n"; fileContent << "Name: " << name() << "\n"; fileContent << m_segments.count() << "\n"; Q_FOREACH (KoGradientSegment* segment, m_segments) { fileContent << QString::number(segment->startOffset(), 'f') << " " << QString::number(segment->middleOffset(), 'f') << " " << QString::number(segment->endOffset(), 'f') << " "; QColor startColor = segment->startColor().toQColor(); QColor endColor = segment->endColor().toQColor(); fileContent << QString::number(startColor.redF(), 'f') << " " << QString::number(startColor.greenF(), 'f') << " " << QString::number(startColor.blueF(), 'f') << " " << QString::number(startColor.alphaF(), 'f') << " "; fileContent << QString::number(endColor.redF(), 'f') << " " << QString::number(endColor.greenF(), 'f') << " " << QString::number(endColor.blueF(), 'f') << " " << QString::number(endColor.alphaF(), 'f') << " "; fileContent << (int)segment->interpolation() << " " << (int)segment->colorInterpolation() << "\n"; } KoResource::saveToDevice(dev); return true; } KoGradientSegment *KoSegmentGradient::segmentAt(qreal t) const { Q_ASSERT(t >= 0 || t <= 1); Q_ASSERT(!m_segments.empty()); for (QList::const_iterator it = m_segments.begin(); it != m_segments.end(); ++it) { if (t > (*it)->startOffset() - DBL_EPSILON && t < (*it)->endOffset() + DBL_EPSILON) { return *it; } } return 0; } void KoSegmentGradient::colorAt(KoColor& dst, qreal t) const { const KoGradientSegment *segment = segmentAt(t); Q_ASSERT(segment != 0); if (segment) { segment->colorAt(dst, t); } } QGradient* KoSegmentGradient::toQGradient() const { QGradient* gradient = new QLinearGradient(); QColor color; Q_FOREACH (KoGradientSegment* segment, m_segments) { segment->startColor().toQColor(&color); gradient->setColorAt(segment->startOffset() , color); segment->endColor().toQColor(&color); gradient->setColorAt(segment->endOffset() , color); } return gradient; } QString KoSegmentGradient::defaultFileExtension() const { return QString(".ggr"); } void KoSegmentGradient::toXML(QDomDocument &doc, QDomElement &gradientElt) const { gradientElt.setAttribute("type", "segment"); Q_FOREACH(KoGradientSegment *segment, this->segments()) { QDomElement segmentElt = doc.createElement("segment"); QDomElement start = doc.createElement("start"); QDomElement end = doc.createElement("end"); segmentElt.setAttribute("start-offset", segment->startOffset()); const KoColor startColor = segment->startColor(); segmentElt.setAttribute("start-bitdepth", startColor.colorSpace()->colorDepthId().id()); startColor.toXML(doc, start); segmentElt.setAttribute("middle-offset", segment->middleOffset()); segmentElt.setAttribute("end-offset", segment->endOffset()); const KoColor endColor = segment->endColor(); segmentElt.setAttribute("end-bitdepth", endColor.colorSpace()->colorDepthId().id()); endColor.toXML(doc, end); segmentElt.setAttribute("interpolation", segment->interpolation()); segmentElt.setAttribute("color-interpolation", segment->colorInterpolation()); segmentElt.appendChild(start); segmentElt.appendChild(end); gradientElt.appendChild(segmentElt); } } KoSegmentGradient KoSegmentGradient::fromXML(const QDomElement &elt) { KoSegmentGradient gradient; QDomElement segmentElt = elt.firstChildElement("segment"); while (!segmentElt.isNull()) { int interpolation = segmentElt.attribute("interpolation", "0.0").toInt(); int colorInterpolation = segmentElt.attribute("color-interpolation", "0.0").toInt(); double startOffset = segmentElt.attribute("start-offset", "0.0").toDouble(); qreal middleOffset = segmentElt.attribute("middle-offset", "0.0").toDouble(); qreal endOffset = segmentElt.attribute("end-offset", "0.0").toDouble(); QDomElement start = segmentElt.firstChildElement("start"); QString startBitdepth = segmentElt.attribute("start-bitdepth", Integer8BitsColorDepthID.id()); QColor left = KoColor::fromXML(start.firstChildElement(), startBitdepth).toQColor(); QString endBitdepth = segmentElt.attribute("end-bitdepth", Integer8BitsColorDepthID.id()); QDomElement end = segmentElt.firstChildElement("end"); QColor right = KoColor::fromXML(end.firstChildElement(), endBitdepth).toQColor(); gradient.createSegment(interpolation, colorInterpolation, startOffset, endOffset, middleOffset, left, right); segmentElt = segmentElt.nextSiblingElement("segment"); } return gradient; } KoGradientSegment::KoGradientSegment(int interpolationType, int colorInterpolationType, qreal startOffset, qreal middleOffset, qreal endOffset, const KoColor& startColor, const KoColor& endColor) { m_interpolator = 0; switch (interpolationType) { case INTERP_LINEAR: m_interpolator = LinearInterpolationStrategy::instance(); break; case INTERP_CURVED: m_interpolator = CurvedInterpolationStrategy::instance(); break; case INTERP_SINE: m_interpolator = SineInterpolationStrategy::instance(); break; case INTERP_SPHERE_INCREASING: m_interpolator = SphereIncreasingInterpolationStrategy::instance(); break; case INTERP_SPHERE_DECREASING: m_interpolator = SphereDecreasingInterpolationStrategy::instance(); break; } m_colorInterpolator = 0; switch (colorInterpolationType) { case COLOR_INTERP_RGB: m_colorInterpolator = RGBColorInterpolationStrategy::instance(); break; case COLOR_INTERP_HSV_CCW: m_colorInterpolator = HSVCCWColorInterpolationStrategy::instance(); break; case COLOR_INTERP_HSV_CW: m_colorInterpolator = HSVCWColorInterpolationStrategy::instance(); break; } if (startOffset < DBL_EPSILON) { m_startOffset = 0; } else if (startOffset > 1 - DBL_EPSILON) { m_startOffset = 1; } else { m_startOffset = startOffset; } if (middleOffset < m_startOffset + DBL_EPSILON) { m_middleOffset = m_startOffset; } else if (middleOffset > 1 - DBL_EPSILON) { m_middleOffset = 1; } else { m_middleOffset = middleOffset; } if (endOffset < m_middleOffset + DBL_EPSILON) { m_endOffset = m_middleOffset; } else if (endOffset > 1 - DBL_EPSILON) { m_endOffset = 1; } else { m_endOffset = endOffset; } m_length = m_endOffset - m_startOffset; if (m_length < DBL_EPSILON) { m_middleT = 0.5; } else { m_middleT = (m_middleOffset - m_startOffset) / m_length; } m_startColor = startColor; m_endColor = endColor; } const KoColor& KoGradientSegment::startColor() const { return m_startColor; } const KoColor& KoGradientSegment::endColor() const { return m_endColor; } qreal KoGradientSegment::startOffset() const { return m_startOffset; } qreal KoGradientSegment::middleOffset() const { return m_middleOffset; } qreal KoGradientSegment::endOffset() const { return m_endOffset; } void KoGradientSegment::setStartOffset(qreal t) { m_startOffset = t; m_length = m_endOffset - m_startOffset; if (m_length < DBL_EPSILON) { m_middleT = 0.5; } else { m_middleT = (m_middleOffset - m_startOffset) / m_length; } } void KoGradientSegment::setMiddleOffset(qreal t) { m_middleOffset = t; if (m_length < DBL_EPSILON) { m_middleT = 0.5; } else { m_middleT = (m_middleOffset - m_startOffset) / m_length; } } void KoGradientSegment::setEndOffset(qreal t) { m_endOffset = t; m_length = m_endOffset - m_startOffset; if (m_length < DBL_EPSILON) { m_middleT = 0.5; } else { m_middleT = (m_middleOffset - m_startOffset) / m_length; } } int KoGradientSegment::interpolation() const { return m_interpolator->type(); } void KoGradientSegment::setInterpolation(int interpolationType) { switch (interpolationType) { case INTERP_LINEAR: m_interpolator = LinearInterpolationStrategy::instance(); break; case INTERP_CURVED: m_interpolator = CurvedInterpolationStrategy::instance(); break; case INTERP_SINE: m_interpolator = SineInterpolationStrategy::instance(); break; case INTERP_SPHERE_INCREASING: m_interpolator = SphereIncreasingInterpolationStrategy::instance(); break; case INTERP_SPHERE_DECREASING: m_interpolator = SphereDecreasingInterpolationStrategy::instance(); break; } } int KoGradientSegment::colorInterpolation() const { return m_colorInterpolator->type(); } void KoGradientSegment::setColorInterpolation(int colorInterpolationType) { switch (colorInterpolationType) { case COLOR_INTERP_RGB: m_colorInterpolator = RGBColorInterpolationStrategy::instance(); break; case COLOR_INTERP_HSV_CCW: m_colorInterpolator = HSVCCWColorInterpolationStrategy::instance(); break; case COLOR_INTERP_HSV_CW: m_colorInterpolator = HSVCWColorInterpolationStrategy::instance(); break; } } void KoGradientSegment::colorAt(KoColor& dst, qreal t) const { Q_ASSERT(t > m_startOffset - DBL_EPSILON && t < m_endOffset + DBL_EPSILON); qreal segmentT; if (m_length < DBL_EPSILON) { segmentT = 0.5; } else { segmentT = (t - m_startOffset) / m_length; } qreal colorT = m_interpolator->valueAt(segmentT, m_middleT); m_colorInterpolator->colorAt(dst, colorT, m_startColor, m_endColor); } bool KoGradientSegment::isValid() const { if (m_interpolator == 0 || m_colorInterpolator == 0) return false; return true; } KoGradientSegment::RGBColorInterpolationStrategy::RGBColorInterpolationStrategy() : m_colorSpace(KoColorSpaceRegistry::instance()->rgb8()) { } KoGradientSegment::RGBColorInterpolationStrategy *KoGradientSegment::RGBColorInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new RGBColorInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } void KoGradientSegment::RGBColorInterpolationStrategy::colorAt(KoColor& dst, qreal t, const KoColor& _start, const KoColor& _end) const { KoColor buffer(m_colorSpace); KoColor start(m_colorSpace); KoColor end(m_colorSpace); KoColor startDummy, endDummy; //hack to get a color space with the bitdepth of the gradients(8bit), but with the colour profile of the image// const KoColorSpace* mixSpace = KoColorSpaceRegistry::instance()->rgb8(dst.colorSpace()->profile()); //convert to the right colorspace for the start and end if we have our mixSpace. if (mixSpace){ startDummy = KoColor(_start, mixSpace); endDummy = KoColor(_end, mixSpace); } else { startDummy = _start; endDummy = _end; } start.fromKoColor(_start); end.fromKoColor(_end); const quint8 *colors[2]; colors[0] = startDummy.data(); colors[1] = endDummy.data(); qint16 colorWeights[2]; colorWeights[0] = static_cast((1.0 - t) * 255 + 0.5); colorWeights[1] = 255 - colorWeights[0]; //check if our mixspace exists, it doesn't at startup. if (mixSpace){ if (*buffer.colorSpace() != *mixSpace) { buffer = KoColor(mixSpace); } mixSpace->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data()); } else { buffer = KoColor(m_colorSpace); m_colorSpace->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data()); } dst.fromKoColor(buffer); } KoGradientSegment::HSVCWColorInterpolationStrategy::HSVCWColorInterpolationStrategy() : m_colorSpace(KoColorSpaceRegistry::instance()->rgb8()) { } KoGradientSegment::HSVCWColorInterpolationStrategy *KoGradientSegment::HSVCWColorInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new HSVCWColorInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } void KoGradientSegment::HSVCWColorInterpolationStrategy::colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const { QColor sc; QColor ec; start.toQColor(&sc); end.toQColor(&ec); int s = static_cast(sc.saturation() + t * (ec.saturation() - sc.saturation()) + 0.5); int v = static_cast(sc.value() + t * (ec.value() - sc.value()) + 0.5); int h; if (ec.hue() < sc.hue()) { h = static_cast(ec.hue() + (1 - t) * (sc.hue() - ec.hue()) + 0.5); } else { h = static_cast(ec.hue() + (1 - t) * (360 - ec.hue() + sc.hue()) + 0.5); if (h > 359) { h -= 360; } } // XXX: added an explicit cast. Is this correct? quint8 opacity = static_cast(sc.alpha() + t * (ec.alpha() - sc.alpha())); QColor result; result.setHsv(h, s, v); result.setAlpha(opacity); dst.fromQColor(result); } KoGradientSegment::HSVCCWColorInterpolationStrategy::HSVCCWColorInterpolationStrategy() : m_colorSpace(KoColorSpaceRegistry::instance()->rgb8()) { } KoGradientSegment::HSVCCWColorInterpolationStrategy *KoGradientSegment::HSVCCWColorInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new HSVCCWColorInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } void KoGradientSegment::HSVCCWColorInterpolationStrategy::colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const { QColor sc; QColor se; start.toQColor(&sc); end.toQColor(&se); int s = static_cast(sc.saturation() + t * (se.saturation() - sc.saturation()) + 0.5); int v = static_cast(sc.value() + t * (se.value() - sc.value()) + 0.5); int h; if (sc.hue() < se.hue()) { h = static_cast(sc.hue() + t * (se.hue() - sc.hue()) + 0.5); } else { h = static_cast(sc.hue() + t * (360 - sc.hue() + se.hue()) + 0.5); if (h > 359) { h -= 360; } } // XXX: Added an explicit static cast quint8 opacity = static_cast(sc.alpha() + t * (se.alpha() - sc.alpha())); QColor result; result.setHsv(h, s, v); result.setAlpha(opacity); dst.fromQColor(result); } KoGradientSegment::LinearInterpolationStrategy *KoGradientSegment::LinearInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new LinearInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::LinearInterpolationStrategy::calcValueAt(qreal t, qreal middle) { Q_ASSERT(t > -DBL_EPSILON && t < 1 + DBL_EPSILON); Q_ASSERT(middle > -DBL_EPSILON && middle < 1 + DBL_EPSILON); qreal value = 0; if (t <= middle) { if (middle < DBL_EPSILON) { value = 0; } else { value = (t / middle) * 0.5; } } else { if (middle > 1 - DBL_EPSILON) { value = 1; } else { value = ((t - middle) / (1 - middle)) * 0.5 + 0.5; } } return value; } qreal KoGradientSegment::LinearInterpolationStrategy::valueAt(qreal t, qreal middle) const { return calcValueAt(t, middle); } KoGradientSegment::CurvedInterpolationStrategy::CurvedInterpolationStrategy() { m_logHalf = log(0.5); } KoGradientSegment::CurvedInterpolationStrategy *KoGradientSegment::CurvedInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new CurvedInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::CurvedInterpolationStrategy::valueAt(qreal t, qreal middle) const { Q_ASSERT(t > -DBL_EPSILON && t < 1 + DBL_EPSILON); Q_ASSERT(middle > -DBL_EPSILON && middle < 1 + DBL_EPSILON); qreal value = 0; if (middle < DBL_EPSILON) { middle = DBL_EPSILON; } value = pow(t, m_logHalf / log(middle)); return value; } KoGradientSegment::SineInterpolationStrategy *KoGradientSegment::SineInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new SineInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::SineInterpolationStrategy::valueAt(qreal t, qreal middle) const { qreal lt = LinearInterpolationStrategy::calcValueAt(t, middle); qreal value = (sin(-M_PI_2 + M_PI * lt) + 1.0) / 2.0; return value; } KoGradientSegment::SphereIncreasingInterpolationStrategy *KoGradientSegment::SphereIncreasingInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new SphereIncreasingInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::SphereIncreasingInterpolationStrategy::valueAt(qreal t, qreal middle) const { qreal lt = LinearInterpolationStrategy::calcValueAt(t, middle) - 1; qreal value = sqrt(1 - lt * lt); return value; } KoGradientSegment::SphereDecreasingInterpolationStrategy *KoGradientSegment::SphereDecreasingInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new SphereDecreasingInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::SphereDecreasingInterpolationStrategy::valueAt(qreal t, qreal middle) const { qreal lt = LinearInterpolationStrategy::calcValueAt(t, middle); qreal value = 1 - sqrt(1 - lt * lt); return value; } void KoSegmentGradient::createSegment(int interpolation, int colorInterpolation, double startOffset, double endOffset, double middleOffset, const QColor & left, const QColor & right) { pushSegment(new KoGradientSegment(interpolation, colorInterpolation, startOffset, middleOffset, endOffset, KoColor(left, colorSpace()), KoColor(right, colorSpace()))); } const QList KoSegmentGradient::getHandlePositions() const { QList handlePositions; handlePositions.push_back(m_segments[0]->startOffset()); for (int i = 0; i < m_segments.count(); i++) { handlePositions.push_back(m_segments[i]->endOffset()); } return handlePositions; } const QList KoSegmentGradient::getMiddleHandlePositions() const { QList middleHandlePositions; for (int i = 0; i < m_segments.count(); i++) { middleHandlePositions.push_back(m_segments[i]->middleOffset()); } return middleHandlePositions; } void KoSegmentGradient::moveSegmentStartOffset(KoGradientSegment* segment, double t) { - QList::iterator it = qFind(m_segments.begin(), m_segments.end(), segment); + QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { if (it == m_segments.begin()) { segment->setStartOffset(0.0); return; } KoGradientSegment* previousSegment = (*(it - 1)); if (t > segment->startOffset()) { if (t > segment->middleOffset()) t = segment->middleOffset(); } else { if (t < previousSegment->middleOffset()) t = previousSegment->middleOffset(); } previousSegment->setEndOffset(t); segment->setStartOffset(t); } } void KoSegmentGradient::moveSegmentEndOffset(KoGradientSegment* segment, double t) { - QList::iterator it = qFind(m_segments.begin(), m_segments.end(), segment); + QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { if (it + 1 == m_segments.end()) { segment->setEndOffset(1.0); return; } KoGradientSegment* followingSegment = (*(it + 1)); if (t < segment->endOffset()) { if (t < segment->middleOffset()) t = segment->middleOffset(); } else { if (t > followingSegment->middleOffset()) t = followingSegment->middleOffset(); } followingSegment->setStartOffset(t); segment->setEndOffset(t); } } void KoSegmentGradient::moveSegmentMiddleOffset(KoGradientSegment* segment, double t) { if (segment) { if (t > segment->endOffset()) segment->setMiddleOffset(segment->endOffset()); else if (t < segment->startOffset()) segment->setMiddleOffset(segment->startOffset()); else segment->setMiddleOffset(t); } } void KoSegmentGradient::splitSegment(KoGradientSegment* segment) { Q_ASSERT(segment != 0); - QList::iterator it = qFind(m_segments.begin(), m_segments.end(), segment); + QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { KoColor midleoffsetColor(segment->endColor().colorSpace()); segment->colorAt(midleoffsetColor, segment->middleOffset()); KoGradientSegment* newSegment = new KoGradientSegment( segment->interpolation(), segment->colorInterpolation(), segment ->startOffset(), (segment->middleOffset() - segment->startOffset()) / 2 + segment->startOffset(), segment->middleOffset(), segment->startColor(), midleoffsetColor); m_segments.insert(it, newSegment); segment->setStartColor(midleoffsetColor); segment->setStartOffset(segment->middleOffset()); segment->setMiddleOffset((segment->endOffset() - segment->startOffset()) / 2 + segment->startOffset()); } } void KoSegmentGradient::duplicateSegment(KoGradientSegment* segment) { Q_ASSERT(segment != 0); - QList::iterator it = qFind(m_segments.begin(), m_segments.end(), segment); + QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { double middlePostionPercentage = (segment->middleOffset() - segment->startOffset()) / segment->length(); double center = segment->startOffset() + segment->length() / 2; KoGradientSegment* newSegment = new KoGradientSegment( segment->interpolation(), segment->colorInterpolation(), segment ->startOffset(), segment->length() / 2 * middlePostionPercentage + segment->startOffset(), center, segment->startColor(), segment->endColor()); m_segments.insert(it, newSegment); segment->setStartOffset(center); segment->setMiddleOffset(segment->length() * middlePostionPercentage + segment->startOffset()); } } void KoSegmentGradient::mirrorSegment(KoGradientSegment* segment) { Q_ASSERT(segment != 0); KoColor tmpColor = segment->startColor(); segment->setStartColor(segment->endColor()); segment->setEndColor(tmpColor); segment->setMiddleOffset(segment->endOffset() - (segment->middleOffset() - segment->startOffset())); if (segment->interpolation() == INTERP_SPHERE_INCREASING) segment->setInterpolation(INTERP_SPHERE_DECREASING); else if (segment->interpolation() == INTERP_SPHERE_DECREASING) segment->setInterpolation(INTERP_SPHERE_INCREASING); if (segment->colorInterpolation() == COLOR_INTERP_HSV_CW) segment->setColorInterpolation(COLOR_INTERP_HSV_CCW); else if (segment->colorInterpolation() == COLOR_INTERP_HSV_CCW) segment->setColorInterpolation(COLOR_INTERP_HSV_CW); } KoGradientSegment* KoSegmentGradient::removeSegment(KoGradientSegment* segment) { Q_ASSERT(segment != 0); if (m_segments.count() < 2) return 0; - QList::iterator it = qFind(m_segments.begin(), m_segments.end(), segment); + QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { double middlePostionPercentage; KoGradientSegment* nextSegment; if (it == m_segments.begin()) { nextSegment = (*(it + 1)); middlePostionPercentage = (nextSegment->middleOffset() - nextSegment->startOffset()) / nextSegment->length(); nextSegment->setStartOffset(segment->startOffset()); nextSegment->setMiddleOffset(middlePostionPercentage * nextSegment->length() + nextSegment->startOffset()); } else { nextSegment = (*(it - 1)); middlePostionPercentage = (nextSegment->middleOffset() - nextSegment->startOffset()) / nextSegment->length(); nextSegment->setEndOffset(segment->endOffset()); nextSegment->setMiddleOffset(middlePostionPercentage * nextSegment->length() + nextSegment->startOffset()); } delete segment; m_segments.erase(it); return nextSegment; } return 0; } bool KoSegmentGradient::removeSegmentPossible() const { if (m_segments.count() < 2) return false; return true; } const QList& KoSegmentGradient::segments() const { return m_segments; } diff --git a/libs/ui/KisMultiFeedRSSModel.cpp b/libs/ui/KisMultiFeedRSSModel.cpp index 071221197e..5a8c234c72 100644 --- a/libs/ui/KisMultiFeedRSSModel.cpp +++ b/libs/ui/KisMultiFeedRSSModel.cpp @@ -1,209 +1,209 @@ /************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** ** GNU Lesser General Public License Usage ** ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this file. ** Please review the following information to ensure the GNU Lesser General ** Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** Other Usage ** ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** **************************************************************************/ #include "KisMultiFeedRSSModel.h" #include #include #include #include #include #include #include #include QString shortenHtml(QString html) { html.replace(QLatin1String("")); uint firstParaEndHtml = (uint) html.indexOf(QLatin1String("

"), html.indexOf(QLatin1String("

"))+1); uint firstParaEndBr = (uint) html.indexOf(QLatin1String("request().url(); requestUrl = source.toString(); streamReader.setDevice(reply); RssItemList list; while (!streamReader.atEnd()) { switch (streamReader.readNext()) { case QXmlStreamReader::StartElement: if (streamReader.name() == QLatin1String("item")) list.append(parseItem()); else if (streamReader.name() == QLatin1String("title")) blogName = streamReader.readElementText(); else if (streamReader.name() == QLatin1String("link")) { if (!streamReader.namespaceUri().isEmpty()) break; QString favIconString(streamReader.readElementText()); QUrl favIconUrl(favIconString); favIconUrl.setPath(QLatin1String("favicon.ico")); blogIcon = favIconUrl.toString(); blogIcon = QString(); // XXX: fix the favicon on krita.org! } break; default: break; } } return list; } private: QXmlStreamReader streamReader; QString requestUrl; QString blogIcon; QString blogName; }; MultiFeedRssModel::MultiFeedRssModel(QObject *parent) : QAbstractListModel(parent), m_networkAccessManager(new KisNetworkAccessManager), m_articleCount(0) { connect(m_networkAccessManager, SIGNAL(finished(QNetworkReply*)), SLOT(appendFeedData(QNetworkReply*)), Qt::QueuedConnection); QHash roleNames; roleNames[TitleRole] = "title"; roleNames[DescriptionRole] = "description"; roleNames[PubDateRole] = "pubDate"; roleNames[LinkRole] = "link"; roleNames[BlogNameRole] = "blogName"; roleNames[BlogIconRole] = "blogIcon"; setRoleNames(roleNames); } MultiFeedRssModel::~MultiFeedRssModel() { } void MultiFeedRssModel::addFeed(const QString& feed) { const QUrl feedUrl(feed); QMetaObject::invokeMethod(m_networkAccessManager, "getUrl", Qt::QueuedConnection, Q_ARG(QUrl, feedUrl)); } bool sortForPubDate(const RssItem& item1, const RssItem& item2) { return item1.pubDate > item2.pubDate; } void MultiFeedRssModel::appendFeedData(QNetworkReply *reply) { RssReader reader; m_aggregatedFeed.append(reader.parse(reply)); - qSort(m_aggregatedFeed.begin(), m_aggregatedFeed.end(), sortForPubDate); + std::sort(m_aggregatedFeed.begin(), m_aggregatedFeed.end(), sortForPubDate); setArticleCount(m_aggregatedFeed.size()); reset(); } void MultiFeedRssModel::removeFeed(const QString &feed) { QMutableListIterator it(m_aggregatedFeed); while (it.hasNext()) { RssItem item = it.next(); if (item.source == feed) it.remove(); } setArticleCount(m_aggregatedFeed.size()); } int MultiFeedRssModel::rowCount(const QModelIndex &) const { return m_aggregatedFeed.size(); } QVariant MultiFeedRssModel::data(const QModelIndex &index, int role) const { RssItem item = m_aggregatedFeed.at(index.row()); switch (role) { case Qt::DisplayRole: // fall through case TitleRole: return item.title; case DescriptionRole: return item.description; case PubDateRole: return item.pubDate.toString("dd-MM-yyyy hh:mm"); case LinkRole: return item.link; case BlogNameRole: return item.blogName; case BlogIconRole: return item.blogIcon; } return QVariant(); } diff --git a/libs/ui/KisPaletteModel.cpp b/libs/ui/KisPaletteModel.cpp index d489a585d6..fc18a79964 100644 --- a/libs/ui/KisPaletteModel.cpp +++ b/libs/ui/KisPaletteModel.cpp @@ -1,617 +1,625 @@ /* * Copyright (c) 2013 Sven Langkamp * * 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 "KisPaletteModel.h" #include #include #include #include #include #include #include #include #include #include KisPaletteModel::KisPaletteModel(QObject* parent) : QAbstractTableModel(parent), m_colorSet(0), m_displayRenderer(KoDumbColorDisplayRenderer::instance()) { } KisPaletteModel::~KisPaletteModel() { } void KisPaletteModel::setDisplayRenderer(KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { if (m_displayRenderer) { disconnect(m_displayRenderer, 0, this, 0); } m_displayRenderer = displayRenderer; connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()), SLOT(slotDisplayConfigurationChanged())); } else { m_displayRenderer = KoDumbColorDisplayRenderer::instance(); } } void KisPaletteModel::slotDisplayConfigurationChanged() { reset(); } QModelIndex KisPaletteModel::getLastEntryIndex() { int endRow = rowCount(); int endColumn = columnCount(); if (m_colorSet->nColors()>0) { QModelIndex i = this->index(endRow, endColumn, QModelIndex()); while (qVariantValue(i.data(RetrieveEntryRole)).isEmpty()) { i = this->index(endRow, endColumn); endColumn -=1; if (endColumn<0) { endColumn = columnCount(); endRow-=1; } } return i; } return QModelIndex(); } QVariant KisPaletteModel::data(const QModelIndex& index, int role) const { KoColorSetEntry entry; if (m_colorSet && m_displayRenderer) { //now to figure out whether we have a groupname row or not. bool groupNameRow = false; quint32 indexInGroup = 0; QString indexGroupName = QString(); int rowstotal = m_colorSet->nColorsGroup()/columnCount(); if (index.row()<=rowstotal && (quint32)(index.row()*columnCount()+index.column())nColorsGroup()) { indexInGroup = (quint32)(index.row()*columnCount()+index.column()); } if (m_colorSet->nColorsGroup()==0) { rowstotal+=1; //always add one for the default group when considering groups. } Q_FOREACH (QString groupName, m_colorSet->getGroupNames()){ //we make an int for the rows added by the current group. int newrows = 1+m_colorSet->nColorsGroup(groupName)/columnCount(); if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) { newrows+=1; } if (newrows==0) { newrows+=1; //always add one for the group when considering groups. } quint32 tempIndex = (quint32)((index.row()-(rowstotal+2))*columnCount()+index.column()); if (index.row() == rowstotal+1) { //rowstotal+1 is taken up by the groupname. indexGroupName = groupName; groupNameRow = true; } else if (index.row() > (rowstotal+1) && index.row() <= rowstotal+newrows && tempIndexnColorsGroup(groupName)){ //otherwise it's an index to the colors in the group. indexGroupName = groupName; indexInGroup = tempIndex; } //add the new rows to the totalrows we've looked at. rowstotal += newrows; } if (groupNameRow) { switch (role) { case Qt::ToolTipRole: case Qt::DisplayRole: { return indexGroupName; } case IsHeaderRole: { return true; } case RetrieveEntryRole: { QStringList entryList; entryList.append(indexGroupName); entryList.append(QString::number(0)); return entryList; } } } else { if (indexInGroup < m_colorSet->nColorsGroup(indexGroupName)) { entry = m_colorSet->getColorGroup(indexInGroup, indexGroupName); switch (role) { case Qt::ToolTipRole: case Qt::DisplayRole: { return entry.name; } case Qt::BackgroundRole: { QColor color = m_displayRenderer->toQColor(entry.color); return QBrush(color); } case IsHeaderRole: { return false; } case RetrieveEntryRole: { QStringList entryList; entryList.append(indexGroupName); entryList.append(QString::number(indexInGroup)); return entryList; } } } } } return QVariant(); } int KisPaletteModel::rowCount(const QModelIndex& /*parent*/) const { if (!m_colorSet) { return 0; } if (columnCount() > 0) { int countedrows = m_colorSet->nColorsGroup("")/columnCount(); if (m_colorSet->nColorsGroup()%columnCount() > 0) { countedrows+=1; } if (m_colorSet->nColorsGroup()==0) { countedrows+=1; } Q_FOREACH (QString groupName, m_colorSet->getGroupNames()) { countedrows += 1; //add one for the name; countedrows += 1+(m_colorSet->nColorsGroup(groupName)/ columnCount()); if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) { countedrows+=1; } if (m_colorSet->nColorsGroup(groupName)==0) { countedrows+=1; } } countedrows +=1; //Our code up till now doesn't take 0 into account. return countedrows; } return m_colorSet->nColors()/15 + 1; } int KisPaletteModel::columnCount(const QModelIndex& /*parent*/) const { if (m_colorSet && m_colorSet->columnCount() > 0) { return m_colorSet->columnCount(); } return 15; } Qt::ItemFlags KisPaletteModel::flags(const QModelIndex& index) const { if (index.isValid()) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } return Qt::ItemIsDropEnabled; } QModelIndex KisPaletteModel::index(int row, int column, const QModelIndex& parent) const { if (m_colorSet) { //make an int to hold the amount of rows we've looked at. The initial is the total rows in the default group. int rowstotal = m_colorSet->nColorsGroup()/columnCount(); if (row<=rowstotal && (quint32)(row*columnCount()+column)nColorsGroup()) { //if the total rows are in the default group, we just return an index. return QAbstractTableModel::index(row, column, parent); } else if(row<0 && column<0) { return QAbstractTableModel::index(0, 0, parent); } if (m_colorSet->nColorsGroup()==0) { rowstotal+=1; //always add one for the default group when considering groups. } Q_FOREACH (QString groupName, m_colorSet->getGroupNames()){ //we make an int for the rows added by the current group. int newrows = 1+m_colorSet->nColorsGroup(groupName)/columnCount(); if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) { newrows+=1; } if (m_colorSet->nColorsGroup(groupName)==0) { newrows+=1; //always add one for the group when considering groups. } if (rowstotal + newrows>rowCount()) { newrows = rowCount() - rowstotal; } quint32 tempIndex = (quint32)((row-(rowstotal+2))*columnCount()+column); if (row == rowstotal+1) { //rowstotal+1 is taken up by the groupname. return QAbstractTableModel::index(row, 0, parent); } else if (row > (rowstotal+1) && row <= rowstotal+newrows && tempIndexnColorsGroup(groupName)){ //otherwise it's an index to the colors in the group. return QAbstractTableModel::index(row, column, parent); } //add the new rows to the totalrows we've looked at. rowstotal += newrows; } } return QModelIndex(); } void KisPaletteModel::setColorSet(KoColorSet* colorSet) { m_colorSet = colorSet; reset(); } KoColorSet* KisPaletteModel::colorSet() const { return m_colorSet; } QModelIndex KisPaletteModel::indexFromId(int i) const { QModelIndex index = QModelIndex(); + if (colorSet()->nColors()==0) { + return index; + } if (i > colorSet()->nColors()) { - qWarning()<<"index is too big"<nColors(); index = this->index(0,0); } if (i < (int)colorSet()->nColorsGroup(0)) { index = QAbstractTableModel::index(i/columnCount(), i%columnCount()); if (!index.isValid()) { index = QAbstractTableModel::index(0,0,QModelIndex()); } return index; } else { int rowstotal = 1+m_colorSet->nColorsGroup()/columnCount(); if (m_colorSet->nColorsGroup()==0) { rowstotal +=1; } int totalIndexes = colorSet()->nColorsGroup(); Q_FOREACH (QString groupName, m_colorSet->getGroupNames()){ if (i+1<=totalIndexes+colorSet()->nColorsGroup(groupName) && i+1>totalIndexes) { int col = (i-totalIndexes)%columnCount(); int row = rowstotal+1+((i-totalIndexes)/columnCount()); index = this->index(row, col); return index; } else { rowstotal += 1+m_colorSet->nColorsGroup(groupName)/columnCount(); totalIndexes += colorSet()->nColorsGroup(groupName); if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) { rowstotal+=1; } if (m_colorSet->nColorsGroup(groupName)==0) { rowstotal+=1; //always add one for the group when considering groups. } } } } return index; } int KisPaletteModel::idFromIndex(const QModelIndex &index) const { - if (index.isValid()==false || qVariantValue(index.data(IsHeaderRole))) { + if (index.isValid()==false) { return -1; + qWarning()<<"invalid index"; } int i=0; QStringList entryList = qVariantValue(data(index, RetrieveEntryRole)); + if (entryList.isEmpty()) { + return -1; + qWarning()<<"invalid index, there's no data to retreive here"; + } if (entryList.at(0)==QString()) { return entryList.at(1).toUInt(); } i = colorSet()->nColorsGroup(""); //find at which position the group is. int groupIndex = colorSet()->getGroupNames().indexOf(entryList.at(0)); //add all the groupsizes onto it till we get to our group. for(int g=0; gnColorsGroup(colorSet()->getGroupNames().at(g)); } //then add the index. i += entryList.at(1).toUInt(); return i; } KoColorSetEntry KisPaletteModel::colorSetEntryFromIndex(const QModelIndex &index) const { QStringList entryList = qVariantValue(data(index, RetrieveEntryRole)); QString groupName = entryList.at(0); quint32 indexInGroup = entryList.at(1).toUInt(); return m_colorSet->getColorGroup(indexInGroup, groupName); } bool KisPaletteModel::addColorSetEntry(KoColorSetEntry entry, QString groupName) { int col = m_colorSet->nColorsGroup(groupName)%columnCount(); QModelIndex i = getLastEntryIndex(); if (col+1>columnCount()) { beginInsertRows(QModelIndex(), i.row(), i.row()+1); } if (m_colorSet->nColors()nColors(), m_colorSet->nColors()+1); } m_colorSet->add(entry, groupName); if (col+1>columnCount()) { endInsertRows(); } if (m_colorSet->nColors()(index.data(RetrieveEntryRole)); if (entryList.empty()) { return false; } QString groupName = entryList.at(0); quint32 indexInGroup = entryList.at(1).toUInt(); if (qVariantValue(index.data(IsHeaderRole))==false) { if (index.column()-1<0 && m_colorSet->nColorsGroup(groupName)%columnCount() <1 && index.row()-1>0 && m_colorSet->nColorsGroup(groupName)/columnCount()>0) { beginRemoveRows(QModelIndex(), index.row(), index.row()-1); } m_colorSet->removeAt(indexInGroup, groupName); if (index.column()-1<0 && m_colorSet->nColorsGroup(groupName)%columnCount() <1 && index.row()-1>0 && m_colorSet->nColorsGroup(groupName)/columnCount()>0) { endRemoveRows(); } } else { beginRemoveRows(QModelIndex(), index.row(), index.row()-1); m_colorSet->removeGroup(groupName, keepColors); endRemoveRows(); } return true; } bool KisPaletteModel::addGroup(QString groupName) { QModelIndex i = getLastEntryIndex(); beginInsertRows(QModelIndex(), i.row(), i.row()+1); m_colorSet->addGroup(groupName); endInsertRows(); return true; } bool KisPaletteModel::removeRows(int row, int count, const QModelIndex &parent) { Q_ASSERT(!parent.isValid()); int beginRow = qMax(0, row); int endRow = qMin(row + count - 1, (int)m_colorSet->nColors() - 1); beginRemoveRows(parent, beginRow, endRow); // Find the palette entry at row, count, remove from KoColorSet endRemoveRows(); return true; } bool KisPaletteModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (!data->hasFormat("krita/x-colorsetentry") && !data->hasFormat("krita/x-colorsetgroup")) { return false; } if (action == Qt::IgnoreAction) { return false; } int endRow; int endColumn; if (!parent.isValid()) { if (row < 0) { endRow = indexFromId(m_colorSet->nColors()).row(); endColumn = indexFromId(m_colorSet->nColors()).column(); } else { endRow = qMin(row, indexFromId(m_colorSet->nColors()).row()); endColumn = qMin(column, m_colorSet->columnCount()); } } else { endRow = qMin(parent.row(), rowCount()); endColumn = qMin(parent.column(), columnCount()); } if (data->hasFormat("krita/x-colorsetgroup")) { QByteArray encodedData = data->data("krita/x-colorsetgroup"); QDataStream stream(&encodedData, QIODevice::ReadOnly); while (!stream.atEnd()) { QString groupName; stream >> groupName; QModelIndex index = this->index(endRow, 0); if (index.isValid()) { QStringList entryList = qVariantValue(index.data(RetrieveEntryRole)); QString groupDroppedOn = QString(); if (!entryList.isEmpty()) { groupDroppedOn = entryList.at(0); } int groupIndex = colorSet()->getGroupNames().indexOf(groupName); beginMoveRows( QModelIndex(), groupIndex, groupIndex, QModelIndex(), endRow); m_colorSet->moveGroup(groupName, groupDroppedOn); m_colorSet->save(); endMoveRows(); ++endRow; } } } else { QByteArray encodedData = data->data("krita/x-colorsetentry"); QDataStream stream(&encodedData, QIODevice::ReadOnly); while (!stream.atEnd()) { KoColorSetEntry entry; QString oldGroupName; int indexInGroup; QString colorXml; stream >> entry.name >> entry.id >> entry.spotColor >> indexInGroup >> oldGroupName >> colorXml; QDomDocument doc; doc.setContent(colorXml); QDomElement e = doc.documentElement(); QDomElement c = e.firstChildElement(); if (!c.isNull()) { QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id()); entry.color = KoColor::fromXML(c, colorDepthId); } QModelIndex index = this->index(endRow, endColumn); if (qVariantValue(index.data(IsHeaderRole))){ endRow+=1; } if (index.isValid()) { /*this is to figure out the row of the old color. * That way we can in turn avoid moverows from complaining the * index is out of bounds when using index. * Makes me wonder if we shouldn't just insert the index of the * old color when requesting the mimetype... */ int i = indexInGroup; if (oldGroupName != QString()) { colorSet()->nColorsGroup(""); //find at which position the group is. int groupIndex = colorSet()->getGroupNames().indexOf(oldGroupName); //add all the groupsizes onto it till we get to our group. for(int g=0; gnColorsGroup(colorSet()->getGroupNames().at(g)); } } QModelIndex indexOld = indexFromId(i); if (action == Qt::MoveAction){ if (indexOld.row()!=qMax(endRow, 0) && indexOld.row()!=qMax(endRow+1,1)) { beginMoveRows(QModelIndex(), indexOld.row(), indexOld.row(), QModelIndex(), qMax(endRow+1,1)); } if (indexOld.column()!=qMax(endColumn, 0) && indexOld.column()!=qMax(endColumn+1,1)) { beginMoveColumns(QModelIndex(), indexOld.column(), indexOld.column(), QModelIndex(), qMax(endColumn+1,1)); } } else { beginInsertRows(QModelIndex(), endRow, endRow); } QStringList entryList = qVariantValue(index.data(RetrieveEntryRole)); QString entryInGroup = "0"; QString groupName = QString(); if (!entryList.isEmpty()) { groupName = entryList.at(0); entryInGroup = entryList.at(1); } int location = entryInGroup.toInt(); // Insert the entry if (groupName==oldGroupName && qVariantValue(index.data(IsHeaderRole))==true) { groupName=QString(); location=m_colorSet->nColorsGroup(); } m_colorSet->insertBefore(entry, location, groupName); if (groupName==oldGroupName && locationremoveAt(indexInGroup, oldGroupName); } m_colorSet->save(); if (action == Qt::MoveAction){ if (indexOld.row()!=qMax(endRow, 0) && indexOld.row()!=qMax(endRow+1,1)) { endMoveRows(); } if (indexOld.column()!=qMax(endColumn, 0) && indexOld.column()!=qMax(endColumn+1,1)) { endMoveColumns(); } } else { endInsertRows(); } ++endRow; } } } return true; } QMimeData *KisPaletteModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); QString mimeTypeName = "krita/x-colorsetentry"; //Q_FOREACH(const QModelIndex &index, indexes) { QModelIndex index = indexes.last(); if (index.isValid()) { if (qVariantValue(index.data(IsHeaderRole))==false) { KoColorSetEntry entry = colorSetEntryFromIndex(index); QStringList entryList = qVariantValue(index.data(RetrieveEntryRole)); QString groupName = QString(); int indexInGroup = 0; if (!entryList.isEmpty()) { groupName = entryList.at(0); QString iig = entryList.at(1); indexInGroup = iig.toInt(); } QDomDocument doc; QDomElement root = doc.createElement("Color"); root.setAttribute("bitdepth", entry.color.colorSpace()->colorDepthId().id()); doc.appendChild(root); entry.color.toXML(doc, root); stream << entry.name << entry.id << entry.spotColor << indexInGroup << groupName << doc.toString(); } else { mimeTypeName = "krita/x-colorsetgroup"; QStringList entryList = qVariantValue(index.data(RetrieveEntryRole)); QString groupName = QString(); if (!entryList.isEmpty()) { groupName = entryList.at(0); } stream << groupName; } } mimeData->setData(mimeTypeName, encodedData); return mimeData; } QStringList KisPaletteModel::mimeTypes() const { return QStringList() << "krita/x-colorsetentry" << "krita/x-colorsetgroup"; } Qt::DropActions KisPaletteModel::supportedDropActions() const { return Qt::MoveAction; } diff --git a/libs/ui/dialogs/kis_dlg_png_import.cpp b/libs/ui/dialogs/kis_dlg_png_import.cpp index b976b60090..601666d3f4 100644 --- a/libs/ui/dialogs/kis_dlg_png_import.cpp +++ b/libs/ui/dialogs/kis_dlg_png_import.cpp @@ -1,64 +1,64 @@ /* * Copyright (C) 2015 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 "kis_dlg_png_import.h" #include #include #include #include #include #include #include "widgets/squeezedcombobox.h" #include "kis_config.h" KisDlgPngImport::KisDlgPngImport(const QString &path, const QString &colorModelID, const QString &colorDepthID, QWidget *parent) : KoDialog(parent) { setButtons(Ok); setDefaultButton(Ok); QWidget *page = new QWidget(this); dlgWidget.setupUi(page); setMainWidget(page); dlgWidget.lblFilename->setText(path); const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(colorModelID, colorDepthID); dlgWidget.cmbProfile->clear(); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); QStringList profileNames; Q_FOREACH (const KoColorProfile *profile, profileList) { profileNames.append(profile->name()); } - qSort(profileNames); + std::sort(profileNames.begin(), profileNames.end()); Q_FOREACH (QString stringName, profileNames) { dlgWidget.cmbProfile->addSqueezedItem(stringName); } KisConfig cfg; QString profile = cfg.readEntry("pngImportProfile", KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId)); dlgWidget.cmbProfile->setCurrent(profile); } QString KisDlgPngImport::profile() const { QString p = dlgWidget.cmbProfile->itemHighlighted(); KisConfig cfg; cfg.writeEntry("pngImportProfile", p); return p; } diff --git a/libs/ui/flake/kis_shape_layer.cc b/libs/ui/flake/kis_shape_layer.cc index 34f64d64c4..4333422880 100644 --- a/libs/ui/flake/kis_shape_layer.cc +++ b/libs/ui/flake/kis_shape_layer.cc @@ -1,643 +1,643 @@ /* * 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); /** * The shape is always added with the absolute transformation set appropiately. * Here we should just squeeze it into the layer's transformation. */ KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(0); child->applyAbsoluteTransformation(parentTransform.inverted()); } } void remove(KoShape *child) override { KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(0); child->applyAbsoluteTransformation(parentTransform); } SimpleShapeContainerModel::remove(child); } void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree) override { q->shapeManager()->addShape(shape); SimpleShapeContainerModel::shapeHasBeenAddedToHierarchy(shape, addedToSubtree); } void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree) override { q->shapeManager()->remove(shape); SimpleShapeContainerModel::shapeToBeRemovedFromHierarchy(shape, removedFromSubtree); } private: KisShapeLayer *q; }; struct KisShapeLayer::Private { public: Private() : canvas(0) , controller(0) , x(0) , y(0) {} KisPaintDeviceSP paintDevice; 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::forceUpdateTimedNode() { 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); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes); writer.save(storeDev, sizeInPt); if (!store->close()) { return false; } return true; } QList KisShapeLayer::createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize) { QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc; 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/libs/ui/input/config/kis_input_mode_delegate.cpp b/libs/ui/input/config/kis_input_mode_delegate.cpp index be63d8996a..58e480a820 100644 --- a/libs/ui/input/config/kis_input_mode_delegate.cpp +++ b/libs/ui/input/config/kis_input_mode_delegate.cpp @@ -1,80 +1,80 @@ /* * This file is part of the KDE project * Copyright (C) 2013 Arjen Hiemstra * * 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_input_mode_delegate.h" #include "../kis_abstract_input_action.h" #include #include class KisInputModeDelegate::Private { public: Private() { } KisAbstractInputAction *action; }; KisInputModeDelegate::KisInputModeDelegate(QObject *parent) : QStyledItemDelegate(parent), d(new Private) { } KisInputModeDelegate::~KisInputModeDelegate() { delete d; } QWidget *KisInputModeDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const { KComboBox *combo = new KComboBox(parent); QStringList sorted = d->action->shortcutIndexes().keys(); - qSort(sorted); + std::sort(sorted.begin(), sorted.end()); combo->addItems(sorted); return combo; } void KisInputModeDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { KComboBox *combo = qobject_cast(editor); Q_ASSERT(combo); int i = combo->findText(d->action->shortcutIndexes().key(index.data(Qt::EditRole).toUInt())); combo->setCurrentIndex(i); } void KisInputModeDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { KComboBox *combo = qobject_cast(editor); Q_ASSERT(combo); int i = d->action->shortcutIndexes().value(combo->currentText()); model->setData(index, i, Qt::EditRole); } void KisInputModeDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const { editor->setGeometry(option.rect); } void KisInputModeDelegate::setAction(KisAbstractInputAction *action) { d->action = action; } diff --git a/libs/ui/kis_bookmarked_configurations_model.cc b/libs/ui/kis_bookmarked_configurations_model.cc index dacb0373d5..f8f432f3b2 100644 --- a/libs/ui/kis_bookmarked_configurations_model.cc +++ b/libs/ui/kis_bookmarked_configurations_model.cc @@ -1,161 +1,161 @@ /* * Copyright (c) 2007 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_bookmarked_configurations_model.h" #include #include #include #include #include struct KisBookmarkedConfigurationsModel::Private { KisBookmarkedConfigurationManager* bookmarkManager; QList configsKey; }; KisBookmarkedConfigurationsModel::KisBookmarkedConfigurationsModel(KisBookmarkedConfigurationManager* bm) : d(new Private) { d->bookmarkManager = bm; d->configsKey = d->bookmarkManager->configurations(); - qSort(d->configsKey); + std::sort(d->configsKey.begin(), d->configsKey.end()); } KisBookmarkedConfigurationsModel::~KisBookmarkedConfigurationsModel() { delete d; } KisBookmarkedConfigurationManager* KisBookmarkedConfigurationsModel::bookmarkedConfigurationManager() { return d->bookmarkManager; } int KisBookmarkedConfigurationsModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 2 + d->configsKey.size(); } QVariant KisBookmarkedConfigurationsModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (role == Qt::DisplayRole || role == Qt::EditRole) { switch (index.row()) { case 0: return i18n("Default"); case 1: return i18n("Last Used"); default: return d->configsKey[ index.row() - 2 ]; } } return QVariant(); } bool KisBookmarkedConfigurationsModel::setData(const QModelIndex & index, const QVariant & value, int role) { if (role == Qt::EditRole && index.row() >= 2) { QString name = value.toString(); int idx = index.row() - 2; KisSerializableConfigurationSP config = d->bookmarkManager->load(d->configsKey[idx]); d->bookmarkManager->remove(d->configsKey[idx]); d->bookmarkManager->save(name, config); d->configsKey[idx] = name; emit(dataChanged(index, index)); return true; } return false; } KisSerializableConfigurationSP KisBookmarkedConfigurationsModel::configuration(const QModelIndex &index) const { if (!index.isValid()) return 0; switch (index.row()) { case 0: dbgKrita << "loading default" << endl; return d->bookmarkManager->load(KisBookmarkedConfigurationManager::ConfigDefault); break; case 1: return d->bookmarkManager->load(KisBookmarkedConfigurationManager::ConfigLastUsed); break; default: return d->bookmarkManager->load(d->configsKey[ index.row() - 2 ]); } } QModelIndex KisBookmarkedConfigurationsModel::indexFor(const QString& name) const { int idx = d->configsKey.indexOf(name); if (idx == -1) return QModelIndex(); return createIndex(idx + 2, 0); } bool KisBookmarkedConfigurationsModel::isIndexDeletable(const QModelIndex &index) const { if (!index.isValid() || index.row() < 2) return false; return true; } void KisBookmarkedConfigurationsModel::newConfiguration(KLocalizedString baseName, const KisSerializableConfigurationSP config) { saveConfiguration(d->bookmarkManager->uniqueName(baseName), config); } void KisBookmarkedConfigurationsModel::saveConfiguration(const QString & name, const KisSerializableConfigurationSP config) { d->bookmarkManager->save(name, config); if (!d->configsKey.contains(name)) { beginInsertRows(QModelIndex(), d->configsKey.count() + 2, d->configsKey.count() + 2); d->configsKey << name; endInsertRows(); } } void KisBookmarkedConfigurationsModel::deleteIndex(const QModelIndex &index) { if (!index.isValid() || index.row() < 2) return ; int idx = index.row() - 2; d->bookmarkManager->remove(d->configsKey[idx]); beginRemoveRows(QModelIndex(), idx + 2, idx + 2); d->configsKey.removeAt(idx); endRemoveRows(); } Qt::ItemFlags KisBookmarkedConfigurationsModel::flags(const QModelIndex & index) const { if (!index.isValid()) return 0; switch (index.row()) { case 0: return Qt::ItemIsSelectable | Qt::ItemIsEnabled; case 1: if (d->bookmarkManager->exists(KisBookmarkedConfigurationManager::ConfigLastUsed)) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } else { return 0; } default: return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled; } } diff --git a/libs/ui/kis_favorite_resource_manager.cpp b/libs/ui/kis_favorite_resource_manager.cpp index eb148f6e17..2ae8781455 100644 --- a/libs/ui/kis_favorite_resource_manager.cpp +++ b/libs/ui/kis_favorite_resource_manager.cpp @@ -1,369 +1,369 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2011 Sven Langkamp This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include "kis_favorite_resource_manager.h" #include "kis_popup_palette.h" #include "kis_paintop_box.h" #include "KisViewManager.h" #include "kis_resource_server_provider.h" #include "kis_min_heap.h" #include "kis_config.h" #include "kis_config_notifier.h" class KisFavoriteResourceManager::ColorDataList { public: static const int MAX_RECENT_COLOR = 12; ColorDataList() { m_key = 0; } ~ColorDataList() { qDeleteAll(m_guiList); } int size() { return m_guiList.size(); } int leastUsedGuiPos() { return findPos(m_priorityList.valueAt(0)); } const KoColor& guiColor(int pos) { Q_ASSERT_X(pos < size(), "ColorDataList::guiColor", "index out of bound"); Q_ASSERT_X(pos >= 0, "ColorDataList::guiColor", "negative index"); return m_guiList.at(pos)->data; } void append(const KoColor& data) { int pos = findPos(data); if (pos > -1) updateKey(pos); else appendNew(data); } void appendNew(const KoColor& data) { if (size() >= ColorDataList::MAX_RECENT_COLOR) removeLeastUsed(); PriorityNode * node; node = new PriorityNode (); node->data = data; node->key = m_key++; m_priorityList.append(node); int pos = guiInsertPos(data); pos >= m_guiList.size() ? m_guiList.append(node) : m_guiList.insert(pos, node); node = 0; } void removeLeastUsed() { Q_ASSERT_X(size() >= 0, "ColorDataList::removeLeastUsed", "index out of bound"); if (size() <= 0) return; int pos = findPos(m_priorityList.valueAt(0)); m_guiList.removeAt(pos); m_priorityList.remove(0); } void updateKey(int guiPos) { if (m_guiList.at(guiPos)->key == m_key - 1) return; m_priorityList.changeKey(m_guiList.at(guiPos)->pos, m_key++); } /*find position of the color on the gui list*/ int findPos(const KoColor& color) { int low = 0, high = size(), mid = 0; while (low < high) { mid = (low + high) / 2; if (hsvComparison(color, m_guiList.at(mid)->data) == 0) return mid; else if (hsvComparison(color, m_guiList.at(mid)->data) < 0) high = mid; else low = mid + 1; } return -1; } private: int m_key; int guiInsertPos(const KoColor& color) { int low = 0, high = size() - 1, mid = (low + high) / 2; while (low < high) { hsvComparison(color, m_guiList[mid]->data) == -1 ? high = mid : low = mid + 1; mid = (low + high) / 2; } if (m_guiList.size() > 0) { if (hsvComparison(color, m_guiList[mid]->data) == 1) ++mid; } return mid; } /*compares c1 and c2 based on HSV. c1 < c2, returns -1 c1 = c2, returns 0 c1 > c2, returns 1 */ int hsvComparison(const KoColor& c1, const KoColor& c2) { QColor qc1 = c1.toQColor(); QColor qc2 = c2.toQColor(); if (qc1.hue() < qc2.hue()) return -1; if (qc1.hue() > qc2.hue()) return 1; // hue is the same, ok let's compare saturation if (qc1.saturation() < qc2.saturation()) return -1; if (qc1.saturation() > qc2.saturation()) return 1; // oh, also saturation is same? if (qc1.value() < qc2.value()) return -1; if (qc1.value() > qc2.value()) return 1; // user selected two similar colors return 0; } KisMinHeap m_priorityList; QList *> m_guiList; }; KisFavoriteResourceManager::KisFavoriteResourceManager(KisPaintopBox *paintopBox) : m_paintopBox(paintopBox) , m_colorList(0) , m_blockUpdates(false) , m_initialized(false) { KisConfig cfg; m_maxPresets = cfg.favoritePresets(); m_colorList = new ColorDataList(); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(configChanged())); KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(false); rServer->addObserver(this); } KisFavoriteResourceManager::~KisFavoriteResourceManager() { KisConfig cfg; cfg.writeEntry("favoritePresetsTag", m_currentTag); KisPaintOpPresetResourceServer *rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); rServer->removeObserver(this); delete m_colorList; } void KisFavoriteResourceManager::unsetResourceServer() { // ... } QVector KisFavoriteResourceManager::favoritePresetList() { init(); return m_favoritePresetsList; } QList KisFavoriteResourceManager::favoritePresetImages() { init(); QList images; Q_FOREACH (KisPaintOpPresetSP preset, m_favoritePresetsList) { if (preset) { images.append(preset->image()); } } return images; } void KisFavoriteResourceManager::setCurrentTag(const QString& tagName) { m_currentTag = tagName; updateFavoritePresets(); } void KisFavoriteResourceManager::slotChangeActivePaintop(int pos) { if (pos < 0 || pos >= m_favoritePresetsList.size()) return; KoResource* resource = const_cast(m_favoritePresetsList.at(pos).data()); m_paintopBox->resourceSelected(resource); emit hidePalettes(); } int KisFavoriteResourceManager::numFavoritePresets() { init(); return m_favoritePresetsList.size(); } //Recent Colors void KisFavoriteResourceManager::slotUpdateRecentColor(int pos) { // Do not update the key, the colour might be selected but it is not used yet. So we are not supposed // to update the colour priority when we select it. m_colorList->updateKey(pos); emit setSelectedColor(pos); emit sigSetFGColor(m_colorList->guiColor(pos)); emit hidePalettes(); } void KisFavoriteResourceManager::slotAddRecentColor(const KoColor& color) { m_colorList->append(color); int pos = m_colorList->findPos(color); emit setSelectedColor(pos); } void KisFavoriteResourceManager::slotChangeFGColorSelector(KoColor c) { emit sigChangeFGColorSelector(c); } void KisFavoriteResourceManager::removingResource(PointerType resource) { if (m_blockUpdates) { return; } if (m_favoritePresetsList.contains(resource.data())) { updateFavoritePresets(); } } void KisFavoriteResourceManager::resourceAdded(PointerType /*resource*/) { if (m_blockUpdates) { return; } updateFavoritePresets(); } void KisFavoriteResourceManager::resourceChanged(PointerType /*resource*/) { } void KisFavoriteResourceManager::setBlockUpdates(bool block) { m_blockUpdates = block; if (!block) { updateFavoritePresets(); } } void KisFavoriteResourceManager::syncTaggedResourceView() { if (m_blockUpdates) { return; } updateFavoritePresets(); } void KisFavoriteResourceManager::syncTagAddition(const QString& /*tag*/) {} void KisFavoriteResourceManager::syncTagRemoval(const QString& /*tag*/) {} int KisFavoriteResourceManager::recentColorsTotal() { return m_colorList->size(); } const KoColor& KisFavoriteResourceManager::recentColorAt(int pos) { return m_colorList->guiColor(pos); } void KisFavoriteResourceManager::slotSetBGColor(const KoColor c) { m_bgColor = c; } KoColor KisFavoriteResourceManager::bgColor() const { return m_bgColor; } bool sortPresetByName(KisPaintOpPresetSP preset1, KisPaintOpPresetSP preset2) { return preset1->name() < preset2->name(); } void KisFavoriteResourceManager::updateFavoritePresets() { m_favoritePresetsList.clear(); KisPaintOpPresetResourceServer* rServer = KisResourceServerProvider::instance()->paintOpPresetServer(false); QStringList presetFilenames = rServer->searchTag(m_currentTag); for(int i = 0; i < qMin(m_maxPresets, presetFilenames.size()); i++) { KisPaintOpPresetSP pr = rServer->resourceByFilename(presetFilenames.at(i)); m_favoritePresetsList.append(pr.data()); - qSort(m_favoritePresetsList.begin(), m_favoritePresetsList.end(), sortPresetByName); + std::sort(m_favoritePresetsList.begin(), m_favoritePresetsList.end(), sortPresetByName); } emit updatePalettes(); } void KisFavoriteResourceManager::configChanged() { KisConfig cfg; m_maxPresets = cfg.favoritePresets(); updateFavoritePresets(); } void KisFavoriteResourceManager::init() { if (!m_initialized) { m_initialized = true; KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(true); KConfigGroup group( KSharedConfig::openConfig(), "favoriteList"); QStringList oldFavoritePresets = (group.readEntry("favoritePresets")).split(',', QString::SkipEmptyParts); KisConfig cfg; m_currentTag = cfg.readEntry("favoritePresetsTag", "Block"); if (!oldFavoritePresets.isEmpty() && m_currentTag.isEmpty()) { m_currentTag = i18n("Number of popup palette presets shown"); Q_FOREACH ( const QString& name, oldFavoritePresets) { KisPaintOpPresetSP preset = rServer->resourceByName(name); rServer->addTag(preset.data(), m_currentTag); } rServer->tagCategoryAdded(m_currentTag); cfg.writeEntry("favoritePresets", QString()); } updateFavoritePresets(); } } diff --git a/libs/ui/kis_filters_model.cc b/libs/ui/kis_filters_model.cc index 57af64182d..4c158fbd14 100644 --- a/libs/ui/kis_filters_model.cc +++ b/libs/ui/kis_filters_model.cc @@ -1,194 +1,194 @@ /* * This file is part of Krita * * Copyright (c) 2007 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_filters_model.h" #include #include #include #include #include #include struct KisFiltersModel::Private { struct Node { virtual ~Node() {} QString name; QString displayRole() { return name; } virtual int childrenCount() = 0; }; struct Filter : public Node { ~Filter() override {} QString id; QPixmap icon; KisFilterSP filter; int childrenCount() override { return 0; } }; struct Category : public Node { ~Category() override {} QString id; QList filters; int childrenCount() override { return filters.count(); } }; QHash categories; QList categoriesKeys; KisPaintDeviceSP thumb; }; KisFiltersModel::KisFiltersModel(bool showAll, KisPaintDeviceSP thumb) : d(new Private) { d->thumb = thumb; QStringList keys = KisFilterRegistry::instance()->keys(); keys.sort(); Q_FOREACH (const QString &filterName, keys) { KisFilterSP filter = KisFilterRegistry::instance()->get(filterName); if (!showAll && !filter->supportsAdjustmentLayers()) { continue; } Q_ASSERT(filter); if (!d->categories.contains(filter->menuCategory().id())) { Private::Category cat; cat.id = filter->menuCategory().id(); cat.name = filter->menuCategory().name(); d->categories[ cat.id ] = cat; d->categoriesKeys.append(cat.id); } Private::Filter filt; filt.id = filter->id(); filt.name = filter->name(); filt.filter = filter; d->categories[filter->menuCategory().id()].filters.append(filt); } - qSort(d->categoriesKeys); + std::sort(d->categoriesKeys.begin(), d->categoriesKeys.end()); } KisFiltersModel::~KisFiltersModel() { delete d; } int KisFiltersModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { Private::Node* node = static_cast(parent.internalPointer()); return node->childrenCount(); } else { return d->categoriesKeys.count(); } } int KisFiltersModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } QModelIndex KisFiltersModel::indexForFilter(const QString& id) { for (int i = 0; i < d->categoriesKeys.size(); i++) { KisFiltersModel::Private::Category& category = d->categories[ d->categoriesKeys[ i ] ]; for (int j = 0; j < category.filters.size(); j++) { KisFiltersModel::Private::Filter& filter = category.filters[j]; if (filter.id == id) { return index(j, i, index(i , 0, QModelIndex())); } } } return QModelIndex(); } const KisFilter* KisFiltersModel::indexToFilter(const QModelIndex& idx) { Private::Node* node = static_cast(idx.internalPointer()); Private::Filter* filter = dynamic_cast(node); if (filter) { return filter->filter; } return 0; } QModelIndex KisFiltersModel::index(int row, int column, const QModelIndex &parent) const { // dbgKrita << parent.isValid() << row << endl; if (parent.isValid()) { Private::Category* category = static_cast(parent.internalPointer()); return createIndex(row, column, &category->filters[row]); } else { return createIndex(row, column, &d->categories[ d->categoriesKeys[row] ]); } } QModelIndex KisFiltersModel::parent(const QModelIndex &child) const { if (!child.isValid()) return QModelIndex(); Private::Node* node = static_cast(child.internalPointer()); Private::Filter* filter = dynamic_cast(node); if (filter) { QString catId = filter->filter->menuCategory().id(); return createIndex(d->categoriesKeys.indexOf(catId) , 0, &d->categories[ catId ]); } return QModelIndex(); // categories don't have parents } QVariant KisFiltersModel::data(const QModelIndex &index, int role) const { if (index.isValid()) { if (role == Qt::DisplayRole) { Private::Node* node = static_cast(index.internalPointer()); return QVariant(node->displayRole()); } } return QVariant(); } Qt::ItemFlags KisFiltersModel::flags(const QModelIndex & index) const { if (!index.isValid()) return 0; Private::Node* node = static_cast(index.internalPointer()); Private::Filter* filter = dynamic_cast(node); if (filter) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } else { return Qt::ItemIsEnabled; } } diff --git a/libs/ui/kis_palette_view.cpp b/libs/ui/kis_palette_view.cpp index 085587af55..d206ba4347 100644 --- a/libs/ui/kis_palette_view.cpp +++ b/libs/ui/kis_palette_view.cpp @@ -1,321 +1,338 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_palette_view.h" #include #include #include "kis_palette_delegate.h" #include "KisPaletteModel.h" #include "kis_config.h" #include #include #include #include #include #include #include #include struct KisPaletteView::Private { KisPaletteModel *model = 0; bool allowPaletteModification = true; }; KisPaletteView::KisPaletteView(QWidget *parent) : KoTableView(parent), m_d(new Private) { setShowGrid(false); horizontalHeader()->setVisible(false); verticalHeader()->setVisible(false); setItemDelegate(new KisPaletteDelegate()); setDragEnabled(true); setDragDropMode(QAbstractItemView::InternalMove); setDropIndicatorShown(true); KisConfig cfg; - QPalette pal(palette()); - pal.setColor(QPalette::Base, cfg.getMDIBackgroundColor()); - setAutoFillBackground(true); - setPalette(pal); + //QPalette pal(palette()); + //pal.setColor(QPalette::Base, cfg.getMDIBackgroundColor()); + //setAutoFillBackground(true); + //setPalette(pal); int defaultSectionSize = cfg.paletteDockerPaletteViewSectionSize(); horizontalHeader()->setDefaultSectionSize(defaultSectionSize); verticalHeader()->setDefaultSectionSize(defaultSectionSize); connect(this, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(modifyEntry(QModelIndex))); } KisPaletteView::~KisPaletteView() { } void KisPaletteView::setCrossedKeyword(const QString &value) { KisPaletteDelegate *delegate = dynamic_cast(itemDelegate()); KIS_ASSERT_RECOVER_RETURN(delegate); delegate->setCrossedKeyword(value); } bool KisPaletteView::addEntryWithDialog(KoColor color) { KoDialog *window = new KoDialog(); window->setWindowTitle(i18nc("@title:window", "Add a new Colorset Entry")); QFormLayout *editableItems = new QFormLayout(); window->mainWidget()->setLayout(editableItems); QComboBox *cmbGroups = new QComboBox(); QString defaultGroupName = i18nc("Name for default group", "Default"); cmbGroups->addItem(defaultGroupName); cmbGroups->addItems(m_d->model->colorSet()->getGroupNames()); QLineEdit *lnIDName = new QLineEdit(); QLineEdit *lnName = new QLineEdit(); KisColorButton *bnColor = new KisColorButton(); QCheckBox *chkSpot = new QCheckBox(); chkSpot->setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color.")); editableItems->addRow(i18n("Group"), cmbGroups); editableItems->addRow(i18n("ID"), lnIDName); editableItems->addRow(i18n("Name"), lnName); editableItems->addRow(i18n("Color"), bnColor); editableItems->addRow(i18n("Spot"), chkSpot); cmbGroups->setCurrentIndex(0); lnName->setText(i18nc("Part of a default name for a color","Color")+" "+QString::number(m_d->model->colorSet()->nColors()+1)); lnIDName->setText(QString::number(m_d->model->colorSet()->nColors()+1)); bnColor->setColor(color); chkSpot->setChecked(false); // if (window->exec() == KoDialog::Accepted) { QString groupName = cmbGroups->currentText(); if (groupName == defaultGroupName) { groupName = QString(); } KoColorSetEntry newEntry; newEntry.color = bnColor->color(); newEntry.name = lnName->text(); newEntry.id = lnIDName->text(); newEntry.spotColor = chkSpot->isChecked(); m_d->model->addColorSetEntry(newEntry, groupName); m_d->model->colorSet()->save(); return true; } return false; } bool KisPaletteView::addGroupWithDialog() { KoDialog *window = new KoDialog(); window->setWindowTitle(i18nc("@title:window","Add a new group")); QFormLayout *editableItems = new QFormLayout(); window->mainWidget()->setLayout(editableItems); QLineEdit *lnName = new QLineEdit(); editableItems->addRow(i18nc("Name for a group", "Name"), lnName); lnName->setText(i18nc("Part of default name for a new group", "Color Group")+""+QString::number(m_d->model->colorSet()->getGroupNames().size()+1)); if (window->exec() == KoDialog::Accepted) { QString groupName = lnName->text(); m_d->model->addGroup(groupName); m_d->model->colorSet()->save(); return true; } return false; } bool KisPaletteView::removeEntryWithDialog(QModelIndex index) { bool keepColors = true; if (qVariantValue(index.data(KisPaletteModel::IsHeaderRole))) { KoDialog *window = new KoDialog(); window->setWindowTitle(i18nc("@title:window","Removing Group")); QFormLayout *editableItems = new QFormLayout(); QCheckBox *chkKeep = new QCheckBox(); window->mainWidget()->setLayout(editableItems); editableItems->addRow(i18nc("Shows up when deleting a group","Keep the Colors"), chkKeep); chkKeep->setChecked(keepColors); if (window->exec() == KoDialog::Accepted) { keepColors = chkKeep->isChecked(); m_d->model->removeEntry(index, keepColors); m_d->model->colorSet()->save(); } } else { m_d->model->removeEntry(index, keepColors); m_d->model->colorSet()->save(); } return true; } void KisPaletteView::trySelectClosestColor(KoColor color) { KoColorSet* color_set = m_d->model->colorSet(); if (!color_set) return; + //also don't select if the color is the same as the current selection + if (selectedIndexes().size()>0) { + QModelIndex currentI = currentIndex(); + if (!currentI.isValid()) { + currentI = selectedIndexes().last(); + } + if (!currentI.isValid()) { + currentI = selectedIndexes().first(); + } + if (currentI.isValid()) { + if (m_d->model->colorSetEntryFromIndex(currentI).color==color) { + return; + } + } + } quint32 i = color_set->getIndexClosestColor(color); QModelIndex index = m_d->model->indexFromId(i); this->selectionModel()->clearSelection(); this->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select); } void KisPaletteView::mouseReleaseEvent(QMouseEvent *event) { bool foreground = false; if (event->button()== Qt::LeftButton) { foreground = true; } entrySelection(foreground); } void KisPaletteView::paletteModelChanged() { updateView(); updateRows(); } void KisPaletteView::setPaletteModel(KisPaletteModel *model) { if (m_d->model) { disconnect(m_d->model, 0, this, 0); } m_d->model = model; setModel(model); paletteModelChanged(); connect(m_d->model, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), this, SLOT(paletteModelChanged())); connect(m_d->model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(paletteModelChanged())); connect(m_d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(paletteModelChanged())); connect(m_d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(paletteModelChanged())); connect(m_d->model, SIGNAL(modelReset()), this, SLOT(paletteModelChanged())); } KisPaletteModel* KisPaletteView::paletteModel() const { return m_d->model; } void KisPaletteView::updateRows() { this->clearSpans(); if (m_d->model) { for (int r=0; r<=m_d->model->rowCount(); r++) { QModelIndex index = m_d->model->index(r, 0); if (qVariantValue(index.data(KisPaletteModel::IsHeaderRole))) { setSpan(r, 0, 1, m_d->model->columnCount()); setRowHeight(r, this->fontMetrics().lineSpacing()+6); } else { this->setRowHeight(r, this->columnWidth(0)); } } } } void KisPaletteView::setAllowModification(bool allow) { m_d->allowPaletteModification = allow; } void KisPaletteView::wheelEvent(QWheelEvent *event) { if (event->modifiers() & Qt::ControlModifier) { int numDegrees = event->delta() / 8; int numSteps = numDegrees / 7; int curSize = horizontalHeader()->sectionSize(0); int setSize = numSteps + curSize; if ( setSize >= 12 ) { horizontalHeader()->setDefaultSectionSize(setSize); verticalHeader()->setDefaultSectionSize(setSize); KisConfig cfg; cfg.setPaletteDockerPaletteViewSectionSize(setSize); } event->accept(); } else { KoTableView::wheelEvent(event); } } void KisPaletteView::entrySelection(bool foreground) { QModelIndex index; if (selectedIndexes().size()<=0) { return; } if (selectedIndexes().last().isValid()) { index = selectedIndexes().last(); } else if (selectedIndexes().first().isValid()) { index = selectedIndexes().first(); } else { return; } if (qVariantValue(index.data(KisPaletteModel::IsHeaderRole))==false) { KoColorSetEntry entry = m_d->model->colorSetEntryFromIndex(index); if (foreground) { emit(entrySelected(entry)); + emit(indexEntrySelected(index)); } else { emit(entrySelectedBackGround(entry)); + emit(indexEntrySelected(index)); } } } void KisPaletteView::modifyEntry(QModelIndex index) { if (m_d->allowPaletteModification) { KoDialog *group = new KoDialog(); QFormLayout *editableItems = new QFormLayout(); group->mainWidget()->setLayout(editableItems); QLineEdit *lnIDName = new QLineEdit(); QLineEdit *lnGroupName = new QLineEdit(); KisColorButton *bnColor = new KisColorButton(); QCheckBox *chkSpot = new QCheckBox(); if (qVariantValue(index.data(KisPaletteModel::IsHeaderRole))) { QString groupName = qVariantValue(index.data(Qt::DisplayRole)); editableItems->addRow(i18nc("Name for a colorgroup","Name"), lnGroupName); lnGroupName->setText(groupName); if (group->exec() == KoDialog::Accepted) { m_d->model->colorSet()->changeGroupName(groupName, lnGroupName->text()); m_d->model->colorSet()->save(); updateRows(); } //rename the group. } else { KoColorSetEntry entry = m_d->model->colorSetEntryFromIndex(index); QStringList entryList = qVariantValue(index.data(KisPaletteModel::RetrieveEntryRole)); chkSpot->setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color.")); editableItems->addRow(i18n("ID"), lnIDName); editableItems->addRow(i18n("Name"), lnGroupName); editableItems->addRow(i18n("Color"), bnColor); editableItems->addRow(i18n("Spot"), chkSpot); lnGroupName->setText(entry.name); lnIDName->setText(entry.id); bnColor->setColor(entry.color); chkSpot->setChecked(entry.spotColor); if (group->exec() == KoDialog::Accepted) { entry.name = lnGroupName->text(); entry.id = lnIDName->text(); entry.color = bnColor->color(); entry.spotColor = chkSpot->isChecked(); m_d->model->colorSet()->changeColorSetEntry(entry, entryList.at(0), entryList.at(1).toUInt()); m_d->model->colorSet()->save(); } } } } diff --git a/libs/ui/kis_palette_view.h b/libs/ui/kis_palette_view.h index 9aee6b9621..86f0f49e98 100644 --- a/libs/ui/kis_palette_view.h +++ b/libs/ui/kis_palette_view.h @@ -1,112 +1,113 @@ /* * Copyright (c) 2016 Dmitry Kazakov * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * 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_PALETTE_VIEW_H #define __KIS_PALETTE_VIEW_H #include #include #include #include "kritaui_export.h" class KisPaletteModel; class QWheelEvent; class KRITAUI_EXPORT KisPaletteView : public KoTableView { Q_OBJECT public: KisPaletteView(QWidget *parent = 0); ~KisPaletteView() override; void setPaletteModel(KisPaletteModel *model); KisPaletteModel* paletteModel() const; /** * @brief updateRows * update the rows so they have the proper columnspanning. */ void updateRows(); /** * @brief setAllowModification * Set whether doubleclick calls up a modification window. This is to prevent users from editing * the palette when the palette is intended to be a list of items. */ void setAllowModification(bool allow); /** * @brief setCrossedKeyword * this apparently allows you to set keywords that can cross out colors. * This is implemented to mark the lazybrush "transparent" color. * @param value */ void setCrossedKeyword(const QString &value); public Q_SLOTS: void paletteModelChanged(); /** * add an entry with a dialog window. */ bool addEntryWithDialog(KoColor color); /** * @brief addGroupWithDialog * summons a little dialog to name the new group. */ bool addGroupWithDialog(); /** * remove entry with a dialog window.(Necessary for groups. */ bool removeEntryWithDialog(QModelIndex index); /** * This tries to select the closest color in the palette. * This doesn't update the foreground color, just the visual selection. */ void trySelectClosestColor(KoColor color); Q_SIGNALS: /** * @brief entrySelected * signals when an entry is selected. * @param entry the selected entry. */ void entrySelected(KoColorSetEntry entry); void entrySelectedBackGround(KoColorSetEntry entry); + void indexEntrySelected(QModelIndex index); protected: void mouseReleaseEvent(QMouseEvent *event); void wheelEvent(QWheelEvent *event) override; private: struct Private; const QScopedPointer m_d; private Q_SLOTS: /** * @brief entrySelection * the function that will emit entrySelected when the entry changes. */ void entrySelection(bool foreground = true); /** * @brief modifyEntry * function for changing the entry at the given index. * if modification isn't allow(@see setAllowModification), this does nothing. */ void modifyEntry(QModelIndex index); }; #endif /* __KIS_PALETTE_VIEW_H */ diff --git a/libs/ui/kis_popup_palette.cpp b/libs/ui/kis_popup_palette.cpp index 85749a004c..a8a44954bb 100644 --- a/libs/ui/kis_popup_palette.cpp +++ b/libs/ui/kis_popup_palette.cpp @@ -1,917 +1,917 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2011 Sven Langkamp Copyright 2016 Scott Petrovic This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_config.h" #include "kis_popup_palette.h" #include "kis_paintop_box.h" #include "kis_favorite_resource_manager.h" #include "kis_icon_utils.h" #include #include "kis_resource_server_provider.h" #include #include #include #include #include "KoColorSpaceRegistry.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_signal_compressor.h" #include #include "brushhud/kis_brush_hud.h" #include "brushhud/kis_round_hud_button.h" #include class PopupColorTriangle : public KoTriangleColorSelector { public: PopupColorTriangle(const KoColorDisplayRendererInterface *displayRenderer, QWidget* parent) : KoTriangleColorSelector(displayRenderer, parent) , m_dragging(false) { } ~PopupColorTriangle() override {} void tabletEvent(QTabletEvent* event) override { event->accept(); QMouseEvent* mouseEvent = 0; switch (event->type()) { case QEvent::TabletPress: mouseEvent = new QMouseEvent(QEvent::MouseButtonPress, event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); m_dragging = true; mousePressEvent(mouseEvent); break; case QEvent::TabletMove: mouseEvent = new QMouseEvent(QEvent::MouseMove, event->pos(), (m_dragging) ? Qt::LeftButton : Qt::NoButton, (m_dragging) ? Qt::LeftButton : Qt::NoButton, event->modifiers()); mouseMoveEvent(mouseEvent); break; case QEvent::TabletRelease: mouseEvent = new QMouseEvent(QEvent::MouseButtonRelease, event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); m_dragging = false; mouseReleaseEvent(mouseEvent); break; default: break; } delete mouseEvent; } private: bool m_dragging; }; KisPopupPalette::KisPopupPalette(KisViewManager* viewManager, KisCoordinatesConverter* coordinatesConverter ,KisFavoriteResourceManager* manager, const KoColorDisplayRendererInterface *displayRenderer, KisCanvasResourceProvider *provider, QWidget *parent) : QWidget(parent, Qt::FramelessWindowHint) , m_hoveredPreset(0) , m_hoveredColor(0) , m_selectedColor(0) , m_coordinatesConverter(coordinatesConverter) , m_actionManager(viewManager->actionManager()) , m_resourceManager(manager) , m_triangleColorSelector(0) , m_displayRenderer(displayRenderer) , m_colorChangeCompressor(new KisSignalCompressor(50, KisSignalCompressor::POSTPONE)) , m_actionCollection(viewManager->actionCollection()) , m_brushHud(0) , m_popupPaletteSize(385.0) , m_colorHistoryInnerRadius(72.0) , m_colorHistoryOuterRadius(92.0) , m_isOverCanvasRotationIndicator(false) , m_isRotatingCanvasIndicator(false) { // some UI controls are defined and created based off these variables const int borderWidth = 3; if (KisConfig().readEntry("popuppalette/usevisualcolorselector", false)) { m_triangleColorSelector = new KisVisualColorSelector(this); } else { m_triangleColorSelector = new PopupColorTriangle(displayRenderer, this); } m_triangleColorSelector->setDisplayRenderer(displayRenderer); m_triangleColorSelector->setConfig(true,false); m_triangleColorSelector->move(m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth, m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth); m_triangleColorSelector->resize(m_colorHistoryInnerRadius*2-borderWidth*2, m_colorHistoryInnerRadius*2-borderWidth*2); m_triangleColorSelector->setVisible(true); KoColor fgcolor(Qt::black, KoColorSpaceRegistry::instance()->rgb8()); if (m_resourceManager) { fgcolor = provider->fgColor(); } m_triangleColorSelector->slotSetColor(fgcolor); QRegion maskedRegion(0, 0, m_triangleColorSelector->width(), m_triangleColorSelector->height(), QRegion::Ellipse ); m_triangleColorSelector->setMask(maskedRegion); //setAttribute(Qt::WA_TranslucentBackground, true); connect(m_triangleColorSelector, SIGNAL(sigNewColor(const KoColor &)), m_colorChangeCompressor.data(), SLOT(start())); connect(m_colorChangeCompressor.data(), SIGNAL(timeout()), SLOT(slotEmitColorChanged())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_triangleColorSelector, SLOT(configurationChanged())); connect(m_resourceManager, SIGNAL(sigChangeFGColorSelector(KoColor)), SLOT(slotExternalFgColorChanged(KoColor))); connect(this, SIGNAL(sigChangefGColor(KoColor)), m_resourceManager, SIGNAL(sigSetFGColor(KoColor))); connect(this, SIGNAL(sigChangeActivePaintop(int)), m_resourceManager, SLOT(slotChangeActivePaintop(int))); connect(this, SIGNAL(sigUpdateRecentColor(int)), m_resourceManager, SLOT(slotUpdateRecentColor(int))); connect(m_resourceManager, SIGNAL(setSelectedColor(int)), SLOT(slotSetSelectedColor(int))); connect(m_resourceManager, SIGNAL(updatePalettes()), SLOT(slotUpdate())); connect(m_resourceManager, SIGNAL(hidePalettes()), SLOT(slotHide())); // This is used to handle a bug: // If pop up palette is visible and a new colour is selected, the new colour // will be added when the user clicks on the canvas to hide the palette // In general, we want to be able to store recent color if the pop up palette // is not visible m_timer.setSingleShot(true); connect(this, SIGNAL(sigTriggerTimer()), this, SLOT(slotTriggerTimer())); connect(&m_timer, SIGNAL(timeout()), this, SLOT(slotEnableChangeFGColor())); connect(this, SIGNAL(sigEnableChangeFGColor(bool)), m_resourceManager, SIGNAL(sigEnableChangeColor(bool))); setCursor(Qt::ArrowCursor); setMouseTracking(true); setHoveredPreset(-1); setHoveredColor(-1); setSelectedColor(-1); m_brushHud = new KisBrushHud(provider, parent); m_brushHud->setMaximumHeight(m_popupPaletteSize); m_brushHud->setVisible(false); const int auxButtonSize = 35; m_settingsButton = new KisRoundHudButton(this); m_settingsButton->setIcon(KisIconUtils::loadIcon("configure")); m_settingsButton->setGeometry(m_popupPaletteSize - 2.2 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_settingsButton, SIGNAL(clicked()), SLOT(slotShowTagsPopup())); KisConfig cfg; m_brushHudButton = new KisRoundHudButton(this); m_brushHudButton->setCheckable(true); m_brushHudButton->setOnOffIcons(KisIconUtils::loadIcon("arrow-left"), KisIconUtils::loadIcon("arrow-right")); m_brushHudButton->setGeometry(m_popupPaletteSize - 1.0 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_brushHudButton, SIGNAL(toggled(bool)), SLOT(showHudWidget(bool))); m_brushHudButton->setChecked(cfg.showBrushHud()); // add some stuff below the pop-up palette that will make it easier to use for tablet people QVBoxLayout* vLayout = new QVBoxLayout(this); // main layout QSpacerItem* verticalSpacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding); vLayout->addSpacerItem(verticalSpacer); // this should push the box to the bottom QHBoxLayout* hLayout = new QHBoxLayout(); vLayout->addLayout(hLayout); mirrorMode = new KisHighlightedToolButton(this); mirrorMode->setCheckable(true); mirrorMode->setFixedSize(35, 35); mirrorMode->setIcon(KisIconUtils::loadIcon("symmetry-horizontal")); mirrorMode->setToolTip(i18n("Mirror Canvas")); connect(mirrorMode, SIGNAL(clicked(bool)), this, SLOT(slotmirroModeClicked())); canvasOnlyButton = new KisHighlightedToolButton(this); canvasOnlyButton->setCheckable(true); canvasOnlyButton->setFixedSize(35, 35); canvasOnlyButton->setIcon(KisIconUtils::loadIcon("document-new")); canvasOnlyButton->setToolTip(i18n("Canvas Only")); connect(canvasOnlyButton, SIGNAL(clicked(bool)), this, SLOT(slotCanvasonlyModeClicked())); zoomToOneHundredPercentButton = new QPushButton(this); zoomToOneHundredPercentButton->setText(i18n("100%")); zoomToOneHundredPercentButton->setFixedHeight(35); zoomToOneHundredPercentButton->setIcon(KisIconUtils::loadIcon("zoom-original")); zoomToOneHundredPercentButton->setToolTip(i18n("Zoom to 100%")); connect(zoomToOneHundredPercentButton, SIGNAL(clicked(bool)), this, SLOT(slotZoomToOneHundredPercentClicked())); zoomCanvasSlider = new QSlider(Qt::Horizontal, this); zoomSliderMinValue = 10; // set in % zoomSliderMaxValue = 200; // set in % zoomCanvasSlider->setRange(zoomSliderMinValue, zoomSliderMaxValue); zoomCanvasSlider->setFixedHeight(35); zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); zoomCanvasSlider->setSingleStep(1); zoomCanvasSlider->setPageStep(1); connect(zoomCanvasSlider, SIGNAL(valueChanged(int)), this, SLOT(slotZoomSliderChanged(int))); hLayout->addWidget(mirrorMode); hLayout->addWidget(canvasOnlyButton); hLayout->addWidget(zoomToOneHundredPercentButton); hLayout->addWidget(zoomCanvasSlider); setVisible(true); setVisible(false); } void KisPopupPalette::slotExternalFgColorChanged(const KoColor &color) { //hack to get around cmyk for now. if (color.colorSpace()->colorChannelCount()>3) { KoColor c(KoColorSpaceRegistry::instance()->rgb8()); c.fromKoColor(color); m_triangleColorSelector->slotSetColor(c); } else { m_triangleColorSelector->slotSetColor(color); } } void KisPopupPalette::slotEmitColorChanged() { if (isVisible()) { update(); emit sigChangefGColor(m_triangleColorSelector->getCurrentColor()); } } //setting KisPopupPalette properties int KisPopupPalette::hoveredPreset() const { return m_hoveredPreset; } void KisPopupPalette::setHoveredPreset(int x) { m_hoveredPreset = x; } int KisPopupPalette::hoveredColor() const { return m_hoveredColor; } void KisPopupPalette::setHoveredColor(int x) { m_hoveredColor = x; } int KisPopupPalette::selectedColor() const { return m_selectedColor; } void KisPopupPalette::setSelectedColor(int x) { m_selectedColor = x; } void KisPopupPalette::slotTriggerTimer() { m_timer.start(750); } void KisPopupPalette::slotEnableChangeFGColor() { emit sigEnableChangeFGColor(true); } void KisPopupPalette::slotZoomSliderChanged(int zoom) { emit zoomLevelChanged(zoom); } void KisPopupPalette::adjustLayout(const QPoint &p) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); if (isVisible() && parentWidget()) { float hudMargin = 30.0; const QRect fitRect = kisGrowRect(parentWidget()->rect(), -20.0); // -20 is widget margin const QPoint paletteCenterOffset(m_popupPaletteSize / 2, m_popupPaletteSize / 2); QRect paletteRect = rect(); paletteRect.moveTo(p - paletteCenterOffset); if (m_brushHudButton->isChecked()) { m_brushHud->updateGeometry(); paletteRect.adjust(0, 0, m_brushHud->width() + hudMargin, 0); } paletteRect = kisEnsureInRect(paletteRect, fitRect); move(paletteRect.topLeft()); m_brushHud->move(paletteRect.topLeft() + QPoint(m_popupPaletteSize + hudMargin, 0)); m_lastCenterPoint = p; } } void KisPopupPalette::showHudWidget(bool visible) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); const bool reallyVisible = visible && m_brushHudButton->isChecked(); if (reallyVisible) { m_brushHud->updateProperties(); } m_brushHud->setVisible(reallyVisible); adjustLayout(m_lastCenterPoint); KisConfig cfg; cfg.setShowBrushHud(visible); } void KisPopupPalette::showPopupPalette(const QPoint &p) { showPopupPalette(!isVisible()); adjustLayout(p); } void KisPopupPalette::showPopupPalette(bool show) { if (show) { // don't set the zoom slider if we are outside of the zoom slider bounds. It will change the zoom level to within // the bounds and cause the canvas to jump between the slider's min and max if (m_coordinatesConverter->zoomInPercent() > zoomSliderMinValue && m_coordinatesConverter->zoomInPercent() < zoomSliderMaxValue ){ zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); // sync the zoom slider } emit sigEnableChangeFGColor(!show); } else { emit sigTriggerTimer(); } setVisible(show); m_brushHud->setVisible(show && m_brushHudButton->isChecked()); } //redefinition of setVariable function to change the scope to private void KisPopupPalette::setVisible(bool b) { QWidget::setVisible(b); } void KisPopupPalette::setParent(QWidget *parent) { m_brushHud->setParent(parent); QWidget::setParent(parent); } QSize KisPopupPalette::sizeHint() const { return QSize(m_popupPaletteSize, m_popupPaletteSize + 50); // last number is the space for the toolbar below } void KisPopupPalette::resizeEvent(QResizeEvent*) { } void KisPopupPalette::paintEvent(QPaintEvent* e) { Q_UNUSED(e); QPainter painter(this); QPen pen(palette().color(QPalette::Text)); pen.setWidth(3); painter.setPen(pen); painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::SmoothPixmapTransform); //painting background color indicator QPainterPath bgColor; bgColor.addEllipse(QPoint( 50, 80), 30, 30); painter.fillPath(bgColor, m_displayRenderer->toQColor(m_resourceManager->bgColor())); painter.drawPath(bgColor); //painting foreground color indicator QPainterPath fgColor; fgColor.addEllipse(QPoint( 60, 50), 30, 30); painter.fillPath(fgColor, m_displayRenderer->toQColor(m_triangleColorSelector->getCurrentColor())); painter.drawPath(fgColor); // create a circle background that everything else will go into QPainterPath backgroundContainer; float shrinkCircleAmount = 3;// helps the circle when the stroke is put around it QRectF circleRect(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); backgroundContainer.addEllipse( circleRect ); painter.fillPath(backgroundContainer,palette().brush(QPalette::Background)); painter.drawPath(backgroundContainer); // create a path slightly inside the container circle. this will create a 'track' to indicate that we can rotate the canvas // with the indicator QPainterPath rotationTrackPath; shrinkCircleAmount = 18; QRectF circleRect2(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); rotationTrackPath.addEllipse( circleRect2 ); pen.setWidth(1); painter.setPen(pen); painter.drawPath(rotationTrackPath); // this thing will help indicate where the starting brush preset is at. // also what direction they go to give sor order to the presets populated /* pen.setWidth(6); pen.setCapStyle(Qt::RoundCap); painter.setPen(pen); painter.drawArc(circleRect, (16*90), (16*-30)); // span angle (last parameter) is in 16th of degrees QPainterPath brushDir; brushDir.arcMoveTo(circleRect, 60); brushDir.lineTo(brushDir.currentPosition().x()-5, brushDir.currentPosition().y() - 14); painter.drawPath(brushDir); brushDir.lineTo(brushDir.currentPosition().x()-2, brushDir.currentPosition().y() + 6); painter.drawPath(brushDir); */ // the following things needs to be based off the center, so let's translate the painter painter.translate(m_popupPaletteSize / 2, m_popupPaletteSize / 2); // create the canvas rotation handle QPainterPath rotationIndicator = drawRotationIndicator(m_coordinatesConverter->rotationAngle(), true); painter.fillPath(rotationIndicator,palette().brush(QPalette::Text)); // hover indicator for the canvas rotation if (m_isOverCanvasRotationIndicator == true) { painter.save(); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(2); painter.setPen(pen); painter.drawPath(rotationIndicator); painter.restore(); } // create a reset canvas rotation indicator to bring the canvas back to 0 degrees QPainterPath resetRotationIndicator = drawRotationIndicator(0, false); QPen resetPen(palette().color(QPalette::Text)); resetPen.setWidth(1); painter.save(); painter.setPen(resetPen); painter.drawPath(resetRotationIndicator); painter.restore(); //painting favorite brushes QList images(m_resourceManager->favoritePresetImages()); //painting favorite brushes pixmap/icon QPainterPath presetPath; for (int pos = 0; pos < numSlots(); pos++) { painter.save(); presetPath = createPathFromPresetIndex(pos); if (pos < images.size()) { painter.setClipPath(presetPath); QRect bounds = presetPath.boundingRect().toAlignedRect(); painter.drawImage(bounds.topLeft() , images.at(pos).scaled(bounds.size() , Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); } else { painter.fillPath(presetPath, palette().brush(QPalette::Window)); // brush slot that has no brush in it } QPen pen = painter.pen(); pen.setWidth(1); painter.setPen(pen); painter.drawPath(presetPath); painter.restore(); } if (hoveredPreset() > -1) { presetPath = createPathFromPresetIndex(hoveredPreset()); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(3); painter.setPen(pen); painter.drawPath(presetPath); } // paint recent colors area. painter.setPen(Qt::NoPen); float rotationAngle = -360.0 / m_resourceManager->recentColorsTotal(); // there might be no recent colors at the start, so paint a placeholder if (m_resourceManager->recentColorsTotal() == 0) { painter.setBrush(Qt::transparent); QPainterPath emptyRecentColorsPath(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.setPen(QPen(palette().color(QPalette::Background).lighter(150), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); painter.drawPath(emptyRecentColorsPath); } else { for (int pos = 0; pos < m_resourceManager->recentColorsTotal(); pos++) { QPainterPath recentColorsPath(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); //accessing recent color of index pos painter.fillPath(recentColorsPath, m_displayRenderer->toQColor( m_resourceManager->recentColorAt(pos) )); painter.drawPath(recentColorsPath); painter.rotate(rotationAngle); } } //painting hovered color if (hoveredColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + hoveredColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(hoveredColor() * -1 * rotationAngle); } } //painting selected color if (selectedColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight).darker(130), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + selectedColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(selectedColor() * -1 * rotationAngle); } } } QPainterPath KisPopupPalette::drawDonutPathFull(int x, int y, int inner_radius, int outer_radius) { QPainterPath path; path.addEllipse(QPointF(x, y), outer_radius, outer_radius); path.addEllipse(QPointF(x, y), inner_radius, inner_radius); path.setFillRule(Qt::OddEvenFill); return path; } QPainterPath KisPopupPalette::drawDonutPathAngle(int inner_radius, int outer_radius, int limit) { QPainterPath path; path.moveTo(-0.999 * outer_radius * sin(M_PI / limit), 0.999 * outer_radius * cos(M_PI / limit)); path.arcTo(-1 * outer_radius, -1 * outer_radius, 2 * outer_radius, 2 * outer_radius, -90.0 - 180.0 / limit, 360.0 / limit); path.arcTo(-1 * inner_radius, -1 * inner_radius, 2 * inner_radius, 2 * inner_radius, -90.0 + 180.0 / limit, - 360.0 / limit); path.closeSubpath(); return path; } QPainterPath KisPopupPalette::drawRotationIndicator(qreal rotationAngle, bool canDrag) { // used for canvas rotation. This function gets called twice. Once by the canvas rotation indicator, // and another time by the reset canvas position float canvasRotationRadians = qDegreesToRadians(rotationAngle - 90); // -90 will make 0 degrees be at the top float rotationDialXPosition = qCos(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); // m_popupPaletteSize/2 = radius float rotationDialYPosition = qSin(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); QPainterPath canvasRotationIndicator; int canvasIndicatorSize = 15; float canvasIndicatorMiddle = canvasIndicatorSize/2; QRect indicatorRectangle = QRect( rotationDialXPosition - canvasIndicatorMiddle, rotationDialYPosition - canvasIndicatorMiddle, canvasIndicatorSize, canvasIndicatorSize ); if (canDrag) { m_canvasRotationIndicatorRect = indicatorRectangle; } else { m_resetCanvasRotationIndicatorRect = indicatorRectangle; } canvasRotationIndicator.addEllipse(indicatorRectangle.x(), indicatorRectangle.y(), indicatorRectangle.width(), indicatorRectangle.height() ); return canvasRotationIndicator; } void KisPopupPalette::mouseMoveEvent(QMouseEvent* event) { QPointF point = event->posF(); event->accept(); setToolTip(QString()); setHoveredPreset(-1); setHoveredColor(-1); // calculate if we are over the canvas rotation knob // before we started painting, we moved the painter to the center of the widget, so the X/Y positions are offset. we need to // correct them first before looking for a click event intersection float rotationCorrectedXPos = m_canvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_canvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_canvasRotationIndicatorRect.width(), m_canvasRotationIndicatorRect.height()); if (correctedCanvasRotationIndicator.contains(point.x(), point.y())) { m_isOverCanvasRotationIndicator = true; } else { m_isOverCanvasRotationIndicator = false; } if (m_isRotatingCanvasIndicator) { // we are rotating the canvas, so calculate the rotation angle based off the center // calculate the angle we are at first QPoint widgetCenterPoint = QPoint(m_popupPaletteSize/2, m_popupPaletteSize/2); float dX = point.x() - widgetCenterPoint.x(); float dY = point.y() - widgetCenterPoint.y(); float finalAngle = qAtan2(dY,dX) * 180 / M_PI; // what we need if we have two points, but don't know the angle finalAngle = finalAngle + 90; // add 90 degrees so 0 degree position points up float angleDifference = finalAngle - m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it out m_coordinatesConverter->rotate(m_coordinatesConverter->widgetCenterPoint(), angleDifference); emit sigUpdateCanvas(); } // don't highlight the presets if we are in the middle of rotating the canvas if (m_isRotatingCanvasIndicator == false) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); { int pos = calculatePresetIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()) { setToolTip(m_resourceManager->favoritePresetList().at(pos).data()->name()); setHoveredPreset(pos); } } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { setHoveredColor(pos); } } } update(); } void KisPopupPalette::mousePressEvent(QMouseEvent* event) { QPointF point = event->posF(); event->accept(); if (event->button() == Qt::LeftButton) { //in favorite brushes area int pos = calculateIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets() && isPointInPixmap(point, pos)) { //setSelectedBrush(pos); update(); } if (m_isOverCanvasRotationIndicator) { m_isRotatingCanvasIndicator = true; } // reset the canvas if we are over the reset canvas rotation indicator float rotationCorrectedXPos = m_resetCanvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_resetCanvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedResetCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_resetCanvasRotationIndicatorRect.width(), m_resetCanvasRotationIndicatorRect.height()); if (correctedResetCanvasRotationIndicator.contains(point.x(), point.y())) { float angleDifference = -m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it ou m_coordinatesConverter->rotate(m_coordinatesConverter->widgetCenterPoint(), angleDifference); emit sigUpdateCanvas(); } } } void KisPopupPalette::slotShowTagsPopup() { KisPaintOpPresetResourceServer* rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); QStringList tags = rServer->tagNamesList(); - qSort(tags); + std::sort(tags.begin(), tags.end()); if (!tags.isEmpty()) { QMenu menu; Q_FOREACH (const QString& tag, tags) { menu.addAction(tag); } QAction* action = menu.exec(QCursor::pos()); if (action) { m_resourceManager->setCurrentTag(action->text()); } } else { QWhatsThis::showText(QCursor::pos(), i18n("There are no tags available to show in this popup. To add presets, you need to tag them and then select the tag here.")); } } void KisPopupPalette::slotmirroModeClicked() { QAction* action = m_actionCollection->action("mirror_canvas"); if (action) { action->trigger(); } } void KisPopupPalette::slotCanvasonlyModeClicked() { QAction* action = m_actionCollection->action("view_show_canvas_only"); if (action) { action->trigger(); } } void KisPopupPalette::slotZoomToOneHundredPercentClicked() { QAction* action = m_actionCollection->action("zoom_to_100pct"); if (action) { action->trigger(); } // also move the zoom slider to 100% position so they are in sync zoomCanvasSlider->setValue(100); } void KisPopupPalette::tabletEvent(QTabletEvent* /*event*/) { } void KisPopupPalette::mouseReleaseEvent(QMouseEvent * event) { QPointF point = event->posF(); event->accept(); m_isOverCanvasRotationIndicator = false; m_isRotatingCanvasIndicator = false; if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); //in favorite brushes area if (hoveredPreset() > -1) { //setSelectedBrush(hoveredBrush()); emit sigChangeActivePaintop(hoveredPreset()); } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { emit sigUpdateRecentColor(pos); } } } } int KisPopupPalette::calculateIndex(QPointF point, int n) { calculatePresetIndex(point, n); //translate to (0,0) point.setX(point.x() - m_popupPaletteSize / 2); point.setY(point.y() - m_popupPaletteSize / 2); //rotate float smallerAngle = M_PI / 2 + M_PI / n - atan2(point.y(), point.x()); float radius = sqrt((float)point.x() * point.x() + point.y() * point.y()); point.setX(radius * cos(smallerAngle)); point.setY(radius * sin(smallerAngle)); //calculate brush index int pos = floor(acos(point.x() / radius) * n / (2 * M_PI)); if (point.y() < 0) pos = n - pos - 1; return pos; } bool KisPopupPalette::isPointInPixmap(QPointF& point, int pos) { if (createPathFromPresetIndex(pos).contains(point + QPointF(-m_popupPaletteSize / 2, -m_popupPaletteSize / 2))) { return true; } return false; } KisPopupPalette::~KisPopupPalette() { } QPainterPath KisPopupPalette::createPathFromPresetIndex(int index) { qreal angleSlice = 360.0 / numSlots() ; // how many degrees each slice will get // the starting angle of the slice we need to draw. the negative sign makes us go clockwise. // adding 90 degrees makes us start at the top. otherwise we would start at the right qreal startingAngle = -(index * angleSlice) + 90; // the radius will get smaller as the amount of presets shown increases. 10 slots == 41 qreal presetRadius = m_colorHistoryOuterRadius * qSin(qDegreesToRadians(angleSlice/2)) / (1-qSin(qDegreesToRadians(angleSlice/2))); QPainterPath path; float pathX = (m_colorHistoryOuterRadius + presetRadius) * qCos(qDegreesToRadians(startingAngle)) - presetRadius; float pathY = -(m_colorHistoryOuterRadius + presetRadius) * qSin(qDegreesToRadians(startingAngle)) - presetRadius; float pathDiameter = 2 * presetRadius; // distance is used to calculate the X/Y in addition to the preset circle size path.addEllipse(pathX, pathY, pathDiameter, pathDiameter); return path; } int KisPopupPalette::calculatePresetIndex(QPointF point, int /*n*/) { for(int i = 0; i < numSlots(); i++) { QPointF adujustedPoint = point - QPointF(m_popupPaletteSize/2, m_popupPaletteSize/2); if(createPathFromPresetIndex(i).contains(adujustedPoint)) { return i; } } return -1; } int KisPopupPalette::numSlots() { KisConfig config; return qMax(config.favoritePresets(), 10); } diff --git a/libs/ui/widgets/kis_advanced_color_space_selector.cc b/libs/ui/widgets/kis_advanced_color_space_selector.cc index 1c30cad434..e98f7cb161 100644 --- a/libs/ui/widgets/kis_advanced_color_space_selector.cc +++ b/libs/ui/widgets/kis_advanced_color_space_selector.cc @@ -1,793 +1,793 @@ /* * Copyright (C) 2007 Cyrille Berger * Copyright (C) 2011 Boudewijn Rempt * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (C) 2015 Wolthera van Hövell tot Westerflier * * 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_advanced_color_space_selector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgcolorspaceselectoradvanced.h" #include struct KisAdvancedColorSpaceSelector::Private { Ui_WdgColorSpaceSelectorAdvanced* colorSpaceSelector; QString knsrcFile; }; KisAdvancedColorSpaceSelector::KisAdvancedColorSpaceSelector(QWidget* parent, const QString &caption) : QDialog(parent) , d(new Private) { setWindowTitle(caption); d->colorSpaceSelector = new Ui_WdgColorSpaceSelectorAdvanced; d->colorSpaceSelector->setupUi(this); d->colorSpaceSelector->cmbColorModels->setIDList(KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::OnlyUserVisible)); fillCmbDepths(d->colorSpaceSelector->cmbColorModels->currentItem()); d->colorSpaceSelector->bnInstallProfile->setIcon(KisIconUtils::loadIcon("document-open")); d->colorSpaceSelector->bnInstallProfile->setToolTip( i18n("Open Color Profile") ); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)), this, SLOT(fillCmbDepths(const KoID &))); connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(const KoID &)), this, SLOT(fillLstProfiles())); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)), this, SLOT(fillLstProfiles())); connect(d->colorSpaceSelector->lstProfile, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(colorSpaceChanged())); connect(this, SIGNAL(selectionChanged(bool)), this, SLOT(fillDescription())); connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TongueWidget, SLOT(repaint())); connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TRCwidget, SLOT(repaint())); connect(d->colorSpaceSelector->bnInstallProfile, SIGNAL(clicked()), this, SLOT(installProfile())); connect(d->colorSpaceSelector->bnOK, SIGNAL(accepted()), this, SLOT(accept())); connect(d->colorSpaceSelector->bnOK, SIGNAL(rejected()), this, SLOT(reject())); fillLstProfiles(); } KisAdvancedColorSpaceSelector::~KisAdvancedColorSpaceSelector() { delete d->colorSpaceSelector; delete d; } void KisAdvancedColorSpaceSelector::fillLstProfiles() { d->colorSpaceSelector->lstProfile->blockSignals(true); const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); const QString defaultProfileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId); d->colorSpaceSelector->lstProfile->clear(); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); QStringList profileNames; Q_FOREACH (const KoColorProfile *profile, profileList) { profileNames.append(profile->name()); } - qSort(profileNames); + std::sort(profileNames.begin(), profileNames.end()); QListWidgetItem *defaultProfile = new QListWidgetItem; defaultProfile->setText(defaultProfileName + " " + i18nc("This is appended to the color profile which is the default for the given colorspace and bit-depth","(Default)")); Q_FOREACH (QString stringName, profileNames) { if (stringName == defaultProfileName) { d->colorSpaceSelector->lstProfile->addItem(defaultProfile); } else { d->colorSpaceSelector->lstProfile->addItem(stringName); } } d->colorSpaceSelector->lstProfile->setCurrentItem(defaultProfile); d->colorSpaceSelector->lstProfile->blockSignals(false); colorSpaceChanged(); } void KisAdvancedColorSpaceSelector::fillCmbDepths(const KoID& id) { KoID activeDepth = d->colorSpaceSelector->cmbColorDepth->currentItem(); d->colorSpaceSelector->cmbColorDepth->clear(); QList depths = KoColorSpaceRegistry::instance()->colorDepthList(id, KoColorSpaceRegistry::OnlyUserVisible); QList sortedDepths; if (depths.contains(Integer8BitsColorDepthID)) { sortedDepths << Integer8BitsColorDepthID; } if (depths.contains(Integer16BitsColorDepthID)) { sortedDepths << Integer16BitsColorDepthID; } if (depths.contains(Float16BitsColorDepthID)) { sortedDepths << Float16BitsColorDepthID; } if (depths.contains(Float32BitsColorDepthID)) { sortedDepths << Float32BitsColorDepthID; } if (depths.contains(Float64BitsColorDepthID)) { sortedDepths << Float64BitsColorDepthID; } d->colorSpaceSelector->cmbColorDepth->setIDList(sortedDepths); if (sortedDepths.contains(activeDepth)) { d->colorSpaceSelector->cmbColorDepth->setCurrent(activeDepth); } } void KisAdvancedColorSpaceSelector::fillDescription() { QString notApplicable = i18nc("Not Applicable, used where there's no colorants or gamma curve found","N/A"); QString notApplicableTooltip = i18nc("@info:tooltip","This profile has no colorants."); QString profileName = i18nc("Shows up instead of the name when there's no profile","No Profile Found"); QString whatIsColorant = i18n("Colorant in d50-adapted xyY."); //set colorants const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); if (!profileList.isEmpty()) { profileName = currentColorSpace()->profile()->name(); if (currentColorSpace()->profile()->hasColorants()){ QVector colorants = currentColorSpace()->profile()->getColorantsxyY(); QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); //QString text = currentColorSpace()->profile()->info() + " =" + d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint)); d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint[0], 'f', 4) + ", " + QString::number(whitepoint[1], 'f', 4) + ", " + QString::number(whitepoint[2], 'f', 4)); d->colorSpaceSelector->TongueWidget->setToolTip("
"+i18nc("@info:tooltip","This profile has the following xyY colorants:")+"
"+ i18n("Red:") +""+QString::number(colorants[0], 'f', 4) + "" + QString::number(colorants[1], 'f', 4) + "" + QString::number(colorants[2], 'f', 4)+"
"+ i18n("Green:")+""+QString::number(colorants[3], 'f', 4) + "" + QString::number(colorants[4], 'f', 4) + "" + QString::number(colorants[5], 'f', 4)+"
"+ i18n("Blue:") +""+QString::number(colorants[6], 'f', 4) + "" + QString::number(colorants[7], 'f', 4) + "" + QString::number(colorants[8], 'f', 4)+"
"); } else { QVector whitepoint2 = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint2)); d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint2[0], 'f', 4) + ", " + QString::number(whitepoint2[1], 'f', 4) + ", " + QString::number(whitepoint2[2], 'f', 4)); d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip); } } else { d->colorSpaceSelector->lblXYZ_W->setText(notApplicable); d->colorSpaceSelector->lblXYZ_W->setToolTip(notApplicableTooltip); d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip); } //set TRC QVector estimatedTRC(3); QString estimatedGamma = i18nc("Estimated Gamma indicates how the TRC (Tone Response Curve or Tone Reproduction Curve) is bent. A Gamma of 1.0 means linear.", "Estimated Gamma: "); QString estimatedsRGB = i18nc("This is for special Gamma types that LCMS cannot differentiate between", "Estimated Gamma: sRGB, L* or rec709 TRC"); QString whatissRGB = i18nc("@info:tooltip","The Tone Response Curve of this color space is either sRGB, L* or rec709 TRC."); QString currentModelStr = d->colorSpaceSelector->cmbColorModels->currentItem().id(); if (profileList.isEmpty()) { d->colorSpaceSelector->TongueWidget->setProfileDataAvailable(false); d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } else if (currentModelStr == "RGBA") { QVector colorants = currentColorSpace()->profile()->getColorantsxyY(); QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); if (currentColorSpace()->profile()->hasColorants()){ d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants); } else { colorants.fill(0.0); d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants); } d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF redcurve; QPolygonF greencurve; QPolygonF bluecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); redcurve<colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve); } else { QPolygonF curve = currentColorSpace()->estimatedTRCXYY(); redcurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4); greencurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9); bluecurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14); d->colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve); } if (estimatedTRC[0] == -1) { d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
"+estimatedCurve+""); } else { d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0]) + "," + QString::number(estimatedTRC[1]) + "," + QString::number(estimatedTRC[2])+"
"+estimatedCurve+""); } } else if (currentModelStr == "GRAYA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setGrayData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } d->colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); if (estimatedTRC[0] == -1) { d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
"+estimatedCurve+""); } else { d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"
"+estimatedCurve+""); } } else if (currentModelStr == "CMYKA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setCMYKData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; QPolygonF cyancurve; QPolygonF magentacurve; QPolygonF yellowcurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { QPolygonF curve = currentColorSpace()->estimatedTRCXYY(); cyancurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4); magentacurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9); yellowcurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14); tonecurve << curve.at(15) << curve.at(16) << curve.at(17) << curve.at(18) << curve.at(19); d->colorSpaceSelector->TRCwidget->setCMYKCurve(cyancurve, magentacurve, yellowcurve, tonecurve); } d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for CMYK.")); } else if (currentModelStr == "XYZA") { QString estimatedCurve = " Estimated curve: "; estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setXYZData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"< br />"+estimatedCurve+""); } else if (currentModelStr == "LABA") { estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setLABData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(""+i18nc("@info:tooltip","This is assumed to be the L * TRC. ")+"
"+estimatedCurve+""); } else if (currentModelStr == "YCbCrA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setYCbCrData(whitepoint); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for YCrCb.")); } d->colorSpaceSelector->textProfileDescription->clear(); if (profileList.isEmpty()==false) { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("About ","About ") + currentColorSpace()->name() + "/" + profileName + "

"); d->colorSpaceSelector->textProfileDescription->append("

"+ i18nc("ICC profile version","ICC Version: ") + QString::number(currentColorSpace()->profile()->version()) + "

"); //d->colorSpaceSelector->textProfileDescription->append("

"+ i18nc("Who made the profile?","Manufacturer: ") + currentColorSpace()->profile()->manufacturer() + "

"); //This would work if people actually wrote the manufacturer into the manufacturer fiedl... d->colorSpaceSelector->textProfileDescription->append("

"+ i18nc("What is the copyright? These are from embedded strings from the icc profile, so they default to english.","Copyright: ") + currentColorSpace()->profile()->copyright() + "

"); } else { d->colorSpaceSelector->textProfileDescription->append("

" + profileName + "

"); } if (currentModelStr == "RGBA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is RGB", "RGB (Red, Green, Blue), is the color model used by screens and other light-based media.
" "RGB is an additive color model: adding colors together makes them brighter. This color " "model is the most extensive of all color models, and is recommended as a model for painting," "that you can later convert to other spaces. RGB is also the recommended colorspace for HDR editing.")+"

"); } else if (currentModelStr == "CMYKA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is CMYK", "CMYK (Cyan, Magenta, Yellow, Key), " "is the model used by printers and other ink-based media.
" "CMYK is a subtractive model, meaning that adding colors together will turn them darker. Because of CMYK " "profiles being very specific per printer, it is recommended to work in RGB space, and then later convert " "to a CMYK profile, preferably one delivered by your printer.
" "CMYK is not recommended for painting." "Unfortunately, Krita cannot retrieve colorants or the TRC for this space.")+"

"); } else if (currentModelStr == "XYZA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is XYZ", "CIE XYZ" "is the space determined by the CIE as the space that encompasses all other colors, and used to " "convert colors between profiles. XYZ is an additive color model, meaning that adding colors together " "makes them brighter. XYZ is not recommended for painting, but can be useful to encode in. The Tone Response " "Curve is assumed to be linear.")+"

"); } else if (currentModelStr == "GRAYA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is Grayscale", "Grayscale only allows for " "gray values and transparent values. Grayscale images use half " "the memory and disk space compared to an RGB image of the same bit-depth.
" "Grayscale is useful for inking and greyscale images. In " "Krita, you can mix Grayscale and RGB layers in the same image.")+"

"); } else if (currentModelStr == "LABA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is LAB", "L*a*b. L stands for Lightness, " "the a and b components represent color channels.
" "L*a*b is a special model for color correction. It is based on human perception, meaning that it " "tries to encode the difference in lightness, red-green balance and yellow-blue balance. " "This makes it useful for color correction, but the vast majority of color maths in the blending " "modes do not work as expected here.
" "Similarly, Krita does not support HDR in LAB, meaning that HDR images converted to LAB lose color " "information. This colorspace is not recommended for painting, nor for export, " "but best as a space to do post-processing in. The TRC is assumed to be the L* TRC.")+"

"); } else if (currentModelStr == "YCbCrA") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("If the selected model is YCbCr", "YCbCr (Luma, Blue Chroma, Red Chroma), is a " "model designed for video encoding. It is based on human perception, meaning that it tries to " "encode the difference in lightness, red-green balance and yellow-blue balance. Chroma in " "this case is then a word indicating a special type of saturation, in these cases the saturation " "of Red and Blue, of which the desaturated equivalents are Green and Yellow respectively. It " "is available to open up certain images correctly, but Krita does not currently ship a profile for " "this due to lack of open source ICC profiles for YCrCb.")+"

"); } QString currentDepthStr = d->colorSpaceSelector->cmbColorDepth->currentItem().id(); if (currentDepthStr == "U8") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("When the selected Bitdepth is 8", "8 bit integer: The default amount of colors per channel. Each channel will have 256 values available, " "leading to a total amount of 256*amount of channels. Recommended to use for images intended for the web, " "or otherwise simple images.")+"

"); } else if (currentDepthStr == "U16") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("When the selected Bitdepth is 16", "16 bit integer: Also known as 'deep color'. 16 bit is ideal for editing images with a linear TRC, large " "color space, or just when you need more precise color blending. This does take twice as much space on " "the RAM and hard-drive than any given 8 bit image of the same properties, and for some devices it " "takes much more processing power. We recommend watching the RAM usage of the file carefully, or " "otherwise use 8 bit if your computer slows down. Take care to disable conversion optimization " "when converting from 16 bit/channel to 8 bit/channel.")+"

"); } else if (currentDepthStr == "F16") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("When the selected Bitdepth is 16 bit float", "16 bit floating point: Also known as 'Half Floating Point', and the standard in VFX industry images. " "16 bit float is ideal for editing images with a linear Tone Response Curve, large color space, or just when you need " "more precise color blending. It being floating point is an absolute requirement for Scene Referred " "(HDR) images. This does take twice as much space on the RAM and hard-drive than any given 8 bit image " "of the same properties, and for some devices it takes much more processing power. We recommend watching " "the RAM usage of the file carefully, or otherwise use 8 bit if your computer slows down.")+"

"); } else if (currentDepthStr == "F32") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("When the selected Bitdepth is 32bit float", "32 bit float point: Also known as 'Full Floating Point'. 32 bit float is ideal for editing images " "with a linear TRC, large color space, or just when you need more precise color blending. It being " "floating point is an absolute requirement for Scene Referred (HDR) images. This does take four times " "as much space on the RAM and hard-drive than any given 8 bit image of the same properties, and for " "some devices it takes much more processing power. We recommend watching the RAM usage of the file " "carefully, or otherwise use 8 bit if your computer slows down.")+"

"); } else if (currentDepthStr == "F64") { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("When the selected Bitdepth is 64bit float, but this isn't actually available in Krita at the moment.",\ "64 bit float point: 64 bit float is as precise as it gets in current technology, and this depth is used " "most of the time for images that are generated or used as an input for software. It being floating point " "is an absolute requirement for Scene Referred (HDR) images. This does take eight times as much space on " "the RAM and hard-drive than any given 8 bit image of the same properties, and for some devices it takes " "much more processing power. We recommend watching the RAM usage of the file carefully, or otherwise use " "8 bit if your computer slows down.")+"

"); } if (profileList.isEmpty()==false) { QString possibleConversionIntents = "

"+i18n("The following conversion intents are possible: ")+"

    "; if (currentColorSpace()->profile()->supportsPerceptual()){ possibleConversionIntents += "
  • "+i18n("Perceptual")+"
  • "; } if (currentColorSpace()->profile()->supportsRelative()){ possibleConversionIntents += "
  • "+i18n("Relative Colorimetric")+"
  • "; } if (currentColorSpace()->profile()->supportsAbsolute()){ possibleConversionIntents += "
  • "+i18n("Absolute Colorimetric")+"
  • "; } if (currentColorSpace()->profile()->supportsSaturation()){ possibleConversionIntents += "
  • "+i18n("Saturation")+"
  • "; } possibleConversionIntents += "

"; d->colorSpaceSelector->textProfileDescription->append(possibleConversionIntents); } if (profileName.contains("-elle-")) { d->colorSpaceSelector->textProfileDescription->append("

"+i18nc("These are Elle Stone's notes on her profiles that we ship.", "

Extra notes on profiles by Elle Stone:

" "

Krita comes with a number of high quality profiles created by " "Elle Stone. This is a summary. Please check " "the full documentation as well.

")); if (profileName.contains("ACES-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

Quoting Wikipedia, 'Academy Color Encoding System (ACES) is a color image " "encoding system proposed by the Academy of Motion Picture Arts and Sciences that will allow for " "a fully encompassing color accurate workflow, with 'seamless interchange of high quality motion " "picture images regardless of source'.

")); } if (profileName.contains("ACEScg-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

The ACEScg color space is smaller than the ACES color space, but large enough to contain the 'Rec-2020 gamut " "and the DCI-P3 gamut', unlike the ACES color space it has no negative values and contains only few colors " "that fall just barely outside the area of real colors humans can see

")); } if (profileName.contains("ClayRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

To avoid possible copyright infringement issues, I used 'ClayRGB' (following ArgyllCMS) as the base name " "for these profiles. As used below, 'Compatible with Adobe RGB 1998' is terminology suggested in the preamble " "to the AdobeRGB 1998 color space specifications.

" "The Adobe RGB 1998 color gamut covers a higher " "percentage of real-world cyans, greens, and yellow-greens than sRGB, but still doesn't include all printable " "cyans, greens, yellow-greens, especially when printing using today's high-end, wider gamut, ink jet printers. " "BetaRGB (not included in the profile pack) and Rec.2020 are better matches for the color gamuts of today's " "wide gamut printers.

" "The Adobe RGB 1998 color gamut is a reasonable approximation to some of today's " "high-end wide gamut monitors.

")); } if (profileName.contains("AllColorsRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

This profile's color gamut is roughly the same size and shape as the ACES color space gamut, " "and like the ACES color space, AllColorsRGB holds all possible real colors. But AllColorsRGB " "actually has a slightly larger color gamut (to capture some fringe colors that barely qualify " "as real when viewed by the standard observer) and uses the D50 white point.

" "Just like the ACES color space, AllColorsRGB holds a high percentage of imaginary colors. See the Completely " "" "Painless Programmer's Guide to XYZ, RGB, ICC, xyY, and TRCs for more information about imaginary " "colors.

" "There is no particular reason why anyone would want to use this profile " "for editing, unless one needs to make sure your color space really does hold all " "possible real colors.

")); } if (profileName.contains("CIERGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

This profile is included mostly for its historical significance. " "It's the color space that was used in the original color matching experiments " "that led to the creation of the XYZ reference color space.

" "The ASTM E white point " "is probably the right E white point to use when making the CIERGB color space profile. " "It's not clear to me what the correct CIERGB primaries really are. " "Lindbloom gives one set. The LCMS version 1 tutorial gives a different set. " "Experts in the field contend that the real primaries " "should be calculated from the spectral wavelengths, so I did.

")); } if (profileName.contains("IdentityRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

The IdentityRGB working space is included in the profile pack because it's a mathematically " "obvious way to include all possible visible colors, though it has a higher percentage of " "imaginary colors than the ACES and AllColorsRGB color spaces. I cannot think of any reason " "why you'd ever want to actually edit images in the IdentityRGB working space.

")); } if (profileName.contains("LargeRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

To avoid possible copyright infringement issues, I used 'LargeRGB' (following RawTherapee) " "as the base name for these profiles.

" "Kodak designed the RIMM/ROMM (ProPhotoRGB) color " "gamut to include all printable and most real world colors. It includes some imaginary colors " "and excludes some of the real world blues and violet blues that can be captured by digital " "cameras. It also excludes some very saturated 'camera-captured' yellows as interpreted by " "some (and probably many) camera matrix input profiles.

" "The ProPhotoRGB primaries are " "hard-coded into Adobe products such as Lightroom and the Dng-DCP camera 'profiles'. However, " "other than being large enough to hold a lot of colors, ProPhotoRGB has no particular merit " "as an RGB working space. Personally and for most editing purposes, I recommend BetaRGB, Rec2020, " "or the ACEScg profiles ProPhotoRGB.

")); } if (profileName.contains("Rec2020-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

Rec.2020 is the up-and-coming replacement for the thoroughly outdated sRGB color space. As of " "June 2015, very few (if any) display devices (and certainly no affordable display devices) can " "display all of Rec.2020. However, display technology is closing in on Rec.2020, movies are " "already being made for Rec.2020, and various cameras offer support for Rec.2020. And in the " "digital darkroom Rec.2020 is much more suitable as a general RGB working space than the " "exceedingly small sRGB color space.

")); } if (profileName.contains("sRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

Hewlett-Packard and Microsoft designed sRGB to match the color gamut of consumer-grade CRTs " "from the 1990s. sRGB is the standard color space for the world wide web and is still the best " "choice for exporting images to the internet.

" "The sRGB color gamut was a good match to " "calibrated decent quality CRTs. But sRGB is not a good match to many consumer-grade LCD monitors, " "which often cannot display the more saturated sRGB blues and magentas (the good news: as technology " "progresses, wider gamuts are trickling down to consumer grade monitors).

" "Printer color gamuts can easily exceed the sRGB color gamut in cyans, greens, and yellow-greens. Colors from interpolated " "camera raw files also often exceed the sRGB color gamut.

" "As a very relevant aside, using perceptual " "intent when converting to sRGB does not magically makes otherwise out of gamut colors fit inside the " "sRGB color gamut! The standard sRGB color space (along with all the other the RGB profiles provided " "in my profile pack) is a matrix profile, and matrix profiles don't have perceptual intent tables.

")); } if (profileName.contains("WideRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

To avoid possible copyright infringement issues, I used 'WideRGB' as the base name for these profiles.

" "WideGamutRGB was designed by Adobe to be a wide gamut color space that uses spectral colors " "as its primaries. Pascale's primary values produce a profile that matches old V2 Widegamut profiles " "from Adobe and Canon. It is an interesting color space, but shortly after its introduction, Adobe " "switched their emphasis to the ProPhotoRGB color space.

")); } if (profileName.contains("Gray-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

These profiles are for use with RGB images that have been converted to monotone gray (black and white). " "The main reason to convert from RGB to Gray is to save the file space needed to encode the image. " "Google places a premium on fast-loading web pages, and images are one of the slower-loading elements " "of a web page. So converting black and white images to Grayscale images does save some kilobytes. " " For grayscale images uploaded to the internet, convert the image to the V2 Gray profile with the sRGB TRC.

")); } if (profileName.contains("-g10")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

The profiles that end in '-g10.icc' are linear gamma (gamma=1.0, 'linear light', etc) profiles and " "should only be used when editing at high bit depths (16-bit floating point, 16-bit integer, 32-bit " "floating point, 32-bit integer). Many editing operations produce better results in linear gamma color " "spaces.

")); } if (profileName.contains("-labl")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

The profiles that end in '-labl.icc' have perceptually uniform TRCs. A few editing operations really " "should be done on perceptually uniform RGB. Make sure you use the V4 versions for editing high bit depth " "images.

")); } if (profileName.contains("-srgbtrc") || profileName.contains("-g22") || profileName.contains("-g18") || profileName.contains("-bt709")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

The profiles that end in '-srgbtrc.icc', '-g22.icc', and '-bt709.icc' have approximately but not exactly " "perceptually uniform TRCs. ProPhotoRGB's gamma=1.8 TRC is not quite as close to being perceptually uniform.

")); } if (d->colorSpaceSelector->cmbColorDepth->currentItem().id()=="U8") { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

When editing 8-bit images, you should use a profile with a small color gamut and an approximately or " "exactly perceptually uniform TRC. Of the profiles supplied in my profile pack, only the sRGB and AdobeRGB1998 " "(ClayRGB) color spaces are small enough for 8-bit editing. Even with the AdobeRGB1998 color space you need to " "be careful to not cause posterization. And of course you cannot use the linear gamma versions of these profiles " "for 8-bit editing.

")); } if (profileName.contains("-V4-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

Use V4 profiles for editing images using high bit depth image editors that use LCMS as the Color Management Module. " "This includes Krita, digiKam/showFoto, and GIMP 2.9.

")); } if (profileName.contains("-V2-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

Use V2 profiles for exporting finished images to be uploaded to the web or for use with imaging software that " "cannot read V4 profiles.

")); } } d->colorSpaceSelector->textProfileDescription->moveCursor(QTextCursor::Start); } QString KisAdvancedColorSpaceSelector::nameWhitePoint(QVector whitePoint) { QString name=(QString::number(whitePoint[0]) + ", " + QString::number(whitePoint[1], 'f', 4)); //A (0.451170, 0.40594) (2856K)(tungsten) if ((whitePoint[0]>0.451170-0.005 && whitePoint[0]<0.451170 + 0.005) && (whitePoint[1]>0.40594-0.005 && whitePoint[1]<0.40594 + 0.005)){ name="A"; return name; } //B (0.34980, 0.35270) (4874K) (Direct Sunlight at noon)(obsolete) //C (0.31039, 0.31905) (6774K) (avarage/north sky daylight)(obsolete) //D50 (0.34773, 0.35952) (5003K) (Horizon Light, default color of white paper, ICC profile standard illuminant) if ((whitePoint[0]>0.34773-0.005 && whitePoint[0]<0.34773 + 0.005) && (whitePoint[1]>0.35952-0.005 && whitePoint[1]<0.35952 + 0.005)){ name="D50"; return name; } //D55 (0.33411, 0.34877) (5503K) (Mid-morning / Mid-afternoon Daylight) if ((whitePoint[0]>0.33411-0.001 && whitePoint[0]<0.33411 + 0.001) && (whitePoint[1]>0.34877-0.005 && whitePoint[1]<0.34877 + 0.005)){ name="D55"; return name; } //D60 (0.3217, 0.3378) (~6000K) (ACES colorspace default) if ((whitePoint[0]>0.3217-0.001 && whitePoint[0]<0.3217 + 0.001) && (whitePoint[1]>0.3378-0.005 && whitePoint[1]<0.3378 + 0.005)){ name="D60"; return name; } //D65 (0.31382, 0.33100) (6504K) (Noon Daylight, default for computer and tv screens, sRGB default) //Elle's are old school with 0.3127 and 0.3289 if ((whitePoint[0]>0.31382-0.002 && whitePoint[0]<0.31382 + 0.002) && (whitePoint[1]>0.33100-0.005 && whitePoint[1]<0.33100 + 0.002)){ name="D65"; return name; } //D75 (0.29968, 0.31740) (7504K) (North sky Daylight) if ((whitePoint[0]>0.29968-0.001 && whitePoint[0]<0.29968 + 0.001) && (whitePoint[1]>0.31740-0.005 && whitePoint[1]<0.31740 + 0.005)){ name="D75"; return name; } //E (1/3, 1/3) (5454K) (Equal Energy. CIERGB default) if ((whitePoint[0]>(1.0/3.0)-0.001 && whitePoint[0]<(1.0/3.0) + 0.001) && (whitePoint[1]>(1.0/3.0)-0.001 && whitePoint[1]<(1.0/3.0) + 0.001)){ name="E"; return name; } //The F series seems to sorta overlap with the D series, so I'll just leave them in comment here.// //F1 (0.31811, 0.33559) (6430K) (Daylight Fluorescent) //F2 (0.37925, 0.36733) (4230K) (Cool White Fluorescent) //F3 (0.41761, 0.38324) (3450K) (White Florescent) //F4 (0.44920, 0.39074) (2940K) (Warm White Fluorescent) //F5 (0.31975, 0.34246) (6350K) (Daylight Fluorescent) //F6 (0.38660, 0.37847) (4150K) (Lite White Fluorescent) //F7 (0.31569, 0.32960) (6500K) (D65 simulator, Daylight simulator) //F8 (0.34902, 0.35939) (5000K) (D50 simulator) //F9 (0.37829, 0.37045) (4150K) (Cool White Deluxe Fluorescent) //F10 (0.35090, 0.35444) (5000K) (Philips TL85, Ultralume 50) //F11 (0.38541, 0.37123) (4000K) (Philips TL84, Ultralume 40) //F12 (0.44256, 0.39717) (3000K) (Philips TL83, Ultralume 30) return name; } const KoColorSpace* KisAdvancedColorSpaceSelector::currentColorSpace() { QString check = ""; if (d->colorSpaceSelector->lstProfile->currentItem()) { check = d->colorSpaceSelector->lstProfile->currentItem()->text(); } else if (d->colorSpaceSelector->lstProfile->item(0)) { check = d->colorSpaceSelector->lstProfile->item(0)->text(); } return KoColorSpaceRegistry::instance()->colorSpace(d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), check); } void KisAdvancedColorSpaceSelector::setCurrentColorModel(const KoID& id) { d->colorSpaceSelector->cmbColorModels->setCurrent(id); fillLstProfiles(); fillCmbDepths(id); } void KisAdvancedColorSpaceSelector::setCurrentColorDepth(const KoID& id) { d->colorSpaceSelector->cmbColorDepth->setCurrent(id); fillLstProfiles(); } void KisAdvancedColorSpaceSelector::setCurrentProfile(const QString& name) { QList Items= d->colorSpaceSelector->lstProfile->findItems(name, Qt::MatchStartsWith); d->colorSpaceSelector->lstProfile->setCurrentItem(Items.at(0)); } void KisAdvancedColorSpaceSelector::setCurrentColorSpace(const KoColorSpace* colorSpace) { if (!colorSpace) { return; } setCurrentColorModel(colorSpace->colorModelId()); setCurrentColorDepth(colorSpace->colorDepthId()); setCurrentProfile(colorSpace->profile()->name()); } void KisAdvancedColorSpaceSelector::colorSpaceChanged() { bool valid = d->colorSpaceSelector->lstProfile->count() != 0; emit(selectionChanged(valid)); if (valid) { emit colorSpaceChanged(currentColorSpace()); } } void KisAdvancedColorSpaceSelector::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { QUrl file(profileName); if (!QFile::copy(profileName, saveLocation + file.fileName())) { dbgKrita << "Could not install profile!"; return; } iccEngine->addProfile(saveLocation + file.fileName()); } fillLstProfiles(); } diff --git a/libs/ui/widgets/kis_cmb_idlist.cc b/libs/ui/widgets/kis_cmb_idlist.cc index 66e6b7b4c8..79a2dc99e6 100644 --- a/libs/ui/widgets/kis_cmb_idlist.cc +++ b/libs/ui/widgets/kis_cmb_idlist.cc @@ -1,99 +1,99 @@ /* * kis_cmb_idlist.cc - part of KImageShop/Krayon/Krita * * Copyright (c) 2005 Boudewijn Rempt (boud@valdyas.org) * * 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 "widgets/kis_cmb_idlist.h" #include #include #include KisCmbIDList::KisCmbIDList(QWidget * parent, const char * name) : QComboBox(parent) { setObjectName(name); setEditable(false); connect(this, SIGNAL(activated(int)), this, SLOT(slotIDActivated(int))); connect(this, SIGNAL(highlighted(int)), this, SLOT(slotIDHighlighted(int))); } KisCmbIDList::~KisCmbIDList() { } void KisCmbIDList::setIDList(const QList & list) { clear(); m_list = list; - qSort(m_list.begin(), m_list.end(), KoID::compareNames); + std::sort(m_list.begin(), m_list.end(), KoID::compareNames); for (qint32 i = 0; i < m_list.count(); ++i) { addItem(m_list.at(i).name()); } } KoID KisCmbIDList::currentItem() const { qint32 i = QComboBox::currentIndex(); if (i > m_list.count() - 1 || i < 0) return KoID(); return m_list[i]; } void KisCmbIDList::setCurrent(const KoID id) { qint32 index = m_list.indexOf(id); if (index >= 0) { QComboBox::setCurrentIndex(index); } else { m_list.push_back(id); addItem(id.name()); QComboBox::setCurrentIndex(m_list.count() - 1); } } void KisCmbIDList::setCurrent(const QString & s) { for (qint32 i = 0; i < m_list.count(); ++i) { if (m_list.at(i).id() == s) { QComboBox::setCurrentIndex(i); break; } } } void KisCmbIDList::slotIDActivated(int i) { if (i > m_list.count() - 1) return; emit activated(m_list[i]); } void KisCmbIDList::slotIDHighlighted(int i) { if (i > m_list.count() - 1) return; emit highlighted(m_list[i]); } diff --git a/libs/ui/widgets/kis_color_space_selector.cc b/libs/ui/widgets/kis_color_space_selector.cc index 870c115705..2f508b769b 100644 --- a/libs/ui/widgets/kis_color_space_selector.cc +++ b/libs/ui/widgets/kis_color_space_selector.cc @@ -1,274 +1,274 @@ /* * Copyright (C) 2007 Cyrille Berger * Copyright (C) 2011 Boudewijn Rempt * Copyright (C) 2011 Srikanth Tiyyagura * * 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_color_space_selector.h" #include "kis_advanced_color_space_selector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgcolorspaceselector.h" struct KisColorSpaceSelector::Private { Ui_WdgColorSpaceSelector* colorSpaceSelector; QString knsrcFile; bool profileValid; QString defaultsuffix; }; KisColorSpaceSelector::KisColorSpaceSelector(QWidget* parent) : QWidget(parent), m_advancedSelector(0), d(new Private) { setObjectName("KisColorSpaceSelector"); d->colorSpaceSelector = new Ui_WdgColorSpaceSelector; d->colorSpaceSelector->setupUi(this); d->colorSpaceSelector->cmbColorModels->setIDList(KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::OnlyUserVisible)); fillCmbDepths(d->colorSpaceSelector->cmbColorModels->currentItem()); d->colorSpaceSelector->bnInstallProfile->setIcon(KisIconUtils::loadIcon("document-open")); d->colorSpaceSelector->bnInstallProfile->setToolTip( i18n("Open Color Profile") ); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)), this, SLOT(fillCmbDepths(const KoID &))); connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(const KoID &)), this, SLOT(fillCmbProfiles())); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)), this, SLOT(fillCmbProfiles())); connect(d->colorSpaceSelector->cmbProfile, SIGNAL(activated(const QString &)), this, SLOT(colorSpaceChanged())); connect(d->colorSpaceSelector->bnInstallProfile, SIGNAL(clicked()), this, SLOT(installProfile())); d->defaultsuffix = " "+i18nc("This is appended to the color profile which is the default for the given colorspace and bit-depth","(Default)"); connect(d->colorSpaceSelector->bnAdvanced, SIGNAL(clicked()), this, SLOT(slotOpenAdvancedSelector())); fillCmbProfiles(); } KisColorSpaceSelector::~KisColorSpaceSelector() { delete d->colorSpaceSelector; delete d; } void KisColorSpaceSelector::fillCmbProfiles() { const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); const QString defaultProfileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId); d->colorSpaceSelector->cmbProfile->clear(); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); QStringList profileNames; Q_FOREACH (const KoColorProfile *profile, profileList) { profileNames.append(profile->name()); } - qSort(profileNames); + std::sort(profileNames.begin(), profileNames.end()); Q_FOREACH (QString stringName, profileNames) { if (stringName == defaultProfileName) { d->colorSpaceSelector->cmbProfile->addSqueezedItem(stringName + d->defaultsuffix); } else { d->colorSpaceSelector->cmbProfile->addSqueezedItem(stringName); } } d->colorSpaceSelector->cmbProfile->setCurrent(defaultProfileName + d->defaultsuffix); colorSpaceChanged(); } void KisColorSpaceSelector::fillCmbDepths(const KoID& id) { KoID activeDepth = d->colorSpaceSelector->cmbColorDepth->currentItem(); d->colorSpaceSelector->cmbColorDepth->clear(); QList depths = KoColorSpaceRegistry::instance()->colorDepthList(id, KoColorSpaceRegistry::OnlyUserVisible); // order the depth by name - qSort(depths.begin(), depths.end(), sortBitDepthsComparer); + std::sort(depths.begin(), depths.end(), sortBitDepthsComparer); d->colorSpaceSelector->cmbColorDepth->setIDList(depths); if (depths.contains(activeDepth)) { d->colorSpaceSelector->cmbColorDepth->setCurrent(activeDepth); } } bool KisColorSpaceSelector::sortBitDepthsComparer(KoID depthOne, KoID depthTwo) { // to order these right, we need to first order by bit depth, then by if it is floating or not QString bitDepthOne = depthOne.name().split(" ")[0]; QString bitDepthTwo = depthTwo.name().split(" ")[0]; if (bitDepthOne.toInt() > bitDepthTwo.toInt()) { return false; } if (bitDepthOne.toInt() == bitDepthTwo.toInt()) { // bit depth number is the same, so now we need to compare if it is a floating type or not // the second value [1], just says 'bits', so that is why we look for [2] which has the float word QString bitDepthOneType = ""; QString bitDepthTwoType = ""; if (depthOne.name().split(" ").length() > 2) { bitDepthOneType = depthOne.name().split(" ")[2]; } if (depthTwo.name().split(" ").length() > 2) { bitDepthTwoType = depthTwo.name().split(" ")[2]; } if (bitDepthOneType.length() > bitDepthTwoType.length()) { return false; } } return true; } const KoColorSpace* KisColorSpaceSelector::currentColorSpace() { QString profilenamestring = d->colorSpaceSelector->cmbProfile->itemHighlighted(); if (profilenamestring.contains(d->defaultsuffix)) { profilenamestring.remove(d->defaultsuffix); return KoColorSpaceRegistry::instance()->colorSpace( d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), profilenamestring); } else { return KoColorSpaceRegistry::instance()->colorSpace( d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), profilenamestring); } } void KisColorSpaceSelector::setCurrentColorModel(const KoID& id) { d->colorSpaceSelector->cmbColorModels->setCurrent(id); fillCmbDepths(id); } void KisColorSpaceSelector::setCurrentColorDepth(const KoID& id) { d->colorSpaceSelector->cmbColorDepth->setCurrent(id); fillCmbProfiles(); } void KisColorSpaceSelector::setCurrentProfile(const QString& name) { d->colorSpaceSelector->cmbProfile->setCurrent(name); } void KisColorSpaceSelector::setCurrentColorSpace(const KoColorSpace* colorSpace) { if (!colorSpace) { return; } setCurrentColorModel(colorSpace->colorModelId()); setCurrentColorDepth(colorSpace->colorDepthId()); setCurrentProfile(colorSpace->profile()->name()); } void KisColorSpaceSelector::showColorBrowserButton(bool showButton) { d->colorSpaceSelector->bnAdvanced->setVisible(showButton); } void KisColorSpaceSelector::colorSpaceChanged() { bool valid = d->colorSpaceSelector->cmbProfile->count() != 0; d->profileValid = valid; emit(selectionChanged(valid)); if(valid) { emit colorSpaceChanged(currentColorSpace()); QString text = currentColorSpace()->profile()->name(); } } void KisColorSpaceSelector::installProfile() { QStringList mime; KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { QUrl file(profileName); if (!QFile::copy(profileName, saveLocation + file.fileName())) { dbgKrita << "Could not install profile!"; return; } iccEngine->addProfile(saveLocation + file.fileName()); } fillCmbProfiles(); } void KisColorSpaceSelector::slotOpenAdvancedSelector() { if (!m_advancedSelector) { m_advancedSelector = new KisAdvancedColorSpaceSelector(this, "Select a Colorspace"); m_advancedSelector->setModal(true); if (currentColorSpace()) { m_advancedSelector->setCurrentColorSpace(currentColorSpace()); } connect(m_advancedSelector, SIGNAL(selectionChanged(bool)), this, SLOT(slotProfileValid(bool)) ); } QDialog::DialogCode result = (QDialog::DialogCode)m_advancedSelector->exec(); if (result) { if (d->profileValid==true) { setCurrentColorSpace(m_advancedSelector->currentColorSpace()); } } } void KisColorSpaceSelector::slotProfileValid(bool valid) { d->profileValid = valid; } diff --git a/libs/ui/widgets/kis_paintop_presets_popup.cpp b/libs/ui/widgets/kis_paintop_presets_popup.cpp index df703f320c..60df1e9e01 100644 --- a/libs/ui/widgets/kis_paintop_presets_popup.cpp +++ b/libs/ui/widgets/kis_paintop_presets_popup.cpp @@ -1,584 +1,584 @@ /* This file is part of the KDE project * Copyright (C) 2008 Boudewijn Rempt * Copyright (C) 2010 Lukáš Tvrdý * Copyright (C) 2011 Silvio Heinrich * * 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 "widgets/kis_paintop_presets_popup.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 "kis_config.h" #include "kis_resource_server_provider.h" #include "kis_lod_availability_widget.h" #include "kis_signal_auto_connection.h" // ones from brush engine selector #include struct KisPaintOpPresetsPopup::Private { public: Ui_WdgPaintOpSettings uiWdgPaintOpPresetSettings; QGridLayout *layout; KisPaintOpConfigWidget *settingsWidget; QFont smallFont; KisCanvasResourceProvider *resourceProvider; bool detached; bool ignoreHideEvents; QSize minimumSettingsWidgetSize; QRect detachedGeometry; KisSignalAutoConnectionsStore widgetConnections; }; KisPaintOpPresetsPopup::KisPaintOpPresetsPopup(KisCanvasResourceProvider * resourceProvider, QWidget * parent) : QWidget(parent) , m_d(new Private()) { setObjectName("KisPaintOpPresetsPopup"); setFont(KoDockRegistry::dockFont()); current_paintOpId = ""; m_d->resourceProvider = resourceProvider; m_d->uiWdgPaintOpPresetSettings.setupUi(this); m_d->layout = new QGridLayout(m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer); m_d->layout->setSizeConstraint(QLayout::SetFixedSize); m_d->uiWdgPaintOpPresetSettings.scratchPad->setupScratchPad(resourceProvider, Qt::white); m_d->uiWdgPaintOpPresetSettings.scratchPad->setCutoutOverlayRect(QRect(25, 25, 200, 200)); m_d->uiWdgPaintOpPresetSettings.fillLayer->setIcon(KisIconUtils::loadIcon("document-new")); m_d->uiWdgPaintOpPresetSettings.fillLayer->hide(); m_d->uiWdgPaintOpPresetSettings.fillGradient->setIcon(KisIconUtils::loadIcon("krita_tool_gradient")); m_d->uiWdgPaintOpPresetSettings.fillSolid->setIcon(KisIconUtils::loadIcon("krita_tool_color_fill")); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setIcon(KisIconUtils::loadIcon("edit-delete")); m_d->uiWdgPaintOpPresetSettings.paintPresetIcon->setIcon(KisIconUtils::loadIcon("krita_tool_freehand")); // DETAIL and THUMBNAIL view changer QMenu* menu = new QMenu(this); menu->setStyleSheet("margin: 6px"); menu->addSection(i18n("Display")); QActionGroup *actionGroup = new QActionGroup(this); KisPresetChooser::ViewMode mode = (KisPresetChooser::ViewMode)KisConfig().presetChooserViewMode(); QAction* action = menu->addAction(KisIconUtils::loadIcon("view-preview"), i18n("Thumbnails"), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotThumbnailMode())); action->setCheckable(true); action->setChecked(mode == KisPresetChooser::THUMBNAIL); action->setActionGroup(actionGroup); action = menu->addAction(KisIconUtils::loadIcon("view-list-details"), i18n("Details"), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotDetailMode())); action->setCheckable(true); action->setChecked(mode == KisPresetChooser::DETAIL); action->setActionGroup(actionGroup); // add horizontal slider for the icon size QSlider* iconSizeSlider = new QSlider(this); iconSizeSlider->setOrientation(Qt::Horizontal); iconSizeSlider->setRange(30, 80); iconSizeSlider->setValue(m_d->uiWdgPaintOpPresetSettings.presetWidget->iconSize()); iconSizeSlider->setMinimumHeight(20); iconSizeSlider->setMinimumWidth(40); iconSizeSlider->setTickInterval(10); QWidgetAction *sliderAction= new QWidgetAction(this); sliderAction->setDefaultWidget(iconSizeSlider); menu->addSection(i18n("Icon Size")); menu->addAction(sliderAction); // configure the button and assign menu m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setMenu(menu); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setIcon(KisIconUtils::loadIcon("view-choose")); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setPopupMode(QToolButton::InstantPopup); // show/hide buttons KisConfig cfg; m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setCheckable(true); m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setChecked(cfg.scratchpadVisible()); m_d->uiWdgPaintOpPresetSettings.showEditorButton->setCheckable(true); m_d->uiWdgPaintOpPresetSettings.showEditorButton->setChecked(true); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setText(i18n("Presets")); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setCheckable(true); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setChecked(false); // use a config to load/save this state slotSwitchShowPresets(false); // hide presets by default // Connections connect(iconSizeSlider, SIGNAL(sliderMoved(int)), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotSetIconSize(int))); connect(iconSizeSlider, SIGNAL(sliderReleased()), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotSaveIconSize())); connect(m_d->uiWdgPaintOpPresetSettings.showScratchpadButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchScratchpad(bool))); connect(m_d->uiWdgPaintOpPresetSettings.showEditorButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchShowEditor(bool))); connect(m_d->uiWdgPaintOpPresetSettings.showPresetsButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchShowPresets(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraseScratchPad, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillDefault())); connect(m_d->uiWdgPaintOpPresetSettings.fillLayer, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillLayer())); connect(m_d->uiWdgPaintOpPresetSettings.fillGradient, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillGradient())); connect(m_d->uiWdgPaintOpPresetSettings.fillSolid, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillBackground())); connect(m_d->uiWdgPaintOpPresetSettings.paintPresetIcon, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(paintPresetImage())); m_d->settingsWidget = 0; setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); connect(m_d->uiWdgPaintOpPresetSettings.bnSave, SIGNAL(clicked()), this, SIGNAL(savePresetClicked())); connect(m_d->uiWdgPaintOpPresetSettings.reload, SIGNAL(clicked()), this, SIGNAL(reloadPresetClicked())); connect(m_d->uiWdgPaintOpPresetSettings.bnDefaultPreset, SIGNAL(clicked()), this, SIGNAL(defaultPresetClicked())); connect(m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(dirtyPresetToggled(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(eraserBrushSizeToggled(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(eraserBrushOpacityToggled(bool))); connect(m_d->uiWdgPaintOpPresetSettings.bnDefaultPreset, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.txtPreset, SLOT(clear())); connect(m_d->uiWdgPaintOpPresetSettings.txtPreset, SIGNAL(textChanged(QString)), SLOT(slotWatchPresetNameLineEdit())); // preset widget connections connect(m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SIGNAL(resourceSelected(KoResource*)), this, SIGNAL(signalResourceSelected(KoResource*))); connect(m_d->uiWdgPaintOpPresetSettings.bnSave, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SLOT(updateViewSettings())); connect(m_d->uiWdgPaintOpPresetSettings.reload, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SLOT(updateViewSettings())); m_d->detached = false; m_d->ignoreHideEvents = false; m_d->minimumSettingsWidgetSize = QSize(0, 0); m_d->uiWdgPaintOpPresetSettings.scratchpadControls->setVisible(cfg.scratchpadVisible()); m_d->detachedGeometry = QRect(100, 100, 0, 0); m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox->setChecked(cfg.useDirtyPresets()); m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox->setChecked(cfg.useEraserBrushSize()); m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox->setChecked(cfg.useEraserBrushOpacity()); m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->setCanvasResourceManager(resourceProvider->resourceManager()); connect(resourceProvider->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), SLOT(slotResourceChanged(int, QVariant))); connect(m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability, SIGNAL(sigUserChangedLodAvailability(bool)), SLOT(slotLodAvailabilityChanged(bool))); slotResourceChanged(KisCanvasResourceProvider::LodAvailability, resourceProvider->resourceManager()-> resource(KisCanvasResourceProvider::LodAvailability)); connect(m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdatePaintOpFilter())); } void KisPaintOpPresetsPopup::slotResourceChanged(int key, const QVariant &value) { if (key == KisCanvasResourceProvider::LodAvailability) { m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->slotUserChangedLodAvailability(value.toBool()); } } void KisPaintOpPresetsPopup::slotLodAvailabilityChanged(bool value) { m_d->resourceProvider->resourceManager()->setResource(KisCanvasResourceProvider::LodAvailability, QVariant(value)); } KisPaintOpPresetsPopup::~KisPaintOpPresetsPopup() { if (m_d->settingsWidget) { m_d->layout->removeWidget(m_d->settingsWidget); m_d->settingsWidget->hide(); m_d->settingsWidget->setParent(0); m_d->settingsWidget = 0; } delete m_d; } void KisPaintOpPresetsPopup::setPaintOpSettingsWidget(QWidget * widget) { if (m_d->settingsWidget) { m_d->layout->removeWidget(m_d->settingsWidget); m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer->updateGeometry(); } m_d->layout->update(); updateGeometry(); m_d->widgetConnections.clear(); m_d->settingsWidget = 0; if (widget) { m_d->settingsWidget = dynamic_cast(widget); KIS_ASSERT_RECOVER_RETURN(m_d->settingsWidget); if (m_d->settingsWidget->supportScratchBox()) { showScratchPad(); } else { hideScratchPad(); } m_d->widgetConnections.addConnection(m_d->settingsWidget, SIGNAL(sigConfigurationItemChanged()), this, SLOT(slotUpdateLodAvailability())); widget->setFont(m_d->smallFont); QSize hint = widget->sizeHint(); m_d->minimumSettingsWidgetSize = QSize(qMax(hint.width(), m_d->minimumSettingsWidgetSize.width()), qMax(hint.height(), m_d->minimumSettingsWidgetSize.height())); widget->setMinimumSize(m_d->minimumSettingsWidgetSize); m_d->layout->addWidget(widget); m_d->layout->update(); widget->show(); } slotUpdateLodAvailability(); } void KisPaintOpPresetsPopup::slotUpdateLodAvailability() { if (!m_d->settingsWidget) return; KisPaintopLodLimitations l = m_d->settingsWidget->lodLimitations(); m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->setLimitations(l); } void KisPaintOpPresetsPopup::slotWatchPresetNameLineEdit() { QString text = m_d->uiWdgPaintOpPresetSettings.txtPreset->text(); KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); bool overwrite = rServer->resourceByName(text) != 0; KisPaintOpPresetSP preset = m_d->resourceProvider->currentPreset(); bool btnSaveAvailable = preset->valid() && (preset->isPresetDirty() | !overwrite); QString btnText = overwrite ? i18n("Overwrite Preset") : i18n("Save to Presets"); m_d->uiWdgPaintOpPresetSettings.bnSave->setText(btnText); m_d->uiWdgPaintOpPresetSettings.bnSave->setEnabled(btnSaveAvailable); m_d->uiWdgPaintOpPresetSettings.reload->setVisible(true); m_d->uiWdgPaintOpPresetSettings.reload->setEnabled(btnSaveAvailable && overwrite); QFont font = m_d->uiWdgPaintOpPresetSettings.txtPreset->font(); font.setItalic(btnSaveAvailable); m_d->uiWdgPaintOpPresetSettings.txtPreset->setFont(font); } QString KisPaintOpPresetsPopup::getPresetName() const { return m_d->uiWdgPaintOpPresetSettings.txtPreset->text(); } QImage KisPaintOpPresetsPopup::cutOutOverlay() { return m_d->uiWdgPaintOpPresetSettings.scratchPad->cutoutOverlay(); } void KisPaintOpPresetsPopup::contextMenuEvent(QContextMenuEvent *e) { Q_UNUSED(e); } void KisPaintOpPresetsPopup::switchDetached(bool show) { if (parentWidget()) { m_d->detached = !m_d->detached; if (m_d->detached) { m_d->ignoreHideEvents = true; if (show) { parentWidget()->show(); } m_d->ignoreHideEvents = false; } else { KisConfig cfg; parentWidget()->hide(); } KisConfig cfg; cfg.setPaintopPopupDetached(m_d->detached); } } void KisPaintOpPresetsPopup::hideScratchPad() { m_d->uiWdgPaintOpPresetSettings.scratchPad->setEnabled(false); m_d->uiWdgPaintOpPresetSettings.fillGradient->setEnabled(false); m_d->uiWdgPaintOpPresetSettings.fillSolid->setEnabled(false); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setEnabled(false); } void KisPaintOpPresetsPopup::showScratchPad() { m_d->uiWdgPaintOpPresetSettings.scratchPad->setEnabled(true); m_d->uiWdgPaintOpPresetSettings.fillGradient->setEnabled(true); m_d->uiWdgPaintOpPresetSettings.fillSolid->setEnabled(true); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setEnabled(true); } void KisPaintOpPresetsPopup::resourceSelected(KoResource* resource) { m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->setCurrentResource(resource); m_d->uiWdgPaintOpPresetSettings.txtPreset->setText(resource->name()); slotWatchPresetNameLineEdit(); // find the display name of the brush engine and append it to the selected preset display QString currentBrushEngineName; for(int i=0; i < sortedBrushEnginesList.length(); i++) { if (sortedBrushEnginesList.at(i).id == currentPaintOpId() ) { currentBrushEngineName = sortedBrushEnginesList.at(i).name; } } QString selectedBrush = resource->name().append(" (").append(currentBrushEngineName).append(" ").append("Engine").append(")"); m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setText(selectedBrush); } bool variantLessThan(const KisPaintOpInfo v1, const KisPaintOpInfo v2) { return v1.priority < v2.priority; } void KisPaintOpPresetsPopup::setPaintOpList(const QList< KisPaintOpFactory* >& list) { m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->clear(); // reset combobox list just in case // create a new list so we can sort it and populate the brush engine combo box sortedBrushEnginesList.clear(); // just in case this function is called again, don't keep adding to the list for(int i=0; i < list.length(); i++) { QString fileName = KoResourcePaths::findResource("kis_images", list.at(i)->pixmap()); QPixmap pixmap(fileName); if(pixmap.isNull()){ pixmap = QPixmap(22,22); pixmap.fill(); } KisPaintOpInfo paintOpInfo; paintOpInfo.id = list.at(i)->id(); paintOpInfo.name = list.at(i)->name(); paintOpInfo.icon = pixmap; paintOpInfo.priority = list.at(i)->priority(); sortedBrushEnginesList.append(paintOpInfo); } - qStableSort(sortedBrushEnginesList.begin(), sortedBrushEnginesList.end(), variantLessThan ); + std::stable_sort(sortedBrushEnginesList.begin(), sortedBrushEnginesList.end(), variantLessThan ); // add an "All" option at the front to show all presets QPixmap emptyPixmap = QPixmap(22,22); emptyPixmap.fill(palette().color(QPalette::Background)); sortedBrushEnginesList.push_front(KisPaintOpInfo(QString("all_options"), i18n("All"), QString(""), emptyPixmap, 0 )); // fill the list into the brush combo box for (int m = 0; m < sortedBrushEnginesList.length(); m++) { m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->addItem(sortedBrushEnginesList[m].icon, sortedBrushEnginesList[m].name, QVariant(sortedBrushEnginesList[m].id)); } } void KisPaintOpPresetsPopup::setCurrentPaintOpId(const QString& paintOpId) { current_paintOpId = paintOpId; } QString KisPaintOpPresetsPopup::currentPaintOpId() { return current_paintOpId; } void KisPaintOpPresetsPopup::setPresetImage(const QImage& image) { m_d->uiWdgPaintOpPresetSettings.scratchPad->setPresetImage(image); } void KisPaintOpPresetsPopup::hideEvent(QHideEvent *event) { if (m_d->ignoreHideEvents) { return; } if (m_d->detached) { m_d->detachedGeometry = window()->geometry(); } QWidget::hideEvent(event); } void KisPaintOpPresetsPopup::showEvent(QShowEvent *) { if (m_d->detached) { window()->setGeometry(m_d->detachedGeometry); } emit brushEditorShown(); } void KisPaintOpPresetsPopup::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); emit sizeChanged(); } bool KisPaintOpPresetsPopup::detached() const { return m_d->detached; } void KisPaintOpPresetsPopup::slotSwitchScratchpad(bool visible) { m_d->uiWdgPaintOpPresetSettings.scratchpadControls->setVisible(visible); KisConfig cfg; cfg.setScratchpadVisible(visible); } void KisPaintOpPresetsPopup::slotSwitchShowEditor(bool visible) { m_d->uiWdgPaintOpPresetSettings.brushEditorSettingsControls->setVisible(visible); } void KisPaintOpPresetsPopup::slotSwitchShowPresets(bool visible) { m_d->uiWdgPaintOpPresetSettings.presetsContainer->setVisible(visible); } void KisPaintOpPresetsPopup::slotUpdatePaintOpFilter() { QVariant userData = m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->currentData(); // grab paintOpID from data QString filterPaintOpId = userData.toString(); if (filterPaintOpId == "all_options") { filterPaintOpId = ""; } m_d->uiWdgPaintOpPresetSettings.presetWidget->setPresetFilter(filterPaintOpId); } void KisPaintOpPresetsPopup::updateViewSettings() { m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->updateViewSettings(); } void KisPaintOpPresetsPopup::currentPresetChanged(KisPaintOpPresetSP preset) { m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->setCurrentResource(preset.data()); setCurrentPaintOpId(preset->paintOp().id()); } void KisPaintOpPresetsPopup::updateThemedIcons() { m_d->uiWdgPaintOpPresetSettings.fillLayer->setIcon(KisIconUtils::loadIcon("document-new")); m_d->uiWdgPaintOpPresetSettings.fillLayer->hide(); m_d->uiWdgPaintOpPresetSettings.fillGradient->setIcon(KisIconUtils::loadIcon("krita_tool_gradient")); m_d->uiWdgPaintOpPresetSettings.fillSolid->setIcon(KisIconUtils::loadIcon("krita_tool_color_fill")); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setIcon(KisIconUtils::loadIcon("edit-delete")); m_d->uiWdgPaintOpPresetSettings.paintPresetIcon->setIcon(KisIconUtils::loadIcon("krita_tool_freehand")); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setIcon(KisIconUtils::loadIcon("view-choose")); } diff --git a/libs/ui/widgets/kis_widget_chooser.cpp b/libs/ui/widgets/kis_widget_chooser.cpp index d9b606711a..a77353eb46 100644 --- a/libs/ui/widgets/kis_widget_chooser.cpp +++ b/libs/ui/widgets/kis_widget_chooser.cpp @@ -1,289 +1,289 @@ /* * Copyright (c) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_widget_chooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" KisWidgetChooser::KisWidgetChooser(int id, QWidget* parent) : QFrame(parent) , m_chooserid(id) { // QFrame::setFrameStyle(QFrame::StyledPanel|QFrame::Raised); m_acceptIcon = KisIconUtils::loadIcon("list-add"); m_buttons = new QButtonGroup(); m_popup = new QFrame(0, Qt::Popup); m_arrowButton = new QToolButton(); m_popup->setFrameStyle(QFrame::Panel|QFrame::Raised); m_arrowButton->setFixedWidth(m_arrowButton->sizeHint().height()/2); m_arrowButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); m_arrowButton->setAutoRaise(true); updateArrowIcon(); connect(m_arrowButton, SIGNAL(clicked(bool)), SLOT(slotButtonPressed())); } KisWidgetChooser::~KisWidgetChooser() { delete m_buttons; delete m_popup; delete m_arrowButton; } void KisWidgetChooser::updateArrowIcon() { QImage image(16, 16, QImage::Format_ARGB32); image.fill(0); QStylePainter painter(&image, this); QStyleOption option; option.rect = image.rect(); option.palette = palette(); option.state = QStyle::State_Enabled; option.palette.setBrush(QPalette::ButtonText, option.palette.text()); painter.setBrush(option.palette.text().color()); painter.setPen(option.palette.text().color()); painter.drawPrimitive(QStyle::PE_IndicatorArrowDown, option); m_arrowButton->setIcon(QIcon(QPixmap::fromImage(image))); } void KisWidgetChooser::addWidget(const QString& id, const QString& label, QWidget* widget) { if(id.isEmpty()) { delete widget; return; } removeWidget(id); if (label.isEmpty()) { m_widgets.push_back(Data(id, widget, 0)); } else { m_widgets.push_back(Data(id, widget, new QLabel(label))); } delete m_popup->layout(); m_popup->setLayout(createPopupLayout()); m_popup->adjustSize(); delete QWidget::layout(); QWidget::setLayout(createLayout()); } QLayout* KisWidgetChooser::createLayout() { QHBoxLayout* layout = new QHBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); for(Iterator i=m_widgets.begin(); i!=m_widgets.end(); ++i) { if(i->choosen) { if (i->label) { layout->addWidget(i->label); } layout->addWidget(i->widget); break; } } layout->addWidget(m_arrowButton); return layout; } QLayout* KisWidgetChooser::createPopupLayout() { QGridLayout* layout = new QGridLayout(); int row = 0; int idx = 0; layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); QButtonGroup* group = new QButtonGroup(); QList buttons = m_buttons->buttons(); for(Iterator i=m_widgets.begin(); i!=m_widgets.end(); ++i) { if(!i->choosen) { if(row == buttons.size()) { QToolButton* bn = new QToolButton(); m_acceptIcon = KisIconUtils::loadIcon("list-add"); bn->setIcon(m_acceptIcon); bn->setAutoRaise(true); buttons.push_back(bn); } if (i->label) { layout->addWidget(i->label , row, 0); layout->addWidget(i->widget , row, 1); layout->addWidget(buttons[row], row, 2); } else { layout->addWidget(i->widget , row, 0); layout->addWidget(buttons[row], row, 1); } group->addButton(buttons[row], idx); ++row; } ++idx; } for(int i=row; ichoosen) { delete m_popup->layout(); m_popup->setLayout(createPopupLayout()); m_popup->adjustSize(); } else delete QWidget::layout(); if (data->label) { delete data->label; } delete data->widget; m_widgets.erase(data); } } QWidget* KisWidgetChooser::chooseWidget(const QString& id) { QWidget* choosenWidget = 0; for(Iterator i=m_widgets.begin(); i!=m_widgets.end(); ++i) { if(i->id == id) { choosenWidget = i->widget; i->choosen = true; } else i->choosen = false; } delete m_popup->layout(); m_popup->setLayout(createPopupLayout()); m_popup->adjustSize(); delete QWidget::layout(); QWidget::setLayout(createLayout()); KisConfig cfg; cfg.setToolbarSlider(m_chooserid, id); return choosenWidget; } QWidget* KisWidgetChooser::getWidget(const QString& id) const { - ConstIterator data = qFind(m_widgets.begin(), m_widgets.end(), Data(id)); + ConstIterator data = std::find(m_widgets.begin(), m_widgets.end(), Data(id)); if(data != m_widgets.end()) return data->widget; return 0; } void KisWidgetChooser::showPopupWidget() { QSize popSize = m_popup->size(); QRect popupRect(QFrame::mapToGlobal(QPoint(-1, QFrame::height())), popSize); // Get the available geometry of the screen which contains this KisPopupButton QRect screenRect = QApplication::desktop()->availableGeometry(this); // Make sure the popup is not drawn outside the screen area if(popupRect.right() > screenRect.right()) popupRect.translate(screenRect.right() - popupRect.right(), 0); if(popupRect.left() < screenRect.left()) popupRect.translate(screenRect.left() - popupRect.left(), 0); if(popupRect.bottom() > screenRect.bottom()) popupRect.translate(0, -popupRect.height()); m_popup->setGeometry(popupRect); m_popup->show(); } void KisWidgetChooser::updateThemedIcons() { for (int i = 0; i < m_buttons->buttons().length(); i++) { if ( m_buttons->button(i)) { m_buttons->button(i)->setIcon(KisIconUtils::loadIcon("list-add")); } } } void KisWidgetChooser::slotButtonPressed() { showPopupWidget(); } void KisWidgetChooser::slotWidgetChoosen(int index) { chooseWidget(m_widgets[index].id); m_popup->hide(); } void KisWidgetChooser::changeEvent(QEvent *e) { QFrame::changeEvent(e); switch (e->type()) { case QEvent::StyleChange: case QEvent::PaletteChange: case QEvent::EnabledChange: updateArrowIcon(); break; default: ; } } diff --git a/libs/widgets/KoPagePreviewWidget.cpp b/libs/widgets/KoPagePreviewWidget.cpp index 517243d99e..cfb3a5515c 100644 --- a/libs/widgets/KoPagePreviewWidget.cpp +++ b/libs/widgets/KoPagePreviewWidget.cpp @@ -1,164 +1,164 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thomas Zander * Copyright (C) 2006 Gary Cramblitt * * 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 "KoPagePreviewWidget.h" #include #include #include #include #include #include class Q_DECL_HIDDEN KoPagePreviewWidget::Private { public: KoPageLayout pageLayout; KoColumns columns; }; KoPagePreviewWidget::KoPagePreviewWidget(QWidget *parent) : QWidget(parent) , d(new Private) { setMinimumSize( 100, 100 ); } KoPagePreviewWidget::~KoPagePreviewWidget() { delete d; } void KoPagePreviewWidget::paintEvent(QPaintEvent *event) { Q_UNUSED(event); // resolution[XY] is in pixel per pt qreal resolutionX = POINT_TO_INCH( static_cast(KoDpi::dpiX()) ); qreal resolutionY = POINT_TO_INCH( static_cast(KoDpi::dpiY()) ); qreal pageWidth = d->pageLayout.width * resolutionX; qreal pageHeight = d->pageLayout.height * resolutionY; const bool pageSpread = (d->pageLayout.bindingSide >= 0 && d->pageLayout.pageEdge >= 0); qreal sheetWidth = pageWidth / (pageSpread?2:1); qreal zoomH = (height() * 90 / 100) / pageHeight; qreal zoomW = (width() * 90 / 100) / pageWidth; qreal zoom = qMin( zoomW, zoomH ); pageWidth *= zoom; sheetWidth *= zoom; pageHeight *= zoom; QPainter painter( this ); QRect page = QRectF((width() - pageWidth) / 2.0, (height() - pageHeight) / 2.0, sheetWidth, pageHeight).toRect(); painter.save(); drawPage(painter, zoom, page, true); painter.restore(); if(pageSpread) { page.moveLeft(page.left() + (int) (sheetWidth)); painter.save(); drawPage(painter, zoom, page, false); painter.restore(); } painter.end(); // paint scale } void KoPagePreviewWidget::drawPage(QPainter &painter, qreal zoom, const QRect &dimensions, bool left) { painter.fillRect(dimensions, QBrush(palette().base())); painter.setPen(QPen(palette().color(QPalette::Dark), 0)); painter.drawRect(dimensions); // draw text areas QRect textArea = dimensions; if ((d->pageLayout.topMargin == 0 && d->pageLayout.bottomMargin == 0 && d->pageLayout.leftMargin == 0 && d->pageLayout.rightMargin == 0) || ( d->pageLayout.pageEdge == 0 && d->pageLayout.bindingSide == 0)) { // no margin return; } else { textArea.setTop(textArea.top() + qRound(zoom * d->pageLayout.topMargin)); textArea.setBottom(textArea.bottom() - qRound(zoom * d->pageLayout.bottomMargin)); qreal leftMargin, rightMargin; if(d->pageLayout.bindingSide < 0) { // normal margins. leftMargin = d->pageLayout.leftMargin; rightMargin = d->pageLayout.rightMargin; } else { // margins mirrored for left/right pages leftMargin = d->pageLayout.bindingSide; rightMargin = d->pageLayout.pageEdge; if(left) - qSwap(leftMargin, rightMargin); + std::swap(leftMargin, rightMargin); } textArea.setLeft(textArea.left() + qRound(zoom * leftMargin)); textArea.setRight(textArea.right() - qRound(zoom * rightMargin)); } painter.setBrush( QBrush( palette().color(QPalette::ButtonText), Qt::HorPattern ) ); painter.setPen(QPen(palette().color(QPalette::Dark), 0)); // uniform columns? if (d->columns.columnData.isEmpty()) { qreal columnWidth = (textArea.width() + (d->columns.gapWidth * zoom)) / d->columns.count; int width = qRound(columnWidth - d->columns.gapWidth * zoom); for ( int i = 0; i < d->columns.count; ++i ) painter.drawRect( qRound(textArea.x() + i * columnWidth), textArea.y(), width, textArea.height()); } else { qreal totalRelativeWidth = 0.0; Q_FOREACH (const KoColumns::ColumnDatum &cd, d->columns.columnData) { totalRelativeWidth += cd.relativeWidth; } int relativeColumnXOffset = 0; for (int i = 0; i < d->columns.count; i++) { const KoColumns::ColumnDatum &columnDatum = d->columns.columnData.at(i); const qreal columnWidth = textArea.width() * columnDatum.relativeWidth / totalRelativeWidth; const qreal columnXOffset = textArea.width() * relativeColumnXOffset / totalRelativeWidth; painter.drawRect( qRound(textArea.x() + columnXOffset + columnDatum.leftMargin * zoom), qRound(textArea.y() + columnDatum.topMargin * zoom), qRound(columnWidth - (columnDatum.leftMargin + columnDatum.rightMargin) * zoom), qRound(textArea.height() - (columnDatum.topMargin + columnDatum.bottomMargin) * zoom)); relativeColumnXOffset += columnDatum.relativeWidth; } } } void KoPagePreviewWidget::setPageLayout(const KoPageLayout &layout) { d->pageLayout = layout; update(); } void KoPagePreviewWidget::setColumns(const KoColumns &columns) { d->columns = columns; update(); } diff --git a/libs/widgets/KoRuler.cpp b/libs/widgets/KoRuler.cpp index 5d42b37519..f7bfc617cf 100644 --- a/libs/widgets/KoRuler.cpp +++ b/libs/widgets/KoRuler.cpp @@ -1,1367 +1,1370 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Reginald Stadlbauer Copyright (C) 2006 Peter Simonsson Copyright (C) 2007 C. Boemann Copyright (C) 2007-2008 Jan Hambrecht Copyright (C) 2007 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 "KoRuler.h" #include "KoRuler_p.h" #include #include #include #include #include #include #include #include #include #include // the distance in pixels of a mouse position considered outside the rule static const int OutsideRulerThreshold = 20; // static const int fullStepMarkerLength = 6; static const int halfStepMarkerLength = 6; static const int quarterStepMarkerLength = 3; static const int measurementTextAboveBelowMargin = 1; void RulerTabChooser::mousePressEvent(QMouseEvent *) { if (! m_showTabs) { return; } switch(m_type) { case QTextOption::LeftTab: m_type = QTextOption::RightTab; break; case QTextOption::RightTab: m_type = QTextOption::CenterTab; break; case QTextOption::CenterTab: m_type = QTextOption::DelimiterTab; break; case QTextOption::DelimiterTab: m_type = QTextOption::LeftTab; break; } update(); } void RulerTabChooser::paintEvent(QPaintEvent *) { if (! m_showTabs) { return; } QPainter painter(this); QPolygonF polygon; painter.setPen(QPen(palette().color(QPalette::Text), 0)); painter.setBrush(palette().color(QPalette::Text)); painter.setRenderHint( QPainter::Antialiasing ); qreal x= width()/2; painter.translate(0,-height()/2+5); switch (m_type) { case QTextOption::LeftTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x+6.5, height() - 2.5) << QPointF(x+0.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::RightTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x-5.5, height() - 2.5) << QPointF(x+0.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::CenterTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x-5.5, height() - 2.5) << QPointF(x+6.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::DelimiterTab: polygon << QPointF(x-5.5, height() - 2.5) << QPointF(x+6.5, height() - 2.5); painter.drawPolyline(polygon); polygon << QPointF(x+0.5, height() - 2.5) << QPointF(x+0.5, height() - 8.5); painter.drawPolyline(polygon); break; default: break; } } -static int compareTabs(KoRuler::Tab &tab1, KoRuler::Tab &tab2) -{ - return tab1.position < tab2.position; -} +struct { + bool operator()(KoRuler::Tab tab1, KoRuler::Tab tab2) const + { + return tab1.position < tab2.position; + } +} compareTabs; + QRectF HorizontalPaintingStrategy::drawBackground(const KoRulerPrivate *d, QPainter &painter) { lengthInPixel = d->viewConverter->documentToViewX(d->rulerLength); QRectF rectangle; rectangle.setX(qMax(0, d->offset)); rectangle.setY(0); rectangle.setWidth(qMin(qreal(d->ruler->width() - 1.0 - rectangle.x()), (d->offset >= 0) ? lengthInPixel : lengthInPixel + d->offset)); rectangle.setHeight(d->ruler->height() - 1); QRectF activeRangeRectangle; activeRangeRectangle.setX(qMax(rectangle.x() + 1, d->viewConverter->documentToViewX(d->effectiveActiveRangeStart()) + d->offset)); activeRangeRectangle.setY(rectangle.y() + 1); activeRangeRectangle.setRight(qMin(rectangle.right() - 1, d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd()) + d->offset)); activeRangeRectangle.setHeight(rectangle.height() - 2); painter.setPen(QPen(d->ruler->palette().color(QPalette::Mid), 0)); painter.fillRect(rectangle,d->ruler->palette().color(QPalette::AlternateBase)); // make background slightly different so it is easier to see painter.drawRect(rectangle); if(d->effectiveActiveRangeStart() != d->effectiveActiveRangeEnd()) painter.fillRect(activeRangeRectangle, d->ruler->palette().brush(QPalette::Base)); if(d->showSelectionBorders) { // Draw first selection border if(d->firstSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewX(d->firstSelectionBorder) + d->offset; painter.drawLine(QPointF(border, rectangle.y() + 1), QPointF(border, rectangle.bottom() - 1)); } // Draw second selection border if(d->secondSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewX(d->secondSelectionBorder) + d->offset; painter.drawLine(QPointF(border, rectangle.y() + 1), QPointF(border, rectangle.bottom() - 1)); } } return rectangle; } void HorizontalPaintingStrategy::drawTabs(const KoRulerPrivate *d, QPainter &painter) { if (! d->showTabs) return; QPolygonF polygon; const QColor tabColor = d->ruler->palette().color(QPalette::Text); painter.setPen(QPen(tabColor, 0)); painter.setBrush(tabColor); painter.setRenderHint( QPainter::Antialiasing ); qreal position = -10000; foreach (const KoRuler::Tab & t, d->tabs) { qreal x; if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - t.position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + t.position) + d->offset; } position = qMax(position, t.position); polygon.clear(); switch (t.type) { case QTextOption::LeftTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x+6.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::RightTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::CenterTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+6.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::DelimiterTab: polygon << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+6.5, d->ruler->height() - 0.5); painter.drawPolyline(polygon); polygon << QPointF(x+0.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 6.5); painter.drawPolyline(polygon); break; default: break; } } // and also draw the regular interval tab that are non editable if (d->tabDistance > 0.0) { // first possible position position = qMax(position, d->relativeTabs ? 0 : d->paragraphIndent); if (position < 0) { position = int(position / d->tabDistance) * d->tabDistance; } else { position = (int(position / d->tabDistance) + 1) * d->tabDistance; } while (position < d->effectiveActiveRangeEnd() - d->effectiveActiveRangeStart() - d->endIndent) { qreal x; if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + position) + d->offset; } polygon.clear(); polygon << QPointF(x+0.5, d->ruler->height() - 3.5) << QPointF(x+4.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); position += d->tabDistance; } } } void HorizontalPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF &rectangle) { qreal numberStep = d->numberStepForUnit(); // number step in unit // QRectF activeRangeRectangle; int numberStepPixel = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue(numberStep))); // const bool adjustMillimeters = (d->unit.type() == KoUnit::Millimeter); const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); painter.setFont(font); if (numberStepPixel == 0 || numberStep == 0) return; // Calc the longest text length int textLength = 0; for(int i = 0; i < lengthInPixel; i += numberStepPixel) { int number = qRound((i / numberStepPixel) * numberStep); textLength = qMax(textLength, fontMetrics.width(QString::number(number))); } textLength += 4; // Add some padding // Change number step so all digits fits while(textLength > numberStepPixel) { numberStepPixel += numberStepPixel; numberStep += numberStep; } int start=0; // Calc the first number step if(d->offset < 0) start = qAbs(d->offset); // make a little hack so rulers shows correctly inversed number aligned const qreal lengthInUnit = d->unit.toUserValue(d->rulerLength); const qreal hackyLength = lengthInUnit - fmod(lengthInUnit, numberStep); if(d->rightToLeft) { start -= int(d->viewConverter->documentToViewX(fmod(d->rulerLength, d->unit.fromUserValue(numberStep)))); } int stepCount = (start / numberStepPixel) + 1; int halfStepCount = (start / qRound(numberStepPixel * 0.5)) + 1; int quarterStepCount = (start / qRound(numberStepPixel * 0.25)) + 1; int pos = 0; const QPen numberPen(d->ruler->palette().color(QPalette::Text), 0); const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text), 0); painter.setPen(markerPen); if(d->offset > 0) painter.translate(d->offset, 0); const int len = qRound(rectangle.width()) + start; int nextStep = qRound(d->viewConverter->documentToViewX( d->unit.fromUserValue(numberStep * stepCount))); int nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); int nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); for(int i = start; i < len; ++i) { pos = i - start; if(i == nextStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - fullStepMarkerLength)); int number = qRound(stepCount * numberStep); QString numberText = QString::number(number); int x = pos; if (d->rightToLeft) { // this is done in a hacky way with the fine tuning done above numberText = QString::number(hackyLength - stepCount * numberStep); } painter.setPen(numberPen); painter.drawText(QPointF(x-fontMetrics.width(numberText)/2.0, rectangle.bottom() -fullStepMarkerLength -measurementTextAboveBelowMargin), numberText); painter.setPen(markerPen); ++stepCount; nextStep = qRound(d->viewConverter->documentToViewX( d->unit.fromUserValue(numberStep * stepCount))); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextHalfStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - halfStepMarkerLength)); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextQuarterStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - quarterStepMarkerLength)); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } } // Draw the mouse indicator const int mouseCoord = d->mouseCoordinate - start; if (d->selected == KoRulerPrivate::None || d->selected == KoRulerPrivate::HotSpot) { const qreal top = rectangle.y() + 1; const qreal bottom = rectangle.bottom() -1; if (d->selected == KoRulerPrivate::None && d->showMousePosition && mouseCoord > 0 && mouseCoord < rectangle.width() ) painter.drawLine(QPointF(mouseCoord, top), QPointF(mouseCoord, bottom)); foreach (const KoRulerPrivate::HotSpotData & hp, d->hotspots) { const qreal x = d->viewConverter->documentToViewX(hp.position) + d->offset; painter.drawLine(QPointF(x, top), QPointF(x, bottom)); } } } void HorizontalPaintingStrategy::drawIndents(const KoRulerPrivate *d, QPainter &painter) { QPolygonF polygon; painter.setBrush(d->ruler->palette().brush(QPalette::Base)); painter.setRenderHint( QPainter::Antialiasing ); qreal x; // Draw first line start indent if (d->rightToLeft) x = d->effectiveActiveRangeEnd() - d->firstLineIndent - d->paragraphIndent; else x = d->effectiveActiveRangeStart() + d->firstLineIndent + d->paragraphIndent; // convert and use the +0.5 to go to nearest integer so that the 0.5 added below ensures sharp lines x = int(d->viewConverter->documentToViewX(x) + d->offset + 0.5); polygon << QPointF(x+6.5, 0.5) << QPointF(x+0.5, 8.5) << QPointF(x-5.5, 0.5) << QPointF(x+5.5, 0.5); painter.drawPolygon(polygon); // draw the hanging indent. if (d->rightToLeft) x = d->effectiveActiveRangeStart() + d->endIndent; else x = d->effectiveActiveRangeStart() + d->paragraphIndent; // convert and use the +0.5 to go to nearest integer so that the 0.5 added below ensures sharp lines x = int(d->viewConverter->documentToViewX(x) + d->offset + 0.5); const int bottom = d->ruler->height(); polygon.clear(); polygon << QPointF(x+6.5, bottom - 0.5) << QPointF(x+0.5, bottom - 8.5) << QPointF(x-5.5, bottom - 0.5) << QPointF(x+5.5, bottom - 0.5); painter.drawPolygon(polygon); // Draw end-indent or paragraph indent if mode is rightToLeft qreal diff; if (d->rightToLeft) diff = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - d->paragraphIndent) + d->offset - x; else diff = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - d->endIndent) + d->offset - x; polygon.translate(diff, 0); painter.drawPolygon(polygon); } QSize HorizontalPaintingStrategy::sizeHint() { // assumes that digits for the number only use glyphs which do not go below the baseline const QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); const int digitsHeight = fm.ascent() + 1; // +1 for baseline const int minimum = digitsHeight + fullStepMarkerLength + 2*measurementTextAboveBelowMargin; return QSize(0, minimum); } QRectF VerticalPaintingStrategy::drawBackground(const KoRulerPrivate *d, QPainter &painter) { lengthInPixel = d->viewConverter->documentToViewY(d->rulerLength); QRectF rectangle; rectangle.setX(0); rectangle.setY(qMax(0, d->offset)); rectangle.setWidth(d->ruler->width() - 1.0); rectangle.setHeight(qMin(qreal(d->ruler->height() - 1.0 - rectangle.y()), (d->offset >= 0) ? lengthInPixel : lengthInPixel + d->offset)); QRectF activeRangeRectangle; activeRangeRectangle.setX(rectangle.x() + 1); activeRangeRectangle.setY(qMax(rectangle.y() + 1, d->viewConverter->documentToViewY(d->effectiveActiveRangeStart()) + d->offset)); activeRangeRectangle.setWidth(rectangle.width() - 2); activeRangeRectangle.setBottom(qMin(rectangle.bottom() - 1, d->viewConverter->documentToViewY(d->effectiveActiveRangeEnd()) + d->offset)); painter.setPen(QPen(d->ruler->palette().color(QPalette::Mid), 0)); painter.fillRect(rectangle,d->ruler->palette().color(QPalette::AlternateBase)); painter.drawRect(rectangle); if(d->effectiveActiveRangeStart() != d->effectiveActiveRangeEnd()) painter.fillRect(activeRangeRectangle, d->ruler->palette().brush(QPalette::Base)); if(d->showSelectionBorders) { // Draw first selection border if(d->firstSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewY(d->firstSelectionBorder) + d->offset; painter.drawLine(QPointF(rectangle.x() + 1, border), QPointF(rectangle.right() - 1, border)); } // Draw second selection border if(d->secondSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewY(d->secondSelectionBorder) + d->offset; painter.drawLine(QPointF(rectangle.x() + 1, border), QPointF(rectangle.right() - 1, border)); } } return rectangle; } void VerticalPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF &rectangle) { qreal numberStep = d->numberStepForUnit(); // number step in unit int numberStepPixel = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep))); if (numberStepPixel <= 0) return; const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); painter.setFont(font); // Calc the longest text length int textLength = 0; for(int i = 0; i < lengthInPixel; i += numberStepPixel) { int number = qRound((i / numberStepPixel) * numberStep); textLength = qMax(textLength, fontMetrics.width(QString::number(number))); } textLength += 4; // Add some padding if (numberStepPixel == 0 || numberStep == 0) return; // Change number step so all digits will fit while(textLength > numberStepPixel) { numberStepPixel += numberStepPixel; numberStep += numberStep; } // Calc the first number step const int start = d->offset < 0 ? qAbs(d->offset) : 0; // make a little hack so rulers shows correctly inversed number aligned int stepCount = (start / numberStepPixel) + 1; int halfStepCount = (start / qRound(numberStepPixel * 0.5)) + 1; int quarterStepCount = (start / qRound(numberStepPixel * 0.25)) + 1; const QPen numberPen(d->ruler->palette().color(QPalette::Text), 0); const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text), 0); painter.setPen(markerPen); if(d->offset > 0) painter.translate(0, d->offset); const int len = qRound(rectangle.height()) + start; int nextStep = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep * stepCount))); int nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); int nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); int pos = 0; for(int i = start; i < len; ++i) { pos = i - start; if(i == nextStep) { painter.save(); painter.translate(rectangle.right()-fullStepMarkerLength, pos); if(pos != 0) painter.drawLine(QPointF(0, 0), QPointF(fullStepMarkerLength-1, 0)); painter.rotate(-90); int number = qRound(stepCount * numberStep); QString numberText = QString::number(number); painter.setPen(numberPen); painter.drawText(QPointF(-fontMetrics.width(numberText) / 2.0, -measurementTextAboveBelowMargin), numberText); painter.restore(); ++stepCount; nextStep = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep * stepCount))); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextHalfStep) { if(pos != 0) painter.drawLine(QPointF(rectangle.right() - halfStepMarkerLength, pos), QPointF(rectangle.right() - 1, pos)); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextQuarterStep) { if(pos != 0) painter.drawLine(QPointF(rectangle.right() - quarterStepMarkerLength, pos), QPointF(rectangle.right() - 1, pos)); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } } // Draw the mouse indicator const int mouseCoord = d->mouseCoordinate - start; if (d->selected == KoRulerPrivate::None || d->selected == KoRulerPrivate::HotSpot) { const qreal left = rectangle.left() + 1; const qreal right = rectangle.right() -1; if (d->selected == KoRulerPrivate::None && d->showMousePosition && mouseCoord > 0 && mouseCoord < rectangle.height() ) painter.drawLine(QPointF(left, mouseCoord), QPointF(right, mouseCoord)); foreach (const KoRulerPrivate::HotSpotData & hp, d->hotspots) { const qreal y = d->viewConverter->documentToViewY(hp.position) + d->offset; painter.drawLine(QPointF(left, y), QPointF(right, y)); } } } QSize VerticalPaintingStrategy::sizeHint() { // assumes that digits for the number only use glyphs which do not go below the baseline const QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); const int digitsHeight = fm.ascent() + 1; // +1 for baseline const int minimum = digitsHeight + fullStepMarkerLength + 2*measurementTextAboveBelowMargin; return QSize(minimum, 0); } void HorizontalDistancesPaintingStrategy::drawDistanceLine(const KoRulerPrivate *d, QPainter &painter, const qreal start, const qreal end) { // Don't draw too short lines if (qMax(start, end) - qMin(start, end) < 1) return; painter.save(); painter.translate(d->offset, d->ruler->height() / 2); painter.setPen(QPen(d->ruler->palette().color(QPalette::Text), 0)); painter.setBrush(d->ruler->palette().color(QPalette::Text)); QLineF line(QPointF(d->viewConverter->documentToViewX(start), 0), QPointF(d->viewConverter->documentToViewX(end), 0)); QPointF midPoint = line.pointAt(0.5); // Draw the label text const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); QString label = d->unit.toUserStringValue( d->viewConverter->viewToDocumentX(line.length())) + ' ' + d->unit.symbol(); QPointF labelPosition = QPointF(midPoint.x() - fontMetrics.width(label)/2, midPoint.y() + fontMetrics.ascent()/2); painter.setFont(font); painter.drawText(labelPosition, label); // Draw the arrow lines qreal arrowLength = (line.length() - fontMetrics.width(label)) / 2 - 2; arrowLength = qMax(qreal(0.0), arrowLength); QLineF startArrow(line.p1(), line.pointAt(arrowLength / line.length())); QLineF endArrow(line.p2(), line.pointAt(1.0 - arrowLength / line.length())); painter.drawLine(startArrow); painter.drawLine(endArrow); // Draw the arrow heads QPolygonF arrowHead; arrowHead << line.p1() << QPointF(line.x1()+3, line.y1()-3) << QPointF(line.x1()+3, line.y1()+3); painter.drawPolygon(arrowHead); arrowHead.clear(); arrowHead << line.p2() << QPointF(line.x2()-3, line.y2()-3) << QPointF(line.x2()-3, line.y2()+3); painter.drawPolygon(arrowHead); painter.restore(); } void HorizontalDistancesPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF&) { QList points; points << 0.0; points << d->effectiveActiveRangeStart() + d->paragraphIndent + d->firstLineIndent; points << d->effectiveActiveRangeStart() + d->paragraphIndent; points << d->effectiveActiveRangeEnd() - d->endIndent; points << d->effectiveActiveRangeStart(); points << d->effectiveActiveRangeEnd(); points << d->rulerLength; - qSort(points.begin(), points.end()); + std::sort(points.begin(), points.end()); QListIterator i(points); i.next(); while (i.hasNext() && i.hasPrevious()) { drawDistanceLine(d, painter, i.peekPrevious(), i.peekNext()); i.next(); } } KoRulerPrivate::KoRulerPrivate(KoRuler *parent, const KoViewConverter *vc, Qt::Orientation o) : unit(KoUnit(KoUnit::Point)), orientation(o), viewConverter(vc), offset(0), rulerLength(0), activeRangeStart(0), activeRangeEnd(0), activeOverrideRangeStart(0), activeOverrideRangeEnd(0), mouseCoordinate(-1), showMousePosition(0), showSelectionBorders(false), firstSelectionBorder(0), secondSelectionBorder(0), showIndents(false), firstLineIndent(0), paragraphIndent(0), endIndent(0), showTabs(false), relativeTabs(false), tabMoved(false), originalIndex(-1), currentIndex(0), rightToLeft(false), selected(None), selectOffset(0), tabChooser(0), normalPaintingStrategy(o == Qt::Horizontal ? (PaintingStrategy*)new HorizontalPaintingStrategy() : (PaintingStrategy*)new VerticalPaintingStrategy()), distancesPaintingStrategy((PaintingStrategy*)new HorizontalDistancesPaintingStrategy()), paintingStrategy(normalPaintingStrategy), ruler(parent), guideCreationStarted(false) { } KoRulerPrivate::~KoRulerPrivate() { delete normalPaintingStrategy; delete distancesPaintingStrategy; } qreal KoRulerPrivate::numberStepForUnit() const { switch(unit.type()) { case KoUnit::Inch: case KoUnit::Centimeter: case KoUnit::Decimeter: case KoUnit::Millimeter: return 1.0; case KoUnit::Pica: case KoUnit::Cicero: return 10.0; case KoUnit::Point: default: return 100.0; } } qreal KoRulerPrivate::doSnapping(const qreal value) const { qreal numberStep = unit.fromUserValue(numberStepForUnit()/4.0); return numberStep * qRound(value / numberStep); } KoRulerPrivate::Selection KoRulerPrivate::selectionAtPosition(const QPoint & pos, int *selectOffset ) { const int height = ruler->height(); if (rightToLeft) { int x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - firstLineIndent - paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x +8 && pos.y() < height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::FirstLineIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x +8 && pos.y() > height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::ParagraphIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + endIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::EndIndent; } } else { int x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + firstLineIndent + paragraphIndent) + offset); if (pos.x() >= x -8 && pos.x() <= x + 8 && pos.y() < height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::FirstLineIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8 && pos.y() > height/2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::ParagraphIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - endIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::EndIndent; } } return KoRulerPrivate::None; } int KoRulerPrivate::hotSpotIndex(const QPoint & pos) { for(int counter = 0; counter < hotspots.count(); counter++) { bool hit; if (orientation == Qt::Horizontal) hit = qAbs(viewConverter->documentToViewX(hotspots[counter].position) - pos.x() + offset) < 3; else hit = qAbs(viewConverter->documentToViewY(hotspots[counter].position) - pos.y() + offset) < 3; if (hit) return counter; } return -1; } qreal KoRulerPrivate::effectiveActiveRangeStart() const { if (activeOverrideRangeStart != activeOverrideRangeEnd) { return activeOverrideRangeStart; } else { return activeRangeStart; } } qreal KoRulerPrivate::effectiveActiveRangeEnd() const { if (activeOverrideRangeStart != activeOverrideRangeEnd) { return activeOverrideRangeEnd; } else { return activeRangeEnd; } } void KoRulerPrivate::emitTabChanged() { KoRuler::Tab tab; if (currentIndex >= 0) tab = tabs[currentIndex]; emit ruler->tabChanged(originalIndex, currentIndex >= 0 ? &tab : 0); } KoRuler::KoRuler(QWidget* parent, Qt::Orientation orientation, const KoViewConverter* viewConverter) : QWidget(parent) , d( new KoRulerPrivate( this, viewConverter, orientation) ) { setMouseTracking( true ); } KoRuler::~KoRuler() { delete d; } KoUnit KoRuler::unit() const { return d->unit; } void KoRuler::setUnit(const KoUnit &unit) { d->unit = unit; update(); } qreal KoRuler::rulerLength() const { return d->rulerLength; } Qt::Orientation KoRuler::orientation() const { return d->orientation; } void KoRuler::setOffset(int offset) { d->offset = offset; update(); } void KoRuler::setRulerLength(qreal length) { d->rulerLength = length; update(); } void KoRuler::paintEvent(QPaintEvent* event) { QPainter painter(this); painter.setClipRegion(event->region()); painter.save(); QRectF rectangle = d->paintingStrategy->drawBackground(d, painter); painter.restore(); painter.save(); d->paintingStrategy->drawMeasurements(d, painter, rectangle); painter.restore(); if (d->showIndents) { painter.save(); d->paintingStrategy->drawIndents(d, painter); painter.restore(); } d->paintingStrategy->drawTabs(d, painter); } QSize KoRuler::minimumSizeHint() const { return d->paintingStrategy->sizeHint(); } QSize KoRuler::sizeHint() const { return d->paintingStrategy->sizeHint(); } void KoRuler::setActiveRange(qreal start, qreal end) { d->activeRangeStart = start; d->activeRangeEnd = end; update(); } void KoRuler::setOverrideActiveRange(qreal start, qreal end) { d->activeOverrideRangeStart = start; d->activeOverrideRangeEnd = end; update(); } void KoRuler::updateMouseCoordinate(int coordinate) { if(d->mouseCoordinate == coordinate) return; d->mouseCoordinate = coordinate; update(); } void KoRuler::setShowMousePosition(bool show) { d->showMousePosition = show; update(); } bool KoRuler::showMousePosition() const { return d->showMousePosition; } void KoRuler::setRightToLeft(bool isRightToLeft) { d->rightToLeft = isRightToLeft; update(); } void KoRuler::setShowIndents(bool show) { d->showIndents = show; update(); } void KoRuler::setFirstLineIndent(qreal indent) { d->firstLineIndent = indent; if (d->showIndents) { update(); } } void KoRuler::setParagraphIndent(qreal indent) { d->paragraphIndent = indent; if (d->showIndents) { update(); } } void KoRuler::setEndIndent(qreal indent) { d->endIndent = indent; if (d->showIndents) { update(); } } qreal KoRuler::firstLineIndent() const { return d->firstLineIndent; } qreal KoRuler::paragraphIndent() const { return d->paragraphIndent; } qreal KoRuler::endIndent() const { return d->endIndent; } QWidget *KoRuler::tabChooser() { if ((d->tabChooser == 0) && (d->orientation == Qt::Horizontal)) { d->tabChooser = new RulerTabChooser(parentWidget()); d->tabChooser->setShowTabs(d->showTabs); } return d->tabChooser; } void KoRuler::setShowSelectionBorders(bool show) { d->showSelectionBorders = show; update(); } void KoRuler::updateSelectionBorders(qreal first, qreal second) { d->firstSelectionBorder = first; d->secondSelectionBorder = second; if(d->showSelectionBorders) update(); } void KoRuler::setShowTabs(bool show) { if (d->showTabs == show) { return; } d->showTabs = show; if (d->tabChooser) { d->tabChooser->setShowTabs(show); } update(); } void KoRuler::setRelativeTabs(bool relative) { d->relativeTabs = relative; if (d->showTabs) { update(); } } void KoRuler::updateTabs(const QList &tabs, qreal tabDistance) { d->tabs = tabs; d->tabDistance = tabDistance; if (d->showTabs) { update(); } } QList KoRuler::tabs() const { QList answer = d->tabs; - qSort(answer.begin(), answer.end(), compareTabs); + std::sort(answer.begin(), answer.end(), compareTabs); return answer; } void KoRuler::setPopupActionList(const QList &popupActionList) { d->popupActions = popupActionList; } QList KoRuler::popupActionList() const { return d->popupActions; } void KoRuler::mousePressEvent ( QMouseEvent* ev ) { d->tabMoved = false; d->selected = KoRulerPrivate::None; if (ev->button() == Qt::RightButton && !d->popupActions.isEmpty()) QMenu::exec(d->popupActions, ev->globalPos()); if (ev->button() != Qt::LeftButton) { ev->ignore(); return; } /** * HACK ALERT: We don't need all that indentation stuff in Krita. * Just ensure the rulers are created correctly. */ if (d->selected == KoRulerPrivate::None) { d->guideCreationStarted = true; return; } QPoint pos = ev->pos(); if (d->showTabs) { int i = 0; int x; foreach (const Tab & t, d->tabs) { if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - t.position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + t.position) + d->offset; } if (pos.x() >= x-6 && pos.x() <= x+6) { d->selected = KoRulerPrivate::Tab; d->selectOffset = x - pos.x(); d->currentIndex = i; break; } i++; } d->originalIndex = d->currentIndex; } if (d->selected == KoRulerPrivate::None) d->selected = d->selectionAtPosition(ev->pos(), &d->selectOffset); if (d->selected == KoRulerPrivate::None) { int hotSpotIndex = d->hotSpotIndex(ev->pos()); if (hotSpotIndex >= 0) { d->selected = KoRulerPrivate::HotSpot; update(); } } if (d->showTabs && d->selected == KoRulerPrivate::None) { // still haven't found something so let assume the user wants to add a tab qreal tabpos; if (d->rightToLeft) { tabpos = d->viewConverter->viewToDocumentX(pos.x() - d->offset) + d->effectiveActiveRangeEnd() + (d->relativeTabs ? d->paragraphIndent : 0); } else { tabpos = d->viewConverter->viewToDocumentX(pos.x() - d->offset) - d->effectiveActiveRangeStart() - (d->relativeTabs ? d->paragraphIndent : 0); } Tab t = {tabpos, d->tabChooser ? d->tabChooser->type() : d->rightToLeft ? QTextOption::RightTab : QTextOption::LeftTab}; d->tabs.append(t); d->selectOffset = 0; d->selected = KoRulerPrivate::Tab; d->currentIndex = d->tabs.count() - 1; d->originalIndex = -1; // new! update(); } if (d->orientation == Qt::Horizontal && (ev->modifiers() & Qt::ShiftModifier) && (d->selected == KoRulerPrivate::FirstLineIndent || d->selected == KoRulerPrivate::ParagraphIndent || d->selected == KoRulerPrivate::Tab || d->selected == KoRulerPrivate::EndIndent)) d->paintingStrategy = d->distancesPaintingStrategy; if (d->selected != KoRulerPrivate::None) emit aboutToChange(); } void KoRuler::mouseReleaseEvent ( QMouseEvent* ev ) { ev->accept(); if (d->selected == KoRulerPrivate::None && d->guideCreationStarted) { d->guideCreationStarted = false; emit guideCreationFinished(d->orientation, ev->globalPos()); } else if (d->selected == KoRulerPrivate::Tab) { if (d->originalIndex >= 0 && !d->tabMoved) { int type = d->tabs[d->currentIndex].type; type++; if (type > 3) type = 0; d->tabs[d->currentIndex].type = static_cast (type); update(); } d->emitTabChanged(); } else if( d->selected != KoRulerPrivate::None) emit indentsChanged(true); else ev->ignore(); d->paintingStrategy = d->normalPaintingStrategy; d->selected = KoRulerPrivate::None; } void KoRuler::mouseMoveEvent ( QMouseEvent* ev ) { QPoint pos = ev->pos(); if (d->selected == KoRulerPrivate::None && d->guideCreationStarted) { emit guideCreationInProgress(d->orientation, ev->globalPos()); ev->accept(); update(); return; } qreal activeLength = d->effectiveActiveRangeEnd() - d->effectiveActiveRangeStart(); switch (d->selected) { case KoRulerPrivate::FirstLineIndent: if (d->rightToLeft) d->firstLineIndent = d->effectiveActiveRangeEnd() - d->paragraphIndent - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->firstLineIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart() - d->paragraphIndent; if( ! (ev->modifiers() & Qt::ShiftModifier)) { d->firstLineIndent = d->doSnapping(d->firstLineIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } emit indentsChanged(false); break; case KoRulerPrivate::ParagraphIndent: if (d->rightToLeft) d->paragraphIndent = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->paragraphIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); if( ! (ev->modifiers() & Qt::ShiftModifier)) { d->paragraphIndent = d->doSnapping(d->paragraphIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } if (d->paragraphIndent + d->endIndent > activeLength) d->paragraphIndent = activeLength - d->endIndent; emit indentsChanged(false); break; case KoRulerPrivate::EndIndent: if (d->rightToLeft) d->endIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); else d->endIndent = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); if (!(ev->modifiers() & Qt::ShiftModifier)) { d->endIndent = d->doSnapping(d->endIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } if (d->paragraphIndent + d->endIndent > activeLength) d->endIndent = activeLength - d->paragraphIndent; emit indentsChanged(false); break; case KoRulerPrivate::Tab: d->tabMoved = true; if (d->currentIndex < 0) { // tab is deleted. if (ev->pos().y() < height()) { // reinstante it. d->currentIndex = d->tabs.count(); d->tabs.append(d->deletedTab); } else { break; } } if (d->rightToLeft) d->tabs[d->currentIndex].position = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->tabs[d->currentIndex].position = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); if (!(ev->modifiers() & Qt::ShiftModifier)) d->tabs[d->currentIndex].position = d->doSnapping(d->tabs[d->currentIndex].position); if (d->tabs[d->currentIndex].position < 0) d->tabs[d->currentIndex].position = 0; if (d->tabs[d->currentIndex].position > activeLength) d->tabs[d->currentIndex].position = activeLength; if (ev->pos().y() > height() + OutsideRulerThreshold ) { // moved out of the ruler, delete it. d->deletedTab = d->tabs.takeAt(d->currentIndex); d->currentIndex = -1; // was that a temporary added tab? if ( d->originalIndex == -1 ) emit guideLineCreated(d->orientation, d->orientation == Qt::Horizontal ? d->viewConverter->viewToDocumentY(ev->pos().y()) : d->viewConverter->viewToDocumentX(ev->pos().x())); } d->emitTabChanged(); break; case KoRulerPrivate::HotSpot: qreal newPos; if (d->orientation == Qt::Horizontal) newPos= d->viewConverter->viewToDocumentX(pos.x() - d->offset); else newPos= d->viewConverter->viewToDocumentY(pos.y() - d->offset); d->hotspots[d->currentIndex].position = newPos; emit hotSpotChanged(d->hotspots[d->currentIndex].id, newPos); break; case KoRulerPrivate::None: d->mouseCoordinate = (d->orientation == Qt::Horizontal ? pos.x() : pos.y()) - d->offset; int hotSpotIndex = d->hotSpotIndex(pos); if (hotSpotIndex >= 0) { setCursor(QCursor( d->orientation == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor )); break; } unsetCursor(); KoRulerPrivate::Selection selection = d->selectionAtPosition(pos); QString text; switch(selection) { case KoRulerPrivate::FirstLineIndent: text = i18n("First line indent"); break; case KoRulerPrivate::ParagraphIndent: text = i18n("Left indent"); break; case KoRulerPrivate::EndIndent: text = i18n("Right indent"); break; case KoRulerPrivate::None: if (ev->buttons() & Qt::LeftButton) { if (d->orientation == Qt::Horizontal && ev->pos().y() > height() + OutsideRulerThreshold) emit guideLineCreated(d->orientation, d->viewConverter->viewToDocumentY(ev->pos().y())); else if (d->orientation == Qt::Vertical && ev->pos().x() > width() + OutsideRulerThreshold) emit guideLineCreated(d->orientation, d->viewConverter->viewToDocumentX(ev->pos().x())); } break; default: break; } setToolTip(text); } update(); } void KoRuler::clearHotSpots() { if (d->hotspots.isEmpty()) return; d->hotspots.clear(); update(); } void KoRuler::setHotSpot(qreal position, int id) { uint hotspotCount = d->hotspots.count(); for( uint i = 0; i < hotspotCount; ++i ) { KoRulerPrivate::HotSpotData & hs = d->hotspots[i]; if (hs.id == id) { hs.position = position; update(); return; } } // not there yet, then insert it. KoRulerPrivate::HotSpotData hs; hs.position = position; hs.id = id; d->hotspots.append(hs); } bool KoRuler::removeHotSpot(int id) { QList::Iterator iter = d->hotspots.begin(); while(iter != d->hotspots.end()) { if (iter->id == id) { d->hotspots.erase(iter); update(); return true; } } return false; } void KoRuler::createGuideToolConnection(KoCanvasBase *canvas) { Q_ASSERT(canvas); KoToolBase *tool = KoToolManager::instance()->toolById(canvas, QLatin1String("GuidesTool")); if (!tool) return; // It's perfectly fine to have no guides tool, we don't have to warn the user about it connect(this, SIGNAL(guideLineCreated(Qt::Orientation,qreal)), tool, SLOT(createGuideLine(Qt::Orientation,qreal))); } diff --git a/libs/widgets/KoZoomAction.cpp b/libs/widgets/KoZoomAction.cpp index a07a3d16eb..96d0f7f92b 100644 --- a/libs/widgets/KoZoomAction.cpp +++ b/libs/widgets/KoZoomAction.cpp @@ -1,368 +1,368 @@ /* This file is part of the KDE libraries Copyright (C) 2004 Ariya Hidayat Copyright (C) 2006 Peter Simonsson Copyright (C) 2006-2007 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 version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoZoomAction.h" #include "KoZoomMode.h" #include "KoZoomWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoZoomAction::Private { public: Private(KoZoomAction *_parent) : parent(_parent) , minimumZoomValue(-1) , maximumZoomValue(-1) {} KoZoomAction *parent; KoZoomMode::Modes zoomModes; QList sliderLookup; qreal effectiveZoom; KoZoomAction::SpecialButtons specialButtons; QList generateSliderZoomLevels() const; QList filterMenuZoomLevels(const QList &zoomLevels) const; qreal minimumZoomValue; qreal maximumZoomValue; }; QList KoZoomAction::Private::generateSliderZoomLevels() const { QList zoomLevels; qreal defaultZoomStep = sqrt(2.0); zoomLevels << 0.25 / 2.0; zoomLevels << 0.25 / 1.5; zoomLevels << 0.25; zoomLevels << 1.0 / 3.0; zoomLevels << 0.5; zoomLevels << 2.0 / 3.0; zoomLevels << 1.0; for (qreal zoom = zoomLevels.first() / defaultZoomStep; zoom > parent->minimumZoom(); zoom /= defaultZoomStep) { zoomLevels.prepend(zoom); } for (qreal zoom = zoomLevels.last() * defaultZoomStep; zoom < parent->maximumZoom(); zoom *= defaultZoomStep) { zoomLevels.append(zoom); } return zoomLevels; } QList KoZoomAction::Private::filterMenuZoomLevels(const QList &zoomLevels) const { QList filteredZoomLevels; Q_FOREACH (qreal zoom, zoomLevels) { if (zoom >= 0.2 && zoom <= 10) { filteredZoomLevels << zoom; } } return filteredZoomLevels; } KoZoomAction::KoZoomAction(KoZoomMode::Modes zoomModes, const QString& text, QObject *parent) : KSelectAction(text, parent) , d(new Private(this)) { d->zoomModes = zoomModes; d->specialButtons = 0; setIcon(koIcon("zoom-original")); setEditable( true ); setMaxComboViewCount( 15 ); d->sliderLookup = d->generateSliderZoomLevels(); d->effectiveZoom = 1.0; regenerateItems(d->effectiveZoom, true); connect( this, SIGNAL( triggered( const QString& ) ), SLOT( triggered( const QString& ) ) ); } KoZoomAction::~KoZoomAction() { delete d; } qreal KoZoomAction::effectiveZoom() const { return d->effectiveZoom; } void KoZoomAction::setZoom(qreal zoom) { setEffectiveZoom(zoom); regenerateItems(d->effectiveZoom, true); } void KoZoomAction::triggered(const QString& text) { QString zoomString = text; zoomString = zoomString.remove( '&' ); KoZoomMode::Mode mode = KoZoomMode::toMode(zoomString); int zoom = 0; if( mode == KoZoomMode::ZOOM_CONSTANT ) { bool ok; QRegExp regexp( ".*(\\d+).*" ); // "Captured" non-empty sequence of digits int pos = regexp.indexIn( zoomString ); if( pos > -1 ) { zoom = regexp.cap( 1 ).toInt( &ok ); if( !ok ) { zoom = 0; } } } emit zoomChanged( mode, zoom/100.0 ); } void KoZoomAction::setZoomModes( KoZoomMode::Modes zoomModes ) { d->zoomModes = zoomModes; regenerateItems( d->effectiveZoom ); } void KoZoomAction::regenerateItems(const qreal zoom, bool asCurrent) { QList zoomLevels = d->filterMenuZoomLevels(d->sliderLookup); if( !zoomLevels.contains( zoom ) ) zoomLevels << zoom; - qSort(zoomLevels.begin(), zoomLevels.end()); + std::sort(zoomLevels.begin(), zoomLevels.end()); // update items with new sorted zoom values QStringList values; if(d->zoomModes & KoZoomMode::ZOOM_WIDTH) { values << KoZoomMode::toString(KoZoomMode::ZOOM_WIDTH); } if(d->zoomModes & KoZoomMode::ZOOM_TEXT) { values << KoZoomMode::toString(KoZoomMode::ZOOM_TEXT); } if(d->zoomModes & KoZoomMode::ZOOM_PAGE) { values << KoZoomMode::toString(KoZoomMode::ZOOM_PAGE); } Q_FOREACH (qreal value, zoomLevels) { const qreal valueInPercent = value * 100; const int precision = (value > 10.0) ? 0 : 1; values << i18n("%1%", QLocale().toString(valueInPercent, 'f', precision)); } setItems( values ); emit zoomLevelsChanged(values); if(asCurrent) { const qreal zoomInPercent = zoom * 100; // TODO: why zoomInPercent and not zoom here? different from above const int precision = (zoomInPercent > 10.0) ? 0 : 1; const QString valueString = i18n("%1%", QLocale().toString(zoomInPercent, 'f', precision)); setCurrentAction(valueString); emit currentZoomLevelChanged(valueString); } } void KoZoomAction::sliderValueChanged(int value) { setZoom(d->sliderLookup[value]); emit zoomChanged(KoZoomMode::ZOOM_CONSTANT, d->sliderLookup[value]); } qreal KoZoomAction::nextZoomLevel() const { const qreal eps = 1e-5; int i = 0; while (d->effectiveZoom > d->sliderLookup[i] - eps && i < d->sliderLookup.size() - 1) i++; return qMax(d->effectiveZoom, d->sliderLookup[i]); } qreal KoZoomAction::prevZoomLevel() const { const qreal eps = 1e-5; int i = d->sliderLookup.size() - 1; while (d->effectiveZoom < d->sliderLookup[i] + eps && i > 0) i--; return qMin(d->effectiveZoom, d->sliderLookup[i]); } void KoZoomAction::zoomIn() { qreal zoom = nextZoomLevel(); if (zoom > d->effectiveZoom) { setZoom(zoom); emit zoomChanged(KoZoomMode::ZOOM_CONSTANT, d->effectiveZoom); } } void KoZoomAction::zoomOut() { qreal zoom = prevZoomLevel(); if (zoom < d->effectiveZoom) { setZoom(zoom); emit zoomChanged(KoZoomMode::ZOOM_CONSTANT, d->effectiveZoom); } } QWidget * KoZoomAction::createWidget(QWidget *parent) { KoZoomWidget* zoomWidget = new KoZoomWidget(parent, d->specialButtons, d->sliderLookup.size() - 1); connect(this, SIGNAL(zoomLevelsChanged(QStringList)), zoomWidget, SLOT(setZoomLevels(QStringList))); connect(this, SIGNAL(currentZoomLevelChanged(QString)), zoomWidget, SLOT(setCurrentZoomLevel(QString))); connect(this, SIGNAL(sliderChanged(int)), zoomWidget, SLOT(setSliderValue(int))); connect(this, SIGNAL(aspectModeChanged(bool)), zoomWidget, SLOT(setAspectMode(bool))); connect(zoomWidget, SIGNAL(sliderValueChanged(int)), this, SLOT(sliderValueChanged(int))); connect(zoomWidget, SIGNAL(zoomLevelChanged(const QString&)), this, SLOT(triggered(const QString&))); connect(zoomWidget, SIGNAL(aspectModeChanged(bool)), this, SIGNAL(aspectModeChanged(bool))); connect(zoomWidget, SIGNAL(zoomedToSelection()), this, SIGNAL(zoomedToSelection())); connect(zoomWidget, SIGNAL(zoomedToAll()), this, SIGNAL(zoomedToAll())); regenerateItems( d->effectiveZoom, true ); syncSliderWithZoom(); return zoomWidget; } void KoZoomAction::setEffectiveZoom(qreal zoom) { if(d->effectiveZoom == zoom) return; zoom = clampZoom(zoom); d->effectiveZoom = zoom; syncSliderWithZoom(); } void KoZoomAction::setSelectedZoomMode(KoZoomMode::Mode mode) { QString modeString(KoZoomMode::toString(mode)); setCurrentAction(modeString); emit currentZoomLevelChanged(modeString); } void KoZoomAction::setSpecialButtons( SpecialButtons buttons ) { d->specialButtons = buttons; } void KoZoomAction::setAspectMode(bool status) { emit aspectModeChanged(status); } void KoZoomAction::syncSliderWithZoom() { const qreal eps = 1e-5; int i = d->sliderLookup.size() - 1; while (d->effectiveZoom < d->sliderLookup[i] + eps && i > 0) i--; emit sliderChanged(i); } qreal KoZoomAction::minimumZoom() { if (d->minimumZoomValue < 0) { return KoZoomMode::minimumZoom(); } return d->minimumZoomValue; } qreal KoZoomAction::maximumZoom() { if (d->maximumZoomValue < 0) { return KoZoomMode::maximumZoom(); } return d->maximumZoomValue; } qreal KoZoomAction::clampZoom(qreal zoom) { return qMin(maximumZoom(), qMax(minimumZoom(), zoom)); } void KoZoomAction::setMinimumZoom(qreal zoom) { Q_ASSERT(zoom > 0.0f); KoZoomMode::setMinimumZoom(zoom); d->minimumZoomValue = zoom; d->generateSliderZoomLevels(); d->sliderLookup = d->generateSliderZoomLevels(); regenerateItems(d->effectiveZoom, true); syncSliderWithZoom(); } void KoZoomAction::setMaximumZoom(qreal zoom) { Q_ASSERT(zoom > 0.0f); KoZoomMode::setMaximumZoom(zoom); d->maximumZoomValue = zoom; d->sliderLookup = d->generateSliderZoomLevels(); regenerateItems(d->effectiveZoom, true); syncSliderWithZoom(); } diff --git a/libs/widgets/kis_double_parse_unit_spin_box.cpp b/libs/widgets/kis_double_parse_unit_spin_box.cpp index ef08bb33bc..9d4bff8a17 100644 --- a/libs/widgets/kis_double_parse_unit_spin_box.cpp +++ b/libs/widgets/kis_double_parse_unit_spin_box.cpp @@ -1,439 +1,439 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * 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_double_parse_unit_spin_box.h" #include "kis_spin_box_unit_manager.h" #include class Q_DECL_HIDDEN KisDoubleParseUnitSpinBox::Private { public: Private(double low, double up, double step, KisSpinBoxUnitManager* unitManager) : lowerInPoints(low), upperInPoints(up), stepInPoints(step), unit(KoUnit(KoUnit::Point)), outPutSymbol(""), unitManager(unitManager), defaultUnitManager(unitManager), isDeleting(false), unitHasBeenChangedFromOutSideOnce(false), letUnitBeChangedFromOutsideMoreThanOnce(true), displayUnit(true), allowResetDecimals(true) { } double lowerInPoints; ///< lowest value in points double upperInPoints; ///< highest value in points double stepInPoints; ///< step in points KoUnit unit; double previousValueInPoint; ///< allow to store the previous value in point, usefull in some cases, even if, usually, we prefere to refer to the actual value (in selected unit) and convert it, since this is not alway updated. QString previousSymbol; QString outPutSymbol; KisSpinBoxUnitManager* unitManager; //manage more units than permitted by KoUnit. KisSpinBoxUnitManager* defaultUnitManager; //the default unit manager is the one the spinbox rely on and go back to if a connected unit manager is destroyed before the spinbox. bool isDeleting; bool unitHasBeenChangedFromOutSideOnce; //in some part of the code the unit is reset. We want to prevent this overriding the unit defined by the user. We use this switch to do so. bool letUnitBeChangedFromOutsideMoreThanOnce; bool displayUnit; bool allowResetDecimals; }; KisDoubleParseUnitSpinBox::KisDoubleParseUnitSpinBox(QWidget *parent) : KisDoubleParseSpinBox(parent), d(new Private(-9999, 9999, 1, KisSpinBoxUnitManagerFactory::buildDefaultUnitManager(this))) { setUnit( KoUnit(KoUnit::Point) ); setAlignment( Qt::AlignRight ); connect(this, SIGNAL(valueChanged( double )), this, SLOT(privateValueChanged())); connect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(detectUnitChanges()) ); connect(d->unitManager, (void (KisSpinBoxUnitManager::*)()) &KisSpinBoxUnitManager::unitAboutToChange, this, (void (KisDoubleParseUnitSpinBox::*)()) &KisDoubleParseUnitSpinBox::prepareUnitChange); connect(d->unitManager, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged, this, (void (KisDoubleParseUnitSpinBox::*)( QString const& )) &KisDoubleParseUnitSpinBox::internalUnitChange); setDecimals(d->unitManager->getApparentUnitRecommandedDecimals()); } KisDoubleParseUnitSpinBox::~KisDoubleParseUnitSpinBox() { d->isDeleting = true; delete d->defaultUnitManager; delete d; } void KisDoubleParseUnitSpinBox::setUnitManager(KisSpinBoxUnitManager* unitManager) { qreal oldVal = d->unitManager->getReferenceValue(KisDoubleParseSpinBox::value()); QString oldSymbol = d->unitManager->getApparentUnitSymbol(); - qreal newVal; + qreal newVal = 0.0; double newMin; double newMax; double newStep; if (oldSymbol == unitManager->getApparentUnitSymbol() && d->unitManager->getUnitDimensionType() == unitManager->getUnitDimensionType()) { d->unitManager = unitManager; //set the new unitmanager anyway, since it may be a subclass, so change the behavior anyway. goto connect_signals; } if (d->unitManager->getUnitDimensionType() == unitManager->getUnitDimensionType()) { //dimension is the same, calculate the new value newVal = unitManager->getApparentValue(oldVal); } else { newVal = unitManager->getApparentValue(d->lowerInPoints); } newMin = unitManager->getApparentValue(d->lowerInPoints); newMax = unitManager->getApparentValue(d->upperInPoints); newStep = unitManager->getApparentValue(d->stepInPoints); if (unitManager->getApparentUnitSymbol() == KoUnit(KoUnit::Pixel).symbol()) { // limit the pixel step by 1.0 newStep = qMax(qreal(1.0), newStep); } KisDoubleParseSpinBox::setMinimum(newMin); KisDoubleParseSpinBox::setMaximum(newMax); KisDoubleParseSpinBox::setSingleStep(newStep); connect_signals: if (d->unitManager != d->defaultUnitManager) { disconnect(d->unitManager, &QObject::destroyed, this, &KisDoubleParseUnitSpinBox::disconnectExternalUnitManager); //there's no dependance anymore. } disconnect(d->unitManager, (void (KisSpinBoxUnitManager::*)()) &KisSpinBoxUnitManager::unitAboutToChange, this, (void (KisDoubleParseUnitSpinBox::*)()) &KisDoubleParseUnitSpinBox::prepareUnitChange); disconnect(d->unitManager, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged, this, (void (KisDoubleParseUnitSpinBox::*)( QString const& )) &KisDoubleParseUnitSpinBox::internalUnitChange); d->unitManager = unitManager; connect(d->unitManager, &QObject::destroyed, this, &KisDoubleParseUnitSpinBox::disconnectExternalUnitManager); connect(d->unitManager, (void (KisSpinBoxUnitManager::*)()) &KisSpinBoxUnitManager::unitAboutToChange, this, (void (KisDoubleParseUnitSpinBox::*)()) &KisDoubleParseUnitSpinBox::prepareUnitChange); connect(d->unitManager, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged, this, (void (KisDoubleParseUnitSpinBox::*)( QString const& )) &KisDoubleParseUnitSpinBox::internalUnitChange); KisDoubleParseSpinBox::setValue(newVal); if (d->allowResetDecimals) { //if the user has not fixed the number of decimals. setDecimals(d->unitManager->getApparentUnitRecommandedDecimals()); } } void KisDoubleParseUnitSpinBox::changeValue( double newValue ) { double apparentValue; - double fact; - double cons; + double fact = 0.0; + double cons = 0.0; if (d->outPutSymbol.isEmpty()) { apparentValue = d->unitManager->getApparentValue(newValue); } else { fact = d->unitManager->getConversionFactor(d->unitManager->getUnitDimensionType(), d->outPutSymbol); cons = d->unitManager->getConversionConstant(d->unitManager->getUnitDimensionType(), d->outPutSymbol); apparentValue = fact*newValue + cons; } if (apparentValue == KisDoubleParseSpinBox::value()) { return; } if (d->outPutSymbol.isEmpty()) { KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue(newValue) ); } else { KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue((newValue - cons)/fact) ); } } void KisDoubleParseUnitSpinBox::setUnit( const KoUnit & unit) { if (d->unitHasBeenChangedFromOutSideOnce && !d->letUnitBeChangedFromOutsideMoreThanOnce) { return; } if (d->unitManager->getUnitDimensionType() != KisSpinBoxUnitManager::LENGTH) { d->unitManager->setUnitDimension(KisSpinBoxUnitManager::LENGTH); //setting the unit using a KoUnit mean you want to use a length. } setUnit(unit.symbol()); d->unit = unit; } void KisDoubleParseUnitSpinBox::setUnit(const QString &symbol) { d->unitManager->setApparentUnitFromSymbol(symbol); //via signals and slots, the correct functions should be called. } void KisDoubleParseUnitSpinBox::setReturnUnit(const QString & symbol) { d->outPutSymbol = symbol; } void KisDoubleParseUnitSpinBox::prepareUnitChange() { d->previousValueInPoint = d->unitManager->getReferenceValue(KisDoubleParseSpinBox::value()); d->previousSymbol = d->unitManager->getApparentUnitSymbol(); } void KisDoubleParseUnitSpinBox::internalUnitChange(const QString &symbol) { //d->unitManager->setApparentUnitFromSymbol(symbol); if (d->unitManager->getApparentUnitSymbol() == d->previousSymbol) { //the setApparentUnitFromSymbol is a bit clever, for example in regard of Casesensitivity. So better check like this. return; } KisDoubleParseSpinBox::setMinimum( d->unitManager->getApparentValue( d->lowerInPoints ) ); KisDoubleParseSpinBox::setMaximum( d->unitManager->getApparentValue( d->upperInPoints ) ); qreal step = d->unitManager->getApparentValue( d->stepInPoints ); if (symbol == KoUnit(KoUnit::Pixel).symbol()) { // limit the pixel step by 1.0 step = qMax(qreal(1.0), step); } KisDoubleParseSpinBox::setSingleStep( step ); KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue( d->previousValueInPoint ) ); if (d->allowResetDecimals) { setDecimals(d->unitManager->getApparentUnitRecommandedDecimals()); } d->unitHasBeenChangedFromOutSideOnce = true; } void KisDoubleParseUnitSpinBox::setDimensionType(int dim) { if (!KisSpinBoxUnitManager::isUnitId(dim)) { return; } d->unitManager->setUnitDimension((KisSpinBoxUnitManager::UnitDimension) dim); } double KisDoubleParseUnitSpinBox::value( ) const { if (d->outPutSymbol.isEmpty()) { return d->unitManager->getReferenceValue( KisDoubleParseSpinBox::value() ); } double ref = d->unitManager->getReferenceValue( KisDoubleParseSpinBox::value() ); double fact = d->unitManager->getConversionFactor(d->unitManager->getUnitDimensionType(), d->outPutSymbol); double cons = d->unitManager->getConversionConstant(d->unitManager->getUnitDimensionType(), d->outPutSymbol); return fact*ref + cons; } void KisDoubleParseUnitSpinBox::setMinimum(double min) { d->lowerInPoints = min; KisDoubleParseSpinBox::setMinimum( d->unitManager->getApparentValue( min ) ); } void KisDoubleParseUnitSpinBox::setMaximum(double max) { d->upperInPoints = max; KisDoubleParseSpinBox::setMaximum( d->unitManager->getApparentValue( max ) ); } void KisDoubleParseUnitSpinBox::setLineStep(double step) { d->stepInPoints = d->unitManager->getReferenceValue(step); KisDoubleParseSpinBox::setSingleStep( step ); } void KisDoubleParseUnitSpinBox::setLineStepPt(double step) { d->stepInPoints = step; KisDoubleParseSpinBox::setSingleStep( d->unitManager->getApparentValue( step ) ); } void KisDoubleParseUnitSpinBox::setMinMaxStep( double min, double max, double step ) { setMinimum( min ); setMaximum( max ); setLineStepPt( step ); } QValidator::State KisDoubleParseUnitSpinBox::validate(QString &input, int &pos) const { Q_UNUSED(pos); QRegExp regexp ("([ a-zA-Z]+)$"); // Letters or spaces at end const int res = input.indexOf( regexp ); /*if ( res == -1 ) { // Nothing like an unit? The user is probably editing the unit return QValidator::Intermediate; }*/ QString expr ( (res > 0) ? input.left( res ) : input ); const QString unitName ( (res > 0) ? regexp.cap( 1 ).trimmed().toLower() : "" ); bool ok = true; bool interm = false; QValidator::State exprState = KisDoubleParseSpinBox::validate(expr, pos); if (res < 0) { return exprState; } if (exprState == QValidator::Invalid) { return exprState; } else if (exprState == QValidator::Intermediate) { interm = true; } //check if we can parse the unit. QStringList listOfSymbol = d->unitManager->getsUnitSymbolList(); ok = listOfSymbol.contains(unitName); if (!ok || interm) { return QValidator::Intermediate; } return QValidator::Acceptable; } QString KisDoubleParseUnitSpinBox::textFromValue( double value ) const { QString txt = KisDoubleParseSpinBox::textFromValue(value); if (d->displayUnit) { if (!txt.endsWith(d->unitManager->getApparentUnitSymbol())) { txt += " " + d->unitManager->getApparentUnitSymbol(); } } return txt; } QString KisDoubleParseUnitSpinBox::veryCleanText() const { return makeTextClean(cleanText()); } double KisDoubleParseUnitSpinBox::valueFromText( const QString& str ) const { QString txt = makeTextClean(str); return KisDoubleParseSpinBox::valueFromText(txt); //this function will take care of prefix (and don't mind if suffix has been removed. } void KisDoubleParseUnitSpinBox::setUnitChangeFromOutsideBehavior(bool toggle) { d->letUnitBeChangedFromOutsideMoreThanOnce = toggle; } void KisDoubleParseUnitSpinBox::setDisplayUnit(bool toggle) { d->displayUnit = toggle; } void KisDoubleParseUnitSpinBox::preventDecimalsChangeFromUnitManager(bool prevent) { d->allowResetDecimals = !prevent; } void KisDoubleParseUnitSpinBox::privateValueChanged() { emit valueChangedPt( value() ); } QString KisDoubleParseUnitSpinBox::detectUnit() { QString str = veryCleanText().trimmed(); //text with the new unit but not the old one. QRegExp regexp ("([ ]*[a-zA-Z]+[ ]*)$"); // Letters or spaces at end int res = str.indexOf( regexp ); if (res > -1) { QString expr ( str.right( str.size() - res ) ); expr = expr.trimmed(); return expr; } return ""; } void KisDoubleParseUnitSpinBox::detectUnitChanges() { QString unitSymb = detectUnit(); if (unitSymb.isEmpty()) { return; } QString oldUnitSymb = d->unitManager->getApparentUnitSymbol(); setUnit(unitSymb); setValue(valueFromText(cleanText())); //change value keep the old value, but converted to new unit... which is different from the value the user entered in the new unit. So we need to set the new value. if (oldUnitSymb != d->unitManager->getApparentUnitSymbol()) { // the user has changed the unit, so we block changes from outside. setUnitChangeFromOutsideBehavior(false); } } QString KisDoubleParseUnitSpinBox::makeTextClean(QString const& txt) const { QString expr = txt; QString symbol = d->unitManager->getApparentUnitSymbol(); if ( expr.endsWith(suffix()) ) { expr.remove(expr.size()-suffix().size(), suffix().size()); } expr = expr.trimmed(); if ( expr.endsWith(symbol) ) { expr.remove(expr.size()-symbol.size(), symbol.size()); } return expr.trimmed(); } void KisDoubleParseUnitSpinBox::disconnectExternalUnitManager() { if (!d->isDeleting) { setUnitManager(d->defaultUnitManager); //go back to default unit manager. } } diff --git a/libs/widgetutils/xmlgui/ktoolbar.cpp b/libs/widgetutils/xmlgui/ktoolbar.cpp index 3f8f9f9e55..b3a4a803ce 100644 --- a/libs/widgetutils/xmlgui/ktoolbar.cpp +++ b/libs/widgetutils/xmlgui/ktoolbar.cpp @@ -1,1437 +1,1437 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Reginald Stadlbauer (reggie@kde.org) (C) 1997, 1998 Stephan Kulow (coolo@kde.org) (C) 1997, 1998 Mark Donohoe (donohoe@kde.org) (C) 1997, 1998 Sven Radej (radej@kde.org) (C) 1997, 1998 Matthias Ettrich (ettrich@kde.org) (C) 1999 Chris Schlaeger (cs@kde.org) (C) 1999 Kurt Granroth (granroth@kde.org) (C) 2005-2006 Hamish Rodda (rodda@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ktoolbar.h" #include "config-xmlgui.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_DBUS #include #include #endif #include #include #include #include #ifdef HAVE_ICONTHEMES #include #endif #include #include #include #include #include "kactioncollection.h" #include "kedittoolbar.h" #include "kxmlguifactory.h" #include "kxmlguiwindow.h" #include /* Toolbar settings (e.g. icon size or toolButtonStyle) ===================================================== We have the following stack of settings (in order of priority) : - user-specified settings (loaded/saved in KConfig) - developer-specified settings in the XMLGUI file (if using xmlgui) (cannot change at runtime) - KDE-global default (user-configurable; can change at runtime) and when switching between kparts, they are saved as xml in memory, which, in the unlikely case of no-kmainwindow-autosaving, could be different from the user-specified settings saved in KConfig and would have priority over it. So, in summary, without XML: Global config / User settings (loaded/saved in kconfig) and with XML: Global config / App-XML attributes / User settings (loaded/saved in kconfig) And all those settings (except the KDE-global defaults) have to be stored in memory since we cannot retrieve them at random points in time, not knowing the xml document nor config file that holds these settings. Hence the iconSizeSettings and toolButtonStyleSettings arrays. For instance, if you change the KDE-global default, whether this makes a change on a given toolbar depends on whether there are settings at Level_AppXML or Level_UserSettings. Only if there are no settings at those levels, should the change of KDEDefault make a difference. */ enum SettingLevel { Level_KDEDefault, Level_AppXML, Level_UserSettings, NSettingLevels }; enum { Unset = -1 }; class KToolBar::Private { public: Private(KToolBar *qq) : q(qq), isMainToolBar(false), unlockedMovable(true), contextOrient(0), contextMode(0), contextSize(0), contextButtonTitle(0), contextShowText(0), contextButtonAction(0), contextTop(0), contextLeft(0), contextRight(0), contextBottom(0), contextIcons(0), contextTextRight(0), contextText(0), contextTextUnder(0), contextLockAction(0), dropIndicatorAction(0), context(0), dragAction(0) { } void slotAppearanceChanged(); void slotContextAboutToShow(); void slotContextAboutToHide(); void slotContextLeft(); void slotContextRight(); void slotContextShowText(); void slotContextTop(); void slotContextBottom(); void slotContextIcons(); void slotContextText(); void slotContextTextRight(); void slotContextTextUnder(); void slotContextIconSize(); void slotLockToolBars(bool lock); void init(bool readConfig = true, bool isMainToolBar = false); QString getPositionAsString() const; QMenu *contextMenu(const QPoint &globalPos); void setLocked(bool locked); void adjustSeparatorVisibility(); void loadKDESettings(); void applyCurrentSettings(); QAction *findAction(const QString &actionName, KXMLGUIClient **client = 0) const; static Qt::ToolButtonStyle toolButtonStyleFromString(const QString &style); static QString toolButtonStyleToString(Qt::ToolButtonStyle); static Qt::ToolBarArea positionFromString(const QString &position); static Qt::ToolButtonStyle toolButtonStyleSetting(); KToolBar *q; bool isMainToolBar : 1; bool unlockedMovable : 1; static bool s_editable; static bool s_locked; QSet xmlguiClients; QMenu *contextOrient; QMenu *contextMode; QMenu *contextSize; QAction *contextButtonTitle; QAction *contextShowText; QAction *contextButtonAction; QAction *contextTop; QAction *contextLeft; QAction *contextRight; QAction *contextBottom; QAction *contextIcons; QAction *contextTextRight; QAction *contextText; QAction *contextTextUnder; KToggleAction *contextLockAction; QMap contextIconSizes; class IntSetting { public: IntSetting() { for (int level = 0; level < NSettingLevels; ++level) { values[level] = Unset; } } int currentValue() const { int val = Unset; for (int level = 0; level < NSettingLevels; ++level) { if (values[level] != Unset) { val = values[level]; } } return val; } // Default value as far as the user is concerned is kde-global + app-xml. // If currentValue()==defaultValue() then nothing to write into kconfig. int defaultValue() const { int val = Unset; for (int level = 0; level < Level_UserSettings; ++level) { if (values[level] != Unset) { val = values[level]; } } return val; } QString toString() const { QString str; for (int level = 0; level < NSettingLevels; ++level) { str += QString::number(values[level]) + QLatin1Char(' '); } return str; } int &operator[](int index) { return values[index]; } private: int values[NSettingLevels]; }; IntSetting iconSizeSettings; IntSetting toolButtonStyleSettings; // either Qt::ToolButtonStyle or -1, hence "int". QList actionsBeingDragged; QAction *dropIndicatorAction; QMenu *context; QAction *dragAction; QPoint dragStartPosition; }; bool KToolBar::Private::s_editable = false; bool KToolBar::Private::s_locked = true; void KToolBar::Private::init(bool readConfig, bool _isMainToolBar) { isMainToolBar = _isMainToolBar; loadKDESettings(); // also read in our configurable settings (for non-xmlgui toolbars) if (readConfig) { KConfigGroup cg(KSharedConfig::openConfig(), QString()); q->applySettings(cg); } if (q->mainWindow()) { // Get notified when settings change connect(q, SIGNAL(allowedAreasChanged(Qt::ToolBarAreas)), q->mainWindow(), SLOT(setSettingsDirty())); connect(q, SIGNAL(iconSizeChanged(QSize)), q->mainWindow(), SLOT(setSettingsDirty())); connect(q, SIGNAL(toolButtonStyleChanged(Qt::ToolButtonStyle)), q->mainWindow(), SLOT(setSettingsDirty())); connect(q, SIGNAL(movableChanged(bool)), q->mainWindow(), SLOT(setSettingsDirty())); connect(q, SIGNAL(orientationChanged(Qt::Orientation)), q->mainWindow(), SLOT(setSettingsDirty())); } if (!KAuthorized::authorize(QStringLiteral("movable_toolbars"))) { q->setMovable(false); } else { q->setMovable(!KToolBar::toolBarsLocked()); } connect(q, SIGNAL(movableChanged(bool)), q, SLOT(slotMovableChanged(bool))); q->setAcceptDrops(true); #ifdef HAVE_DBUS QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KToolBar"), QStringLiteral("org.kde.KToolBar"), QStringLiteral("styleChanged"), q, SLOT(slotAppearanceChanged())); #endif } QString KToolBar::Private::getPositionAsString() const { // get all of the stuff to save switch (q->mainWindow()->toolBarArea(const_cast(q))) { case Qt::BottomToolBarArea: return QStringLiteral("Bottom"); case Qt::LeftToolBarArea: return QStringLiteral("Left"); case Qt::RightToolBarArea: return QStringLiteral("Right"); case Qt::TopToolBarArea: default: return QStringLiteral("Top"); } } QMenu *KToolBar::Private::contextMenu(const QPoint &globalPos) { if (!context) { context = new QMenu(q); contextButtonTitle = context->addSection(i18nc("@title:menu", "Show Text")); contextShowText = context->addAction(QString(), q, SLOT(slotContextShowText())); context->addSection(i18nc("@title:menu", "Toolbar Settings")); contextOrient = new QMenu(i18nc("Toolbar orientation", "Orientation"), context); contextTop = contextOrient->addAction(i18nc("toolbar position string", "Top"), q, SLOT(slotContextTop())); contextTop->setChecked(true); contextLeft = contextOrient->addAction(i18nc("toolbar position string", "Left"), q, SLOT(slotContextLeft())); contextRight = contextOrient->addAction(i18nc("toolbar position string", "Right"), q, SLOT(slotContextRight())); contextBottom = contextOrient->addAction(i18nc("toolbar position string", "Bottom"), q, SLOT(slotContextBottom())); QActionGroup *positionGroup = new QActionGroup(contextOrient); Q_FOREACH (QAction *action, contextOrient->actions()) { action->setActionGroup(positionGroup); action->setCheckable(true); } contextMode = new QMenu(i18n("Text Position"), context); contextIcons = contextMode->addAction(i18n("Icons Only"), q, SLOT(slotContextIcons())); contextText = contextMode->addAction(i18n("Text Only"), q, SLOT(slotContextText())); contextTextRight = contextMode->addAction(i18n("Text Alongside Icons"), q, SLOT(slotContextTextRight())); contextTextUnder = contextMode->addAction(i18n("Text Under Icons"), q, SLOT(slotContextTextUnder())); QActionGroup *textGroup = new QActionGroup(contextMode); Q_FOREACH (QAction *action, contextMode->actions()) { action->setActionGroup(textGroup); action->setCheckable(true); } contextSize = new QMenu(i18n("Icon Size"), context); contextIconSizes.insert(contextSize->addAction(i18nc("@item:inmenu Icon size", "Default"), q, SLOT(slotContextIconSize())), iconSizeSettings.defaultValue()); QList avSizes; avSizes << 16 << 22 << 24 << 32 << 48 << 64 << 128 << 256; - qSort(avSizes); + std::sort(avSizes.begin(), avSizes.end()); if (avSizes.count() < 10) { // Fixed or threshold type icons Q_FOREACH (int it, avSizes) { QString text; if (it < 19) { text = i18n("Small (%1x%2)", it, it); } else if (it < 25) { text = i18n("Medium (%1x%2)", it, it); } else if (it < 35) { text = i18n("Large (%1x%2)", it, it); } else { text = i18n("Huge (%1x%2)", it, it); } // save the size in the contextIconSizes map contextIconSizes.insert(contextSize->addAction(text, q, SLOT(slotContextIconSize())), it); } } else { // Scalable icons. const int progression[] = { 16, 22, 32, 48, 64, 96, 128, 192, 256 }; for (uint i = 0; i < 9; i++) { Q_FOREACH (int it, avSizes) { if (it >= progression[ i ]) { QString text; if (it < 19) { text = i18n("Small (%1x%2)", it, it); } else if (it < 25) { text = i18n("Medium (%1x%2)", it, it); } else if (it < 35) { text = i18n("Large (%1x%2)", it, it); } else { text = i18n("Huge (%1x%2)", it, it); } // save the size in the contextIconSizes map contextIconSizes.insert(contextSize->addAction(text, q, SLOT(slotContextIconSize())), it); break; } } } } QActionGroup *sizeGroup = new QActionGroup(contextSize); Q_FOREACH (QAction *action, contextSize->actions()) { action->setActionGroup(sizeGroup); action->setCheckable(true); } if (!q->toolBarsLocked() && !q->isMovable()) { unlockedMovable = false; } delete contextLockAction; contextLockAction = new KToggleAction(KisIconUtils::loadIcon(QStringLiteral("system-lock-screen")), i18n("Lock Toolbar Positions"), q); contextLockAction->setChecked(q->toolBarsLocked()); connect(contextLockAction, SIGNAL(toggled(bool)), q, SLOT(slotLockToolBars(bool))); // Now add the actions to the menu context->addMenu(contextMode); context->addMenu(contextSize); context->addMenu(contextOrient); context->addSeparator(); connect(context, SIGNAL(aboutToShow()), q, SLOT(slotContextAboutToShow())); } contextButtonAction = q->actionAt(q->mapFromGlobal(globalPos)); if (contextButtonAction) { contextShowText->setText(contextButtonAction->text()); contextShowText->setIcon(contextButtonAction->icon()); contextShowText->setCheckable(true); } contextOrient->menuAction()->setVisible(!q->toolBarsLocked()); // Unplugging a submenu from abouttohide leads to the popupmenu floating around // So better simply call that code from after exec() returns (DF) //connect(context, SIGNAL(aboutToHide()), this, SLOT(slotContextAboutToHide())); return context; } void KToolBar::Private::setLocked(bool locked) { if (unlockedMovable) { q->setMovable(!locked); } } void KToolBar::Private::adjustSeparatorVisibility() { bool visibleNonSeparator = false; int separatorToShow = -1; for (int index = 0; index < q->actions().count(); ++index) { QAction *action = q->actions()[ index ]; if (action->isSeparator()) { if (visibleNonSeparator) { separatorToShow = index; visibleNonSeparator = false; } else { action->setVisible(false); } } else if (!visibleNonSeparator) { if (action->isVisible()) { visibleNonSeparator = true; if (separatorToShow != -1) { q->actions()[ separatorToShow ]->setVisible(true); separatorToShow = -1; } } } } if (separatorToShow != -1) { q->actions()[ separatorToShow ]->setVisible(false); } } Qt::ToolButtonStyle KToolBar::Private::toolButtonStyleFromString(const QString &_style) { QString style = _style.toLower(); if (style == QStringLiteral("textbesideicon") || style == QLatin1String("icontextright")) { return Qt::ToolButtonTextBesideIcon; } else if (style == QStringLiteral("textundericon") || style == QLatin1String("icontextbottom")) { return Qt::ToolButtonTextUnderIcon; } else if (style == QStringLiteral("textonly")) { return Qt::ToolButtonTextOnly; } else { return Qt::ToolButtonIconOnly; } } QString KToolBar::Private::toolButtonStyleToString(Qt::ToolButtonStyle style) { switch (style) { case Qt::ToolButtonIconOnly: default: return QStringLiteral("IconOnly"); case Qt::ToolButtonTextBesideIcon: return QStringLiteral("TextBesideIcon"); case Qt::ToolButtonTextOnly: return QStringLiteral("TextOnly"); case Qt::ToolButtonTextUnderIcon: return QStringLiteral("TextUnderIcon"); } } Qt::ToolBarArea KToolBar::Private::positionFromString(const QString &position) { Qt::ToolBarArea newposition = Qt::TopToolBarArea; if (position == QStringLiteral("left")) { newposition = Qt::LeftToolBarArea; } else if (position == QStringLiteral("bottom")) { newposition = Qt::BottomToolBarArea; } else if (position == QStringLiteral("right")) { newposition = Qt::RightToolBarArea; } return newposition; } // Global setting was changed void KToolBar::Private::slotAppearanceChanged() { loadKDESettings(); applyCurrentSettings(); } Qt::ToolButtonStyle KToolBar::Private::toolButtonStyleSetting() { KConfigGroup group(KSharedConfig::openConfig(), "Toolbar style"); const QString fallback = KToolBar::Private::toolButtonStyleToString(Qt::ToolButtonTextBesideIcon); return KToolBar::Private::toolButtonStyleFromString(group.readEntry("ToolButtonStyle", fallback)); } void KToolBar::Private::loadKDESettings() { iconSizeSettings[Level_KDEDefault] = q->iconSizeDefault(); if (isMainToolBar) { toolButtonStyleSettings[Level_KDEDefault] = toolButtonStyleSetting(); } else { const QString fallBack = toolButtonStyleToString(Qt::ToolButtonTextBesideIcon); /** TODO: if we get complaints about text beside icons on small screens, try the following code out on such systems - aseigo. // if we are on a small screen with a non-landscape ratio, then // we revert to text under icons since width is probably not our // friend in such cases QDesktopWidget *desktop = QApplication::desktop(); QRect screenGeom = desktop->screenGeometry(desktop->primaryScreen()); qreal ratio = screenGeom.width() / qreal(screenGeom.height()); if (screenGeom.width() < 1024 && ratio <= 1.4) { fallBack = "TextUnderIcon"; } **/ KConfigGroup group(KSharedConfig::openConfig(), "Toolbar style"); const QString value = group.readEntry("ToolButtonStyleOtherToolbars", fallBack); toolButtonStyleSettings[Level_KDEDefault] = KToolBar::Private::toolButtonStyleFromString(value); } } // Call this after changing something in d->iconSizeSettings or d->toolButtonStyleSettings void KToolBar::Private::applyCurrentSettings() { //qDebug() << q->objectName() << "iconSizeSettings:" << iconSizeSettings.toString() << "->" << iconSizeSettings.currentValue(); const int currentIconSize = iconSizeSettings.currentValue(); q->setIconSize(QSize(currentIconSize, currentIconSize)); //qDebug() << q->objectName() << "toolButtonStyleSettings:" << toolButtonStyleSettings.toString() << "->" << toolButtonStyleSettings.currentValue(); q->setToolButtonStyle(static_cast(toolButtonStyleSettings.currentValue())); // And remember to save the new look later KMainWindow *kmw = q->mainWindow(); if (kmw) { kmw->setSettingsDirty(); } } QAction *KToolBar::Private::findAction(const QString &actionName, KXMLGUIClient **clientOut) const { Q_FOREACH (KXMLGUIClient *client, xmlguiClients) { QAction *action = client->actionCollection()->action(actionName); if (action) { if (clientOut) { *clientOut = client; } return action; } } return 0; } void KToolBar::Private::slotContextAboutToShow() { /** * The idea here is to reuse the "static" part of the menu to save time. * But the "Toolbars" action is dynamic (can be a single action or a submenu) * and ToolBarHandler::setupActions() deletes it, so better not keep it around. * So we currently plug/unplug the last two actions of the menu. * Another way would be to keep around the actions and plug them all into a (new each time) popupmenu. */ KXmlGuiWindow *kmw = qobject_cast(q->mainWindow()); // try to find "configure toolbars" action QAction *configureAction = 0; const char *actionName; actionName = KStandardAction::name(KStandardAction::ConfigureToolbars); configureAction = findAction(QLatin1String(actionName)); if (!configureAction && kmw) { configureAction = kmw->actionCollection()->action(QLatin1String(actionName)); } if (configureAction) { context->addAction(configureAction); } context->addAction(contextLockAction); if (kmw) { kmw->setupToolbarMenuActions(); // Only allow hiding a toolbar if the action is also plugged somewhere else (e.g. menubar) QAction *tbAction = kmw->toolBarMenuAction(); if (!q->toolBarsLocked() && tbAction && tbAction->associatedWidgets().count() > 0) { context->addAction(tbAction); } } KEditToolBar::setGlobalDefaultToolBar(q->QObject::objectName().toLatin1().constData()); // Check the actions that should be checked switch (q->toolButtonStyle()) { case Qt::ToolButtonIconOnly: default: contextIcons->setChecked(true); break; case Qt::ToolButtonTextBesideIcon: contextTextRight->setChecked(true); break; case Qt::ToolButtonTextOnly: contextText->setChecked(true); break; case Qt::ToolButtonTextUnderIcon: contextTextUnder->setChecked(true); break; } QMapIterator< QAction *, int > it = contextIconSizes; while (it.hasNext()) { it.next(); if (it.value() == q->iconSize().width()) { it.key()->setChecked(true); break; } } switch (q->mainWindow()->toolBarArea(q)) { case Qt::BottomToolBarArea: contextBottom->setChecked(true); break; case Qt::LeftToolBarArea: contextLeft->setChecked(true); break; case Qt::RightToolBarArea: contextRight->setChecked(true); break; default: case Qt::TopToolBarArea: contextTop->setChecked(true); break; } const bool showButtonSettings = contextButtonAction && !contextShowText->text().isEmpty() && contextTextRight->isChecked(); contextButtonTitle->setVisible(showButtonSettings); contextShowText->setVisible(showButtonSettings); if (showButtonSettings) { contextShowText->setChecked(contextButtonAction->priority() >= QAction::NormalPriority); } } void KToolBar::Private::slotContextAboutToHide() { // We have to unplug whatever slotContextAboutToShow plugged into the menu. // Unplug the toolbar menu action KXmlGuiWindow *kmw = qobject_cast(q->mainWindow()); if (kmw && kmw->toolBarMenuAction()) { if (kmw->toolBarMenuAction()->associatedWidgets().count() > 1) { context->removeAction(kmw->toolBarMenuAction()); } } // Unplug the configure toolbars action too, since it's afterwards anyway QAction *configureAction = 0; const char *actionName; actionName = KStandardAction::name(KStandardAction::ConfigureToolbars); configureAction = findAction(QLatin1String(actionName)); if (!configureAction && kmw) { configureAction = kmw->actionCollection()->action(QLatin1String(actionName)); } if (configureAction) { context->removeAction(configureAction); } context->removeAction(contextLockAction); } void KToolBar::Private::slotContextLeft() { q->mainWindow()->addToolBar(Qt::LeftToolBarArea, q); } void KToolBar::Private::slotContextRight() { q->mainWindow()->addToolBar(Qt::RightToolBarArea, q); } void KToolBar::Private::slotContextShowText() { Q_ASSERT(contextButtonAction); const QAction::Priority priority = contextShowText->isChecked() ? QAction::NormalPriority : QAction::LowPriority; contextButtonAction->setPriority(priority); // Find to which xml file and componentData the action belongs to QString componentName; QString filename; KXMLGUIClient *client; if (findAction(contextButtonAction->objectName(), &client)) { componentName = client->componentName(); filename = client->xmlFile(); } if (filename.isEmpty()) { componentName = QCoreApplication::applicationName(); filename = componentName + QStringLiteral("ui.xmlgui"); } // Save the priority state of the action const QString configFile = KXMLGUIFactory::readConfigFile(filename, componentName); QDomDocument document; document.setContent(configFile); QDomElement elem = KXMLGUIFactory::actionPropertiesElement(document); QDomElement actionElem = KXMLGUIFactory::findActionByName(elem, contextButtonAction->objectName(), true); actionElem.setAttribute(QStringLiteral("priority"), priority); KXMLGUIFactory::saveConfigFile(document, filename, componentName); } void KToolBar::Private::slotContextTop() { q->mainWindow()->addToolBar(Qt::TopToolBarArea, q); } void KToolBar::Private::slotContextBottom() { q->mainWindow()->addToolBar(Qt::BottomToolBarArea, q); } void KToolBar::Private::slotContextIcons() { q->setToolButtonStyle(Qt::ToolButtonIconOnly); toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle(); } void KToolBar::Private::slotContextText() { q->setToolButtonStyle(Qt::ToolButtonTextOnly); toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle(); } void KToolBar::Private::slotContextTextUnder() { q->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle(); } void KToolBar::Private::slotContextTextRight() { q->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle(); } void KToolBar::Private::slotContextIconSize() { QAction *action = qobject_cast(q->sender()); if (action && contextIconSizes.contains(action)) { const int iconSize = contextIconSizes.value(action); q->setIconDimensions(iconSize); } } void KToolBar::Private::slotLockToolBars(bool lock) { q->setToolBarsLocked(lock); } KToolBar::KToolBar(QWidget *parent, bool isMainToolBar, bool readConfig) : QToolBar(parent), d(new Private(this)) { d->init(readConfig, isMainToolBar); // KToolBar is auto-added to the top area of the main window if parent is a QMainWindow if (QMainWindow *mw = qobject_cast(parent)) { mw->addToolBar(this); } } KToolBar::KToolBar(const QString &objectName, QWidget *parent, bool readConfig) : QToolBar(parent), d(new Private(this)) { setObjectName(objectName); // mainToolBar -> isMainToolBar = true -> buttonStyle is configurable // others -> isMainToolBar = false -> ### hardcoded default for buttonStyle !!! should be configurable? -> hidden key added d->init(readConfig, objectName == QStringLiteral("mainToolBar")); // KToolBar is auto-added to the top area of the main window if parent is a QMainWindow if (QMainWindow *mw = qobject_cast(parent)) { mw->addToolBar(this); } } KToolBar::KToolBar(const QString &objectName, QMainWindow *parent, Qt::ToolBarArea area, bool newLine, bool isMainToolBar, bool readConfig) : QToolBar(parent), d(new Private(this)) { setObjectName(objectName); d->init(readConfig, isMainToolBar); if (newLine) { mainWindow()->addToolBarBreak(area); } mainWindow()->addToolBar(area, this); if (newLine) { mainWindow()->addToolBarBreak(area); } } KToolBar::~KToolBar() { delete d->contextLockAction; delete d; } void KToolBar::saveSettings(KConfigGroup &cg) { Q_ASSERT(!cg.name().isEmpty()); const int currentIconSize = iconSize().width(); //qDebug() << objectName() << currentIconSize << d->iconSizeSettings.toString() << "defaultValue=" << d->iconSizeSettings.defaultValue(); if (!cg.hasDefault("IconSize") && currentIconSize == d->iconSizeSettings.defaultValue()) { cg.revertToDefault("IconSize"); d->iconSizeSettings[Level_UserSettings] = Unset; } else { cg.writeEntry("IconSize", currentIconSize); d->iconSizeSettings[Level_UserSettings] = currentIconSize; } const Qt::ToolButtonStyle currentToolButtonStyle = toolButtonStyle(); if (!cg.hasDefault("ToolButtonStyle") && currentToolButtonStyle == d->toolButtonStyleSettings.defaultValue()) { cg.revertToDefault("ToolButtonStyle"); d->toolButtonStyleSettings[Level_UserSettings] = Unset; } else { cg.writeEntry("ToolButtonStyle", d->toolButtonStyleToString(currentToolButtonStyle)); d->toolButtonStyleSettings[Level_UserSettings] = currentToolButtonStyle; } } void KToolBar::addXMLGUIClient(KXMLGUIClient *client) { d->xmlguiClients << client; } void KToolBar::removeXMLGUIClient(KXMLGUIClient *client) { d->xmlguiClients.remove(client); } void KToolBar::contextMenuEvent(QContextMenuEvent *event) { QToolBar::contextMenuEvent(event); } void KToolBar::loadState(const QDomElement &element) { QMainWindow *mw = mainWindow(); if (!mw) { return; } { QDomNode textNode = element.namedItem(QStringLiteral("text")); QByteArray domain; QByteArray text; QByteArray context; if (textNode.isElement()) { QDomElement textElement = textNode.toElement(); domain = textElement.attribute(QStringLiteral("translationDomain")).toUtf8(); text = textElement.text().toUtf8(); context = textElement.attribute(QStringLiteral("context")).toUtf8(); } else { textNode = element.namedItem(QStringLiteral("Text")); if (textNode.isElement()) { QDomElement textElement = textNode.toElement(); domain = textElement.attribute(QStringLiteral("translationDomain")).toUtf8(); text = textElement.text().toUtf8(); context = textElement.attribute(QStringLiteral("context")).toUtf8(); } } if (domain.isEmpty()) { domain = element.ownerDocument().documentElement().attribute(QStringLiteral("translationDomain")).toUtf8(); if (domain.isEmpty()) { domain = KLocalizedString::applicationDomain(); } } QString i18nText; if (!text.isEmpty() && !context.isEmpty()) { i18nText = i18ndc(domain.constData(), context.constData(), text.constData()); } else if (!text.isEmpty()) { i18nText = i18nd(domain.constData(), text.constData()); } if (!i18nText.isEmpty()) { setWindowTitle(i18nText); } } /* This method is called in order to load toolbar settings from XML. However this can be used in two rather different cases: - for the initial loading of the app's XML. In that case the settings are only the defaults (Level_AppXML), the user's KConfig settings will override them - for later re-loading when switching between parts in KXMLGUIFactory. In that case the XML contains the final settings, not the defaults. We do need the defaults, and the toolbar might have been completely deleted and recreated meanwhile. So we store the app-default settings into the XML. */ bool loadingAppDefaults = true; if (element.hasAttribute(QStringLiteral("tempXml"))) { // this isn't the first time, so the app-xml defaults have been saved into the (in-memory) XML loadingAppDefaults = false; const QString iconSizeDefault = element.attribute(QStringLiteral("iconSizeDefault")); if (!iconSizeDefault.isEmpty()) { d->iconSizeSettings[Level_AppXML] = iconSizeDefault.toInt(); } const QString toolButtonStyleDefault = element.attribute(QStringLiteral("toolButtonStyleDefault")); if (!toolButtonStyleDefault.isEmpty()) { d->toolButtonStyleSettings[Level_AppXML] = d->toolButtonStyleFromString(toolButtonStyleDefault); } } else { // loading app defaults bool newLine = false; QString attrNewLine = element.attribute(QStringLiteral("newline")).toLower(); if (!attrNewLine.isEmpty()) { newLine = attrNewLine == QStringLiteral("true"); } if (newLine && mw) { mw->insertToolBarBreak(this); } } int newIconSize = -1; if (element.hasAttribute(QStringLiteral("iconSize"))) { bool ok; newIconSize = element.attribute(QStringLiteral("iconSize")).trimmed().toInt(&ok); if (!ok) { newIconSize = -1; } } if (newIconSize != -1) { d->iconSizeSettings[loadingAppDefaults ? Level_AppXML : Level_UserSettings] = newIconSize; } const QString newToolButtonStyle = element.attribute(QStringLiteral("iconText")); if (!newToolButtonStyle.isEmpty()) { d->toolButtonStyleSettings[loadingAppDefaults ? Level_AppXML : Level_UserSettings] = d->toolButtonStyleFromString(newToolButtonStyle); } bool hidden = false; { QString attrHidden = element.attribute(QStringLiteral("hidden")).toLower(); if (!attrHidden.isEmpty()) { hidden = attrHidden == QStringLiteral("true"); } } Qt::ToolBarArea pos = Qt::NoToolBarArea; { QString attrPosition = element.attribute(QStringLiteral("position")).toLower(); if (!attrPosition.isEmpty()) { pos = KToolBar::Private::positionFromString(attrPosition); } } if (pos != Qt::NoToolBarArea) { mw->addToolBar(pos, this); } setVisible(!hidden); d->applyCurrentSettings(); } // Called when switching between xmlgui clients, in order to find any unsaved settings // again when switching back to the current xmlgui client. void KToolBar::saveState(QDomElement ¤t) const { Q_ASSERT(!current.isNull()); current.setAttribute(QStringLiteral("tempXml"), QLatin1String("true")); current.setAttribute(QStringLiteral("noMerge"), QLatin1String("1")); current.setAttribute(QStringLiteral("position"), d->getPositionAsString().toLower()); current.setAttribute(QStringLiteral("hidden"), isHidden() ? QLatin1String("true") : QLatin1String("false")); const int currentIconSize = iconSize().width(); if (currentIconSize == d->iconSizeSettings.defaultValue()) { current.removeAttribute(QStringLiteral("iconSize")); } else { current.setAttribute(QStringLiteral("iconSize"), iconSize().width()); } if (toolButtonStyle() == d->toolButtonStyleSettings.defaultValue()) { current.removeAttribute(QStringLiteral("iconText")); } else { current.setAttribute(QStringLiteral("iconText"), d->toolButtonStyleToString(toolButtonStyle())); } // Note: if this method is used by more than KXMLGUIBuilder, e.g. to save XML settings to *disk*, // then the stuff below shouldn't always be done. This is not the case currently though. if (d->iconSizeSettings[Level_AppXML] != Unset) { current.setAttribute(QStringLiteral("iconSizeDefault"), d->iconSizeSettings[Level_AppXML]); } if (d->toolButtonStyleSettings[Level_AppXML] != Unset) { const Qt::ToolButtonStyle bs = static_cast(d->toolButtonStyleSettings[Level_AppXML]); current.setAttribute(QStringLiteral("toolButtonStyleDefault"), d->toolButtonStyleToString(bs)); } } // called by KMainWindow::applyMainWindowSettings to read from the user settings void KToolBar::applySettings(const KConfigGroup &cg) { Q_ASSERT(!cg.name().isEmpty()); if (cg.hasKey("IconSize")) { d->iconSizeSettings[Level_UserSettings] = cg.readEntry("IconSize", 0); } if (cg.hasKey("ToolButtonStyle")) { d->toolButtonStyleSettings[Level_UserSettings] = d->toolButtonStyleFromString(cg.readEntry("ToolButtonStyle", QString())); } d->applyCurrentSettings(); } KMainWindow *KToolBar::mainWindow() const { return qobject_cast(const_cast(parent())); } void KToolBar::setIconDimensions(int size) { QToolBar::setIconSize(QSize(size, size)); d->iconSizeSettings[Level_UserSettings] = size; } int KToolBar::iconSizeDefault() const { return 22; } void KToolBar::slotMovableChanged(bool movable) { if (movable && !KAuthorized::authorize(QStringLiteral("movable_toolbars"))) { setMovable(false); } } void KToolBar::dragEnterEvent(QDragEnterEvent *event) { if (toolBarsEditable() && event->proposedAction() & (Qt::CopyAction | Qt::MoveAction) && event->mimeData()->hasFormat(QStringLiteral("application/x-kde-action-list"))) { QByteArray data = event->mimeData()->data(QStringLiteral("application/x-kde-action-list")); QDataStream stream(data); QStringList actionNames; stream >> actionNames; Q_FOREACH (const QString &actionName, actionNames) { Q_FOREACH (KActionCollection *ac, KActionCollection::allCollections()) { QAction *newAction = ac->action(actionName); if (newAction) { d->actionsBeingDragged.append(newAction); break; } } } if (d->actionsBeingDragged.count()) { QAction *overAction = actionAt(event->pos()); QFrame *dropIndicatorWidget = new QFrame(this); dropIndicatorWidget->resize(8, height() - 4); dropIndicatorWidget->setFrameShape(QFrame::VLine); dropIndicatorWidget->setLineWidth(3); d->dropIndicatorAction = insertWidget(overAction, dropIndicatorWidget); insertAction(overAction, d->dropIndicatorAction); event->acceptProposedAction(); return; } } QToolBar::dragEnterEvent(event); } void KToolBar::dragMoveEvent(QDragMoveEvent *event) { if (toolBarsEditable()) Q_FOREVER { if (d->dropIndicatorAction) { QAction *overAction = 0L; Q_FOREACH (QAction *action, actions()) { // want to make it feel that half way across an action you're dropping on the other side of it QWidget *widget = widgetForAction(action); if (event->pos().x() < widget->pos().x() + (widget->width() / 2)) { overAction = action; break; } } if (overAction != d->dropIndicatorAction) { // Check to see if the indicator is already in the right spot int dropIndicatorIndex = actions().indexOf(d->dropIndicatorAction); if (dropIndicatorIndex + 1 < actions().count()) { if (actions()[ dropIndicatorIndex + 1 ] == overAction) { break; } } else if (!overAction) { break; } insertAction(overAction, d->dropIndicatorAction); } event->accept(); return; } break; } QToolBar::dragMoveEvent(event); } void KToolBar::dragLeaveEvent(QDragLeaveEvent *event) { // Want to clear this even if toolBarsEditable was changed mid-drag (unlikey) delete d->dropIndicatorAction; d->dropIndicatorAction = 0L; d->actionsBeingDragged.clear(); if (toolBarsEditable()) { event->accept(); return; } QToolBar::dragLeaveEvent(event); } void KToolBar::dropEvent(QDropEvent *event) { if (toolBarsEditable()) { Q_FOREACH (QAction *action, d->actionsBeingDragged) { if (actions().contains(action)) { removeAction(action); } insertAction(d->dropIndicatorAction, action); } } // Want to clear this even if toolBarsEditable was changed mid-drag (unlikey) delete d->dropIndicatorAction; d->dropIndicatorAction = 0L; d->actionsBeingDragged.clear(); if (toolBarsEditable()) { event->accept(); return; } QToolBar::dropEvent(event); } void KToolBar::mousePressEvent(QMouseEvent *event) { if (toolBarsEditable() && event->button() == Qt::LeftButton) { if (QAction *action = actionAt(event->pos())) { d->dragAction = action; d->dragStartPosition = event->pos(); event->accept(); return; } } QToolBar::mousePressEvent(event); } void KToolBar::mouseMoveEvent(QMouseEvent *event) { if (!toolBarsEditable() || !d->dragAction) { return QToolBar::mouseMoveEvent(event); } if ((event->pos() - d->dragStartPosition).manhattanLength() < QApplication::startDragDistance()) { event->accept(); return; } QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; QByteArray data; { QDataStream stream(&data, QIODevice::WriteOnly); QStringList actionNames; actionNames << d->dragAction->objectName(); stream << actionNames; } mimeData->setData(QStringLiteral("application/x-kde-action-list"), data); drag->setMimeData(mimeData); Qt::DropAction dropAction = drag->start(Qt::MoveAction); if (dropAction == Qt::MoveAction) // Only remove from this toolbar if it was moved to another toolbar // Otherwise the receiver moves it. if (drag->target() != this) { removeAction(d->dragAction); } d->dragAction = 0L; event->accept(); } void KToolBar::mouseReleaseEvent(QMouseEvent *event) { // Want to clear this even if toolBarsEditable was changed mid-drag (unlikey) if (d->dragAction) { d->dragAction = 0L; event->accept(); return; } QToolBar::mouseReleaseEvent(event); } bool KToolBar::eventFilter(QObject *watched, QEvent *event) { // Generate context menu events for disabled buttons too... if (event->type() == QEvent::MouseButtonPress) { QMouseEvent *me = static_cast(event); if (me->buttons() & Qt::RightButton) if (QWidget *ww = qobject_cast(watched)) if (ww->parent() == this && !ww->isEnabled()) { QCoreApplication::postEvent(this, new QContextMenuEvent(QContextMenuEvent::Mouse, me->pos(), me->globalPos())); } } else if (event->type() == QEvent::ParentChange) { // Make sure we're not leaving stale event filters around, // when a child is reparented somewhere else if (QWidget *ww = qobject_cast(watched)) { if (!this->isAncestorOf(ww)) { // New parent is not a subwidget - remove event filter ww->removeEventFilter(this); Q_FOREACH (QWidget *child, ww->findChildren()) { child->removeEventFilter(this); } } } } QToolButton *tb; if ((tb = qobject_cast(watched))) { const QList tbActions = tb->actions(); if (!tbActions.isEmpty()) { // Handle MMB on toolbar buttons if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { QMouseEvent *me = static_cast(event); if (me->button() == Qt::MidButton /*&& act->receivers(SIGNAL(triggered(Qt::MouseButtons,Qt::KeyboardModifiers)))*/) { QAction *act = tbActions.first(); if (me->type() == QEvent::MouseButtonPress) { tb->setDown(act->isEnabled()); } else { tb->setDown(false); if (act->isEnabled()) { QMetaObject::invokeMethod(act, "triggered", Qt::DirectConnection, Q_ARG(Qt::MouseButtons, me->button()), Q_ARG(Qt::KeyboardModifiers, QApplication::keyboardModifiers())); } } } } // CJK languages use more verbose accelerator marker: they add a Latin // letter in parenthesis, and put accelerator on that. Hence, the default // removal of ampersand only may not be enough there, instead the whole // parenthesis construct should be removed. Use KLocalizedString's method to do this. if (event->type() == QEvent::Show || event->type() == QEvent::Paint || event->type() == QEvent::EnabledChange) { QAction *act = tb->defaultAction(); if (act) { const QString text = KLocalizedString::removeAcceleratorMarker(act->iconText().isEmpty() ? act->text() : act->iconText()); const QString toolTip = KLocalizedString::removeAcceleratorMarker(act->toolTip()); // Filtering messages requested by translators (scripting). tb->setText(i18nc("@action:intoolbar Text label of toolbar button", "%1", text)); tb->setToolTip(i18nc("@info:tooltip Tooltip of toolbar button", "%1", toolTip)); } } } } // Redirect mouse events to the toolbar when drag + drop editing is enabled if (toolBarsEditable()) { if (QWidget *ww = qobject_cast(watched)) { switch (event->type()) { case QEvent::MouseButtonPress: { QMouseEvent *me = static_cast(event); QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(), me->button(), me->buttons(), me->modifiers()); mousePressEvent(&newEvent); return true; } case QEvent::MouseMove: { QMouseEvent *me = static_cast(event); QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(), me->button(), me->buttons(), me->modifiers()); mouseMoveEvent(&newEvent); return true; } case QEvent::MouseButtonRelease: { QMouseEvent *me = static_cast(event); QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(), me->button(), me->buttons(), me->modifiers()); mouseReleaseEvent(&newEvent); return true; } default: break; } } } return QToolBar::eventFilter(watched, event); } void KToolBar::actionEvent(QActionEvent *event) { if (event->type() == QEvent::ActionRemoved) { QWidget *widget = widgetForAction(event->action()); if (widget) { widget->removeEventFilter(this); Q_FOREACH (QWidget *child, widget->findChildren()) { child->removeEventFilter(this); } } } QToolBar::actionEvent(event); if (event->type() == QEvent::ActionAdded) { QWidget *widget = widgetForAction(event->action()); if (widget) { widget->installEventFilter(this); Q_FOREACH (QWidget *child, widget->findChildren()) { child->installEventFilter(this); } // Center widgets that do not have any use for more space. See bug 165274 if (!(widget->sizePolicy().horizontalPolicy() & QSizePolicy::GrowFlag) // ... but do not center when using text besides icon in vertical toolbar. See bug 243196 && !(orientation() == Qt::Vertical && toolButtonStyle() == Qt::ToolButtonTextBesideIcon)) { const int index = layout()->indexOf(widget); if (index != -1) { layout()->itemAt(index)->setAlignment(Qt::AlignJustify); } } } } d->adjustSeparatorVisibility(); } bool KToolBar::toolBarsEditable() { return KToolBar::Private::s_editable; } void KToolBar::setToolBarsEditable(bool editable) { if (KToolBar::Private::s_editable != editable) { KToolBar::Private::s_editable = editable; } } void KToolBar::setToolBarsLocked(bool locked) { if (KToolBar::Private::s_locked != locked) { KToolBar::Private::s_locked = locked; Q_FOREACH (KMainWindow *mw, KMainWindow::memberList()) { Q_FOREACH (KToolBar *toolbar, mw->findChildren()) { toolbar->d->setLocked(locked); } } } } bool KToolBar::toolBarsLocked() { return KToolBar::Private::s_locked; } void KToolBar::emitToolbarStyleChanged() { #ifdef HAVE_DBUS QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KToolBar"), QStringLiteral("org.kde.KToolBar"), QStringLiteral("styleChanged")); QDBusConnection::sessionBus().send(message); #endif } #include "moc_ktoolbar.cpp" diff --git a/plugins/assistants/RulerAssistant/PerspectiveAssistant.cc b/plugins/assistants/RulerAssistant/PerspectiveAssistant.cc index d837ba6033..837ee546a7 100644 --- a/plugins/assistants/RulerAssistant/PerspectiveAssistant.cc +++ b/plugins/assistants/RulerAssistant/PerspectiveAssistant.cc @@ -1,420 +1,420 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song * * 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; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "PerspectiveAssistant.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include #include PerspectiveAssistant::PerspectiveAssistant(QObject *parent) : KisAbstractPerspectiveGrid(parent) , KisPaintingAssistant("perspective", i18n("Perspective assistant")) { } // squared distance from a point to a line inline qreal distsqr(const QPointF& pt, const QLineF& line) { // distance = |(p2 - p1) x (p1 - pt)| / |p2 - p1| // magnitude of (p2 - p1) x (p1 - pt) const qreal cross = (line.dx() * (line.y1() - pt.y()) - line.dy() * (line.x1() - pt.x())); return cross * cross / (line.dx() * line.dx() + line.dy() * line.dy()); } QPointF PerspectiveAssistant::project(const QPointF& pt, const QPointF& strokeBegin) { const static QPointF nullPoint(std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()); Q_ASSERT(handles().size() == 4); if (m_snapLine.isNull()) { QPolygonF poly; QTransform transform; if (!getTransform(poly, transform)) return nullPoint; // avoid problems with multiple assistants: only snap if starting in the grid if (!poly.containsPoint(strokeBegin, Qt::OddEvenFill)) return nullPoint; const qreal dx = pt.x() - strokeBegin.x(), dy = pt.y() - strokeBegin.y(); if (dx * dx + dy * dy < 4.0) { // allow some movement before snapping return strokeBegin; } // construct transformation bool invertible; const QTransform inverse = transform.inverted(&invertible); if (!invertible) return nullPoint; // shouldn't happen // figure out which direction to go const QPointF start = inverse.map(strokeBegin); const QLineF verticalLine = QLineF(strokeBegin, transform.map(start + QPointF(0, 1))), horizontalLine = QLineF(strokeBegin, transform.map(start + QPointF(1, 0))); // determine whether the horizontal or vertical line is closer to the point m_snapLine = distsqr(pt, verticalLine) < distsqr(pt, horizontalLine) ? verticalLine : horizontalLine; } // snap to line const qreal dx = m_snapLine.dx(), dy = m_snapLine.dy(), dx2 = dx * dx, dy2 = dy * dy, invsqrlen = 1.0 / (dx2 + dy2); QPointF r(dx2 * pt.x() + dy2 * m_snapLine.x1() + dx * dy * (pt.y() - m_snapLine.y1()), dx2 * m_snapLine.y1() + dy2 * pt.y() + dx * dy * (pt.x() - m_snapLine.x1())); r *= invsqrlen; return r; } QPointF PerspectiveAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin) { return project(pt, strokeBegin); } void PerspectiveAssistant::endStroke() { m_snapLine = QLineF(); } bool PerspectiveAssistant::contains(const QPointF& pt) const { QPolygonF poly; if (!quad(poly)) return false; return poly.containsPoint(pt, Qt::OddEvenFill); } inline qreal lengthSquared(const QPointF& vector) { return vector.x() * vector.x() + vector.y() * vector.y(); } inline qreal localScale(const QTransform& transform, QPointF pt) { // const qreal epsilon = 1e-5, epsilonSquared = epsilon * epsilon; // qreal xSizeSquared = lengthSquared(transform.map(pt + QPointF(epsilon, 0.0)) - orig) / epsilonSquared; // qreal ySizeSquared = lengthSquared(transform.map(pt + QPointF(0.0, epsilon)) - orig) / epsilonSquared; // xSizeSquared /= lengthSquared(transform.map(QPointF(0.0, pt.y())) - transform.map(QPointF(1.0, pt.y()))); // ySizeSquared /= lengthSquared(transform.map(QPointF(pt.x(), 0.0)) - transform.map(QPointF(pt.x(), 1.0))); // when taking the limit epsilon->0: // xSizeSquared=((m23*y+m33)^2*(m23*y+m33+m13)^2)/(m23*y+m13*x+m33)^4 // ySizeSquared=((m23*y+m33)^2*(m23*y+m33+m13)^2)/(m23*y+m13*x+m33)^4 // xSize*ySize=(abs(m13*x+m33)*abs(m13*x+m33+m23)*abs(m23*y+m33)*abs(m23*y+m33+m13))/(m23*y+m13*x+m33)^4 const qreal x = transform.m13() * pt.x(), y = transform.m23() * pt.y(), a = x + transform.m33(), b = y + transform.m33(), c = x + y + transform.m33(), d = c * c; return fabs(a*(a + transform.m23())*b*(b + transform.m13()))/(d * d); } // returns the reciprocal of the maximum local scale at the points (0,0),(0,1),(1,0),(1,1) inline qreal inverseMaxLocalScale(const QTransform& transform) { const qreal a = fabs((transform.m33() + transform.m13()) * (transform.m33() + transform.m23())), b = fabs((transform.m33()) * (transform.m13() + transform.m33() + transform.m23())), d00 = transform.m33() * transform.m33(), d11 = (transform.m33() + transform.m23() + transform.m13())*(transform.m33() + transform.m23() + transform.m13()), s0011 = qMin(d00, d11) / a, d10 = (transform.m33() + transform.m13()) * (transform.m33() + transform.m13()), d01 = (transform.m33() + transform.m23()) * (transform.m33() + transform.m23()), s1001 = qMin(d10, d01) / b; return qMin(s0011, s1001); } qreal PerspectiveAssistant::distance(const QPointF& pt) const { QPolygonF poly; QTransform transform; if (!getTransform(poly, transform)) return 1.0; bool invertible; QTransform inverse = transform.inverted(&invertible); if (!invertible) return 1.0; if (inverse.m13() * pt.x() + inverse.m23() * pt.y() + inverse.m33() == 0.0) { // point at infinity return 0.0; } return localScale(transform, inverse.map(pt)) * inverseMaxLocalScale(transform); } // draw a vanishing point marker inline QPainterPath drawX(const QPointF& pt) { QPainterPath path; path.moveTo(QPointF(pt.x() - 5.0, pt.y() - 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() + 5.0)); path.moveTo(QPointF(pt.x() - 5.0, pt.y() + 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() - 5.0)); return path; } void PerspectiveAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible) { gc.save(); gc.resetTransform(); QTransform initialTransform = converter->documentToWidgetTransform(); //QTransform reverseTransform = converter->widgetToDocument(); QPolygonF poly; QTransform transform; // unused, but computed for caching purposes if (getTransform(poly, transform) && assistantVisible==true) { // draw vanishing points QPointF intersection(0, 0); if (fmod(QLineF(poly[0], poly[1]).angle(), 180.0)>=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)+2.0 || fmod(QLineF(poly[0], poly[1]).angle(), 180.0)<=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)-2.0) { if (QLineF(poly[0], poly[1]).intersect(QLineF(poly[2], poly[3]), &intersection) != QLineF::NoIntersection) { drawPath(gc, drawX(initialTransform.map(intersection))); } } if (fmod(QLineF(poly[1], poly[2]).angle(), 180.0)>=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)+2.0 || fmod(QLineF(poly[1], poly[2]).angle(), 180.0)<=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)-2.0){ if (QLineF(poly[1], poly[2]).intersect(QLineF(poly[3], poly[0]), &intersection) != QLineF::NoIntersection) { drawPath(gc, drawX(initialTransform.map(intersection))); } } } if (outline()==true && getTransform(poly, transform) && previewVisible==true){ //find vanishing point, find mouse, draw line between both. QPainterPath path2; QPointF intersection(0, 0);//this is the position of the vanishing point. QPointF mousePos(0,0); QLineF snapLine; QRect viewport= gc.viewport(); QRect bounds; if (canvas){ //simplest, cheapest way to get the mouse-position// mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos()); } else { //...of course, you need to have access to a canvas-widget for that.// mousePos = QCursor::pos();//this'll give an offset// dbgFile<<"canvas does not exist, you may have passed arguments incorrectly:"<=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)+2.0 || fmod(QLineF(poly[0], poly[1]).angle(), 180.0)<=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)-2.0) { if (QLineF(poly[0], poly[1]).intersect(QLineF(poly[2], poly[3]), &intersection) != QLineF::NoIntersection) { intersectTransformed = initialTransform.map(intersection); snapLine = QLineF(intersectTransformed, mousePos); KisAlgebra2D::intersectLineRect(snapLine, viewport); bounds= QRect(snapLine.p1().toPoint(), snapLine.p2().toPoint()); QPainterPath path; if (bounds.contains(intersectTransformed.toPoint())){ path2.moveTo(intersectTransformed); path2.lineTo(snapLine.p1()); } else { path2.moveTo(snapLine.p1()); path2.lineTo(snapLine.p2()); } } } if (fmod(QLineF(poly[1], poly[2]).angle(), 180.0)>=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)+2.0 || fmod(QLineF(poly[1], poly[2]).angle(), 180.0)<=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)-2.0){ if (QLineF(poly[1], poly[2]).intersect(QLineF(poly[3], poly[0]), &intersection) != QLineF::NoIntersection) { intersectTransformed = initialTransform.map(intersection); snapLine = QLineF(intersectTransformed, mousePos); KisAlgebra2D::intersectLineRect(snapLine, viewport); bounds= QRect(snapLine.p1().toPoint(), snapLine.p2().toPoint()); QPainterPath path; if (bounds.contains(intersectTransformed.toPoint())){ path2.moveTo(intersectTransformed); path2.lineTo(snapLine.p1()); } else { path2.moveTo(snapLine.p1()); path2.lineTo(snapLine.p2()); } } } drawPreview(gc, path2); } } gc.restore(); KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached,canvas, assistantVisible, previewVisible); } void PerspectiveAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible) { if (assistantVisible==false) { return; } gc.setTransform(converter->documentToWidgetTransform()); QPolygonF poly; QTransform transform; if (!getTransform(poly, transform)) { // color red for an invalid transform, but not for an incomplete one if(handles().size() == 4) { gc.setPen(QColor(255, 0, 0, 125)); gc.drawPolygon(poly); } else { QPainterPath path; path.addPolygon(poly); drawPath(gc, path, snapping()); } } else { gc.setPen(QColor(0, 0, 0, 125)); gc.setTransform(transform, true); QPainterPath path; for (int y = 0; y <= 8; ++y) { path.moveTo(QPointF(0.0, y * 0.125)); path.lineTo(QPointF(1.0, y * 0.125)); } for (int x = 0; x <= 8; ++x) { path.moveTo(QPointF(x * 0.125, 0.0)); path.lineTo(QPointF(x * 0.125, 1.0)); } drawPath(gc, path, snapping()); } } QPointF PerspectiveAssistant::buttonPosition() const { QPointF centroid(0, 0); for (int i = 0; i < 4; ++i) centroid += *handles()[i]; return centroid * 0.25; } template int sign(T a) { return (a > 0) - (a < 0); } // perpendicular dot product inline qreal pdot(const QPointF& a, const QPointF& b) { return a.x() * b.y() - a.y() * b.x(); } bool PerspectiveAssistant::quad(QPolygonF& poly) const { for (int i = 0; i < handles().size(); ++i) poly.push_back(*handles()[i]); if (handles().size() != 4) { return false; } int sum = 0; int signs[4]; for (int i = 0; i < 4; ++i) { int j = (i == 3) ? 0 : (i + 1); int k = (j == 3) ? 0 : (j + 1); signs[i] = sign(pdot(poly[j] - poly[i], poly[k] - poly[j])); sum += signs[i]; } if (sum == 0) { // complex (crossed) for (int i = 0; i < 4; ++i) { int j = (i == 3) ? 0 : (i + 1); if (signs[i] * signs[j] == -1) { // opposite signs: uncross - qSwap(poly[i], poly[j]); + std::swap(poly[i], poly[j]); return true; } } // okay, maybe it's just a line return false; } else if (sum != 4 && sum != -4) { // concave, or a triangle if (sum == 2 || sum == -2) { // concave, let's return a triangle instead for (int i = 0; i < 4; ++i) { int j = (i == 3) ? 0 : (i + 1); if (signs[i] != sign(sum)) { // wrong sign: drop the inside node poly.remove(j); return false; } } } return false; } // convex return true; } bool PerspectiveAssistant::getTransform(QPolygonF& poly, QTransform& transform) const { if (m_cachedPolygon.size() != 0 && handles().size() == 4) { for (int i = 0; i <= 4; ++i) { if (i == 4) { poly = m_cachedPolygon; transform = m_cachedTransform; return m_cacheValid; } if (m_cachedPoints[i] != *handles()[i]) break; } } m_cachedPolygon.clear(); m_cacheValid = false; if (!quad(poly)) { m_cachedPolygon = poly; return false; } if (!QTransform::squareToQuad(poly, transform)) { qWarning("Failed to create perspective mapping"); return false; } for (int i = 0; i < 4; ++i) { m_cachedPoints[i] = *handles()[i]; } m_cachedPolygon = poly; m_cachedTransform = transform; m_cacheValid = true; return true; } PerspectiveAssistantFactory::PerspectiveAssistantFactory() { } PerspectiveAssistantFactory::~PerspectiveAssistantFactory() { } QString PerspectiveAssistantFactory::id() const { return "perspective"; } QString PerspectiveAssistantFactory::name() const { return i18n("Perspective"); } KisPaintingAssistant* PerspectiveAssistantFactory::createPaintingAssistant() const { return new PerspectiveAssistant; } diff --git a/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc b/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc index 4fb7553892..4740a69f4e 100644 --- a/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc +++ b/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc @@ -1,914 +1,914 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song * * 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; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_global.h" #include KisRulerAssistantTool::KisRulerAssistantTool(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::arrowCursor()), m_canvas(dynamic_cast(canvas)), m_assistantDrag(0), m_newAssistant(0), m_optionsWidget(0), m_handleSize(32), m_handleHalfSize(16) { Q_ASSERT(m_canvas); setObjectName("tool_rulerassistanttool"); } KisRulerAssistantTool::~KisRulerAssistantTool() { } QPointF adjustPointF(const QPointF& _pt, const QRectF& _rc) { return QPointF(qBound(_rc.left(), _pt.x(), _rc.right()), qBound(_rc.top(), _pt.y(), _rc.bottom())); } void KisRulerAssistantTool::activate(ToolActivation toolActivation, const QSet &shapes) { // Add code here to initialize your tool when it got activated KisTool::activate(toolActivation, shapes); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->paintingAssistantsDecoration()->setVisible(true); m_canvas->updateCanvas(); m_handleDrag = 0; m_internalMode = MODE_CREATION; m_assistantHelperYOffset = 10; } void KisRulerAssistantTool::deactivate() { // Add code here to initialize your tool when it got deactivated m_canvas->updateCanvas(); KisTool::deactivate(); } bool KisRulerAssistantTool::mouseNear(const QPointF& mousep, const QPointF& point) { QRectF handlerect(point-QPointF(m_handleHalfSize,m_handleHalfSize), QSizeF(m_handleSize, m_handleSize)); return handlerect.contains(mousep); } KisPaintingAssistantHandleSP KisRulerAssistantTool::nodeNearPoint(KisPaintingAssistantSP grid, QPointF point) { if (mouseNear(point, pixelToView(*grid->topLeft()))) { return grid->topLeft(); } else if (mouseNear(point, pixelToView(*grid->topRight()))) { return grid->topRight(); } else if (mouseNear(point, pixelToView(*grid->bottomLeft()))) { return grid->bottomLeft(); } else if (mouseNear(point, pixelToView(*grid->bottomRight()))) { return grid->bottomRight(); } return 0; } inline double norm2(const QPointF& p) { return p.x() * p.x() + p.y() * p.y(); } void KisRulerAssistantTool::beginPrimaryAction(KoPointerEvent *event) { setMode(KisTool::PAINT_MODE); bool newAssistantAllowed = true; if (m_newAssistant) { m_internalMode = MODE_CREATION; *m_newAssistant->handles().back() = snapToGuide(event, QPointF(), false); if (m_newAssistant->handles().size() == m_newAssistant->numHandles()) { addAssistant(); } else { m_newAssistant->addHandle(new KisPaintingAssistantHandle(snapToGuide(event, QPointF(), false))); } m_canvas->updateCanvas(); return; } m_handleDrag = 0; double minDist = 81.0; QPointF mousePos = m_canvas->viewConverter()->documentToView(snapToGuide(event, QPointF(), false));//m_canvas->viewConverter()->documentToView(event->point); Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle)); if (dist < minDist) { minDist = dist; m_handleDrag = handle; } } if(m_handleDrag && assistant->id() == "perspective") { // Look for the handle which was pressed if (m_handleDrag == assistant->topLeft()) { double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_dragStart = QPointF(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_internalMode = MODE_DRAGGING_NODE; } else if (m_handleDrag == assistant->topRight()) { double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); } else if (m_handleDrag == assistant->bottomLeft()) { double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); } else if (m_handleDrag == assistant->bottomRight()) { double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); } else if (m_handleDrag == assistant->leftMiddle()) { m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->topLeft().data()->x())*0.5, (assistant->bottomLeft().data()->y()+assistant->topLeft().data()->y())*0.5); m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->topLeft()); m_newAssistant->addHandle(m_selectedNode1); m_newAssistant->addHandle(m_selectedNode2); m_newAssistant->addHandle(assistant->bottomLeft()); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->rightMiddle()) { m_dragStart = QPointF((assistant->topRight().data()->x()+assistant->bottomRight().data()->x())*0.5, (assistant->topRight().data()->y()+assistant->bottomRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->topRight()); m_newAssistant->addHandle(m_selectedNode1); m_newAssistant->addHandle(m_selectedNode2); m_newAssistant->addHandle(assistant->bottomRight()); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->topMiddle()) { m_dragStart = QPointF((assistant->topLeft().data()->x()+assistant->topRight().data()->x())*0.5, (assistant->topLeft().data()->y()+assistant->topRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(m_selectedNode1); m_newAssistant->addHandle(m_selectedNode2); m_newAssistant->addHandle(assistant->topRight()); m_newAssistant->addHandle(assistant->topLeft()); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->bottomMiddle()) { m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->bottomRight().data()->x())*0.5, (assistant->bottomLeft().data()->y()+assistant->bottomRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->bottomLeft()); m_newAssistant->addHandle(assistant->bottomRight()); m_newAssistant->addHandle(m_selectedNode2); m_newAssistant->addHandle(m_selectedNode1); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } m_snapIsRadial = false; } else if (m_handleDrag && assistant->handles().size()>1 && (assistant->id() == "ruler" || assistant->id() == "parallel ruler" || assistant->id() == "infinite ruler" || assistant->id() == "spline")){ if (m_handleDrag == assistant->handles()[0]) { m_dragStart = *assistant->handles()[1]; } else if (m_handleDrag == assistant->handles()[1]) { m_dragStart = *assistant->handles()[0]; } else if(assistant->handles().size()==4){ if (m_handleDrag == assistant->handles()[2]) { m_dragStart = *assistant->handles()[0]; } else if (m_handleDrag == assistant->handles()[3]) { m_dragStart = *assistant->handles()[1]; } } m_snapIsRadial = false; } else if (m_handleDrag && assistant->handles().size()>2 && (assistant->id() == "ellipse" || assistant->id() == "concentric ellipse" || assistant->id() == "fisheye-point")){ m_snapIsRadial = false; if (m_handleDrag == assistant->handles()[0]) { m_dragStart = *assistant->handles()[1]; } else if (m_handleDrag == assistant->handles()[1]) { m_dragStart = *assistant->handles()[0]; } else if (m_handleDrag == assistant->handles()[2]) { m_dragStart = assistant->buttonPosition(); m_radius = QLineF(m_dragStart, *assistant->handles()[0]); m_snapIsRadial = true; } } else { m_dragStart = assistant->buttonPosition(); m_snapIsRadial = false; } } if (m_handleDrag) { // TODO: Shift-press should now be handled using the alternate actions // if (event->modifiers() & Qt::ShiftModifier) { // m_handleDrag->uncache(); // m_handleDrag = m_handleDrag->split()[0]; // m_handles = m_canvas->view()->paintingAssistantsDecoration()->handles(); // } m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } m_assistantDrag.clear(); Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { // This code contains the click event behavior. The actual display of the icons are done at the bottom // of the paint even. Make sure the rectangles positions are the same between the two. // TODO: These 6 lines are duplicated below in the paint layer. It shouldn't be done like this. QPointF actionsPosition = m_canvas->viewConverter()->documentToView(assistant->buttonPosition()); QPointF iconDeletePosition(actionsPosition + QPointF(78, m_assistantHelperYOffset + 7)); QPointF iconSnapPosition(actionsPosition + QPointF(54, m_assistantHelperYOffset + 7)); QPointF iconMovePosition(actionsPosition + QPointF(15, m_assistantHelperYOffset)); QRectF deleteRect(iconDeletePosition, QSizeF(16, 16)); QRectF visibleRect(iconSnapPosition, QSizeF(16, 16)); QRectF moveRect(iconMovePosition, QSizeF(32, 32)); if (moveRect.contains(mousePos)) { m_assistantDrag = assistant; m_cursorStart = event->point; m_currentAdjustment = QPointF(); m_internalMode = MODE_EDITING; return; } if (deleteRect.contains(mousePos)) { removeAssistant(assistant); if(m_canvas->paintingAssistantsDecoration()->assistants().isEmpty()) { m_internalMode = MODE_CREATION; } else m_internalMode = MODE_EDITING; m_canvas->updateCanvas(); return; } if (visibleRect.contains(mousePos)) { newAssistantAllowed = false; if (assistant->snapping()==true){ snappingOff(assistant); outlineOff(assistant); } else{ snappingOn(assistant); outlineOn(assistant); } assistant->uncache();//this updates the chache of the assistant, very important. } } if (newAssistantAllowed==true){//don't make a new assistant when I'm just toogling visiblity// QString key = m_options.comboBox->model()->index( m_options.comboBox->currentIndex(), 0 ).data(Qt::UserRole).toString(); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get(key)->createPaintingAssistant()); m_internalMode = MODE_CREATION; m_newAssistant->addHandle(new KisPaintingAssistantHandle(snapToGuide(event, QPointF(), false))); if (m_newAssistant->numHandles() <= 1) { if (key == "vanishing point"){ m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(-70,0))); m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(-140,0))); m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(70,0))); m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(140,0))); } addAssistant(); } else { m_newAssistant->addHandle(new KisPaintingAssistantHandle(snapToGuide(event, QPointF(), false))); } } m_canvas->updateCanvas(); } void KisRulerAssistantTool::continuePrimaryAction(KoPointerEvent *event) { if (m_handleDrag) { *m_handleDrag = event->point; //ported from the gradient tool... we need to think about this more in the future. if (event->modifiers() == Qt::ShiftModifier && m_snapIsRadial) { QLineF dragRadius = QLineF(m_dragStart, event->point); dragRadius.setLength(m_radius.length()); *m_handleDrag = dragRadius.p2(); } else if (event->modifiers() == Qt::ShiftModifier ) { QPointF move = snapToClosestAxis(event->point - m_dragStart); *m_handleDrag = m_dragStart + move; } else { *m_handleDrag = snapToGuide(event, QPointF(), false); } m_handleDrag->uncache(); m_handleCombine = 0; if (!(event->modifiers() & Qt::ShiftModifier)) { double minDist = 49.0; QPointF mousePos = m_canvas->viewConverter()->documentToView(event->point); Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { if (handle == m_handleDrag) continue; double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle)); if (dist < minDist) { minDist = dist; m_handleCombine = handle; } } } m_canvas->updateCanvas(); } else if (m_assistantDrag) { QPointF newAdjustment = snapToGuide(event, QPointF(), false) - m_cursorStart; if (event->modifiers() == Qt::ShiftModifier ) { newAdjustment = snapToClosestAxis(newAdjustment); } Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->handles()) { *handle += (newAdjustment - m_currentAdjustment); } if (m_assistantDrag->id()== "vanishing point"){ Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->sideHandles()) { *handle += (newAdjustment - m_currentAdjustment); } } m_currentAdjustment = newAdjustment; m_canvas->updateCanvas(); } else { event->ignore(); } bool wasHiglightedNode = m_higlightedNode != 0; QPointF mousep = m_canvas->viewConverter()->documentToView(event->point); QList pAssistant= m_canvas->paintingAssistantsDecoration()->assistants(); Q_FOREACH (KisPaintingAssistantSP assistant, pAssistant) { if(assistant->id() == "perspective") { if ((m_higlightedNode = nodeNearPoint(assistant, mousep))) { if (m_higlightedNode == m_selectedNode1 || m_higlightedNode == m_selectedNode2) { m_higlightedNode = 0; } else { m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas break; } } } //this following bit sets the translations for the vanishing-point handles. if(m_handleDrag && assistant->id() == "vanishing point" && assistant->sideHandles().size()==4) { //for inner handles, the outer handle gets translated. if (m_handleDrag == assistant->sideHandles()[0]){ QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]); qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length(); if (length<2.0){length=2.0;} length +=perspectiveline.length(); perspectiveline.setLength(length); *assistant->sideHandles()[1] = perspectiveline.p2(); } else if (m_handleDrag == assistant->sideHandles()[2]){ QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]); qreal length = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length(); if (length<2.0){length=2.0;} length +=perspectiveline.length(); perspectiveline.setLength(length); *assistant->sideHandles()[3] = perspectiveline.p2(); } //for outer handles, only the vanishing point is translated, but only if there's an intersection. else if (m_handleDrag == assistant->sideHandles()[1]|| m_handleDrag == assistant->sideHandles()[3]){ QPointF vanishingpoint(0,0); QLineF perspectiveline = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]); QLineF perspectiveline2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]); if (QLineF(perspectiveline2).intersect(QLineF(perspectiveline), &vanishingpoint) != QLineF::NoIntersection){ *assistant->handles()[0] = vanishingpoint;} }//and for the vanishing point itself, only the outer handles get translated. else if (m_handleDrag == assistant->handles()[0]){ QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]); QLineF perspectiveline2 = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]); qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length(); qreal length2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length(); if (length<2.0){length=2.0;} if (length2<2.0){length2=2.0;} length +=perspectiveline.length(); length2 +=perspectiveline2.length(); perspectiveline.setLength(length); perspectiveline2.setLength(length2); *assistant->sideHandles()[1] = perspectiveline.p2(); *assistant->sideHandles()[3] = perspectiveline2.p2(); } } } if (wasHiglightedNode && !m_higlightedNode) { m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } } void KisRulerAssistantTool::endPrimaryAction(KoPointerEvent *event) { setMode(KisTool::HOVER_MODE); if (m_handleDrag) { if (!(event->modifiers() & Qt::ShiftModifier) && m_handleCombine) { m_handleCombine->mergeWith(m_handleDrag); m_handleCombine->uncache(); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); } m_handleDrag = m_handleCombine = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } else if (m_assistantDrag) { m_assistantDrag.clear(); m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } else if(m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) { addAssistant(); m_internalMode = MODE_CREATION; m_canvas->updateCanvas(); } else { event->ignore(); } } void KisRulerAssistantTool::addAssistant() { m_canvas->paintingAssistantsDecoration()->addAssistant(m_newAssistant); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); KisAbstractPerspectiveGrid* grid = dynamic_cast(m_newAssistant.data()); if (grid) { m_canvas->viewManager()->resourceProvider()->addPerspectiveGrid(grid); } m_newAssistant.clear(); } void KisRulerAssistantTool::removeAssistant(KisPaintingAssistantSP assistant) { KisAbstractPerspectiveGrid* grid = dynamic_cast(assistant.data()); if (grid) { m_canvas->viewManager()->resourceProvider()->removePerspectiveGrid(grid); } m_canvas->paintingAssistantsDecoration()->removeAssistant(assistant); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); } void KisRulerAssistantTool::snappingOn(KisPaintingAssistantSP assistant) { assistant->setSnapping(true); } void KisRulerAssistantTool::snappingOff(KisPaintingAssistantSP assistant) { assistant->setSnapping(false); } void KisRulerAssistantTool::outlineOn(KisPaintingAssistantSP assistant) { assistant->setOutline(true); } void KisRulerAssistantTool::outlineOff(KisPaintingAssistantSP assistant) { assistant->setOutline(false); } #include QPointF KisRulerAssistantTool::snapToGuide(KoPointerEvent *e, const QPointF &offset, bool useModifiers) { if (!m_canvas->currentImage()) return e->point; KoSnapGuide *snapGuide = m_canvas->snapGuide(); QPointF pos = snapGuide->snap(e->point, offset, useModifiers ? e->modifiers() : Qt::NoModifier); //return m_canvas->currentImage()->documentToPixel(pos); return pos; } QPointF KisRulerAssistantTool::snapToGuide(const QPointF& pt, const QPointF &offset) { if (!m_canvas) return pt; KoSnapGuide *snapGuide = m_canvas->snapGuide(); QPointF pos = snapGuide->snap(pt, offset, Qt::NoModifier); return pos; } void KisRulerAssistantTool::mouseMoveEvent(KoPointerEvent *event) { if (m_newAssistant && m_internalMode == MODE_CREATION) { *m_newAssistant->handles().back() = event->point; m_canvas->updateCanvas(); } else if (m_newAssistant && m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) { QPointF translate = event->point - m_dragEnd;; m_dragEnd = event->point; m_selectedNode1.data()->operator =(QPointF(m_selectedNode1.data()->x(),m_selectedNode1.data()->y()) + translate); m_selectedNode2.data()->operator = (QPointF(m_selectedNode2.data()->x(),m_selectedNode2.data()->y()) + translate); m_canvas->updateCanvas(); } } void KisRulerAssistantTool::paint(QPainter& _gc, const KoViewConverter &_converter) { QPixmap iconDelete = KisIconUtils::loadIcon("dialog-cancel").pixmap(16, 16); QPixmap iconSnapOn = KisIconUtils::loadIcon("visible").pixmap(16, 16); QPixmap iconSnapOff = KisIconUtils::loadIcon("novisible").pixmap(16, 16); QPixmap iconMove = KisIconUtils::loadIcon("transform-move").pixmap(32, 32); QColor handlesColor(0, 0, 0, 125); if (m_newAssistant) { m_newAssistant->drawAssistant(_gc, QRectF(QPointF(0, 0), QSizeF(m_canvas->image()->size())), m_canvas->coordinatesConverter(), false,m_canvas, true, false); Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_newAssistant->handles()) { QPainterPath path; path.addEllipse(QRectF(_converter.documentToView(*handle) - QPointF(6, 6), QSizeF(12, 12))); KisPaintingAssistant::drawPath(_gc, path); } } // TODO: too many Q_FOREACH loops going through all assistants. Condense this to one to be a little more performant // Draw corner and middle perspective nodes Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { QRectF ellipse(_converter.documentToView(*handle) - QPointF(6, 6), QSizeF(12, 12)); // render handles when they are being dragged and moved if (handle == m_handleDrag || handle == m_handleCombine) { _gc.save(); _gc.setPen(Qt::transparent); _gc.setBrush(handlesColor); _gc.drawEllipse(ellipse); _gc.restore(); } if ( assistant->id() =="vanishing point") { if (assistant->handles().at(0) == handle ) { // vanishing point handle ellipse = QRectF(_converter.documentToView(*handle) - QPointF(10, 10), QSizeF(20, 20)); // TODO: change this to be smaller, but fill in with a color } //TODO: render outside handles a little bigger than rotation anchor handles } QPainterPath path; path.addEllipse(ellipse); KisPaintingAssistant::drawPath(_gc, path); } } Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { // Draw middle perspective handles if(assistant->id()=="perspective") { assistant->findHandleLocation(); QPointF topMiddle, bottomMiddle, rightMiddle, leftMiddle; topMiddle = (_converter.documentToView(*assistant->topLeft()) + _converter.documentToView(*assistant->topRight()))*0.5; bottomMiddle = (_converter.documentToView(*assistant->bottomLeft()) + _converter.documentToView(*assistant->bottomRight()))*0.5; rightMiddle = (_converter.documentToView(*assistant->topRight()) + _converter.documentToView(*assistant->bottomRight()))*0.5; leftMiddle = (_converter.documentToView(*assistant->topLeft()) + _converter.documentToView(*assistant->bottomLeft()))*0.5; QPainterPath path; path.addEllipse(QRectF(leftMiddle-QPointF(6,6),QSizeF(12,12))); path.addEllipse(QRectF(topMiddle-QPointF(6,6),QSizeF(12,12))); path.addEllipse(QRectF(rightMiddle-QPointF(6,6),QSizeF(12,12))); path.addEllipse(QRectF(bottomMiddle-QPointF(6,6),QSizeF(12,12))); KisPaintingAssistant::drawPath(_gc, path); } if(assistant->id()=="vanishing point") { if (assistant->sideHandles().size() == 4) { // Draw the line QPointF p0 = _converter.documentToView(*assistant->handles()[0]); QPointF p1 = _converter.documentToView(*assistant->sideHandles()[0]); QPointF p2 = _converter.documentToView(*assistant->sideHandles()[1]); QPointF p3 = _converter.documentToView(*assistant->sideHandles()[2]); QPointF p4 = _converter.documentToView(*assistant->sideHandles()[3]); _gc.setPen(QColor(0, 0, 0, 75)); // Draw control lines QPen penStyle(QColor(120, 120, 120, 60), 2.0, Qt::DashDotDotLine); _gc.setPen(penStyle); _gc.drawLine(p0, p1); _gc.drawLine(p0, p3); _gc.drawLine(p1, p2); _gc.drawLine(p3, p4); } } } // Draw the assistant widget Q_FOREACH (const KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { // We are going to put all of the assistant actions below the bounds of the assistant // so they are out of the way // assistant->buttonPosition() gets the center X/Y position point QPointF actionsPosition = m_canvas->viewConverter()->documentToView(assistant->buttonPosition()); QPointF iconDeletePosition(actionsPosition + QPointF(78, m_assistantHelperYOffset + 7)); QPointF iconSnapPosition(actionsPosition + QPointF(54, m_assistantHelperYOffset + 7)); QPointF iconMovePosition(actionsPosition + QPointF(15, m_assistantHelperYOffset )); // Background container for helpers QBrush backgroundColor = m_canvas->viewManager()->mainWindow()->palette().window(); QPointF actionsBGRectangle(actionsPosition + QPointF(25, m_assistantHelperYOffset)); _gc.setRenderHint(QPainter::Antialiasing); QPainterPath bgPath; bgPath.addRoundedRect(QRectF(actionsBGRectangle.x(), actionsBGRectangle.y(), 80, 30), 6, 6); QPen stroke(QColor(60, 60, 60, 80), 2); _gc.setPen(stroke); _gc.fillPath(bgPath, backgroundColor); _gc.drawPath(bgPath); QPainterPath movePath; // render circle behind by move helper _gc.setPen(stroke); movePath.addEllipse(iconMovePosition.x()-5, iconMovePosition.y()-5, 40, 40);// background behind icon _gc.fillPath(movePath, backgroundColor); _gc.drawPath(movePath); // Preview/Snap Tool helper _gc.drawPixmap(iconDeletePosition, iconDelete); if (assistant->snapping()==true) { _gc.drawPixmap(iconSnapPosition, iconSnapOn); } else { _gc.drawPixmap(iconSnapPosition, iconSnapOff); } // Move Assistant Tool helper _gc.drawPixmap(iconMovePosition, iconMove); } } void KisRulerAssistantTool::removeAllAssistants() { m_canvas->viewManager()->resourceProvider()->clearPerspectiveGrids(); m_canvas->paintingAssistantsDecoration()->removeAll(); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->updateCanvas(); } void KisRulerAssistantTool::loadAssistants() { KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::OpenFile, "OpenAssistant"); dialog.setCaption(i18n("Select an Assistant")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant"); QString filename = dialog.filename(); if (filename.isEmpty()) return; if (!QFileInfo(filename).exists()) return; QFile file(filename); file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); QXmlStreamReader xml(data); QMap handleMap; KisPaintingAssistantSP assistant; bool errors = false; while (!xml.atEnd()) { switch (xml.readNext()) { case QXmlStreamReader::StartElement: if (xml.name() == "handle") { if (assistant && !xml.attributes().value("ref").isEmpty()) { KisPaintingAssistantHandleSP handle = handleMap.value(xml.attributes().value("ref").toString().toInt()); if (handle) { assistant->addHandle(handle); } else { errors = true; } } else { QString strId = xml.attributes().value("id").toString(), strX = xml.attributes().value("x").toString(), strY = xml.attributes().value("y").toString(); if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) { int id = strId.toInt(); double x = strX.toDouble(), y = strY.toDouble(); if (!handleMap.contains(id)) { handleMap.insert(id, new KisPaintingAssistantHandle(x, y)); } else { errors = true; } } else { errors = true; } } } else if (xml.name() == "assistant") { const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(xml.attributes().value("type").toString()); if (factory) { if (assistant) { errors = true; assistant.clear(); } assistant = toQShared(factory->createPaintingAssistant()); } else { errors = true; } } break; case QXmlStreamReader::EndElement: if (xml.name() == "assistant") { if (assistant) { if (assistant->handles().size() == assistant->numHandles()) { if (assistant->id() == "vanishing point"){ //ideally we'd save and load side-handles as well, but this is all I've got// QPointF pos = *assistant->handles()[0]; assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(-70,0))); assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(-140,0))); assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(70,0))); assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(140,0))); } m_canvas->paintingAssistantsDecoration()->addAssistant(assistant); KisAbstractPerspectiveGrid* grid = dynamic_cast(assistant.data()); if (grid) { m_canvas->viewManager()->resourceProvider()->addPerspectiveGrid(grid); } } else { errors = true; } assistant.clear(); } } break; default: break; } } if (assistant) { errors = true; assistant.clear(); } if (xml.hasError()) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), xml.errorString()); } if (errors) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Errors were encountered. Not all assistants were successfully loaded.")); } m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->updateCanvas(); } void KisRulerAssistantTool::saveAssistants() { if (m_handles.isEmpty()) return; QByteArray data; QXmlStreamWriter xml(&data); xml.writeStartDocument(); xml.writeStartElement("paintingassistant"); xml.writeStartElement("handles"); QMap handleMap; Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { int id = handleMap.size(); handleMap.insert(handle, id); xml.writeStartElement("handle"); //xml.writeAttribute("type", handle->handleType()); xml.writeAttribute("id", QString::number(id)); xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); xml.writeEndElement(); } xml.writeEndElement(); xml.writeStartElement("assistants"); Q_FOREACH (const KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { xml.writeStartElement("assistant"); xml.writeAttribute("type", assistant->id()); xml.writeStartElement("handles"); Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->handles()) { xml.writeStartElement("handle"); xml.writeAttribute("ref", QString::number(handleMap.value(handle))); xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndElement(); xml.writeEndDocument(); KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::SaveFile, "OpenAssistant"); dialog.setCaption(i18n("Save Assistant")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant"); QString filename = dialog.filename(); if (filename.isEmpty()) return; QFile file(filename); file.open(QIODevice::WriteOnly); file.write(data); } QWidget *KisRulerAssistantTool::createOptionWidget() { if (!m_optionsWidget) { m_optionsWidget = new QWidget; m_options.setupUi(m_optionsWidget); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); m_options.loadButton->setIcon(KisIconUtils::loadIcon("document-open")); m_options.saveButton->setIcon(KisIconUtils::loadIcon("document-save")); m_options.deleteButton->setIcon(KisIconUtils::loadIcon("edit-delete")); QList assistants; Q_FOREACH (const QString& key, KisPaintingAssistantFactoryRegistry::instance()->keys()) { QString name = KisPaintingAssistantFactoryRegistry::instance()->get(key)->name(); assistants << KoID(key, name); } - qSort(assistants.begin(), assistants.end(), KoID::compareNames); + std::sort(assistants.begin(), assistants.end(), KoID::compareNames); Q_FOREACH(const KoID &id, assistants) { m_options.comboBox->addItem(id.name(), id.id()); } connect(m_options.saveButton, SIGNAL(clicked()), SLOT(saveAssistants())); connect(m_options.loadButton, SIGNAL(clicked()), SLOT(loadAssistants())); connect(m_options.deleteButton, SIGNAL(clicked()), SLOT(removeAllAssistants())); } return m_optionsWidget; } diff --git a/plugins/dockers/advancedcolorselector/kis_common_colors_recalculation_runner.cpp b/plugins/dockers/advancedcolorselector/kis_common_colors_recalculation_runner.cpp index b9e1982416..a740764934 100644 --- a/plugins/dockers/advancedcolorselector/kis_common_colors_recalculation_runner.cpp +++ b/plugins/dockers/advancedcolorselector/kis_common_colors_recalculation_runner.cpp @@ -1,213 +1,213 @@ #include "kis_common_colors_recalculation_runner.h" #include #include #include "KoColor.h" #include "KoColorSpaceRegistry.h" #include "kis_common_colors.h" enum ColorAxis {RedAxis=0, GreenAxis, BlueAxis}; class Color { public: Color(QRgb rgb) : r(qRed(rgb)), g(qGreen(rgb)), b(qBlue(rgb)) {} unsigned char r; unsigned char g; unsigned char b; inline unsigned char operator[](ColorAxis i) const { if(i==RedAxis) return r; if(i==GreenAxis) return g; return b; } }; class VBox { QList m_colors; public: VBox(QList rgbList) { QList colorList; for(int i=0; i colorList) : m_colors(colorList) {} int population() const { return m_colors.size(); } VBox divide() { ColorAxis axis = biggestAxis(); Q_ASSERT(axisSize(axis)>=3); unsigned char divpos = divPos(axis); QList newVBoxColors; for(int i=m_colors.size()-1; i>=0; i--) { Color c = m_colors.at(i); if(c[axis]>divpos) { m_colors.removeAt(i); newVBoxColors.append(c); } } return VBox(newVBoxColors); } QRgb mean() const { int r=0; int g=0; int b=0; for(int i=0;i0); return qRgb(r/size, g/size, b/size); } unsigned char axisSize(ColorAxis axis) const { unsigned char valMin = 255; unsigned char valMax = 0; for(int i=0; ivalMax) valMax=m_colors.at(i)[axis]; if(m_colors.at(i)[axis]sG && sR>sB) return RedAxis; if(sG>sR && sG>sB) return GreenAxis; return BlueAxis; } private: // unsigned char divPos(ColorAxis axis) const // { // QList values; // for(int i=0;im_colors.at(i)[axis]) min=m_colors.at(i)[axis]; if(maxsetColors(extractColors()); } QList KisCommonColorsRecalculationRunner::extractColors() { QList colors = getColors(); VBox startBox(colors); QList boxes; boxes.append(startBox); while (boxes.size()m_numColors*3/5) { int biggestBox=-1; int biggestBoxPopulation=-1; for(int i=0; ibiggestBoxPopulation && boxes.at(i).axisSize(boxes.at(i).biggestAxis())>=3) { biggestBox=i; biggestBoxPopulation=boxes.at(i).population(); } } if(biggestBox==-1 || boxes[biggestBox].population()<=3) break; VBox newBox = boxes[biggestBox].divide(); boxes.append(newBox); } while (boxes.size()m_numColors) { int biggestBox=-1; int biggestBoxAxisSize=-1; for(int i=0; ibiggestBoxAxisSize && boxes.at(i).axisSize(boxes.at(i).biggestAxis())>=3) { biggestBox=i; biggestBoxAxisSize=boxes.at(i).axisSize(boxes.at(i).biggestAxis()); } } if(biggestBox==-1 || boxes[biggestBox].population()<=3) break; VBox newBox = boxes[biggestBox].divide(); boxes.append(newBox); } const KoColorSpace* colorSpace = KoColorSpaceRegistry::instance()->rgb8(); QList colorList; for(int i=0; i=1) { colorList.append(KoColor(QColor(boxes.at(i).mean()), colorSpace)); } } return colorList; } QList KisCommonColorsRecalculationRunner::getColors() { int width = m_imageData.width(); int height = m_imageData.height(); QImage tmpImage; int pixelCount = height*width; if(pixelCount> (1<<16)) { qreal factor = sqrt((1<<16)/(qreal) pixelCount); tmpImage = m_imageData.scaledToWidth(width*factor); } else { tmpImage = m_imageData; } width=tmpImage.width(); height=tmpImage.height(); QSet colorList; for (int i=0; i * * 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_animation_utils.h" #include "kundo2command.h" #include "kis_algebra_2d.h" #include "kis_image.h" #include "kis_node.h" #include "kis_keyframe_channel.h" #include "kis_post_execution_undo_adapter.h" #include "kis_global.h" #include "kis_tool_utils.h" #include "kis_image_animation_interface.h" #include "kis_command_utils.h" #include "kis_processing_applicator.h" #include "kis_transaction.h" namespace KisAnimationUtils { const QString addFrameActionName = i18n("New Frame"); const QString duplicateFrameActionName = i18n("Copy Frame"); const QString removeFrameActionName = i18n("Remove Frame"); const QString removeFramesActionName = i18n("Remove Frames"); const QString lazyFrameCreationActionName = i18n("Auto Frame Mode"); const QString dropFramesActionName = i18n("Drop Frames"); const QString showLayerActionName = i18n("Show in Timeline"); const QString newLayerActionName = i18n("New Layer"); const QString addExistingLayerActionName = i18n("Add Existing Layer"); const QString removeLayerActionName = i18n("Remove Layer"); const QString addOpacityKeyframeActionName = i18n("Add opacity keyframe"); const QString addTransformKeyframeActionName = i18n("Add transform keyframe"); const QString removeOpacityKeyframeActionName = i18n("Remove opacity keyframe"); const QString removeTransformKeyframeActionName = i18n("Remove transform keyframe"); void createKeyframeLazy(KisImageSP image, KisNodeSP node, const QString &channelId, int time, bool copy) { KIS_SAFE_ASSERT_RECOVER_RETURN(!image->locked()); KUndo2Command *cmd = new KisCommandUtils::LambdaCommand( copy ? kundo2_i18n("Copy Keyframe") : kundo2_i18n("Add Keyframe"), [image, node, channelId, time, copy] () mutable -> KUndo2Command* { bool result = false; QScopedPointer cmd(new KUndo2Command()); KisKeyframeChannel *channel = node->getKeyframeChannel(channelId); bool createdChannel = false; if (!channel) { node->enableAnimation(); channel = node->getKeyframeChannel(channelId, true); if (!channel) return nullptr; createdChannel = true; } if (copy) { if (!channel->keyframeAt(time)) { KisKeyframeSP srcFrame = channel->activeKeyframeAt(time); channel->copyKeyframe(srcFrame, time, cmd.data()); result = true; } } else { if (channel->keyframeAt(time) && !createdChannel) { if (image->animationInterface()->currentTime() == time && channelId == KisKeyframeChannel::Content.id()) { //shortcut: clearing the image instead KisPaintDeviceSP device = node->paintDevice(); if (device) { KisTransaction transaction(kundo2_i18n("Clear"), device, cmd.data()); device->clear(); (void) transaction.endAndTake(); // saved as 'parent' result = true; } } } else { channel->addKeyframe(time, cmd.data()); result = true; } } return result ? new KisCommandUtils::SkipFirstRedoWrapper(cmd.take()) : nullptr; }); KisProcessingApplicator::runSingleCommandStroke(image, cmd, KisStrokeJobData::BARRIER); } void removeKeyframes(KisImageSP image, const FrameItemList &frames) { KIS_SAFE_ASSERT_RECOVER_RETURN(!image->locked()); KUndo2Command *cmd = new KisCommandUtils::LambdaCommand( kundo2_i18np("Remove Keyframe", "Remove Keyframes", frames.size()), [image, frames] () { bool result = false; QScopedPointer cmd(new KUndo2Command()); Q_FOREACH (const FrameItem &item, frames) { const int time = item.time; KisNodeSP node = item.node; KisKeyframeChannel *channel = node->getKeyframeChannel(item.channel); if (!channel) continue; KisKeyframeSP keyframe = channel->keyframeAt(time); if (!keyframe) continue; channel->deleteKeyframe(keyframe, cmd.data()); result = true; } return result ? new KisCommandUtils::SkipFirstRedoWrapper(cmd.take()) : 0; }); KisProcessingApplicator::runSingleCommandStroke(image, cmd, KisStrokeJobData::BARRIER); } void removeKeyframe(KisImageSP image, KisNodeSP node, const QString &channel, int time) { QVector frames; frames << FrameItem(node, channel, time); removeKeyframes(image, frames); } struct LessOperator { LessOperator(const QPoint &offset) : m_columnCoeff(-KisAlgebra2D::signPZ(offset.x())), m_rowCoeff(-1000000 * KisAlgebra2D::signZZ(offset.y())) { } bool operator()(const QModelIndex &lhs, const QModelIndex &rhs) { return m_columnCoeff * lhs.column() + m_rowCoeff * lhs.row() < m_columnCoeff * rhs.column() + m_rowCoeff * rhs.row(); } private: int m_columnCoeff; int m_rowCoeff; }; void sortPointsForSafeMove(QModelIndexList *points, const QPoint &offset) { - qSort(points->begin(), points->end(), LessOperator(offset)); + std::sort(points->begin(), points->end(), LessOperator(offset)); } KUndo2Command* createMoveKeyframesCommand(const FrameItemList &srcFrames, const FrameItemList &dstFrames, bool copy, KUndo2Command *parentCommand) { KUndo2Command *cmd = new KisCommandUtils::LambdaCommand( !copy ? kundo2_i18np("Move Keyframe", "Move %1 Keyframes", srcFrames.size()) : kundo2_i18np("Copy Keyframe", "Copy %1 Keyframes", srcFrames.size()), parentCommand, [srcFrames, dstFrames, copy] () -> KUndo2Command* { bool result = false; QScopedPointer cmd(new KUndo2Command()); for (int i = 0; i < srcFrames.size(); i++) { const int srcTime = srcFrames[i].time; KisNodeSP srcNode = srcFrames[i].node; KisKeyframeChannel *srcChannel = srcNode->getKeyframeChannel(srcFrames[i].channel); const int dstTime = dstFrames[i].time; KisNodeSP dstNode = dstFrames[i].node; KisKeyframeChannel *dstChannel = dstNode->getKeyframeChannel(dstFrames[i].channel, true); if (srcNode == dstNode) { if (!srcChannel) continue; KisKeyframeSP srcKeyframe = srcChannel->keyframeAt(srcTime); if (srcKeyframe) { if (copy) { srcChannel->copyKeyframe(srcKeyframe, dstTime, cmd.data()); } else { srcChannel->moveKeyframe(srcKeyframe, dstTime, cmd.data()); } } } else { if (!srcChannel|| !dstChannel) continue; KisKeyframeSP srcKeyframe = srcChannel->keyframeAt(srcTime); if (!srcKeyframe) continue; dstChannel->copyExternalKeyframe(srcChannel, srcTime, dstTime, cmd.data()); if (!copy) { srcChannel->deleteKeyframe(srcKeyframe, cmd.data()); } } result = true; } return result ? new KisCommandUtils::SkipFirstRedoWrapper(cmd.take()) : 0; }); return cmd; } void moveKeyframes(KisImageSP image, const FrameItemList &srcFrames, const FrameItemList &dstFrames, bool copy) { KIS_SAFE_ASSERT_RECOVER_RETURN(srcFrames.size() != dstFrames.size()); KIS_SAFE_ASSERT_RECOVER_RETURN(!image->locked()); KUndo2Command *cmd = createMoveKeyframesCommand(srcFrames, dstFrames, copy); KisProcessingApplicator::runSingleCommandStroke(image, cmd, KisStrokeJobData::BARRIER); } void moveKeyframe(KisImageSP image, KisNodeSP node, const QString &channel, int srcTime, int dstTime) { QVector srcFrames; srcFrames << FrameItem(node, channel, srcTime); QVector dstFrames; dstFrames << FrameItem(node, channel, dstTime); moveKeyframes(image, srcFrames, dstFrames); } bool supportsContentFrames(KisNodeSP node) { return node->inherits("KisPaintLayer") || node->inherits("KisFilterMask") || node->inherits("KisTransparencyMask") || node->inherits("KisSelectionBasedLayer"); } } diff --git a/plugins/extensions/layersplit/layersplit.cpp b/plugins/extensions/layersplit/layersplit.cpp index 162d0137d6..a288da7199 100644 --- a/plugins/extensions/layersplit/layersplit.cpp +++ b/plugins/extensions/layersplit/layersplit.cpp @@ -1,228 +1,228 @@ /* * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "layersplit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dlg_layersplit.h" #include "kis_node_manager.h" #include "kis_node_commands_adapter.h" #include "kis_undo_adapter.h" #include #include K_PLUGIN_FACTORY_WITH_JSON(LayerSplitFactory, "kritalayersplit.json", registerPlugin();) LayerSplit::LayerSplit(QObject *parent, const QVariantList &) : KisViewPlugin(parent) { KisAction *action = createAction("layersplit"); connect(action, SIGNAL(triggered()), this, SLOT(slotLayerSplit())); } LayerSplit::~LayerSplit() { } struct Layer { KoColor color; KisPaintDeviceSP device; KisRandomAccessorSP accessor; int pixelsWritten; bool operator<(const Layer& other) const { return pixelsWritten < other.pixelsWritten; } }; void LayerSplit::slotLayerSplit() { DlgLayerSplit dlg; if (dlg.exec() == QDialog::Accepted) { dlg.hide(); QApplication::setOverrideCursor(Qt::WaitCursor); KoProgressUpdater* pu = m_view->createProgressUpdater(KoProgressUpdater::Unthreaded); pu->start(100, i18n("Split into Layers")); QPointer updater = pu->startSubtask(); KisImageSP image = m_view->image(); if (!image) return; image->lock(); KisNodeSP node = m_view->activeNode(); if (!node) return; KisPaintDeviceSP projection = node->projection(); if (!projection) return; QList colorMap; const KoColorSpace *cs = projection->colorSpace(); QRect rc = image->bounds(); int fuzziness = dlg.fuzziness(); updater->setProgress(0); KisRandomConstAccessorSP acc = projection->createRandomConstAccessorNG(rc.x(), rc.y()); for (int row = rc.y(); row < rc.height(); ++row) { for (int col = rc.x(); col < rc.width(); ++col) { acc->moveTo(col, row); KoColor c(cs); c.setColor(acc->rawDataConst(), cs); if (c.opacityU8() == OPACITY_TRANSPARENT_U8) { continue; } if (dlg.disregardOpacity()) { c.setOpacity(OPACITY_OPAQUE_U8); } bool found = false; Q_FOREACH (const Layer &l, colorMap) { if (fuzziness == 0) { found = (l.color == c); } else { quint8 match = cs->difference(l.color.data(), c.data()); found = (match <= fuzziness); } if (found) { KisRandomAccessorSP dstAcc = l.accessor; dstAcc->moveTo(col, row); memcpy(dstAcc->rawData(), acc->rawDataConst(), cs->pixelSize()); const_cast(&l)->pixelsWritten++; break; } } if (!found) { QString name = ""; if (dlg.palette()) { name = dlg.palette()->closestColorName(c); } if (name.toLower() == "untitled" || name.toLower() == "none" || name.toLower() == "") { name = KoColor::toQString(c); } Layer l; l.color = c; l.device = new KisPaintDevice(cs, name); l.accessor = l.device->createRandomAccessorNG(col, row); l.accessor->moveTo(col, row); memcpy(l.accessor->rawData(), acc->rawDataConst(), cs->pixelSize()); l.pixelsWritten = 1; colorMap << l; } } if (updater->interrupted()) { return; } updater->setProgress((row - rc.y()) * 100 / rc.height() - rc.y()); } updater->setProgress(100); dbgKrita << "Created" << colorMap.size() << "layers"; // Q_FOREACH (const Layer &l, colorMap) { // dbgKrita << "\t" << l.device->objectName() << ":" << l.pixelsWritten; // } if (dlg.sortLayers()) { - qSort(colorMap); + std::sort(colorMap.begin(), colorMap.end()); } KisUndoAdapter *undo = image->undoAdapter(); undo->beginMacro(kundo2_i18n("Split Layer")); KisNodeCommandsAdapter adapter(m_view); KisGroupLayerSP baseGroup = dynamic_cast(node->parent().data()); if (!baseGroup) { // Masks are never nested baseGroup = dynamic_cast(node->parent()->parent().data()); } if (dlg.hideOriginal()) { node->setVisible(false); } if (dlg.createBaseGroup()) { KisGroupLayerSP grp = new KisGroupLayer(image, i18n("Color"), OPACITY_OPAQUE_U8); adapter.addNode(grp, baseGroup, 1); baseGroup = grp; } Q_FOREACH (const Layer &l, colorMap) { KisGroupLayerSP grp = baseGroup; if (dlg.createSeparateGroups()) { grp = new KisGroupLayer(image, l.device->objectName(), OPACITY_OPAQUE_U8); adapter.addNode(grp, baseGroup, 1); } KisPaintLayerSP paintLayer = new KisPaintLayer(image, l.device->objectName(), OPACITY_OPAQUE_U8, l.device); adapter.addNode(paintLayer, grp, 0); paintLayer->setAlphaLocked(dlg.lockAlpha()); } undo->endMacro(); image->unlock(); image->setModified(); } QApplication::restoreOverrideCursor(); } #include "layersplit.moc" diff --git a/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt b/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt index 61bc5eb185..4a58aa22f7 100644 --- a/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt +++ b/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt @@ -1,98 +1,99 @@ # Copyright (C) 2012, 2013 Shaheed Haque # Copyright (C) 2013 Alex Turbov # Copyright (C) 2014-2016 Boudewijn Rempt # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. include(CMakeParseArguments) # # Simple helper function to install plugin and related files # having only a name of the plugin... # (just to reduce syntactic noise when a lot of plugins get installed) # function(install_pykrita_plugin name) set(_options) set(_one_value_args) set(_multi_value_args PATTERNS FILE) cmake_parse_arguments(install_pykrita_plugin "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN}) if(NOT name) message(FATAL_ERROR "Plugin filename is not given") endif() if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${name}.py) install(FILES kritapykrita_${name}.desktop DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita) foreach(_f ${name}.py ${name}.ui ${install_pykrita_plugin_FILE}) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${_f}) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${_f} DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita) endif() endforeach() elseif(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${name}) install(FILES ${name}/kritapykrita_${name}.desktop DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita) install( DIRECTORY ${name} DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita FILES_MATCHING PATTERN "*.py" PATTERN "*.ui" PATTERN "__pycache__*" EXCLUDE ) # TODO Is there any way to form a long PATTERN options string # and use it in a single install() call? # NOTE Install specified patterns one-by-one... foreach(_pattern ${install_pykrita_plugin_PATTERNS}) install( DIRECTORY ${name} DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita FILES_MATCHING PATTERN "${_pattern}" PATTERN "__pycache__*" EXCLUDE ) endforeach() else() message(FATAL_ERROR "Do not know what to do with ${name}") endif() endfunction() install_pykrita_plugin(hello) install_pykrita_plugin(assignprofiledialog) install_pykrita_plugin(scripter) #install_pykrita_plugin(highpass) install_pykrita_plugin(tenbrushes) install( FILES tenbrushes/tenbrushes.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions) install_pykrita_plugin(palette_docker) +install_pykrita_plugin(quick_settings_docker) # if(PYTHON_VERSION_MAJOR VERSION_EQUAL 3) # install_pykrita_plugin(cmake_utils) # install_pykrita_plugin(js_utils PATTERNS "*.json") # install_pykrita_plugin(expand PATTERNS "*.expand" "templates/*.tpl") # endif() install( DIRECTORY libkritapykrita DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita FILES_MATCHING PATTERN "*.py" PATTERN "__pycache__*" EXCLUDE ) diff --git a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py index 7eb645642a..4077bae052 100644 --- a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py +++ b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py @@ -1,143 +1,143 @@ # Description: A Python based docker that allows you to edit KPL color palettes. # By Wolthera # Importing the relevant dependancies: import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5.Qt import * import math from krita import * class Palette_Docker(DockWidget): #Init the docker def __init__(self): super().__init__() # make base-widget and layout widget = QWidget() layout = QVBoxLayout() buttonLayout = QHBoxLayout() widget.setLayout(layout) self.setWindowTitle("Python Palette Docker") #Make a combobox and add palettes self.cmb_palettes = QComboBox() allPalettes = Application.resources("palette") for palette_name in allPalettes: self.cmb_palettes.addItem(palette_name) - self.currentPalette = Palette(allPalettes["Default"]) + self.currentPalette = Palette(allPalettes[list(allPalettes.keys())[0]]) self.cmb_palettes.currentTextChanged.connect(self.slot_paletteChanged) layout.addWidget(self.cmb_palettes) # add combobox to the layout self.paletteView = PaletteView() self.paletteView.setPalette(self.currentPalette) layout.addWidget(self.paletteView) self.paletteView.entrySelectedForeGround.connect(self.slot_swatchSelected) self.colorComboBox = QComboBox() self.colorList = list() buttonLayout.addWidget(self.colorComboBox) self.addEntry = QPushButton() self.addEntry.setText("A") self.addEntry.setToolTip("Add Entry") self.addEntry.clicked.connect(self.slot_add_entry) buttonLayout.addWidget(self.addEntry) self.addGroup = QPushButton() self.addGroup.clicked.connect(self.slot_add_group) self.addGroup.setText("G") self.addGroup.setToolTip("Add Group") buttonLayout.addWidget(self.addGroup) self.removeEntry = QPushButton() self.removeEntry.setText("R") self.removeEntry.setToolTip("Remove Entry") self.removeEntry.clicked.connect(self.slot_remove_entry) buttonLayout.addWidget(self.removeEntry) layout.addLayout(buttonLayout) self.slot_fill_combobox() self.setWidget(widget) # add widget to the docker def slot_paletteChanged(self, name): self.currentPalette = Palette(Application.resources("palette")[name]) self.paletteView.setPalette(self.currentPalette) self.slot_fill_combobox() #self.fill_palette_frame() @pyqtSlot('KoColorSetEntry') def slot_swatchSelected(self, entry): print("entry "+entry.name) if (self.canvas()) is not None: if (self.canvas().view()) is not None: name = entry.name if len(entry.id)>0: name = entry.id+" - "+entry.name if len(name)>0: if name in self.colorList: self.colorComboBox.setCurrentIndex(self.colorList.index(name)) color = self.currentPalette.colorForEntry(entry) self.canvas().view().setForeGroundColor(color) def slot_fill_combobox(self): if self.currentPalette is None: pass palette = self.currentPalette self.colorComboBox.clear() self.colorList.clear() for i in range(palette.colorsCountTotal()): entry = palette.colorSetEntryByIndex(i) color = palette.colorForEntry(entry).colorForCanvas(self.canvas()) colorSquare = QPixmap(12, 12) if entry.spotColor is True: img = colorSquare.toImage() circlePainter = QPainter() img.fill(self.colorComboBox.palette().color(QPalette.Base)) circlePainter.begin(img) brush = QBrush(Qt.SolidPattern) brush.setColor(color) circlePainter.setBrush(brush) circlePainter.drawEllipse(0, 0, 11, 11) circlePainter.end() colorSquare = QPixmap.fromImage(img) else: colorSquare.fill(color) name = entry.name if len(entry.id)>0: name = entry.id+" - "+entry.name self.colorList.append(name) self.colorComboBox.addItem(QIcon(colorSquare), name) self.colorComboBox.setEditable(True) self.colorComboBox.setInsertPolicy(QComboBox.NoInsert) self.colorComboBox.completer().setCompletionMode(QCompleter.PopupCompletion) self.colorComboBox.completer().setCaseSensitivity(False) self.colorComboBox.completer().setFilterMode(Qt.MatchContains) self.colorComboBox.currentIndexChanged.connect(self.slot_get_color_from_combobox) def slot_get_color_from_combobox(self): if self.currentPalette is not None: entry = self.currentPalette.colorSetEntryByIndex(self.colorComboBox.currentIndex()) self.slot_swatchSelected(entry) def slot_add_entry(self): if (self.canvas()) is not None: if (self.canvas().view()) is not None: color = self.canvas().view().foreGroundColor() succes = self.paletteView.addEntryWithDialog(color) if succes is True: self.slot_fill_combobox() def slot_add_group(self): succes = self.paletteView.addGroupWithDialog() if succes is True: self.slot_fill_combobox() def slot_remove_entry(self): succes = self.paletteView.removeSelectedEntryWithDialog() if succes is True: self.slot_fill_combobox() def canvasChanged(self, canvas): pass #Add docker to the application :) Application.addDockWidgetFactory(DockWidgetFactory("palette_docker", DockWidgetFactoryBase.DockRight, Palette_Docker)) diff --git a/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/__init__.py b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/__init__.py new file mode 100644 index 0000000000..0d88fbd7ea --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/__init__.py @@ -0,0 +1,2 @@ + # let's make a module +from .quick_settings_docker import * diff --git a/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/kritapykrita_quick_settings_docker.desktop b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/kritapykrita_quick_settings_docker.desktop new file mode 100644 index 0000000000..a0fb1bf2bb --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/kritapykrita_quick_settings_docker.desktop @@ -0,0 +1,23 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Krita/PythonPlugin +X-KDE-Library=quick_settings_docker +X-Python-2-Compatible=false +Name=Quick Settings Docker +Name[ca]=Acoblador d'arranjament ràpid +Name[ca@valencia]=Acoblador d'arranjament ràpid +Name[es]=Panel de ajustes rápidos +Name[nl]=Docker voor snelle instellingen +Name[pt]=Área de Configuração Rápida +Name[sv]=Dockningspanel med snabbinställningar +Name[uk]=Панель швидких параметрів +Name[x-test]=xxQuick Settings Dockerxx +Comment=A Python-based docker for quickly changing brush size and opacity. +Comment[ca]=Un acoblador basant en el Python per canviar ràpidament la mida del pinzell i l'opacitat. +Comment[ca@valencia]=Un acoblador basant en el Python per canviar ràpidament la mida del pinzell i l'opacitat. +Comment[es]=Un panel basado en Python para cambiar rápidamente el tamaño y la opacidad del pincel. +Comment[nl]=Een op Python gebaseerde docker voor snel wijzigen van penseelgrootte en dekking. +Comment[pt]=Uma área acoplável em Python para mudar rapidamente o tamanho e opacidade do pincel. +Comment[sv]=En Python-baserad dockningspanel för att snabbt ändra penselstorlek och ogenomskinlighet. +Comment[uk]=Панель на основі мови програмування Python для швидкої зміни розміру та непрозорості пензля. +Comment[x-test]=xxA Python-based docker for quickly changing brush size and opacity.xx diff --git a/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/quick_settings_docker.py b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/quick_settings_docker.py new file mode 100644 index 0000000000..86015be5e8 --- /dev/null +++ b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/quick_settings_docker.py @@ -0,0 +1,160 @@ +''' +Description: A Python based docker for quickly choosing the brushsize like similar dockers in other drawing programs. + +By Wolthera + +@package quick_settings_docker +''' + +# Importing the relevant dependancies: +import sys +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from krita import * + +class QuickSettingsDocker(DockWidget): +#Init the docker + def __init__(self): + super().__init__() + # make base-widget and layout + widget = QWidget() + layout = QVBoxLayout() + widget.setLayout(layout) + self.setWindowTitle("Quick Settings Docker") + tabWidget = QTabWidget() + + self.brushSizeTableView = QTableView() + self.brushSizeTableView.verticalHeader().hide() + self.brushSizeTableView.horizontalHeader().hide() + self.brushSizeTableView.setSelectionMode(QTableView.SingleSelection) + + self.brushOpacityTableView = QTableView() + self.brushOpacityTableView.verticalHeader().hide() + self.brushOpacityTableView.horizontalHeader().hide() + self.brushOpacityTableView.setSelectionMode(QTableView.SingleSelection) + + self.brushFlowTableView = QTableView() + self.brushFlowTableView.verticalHeader().hide() + self.brushFlowTableView.horizontalHeader().hide() + self.brushFlowTableView.setSelectionMode(QTableView.SingleSelection) + + tabWidget.addTab(self.brushSizeTableView, "Size") + tabWidget.addTab(self.brushOpacityTableView, "Opacity") + tabWidget.addTab(self.brushFlowTableView, "Flow") + layout.addWidget(tabWidget) + self.setWidget(widget) #Add the widget to the docker. + + #amount of columns in each row for all the tables. + self.columns = 4 + + # We want a grid with possible options to select. + # To do this, we'll make a TableView widget and use a standarditemmodel for the entries. + # The entries are filled out based on the sizes and opacity lists. + + #Sizes and opacity lists. The former is half-way copied from ptsai, the latter is based on personal experience of useful opacities. + self.sizesList = [0.7, 1.0, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 20, 25, 30, 35, 40, 50, 60, 70, 80, 100, 120, 160, 200, 250, 300, 350, 400, 450, 500] + self.opacityList = [0.1, 0.5, 1, 5, 10, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100] + self.brushSizeModel = QStandardItemModel( (len(self.sizesList)/self.columns)+1 , self.columns) + self.brushOpacityModel = QStandardItemModel( (len(self.opacityList)/self.columns)+1 , self.columns ) + self.brushFlowModel = QStandardItemModel( (len(self.opacityList)/self.columns)+1 , self.columns ) + self.fillSizesModel() + self.fillOpacityModel() + + # Now we're done filling out our tables, we connect the views to the functions that'll change the settings. + self.brushSizeTableView.clicked.connect(self.setBrushSize) + self.brushOpacityTableView.clicked.connect(self.setBrushOpacity) + self.brushFlowTableView.clicked.connect(self.setBrushFlow) + + + def fillSizesModel(self): + #First we empty the old model. We might wanna use this function in the future to fill it with the brushmask of the selected brush, but there's currently no API for recognising changes in the current brush nor is there a way to get its brushmask. + self.brushSizeModel.clear() + for s in range(len(self.sizesList)): + # we're gonna itterate over our list, and make a new item for each entry. + # We need to disable a bunch of stuff to make sure people won't do funny things to our entries. + item = QStandardItem() + item.setCheckable(False) + item.setEditable(False) + item.setDragEnabled(False) + item.setText(str(self.sizesList[s])) + # And from here on we'll make an icon. + brushImage = QPixmap(32, 32) + img = brushImage.toImage() + circlePainter = QPainter() + img.fill(self.brushSizeTableView.palette().color(QPalette.Base)) + circlePainter.begin(img) + brush = QBrush(Qt.SolidPattern) + brush.setColor(self.brushSizeTableView.palette().color(QPalette.Text)) + circlePainter.setBrush(brush) + circlePainter.pen().setWidth(0) + brushSize = max(min((self.sizesList[s]/500)*100, 32), 1) + brushSize = brushSize*0.5 + circlePainter.drawEllipse(QPointF(16, 16), brushSize, brushSize) + circlePainter.end() + brushImage = QPixmap.fromImage(img) + # now we're done with drawing the icon, so we set it on the item. + item.setIcon(QIcon(brushImage)) + self.brushSizeModel.setItem(s/4, s%4, item) + self.brushSizeTableView.setModel(self.brushSizeModel) + self.brushSizeTableView.resizeColumnsToContents() + + def fillOpacityModel(self): + self.brushOpacityModel.clear() + self.brushFlowModel.clear() + for s in range(len(self.opacityList)): + # we're gonna itterate over our list, and make a new item for each entry. + item = QStandardItem() + item.setCheckable(False) + item.setEditable(False) + item.setDragEnabled(False) + item.setText(str(self.opacityList[s])) + brushImage = QPixmap(32, 32) + img = brushImage.toImage() + circlePainter = QPainter() + img.fill(self.brushSizeTableView.palette().color(QPalette.Base)) + circlePainter.begin(img) + brush = QBrush(Qt.SolidPattern) + brush.setColor(self.brushSizeTableView.palette().color(QPalette.Text)) + circlePainter.setBrush(brush) + circlePainter.pen().setWidth(0) + circlePainter.setOpacity(self.opacityList[s]/100) + circlePainter.drawEllipse(QPointF(16, 16), 16, 16) + circlePainter.end() + brushImage = QPixmap.fromImage(img) + item.setIcon(QIcon(brushImage)) + # the flow and opacity models will use virtually the same items, but Qt would like us to make sure we understand + # these are not really the same items, so hence the clone. + itemFlow = item.clone() + self.brushOpacityModel.setItem(s/4, s%4, item) + self.brushFlowModel.setItem(s/4, s%4, itemFlow) + self.brushOpacityTableView.setModel(self.brushOpacityModel) + self.brushFlowTableView.setModel(self.brushFlowModel) + self.brushFlowTableView.resizeColumnsToContents() + self.brushOpacityTableView.resizeColumnsToContents() + + def canvasChanged(self, canvas): + pass + + @pyqtSlot('QModelIndex') + def setBrushSize(self, index): + i = index.column()+(index.row()*self.columns) + brushSize = self.sizesList[i] + if Application.activeWindow() and len(Application.activeWindow().views()) > 0: + Application.activeWindow().views()[0].setBrushSize(brushSize); + @pyqtSlot('QModelIndex') + def setBrushOpacity(self, index): + i = index.column()+(index.row()*self.columns) + brushOpacity = self.opacityList[i]/100 + if Application.activeWindow() and len(Application.activeWindow().views()) > 0: + Application.activeWindow().views()[0].setPaintingOpacity(brushOpacity); + @pyqtSlot('QModelIndex') + def setBrushFlow(self, index): + i = index.column()+(index.row()*self.columns) + brushOpacity = self.opacityList[i]/100 + if Application.activeWindow() and len(Application.activeWindow().views()) > 0: + Application.activeWindow().views()[0].setPaintingFlow(brushOpacity); + + +#Add docker to the application :) +Application.addDockWidgetFactory(DockWidgetFactory("quick_settings_docker", DockWidgetFactoryBase.DockRight, QuickSettingsDocker)) + diff --git a/plugins/extensions/qmic/QMic.cpp b/plugins/extensions/qmic/QMic.cpp index ac78fee05a..e6a3b7d988 100644 --- a/plugins/extensions/qmic/QMic.cpp +++ b/plugins/extensions/qmic/QMic.cpp @@ -1,483 +1,494 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "QMic.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 "kis_input_output_mapper.h" #include "kis_qmic_simple_convertor.h" #include "kis_import_qmic_processing_visitor.h" #include #include "kis_qmic_applicator.h" #include "kis_qmic_progress_manager.h" static const char ack[] = "ack"; K_PLUGIN_FACTORY_WITH_JSON(QMicFactory, "kritaqmic.json", registerPlugin();) QMic::QMic(QObject *parent, const QVariantList &) : KisViewPlugin(parent) , m_gmicApplicator(0) , m_progressManager(0) { KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); PluginSettingsFactory* settingsFactory = new PluginSettingsFactory(); preferenceSetRegistry->add("QMicPluginSettingsFactory", settingsFactory); m_qmicAction = createAction("QMic"); m_qmicAction->setActivationFlags(KisAction::ACTIVE_DEVICE); connect(m_qmicAction , SIGNAL(triggered()), this, SLOT(slotQMic())); m_againAction = createAction("QMicAgain"); m_againAction->setActivationFlags(KisAction::ACTIVE_DEVICE); m_againAction->setEnabled(false); connect(m_againAction, SIGNAL(triggered()), this, SLOT(slotQMicAgain())); m_gmicApplicator = new KisQmicApplicator(); connect(m_gmicApplicator, SIGNAL(gmicFinished(bool, int, QString)), this, SLOT(slotGmicFinished(bool, int, QString))); } QMic::~QMic() { Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) { qDebug() << "detaching" << memorySegment->key(); memorySegment->detach(); } qDeleteAll(m_sharedMemorySegments); m_sharedMemorySegments.clear(); if (m_pluginProcess) { m_pluginProcess->close(); } delete m_gmicApplicator; delete m_progressManager; delete m_localServer; } void QMic::slotQMicAgain() { slotQMic(true); } void QMic::slotQMic(bool again) { m_qmicAction->setEnabled(false); m_againAction->setEnabled(false); if (m_pluginProcess) { qDebug() << "Plugin is already started" << m_pluginProcess->state(); return; } delete m_progressManager; m_progressManager = new KisQmicProgressManager(m_view); connect(m_progressManager, SIGNAL(sigProgress()), this, SLOT(slotUpdateProgress())); // find the krita-gmic-qt plugin KisConfig cfg; QString pluginPath = cfg.readEntry("gmic_qt_plugin_path", QString::null); if (pluginPath.isEmpty() || !QFileInfo(pluginPath).exists()) { KoDialog dlg; dlg.setWindowTitle(i18nc("@title:Window", "Krita")); QWidget *w = new QWidget(&dlg); dlg.setMainWidget(w); QVBoxLayout *l = new QVBoxLayout(w); l->addWidget(new PluginSettings(w)); dlg.setButtons(KoDialog::Ok); dlg.exec(); pluginPath = cfg.readEntry("gmic_qt_plugin_path", QString::null); if (pluginPath.isEmpty() || !QFileInfo(pluginPath).exists()) { return; } } m_key = QUuid::createUuid().toString(); m_localServer = new QLocalServer(); m_localServer->listen(m_key); connect(m_localServer, SIGNAL(newConnection()), SLOT(connected())); m_pluginProcess = new QProcess(this); m_pluginProcess->setProcessChannelMode(QProcess::ForwardedChannels); connect(m_pluginProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(pluginFinished(int,QProcess::ExitStatus))); connect(m_pluginProcess, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(pluginStateChanged(QProcess::ProcessState))); m_pluginProcess->start(pluginPath, QStringList() << m_key << (again ? QString(" reapply") : QString::null)); bool r = m_pluginProcess->waitForStarted(); while (m_pluginProcess->waitForFinished(10)) { qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } qDebug() << "Plugin started" << r << m_pluginProcess->state(); } void QMic::connected() { qDebug() << "connected"; QLocalSocket *socket = m_localServer->nextPendingConnection(); if (!socket) { return; } while (socket->bytesAvailable() < static_cast(sizeof(quint32))) { if (!socket->isValid()) { // stale request return; } socket->waitForReadyRead(1000); } QDataStream ds(socket); QByteArray msg; quint32 remaining; ds >> remaining; msg.resize(remaining); int got = 0; char* uMsgBuf = msg.data(); do { got = ds.readRawData(uMsgBuf, remaining); remaining -= got; uMsgBuf += got; } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); if (got < 0) { qWarning() << "Message reception failed" << socket->errorString(); delete socket; m_localServer->close(); delete m_localServer; m_localServer = 0; return; } QString message = QString::fromUtf8(msg); qDebug() << "Received" << message; // Check the message: we can get three different ones QMultiMap messageMap; Q_FOREACH(QString line, message.split('\n', QString::SkipEmptyParts)) { QList kv = line.split('=', QString::SkipEmptyParts); if (kv.size() == 2) { messageMap.insert(kv[0], kv[1]); } else { qWarning() << "line" << line << "is invalid."; } } if (!messageMap.contains("command")) { qWarning() << "Message did not contain a command"; return; } int mode = 0; if (messageMap.contains("mode")) { mode = messageMap.values("mode").first().toInt(); } QByteArray ba; if (messageMap.values("command").first() == "gmic_qt_get_image_size") { KisSelectionSP selection = m_view->image()->globalSelection(); if (selection) { QRect selectionRect = selection->selectedExactRect(); ba = QByteArray::number(selectionRect.width()) + "," + QByteArray::number(selectionRect.height()); } else { ba = QByteArray::number(m_view->image()->width()) + "," + QByteArray::number(m_view->image()->height()); } } else if (messageMap.values("command").first() == "gmic_qt_get_cropped_images") { // Parse the message, create the shared memory segments, and create a new message to send back and waid for ack QRectF cropRect(0.0, 0.0, 1.0, 1.0); if (!messageMap.contains("croprect") || messageMap.values("croprect").first().split(',', QString::SkipEmptyParts).size() != 4) { qWarning() << "gmic-qt didn't send a croprect or not a valid croprect"; } else { QStringList cr = messageMap.values("croprect").first().split(',', QString::SkipEmptyParts); cropRect.setX(cr[0].toFloat()); cropRect.setY(cr[1].toFloat()); cropRect.setWidth(cr[2].toFloat()); cropRect.setHeight(cr[3].toFloat()); } if (!prepareCroppedImages(&ba, cropRect, mode)) { qWarning() << "Failed to prepare images for gmic-qt"; } } else if (messageMap.values("command").first() == "gmic_qt_output_images") { // Parse the message. read the shared memory segments, fix up the current image and send an ack qDebug() << "gmic_qt_output_images"; QStringList layers = messageMap.values("layer"); m_outputMode = (OutputMode)mode; if (m_outputMode != IN_PLACE) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Sorry, this output mode is not implemented yet.")); m_outputMode = IN_PLACE; } slotStartApplicator(layers); } else if (messageMap.values("command").first() == "gmic_qt_detach") { Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) { qDebug() << "detaching" << memorySegment->key() << memorySegment->isAttached(); if (memorySegment->isAttached()) { if (!memorySegment->detach()) { qDebug() << "\t" << memorySegment->error() << memorySegment->errorString(); } } } qDeleteAll(m_sharedMemorySegments); m_sharedMemorySegments.clear(); } else { qWarning() << "Received unknown command" << messageMap.values("command"); } qDebug() << "Sending" << QString::fromUtf8(ba); ds.writeBytes(ba.constData(), ba.length()); + // Flush the socket because we might not return to the event loop! + if (!socket->waitForBytesWritten(2000)) { + qWarning() << "Failed to write response:" << socket->error(); + } // Wait for the ack bool r = true; - r &= socket->waitForReadyRead(); // wait for ack + r &= socket->waitForReadyRead(2000); // wait for ack r &= (socket->read(qstrlen(ack)) == ack); - socket->waitForDisconnected(-1); + if (!socket->waitForDisconnected(2000)) { + qWarning() << "Remote not disconnected:" << socket->error(); + // Wait again + socket->disconnectFromServer(); + if (socket->waitForDisconnected(2000)) { + qWarning() << "Disconnect timed out:" << socket->error(); + } + } } void QMic::pluginStateChanged(QProcess::ProcessState state) { qDebug() << "stateChanged" << state; } void QMic::pluginFinished(int exitCode, QProcess::ExitStatus exitStatus) { qDebug() << "pluginFinished" << exitCode << exitStatus; delete m_pluginProcess; m_pluginProcess = 0; delete m_localServer; m_localServer = 0; delete m_progressManager; m_progressManager = 0; m_qmicAction->setEnabled(true); m_againAction->setEnabled(true); } void QMic::slotUpdateProgress() { if (!m_gmicApplicator) { qWarning() << "G'Mic applicator already deleted!"; return; } qDebug() << "slotUpdateProgress" << m_gmicApplicator->getProgress(); m_progressManager->updateProgress(m_gmicApplicator->getProgress()); } void QMic::slotStartProgressReporting() { qDebug() << "slotStartProgressReporting();"; if (m_progressManager->inProgress()) { m_progressManager->finishProgress(); } m_progressManager->initProgress(); } void QMic::slotGmicFinished(bool successfully, int milliseconds, const QString &msg) { qDebug() << "slotGmicFinished();" << successfully << milliseconds << msg; if (successfully) { m_gmicApplicator->finish(); } else { m_gmicApplicator->cancel(); QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("G'Mic failed, reason:") + msg); } } void QMic::slotStartApplicator(QStringList gmicImages) { qDebug() << "slotStartApplicator();" << gmicImages; // Create a vector of gmic images QVector *> images; Q_FOREACH(const QString &image, gmicImages) { QStringList parts = image.split(',', QString::SkipEmptyParts); Q_ASSERT(parts.size() == 4); QString key = parts[0]; QString layerName = QByteArray::fromHex(parts[1].toLatin1()); int spectrum = parts[2].toInt(); int width = parts[3].toInt(); int height = parts[4].toInt(); qDebug() << key << layerName << width << height; QSharedMemory m(key); if (!m.attach(QSharedMemory::ReadOnly)) { qWarning() << "Could not attach to shared memory area." << m.error() << m.errorString(); } if (m.isAttached()) { if (!m.lock()) { qDebug() << "Could not lock memeory segment" << m.error() << m.errorString(); } qDebug() << "Memory segment" << key << m.size() << m.constData() << m.data(); gmic_image *gimg = new gmic_image(); gimg->assign(width, height, 1, spectrum); gimg->name = layerName; gimg->_data = new float[width * height * spectrum * sizeof(float)]; qDebug() << "width" << width << "height" << height << "size" << width * height * spectrum * sizeof(float) << "shared memory size" << m.size(); memcpy(gimg->_data, m.constData(), width * height * spectrum * sizeof(float)); qDebug() << "created gmic image" << gimg->name << gimg->_width << gimg->_height; if (!m.unlock()) { qDebug() << "Could not unlock memeory segment" << m.error() << m.errorString(); } if (!m.detach()) { qDebug() << "Could not detach from memeory segment" << m.error() << m.errorString(); } images.append(gimg); } } qDebug() << "Got" << images.size() << "gmic images"; // Start the applicator KUndo2MagicString actionName = kundo2_i18n("Gmic filter"); KisNodeSP rootNode = m_view->image()->root(); KisInputOutputMapper mapper(m_view->image(), m_view->activeNode()); KisNodeListSP layers = mapper.inputNodes(m_inputMode); m_gmicApplicator->setProperties(m_view->image(), rootNode, images, actionName, layers); slotStartProgressReporting(); m_gmicApplicator->preview(); m_gmicApplicator->finish(); } bool QMic::prepareCroppedImages(QByteArray *message, QRectF &rc, int inputMode) { m_view->image()->lock(); m_inputMode = (InputLayerMode)inputMode; qDebug() << "prepareCroppedImages()" << QString::fromUtf8(*message) << rc << inputMode; KisInputOutputMapper mapper(m_view->image(), m_view->activeNode()); KisNodeListSP nodes = mapper.inputNodes(m_inputMode); if (nodes->isEmpty()) { m_view->image()->unlock(); return false; } for (int i = 0; i < nodes->size(); ++i) { KisNodeSP node = nodes->at(i); if (node->paintDevice()) { QRect cropRect; KisSelectionSP selection = m_view->image()->globalSelection(); if (selection) { cropRect = selection->selectedExactRect(); } else { cropRect = m_view->image()->bounds(); } qDebug() << "Converting node" << node->name() << cropRect; const QRectF mappedRect = KisAlgebra2D::mapToRect(cropRect).mapRect(rc); const QRect resultRect = mappedRect.toAlignedRect(); QSharedMemory *m = new QSharedMemory(QString("key_%1").arg(QUuid::createUuid().toString())); m_sharedMemorySegments.append(m); if (!m->create(resultRect.width() * resultRect.height() * 4 * sizeof(float))) { //buf.size())) { qWarning() << "Could not create shared memory segment" << m->error() << m->errorString(); return false; } m->lock(); gmic_image img; img.assign(resultRect.width(), resultRect.height(), 1, 4); img._data = reinterpret_cast(m->data()); KisQmicSimpleConvertor::convertToGmicImageFast(node->paintDevice(), &img, resultRect); message->append(m->key().toUtf8()); m->unlock(); qDebug() << "size" << m->size(); message->append(","); message->append(node->name().toUtf8().toHex()); message->append(","); message->append(QByteArray::number(resultRect.width())); message->append(","); message->append(QByteArray::number(resultRect.height())); message->append("\n"); } } qDebug() << QString::fromUtf8(*message); m_view->image()->unlock(); return true; } #include "QMic.moc" diff --git a/plugins/filters/colorsfilters/kis_perchannel_filter.cpp b/plugins/filters/colorsfilters/kis_perchannel_filter.cpp index 5d92ce494a..dc31bfd439 100644 --- a/plugins/filters/colorsfilters/kis_perchannel_filter.cpp +++ b/plugins/filters/colorsfilters/kis_perchannel_filter.cpp @@ -1,598 +1,613 @@ /* * This file is part of Krita * * Copyright (c) 2005 C. Boemann * * 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_perchannel_filter.h" #include #include #include #include #include #include #include #include #include "KoChannelInfo.h" #include "KoBasicHistogramProducers.h" #include "KoColorModelStandardIds.h" #include "KoColorSpace.h" #include "KoColorTransformation.h" #include "KoCompositeColorTransformation.h" #include "KoCompositeOp.h" #include "KoID.h" #include "kis_signals_blocker.h" #include "kis_bookmarked_configuration_manager.h" #include "kis_config_widget.h" #include #include #include #include #include "kis_histogram.h" #include "kis_painter.h" #include "widgets/kis_curve_widget.h" QVector getVirtualChannels(const KoColorSpace *cs) { const bool supportsLightness = cs->colorModelId() != LABAColorModelID && cs->colorModelId() != GrayAColorModelID && cs->colorModelId() != GrayColorModelID && cs->colorModelId() != AlphaColorModelID; QVector vchannels; QList sortedChannels = KoChannelInfo::displayOrderSorted(cs->channels()); if (supportsLightness) { vchannels << VirtualChannelInfo(VirtualChannelInfo::ALL_COLORS, -1, 0, cs); } Q_FOREACH (KoChannelInfo *channel, sortedChannels) { int pixelIndex = KoChannelInfo::displayPositionToChannelIndex(channel->displayPosition(), sortedChannels); vchannels << VirtualChannelInfo(VirtualChannelInfo::REAL, pixelIndex, channel, cs); } if (supportsLightness) { vchannels << VirtualChannelInfo(VirtualChannelInfo::LIGHTNESS, -1, 0, cs); } return vchannels; } KisPerChannelConfigWidget::KisPerChannelConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WFlags f) : KisConfigWidget(parent, f), m_histogram(0) { Q_ASSERT(dev); m_page = new WdgPerChannel(this); QHBoxLayout * layout = new QHBoxLayout(this); Q_CHECK_PTR(layout); layout->setContentsMargins(0,0,0,0); layout->addWidget(m_page); m_dev = dev; m_activeVChannel = 0; const KoColorSpace *targetColorSpace = dev->compositionSourceColorSpace(); // fill in the channel chooser, in the display order, but store the pixel index as well. m_virtualChannels = getVirtualChannels(targetColorSpace); const int virtualChannelCount = m_virtualChannels.size(); KisPerChannelFilterConfiguration::initDefaultCurves(m_curves, virtualChannelCount); for (int i = 0; i < virtualChannelCount; i++) { const VirtualChannelInfo &info = m_virtualChannels[i]; m_page->cmbChannel->addItem(info.name(), info.pixelIndex()); m_curves[i].setName(info.name()); } connect(m_page->cmbChannel, SIGNAL(activated(int)), this, SLOT(setActiveChannel(int))); + connect((QObject*)(m_page->chkLogarithmic), SIGNAL(toggled(bool)), this, SLOT(logHistView())); + // create the horizontal and vertical gradient labels m_page->hgradient->setPixmap(createGradient(Qt::Horizontal)); m_page->vgradient->setPixmap(createGradient(Qt::Vertical)); // init histogram calculator QList keys = KoHistogramProducerFactoryRegistry::instance()->keysCompatibleWith(targetColorSpace); if(keys.size() > 0) { KoHistogramProducerFactory *hpf; hpf = KoHistogramProducerFactoryRegistry::instance()->get(keys.at(0)); m_histogram = new KisHistogram(m_dev, m_dev->exactBounds(), hpf->generate(), LINEAR); } connect(m_page->curveWidget, SIGNAL(modified()), this, SIGNAL(sigConfigurationItemChanged())); m_page->curveWidget->setupInOutControls(m_page->intIn, m_page->intOut, 0, 100); { KisSignalsBlocker b(m_page->curveWidget); m_page->curveWidget->setCurve(m_curves[0]); setActiveChannel(0); } } KisPerChannelConfigWidget::~KisPerChannelConfigWidget() { delete m_histogram; } inline QPixmap KisPerChannelConfigWidget::createGradient(Qt::Orientation orient /*, int invert (not used yet) */) { int width; int height; int *i, inc, col; int x = 0, y = 0; if (orient == Qt::Horizontal) { i = &x; inc = 1; col = 0; width = 256; height = 1; } else { i = &y; inc = -1; col = 255; width = 1; height = 256; } QPixmap gradientpix(width, height); QPainter p(&gradientpix); p.setPen(QPen(QColor(0, 0, 0), 1, Qt::SolidLine)); for (; *i < 256; (*i)++, col += inc) { p.setPen(QColor(col, col, col)); p.drawPoint(x, y); } return gradientpix; } inline QPixmap KisPerChannelConfigWidget::getHistogram() { int i; int height = 256; QPixmap pix(256, height); + bool logarithmic = m_page->chkLogarithmic->isChecked(); + + if (logarithmic) + m_histogram->setHistogramType(LOGARITHMIC); + else + m_histogram->setHistogramType(LINEAR); + + QPalette appPalette = QApplication::palette(); pix.fill(QColor(appPalette.color(QPalette::Base))); QPainter p(&pix); p.setPen(QColor(appPalette.color(QPalette::Text))); p.save(); p.setOpacity(0.2); const VirtualChannelInfo &info = m_virtualChannels[m_activeVChannel]; if (m_histogram && info.type() == VirtualChannelInfo::REAL) { m_histogram->setChannel(info.pixelIndex()); double highest = (double)m_histogram->calculations().getHighest(); qint32 bins = m_histogram->producer()->numberOfBins(); if (m_histogram->getHistogramType() == LINEAR) { double factor = (double)height / highest; for (i = 0; i < bins; ++i) { p.drawLine(i, height, i, height - int(m_histogram->getValue(i) * factor)); } } else { double factor = (double)height / (double)log(highest); for (i = 0; i < bins; ++i) { p.drawLine(i, height, i, height - int(log((double)m_histogram->getValue(i)) * factor)); } } } p.restore(); return pix; } #define BITS_PER_BYTE 8 #define pwr2(p) (1<curveWidget->curve(); m_activeVChannel = ch; m_page->curveWidget->setCurve(m_curves[m_activeVChannel]); m_page->curveWidget->setPixmap(getHistogram()); m_page->cmbChannel->setCurrentIndex(m_activeVChannel); // Getting range accepted by channel VirtualChannelInfo ¤tVChannel = m_virtualChannels[m_activeVChannel]; KoChannelInfo::enumChannelValueType valueType = currentVChannel.valueType(); int order = BITS_PER_BYTE * currentVChannel.channelSize(); int maxValue = pwr2(order); int min; int max; m_page->curveWidget->dropInOutControls(); switch (valueType) { case KoChannelInfo::UINT8: case KoChannelInfo::UINT16: case KoChannelInfo::UINT32: m_shift = 0; m_scale = double(maxValue); min = 0; max = maxValue - 1; break; case KoChannelInfo::INT8: case KoChannelInfo::INT16: m_shift = 0.5; m_scale = double(maxValue); min = -maxValue / 2; max = maxValue / 2 - 1; break; case KoChannelInfo::FLOAT16: case KoChannelInfo::FLOAT32: case KoChannelInfo::FLOAT64: default: m_shift = 0; m_scale = 100.0; //Hack Alert: should be changed to float min = 0; max = 100; break; } m_page->curveWidget->setupInOutControls(m_page->intIn, m_page->intOut, min, max); } KisPropertiesConfigurationSP KisPerChannelConfigWidget::configuration() const { int numChannels = m_virtualChannels.size(); KisPropertiesConfigurationSP cfg = new KisPerChannelFilterConfiguration(numChannels); KIS_ASSERT_RECOVER(m_activeVChannel < m_curves.size()) { return cfg; } m_curves[m_activeVChannel] = m_page->curveWidget->curve(); static_cast(cfg.data())->setCurves(m_curves); return cfg; } void KisPerChannelConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config) { const KisPerChannelFilterConfiguration * cfg = dynamic_cast(config.data()); if (!cfg) return; if (cfg->curves().size() == 0) { /** * HACK ALERT: our configuration factory generates * default configuration with nTransfers==0. * Catching it here. Just reset all the transfers. */ const int virtualChannelCount = m_virtualChannels.size(); KisPerChannelFilterConfiguration::initDefaultCurves(m_curves, virtualChannelCount); for (int i = 0; i < virtualChannelCount; i++) { const VirtualChannelInfo &info = m_virtualChannels[i]; m_curves[i].setName(info.name()); } } else if (cfg->curves().size() != int(m_virtualChannels.size())) { warnKrita << "WARNING: trying to load a curve with incorrect number of channels!"; warnKrita << "WARNING: expected:" << m_virtualChannels.size(); warnKrita << "WARNING: got:" << cfg->curves().size(); return; } else { for (int ch = 0; ch < cfg->curves().size(); ch++) m_curves[ch] = cfg->curves()[ch]; } // HACK: we save the previous curve in setActiveChannel, so just copy it m_page->curveWidget->setCurve(m_curves[m_activeVChannel]); setActiveChannel(0); } KisPerChannelFilterConfiguration::KisPerChannelFilterConfiguration(int nCh) : KisColorTransformationConfiguration("perchannel", 1) { initDefaultCurves(m_curves, nCh); updateTransfers(); } KisPerChannelFilterConfiguration::~KisPerChannelFilterConfiguration() { } bool KisPerChannelFilterConfiguration::isCompatible(const KisPaintDeviceSP dev) const { return (int)dev->compositionSourceColorSpace()->channelCount() == m_curves.size(); } void KisPerChannelFilterConfiguration::setCurves(QList &curves) { m_curves.clear(); m_curves = curves; updateTransfers(); } void KisPerChannelFilterConfiguration::initDefaultCurves(QList &curves, int nCh) { curves.clear(); for (int i = 0; i < nCh; i++) { curves.append(KisCubicCurve()); } } void KisPerChannelFilterConfiguration::updateTransfers() { m_transfers.resize(m_curves.size()); for (int i = 0; i < m_curves.size(); i++) { m_transfers[i] = m_curves[i].uint16Transfer(); } } const QVector >& KisPerChannelFilterConfiguration::transfers() const { return m_transfers; } const QList& KisPerChannelFilterConfiguration::curves() const { return m_curves; } void KisPerChannelFilterConfiguration::fromLegacyXML(const QDomElement& root) { fromXML(root); } void KisPerChannelFilterConfiguration::fromXML(const QDomElement& root) { QList curves; quint16 numTransfers = 0; int version; version = root.attribute("version").toInt(); QDomElement e = root.firstChild().toElement(); QString attributeName; KisCubicCurve curve; quint16 index; while (!e.isNull()) { if ((attributeName = e.attribute("name")) == "nTransfers") { numTransfers = e.text().toUShort(); } else { QRegExp rx("curve(\\d+)"); if (rx.indexIn(attributeName, 0) != -1) { index = rx.cap(1).toUShort(); index = qMin(index, quint16(curves.count())); if (!e.text().isEmpty()) { curve.fromString(e.text()); } curves.insert(index, curve); } } e = e.nextSiblingElement(); } if (!numTransfers) return; setVersion(version); setCurves(curves); } /** * Inherited from KisPropertiesConfiguration */ //void KisPerChannelFilterConfiguration::fromXML(const QString& s) void addParamNode(QDomDocument& doc, QDomElement& root, const QString &name, const QString &value) { QDomText text = doc.createTextNode(value); QDomElement t = doc.createElement("param"); t.setAttribute("name", name); t.appendChild(text); root.appendChild(t); } void KisPerChannelFilterConfiguration::toXML(QDomDocument& doc, QDomElement& root) const { /** * * 3 * 0,0;0.5,0.5;1,1; * 0,0;1,1; * 0,0;1,1; * */ root.setAttribute("version", version()); QDomText text; QDomElement t; addParamNode(doc, root, "nTransfers", QString::number(m_curves.size())); KisCubicCurve curve; QString paramName; for (int i = 0; i < m_curves.size(); ++i) { QString name = QLatin1String("curve") + QString::number(i); QString value = m_curves[i].toString(); addParamNode(doc, root, name, value); } } /** * Inherited from KisPropertiesConfiguration */ //QString KisPerChannelFilterConfiguration::toXML() KisPerChannelFilter::KisPerChannelFilter() : KisColorTransformationFilter(id(), categoryAdjust(), i18n("&Color Adjustment curves...")) { setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M)); setSupportsPainting(true); setColorSpaceIndependence(TO_LAB16); } KisConfigWidget * KisPerChannelFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev) const { return new KisPerChannelConfigWidget(parent, dev); } KisFilterConfigurationSP KisPerChannelFilter::factoryConfiguration() const { return new KisPerChannelFilterConfiguration(0); } KoColorTransformation* KisPerChannelFilter::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const { const KisPerChannelFilterConfiguration* configBC = dynamic_cast(config.data()); // Somehow, this shouldn't happen Q_ASSERT(configBC); const QVector > &originalTransfers = configBC->transfers(); const QList &originalCurves = configBC->curves(); /** * TODO: What about the order of channels? (DK) * * Virtual channels are sorted in display order, does Lcms accepts * transforms in display order? Why on Earth it works?! Is it * documented anywhere? */ const QVector virtualChannels = getVirtualChannels(cs); if (originalTransfers.size() != int(virtualChannels.size())) { // We got an illegal number of colorchannels :( return 0; } bool colorsNull = true; bool lightnessNull = true; bool allColorsNull = true; int alphaIndexInReal = -1; QVector > realTransfers; QVector lightnessTransfer; QVector allColorsTransfer; for (int i = 0; i < virtualChannels.size(); i++) { if (virtualChannels[i].type() == VirtualChannelInfo::REAL) { realTransfers << originalTransfers[i]; if (virtualChannels[i].isAlpha()) { alphaIndexInReal = realTransfers.size() - 1; } if (colorsNull && !originalCurves[i].isNull()) { colorsNull = false; } } else if (virtualChannels[i].type() == VirtualChannelInfo::LIGHTNESS) { KIS_ASSERT_RECOVER_NOOP(lightnessTransfer.isEmpty()); lightnessTransfer = originalTransfers[i]; if (lightnessNull && !originalCurves[i].isNull()) { lightnessNull = false; } } else if (virtualChannels[i].type() == VirtualChannelInfo::ALL_COLORS) { KIS_ASSERT_RECOVER_NOOP(allColorsTransfer.isEmpty()); allColorsTransfer = originalTransfers[i]; if (allColorsNull && !originalCurves[i].isNull()) { allColorsNull = false; } } } KoColorTransformation *lightnessTransform = 0; KoColorTransformation *allColorsTransform = 0; KoColorTransformation *colorTransform = 0; if (!colorsNull) { const quint16** transfers = new const quint16*[realTransfers.size()]; for(int i = 0; i < realTransfers.size(); ++i) { transfers[i] = realTransfers[i].constData(); /** * createPerChannelAdjustment() expects alpha channel to * be the last channel in the list, so just it here */ KIS_ASSERT_RECOVER_NOOP(i != alphaIndexInReal || alphaIndexInReal == (realTransfers.size() - 1)); } colorTransform = cs->createPerChannelAdjustment(transfers); delete [] transfers; } if (!lightnessNull) { lightnessTransform = cs->createBrightnessContrastAdjustment(lightnessTransfer.constData()); } if (!allColorsNull) { const quint16** allColorsTransfers = new const quint16*[realTransfers.size()]; for(int i = 0; i < realTransfers.size(); ++i) { allColorsTransfers[i] = (i != alphaIndexInReal) ? allColorsTransfer.constData() : 0; /** * createPerChannelAdjustment() expects alpha channel to * be the last channel in the list, so just it here */ KIS_ASSERT_RECOVER_NOOP(i != alphaIndexInReal || alphaIndexInReal == (realTransfers.size() - 1)); } allColorsTransform = cs->createPerChannelAdjustment(allColorsTransfers); delete[] allColorsTransfers; } QVector allTransforms; allTransforms << colorTransform; allTransforms << allColorsTransform; allTransforms << lightnessTransform; return KoCompositeColorTransformation::createOptimizedCompositeTransform(allTransforms); } bool KisPerChannelFilter::needsTransparentPixels(const KisFilterConfigurationSP config, const KoColorSpace *cs) const { Q_UNUSED(config); return cs->colorModelId() == AlphaColorModelID; } + +void KisPerChannelConfigWidget::logHistView() +{ + m_page->curveWidget->setPixmap(getHistogram()); +} diff --git a/plugins/filters/colorsfilters/kis_perchannel_filter.h b/plugins/filters/colorsfilters/kis_perchannel_filter.h index 2169279ccf..4844503100 100644 --- a/plugins/filters/colorsfilters/kis_perchannel_filter.h +++ b/plugins/filters/colorsfilters/kis_perchannel_filter.h @@ -1,135 +1,136 @@ /* * This file is part of Krita * * Copyright (c) 2004 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. */ #ifndef _KIS_PERCHANNEL_FILTER_H_ #define _KIS_PERCHANNEL_FILTER_H_ #include #include #include #include #include #include #include "ui_wdg_perchannel.h" #include "virtual_channel_info.h" class WdgPerChannel : public QWidget, public Ui::WdgPerChannel { Q_OBJECT public: WdgPerChannel(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class KisPerChannelFilterConfiguration : public KisColorTransformationConfiguration { public: KisPerChannelFilterConfiguration(int n); ~KisPerChannelFilterConfiguration() override; using KisFilterConfiguration::fromXML; using KisFilterConfiguration::toXML; using KisFilterConfiguration::fromLegacyXML; void fromLegacyXML(const QDomElement& root) override; void fromXML(const QDomElement& e) override; void toXML(QDomDocument& doc, QDomElement& root) const override; void setCurves(QList &curves) override; static inline void initDefaultCurves(QList &curves, int nCh); bool isCompatible(const KisPaintDeviceSP) const override; const QVector >& transfers() const; const QList& curves() const override; private: QList m_curves; private: void updateTransfers(); private: QVector > m_transfers; }; /** * This class is generic for filters that affect channel separately */ class KisPerChannelFilter : public KisColorTransformationFilter { public: KisPerChannelFilter(); public: KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev) const override; KisFilterConfigurationSP factoryConfiguration() const override; KoColorTransformation* createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override; bool needsTransparentPixels(const KisFilterConfigurationSP config, const KoColorSpace *cs) const override; static inline KoID id() { return KoID("perchannel", i18n("Color Adjustment")); } private: }; class KisPerChannelConfigWidget : public KisConfigWidget { Q_OBJECT public: KisPerChannelConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WFlags f = 0); ~KisPerChannelConfigWidget() override; void setConfiguration(const KisPropertiesConfigurationSP config) override; KisPropertiesConfigurationSP configuration() const override; private Q_SLOTS: virtual void setActiveChannel(int ch); + void logHistView(); private: QVector m_virtualChannels; int m_activeVChannel; // private routines inline QPixmap getHistogram(); inline QPixmap createGradient(Qt::Orientation orient /*, int invert (not used now) */); // members WdgPerChannel * m_page; KisPaintDeviceSP m_dev; KisHistogram *m_histogram; mutable QList m_curves; // scales for displaying color numbers double m_scale; double m_shift; }; #endif diff --git a/plugins/filters/colorsfilters/wdg_perchannel.ui b/plugins/filters/colorsfilters/wdg_perchannel.ui index f53aa0fd38..f4a695585d 100644 --- a/plugins/filters/colorsfilters/wdg_perchannel.ui +++ b/plugins/filters/colorsfilters/wdg_perchannel.ui @@ -1,338 +1,358 @@ WdgPerChannel 0 0 317 396 BrightnessCon 0 0 0 0 0 6 Qt::Vertical QSizePolicy::MinimumExpanding 9 9 QLayout::SetDefaultConstraint 0 0 0 256 256 256 256 QFrame::Panel QFrame::Sunken 0 0 0 0 0 0 0 256 256 256 256 0 0 256 20 256 20 QFrame::Panel QFrame::Sunken true 0 0 20 256 20 256 QFrame::Panel QFrame::Sunken true 0 0 Channel: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + Qt::Horizontal + + + + 10 + 10 + + + + + + + + Logarithmic + + + Qt::Horizontal 1 20 0 0 0 0 Qt::Horizontal QSizePolicy::MinimumExpanding 20 20 0 0 Output: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 0 0 Input: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::Horizontal 1 20 KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KisCurveWidget
widgets/kis_curve_widget.h
cmbChannel intIn intOut
diff --git a/plugins/filters/tests/kis_all_filter_test.cpp b/plugins/filters/tests/kis_all_filter_test.cpp index 8eed780ab0..09e0e1aa86 100644 --- a/plugins/filters/tests/kis_all_filter_test.cpp +++ b/plugins/filters/tests/kis_all_filter_test.cpp @@ -1,316 +1,316 @@ /* * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_all_filter_test.h" #include #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_selection.h" #include "kis_processing_information.h" #include "filter/kis_filter.h" #include "kis_pixel_selection.h" #include "kis_transaction.h" #include bool compareQImages(QPoint & pt, const QImage & image1, const QImage & image2) { // QTime t; // t.start(); int w1 = image1.width(); int h1 = image1.height(); int w2 = image2.width(); int h2 = image2.height(); if (w1 != w2 || h1 != h2) { dbgKrita << w1 << " " << w2 << " " << h1 << " " << h2; pt.setX(-1); pt.setY(-1); return false; } for (int x = 0; x < w1; ++x) { for (int y = 0; y < h1; ++y) { if (image1.pixel(x, y) != image2.pixel(x, y)) { pt.setX(x); pt.setY(y); return false; } } } // dbgKrita << "compareQImages time elapsed:" << t.elapsed(); return true; } bool testFilterSrcNotIsDev(KisFilterSP f) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QImage qimage(QString(FILES_DATA_DIR) + QDir::separator() + "carrot.png"); QImage result(QString(FILES_DATA_DIR) + QDir::separator() + "carrot_" + f->id() + ".png"); KisPaintDeviceSP dev = new KisPaintDevice(cs); KisPaintDeviceSP dstdev = new KisPaintDevice(cs); dev->convertFromQImage(qimage, 0, 0, 0); // Get the predefined configuration from a file KisFilterConfigurationSP kfc = f->defaultConfiguration(); QFile file(QString(FILES_DATA_DIR) + QDir::separator() + f->id() + ".cfg"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { //dbgKrita << "creating new file for " << f->id(); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << kfc->toXML(); } else { QString s; QTextStream in(&file); in.setCodec("UTF-8"); s = in.readAll(); //dbgKrita << "Read for " << f->id() << "\n" << s; kfc->fromXML(s); } dbgKrita << f->id();// << "\n" << kfc->toXML() << "\n"; f->process(dev, dstdev, 0, QRect(QPoint(0,0), qimage.size()), kfc); QPoint errpoint; if (!compareQImages(errpoint, result, dstdev->convertToQImage(0, 0, 0, qimage.width(), qimage.height()))) { dev->convertToQImage(0, 0, 0, qimage.width(), qimage.height()).save(QString("src_not_is_dst_carrot_%1.png").arg(f->id())); return false; } return true; } bool testFilterNoTransaction(KisFilterSP f) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QImage qimage(QString(FILES_DATA_DIR) + QDir::separator() + "carrot.png"); QImage result(QString(FILES_DATA_DIR) + QDir::separator() + "carrot_" + f->id() + ".png"); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->convertFromQImage(qimage, 0, 0, 0); // Get the predefined configuration from a file KisFilterConfigurationSP kfc = f->defaultConfiguration(); QFile file(QString(FILES_DATA_DIR) + QDir::separator() + f->id() + ".cfg"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { //dbgKrita << "creating new file for " << f->id(); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << kfc->toXML(); } else { QString s; QTextStream in(&file); in.setCodec("UTF-8"); s = in.readAll(); //dbgKrita << "Read for " << f->id() << "\n" << s; kfc->fromXML(s); } dbgKrita << f->id();// << "\n" << kfc->toXML() << "\n"; f->process(dev, QRect(QPoint(0,0), qimage.size()), kfc); QPoint errpoint; if (!compareQImages(errpoint, result, dev->convertToQImage(0, 0, 0, qimage.width(), qimage.height()))) { dev->convertToQImage(0, 0, 0, qimage.width(), qimage.height()).save(QString("no_transactio_carrot_%1.png").arg(f->id())); return false; } return true; } bool testFilter(KisFilterSP f) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QImage qimage(QString(FILES_DATA_DIR) + QDir::separator() + "carrot.png"); QString resultFileName = QString(FILES_DATA_DIR) + QDir::separator() + "carrot_" + f->id() + ".png"; QImage result(resultFileName); if (!QFileInfo(resultFileName).exists()) { dbgKrita << resultFileName << " not found"; return false; } KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->convertFromQImage(qimage, 0, 0, 0); KisTransaction * cmd = new KisTransaction(kundo2_noi18n(f->name()), dev); // Get the predefined configuration from a file KisFilterConfigurationSP kfc = f->defaultConfiguration(); QFile file(QString(FILES_DATA_DIR) + QDir::separator() + f->id() + ".cfg"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { //dbgKrita << "creating new file for " << f->id(); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << kfc->toXML(); } else { QString s; QTextStream in(&file); in.setCodec("UTF-8"); s = in.readAll(); //dbgKrita << "Read for " << f->id() << "\n" << s; kfc->fromXML(s); } dbgKrita << f->id();// << "\n" << kfc->toXML() << "\n"; f->process(dev, QRect(QPoint(0,0), qimage.size()), kfc); QPoint errpoint; delete cmd; if (!compareQImages(errpoint, result, dev->convertToQImage(0, 0, 0, qimage.width(), qimage.height()))) { dbgKrita << errpoint; dev->convertToQImage(0, 0, 0, qimage.width(), qimage.height()).save(QString("carrot_%1.png").arg(f->id())); return false; } return true; } bool testFilterWithSelections(KisFilterSP f) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QImage qimage(QString(FILES_DATA_DIR) + QDir::separator() + "carrot.png"); QImage result(QString(FILES_DATA_DIR) + QDir::separator() + "carrot_" + f->id() + ".png"); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->convertFromQImage(qimage, 0, 0, 0); // Get the predefined configuration from a file KisFilterConfigurationSP kfc = f->defaultConfiguration(); QFile file(QString(FILES_DATA_DIR) + QDir::separator() + f->id() + ".cfg"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { //dbgKrita << "creating new file for " << f->id(); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << kfc->toXML(); } else { QString s; QTextStream in(&file); in.setCodec("UTF-8"); s = in.readAll(); //dbgKrita << "Read for " << f->id() << "\n" << s; kfc->fromXML(s); } dbgKrita << f->id();// << "\n"; << kfc->toXML() << "\n"; KisSelectionSP sel1 = new KisSelection(new KisSelectionDefaultBounds(dev)); sel1->pixelSelection()->select(qimage.rect()); f->process(dev, dev, sel1, QRect(QPoint(0,0), qimage.size()), kfc); QPoint errpoint; if (!compareQImages(errpoint, result, dev->convertToQImage(0, 0, 0, qimage.width(), qimage.height()))) { dev->convertToQImage(0, 0, 0, qimage.width(), qimage.height()).save(QString("sel_carrot_%1.png").arg(f->id())); return false; } return true; } void KisAllFilterTest::testAllFilters() { QStringList failures; QStringList successes; QList filterList = KisFilterRegistry::instance()->keys(); - qSort(filterList); + std::sort(filterList.begin(), filterList.end()); for (QList::Iterator it = filterList.begin(); it != filterList.end(); ++it) { if (testFilter(KisFilterRegistry::instance()->value(*it))) successes << *it; else failures << *it; } dbgKrita << "Success: " << successes; if (failures.size() > 0) { QFAIL(QString("Failed filters:\n\t %1").arg(failures.join("\n\t")).toLatin1()); } } void KisAllFilterTest::testAllFiltersNoTransaction() { QStringList failures; QStringList successes; QList filterList = KisFilterRegistry::instance()->keys(); - qSort(filterList); + std::sort(filterList.begin(), filterList.end()); for (QList::Iterator it = filterList.begin(); it != filterList.end(); ++it) { if (testFilterNoTransaction(KisFilterRegistry::instance()->value(*it))) successes << *it; else failures << *it; } dbgKrita << "Success (no transaction): " << successes; if (failures.size() > 0) { QFAIL(QString("Failed filters (no transaction):\n\t %1").arg(failures.join("\n\t")).toLatin1()); } } void KisAllFilterTest::testAllFiltersSrcNotIsDev() { QStringList failures; QStringList successes; QList filterList = KisFilterRegistry::instance()->keys(); - qSort(filterList); + std::sort(filterList.begin(), filterList.end()); for (QList::Iterator it = filterList.begin(); it != filterList.end(); ++it) { if (testFilterSrcNotIsDev(KisFilterRegistry::instance()->value(*it))) successes << *it; else failures << *it; } dbgKrita << "Src!=Dev Success: " << successes; if (failures.size() > 0) { QFAIL(QString("Src!=Dev Failed filters:\n\t %1").arg(failures.join("\n\t")).toLatin1()); } } void KisAllFilterTest::testAllFiltersWithSelections() { QStringList failures; QStringList successes; QList filterList = KisFilterRegistry::instance()->keys(); - qSort(filterList); + std::sort(filterList.begin(), filterList.end()); for (QList::Iterator it = filterList.begin(); it != filterList.end(); ++it) { if (testFilterWithSelections(KisFilterRegistry::instance()->value(*it))) successes << *it; else failures << *it; } dbgKrita << "Success: " << successes; if (failures.size() > 0) { QFAIL(QString("Failed filters with selections:\n\t %1").arg(failures.join("\n\t")).toLatin1()); } } QTEST_MAIN(KisAllFilterTest) diff --git a/plugins/filters/tests/kis_crash_filter_test.cpp b/plugins/filters/tests/kis_crash_filter_test.cpp index 565216b766..1fbdc3058e 100644 --- a/plugins/filters/tests/kis_crash_filter_test.cpp +++ b/plugins/filters/tests/kis_crash_filter_test.cpp @@ -1,99 +1,99 @@ /* * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_crash_filter_test.h" #include #include #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_selection.h" #include "kis_processing_information.h" #include "filter/kis_filter.h" #include "kis_pixel_selection.h" #include bool KisCrashFilterTest::applyFilter(const KoColorSpace * cs, KisFilterSP f) { QImage qimage(QString(FILES_DATA_DIR) + QDir::separator() + "carrot.png"); KisPaintDeviceSP dev = new KisPaintDevice(cs); // dev->fill(0, 0, 100, 100, dev->defaultPixel()); dev->convertFromQImage(qimage, 0, 0, 0); // Get the predefined configuration from a file KisFilterConfigurationSP kfc = f->defaultConfiguration(); QFile file(QString(FILES_DATA_DIR) + QDir::separator() + f->id() + ".cfg"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { dbgKrita << "creating new file for " << f->id(); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << kfc->toXML(); } else { QString s; QTextStream in(&file); in.setCodec("UTF-8"); s = in.readAll(); kfc->fromXML(s); } dbgKrita << f->id() << ", " << cs->id() << ", " << cs->profile()->name();// << kfc->toXML() << "\n"; f->process(dev, QRect(QPoint(0,0), qimage.size()), kfc); return true; } bool KisCrashFilterTest::testFilter(KisFilterSP f) { QList colorSpaces = KoColorSpaceRegistry::instance()->allColorSpaces(KoColorSpaceRegistry::AllColorSpaces, KoColorSpaceRegistry::AllProfiles); bool ok = false; Q_FOREACH (const KoColorSpace* colorSpace, colorSpaces) { // XXX: Let's not check the painterly colorspaces right now if (colorSpace->id().startsWith("KS", Qt::CaseInsensitive)) { continue; } ok = applyFilter(colorSpace, f); } return ok; } void KisCrashFilterTest::testCrashFilters() { QStringList failures; QStringList successes; QList filterList = KisFilterRegistry::instance()->keys(); - qSort(filterList); + std::sort(filterList.begin(), filterList.end()); for (QList::Iterator it = filterList.begin(); it != filterList.end(); ++it) { if (testFilter(KisFilterRegistry::instance()->value(*it))) successes << *it; else failures << *it; } dbgKrita << "Success: " << successes; if (failures.size() > 0) { QFAIL(QString("Failed filters:\n\t %1").arg(failures.join("\n\t")).toLatin1()); } } QTEST_MAIN(KisCrashFilterTest) diff --git a/plugins/flake/textshape/FontSizeAction.cpp b/plugins/flake/textshape/FontSizeAction.cpp index 9f61f8072c..740f3c616d 100644 --- a/plugins/flake/textshape/FontSizeAction.cpp +++ b/plugins/flake/textshape/FontSizeAction.cpp @@ -1,155 +1,155 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Reginald Stadlbauer (C) 1999 Simon Hausmann (C) 2000 Nicolas Hadacek (C) 2000 Kurt Granroth (C) 2000 Michael Koch (C) 2001 Holger Freyther (C) 2002 Ellis Whitehead (C) 2002 Joseph Wenninger (C) 2003 Andras Mantia (C) 2005-2006 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include "FontSizeAction.h" QString format(qreal v) { static const QString f("%1"); static const QString e; static const QRegExp r("\\.?0+$"); return f.arg(v, 0, 'f').replace(r, e); } class FontSizeAction::Private { public: Private(FontSizeAction *parent) : q(parent) { } void init(); FontSizeAction *q; }; // BEGIN FontSizeAction FontSizeAction::FontSizeAction(QObject *parent) : KSelectAction(parent) , d(new Private(this)) { d->init(); } FontSizeAction::FontSizeAction(const QString &text, QObject *parent) : KSelectAction(text, parent) , d(new Private(this)) { d->init(); } FontSizeAction::FontSizeAction(const QIcon &icon, const QString &text, QObject *parent) : KSelectAction(icon, text, parent) , d(new Private(this)) { d->init(); } FontSizeAction::~FontSizeAction() { delete d; } void FontSizeAction::Private::init() { q->setEditable(true); QFontDatabase fontDB; const QList sizes = fontDB.standardSizes(); QStringList lst; for (QList::ConstIterator it = sizes.begin(); it != sizes.end(); ++it) { lst.append(format(*it)); } q->setItems(lst); } void FontSizeAction::setFontSize(qreal size) { if (size == fontSize()) { const QString test = format(size); Q_FOREACH (QAction *action, actions()) { if (action->text() == test) { setCurrentAction(action); return; } } } if (size < 1) { qWarning() << "FontSizeAction: Size " << size << " is out of range"; return; } QAction *a = action(format(size)); if (!a) { // Insert at the correct position in the list (to keep sorting) QList lst; // Convert to list of qreals QStringListIterator itemsIt(items()); QStringList debug_lst = items(); while (itemsIt.hasNext()) { lst.append(itemsIt.next().toDouble()); } //add the new size lst.append(size); //remove actions clear(); // Sort the list - qSort(lst); + std::sort(lst.begin(), lst.end()); Q_FOREACH (qreal it, lst) { QAction *const action = addAction(format(it)); if (it == size) { setCurrentAction(action); } } } else { setCurrentAction(a); } } qreal FontSizeAction::fontSize() const { return currentText().toDouble(); } void FontSizeAction::actionTriggered(QAction *action) { emit fontSizeChanged(action->text().toDouble()); KSelectAction::actionTriggered(action); } diff --git a/plugins/flake/textshape/TextTool.cpp b/plugins/flake/textshape/TextTool.cpp index fc7218feb0..fe8c07b6fc 100644 --- a/plugins/flake/textshape/TextTool.cpp +++ b/plugins/flake/textshape/TextTool.cpp @@ -1,3164 +1,3164 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2008, 2012 Pierre Stirnweiss * Copyright (C) 2009 KO GmbH * Copyright (C) 2011 Mojtaba Shahi Senobari * Copyright (C) 2014 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TextTool.h" #include "TextEditingPluginContainer.h" #include "dialogs/SimpleCharacterWidget.h" #include "dialogs/SimpleParagraphWidget.h" #include "dialogs/SimpleTableWidget.h" #include "dialogs/SimpleInsertWidget.h" #include "dialogs/ParagraphSettingsDialog.h" #include "dialogs/StyleManagerDialog.h" #include "dialogs/InsertCharacter.h" #include "dialogs/FontDia.h" #include "dialogs/TableDialog.h" #include "dialogs/SectionFormatDialog.h" #include "dialogs/SectionsSplitDialog.h" #include "dialogs/SimpleTableWidget.h" #include "commands/AutoResizeCommand.h" #include "commands/ChangeListLevelCommand.h" #include "FontSizeAction.h" #include "FontFamilyAction.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 "kis_action_registry.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AnnotationTextShape.h" #define AnnotationShape_SHAPEID "AnnotationTextShapeID" #include "KoShapeBasedDocumentBase.h" #include #include #include #include class TextToolSelection : public KoToolSelection { public: TextToolSelection(QWeakPointer editor) : KoToolSelection(0) , m_editor(editor) { } bool hasSelection() override { if (!m_editor.isNull()) { return m_editor.data()->hasSelection(); } return false; } QWeakPointer m_editor; }; static bool hit(const QKeySequence &input, KStandardShortcut::StandardShortcut shortcut) { foreach (const QKeySequence &ks, KStandardShortcut::shortcut(shortcut)) { if (input == ks) { return true; } } return false; } TextTool::TextTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_textShape(0) , m_textShapeData(0) , m_changeTracker(0) , m_allowActions(true) , m_allowAddUndoCommand(true) , m_allowResourceManagerUpdates(true) , m_prevCursorPosition(-1) , m_caretTimer(this) , m_caretTimerState(true) , m_currentCommand(0) , m_currentCommandHasChildren(false) , m_specialCharacterDocker(0) , m_textTyping(false) , m_textDeleting(false) , m_editTipTimer(this) , m_delayedEnsureVisible(false) , m_toolSelection(0) , m_tableDraggedOnce(false) , m_tablePenMode(false) , m_lastImMicroFocus(QRectF(0, 0, 0, 0)) , m_drag(0) { setTextMode(true); createActions(); m_unit = canvas->resourceManager()->unitResource(KoCanvasResourceManager::Unit); foreach (KoTextEditingPlugin *plugin, textEditingPluginContainer()->values()) { connect(plugin, SIGNAL(startMacro(QString)), this, SLOT(startMacro(QString))); connect(plugin, SIGNAL(stopMacro()), this, SLOT(stopMacro())); QHash actions = plugin->actions(); QHash::iterator i = actions.begin(); while (i != actions.end()) { addAction(i.key(), i.value()); ++i; } } m_contextMenu.reset(new QMenu()); // setup the context list. QSignalMapper *signalMapper = new QSignalMapper(this); connect(signalMapper, SIGNAL(mapped(QString)), this, SLOT(startTextEditingPlugin(QString))); m_contextMenu->addAction(this->action("format_font")); foreach (const QString &key, KoTextEditingRegistry::instance()->keys()) { KoTextEditingFactory *factory = KoTextEditingRegistry::instance()->value(key); if (factory->showInMenu()) { QAction *a = new QAction(factory->title(), this); connect(a, SIGNAL(triggered()), signalMapper, SLOT(map())); signalMapper->setMapping(a, factory->id()); m_contextMenu->addAction(a); addAction(QString("apply_%1").arg(factory->id()), a); } } connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeAddedToCanvas())); m_caretTimer.setInterval(500); connect(&m_caretTimer, SIGNAL(timeout()), this, SLOT(blinkCaret())); m_editTipTimer.setInterval(500); m_editTipTimer.setSingleShot(true); connect(&m_editTipTimer, SIGNAL(timeout()), this, SLOT(showEditTip())); } void TextTool::createActions() { bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); KisActionRegistry *actionRegistry = KisActionRegistry::instance(); // FIXME: find new icons for these m_actionConfigureSection = actionRegistry->makeQAction("configure_section", this); addAction("configure_section", m_actionConfigureSection); connect(m_actionConfigureSection, SIGNAL(triggered(bool)), this, SLOT(configureSection())); m_actionInsertSection = actionRegistry->makeQAction("insert_section", this); addAction("insert_section", m_actionInsertSection); connect(m_actionInsertSection, SIGNAL(triggered(bool)), this, SLOT(insertNewSection())); m_actionSplitSections = actionRegistry->makeQAction("split_sections", this); addAction("split_sections", m_actionSplitSections); connect(m_actionSplitSections, SIGNAL(triggered(bool)), this, SLOT(splitSections())); m_actionPasteAsText = actionRegistry->makeQAction("edit_paste_text", this); addAction("edit_paste_text", m_actionPasteAsText); connect(m_actionPasteAsText, SIGNAL(triggered(bool)), this, SLOT(pasteAsText())); m_actionFormatBold = actionRegistry->makeQAction("format_bold", this); addAction("format_bold", m_actionFormatBold); m_actionFormatBold->setCheckable(true); connect(m_actionFormatBold, SIGNAL(triggered(bool)), this, SLOT(bold(bool))); m_actionFormatItalic = actionRegistry->makeQAction("format_italic", this); m_actionFormatItalic->setCheckable(true); addAction("format_italic", m_actionFormatItalic); connect(m_actionFormatItalic, SIGNAL(triggered(bool)), this, SLOT(italic(bool))); m_actionFormatUnderline = actionRegistry->makeQAction("format_underline", this); m_actionFormatUnderline->setCheckable(true); addAction("format_underline", m_actionFormatUnderline); connect(m_actionFormatUnderline, SIGNAL(triggered(bool)), this, SLOT(underline(bool))); m_actionFormatStrikeOut = actionRegistry->makeQAction("format_strike", this); m_actionFormatStrikeOut->setCheckable(true); addAction("format_strike", m_actionFormatStrikeOut); connect(m_actionFormatStrikeOut, SIGNAL(triggered(bool)), this, SLOT(strikeOut(bool))); QActionGroup *alignmentGroup = new QActionGroup(this); m_actionAlignLeft = actionRegistry->makeQAction("format_alignleft", this); m_actionAlignLeft->setCheckable(true); alignmentGroup->addAction(m_actionAlignLeft); addAction("format_alignleft", m_actionAlignLeft); connect(m_actionAlignLeft, SIGNAL(triggered(bool)), this, SLOT(alignLeft())); m_actionAlignRight = actionRegistry->makeQAction("format_alignright", this); m_actionAlignRight->setCheckable(true); alignmentGroup->addAction(m_actionAlignRight); addAction("format_alignright", m_actionAlignRight); connect(m_actionAlignRight, SIGNAL(triggered(bool)), this, SLOT(alignRight())); m_actionAlignCenter = actionRegistry->makeQAction("format_aligncenter", this); m_actionAlignCenter->setCheckable(true); addAction("format_aligncenter", m_actionAlignCenter); alignmentGroup->addAction(m_actionAlignCenter); connect(m_actionAlignCenter, SIGNAL(triggered(bool)), this, SLOT(alignCenter())); m_actionAlignBlock = actionRegistry->makeQAction("format_alignblock", this); m_actionAlignBlock->setCheckable(true); alignmentGroup->addAction(m_actionAlignBlock); addAction("format_alignblock", m_actionAlignBlock); connect(m_actionAlignBlock, SIGNAL(triggered(bool)), this, SLOT(alignBlock())); m_actionChangeDirection = actionRegistry->makeQAction("change_text_direction", this); m_actionChangeDirection->setCheckable(true); addAction("change_text_direction", m_actionChangeDirection); connect(m_actionChangeDirection, SIGNAL(triggered()), this, SLOT(textDirectionChanged())); m_actionFormatSuper = actionRegistry->makeQAction("format_super", this); m_actionFormatSuper->setCheckable(true); addAction("format_super", m_actionFormatSuper); connect(m_actionFormatSuper, SIGNAL(triggered(bool)), this, SLOT(superScript(bool))); m_actionFormatSub = actionRegistry->makeQAction("format_sub", this); m_actionFormatSub->setCheckable(true); addAction("format_sub", m_actionFormatSub); connect(m_actionFormatSub, SIGNAL(triggered(bool)), this, SLOT(subScript(bool))); // TODO: check these rtl-things work properly m_actionFormatIncreaseIndent = actionRegistry->makeQAction("format_increaseindent", this); addAction("format_increaseindent", m_actionFormatIncreaseIndent); connect(m_actionFormatIncreaseIndent, SIGNAL(triggered()), this, SLOT(increaseIndent())); m_actionFormatDecreaseIndent = actionRegistry->makeQAction("format_decreaseindent", this); addAction("format_decreaseindent", m_actionFormatDecreaseIndent); connect(m_actionFormatDecreaseIndent, SIGNAL(triggered()), this, SLOT(decreaseIndent())); const char *const increaseIndentActionIconName = QApplication::isRightToLeft() ? koIconNameCStr("format-indent-less") : koIconNameCStr("format-indent-more"); m_actionFormatIncreaseIndent->setIcon(koIcon(increaseIndentActionIconName)); const char *const decreaseIndentActionIconName = QApplication::isRightToLeft() ? koIconNameCStr("format_decreaseindent") : koIconNameCStr("format-indent-less"); m_actionFormatIncreaseIndent->setIcon(koIcon(decreaseIndentActionIconName)); QAction *action = actionRegistry->makeQAction("format_bulletlist", this); addAction("format_bulletlist", action); action = actionRegistry->makeQAction("format_numberlist", this); addAction("format_numberlist", action); action = actionRegistry->makeQAction("fontsizeup", this); addAction("fontsizeup", action); connect(action, SIGNAL(triggered()), this, SLOT(increaseFontSize())); action = actionRegistry->makeQAction("fontsizedown", this); addAction("fontsizedown", action); connect(action, SIGNAL(triggered()), this, SLOT(decreaseFontSize())); m_actionFormatFontFamily = new KoFontFamilyAction(this); m_actionFormatFontFamily->setText(i18n("Font Family")); addAction("format_fontfamily", m_actionFormatFontFamily); connect(m_actionFormatFontFamily, SIGNAL(triggered(QString)), this, SLOT(setFontFamily(QString))); m_variableMenu = new KActionMenu(i18n("Variable"), this); addAction("insert_variable", m_variableMenu); // ------------------- Actions with a key binding and no GUI item action = actionRegistry->makeQAction("nonbreaking_space", this); addAction("nonbreaking_space", action); connect(action, SIGNAL(triggered()), this, SLOT(nonbreakingSpace())); action = actionRegistry->makeQAction("nonbreaking_hyphen", this); addAction("nonbreaking_hyphen", action); connect(action, SIGNAL(triggered()), this, SLOT(nonbreakingHyphen())); action = actionRegistry->makeQAction("insert_index", this); addAction("insert_index", action); connect(action, SIGNAL(triggered()), this, SLOT(insertIndexMarker())); action = actionRegistry->makeQAction("soft_hyphen", this); // TODO: double check this one works, conflicts with "zoom out" addAction("soft_hyphen", action); connect(action, SIGNAL(triggered()), this, SLOT(softHyphen())); if (useAdvancedText) { action = actionRegistry->makeQAction("line_break", this); addAction("line_break", action); connect(action, SIGNAL(triggered()), this, SLOT(lineBreak())); action = actionRegistry->makeQAction("insert_framebreak", this); addAction("insert_framebreak", action); connect(action, SIGNAL(triggered()), this, SLOT(insertFrameBreak())); } action = actionRegistry->makeQAction("format_font", this); addAction("format_font", action); connect(action, SIGNAL(triggered()), this, SLOT(selectFont())); m_actionFormatFontSize = new FontSizeAction(i18n("Font Size"), this); addAction("format_fontsize", m_actionFormatFontSize); connect(m_actionFormatFontSize, SIGNAL(fontSizeChanged(qreal)), this, SLOT(setFontSize(qreal))); m_actionFormatTextColor = new KoColorPopupAction(this); addAction("format_textcolor", m_actionFormatTextColor); connect(m_actionFormatTextColor, SIGNAL(colorChanged(KoColor)), this, SLOT(setTextColor(KoColor))); m_actionFormatBackgroundColor = new KoColorPopupAction(this); addAction("format_backgroundcolor", m_actionFormatBackgroundColor); connect(m_actionFormatBackgroundColor, SIGNAL(colorChanged(KoColor)), this, SLOT(setBackgroundColor(KoColor))); m_growWidthAction = actionRegistry->makeQAction("grow_to_fit_width", this); addAction("grow_to_fit_width", m_growWidthAction); connect(m_growWidthAction, SIGNAL(triggered(bool)), this, SLOT(setGrowWidthToFit(bool))); m_growHeightAction = actionRegistry->makeQAction("grow_to_fit_height", this); addAction("grow_to_fit_height", m_growHeightAction); connect(m_growHeightAction, SIGNAL(triggered(bool)), this, SLOT(setGrowHeightToFit(bool))); m_shrinkToFitAction = actionRegistry->makeQAction("shrink_to_fit", this); addAction("shrink_to_fit", m_shrinkToFitAction); connect(m_shrinkToFitAction, SIGNAL(triggered(bool)), this, SLOT(setShrinkToFit(bool))); if (useAdvancedText) { action = actionRegistry->makeQAction("insert_table", this); addAction("insert_table", action); connect(action, SIGNAL(triggered()), this, SLOT(insertTable())); action = actionRegistry->makeQAction("insert_tablerow_above", this); addAction("insert_tablerow_above", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableRowAbove())); action = actionRegistry->makeQAction("insert_tablerow_below", this); addAction("insert_tablerow_below", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableRowBelow())); action = actionRegistry->makeQAction("insert_tablecolumn_left", this); addAction("insert_tablecolumn_left", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableColumnLeft())); action = actionRegistry->makeQAction("insert_tablecolumn_right", this); addAction("insert_tablecolumn_right", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableColumnRight())); action = actionRegistry->makeQAction("delete_tablecolumn", this); addAction("delete_tablecolumn", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteTableColumn())); action = actionRegistry->makeQAction("delete_tablerow", this); addAction("delete_tablerow", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteTableRow())); action = actionRegistry->makeQAction("merge_tablecells", this); addAction("merge_tablecells", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(mergeTableCells())); action = actionRegistry->makeQAction("split_tablecells", this); addAction("split_tablecells", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(splitTableCells())); action = actionRegistry->makeQAction("activate_borderpainter", this); addAction("activate_borderpainter", action); } action = actionRegistry->makeQAction("format_paragraph", this); addAction("format_paragraph", action); connect(action, SIGNAL(triggered()), this, SLOT(formatParagraph())); action = actionRegistry->makeQAction("format_stylist", this); addAction("format_stylist", action); connect(action, SIGNAL(triggered()), this, SLOT(showStyleManager())); action = KStandardAction::selectAll(this, SLOT(selectAll()), this); addAction("edit_select_all", action); action = actionRegistry->makeQAction("insert_specialchar", this); addAction("insert_specialchar", action); connect(action, SIGNAL(triggered()), this, SLOT(insertSpecialCharacter())); action = actionRegistry->makeQAction("repaint", this); addAction("repaint", action); connect(action, SIGNAL(triggered()), this, SLOT(relayoutContent())); action = actionRegistry->makeQAction("insert_annotation", this); addAction("insert_annotation", action); connect(action, SIGNAL(triggered()), this, SLOT(insertAnnotation())); #ifndef NDEBUG action = actionRegistry->makeQAction("detailed_debug_paragraphs", this); addAction("detailed_debug_paragraphs", action); connect(action, SIGNAL(triggered()), this, SLOT(debugTextDocument())); action = actionRegistry->makeQAction("detailed_debug_styles", this); addAction("detailed_debug_styles", action); connect(action, SIGNAL(triggered()), this, SLOT(debugTextStyles())); #endif } #ifndef NDEBUG #include "tests/MockShapes.h" #include #include TextTool::TextTool(MockCanvas *canvas) // constructor for our unit tests; : KoToolBase(canvas), m_textShape(0), m_textShapeData(0), m_changeTracker(0), m_allowActions(true), m_allowAddUndoCommand(true), m_allowResourceManagerUpdates(true), m_prevCursorPosition(-1), m_caretTimer(this), m_caretTimerState(true), m_currentCommand(0), m_currentCommandHasChildren(false), m_specialCharacterDocker(0), m_textEditingPlugins(0) , m_editTipTimer(this) , m_delayedEnsureVisible(false) , m_tableDraggedOnce(false) , m_tablePenMode(false) { // we could init some vars here, but we probably don't have to QLocale::setDefault(QLocale("en")); QTextDocument *document = new QTextDocument(); // this document is leaked KoInlineTextObjectManager *inlineManager = new KoInlineTextObjectManager(); KoTextDocument(document).setInlineTextObjectManager(inlineManager); KoTextRangeManager *locationManager = new KoTextRangeManager(); KoTextDocument(document).setTextRangeManager(locationManager); m_textEditor = new KoTextEditor(document); KoTextDocument(document).setTextEditor(m_textEditor.data()); m_toolSelection = new TextToolSelection(m_textEditor); m_changeTracker = new KoChangeTracker(); KoTextDocument(document).setChangeTracker(m_changeTracker); KoTextDocument(document).setUndoStack(new KUndo2Stack()); } #endif TextTool::~TextTool() { delete m_toolSelection; } void TextTool::showEditTip() { if (!m_textShapeData || m_editTipPointedAt.position == -1) { return; } QTextCursor c(m_textShapeData->document()); c.setPosition(m_editTipPointedAt.position); QString text = "

"; int toolTipWidth = 0; if (m_changeTracker && m_changeTracker->containsInlineChanges(c.charFormat()) && m_changeTracker->displayChanges()) { KoChangeTrackerElement *element = m_changeTracker->elementById(c.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt()); if (element->isEnabled()) { QString changeType; if (element->getChangeType() == KoGenChange::InsertChange) { changeType = i18n("Insertion"); } else if (element->getChangeType() == KoGenChange::DeleteChange) { changeType = i18n("Deletion"); } else { changeType = i18n("Formatting"); } text += "" + changeType + "
"; QString date = element->getDate(); //Remove the T which separates the Data and Time. date[10] = QLatin1Char(' '); date = element->getCreator() + QLatin1Char(' ') + date; text += date + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(date).width(); } } if (m_editTipPointedAt.bookmark || !m_editTipPointedAt.externalHRef.isEmpty()) { QString help = i18n("Ctrl+click to go to link "); help += m_editTipPointedAt.externalHRef; text += help + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width(); } if (m_editTipPointedAt.note) { QString help = i18n("Ctrl+click to go to the note "); text += help + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width(); } if (m_editTipPointedAt.noteReference > 0) { QString help = i18n("Ctrl+click to go to the note reference"); text += help + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width(); } QToolTip::hideText(); if (toolTipWidth) { QRect keepRect(m_editTipPos - QPoint(3, 3), QSize(6, 6)); QToolTip::showText(m_editTipPos - QPoint(toolTipWidth / 2, 0), text, canvas()->canvasWidget(), keepRect); } } void TextTool::blinkCaret() { if (!(canvas()->canvasWidget() && canvas()->canvasWidget()->hasFocus())) { m_caretTimer.stop(); m_caretTimerState = false; // not visible. } else { m_caretTimerState = !m_caretTimerState; } repaintCaret(); } void TextTool::relayoutContent() { KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); foreach (KoTextLayoutRootArea *rootArea, lay->rootAreas()) { rootArea->setDirty(); } lay->emitLayoutIsDirty(); } void TextTool::paint(QPainter &painter, const KoViewConverter &converter) { if (m_textEditor.isNull()) { return; } if (canvas() && (canvas()->canvasWidget() && canvas()->canvasWidget()->hasFocus()) && !m_caretTimer.isActive()) { // make sure we blink m_caretTimer.start(); m_caretTimerState = true; } if (!m_caretTimerState) { m_caretTimer.setInterval(500); // we set it lower during typing, so set it back to normal } if (!m_textShapeData) { return; } if (m_textShapeData->isDirty()) { return; } qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); painter.save(); QTransform shapeMatrix = m_textShape->absoluteTransformation(&converter); shapeMatrix.scale(zoomX, zoomY); shapeMatrix.translate(0, -m_textShapeData->documentOffset()); // Possibly draw table dragging visual cues const qreal boxHeight = 20; if (m_tableDragInfo.tableHit == KoPointedAt::ColumnDivider) { QPointF anchorPos = m_tableDragInfo.tableDividerPos - QPointF(m_dx, 0.0); if (m_tableDragInfo.tableColumnDivider > 0) { //let's draw left qreal w = m_tableDragInfo.tableLeadSize - m_dx; QRectF rect(anchorPos - QPointF(w, 0.0), QSizeF(w, 0.0)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setHeight(boxHeight); drawRect.moveTop(drawRect.top() - 1.5 * boxHeight); QString label = m_unit.toUserStringValue(w); int labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); painter.setPen(QColor(0, 0, 0, 196)); if (labelWidth + 10 < drawRect.width()) { QPointF centerLeft(drawRect.left(), drawRect.center().y()); QPointF centerRight(drawRect.right(), drawRect.center().y()); painter.drawLine(centerLeft, drawRect.center() - QPointF(labelWidth / 2 + 5, 0.0)); painter.drawLine(centerLeft, centerLeft + QPointF(7, -5)); painter.drawLine(centerLeft, centerLeft + QPointF(7, 5)); painter.drawLine(drawRect.center() + QPointF(labelWidth / 2 + 5, 0.0), centerRight); painter.drawLine(centerRight, centerRight + QPointF(-7, -5)); painter.drawLine(centerRight, centerRight + QPointF(-7, 5)); painter.drawText(drawRect, Qt::AlignCenter, label); } } if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns()) { //let's draw right qreal w = m_tableDragInfo.tableTrailSize + m_dx; QRectF rect(anchorPos, QSizeF(w, 0.0)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setHeight(boxHeight); drawRect.moveTop(drawRect.top() - 1.5 * boxHeight); QString label; int labelWidth; if (m_tableDragWithShift) { label = i18n("follows along"); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); drawRect.setWidth(2 * labelWidth); QLinearGradient g(drawRect.topLeft(), drawRect.topRight()); g.setColorAt(0.6, QColor(255, 64, 64, 196)); g.setColorAt(1.0, QColor(255, 64, 64, 0)); QBrush brush(g); painter.fillRect(drawRect, brush); } else { label = m_unit.toUserStringValue(w); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); drawRect.setHeight(boxHeight); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); } painter.setPen(QColor(0, 0, 0, 196)); if (labelWidth + 10 < drawRect.width()) { QPointF centerLeft(drawRect.left(), drawRect.center().y()); QPointF centerRight(drawRect.right(), drawRect.center().y()); painter.drawLine(centerLeft, drawRect.center() - QPointF(labelWidth / 2 + 5, 0.0)); painter.drawLine(centerLeft, centerLeft + QPointF(7, -5)); painter.drawLine(centerLeft, centerLeft + QPointF(7, 5)); if (!m_tableDragWithShift) { painter.drawLine(drawRect.center() + QPointF(labelWidth / 2 + 5, 0.0), centerRight); painter.drawLine(centerRight, centerRight + QPointF(-7, -5)); painter.drawLine(centerRight, centerRight + QPointF(-7, 5)); } painter.drawText(drawRect, Qt::AlignCenter, label); } if (!m_tableDragWithShift) { // let's draw a helper text too label = i18n("Press shift to not resize this"); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); labelWidth += 10; //if (labelWidth < drawRect.width()) { drawRect.moveTop(drawRect.top() + boxHeight); drawRect.moveLeft(drawRect.left() + (drawRect.width() - labelWidth) / 2); drawRect.setWidth(labelWidth); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); painter.drawText(drawRect, Qt::AlignCenter, label); } } } } // Possibly draw table dragging visual cues if (m_tableDragInfo.tableHit == KoPointedAt::RowDivider) { QPointF anchorPos = m_tableDragInfo.tableDividerPos - QPointF(0.0, m_dy); if (m_tableDragInfo.tableRowDivider > 0) { qreal h = m_tableDragInfo.tableLeadSize - m_dy; QRectF rect(anchorPos - QPointF(0.0, h), QSizeF(0.0, h)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setWidth(boxHeight); drawRect.moveLeft(drawRect.left() - 1.5 * boxHeight); QString label = m_unit.toUserStringValue(h); QRectF labelRect = QFontMetrics(QToolTip::font()).boundingRect(label); labelRect.setHeight(boxHeight); labelRect.setWidth(labelRect.width() + 10); labelRect.moveTopLeft(drawRect.center() - QPointF(labelRect.width(), labelRect.height()) / 2); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); painter.fillRect(labelRect, QColor(64, 255, 64, 196)); painter.setPen(QColor(0, 0, 0, 196)); if (labelRect.height() + 10 < drawRect.height()) { QPointF centerTop(drawRect.center().x(), drawRect.top()); QPointF centerBottom(drawRect.center().x(), drawRect.bottom()); painter.drawLine(centerTop, drawRect.center() - QPointF(0.0, labelRect.height() / 2 + 5)); painter.drawLine(centerTop, centerTop + QPointF(-5, 7)); painter.drawLine(centerTop, centerTop + QPointF(5, 7)); painter.drawLine(drawRect.center() + QPointF(0.0, labelRect.height() / 2 + 5), centerBottom); painter.drawLine(centerBottom, centerBottom + QPointF(-5, -7)); painter.drawLine(centerBottom, centerBottom + QPointF(5, -7)); } painter.drawText(labelRect, Qt::AlignCenter, label); } } if (m_caretTimerState) { // Lets draw the caret ourselves, as the Qt method doesn't take cursor // charFormat into consideration. QTextBlock block = m_textEditor.data()->block(); if (block.isValid()) { int posInParag = m_textEditor.data()->position() - block.position(); if (posInParag <= block.layout()->preeditAreaPosition()) { posInParag += block.layout()->preeditAreaText().length(); } QTextLine tl = block.layout()->lineForTextPosition(m_textEditor.data()->position() - block.position()); if (tl.isValid()) { painter.setRenderHint(QPainter::Antialiasing, false); QRectF rect = caretRect(m_textEditor.data()->cursor()); QPointF baselinePoint; if (tl.ascent() > 0) { QFontMetricsF fm(m_textEditor.data()->charFormat().font(), painter.device()); rect.setY(rect.y() + tl.ascent() - qMin(tl.ascent(), fm.ascent())); rect.setHeight(qMin(tl.ascent(), fm.ascent()) + qMin(tl.descent(), fm.descent())); baselinePoint = QPoint(rect.x(), rect.y() + tl.ascent()); } else { //line only filled with characters-without-size (eg anchors) // layout will make sure line has height of block font QFontMetricsF fm(block.charFormat().font(), painter.device()); rect.setHeight(fm.ascent() + fm.descent()); baselinePoint = QPoint(rect.x(), rect.y() + fm.ascent()); } QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomLeft())); drawRect.setWidth(2); painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination); if (m_textEditor.data()->isEditProtected(true)) { QRectF circleRect(shapeMatrix.map(baselinePoint), QSizeF(14, 14)); circleRect.translate(-6.5, -6.5); QPen pen(QColor(16, 255, 255)); pen.setWidthF(2.0); painter.setPen(pen); painter.setRenderHint(QPainter::Antialiasing, true); painter.drawEllipse(circleRect); painter.drawLine(circleRect.topLeft() + QPointF(4.5, 4.5), circleRect.bottomRight() - QPointF(4.5, 4.5)); } else { painter.fillRect(drawRect, QColor(128, 255, 128)); } } } } painter.restore(); } void TextTool::updateSelectedShape(const QPointF &point, bool noDocumentChange) { QRectF area(point, QSizeF(1, 1)); if (m_textEditor.data()->hasSelection()) { repaintSelection(); } else { repaintCaret(); } QList sortedShapes = canvas()->shapeManager()->shapesAt(area, true); - qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); + std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); for (int count = sortedShapes.count() - 1; count >= 0; count--) { KoShape *shape = sortedShapes.at(count); if (shape->isContentProtected()) { continue; } TextShape *textShape = dynamic_cast(shape); if (textShape) { if (textShape != m_textShape) { if (static_cast(textShape->userData())->document() != m_textShapeData->document()) { //we should only change to another document if allowed if (noDocumentChange) { return; } // if we change to another textdocument we need to remove selection in old document // or it would continue to be painted etc m_textEditor.data()->setPosition(m_textEditor.data()->position()); } m_textShape = textShape; setShapeData(static_cast(m_textShape->userData())); // This is how we inform the rulers of the active range // For now we will not consider table cells, but just give the shape dimensions QVariant v; QRectF rect(QPoint(), m_textShape->size()); rect = m_textShape->absoluteTransformation(0).mapRect(rect); v.setValue(rect); canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, v); } return; } } } void TextTool::mousePressEvent(KoPointerEvent *event) { if (m_textEditor.isNull()) { return; } // request the software keyboard, if any if (event->button() == Qt::LeftButton && qApp->autoSipEnabled()) { QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(qApp->style()->styleHint(QStyle::SH_RequestSoftwareInputPanel)); // the two following bools just make it all a lot easier to read in the following if() // basically, we require a widget for this to work (passing 0 to QApplication::sendEvent // crashes) and there are three tests any one of which can be true to trigger the event const bool hasWidget = canvas()->canvasWidget(); if ((behavior == QStyle::RSIP_OnMouseClick && hasWidget) || (hasWidget && canvas()->canvasWidget()->hasFocus())) { QEvent event(QEvent::RequestSoftwareInputPanel); if (hasWidget) { QApplication::sendEvent(canvas()->canvasWidget(), &event); } } } bool shiftPressed = event->modifiers() & Qt::ShiftModifier; updateSelectedShape(event->point, shiftPressed); KoSelection *selection = canvas()->selectedShapesProxy()->selection(); if (m_textShape && !selection->isSelected(m_textShape) && m_textShape->isSelectable()) { selection->deselectAll(); selection->select(m_textShape); } KoPointedAt pointedAt = hitTest(event->point); m_tableDraggedOnce = false; m_clickWithinSelection = false; if (pointedAt.position != -1) { m_tablePenMode = false; if ((event->button() == Qt::LeftButton) && !shiftPressed && m_textEditor.data()->hasSelection() && m_textEditor.data()->isWithinSelection(pointedAt.position)) { m_clickWithinSelection = true; m_draggingOrigin = event->pos(); //we store the pixel pos } else if (!(event->button() == Qt::RightButton && m_textEditor.data()->hasSelection() && m_textEditor.data()->isWithinSelection(pointedAt.position))) { m_textEditor.data()->setPosition(pointedAt.position, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); useCursor(Qt::IBeamCursor); } m_tableDragInfo.tableHit = KoPointedAt::None; if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } } else { if (event->button() == Qt::RightButton) { m_tablePenMode = false; KoTextEditingPlugin *plugin = textEditingPluginContainer()->spellcheck(); if (plugin) { plugin->setCurrentCursorPosition(m_textShapeData->document(), -1); } event->ignore(); } else if (m_tablePenMode) { m_textEditor.data()->beginEditBlock(kundo2_i18n("Change Border Formatting")); if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { if (pointedAt.tableColumnDivider < pointedAt.table->columns()) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider, KoBorder::LeftBorder, m_tablePenBorderData); } if (pointedAt.tableColumnDivider > 0) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider - 1, KoBorder::RightBorder, m_tablePenBorderData); } } else if (pointedAt.tableHit == KoPointedAt::RowDivider) { if (pointedAt.tableRowDivider < pointedAt.table->rows()) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider, KoBorder::TopBorder, m_tablePenBorderData); } if (pointedAt.tableRowDivider > 0) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider - 1, pointedAt.tableColumnDivider, KoBorder::BottomBorder, m_tablePenBorderData); } } m_textEditor.data()->endEditBlock(); } else { m_tableDragInfo = pointedAt; m_tablePenMode = false; } return; } if (shiftPressed) { // altered selection. repaintSelection(); } else { repaintCaret(); } updateSelectionHandler(); updateStyleManager(); updateActions(); //activate context-menu for spelling-suggestions if (event->button() == Qt::RightButton) { KoTextEditingPlugin *plugin = textEditingPluginContainer()->spellcheck(); if (plugin) { plugin->setCurrentCursorPosition(m_textShapeData->document(), m_textEditor.data()->position()); } event->ignore(); } if (event->button() == Qt::MidButton) { // Paste const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Selection); // on windows we do not have data if we try to paste this selection if (data) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data, canvas()->resourceManager()); editingPluginEvents(); } } } void TextTool::setShapeData(KoTextShapeData *data) { bool docChanged = !data || !m_textShapeData || m_textShapeData->document() != data->document(); if (m_textShapeData) { disconnect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); } m_textShapeData = data; if (!m_textShapeData) { return; } connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); if (docChanged) { if (!m_textEditor.isNull()) { disconnect(m_textEditor.data(), SIGNAL(textFormatChanged()), this, SLOT(updateActions())); } m_textEditor = KoTextDocument(m_textShapeData->document()).textEditor(); Q_ASSERT(m_textEditor.data()); if (!m_toolSelection) { m_toolSelection = new TextToolSelection(m_textEditor.data()); } else { m_toolSelection->m_editor = m_textEditor.data(); } m_variableMenu->menu()->clear(); KoTextDocument document(m_textShapeData->document()); foreach (QAction *action, document.inlineTextObjectManager()->createInsertVariableActions(canvas())) { m_variableMenu->addAction(action); connect(action, SIGNAL(triggered()), this, SLOT(returnFocusToCanvas())); } connect(m_textEditor.data(), SIGNAL(textFormatChanged()), this, SLOT(updateActions())); updateActions(); } } void TextTool::updateSelectionHandler() { if (m_textEditor) { emit selectionChanged(m_textEditor.data()->hasSelection()); if (m_textEditor.data()->hasSelection()) { QClipboard *clipboard = QApplication::clipboard(); if (clipboard->supportsSelection()) { clipboard->setText(m_textEditor.data()->selectedText(), QClipboard::Selection); } } } KoCanvasResourceManager *p = canvas()->resourceManager(); m_allowResourceManagerUpdates = false; if (m_textEditor && m_textShapeData) { p->setResource(KoText::CurrentTextPosition, m_textEditor.data()->position()); p->setResource(KoText::CurrentTextAnchor, m_textEditor.data()->anchor()); QVariant variant; variant.setValue(m_textShapeData->document()); p->setResource(KoText::CurrentTextDocument, variant); } else { p->clearResource(KoText::CurrentTextPosition); p->clearResource(KoText::CurrentTextAnchor); p->clearResource(KoText::CurrentTextDocument); } m_allowResourceManagerUpdates = true; } QMimeData *TextTool::generateMimeData() const { if (!m_textShapeData || m_textEditor.isNull() || !m_textEditor.data()->hasSelection()) { return 0; } int from = m_textEditor.data()->position(); int to = m_textEditor.data()->anchor(); KoTextOdfSaveHelper saveHelper(m_textShapeData->document(), from, to); KoTextDrag drag; #ifdef SHOULD_BUILD_RDF KoDocumentResourceManager *rm = 0; if (canvas()->shapeController()) { rm = canvas()->shapeController()->resourceManager(); } if (rm && rm->hasResource(KoText::DocumentRdf)) { KoDocumentRdfBase *rdf = qobject_cast(rm->resource(KoText::DocumentRdf).value()); if (rdf) { saveHelper.setRdfModel(rdf->model()); } } #endif drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper); QTextDocumentFragment fragment = m_textEditor.data()->selection(); drag.setData("text/html", fragment.toHtml("utf-8").toUtf8()); drag.setData("text/plain", fragment.toPlainText().toUtf8()); return drag.takeMimeData(); } TextEditingPluginContainer *TextTool::textEditingPluginContainer() { m_textEditingPlugins = canvas()->resourceManager()-> resource(TextEditingPluginContainer::ResourceId).value(); if (m_textEditingPlugins == 0) { m_textEditingPlugins = new TextEditingPluginContainer(canvas()->resourceManager()); QVariant variant; variant.setValue(m_textEditingPlugins.data()); canvas()->resourceManager()->setResource(TextEditingPluginContainer::ResourceId, variant); foreach (KoTextEditingPlugin *plugin, m_textEditingPlugins->values()) { connect(plugin, SIGNAL(startMacro(QString)), this, SLOT(startMacro(QString))); connect(plugin, SIGNAL(stopMacro()), this, SLOT(stopMacro())); QHash actions = plugin->actions(); QHash::iterator i = actions.begin(); while (i != actions.end()) { addAction(i.key(), i.value()); ++i; } } } return m_textEditingPlugins; } void TextTool::copy() const { QMimeData *mimeData = generateMimeData(); if (mimeData) { QApplication::clipboard()->setMimeData(mimeData); } } void TextTool::deleteSelection() { m_textEditor.data()->deleteChar(); editingPluginEvents(); } bool TextTool::paste() { const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Clipboard); // on windows we do not have data if we try to paste the selection if (!data) { return false; } // since this is not paste-as-text we will not paste in urls, but instead let KoToolProxy solve it if (data->hasUrls()) { return false; } if (data->hasFormat(KoOdf::mimeType(KoOdf::Text)) || data->hasText()) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data); editingPluginEvents(); return true; } return false; } void TextTool::cut() { if (m_textEditor.data()->hasSelection()) { copy(); KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Cut")); m_textEditor.data()->deleteChar(false, topCmd); m_textEditor.data()->endEditBlock(); } } void TextTool::dragMoveEvent(QDragMoveEvent *event, const QPointF &point) { if (event->mimeData()->hasFormat(KoOdf::mimeType(KoOdf::Text)) || event->mimeData()->hasFormat(KoOdf::mimeType(KoOdf::OpenOfficeClipboard)) || event->mimeData()->hasText()) { if (m_drag) { event->setDropAction(Qt::MoveAction); event->accept(); } else if (event->proposedAction() == Qt::CopyAction) { event->acceptProposedAction(); } else { event->ignore(); return; } KoPointedAt pointedAt = hitTest(point); if (pointedAt.position == -1) { event->ignore(); } if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } if (m_preDragSelection.cursor.isNull()) { repaintSelection(); m_preDragSelection.cursor = QTextCursor(*m_textEditor.data()->cursor()); if (m_drag) { // Make a selection that looks like the current cursor selection // so we can move the real carent around freely QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); m_preDragSelection.format = QTextCharFormat(); m_preDragSelection.format.setBackground(qApp->palette().brush(QPalette::Highlight)); m_preDragSelection.format.setForeground(qApp->palette().brush(QPalette::HighlightedText)); sels.append(m_preDragSelection); KoTextDocument(m_textShapeData->document()).setSelections(sels); } // else we wantt the selection ot disappaear } repaintCaret(); // will erase caret m_textEditor.data()->setPosition(pointedAt.position); repaintCaret(); // will paint caret in new spot // Selection has visually not appeared at a new spot so no need to repaint it } } void TextTool::dragLeaveEvent(QDragLeaveEvent *event) { if (m_drag) { // restore the old selections QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); sels.pop_back(); KoTextDocument(m_textShapeData->document()).setSelections(sels); } repaintCaret(); // will erase caret in old spot m_textEditor.data()->setPosition(m_preDragSelection.cursor.anchor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.position(), QTextCursor::KeepAnchor); repaintCaret(); // will paint caret in new spot if (!m_drag) { repaintSelection(); // will paint selection again } // mark that we now are back to normal selection m_preDragSelection.cursor = QTextCursor(); event->accept(); } void TextTool::dropEvent(QDropEvent *event, const QPointF &) { if (m_drag) { // restore the old selections QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); sels.pop_back(); KoTextDocument(m_textShapeData->document()).setSelections(sels); } QTextCursor insertCursor(*m_textEditor.data()->cursor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.anchor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.position(), QTextCursor::KeepAnchor); repaintSelection(); // will erase the selection in new spot if (m_drag) { m_textEditor.data()->deleteChar(); } m_prevCursorPosition = insertCursor.position(); m_textEditor.data()->setPosition(m_prevCursorPosition); m_textEditor.data()->paste(canvas(), event->mimeData()); m_textEditor.data()->setPosition(m_prevCursorPosition); //since the paste made insertCursor we can now use that for the end position m_textEditor.data()->setPosition(insertCursor.position(), QTextCursor::KeepAnchor); // mark that we no are back to normal selection m_preDragSelection.cursor = QTextCursor(); event->accept(); } KoPointedAt TextTool::hitTest(const QPointF &point) const { if (!m_textShape || !m_textShapeData) { return KoPointedAt(); } QPointF p = m_textShape->convertScreenPos(point); KoTextLayoutRootArea *rootArea = m_textShapeData->rootArea(); return rootArea ? rootArea->hitTest(p, Qt::FuzzyHit) : KoPointedAt(); } void TextTool::mouseDoubleClickEvent(KoPointerEvent *event) { if (canvas()->shapeManager()->shapeAt(event->point) != m_textShape) { event->ignore(); // allow the event to be used by another return; } if (event->modifiers() & Qt::ShiftModifier) { // When whift is pressed we behave as a single press return mousePressEvent(event); } m_textEditor.data()->select(QTextCursor::WordUnderCursor); m_clickWithinSelection = false; repaintSelection(); updateSelectionHandler(); } void TextTool::mouseTripleClickEvent(KoPointerEvent *event) { if (canvas()->shapeManager()->shapeAt(event->point) != m_textShape) { event->ignore(); // allow the event to be used by another return; } if (event->modifiers() & Qt::ShiftModifier) { // When whift is pressed we behave as a single press return mousePressEvent(event); } m_textEditor.data()->clearSelection(); m_textEditor.data()->movePosition(QTextCursor::StartOfBlock); m_textEditor.data()->movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); m_clickWithinSelection = false; repaintSelection(); updateSelectionHandler(); } void TextTool::mouseMoveEvent(KoPointerEvent *event) { m_editTipPos = event->globalPos(); if (event->buttons()) { updateSelectedShape(event->point, true); } m_editTipTimer.stop(); if (QToolTip::isVisible()) { QToolTip::hideText(); } KoPointedAt pointedAt = hitTest(event->point); if (event->buttons() == Qt::NoButton) { if (m_tablePenMode) { if (pointedAt.tableHit == KoPointedAt::ColumnDivider || pointedAt.tableHit == KoPointedAt::RowDivider) { useTableBorderCursor(); } else { useCursor(Qt::IBeamCursor); } // do nothing else return; } if (!m_textShapeData || pointedAt.position < 0) { if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { useCursor(Qt::SplitHCursor); m_draggingOrigin = event->point; } else if (pointedAt.tableHit == KoPointedAt::RowDivider) { if (pointedAt.tableRowDivider > 0) { useCursor(Qt::SplitVCursor); m_draggingOrigin = event->point; } else { useCursor(Qt::IBeamCursor); } } else { useCursor(Qt::IBeamCursor); } return; } QTextCursor mouseOver(m_textShapeData->document()); mouseOver.setPosition(pointedAt.position); if (m_changeTracker && m_changeTracker->containsInlineChanges(mouseOver.charFormat())) { m_editTipPointedAt = pointedAt; if (QToolTip::isVisible()) { QTimer::singleShot(0, this, SLOT(showEditTip())); } else { m_editTipTimer.start(); } } if ((pointedAt.bookmark || !pointedAt.externalHRef.isEmpty()) || pointedAt.note || (pointedAt.noteReference > 0)) { if (event->modifiers() & Qt::ControlModifier) { useCursor(Qt::PointingHandCursor); } m_editTipPointedAt = pointedAt; if (QToolTip::isVisible()) { QTimer::singleShot(0, this, SLOT(showEditTip())); } else { m_editTipTimer.start(); } return; } // check if mouse pointer is over shape with hyperlink KoShape *selectedShape = canvas()->shapeManager()->shapeAt(event->point); if (selectedShape != 0 && selectedShape != m_textShape && selectedShape->hyperLink().size() != 0) { useCursor(Qt::PointingHandCursor); return; } useCursor(Qt::IBeamCursor); // Set Arrow Cursor when mouse is on top of annotation shape. if (selectedShape) { if (selectedShape->shapeId() == "AnnotationTextShapeID") { QPointF point(event->point); if (point.y() <= (selectedShape->position().y() + 25)) { useCursor(Qt::ArrowCursor); } } } return; } else { if (m_tableDragInfo.tableHit == KoPointedAt::ColumnDivider) { m_tableDragWithShift = event->modifiers() & Qt::ShiftModifier; if (m_tableDraggedOnce) { canvas()->shapeController()->resourceManager()->undoStack()->undo(); } KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Adjust Column Width")); m_dx = m_draggingOrigin.x() - event->point.x(); if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns() && m_tableDragInfo.tableTrailSize + m_dx < 0) { m_dx = -m_tableDragInfo.tableTrailSize; } if (m_tableDragInfo.tableColumnDivider > 0) { if (m_tableDragInfo.tableLeadSize - m_dx < 0) { m_dx = m_tableDragInfo.tableLeadSize; } m_textEditor.data()->adjustTableColumnWidth(m_tableDragInfo.table, m_tableDragInfo.tableColumnDivider - 1, m_tableDragInfo.tableLeadSize - m_dx, topCmd); } else { m_textEditor.data()->adjustTableWidth(m_tableDragInfo.table, -m_dx, 0.0); } if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns()) { if (!m_tableDragWithShift) { m_textEditor.data()->adjustTableColumnWidth(m_tableDragInfo.table, m_tableDragInfo.tableColumnDivider, m_tableDragInfo.tableTrailSize + m_dx, topCmd); } } else { m_tableDragWithShift = true; // act like shift pressed } if (m_tableDragWithShift) { m_textEditor.data()->adjustTableWidth(m_tableDragInfo.table, 0.0, m_dx); } m_textEditor.data()->endEditBlock(); m_tableDragInfo.tableDividerPos.setY(m_textShape->convertScreenPos(event->point).y()); if (m_tableDraggedOnce) { //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) { canvas()->canvasWidget()->update(); } } m_tableDraggedOnce = true; } else if (m_tableDragInfo.tableHit == KoPointedAt::RowDivider) { if (m_tableDraggedOnce) { canvas()->shapeController()->resourceManager()->undoStack()->undo(); } if (m_tableDragInfo.tableRowDivider > 0) { KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Adjust Row Height")); m_dy = m_draggingOrigin.y() - event->point.y(); if (m_tableDragInfo.tableLeadSize - m_dy < 0) { m_dy = m_tableDragInfo.tableLeadSize; } m_textEditor.data()->adjustTableRowHeight(m_tableDragInfo.table, m_tableDragInfo.tableRowDivider - 1, m_tableDragInfo.tableLeadSize - m_dy, topCmd); m_textEditor.data()->endEditBlock(); m_tableDragInfo.tableDividerPos.setX(m_textShape->convertScreenPos(event->point).x()); if (m_tableDraggedOnce) { //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) { canvas()->canvasWidget()->update(); } } m_tableDraggedOnce = true; } } else if (m_tablePenMode) { // do nothing } else if (m_clickWithinSelection) { if (!m_drag && (event->pos() - m_draggingOrigin).manhattanLength() >= QApplication::startDragDistance()) { QMimeData *mimeData = generateMimeData(); if (mimeData) { m_drag = new QDrag(canvas()->canvasWidget()); m_drag->setMimeData(mimeData); m_drag->exec(Qt::MoveAction | Qt::CopyAction, Qt::CopyAction); m_drag = 0; } } } else { useCursor(Qt::IBeamCursor); if (pointedAt.position == m_textEditor.data()->position()) { return; } if (pointedAt.position >= 0) { if (m_textEditor.data()->hasSelection()) { repaintSelection(); // will erase selection } else { repaintCaret(); } m_textEditor.data()->setPosition(pointedAt.position, QTextCursor::KeepAnchor); if (m_textEditor.data()->hasSelection()) { repaintSelection(); } else { repaintCaret(); } } } updateSelectionHandler(); } } void TextTool::mouseReleaseEvent(KoPointerEvent *event) { event->ignore(); editingPluginEvents(); m_tableDragInfo.tableHit = KoPointedAt::None; if (m_tableDraggedOnce) { m_tableDraggedOnce = false; //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) { canvas()->canvasWidget()->update(); } } if (!m_textShapeData) { return; } // check if mouse pointer is not over some shape with hyperlink KoShape *selectedShape = canvas()->shapeManager()->shapeAt(event->point); if (selectedShape != 0 && selectedShape != m_textShape && selectedShape->hyperLink().size() != 0) { QString url = selectedShape->hyperLink(); runUrl(event, url); return; } KoPointedAt pointedAt = hitTest(event->point); if (m_clickWithinSelection && !m_drag) { if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } repaintCaret(); // will erase caret repaintSelection(); // will erase selection m_textEditor.data()->setPosition(pointedAt.position); repaintCaret(); // will paint caret in new spot } // Is there an anchor here ? if ((event->modifiers() & Qt::ControlModifier) && !m_textEditor.data()->hasSelection()) { if (pointedAt.bookmark) { m_textEditor.data()->setPosition(pointedAt.bookmark->rangeStart()); ensureCursorVisible(); event->accept(); return; } if (pointedAt.note) { m_textEditor.data()->setPosition(pointedAt.note->textFrame()->firstPosition()); ensureCursorVisible(); event->accept(); return; } if (pointedAt.noteReference > 0) { m_textEditor.data()->setPosition(pointedAt.noteReference); ensureCursorVisible(); event->accept(); return; } if (!pointedAt.externalHRef.isEmpty()) { runUrl(event, pointedAt.externalHRef); } } } void TextTool::keyPressEvent(QKeyEvent *event) { int destinationPosition = -1; // for those cases where the moveOperation is not relevant; QTextCursor::MoveOperation moveOperation = QTextCursor::NoMove; KoTextEditor *textEditor = m_textEditor.data(); m_tablePenMode = false; // keypress always stops the table (border) pen mode Q_ASSERT(textEditor); if (event->key() == Qt::Key_Backspace) { if (!textEditor->hasSelection() && textEditor->block().textList() && (textEditor->position() == textEditor->block().position()) && !(m_changeTracker && m_changeTracker->recordChanges())) { if (!textEditor->blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) { // backspace at beginning of numbered list item, makes it unnumbered textEditor->toggleListNumbering(false); } else { KoListLevelProperties llp; llp.setStyle(KoListStyle::None); llp.setLevel(0); // backspace on numbered, empty parag, removes numbering. textEditor->setListProperties(llp); } } else if (textEditor->position() > 0 || textEditor->hasSelection()) { if (!textEditor->hasSelection() && event->modifiers() & Qt::ControlModifier) { // delete prev word. textEditor->movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor); } textEditor->deletePreviousChar(); editingPluginEvents(); } } else if ((event->key() == Qt::Key_Tab) && ((!textEditor->hasSelection() && (textEditor->position() == textEditor->block().position())) || (textEditor->block().document()->findBlock(textEditor->anchor()) != textEditor->block().document()->findBlock(textEditor->position()))) && textEditor->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::IncreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, 1); textEditor->addCommand(cll); editingPluginEvents(); } else if ((event->key() == Qt::Key_Backtab) && ((!textEditor->hasSelection() && (textEditor->position() == textEditor->block().position())) || (textEditor->block().document()->findBlock(textEditor->anchor()) != textEditor->block().document()->findBlock(textEditor->position()))) && textEditor->block().textList() && !(m_changeTracker && m_changeTracker->recordChanges())) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::DecreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, 1); textEditor->addCommand(cll); editingPluginEvents(); } else if (event->key() == Qt::Key_Delete) { if (!textEditor->hasSelection() && event->modifiers() & Qt::ControlModifier) {// delete next word. textEditor->movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor); } // the event only gets through when the Del is not used in the app // if the app forwards Del then deleteSelection is used textEditor->deleteChar(); editingPluginEvents(); } else if ((event->key() == Qt::Key_Left) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Left; } else if ((event->key() == Qt::Key_Right) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Right; } else if ((event->key() == Qt::Key_Up) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Up; } else if ((event->key() == Qt::Key_Down) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Down; } else { // check for shortcuts. QKeySequence item(event->key() | ((Qt::ControlModifier | Qt::AltModifier) & event->modifiers())); if (hit(item, KStandardShortcut::Begin)) // Goto beginning of the document. Default: Ctrl-Home { destinationPosition = 0; } else if (hit(item, KStandardShortcut::End)) { // Goto end of the document. Default: Ctrl-End if (m_textShapeData) { QTextBlock last = m_textShapeData->document()->lastBlock(); destinationPosition = last.position() + last.length() - 1; } } else if (hit(item, KStandardShortcut::Prior)) { // page up // Scroll up one page. Default: Prior event->ignore(); // let app level actions handle it return; } else if (hit(item, KStandardShortcut::Next)) { // Scroll down one page. Default: Next event->ignore(); // let app level actions handle it return; } else if (hit(item, KStandardShortcut::BeginningOfLine)) // Goto beginning of current line. Default: Home { moveOperation = QTextCursor::StartOfLine; } else if (hit(item, KStandardShortcut::EndOfLine)) // Goto end of current line. Default: End { moveOperation = QTextCursor::EndOfLine; } else if (hit(item, KStandardShortcut::BackwardWord)) { moveOperation = QTextCursor::WordLeft; } else if (hit(item, KStandardShortcut::ForwardWord)) { moveOperation = QTextCursor::WordRight; } #ifdef Q_OS_OSX // Don't reject "alt" key, it may be used for typing text on Mac OS else if ((event->modifiers() & Qt::ControlModifier) || event->text().length() == 0) { #else else if ((event->modifiers() & (Qt::ControlModifier | Qt::AltModifier)) || event->text().length() == 0) { #endif event->ignore(); return; } else if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) { m_prevCursorPosition = textEditor->position(); textEditor->newLine(); updateActions(); editingPluginEvents(); } else if ((event->key() == Qt::Key_Tab || !(event->text().length() == 1 && !event->text().at(0).isPrint()))) { // insert the text m_prevCursorPosition = textEditor->position(); startingSimpleEdit(); //signal editing plugins that this is a simple edit textEditor->insertText(event->text()); editingPluginEvents(); } } if (moveOperation != QTextCursor::NoMove || destinationPosition != -1) { useCursor(Qt::BlankCursor); bool shiftPressed = event->modifiers() & Qt::ShiftModifier; if (textEditor->hasSelection()) { repaintSelection(); // will erase selection } else { repaintCaret(); } QTextBlockFormat format = textEditor->blockFormat(); KoText::Direction dir = static_cast(format.intProperty(KoParagraphStyle::TextProgressionDirection)); bool isRtl; if (dir == KoText::AutoDirection) { isRtl = textEditor->block().text().isRightToLeft(); } else { isRtl = dir == KoText::RightLeftTopBottom; } if (isRtl) { // if RTL toggle direction of cursor movement. switch (moveOperation) { case QTextCursor::Left: moveOperation = QTextCursor::Right; break; case QTextCursor::Right: moveOperation = QTextCursor::Left; break; case QTextCursor::WordRight: moveOperation = QTextCursor::WordLeft; break; case QTextCursor::WordLeft: moveOperation = QTextCursor::WordRight; break; default: break; } } int prevPosition = textEditor->position(); if (moveOperation != QTextCursor::NoMove) textEditor->movePosition(moveOperation, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); else textEditor->setPosition(destinationPosition, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); if (moveOperation == QTextCursor::Down && prevPosition == textEditor->position()) { // change behavior a little big from Qt; at the bottom of the doc we go to the end of the doc textEditor->movePosition(QTextCursor::End, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); } if (shiftPressed) { // altered selection. repaintSelection(); } else { repaintCaret(); } updateActions(); editingPluginEvents(); } if (m_caretTimer.isActive()) { // make the caret not blink but decide on the action if its visible or not. m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret on while typing } if (moveOperation != QTextCursor::NoMove) // this difference in handling is need to prevent leaving a trail of old cursors onscreen { ensureCursorVisible(); } else { m_delayedEnsureVisible = true; } updateActions(); updateSelectionHandler(); } QVariant TextTool::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) { return QVariant(); } switch (query) { case Qt::ImMicroFocus: { // The rectangle covering the area of the input cursor in widget coordinates. QRectF rect = caretRect(textEditor->cursor()); rect.moveTop(rect.top() - m_textShapeData->documentOffset()); QTransform shapeMatrix = m_textShape->absoluteTransformation(&converter); qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); shapeMatrix.scale(zoomX, zoomY); rect = shapeMatrix.mapRect(rect); return rect.toRect(); } case Qt::ImFont: // The currently used font for text input. return textEditor->charFormat().font(); case Qt::ImCursorPosition: // The logical position of the cursor within the text surrounding the input area (see ImSurroundingText). return textEditor->position() - textEditor->block().position(); case Qt::ImSurroundingText: // The plain text around the input area, for example the current paragraph. return textEditor->block().text(); case Qt::ImCurrentSelection: // The currently selected text. return textEditor->selectedText(); default: ; // Qt 4.6 adds ImMaximumTextLength and ImAnchorPosition } return QVariant(); } void TextTool::inputMethodEvent(QInputMethodEvent *event) { KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) { return; } if (event->replacementLength() > 0) { textEditor->setPosition(textEditor->position() + event->replacementStart()); for (int i = event->replacementLength(); i > 0; --i) { textEditor->deleteChar(); } } if (!event->commitString().isEmpty()) { QKeyEvent ke(QEvent::KeyPress, -1, 0, event->commitString()); keyPressEvent(&ke); // The cursor may reside in a different block before vs. after keyPressEvent. QTextBlock block = textEditor->block(); QTextLayout *layout = block.layout(); Q_ASSERT(layout); layout->setPreeditArea(-1, QString()); } else { QTextBlock block = textEditor->block(); QTextLayout *layout = block.layout(); Q_ASSERT(layout); layout->setPreeditArea(textEditor->position() - block.position(), event->preeditString()); const_cast(textEditor->document())->markContentsDirty(textEditor->position(), event->preeditString().length()); } event->accept(); } void TextTool::ensureCursorVisible(bool moveView) { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) { return; } bool upToDate; QRectF cRect = caretRect(textEditor->cursor(), &upToDate); KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); KoTextLayoutRootArea *rootArea = lay->rootAreaForPoint(cRect.center()); if (rootArea && rootArea->associatedShape() && m_textShapeData->rootArea() != rootArea) { // If we have changed root area we need to update m_textShape and m_textShapeData m_textShape = static_cast(rootArea->associatedShape()); Q_ASSERT(m_textShape); disconnect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); m_textShapeData = static_cast(m_textShape->userData()); Q_ASSERT(m_textShapeData); connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); } if (!moveView) { return; } if (!upToDate) { // paragraph is not yet layouted. // The number one usecase for this is when the user pressed enter. // try to do it on next caret blink m_delayedEnsureVisible = true; return; // we shouldn't move to an obsolete position } cRect.moveTop(cRect.top() - m_textShapeData->documentOffset()); canvas()->ensureVisible(m_textShape->absoluteTransformation(0).mapRect(cRect)); } void TextTool::keyReleaseEvent(QKeyEvent *event) { event->accept(); } void TextTool::updateActions() { bool notInAnnotation = !dynamic_cast(m_textShape); KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) { return; } m_allowActions = false; //Update the characterStyle related GUI elements QTextCharFormat cf = textEditor->charFormat(); m_actionFormatBold->setChecked(cf.fontWeight() > QFont::Normal); m_actionFormatItalic->setChecked(cf.fontItalic()); m_actionFormatUnderline->setChecked(cf.intProperty(KoCharacterStyle::UnderlineType) != KoCharacterStyle::NoLineType); m_actionFormatStrikeOut->setChecked(cf.intProperty(KoCharacterStyle::StrikeOutType) != KoCharacterStyle::NoLineType); bool super = false, sub = false; switch (cf.verticalAlignment()) { case QTextCharFormat::AlignSuperScript: super = true; break; case QTextCharFormat::AlignSubScript: sub = true; break; default:; } m_actionFormatSuper->setChecked(super); m_actionFormatSub->setChecked(sub); m_actionFormatFontSize->setFontSize(cf.font().pointSizeF()); m_actionFormatFontFamily->setFont(cf.font().family()); KoTextShapeData::ResizeMethod resizemethod = KoTextShapeData::AutoResize; if (m_textShapeData) { resizemethod = m_textShapeData->resizeMethod(); } m_shrinkToFitAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_shrinkToFitAction->setChecked(resizemethod == KoTextShapeData::ShrinkToFitResize); m_growWidthAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_growWidthAction->setChecked(resizemethod == KoTextShapeData::AutoGrowWidth || resizemethod == KoTextShapeData::AutoGrowWidthAndHeight); m_growHeightAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_growHeightAction->setChecked(resizemethod == KoTextShapeData::AutoGrowHeight || resizemethod == KoTextShapeData::AutoGrowWidthAndHeight); //update paragraphStyle GUI element QTextBlockFormat bf = textEditor->blockFormat(); if (bf.hasProperty(KoParagraphStyle::TextProgressionDirection)) { switch (bf.intProperty(KoParagraphStyle::TextProgressionDirection)) { case KoText::RightLeftTopBottom: m_actionChangeDirection->setChecked(true); break; case KoText::LeftRightTopBottom: default: m_actionChangeDirection->setChecked(false); break; } } else { m_actionChangeDirection->setChecked(textEditor->block().text().isRightToLeft()); } if (bf.alignment() == Qt::AlignLeading || bf.alignment() == Qt::AlignTrailing) { bool revert = (textEditor->block().layout()->textOption().textDirection() == Qt::RightToLeft); if ((bf.alignment() == Qt::AlignLeading) ^ revert) { m_actionAlignLeft->setChecked(true); } else { m_actionAlignRight->setChecked(true); } } else if (bf.alignment() == Qt::AlignHCenter) { m_actionAlignCenter->setChecked(true); } if (bf.alignment() == Qt::AlignJustify) { m_actionAlignBlock->setChecked(true); } else if (bf.alignment() == (Qt::AlignLeft | Qt::AlignAbsolute)) { m_actionAlignLeft->setChecked(true); } else if (bf.alignment() == (Qt::AlignRight | Qt::AlignAbsolute)) { m_actionAlignRight->setChecked(true); } if (textEditor->block().textList()) { QTextListFormat listFormat = textEditor->block().textList()->format(); if (listFormat.intProperty(KoListStyle::Level) > 1) { m_actionFormatDecreaseIndent->setEnabled(true); } else { m_actionFormatDecreaseIndent->setEnabled(false); } if (listFormat.intProperty(KoListStyle::Level) < 10) { m_actionFormatIncreaseIndent->setEnabled(true); } else { m_actionFormatIncreaseIndent->setEnabled(false); } } else { m_actionFormatDecreaseIndent->setEnabled(textEditor->blockFormat().leftMargin() > 0.); } m_allowActions = true; bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); if (useAdvancedText) { action("insert_table")->setEnabled(notInAnnotation); bool hasTable = textEditor->currentTable(); action("insert_tablerow_above")->setEnabled(hasTable && notInAnnotation); action("insert_tablerow_below")->setEnabled(hasTable && notInAnnotation); action("insert_tablecolumn_left")->setEnabled(hasTable && notInAnnotation); action("insert_tablecolumn_right")->setEnabled(hasTable && notInAnnotation); action("delete_tablerow")->setEnabled(hasTable && notInAnnotation); action("delete_tablecolumn")->setEnabled(hasTable && notInAnnotation); action("merge_tablecells")->setEnabled(hasTable && notInAnnotation); action("split_tablecells")->setEnabled(hasTable && notInAnnotation); action("activate_borderpainter")->setEnabled(hasTable && notInAnnotation); } action("insert_annotation")->setEnabled(notInAnnotation); ///TODO if selection contains several different format emit blockChanged(textEditor->block()); emit charFormatChanged(cf, textEditor->blockCharFormat()); emit blockFormatChanged(bf); } QMenu *TextTool::popupActionsMenu() { return m_contextMenu.data(); } void TextTool::updateStyleManager() { if (!m_textShapeData) { return; } KoStyleManager *styleManager = KoTextDocument(m_textShapeData->document()).styleManager(); emit styleManagerChanged(styleManager); //TODO move this to its own method m_changeTracker = KoTextDocument(m_textShapeData->document()).changeTracker(); } void TextTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); m_caretTimer.start(); m_caretTimerState = true; foreach (KoShape *shape, shapes) { m_textShape = dynamic_cast(shape); if (m_textShape) { break; } } if (!m_textShape) { // none found emit done(); // This is how we inform the rulers of the active range // No shape means no active range canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, QVariant(QRectF())); return; } // This is how we inform the rulers of the active range // For now we will not consider table cells, but just give the shape dimensions QVariant v; QRectF rect(QPoint(), m_textShape->size()); rect = m_textShape->absoluteTransformation(0).mapRect(rect); v.setValue(rect); canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, v); if ((!m_oldTextEditor.isNull()) && m_oldTextEditor.data()->document() != static_cast(m_textShape->userData())->document()) { m_oldTextEditor.data()->setPosition(m_oldTextEditor.data()->position()); //we need to redraw like this so we update the old textshape whereever it may be if (canvas()->canvasWidget()) { canvas()->canvasWidget()->update(); } } setShapeData(static_cast(m_textShape->userData())); useCursor(Qt::IBeamCursor); updateStyleManager(); repaintSelection(); updateSelectionHandler(); updateActions(); if (m_specialCharacterDocker) { m_specialCharacterDocker->setEnabled(true); } } void TextTool::deactivate() { m_caretTimer.stop(); m_caretTimerState = false; repaintCaret(); m_textShape = 0; // This is how we inform the rulers of the active range // No shape means no active range canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, QVariant(QRectF())); m_oldTextEditor = m_textEditor; setShapeData(0); updateSelectionHandler(); if (m_specialCharacterDocker) { m_specialCharacterDocker->setEnabled(false); m_specialCharacterDocker->setVisible(false); } KoToolBase::deactivate(); } void TextTool::repaintDecorations() { if (m_textShapeData) { repaintSelection(); } } void TextTool::repaintCaret() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) { return; } KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); Q_UNUSED(lay); // If we have changed root area we need to update m_textShape and m_textShapeData if (m_delayedEnsureVisible) { m_delayedEnsureVisible = false; ensureCursorVisible(); return; } ensureCursorVisible(false); // ensures the various vars are updated bool upToDate; QRectF repaintRect = caretRect(textEditor->cursor(), &upToDate); repaintRect.moveTop(repaintRect.top() - m_textShapeData->documentOffset()); if (repaintRect.isValid()) { repaintRect = m_textShape->absoluteTransformation(0).mapRect(repaintRect); // Make sure there is enough space to show an icon QRectF iconSize = canvas()->viewConverter()->viewToDocument(QRect(0, 0, 18, 18)); repaintRect.setX(repaintRect.x() - iconSize.width() / 2); repaintRect.setRight(repaintRect.right() + iconSize.width() / 2); repaintRect.setTop(repaintRect.y() - iconSize.height() / 2); repaintRect.setBottom(repaintRect.bottom() + iconSize.height() / 2); canvas()->updateCanvas(repaintRect); } } void TextTool::repaintSelection() { KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) { return; } QTextCursor cursor = *textEditor->cursor(); QList shapes; KoTextDocumentLayout *lay = qobject_cast(textEditor->document()->documentLayout()); Q_ASSERT(lay); foreach (KoShape *shape, lay->shapes()) { TextShape *textShape = dynamic_cast(shape); if (textShape == 0) { // when the shape is being deleted its no longer a TextShape but a KoShape continue; } //Q_ASSERT(!shapes.contains(textShape)); if (!shapes.contains(textShape)) { shapes.append(textShape); } } // loop over all shapes that contain the text and update per shape. QRectF repaintRect = textRect(cursor); foreach (TextShape *ts, shapes) { QRectF rect = repaintRect; rect.moveTop(rect.y() - ts->textShapeData()->documentOffset()); rect = ts->absoluteTransformation(0).mapRect(rect); QRectF r = ts->boundingRect().intersected(rect); canvas()->updateCanvas(r); } } QRectF TextTool::caretRect(QTextCursor *cursor, bool *upToDate) const { QTextCursor tmpCursor(*cursor); tmpCursor.setPosition(cursor->position()); // looses the anchor QRectF rect = textRect(tmpCursor); if (rect.size() == QSizeF(0, 0)) { if (upToDate) { *upToDate = false; } rect = m_lastImMicroFocus; // prevent block changed but layout not done } else { if (upToDate) { *upToDate = true; } m_lastImMicroFocus = rect; } return rect; } QRectF TextTool::textRect(QTextCursor &cursor) const { if (!m_textShapeData) { return QRectF(); } KoTextEditor *textEditor = m_textEditor.data(); KoTextDocumentLayout *lay = qobject_cast(textEditor->document()->documentLayout()); return lay->selectionBoundingBox(cursor); } KoToolSelection *TextTool::selection() { return m_toolSelection; } QList > TextTool::createOptionWidgets() { QList > widgets; SimpleCharacterWidget *scw = new SimpleCharacterWidget(this, 0); SimpleParagraphWidget *spw = new SimpleParagraphWidget(this, 0); if (m_textEditor.data()) { // connect(m_textEditor.data(), SIGNAL(paragraphStyleApplied(KoParagraphStyle*)), spw, SLOT(slotParagraphStyleApplied(KoParagraphStyle*))); // connect(m_textEditor.data(), SIGNAL(characterStyleApplied(KoCharacterStyle*)), scw, SLOT(slotCharacterStyleApplied(KoCharacterStyle*))); //initialise the char- and par- widgets with the current block and formats. scw->setCurrentBlockFormat(m_textEditor.data()->blockFormat()); scw->setCurrentFormat(m_textEditor.data()->charFormat(), m_textEditor.data()-> blockCharFormat()); spw->setCurrentBlock(m_textEditor.data()->block()); spw->setCurrentFormat(m_textEditor.data()->blockFormat()); } SimpleTableWidget *stw = new SimpleTableWidget(this, 0); SimpleInsertWidget *siw = new SimpleInsertWidget(this, 0); /* We do not use these for now. Let's see if they become useful at a certain point in time. If not, we can remove the whole chain (SimpleCharWidget, SimpleParWidget, DockerStyleComboModel) if (m_textShapeData && KoTextDocument(m_textShapeData->document()).styleManager()) { scw->setInitialUsedStyles(KoTextDocument(m_textShapeData->document()).styleManager()->usedCharacterStyles()); spw->setInitialUsedStyles(KoTextDocument(m_textShapeData->document()).styleManager()->usedParagraphStyles()); } */ // Connect to/with simple character widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), scw, SLOT(setStyleManager(KoStyleManager*))); connect(this, SIGNAL(charFormatChanged(QTextCharFormat,QTextCharFormat)), scw, SLOT(setCurrentFormat(QTextCharFormat,QTextCharFormat))); connect(this, SIGNAL(blockFormatChanged(QTextBlockFormat)), scw, SLOT(setCurrentBlockFormat(QTextBlockFormat))); connect(scw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(scw, SIGNAL(characterStyleSelected(KoCharacterStyle*)), this, SLOT(setStyle(KoCharacterStyle*))); connect(scw, SIGNAL(newStyleRequested(QString)), this, SLOT(createStyleFromCurrentCharFormat(QString))); connect(scw, SIGNAL(showStyleManager(int)), this, SLOT(showStyleManager(int))); // Connect to/with simple paragraph widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), spw, SLOT(setStyleManager(KoStyleManager*))); connect(this, SIGNAL(blockChanged(QTextBlock)), spw, SLOT(setCurrentBlock(QTextBlock))); connect(this, SIGNAL(blockFormatChanged(QTextBlockFormat)), spw, SLOT(setCurrentFormat(QTextBlockFormat))); connect(spw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(spw, SIGNAL(paragraphStyleSelected(KoParagraphStyle*)), this, SLOT(setStyle(KoParagraphStyle*))); connect(spw, SIGNAL(newStyleRequested(QString)), this, SLOT(createStyleFromCurrentBlockFormat(QString))); connect(spw, SIGNAL(showStyleManager(int)), this, SLOT(showStyleManager(int))); // Connect to/with simple table widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), stw, SLOT(setStyleManager(KoStyleManager*))); connect(stw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(stw, SIGNAL(tableBorderDataUpdated(KoBorder::BorderData)), this, SLOT(setTableBorderData(KoBorder::BorderData))); // Connect to/with simple insert widget (docker) connect(siw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(siw, SIGNAL(insertTableQuick(int,int)), this, SLOT(insertTableQuick(int,int))); updateStyleManager(); if (m_textShape) { updateActions(); } scw->setWindowTitle(i18n("Character")); widgets.append(scw); spw->setWindowTitle(i18n("Paragraph")); widgets.append(spw); bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); if (useAdvancedText) { stw->setWindowTitle(i18n("Table")); widgets.append(stw); siw->setWindowTitle(i18n("Insert")); widgets.append(siw); } return widgets; } void TextTool::returnFocusToCanvas() { canvas()->canvasWidget()->setFocus(); } void TextTool::startEditing(KUndo2Command *command) { m_currentCommand = command; m_currentCommandHasChildren = true; } void TextTool::stopEditing() { m_currentCommand = 0; m_currentCommandHasChildren = false; } void TextTool::insertNewSection() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) { return; } textEditor->newSection(); } void TextTool::configureSection() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) { return; } SectionFormatDialog *dia = new SectionFormatDialog(0, m_textEditor.data()); dia->exec(); delete dia; returnFocusToCanvas(); updateActions(); } void TextTool::splitSections() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) { return; } SectionsSplitDialog *dia = new SectionsSplitDialog(0, m_textEditor.data()); dia->exec(); delete dia; returnFocusToCanvas(); updateActions(); } void TextTool::pasteAsText() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) { return; } const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Clipboard); // on windows we do not have data if we try to paste this selection if (!data) { return; } if (data->hasFormat(KoOdf::mimeType(KoOdf::Text)) || data->hasText()) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data, true); editingPluginEvents(); } } void TextTool::bold(bool bold) { m_textEditor.data()->bold(bold); } void TextTool::italic(bool italic) { m_textEditor.data()->italic(italic); } void TextTool::underline(bool underline) { m_textEditor.data()->underline(underline); } void TextTool::strikeOut(bool strikeOut) { m_textEditor.data()->strikeOut(strikeOut); } void TextTool::nonbreakingSpace() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->insertText(QString(QChar(Qt::Key_nobreakspace))); } void TextTool::nonbreakingHyphen() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->insertText(QString(QChar(0x2013))); } void TextTool::softHyphen() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->insertText(QString(QChar(Qt::Key_hyphen))); } void TextTool::lineBreak() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->insertText(QString(QChar(0x2028))); } void TextTool::alignLeft() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignLeft | Qt::AlignAbsolute); } void TextTool::alignRight() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignRight | Qt::AlignAbsolute); } void TextTool::alignCenter() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignHCenter); } void TextTool::alignBlock() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignJustify); } void TextTool::superScript(bool on) { if (!m_allowActions || !m_textEditor.data()) { return; } if (on) { m_actionFormatSub->setChecked(false); } m_textEditor.data()->setVerticalTextAlignment(on ? Qt::AlignTop : Qt::AlignVCenter); } void TextTool::subScript(bool on) { if (!m_allowActions || !m_textEditor.data()) { return; } if (on) { m_actionFormatSuper->setChecked(false); } m_textEditor.data()->setVerticalTextAlignment(on ? Qt::AlignBottom : Qt::AlignVCenter); } void TextTool::increaseIndent() { if (!m_allowActions || !m_textEditor.data()) { return; } if (m_textEditor.data()->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::IncreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*(m_textEditor.data()->cursor()), type, 1); m_textEditor.data()->addCommand(cll); editingPluginEvents(); } else { m_textEditor.data()->increaseIndent(); } updateActions(); } void TextTool::decreaseIndent() { if (!m_allowActions || !m_textEditor.data()) { return; } if (m_textEditor.data()->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::DecreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*(m_textEditor.data()->cursor()), type, 1); m_textEditor.data()->addCommand(cll); editingPluginEvents(); } else { m_textEditor.data()->decreaseIndent(); } updateActions(); } void TextTool::decreaseFontSize() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->decreaseFontSize(); } void TextTool::increaseFontSize() { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->increaseFontSize(); } void TextTool::setFontFamily(const QString &font) { if (!m_allowActions || !m_textEditor.data()) { return; } m_textEditor.data()->setFontFamily(font); } void TextTool::setFontSize(qreal size) { if (!m_allowActions || !m_textEditor.data() || m_textEditor.isNull()) { return; } m_textEditor.data()->setFontSize(size); } void TextTool::insertIndexMarker() { // TODO handle result when we figure out how to report errors from a tool. m_textEditor.data()->insertIndexMarker(); } void TextTool::insertFrameBreak() { m_textEditor.data()->insertFrameBreak(); ensureCursorVisible(); m_delayedEnsureVisible = true; } void TextTool::setStyle(KoCharacterStyle *style) { KoCharacterStyle *charStyle = style; //if the given KoCharacterStyle is null, set the KoParagraphStyle character properties if (!charStyle) { charStyle = static_cast(KoTextDocument(m_textShapeData->document()).styleManager()->paragraphStyle(m_textEditor.data()->blockFormat().intProperty(KoParagraphStyle::StyleId))); } if (charStyle) { m_textEditor.data()->setStyle(charStyle); updateActions(); } } void TextTool::setStyle(KoParagraphStyle *style) { m_textEditor.data()->setStyle(style); updateActions(); } void TextTool::insertTable() { TableDialog *dia = new TableDialog(0); if (dia->exec() == TableDialog::Accepted) { m_textEditor.data()->insertTable(dia->rows(), dia->columns()); } delete dia; updateActions(); } void TextTool::insertTableQuick(int rows, int columns) { m_textEditor.data()->insertTable(rows, columns); updateActions(); } void TextTool::insertTableRowAbove() { m_textEditor.data()->insertTableRowAbove(); } void TextTool::insertTableRowBelow() { m_textEditor.data()->insertTableRowBelow(); } void TextTool::insertTableColumnLeft() { m_textEditor.data()->insertTableColumnLeft(); } void TextTool::insertTableColumnRight() { m_textEditor.data()->insertTableColumnRight(); } void TextTool::deleteTableColumn() { m_textEditor.data()->deleteTableColumn(); } void TextTool::deleteTableRow() { m_textEditor.data()->deleteTableRow(); } void TextTool::mergeTableCells() { m_textEditor.data()->mergeTableCells(); } void TextTool::splitTableCells() { m_textEditor.data()->splitTableCells(); } void TextTool::useTableBorderCursor() { static const unsigned char data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x80, 0x7e, 0x00, 0x00, 0x40, 0x3f, 0x00, 0x00, 0xa0, 0x1f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xf4, 0x03, 0x00, 0x00, 0xe4, 0x01, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x80, 0x41, 0x00, 0x00, 0x40, 0x32, 0x00, 0x00, 0xa0, 0x0f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; QBitmap result(32, 32); result.fill(Qt::color0); QPainter painter(&result); painter.drawPixmap(0, 0, QBitmap::fromData(QSize(25, 23), data)); QBitmap brushMask = result.createHeuristicMask(false); useCursor(QCursor(result, brushMask, 1, 21)); } void TextTool::setTableBorderData(const KoBorder::BorderData &data) { m_tablePenMode = true; m_tablePenBorderData = data; } void TextTool::formatParagraph() { ParagraphSettingsDialog *dia = new ParagraphSettingsDialog(this, m_textEditor.data()); dia->setUnit(canvas()->unit()); dia->setImageCollection(m_textShape->imageCollection()); dia->exec(); delete dia; returnFocusToCanvas(); } void TextTool::testSlot(bool on) { qDebug() << "signal received. bool:" << on; } void TextTool::selectAll() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) { return; } const int selectionLength = qAbs(textEditor->position() - textEditor->anchor()); textEditor->movePosition(QTextCursor::End); textEditor->setPosition(0, QTextCursor::KeepAnchor); repaintSelection(); if (selectionLength != qAbs(textEditor->position() - textEditor->anchor())) { // it actually changed emit selectionChanged(true); } } void TextTool::startMacro(const QString &title) { if (title != i18n("Key Press") && title != i18n("Autocorrection")) { //dirty hack while waiting for refactor of text editing m_textTyping = false; } else { m_textTyping = true; } if (title != i18n("Delete") && title != i18n("Autocorrection")) { //same dirty hack as above m_textDeleting = false; } else { m_textDeleting = true; } if (m_currentCommand) { return; } class MacroCommand : public KUndo2Command { public: MacroCommand(const KUndo2MagicString &title) : KUndo2Command(title), m_first(true) {} void redo() override { if (!m_first) { KUndo2Command::redo(); } m_first = false; } bool mergeWith(const KUndo2Command *) override { return false; } bool m_first; }; /** * FIXME: The messages genearted by the Text Tool might not be * properly translated, since we don't control it in * type-safe way. * * The title is already translated string, we just don't * have any type control over it. */ KUndo2MagicString title_workaround = kundo2_noi18n(title); m_currentCommand = new MacroCommand(title_workaround); m_currentCommandHasChildren = false; } void TextTool::stopMacro() { if (!m_currentCommand) { return; } if (!m_currentCommandHasChildren) { delete m_currentCommand; } m_currentCommand = 0; } void TextTool::showStyleManager(int styleId) { if (!m_textShapeData) { return; } KoStyleManager *styleManager = KoTextDocument(m_textShapeData->document()).styleManager(); Q_ASSERT(styleManager); if (!styleManager) { return; //don't crash } StyleManagerDialog *dia = new StyleManagerDialog(canvas()->canvasWidget()); dia->setStyleManager(styleManager); dia->setUnit(canvas()->unit()); KoParagraphStyle *paragraphStyle = styleManager->paragraphStyle(styleId); if (paragraphStyle) { dia->setParagraphStyle(paragraphStyle); } KoCharacterStyle *characterStyle = styleManager->characterStyle(styleId); if (characterStyle) { dia->setCharacterStyle(characterStyle); } dia->show(); } void TextTool::startTextEditingPlugin(const QString &pluginId) { KoTextEditingPlugin *plugin = textEditingPluginContainer()->plugin(pluginId); if (plugin) { if (m_textEditor.data()->hasSelection()) { plugin->checkSection(m_textShapeData->document(), m_textEditor.data()->selectionStart(), m_textEditor.data()->selectionEnd()); } else { plugin->finishedWord(m_textShapeData->document(), m_textEditor.data()->position()); } } } void TextTool::canvasResourceChanged(int key, const QVariant &var) { if (m_textEditor.isNull()) { return; } if (!m_textShapeData) { return; } if (m_allowResourceManagerUpdates == false) { return; } if (key == KoText::CurrentTextPosition) { repaintSelection(); m_textEditor.data()->setPosition(var.toInt()); ensureCursorVisible(); } else if (key == KoText::CurrentTextAnchor) { repaintSelection(); int pos = m_textEditor.data()->position(); m_textEditor.data()->setPosition(var.toInt()); m_textEditor.data()->setPosition(pos, QTextCursor::KeepAnchor); } else if (key == KoCanvasResourceManager::Unit) { m_unit = var.value(); } else { return; } repaintSelection(); } void TextTool::insertSpecialCharacter() { if (m_specialCharacterDocker == 0) { m_specialCharacterDocker = new InsertCharacter(canvas()->canvasWidget()); connect(m_specialCharacterDocker, SIGNAL(insertCharacter(QString)), this, SLOT(insertString(QString))); } m_specialCharacterDocker->show(); } void TextTool::insertString(const QString &string) { m_textEditor.data()->insertText(string); returnFocusToCanvas(); } void TextTool::selectFont() { FontDia *fontDlg = new FontDia(m_textEditor.data()); fontDlg->exec(); delete fontDlg; returnFocusToCanvas(); } void TextTool::shapeAddedToCanvas() { qDebug(); if (m_textShape) { KoSelection *selection = canvas()->selectedShapesProxy()->selection(); KoShape *shape = selection->firstSelectedShape(); if (shape != m_textShape && canvas()->shapeManager()->shapes().contains(m_textShape)) { // this situation applies when someone, not us, changed the selection by selecting another // text shape. Possibly by adding one. // Deselect the new shape again, so we can keep editing what we were already editing selection->select(m_textShape); selection->deselect(shape); } } } void TextTool::shapeDataRemoved() { m_textShapeData = 0; m_textShape = 0; if (!m_textEditor.isNull() && !m_textEditor.data()->cursor()->isNull()) { const QTextDocument *doc = m_textEditor.data()->document(); Q_ASSERT(doc); KoTextDocumentLayout *lay = qobject_cast(doc->documentLayout()); if (!lay || lay->shapes().isEmpty()) { emit done(); return; } m_textShape = static_cast(lay->shapes().first()); m_textShapeData = static_cast(m_textShape->userData()); connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); } } void TextTool::createStyleFromCurrentBlockFormat(const QString &name) { KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoParagraphStyle *paragraphStyle = new KoParagraphStyle(m_textEditor.data()->blockFormat(), m_textEditor.data()->charFormat()); paragraphStyle->setName(name); styleManager->add(paragraphStyle); m_textEditor.data()->setStyle(paragraphStyle); emit charFormatChanged(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); emit blockFormatChanged(m_textEditor.data()->blockFormat()); } void TextTool::createStyleFromCurrentCharFormat(const QString &name) { KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoCharacterStyle *originalCharStyle = styleManager->characterStyle(m_textEditor.data()->charFormat().intProperty(KoCharacterStyle::StyleId)); KoCharacterStyle *autoStyle; if (!originalCharStyle) { KoCharacterStyle blankStyle; originalCharStyle = &blankStyle; autoStyle = originalCharStyle->autoStyle(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); autoStyle->setParentStyle(0); } else { autoStyle = originalCharStyle->autoStyle(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); } autoStyle->setName(name); styleManager->add(autoStyle); m_textEditor.data()->setStyle(autoStyle); emit charFormatChanged(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); } // ---------- editing plugins methods. void TextTool::editingPluginEvents() { if (m_prevCursorPosition == -1 || m_prevCursorPosition == m_textEditor.data()->position()) { qDebug() << "m_prevCursorPosition=" << m_prevCursorPosition << "m_textEditor.data()->position()=" << m_textEditor.data()->position(); return; } QTextBlock block = m_textEditor.data()->block(); if (!block.contains(m_prevCursorPosition)) { qDebug() << "m_prevCursorPosition=" << m_prevCursorPosition; finishedWord(); finishedParagraph(); m_prevCursorPosition = -1; } else { int from = m_prevCursorPosition; int to = m_textEditor.data()->position(); if (from > to) { - qSwap(from, to); + std::swap(from, to); } QString section = block.text().mid(from - block.position(), to - from); qDebug() << "from=" << from << "to=" << to; if (section.contains(' ')) { finishedWord(); m_prevCursorPosition = -1; } } } void TextTool::finishedWord() { if (m_textShapeData && textEditingPluginContainer()) { foreach (KoTextEditingPlugin *plugin, textEditingPluginContainer()->values()) { plugin->finishedWord(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::finishedParagraph() { if (m_textShapeData && textEditingPluginContainer()) { foreach (KoTextEditingPlugin *plugin, textEditingPluginContainer()->values()) { plugin->finishedParagraph(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::startingSimpleEdit() { if (m_textShapeData && textEditingPluginContainer()) { foreach (KoTextEditingPlugin *plugin, textEditingPluginContainer()->values()) { plugin->startingSimpleEdit(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::setTextColor(const KoColor &color) { m_textEditor.data()->setTextColor(color.toQColor()); } void TextTool::setBackgroundColor(const KoColor &color) { m_textEditor.data()->setTextBackgroundColor(color.toQColor()); } void TextTool::setGrowWidthToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoGrowWidth, enabled)); updateActions(); } void TextTool::setGrowHeightToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoGrowHeight, enabled)); updateActions(); } void TextTool::setShrinkToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::ShrinkToFitResize, enabled)); updateActions(); } void TextTool::runUrl(KoPointerEvent *event, QString &url) { QUrl _url = QUrl::fromUserInput(url); if (!_url.isLocalFile()) { QDesktopServices::openUrl(_url); } event->accept(); } void TextTool::debugTextDocument() { #ifndef NDEBUG if (!m_textShapeData) { return; } const int CHARSPERLINE = 80; // TODO Make configurable using ENV var? const int CHARPOSITION = 278301935; KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoInlineTextObjectManager *inlineManager = document.inlineTextObjectManager(); QTextBlock block = m_textShapeData->document()->begin(); for (; block.isValid(); block = block.next()) { QVariant var = block.blockFormat().property(KoParagraphStyle::StyleId); if (!var.isNull()) { KoParagraphStyle *ps = styleManager->paragraphStyle(var.toInt()); qDebug() << "--- Paragraph Style:" << (ps ? ps->name() : QString()) << var.toInt(); } var = block.charFormat().property(KoCharacterStyle::StyleId); if (!var.isNull()) { KoCharacterStyle *cs = styleManager->characterStyle(var.toInt()); qDebug() << "--- Character Style:" << (cs ? cs->name() : QString()) << var.toInt(); } int lastPrintedChar = -1; QTextBlock::iterator it; QString fragmentText; QList inlineCharacters; for (it = block.begin(); !it.atEnd(); ++it) { QTextFragment fragment = it.fragment(); if (!fragment.isValid()) { continue; } QTextCharFormat fmt = fragment.charFormat(); qDebug() << "changeId: " << fmt.property(KoCharacterStyle::ChangeTrackerId); const int fragmentStart = fragment.position() - block.position(); for (int i = fragmentStart; i < fragmentStart + fragment.length(); i += CHARSPERLINE) { if (lastPrintedChar == fragmentStart - 1) { fragmentText += '|'; } if (lastPrintedChar < fragmentStart || i > fragmentStart) { QString debug = block.text().mid(lastPrintedChar, CHARSPERLINE); lastPrintedChar += CHARSPERLINE; if (lastPrintedChar > block.length()) { debug += "\\n"; } qDebug() << debug; } var = fmt.property(KoCharacterStyle::StyleId); QString charStyleLong, charStyleShort; if (!var.isNull()) { // named style charStyleShort = QString::number(var.toInt()); KoCharacterStyle *cs = styleManager->characterStyle(var.toInt()); if (cs) { charStyleLong = cs->name(); } } if (inlineManager && fmt.hasProperty(KoCharacterStyle::InlineInstanceId)) { QTextCharFormat inlineFmt = fmt; inlineFmt.setProperty(CHARPOSITION, fragmentStart); inlineCharacters << inlineFmt; } if (fragment.length() > charStyleLong.length()) { fragmentText += charStyleLong; } else if (fragment.length() > charStyleShort.length()) { fragmentText += charStyleShort; } else if (fragment.length() >= 2) { fragmentText += QChar(8230); // elipses } int rest = fragmentStart - (lastPrintedChar - CHARSPERLINE) + fragment.length() - fragmentText.length(); rest = qMin(rest, CHARSPERLINE - fragmentText.length()); if (rest >= 2) { fragmentText = QString("%1%2").arg(fragmentText).arg(' ', rest); } if (rest >= 0) { fragmentText += '|'; } if (fragmentText.length() >= CHARSPERLINE) { qDebug() << fragmentText; fragmentText.clear(); } } } if (!fragmentText.isEmpty()) { qDebug() << fragmentText; } else if (block.length() == 1) { // no actual tet qDebug() << "\\n"; } foreach (const QTextCharFormat &cf, inlineCharacters) { KoInlineObject *object = inlineManager->inlineTextObject(cf); qDebug() << "At pos:" << cf.intProperty(CHARPOSITION) << object; // qDebug() << "-> id:" << cf.intProperty(577297549); } QTextList *list = block.textList(); if (list) { if (list->format().hasProperty(KoListStyle::StyleId)) { KoListStyle *ls = styleManager->listStyle(list->format().intProperty(KoListStyle::StyleId)); qDebug() << " List style applied:" << ls->styleId() << ls->name(); } else { qDebug() << " +- is a list..." << list; } } } #endif } void TextTool::debugTextStyles() { #ifndef NDEBUG if (!m_textShapeData) { return; } KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); QSet seenStyles; foreach (KoParagraphStyle *style, styleManager->paragraphStyles()) { qDebug() << style->styleId() << style->name() << (styleManager->defaultParagraphStyle() == style ? "[Default]" : ""); KoListStyle *ls = style->listStyle(); if (ls) { // optional ;) qDebug() << " +- ListStyle: " << ls->styleId() << ls->name() << (ls == styleManager->defaultListStyle() ? "[Default]" : ""); foreach (int level, ls->listLevels()) { KoListLevelProperties llp = ls->levelProperties(level); qDebug() << " | level" << llp.level() << " style (enum):" << llp.style(); if (llp.bulletCharacter().unicode() != 0) { qDebug() << " | bullet" << llp.bulletCharacter(); } } seenStyles << ls->styleId(); } } bool first = true; foreach (KoCharacterStyle *style, styleManager->characterStyles()) { if (seenStyles.contains(style->styleId())) { continue; } if (first) { qDebug() << "--- Character styles ---"; first = false; } qDebug() << style->styleId() << style->name(); qDebug() << style->font(); } first = true; foreach (KoListStyle *style, styleManager->listStyles()) { if (seenStyles.contains(style->styleId())) { continue; } if (first) { qDebug() << "--- List styles ---"; first = false; } qDebug() << style->styleId() << style->name() << (style == styleManager->defaultListStyle() ? "[Default]" : ""); } #endif } void TextTool::textDirectionChanged() { if (!m_allowActions || !m_textEditor.data()) { return; } QTextBlockFormat blockFormat; if (m_actionChangeDirection->isChecked()) { blockFormat.setProperty(KoParagraphStyle::TextProgressionDirection, KoText::RightLeftTopBottom); } else { blockFormat.setProperty(KoParagraphStyle::TextProgressionDirection, KoText::LeftRightTopBottom); } m_textEditor.data()->mergeBlockFormat(blockFormat); } void TextTool::setListLevel(int level) { if (level < 1 || level > 10) { return; } KoTextEditor *textEditor = m_textEditor.data(); if (textEditor->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::SetLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, level); textEditor->addCommand(cll); editingPluginEvents(); } } void TextTool::insertAnnotation() { AnnotationTextShape *shape = (AnnotationTextShape *)KoShapeRegistry::instance()->value(AnnotationShape_SHAPEID)->createDefaultShape(canvas()->shapeController()->resourceManager()); textEditor()->addAnnotation(shape); // Set annotation creator. KConfig cfg("kritarc"); cfg.reparseConfiguration(); KConfigGroup authorGroup(&cfg, "Author"); QStringList profiles = authorGroup.readEntry("profile-names", QStringList()); KSharedConfig::openConfig()->reparseConfiguration(); KConfigGroup appAuthorGroup(KSharedConfig::openConfig(), "Author"); QString profile = appAuthorGroup.readEntry("active-profile", ""); KConfigGroup cgs(&authorGroup, "Author-" + profile); if (profiles.contains(profile)) { KConfigGroup cgs(&authorGroup, "Author-" + profile); shape->setCreator(cgs.readEntry("creator")); } else { if (profile == "anonymous") { shape->setCreator("Anonymous"); } else { KUser user(KUser::UseRealUserID); shape->setCreator(user.property(KUser::FullName).toString()); } } // Set Annotation creation date. shape->setDate(QDate::currentDate().toString(Qt::ISODate)); } diff --git a/plugins/flake/textshape/commands/ShowChangesCommand.cpp b/plugins/flake/textshape/commands/ShowChangesCommand.cpp index f1d965e687..4159f20a95 100644 --- a/plugins/flake/textshape/commands/ShowChangesCommand.cpp +++ b/plugins/flake/textshape/commands/ShowChangesCommand.cpp @@ -1,195 +1,195 @@ /* * This file is part of the KDE project * Copyright (C) 2009 Ganesh Paramasivam * Copyright (C) 2009 Pierre Stirnweiss * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include "ShowChangesCommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ShowChangesCommand::ShowChangesCommand(bool showChanges, QTextDocument *document, KoCanvasBase *canvas, KUndo2Command *parent) : KoTextCommandBase(parent) , m_document(document) , m_first(true) , m_showChanges(showChanges) , m_canvas(canvas) { Q_ASSERT(document); m_changeTracker = KoTextDocument(m_document).changeTracker(); m_textEditor = KoTextDocument(m_document).textEditor(); if (showChanges) { setText(kundo2_i18n("Show Changes")); } else { setText(kundo2_i18n("Hide Changes")); } } void ShowChangesCommand::undo() { KoTextCommandBase::undo(); UndoRedoFinalizer finalizer(this); foreach (KUndo2Command *shapeCommand, m_shapeCommands) { shapeCommand->undo(); } emit toggledShowChange(!m_showChanges); enableDisableStates(!m_showChanges); } void ShowChangesCommand::redo() { if (!m_first) { KoTextCommandBase::redo(); UndoRedoFinalizer finalizer(this); foreach (KUndo2Command *shapeCommand, m_shapeCommands) { shapeCommand->redo(); } emit toggledShowChange(m_showChanges); enableDisableStates(m_showChanges); } else { m_first = false; enableDisableChanges(); } } void ShowChangesCommand::enableDisableChanges() { if (m_changeTracker) { enableDisableStates(m_showChanges); if (m_showChanges) { insertDeletedChanges(); } else { removeDeletedChanges(); } #if 0 TODO KoTextDocumentLayout *lay = qobject_cast(m_document->documentLayout()); if (lay) { lay->scheduleLayout(); } #endif } } void ShowChangesCommand::enableDisableStates(bool showChanges) { m_changeTracker->setDisplayChanges(showChanges); QTextCharFormat format = m_textEditor->charFormat(); format.clearProperty(KoCharacterStyle::ChangeTrackerId); m_textEditor->setCharFormat(format); } void ShowChangesCommand::insertDeletedChanges() { QVector elementVector; KoTextDocument(m_textEditor->document()).changeTracker()->getDeletedChanges(elementVector); - qSort(elementVector.begin(), elementVector.end()); + std::sort(elementVector.begin(), elementVector.end()); } void ShowChangesCommand::checkAndAddAnchoredShapes(int position, int length) { KoInlineTextObjectManager *inlineObjectManager = KoTextDocument(m_document).inlineTextObjectManager(); Q_ASSERT(inlineObjectManager); QTextCursor cursor = m_textEditor->document()->find(QString(QChar::ObjectReplacementCharacter), position); while (!cursor.isNull() && cursor.position() < position + length) { QTextCharFormat fmt = cursor.charFormat(); KoInlineObject *object = inlineObjectManager->inlineTextObject(fmt); Q_ASSERT(object); Q_UNUSED(object); /* FIXME KoTextAnchor *anchor = dynamic_cast(object); if (!anchor) { continue; } */ #if 0 // TODO -- since March 2010... KoTextDocumentLayout *lay = qobject_cast(m_document->documentLayout()); KoShapeContainer *container = dynamic_cast(lay->shapeForPosition(i)); // a very ugly hack. Since this class is going away soon, it should be okay if (!container) { container = dynamic_cast((lay->shapes()).at(0)); } if (container) { container->addShape(anchor->shape()); KUndo2Command *shapeCommand = m_canvas->shapeController()->addShapeDirect(anchor->shape()); shapeCommand->redo(); m_shapeCommands.push_front(shapeCommand); } #endif cursor = m_textEditor->document()->find(QString(QChar::ObjectReplacementCharacter), position); } } void ShowChangesCommand::removeDeletedChanges() { QVector elementVector; m_changeTracker->getDeletedChanges(elementVector); - qSort(elementVector.begin(), elementVector.end()); + std::sort(elementVector.begin(), elementVector.end()); } void ShowChangesCommand::checkAndRemoveAnchoredShapes(int position, int length) { KoInlineTextObjectManager *inlineObjectManager = KoTextDocument(m_document).inlineTextObjectManager(); Q_ASSERT(inlineObjectManager); QTextCursor cursor = m_textEditor->document()->find(QString(QChar::ObjectReplacementCharacter), position); while (!cursor.isNull() && cursor.position() < position + length) { QTextCharFormat fmt = cursor.charFormat(); KoInlineObject *object = inlineObjectManager->inlineTextObject(fmt); Q_ASSERT(object); Q_UNUSED(object); /* FIXME KoTextAnchor *anchor = dynamic_cast(object); if (!anchor) continue; KUndo2Command *shapeCommand = m_canvas->shapeController()->removeShape(anchor->shape()); shapeCommand->redo(); m_shapeCommands.push_front(shapeCommand); */ } } ShowChangesCommand::~ShowChangesCommand() { } diff --git a/plugins/flake/textshape/dialogs/StylesModel.cpp b/plugins/flake/textshape/dialogs/StylesModel.cpp index a1156350dc..e6b99a3ed8 100644 --- a/plugins/flake/textshape/dialogs/StylesModel.cpp +++ b/plugins/flake/textshape/dialogs/StylesModel.cpp @@ -1,584 +1,584 @@ /* This file is part of the KDE project * Copyright (C) 2008 Thomas Zander * Copyright (C) 2011 C. Boemann * Copyright (C) 2011-2012 Pierre Stirnweiss * * 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 "StylesModel.h" #include #include #include #include #include #include #include #include #include #include #include StylesModel::StylesModel(KoStyleManager *manager, AbstractStylesModel::Type modelType, QObject *parent) : AbstractStylesModel(parent) , m_styleManager(0) , m_currentParagraphStyle(0) , m_defaultCharacterStyle(0) , m_styleMapper(new QSignalMapper(this)) , m_provideStyleNone(false) { m_modelType = modelType; setStyleManager(manager); //Create a default characterStyle for the preview of "None" character style if (m_modelType == StylesModel::CharacterStyle) { m_defaultCharacterStyle = new KoCharacterStyle(); m_defaultCharacterStyle->setStyleId(NoneStyleId); m_defaultCharacterStyle->setName(i18n("None")); m_defaultCharacterStyle->setFontPointSize(12); m_provideStyleNone = true; } connect(m_styleMapper, SIGNAL(mapped(int)), this, SLOT(updateName(int))); } StylesModel::~StylesModel() { delete m_currentParagraphStyle; delete m_defaultCharacterStyle; } QModelIndex StylesModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column != 0) { return QModelIndex(); } if (!parent.isValid()) { if (row >= m_styleList.count()) { return QModelIndex(); } return createIndex(row, column, m_styleList[row]); } return QModelIndex(); } QModelIndex StylesModel::parent(const QModelIndex &child) const { Q_UNUSED(child); return QModelIndex(); } int StylesModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return m_styleList.count(); } return 0; } int StylesModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } QVariant StylesModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } int id = (int)index.internalId(); switch (role) { case Qt::DisplayRole: { return QVariant(); } case Qt::DecorationRole: { if (!m_styleThumbnailer) { return QPixmap(); } if (m_modelType == StylesModel::ParagraphStyle) { KoParagraphStyle *paragStyle = m_styleManager->paragraphStyle(id); if (paragStyle) { return m_styleThumbnailer->thumbnail(paragStyle); } if (!paragStyle && m_draftParStyleList.contains(id)) { return m_styleThumbnailer->thumbnail(m_draftParStyleList[id]); } } else { KoCharacterStyle *usedStyle = 0; if (id == NoneStyleId) { usedStyle = static_cast(m_currentParagraphStyle); if (!usedStyle) { usedStyle = m_defaultCharacterStyle; } usedStyle->setName(i18n("None")); if (usedStyle->styleId() >= 0) { //if the styleId is NoneStyleId, we are using the default character style usedStyle->setStyleId(-usedStyle->styleId()); //this style is not managed by the styleManager but its styleId will be used in the thumbnail cache as part of the key. } return m_styleThumbnailer->thumbnail(usedStyle); } else { usedStyle = m_styleManager->characterStyle(id); if (usedStyle) { return m_styleThumbnailer->thumbnail(usedStyle, m_currentParagraphStyle); } if (!usedStyle && m_draftCharStyleList.contains(id)) { return m_styleThumbnailer->thumbnail(m_draftCharStyleList[id]); } } } break; } case Qt::SizeHintRole: { return QVariant(QSize(250, 48)); } default: break; }; return QVariant(); } Qt::ItemFlags StylesModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return 0; } return (Qt::ItemIsSelectable | Qt::ItemIsEnabled); } void StylesModel::setCurrentParagraphStyle(int styleId) { if (!m_styleManager || m_currentParagraphStyle == m_styleManager->paragraphStyle(styleId) || !m_styleManager->paragraphStyle(styleId)) { return; //TODO do we create a default paragraphStyle? use the styleManager default? } if (m_currentParagraphStyle) { delete m_currentParagraphStyle; m_currentParagraphStyle = 0; } m_currentParagraphStyle = m_styleManager->paragraphStyle(styleId)->clone(); } void StylesModel::setProvideStyleNone(bool provide) { if (m_modelType == StylesModel::CharacterStyle) { m_provideStyleNone = provide; } } QModelIndex StylesModel::indexOf(const KoCharacterStyle *style) const { if (style) { return createIndex(m_styleList.indexOf(style->styleId()), 0, style->styleId()); } else { return QModelIndex(); } } QImage StylesModel::stylePreview(int row, const QSize &size) { if (!m_styleManager || !m_styleThumbnailer) { return QImage(); } if (m_modelType == StylesModel::ParagraphStyle) { KoParagraphStyle *usedStyle = 0; usedStyle = m_styleManager->paragraphStyle(index(row).internalId()); if (usedStyle) { return m_styleThumbnailer->thumbnail(usedStyle, size); } if (!usedStyle && m_draftParStyleList.contains(index(row).internalId())) { return m_styleThumbnailer->thumbnail(m_draftParStyleList[index(row).internalId()], size); } } else { KoCharacterStyle *usedStyle = 0; if (index(row).internalId() == (quintptr)NoneStyleId) { usedStyle = static_cast(m_currentParagraphStyle); if (!usedStyle) { usedStyle = m_defaultCharacterStyle; } usedStyle->setName(i18n("None")); if (usedStyle->styleId() >= 0) { usedStyle->setStyleId(-usedStyle->styleId()); //this style is not managed by the styleManager but its styleId will be used in the thumbnail cache as part of the key. } return m_styleThumbnailer->thumbnail(usedStyle, m_currentParagraphStyle, size); } else { usedStyle = m_styleManager->characterStyle(index(row).internalId()); if (usedStyle) { return m_styleThumbnailer->thumbnail(usedStyle, m_currentParagraphStyle, size); } if (!usedStyle && m_draftCharStyleList.contains(index(row).internalId())) { return m_styleThumbnailer->thumbnail(m_draftCharStyleList[index(row).internalId()], m_currentParagraphStyle, size); } } } return QImage(); } /* QImage StylesModel::stylePreview(QModelIndex &index, const QSize &size) { if (!m_styleManager || !m_styleThumbnailer) { return QImage(); } if (m_modelType == StylesModel::ParagraphStyle) { KoParagraphStyle *usedStyle = 0; usedStyle = m_styleManager->paragraphStyle(index.internalId()); if (usedStyle) { return m_styleThumbnailer->thumbnail(usedStyle, size); } if (!usedStyle && m_draftParStyleList.contains(index.internalId())) { return m_styleThumbnailer->thumbnail(m_draftParStyleList[index.internalId()], size); } } else { KoCharacterStyle *usedStyle = 0; if (index.internalId() == NoneStyleId) { usedStyle = static_cast(m_currentParagraphStyle); if (!usedStyle) { usedStyle = m_defaultCharacterStyle; } usedStyle->setName(i18n("None")); if (usedStyle->styleId() >= 0) { usedStyle->setStyleId(-usedStyle->styleId()); //this style is not managed by the styleManager but its styleId will be used in the thumbnail cache as part of the key. } return m_styleThumbnailer->thumbnail(usedStyle, m_currentParagraphStyle, size); } else { usedStyle = m_styleManager->characterStyle(index.internalId()); if (usedStyle) { return m_styleThumbnailer->thumbnail(usedStyle, m_currentParagraphStyle, size); } if (!usedStyle && m_draftCharStyleList.contains(index.internalId())) { return m_styleThumbnailer->thumbnail(m_draftCharStyleList[index.internalId()],m_currentParagraphStyle, size); } } } return QImage(); } */ void StylesModel::setStyleManager(KoStyleManager *sm) { if (sm == m_styleManager) { return; } if (m_styleManager) { disconnect(sm, SIGNAL(styleAdded(KoParagraphStyle*)), this, SLOT(addParagraphStyle(KoParagraphStyle*))); disconnect(sm, SIGNAL(styleAdded(KoCharacterStyle*)), this, SLOT(addCharacterStyle(KoCharacterStyle*))); disconnect(sm, SIGNAL(styleRemoved(KoParagraphStyle*)), this, SLOT(removeParagraphStyle(KoParagraphStyle*))); disconnect(sm, SIGNAL(styleRemoved(KoCharacterStyle*)), this, SLOT(removeCharacterStyle(KoCharacterStyle*))); } m_styleManager = sm; if (m_styleManager == 0) { return; } if (m_modelType == StylesModel::ParagraphStyle) { updateParagraphStyles(); connect(sm, SIGNAL(styleAdded(KoParagraphStyle*)), this, SLOT(addParagraphStyle(KoParagraphStyle*))); connect(sm, SIGNAL(styleRemoved(KoParagraphStyle*)), this, SLOT(removeParagraphStyle(KoParagraphStyle*))); } else { updateCharacterStyles(); connect(sm, SIGNAL(styleAdded(KoCharacterStyle*)), this, SLOT(addCharacterStyle(KoCharacterStyle*))); connect(sm, SIGNAL(styleRemoved(KoCharacterStyle*)), this, SLOT(removeCharacterStyle(KoCharacterStyle*))); } } void StylesModel::setStyleThumbnailer(KoStyleThumbnailer *thumbnailer) { m_styleThumbnailer = thumbnailer; } // called when the stylemanager adds a style void StylesModel::addParagraphStyle(KoParagraphStyle *style) { Q_ASSERT(style); QCollator collator; QList::iterator begin = m_styleList.begin(); int index = 0; for (; begin != m_styleList.end(); ++begin) { KoParagraphStyle *s = m_styleManager->paragraphStyle(*begin); if (!s && m_draftParStyleList.contains(*begin)) { s = m_draftParStyleList[*begin]; } // s should be found as the manager and the m_styleList should be in sync Q_ASSERT(s); if (collator.compare(style->name(), s->name()) < 0) { break; } ++index; } beginInsertRows(QModelIndex(), index, index); m_styleList.insert(begin, style->styleId()); m_styleMapper->setMapping(style, style->styleId()); connect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map())); endInsertRows(); } bool sortParagraphStyleByName(KoParagraphStyle *style1, KoParagraphStyle *style2) { Q_ASSERT(style1); Q_ASSERT(style2); return QCollator().compare(style1->name(), style2->name()) < 0; } void StylesModel::updateParagraphStyles() { Q_ASSERT(m_styleManager); beginResetModel(); m_styleList.clear(); QList styles = m_styleManager->paragraphStyles(); - qSort(styles.begin(), styles.end(), sortParagraphStyleByName); + std::sort(styles.begin(), styles.end(), sortParagraphStyleByName); Q_FOREACH (KoParagraphStyle *style, styles) { if (style != m_styleManager->defaultParagraphStyle()) { //The default character style is not user selectable. It only provides individual property defaults and is not a style per say. m_styleList.append(style->styleId()); m_styleMapper->setMapping(style, style->styleId()); connect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map())); } } endResetModel(); } // called when the stylemanager adds a style void StylesModel::addCharacterStyle(KoCharacterStyle *style) { Q_ASSERT(style); // find the place where we need to insert the style QCollator collator; QList::iterator begin = m_styleList.begin(); int index = 0; // the None style should also be the first one so only start after it if (begin != m_styleList.end() && *begin == NoneStyleId) { ++begin; ++index; } for (; begin != m_styleList.end(); ++begin) { KoCharacterStyle *s = m_styleManager->characterStyle(*begin); if (!s && m_draftCharStyleList.contains(*begin)) { s = m_draftCharStyleList[*begin]; } // s should be found as the manager and the m_styleList should be in sync Q_ASSERT(s); if (collator.compare(style->name(), s->name()) < 0) { break; } ++index; } beginInsertRows(QModelIndex(), index, index); m_styleList.insert(index, style->styleId()); endInsertRows(); m_styleMapper->setMapping(style, style->styleId()); connect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map())); } bool sortCharacterStyleByName(KoCharacterStyle *style1, KoCharacterStyle *style2) { Q_ASSERT(style1); Q_ASSERT(style2); return QCollator().compare(style1->name(), style2->name()) < 0; } void StylesModel::updateCharacterStyles() { Q_ASSERT(m_styleManager); beginResetModel(); m_styleList.clear(); if (m_provideStyleNone && m_styleManager->paragraphStyles().count()) { m_styleList.append(NoneStyleId); } QList styles = m_styleManager->characterStyles(); - qSort(styles.begin(), styles.end(), sortCharacterStyleByName); + std::sort(styles.begin(), styles.end(), sortCharacterStyleByName); Q_FOREACH (KoCharacterStyle *style, styles) { if (style != m_styleManager->defaultCharacterStyle()) { //The default character style is not user selectable. It only provides individual property defaults and is not a style per say. m_styleList.append(style->styleId()); m_styleMapper->setMapping(style, style->styleId()); connect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map())); } } endResetModel(); } // called when the stylemanager removes a style void StylesModel::removeParagraphStyle(KoParagraphStyle *style) { int row = m_styleList.indexOf(style->styleId()); beginRemoveRows(QModelIndex(), row, row); m_styleMapper->removeMappings(style); disconnect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map())); m_styleList.removeAt(row); endRemoveRows(); } // called when the stylemanager removes a style void StylesModel::removeCharacterStyle(KoCharacterStyle *style) { int row = m_styleList.indexOf(style->styleId()); beginRemoveRows(QModelIndex(), row, row); m_styleMapper->removeMappings(style); disconnect(style, SIGNAL(nameChanged(QString)), m_styleMapper, SLOT(map())); m_styleList.removeAt(row); endRemoveRows(); } void StylesModel::updateName(int styleId) { // updating the name of a style can mean that the style needs to be moved inside the list to keep the sort order. QCollator collator; int oldIndex = m_styleList.indexOf(styleId); if (oldIndex >= 0) { int newIndex = 0; if (m_modelType == StylesModel::ParagraphStyle) { KoParagraphStyle *paragStyle = m_styleManager->paragraphStyle(styleId); if (!paragStyle && m_draftParStyleList.contains(styleId)) { paragStyle = m_draftParStyleList.value(styleId); } if (paragStyle) { m_styleThumbnailer->removeFromCache(paragStyle); QList::iterator begin = m_styleList.begin(); for (; begin != m_styleList.end(); ++begin) { // don't test again the same style if (*begin == styleId) { continue; } KoParagraphStyle *s = m_styleManager->paragraphStyle(*begin); if (!s && m_draftParStyleList.contains(*begin)) { s = m_draftParStyleList[*begin]; } // s should be found as the manager and the m_styleList should be in sync Q_ASSERT(s); if (collator.compare(paragStyle->name(), s->name()) < 0) { break; } ++newIndex; } if (oldIndex != newIndex) { // beginMoveRows needs the index where it would be placed when it is still in the old position // so add one when newIndex > oldIndex beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newIndex > oldIndex ? newIndex + 1 : newIndex); m_styleList.removeAt(oldIndex); m_styleList.insert(newIndex, styleId); endMoveRows(); } } } else { KoCharacterStyle *characterStyle = m_styleManager->characterStyle(styleId); if (!characterStyle && m_draftCharStyleList.contains(styleId)) { characterStyle = m_draftCharStyleList[styleId]; } if (characterStyle) { m_styleThumbnailer->removeFromCache(characterStyle); QList::iterator begin = m_styleList.begin(); if (begin != m_styleList.end() && *begin == NoneStyleId) { ++begin; ++newIndex; } for (; begin != m_styleList.end(); ++begin) { // don't test again the same style if (*begin == styleId) { continue; } KoCharacterStyle *s = m_styleManager->characterStyle(*begin); if (!s && m_draftCharStyleList.contains(*begin)) { s = m_draftCharStyleList[*begin]; } // s should be found as the manager and the m_styleList should be in sync Q_ASSERT(s); if (collator.compare(characterStyle->name(), s->name()) < 0) { break; } ++newIndex; } if (oldIndex != newIndex) { // beginMoveRows needs the index where it would be placed when it is still in the old position // so add one when newIndex > oldIndex beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newIndex > oldIndex ? newIndex + 1 : newIndex); m_styleList.removeAt(oldIndex); m_styleList.insert(newIndex, styleId); endMoveRows(); } } } } } QModelIndex StylesModel::firstStyleIndex() { if (!m_styleList.count()) { return QModelIndex(); } return createIndex(m_styleList.indexOf(m_styleList.at(0)), 0, m_styleList.at(0)); } QList StylesModel::StyleList() { return m_styleList; } QHash StylesModel::draftParStyleList() { return m_draftParStyleList; } QHash StylesModel::draftCharStyleList() { return m_draftCharStyleList; } void StylesModel::addDraftParagraphStyle(KoParagraphStyle *style) { style->setStyleId(-(m_draftParStyleList.count() + 1)); m_draftParStyleList.insert(style->styleId(), style); addParagraphStyle(style); } void StylesModel::addDraftCharacterStyle(KoCharacterStyle *style) { if (m_draftCharStyleList.count() == 0) { // we have a character style "m_defaultCharacterStyle" with style id NoneStyleId in style model. style->setStyleId(-(m_draftCharStyleList.count() + 2)); } else { style->setStyleId(-(m_draftCharStyleList.count() + 1)); } m_draftCharStyleList.insert(style->styleId(), style); addCharacterStyle(style); } void StylesModel::clearDraftStyles() { Q_FOREACH (KoParagraphStyle *style, m_draftParStyleList.values()) { removeParagraphStyle(style); } m_draftParStyleList.clear(); Q_FOREACH (KoCharacterStyle *style, m_draftCharStyleList.values()) { removeCharacterStyle(style); } m_draftCharStyleList.clear(); } StylesModel::Type StylesModel::stylesType() const { return m_modelType; } diff --git a/plugins/flake/textshape/kotext/BibliographyGenerator.cpp b/plugins/flake/textshape/kotext/BibliographyGenerator.cpp index 6182b6a5cf..934d46d709 100644 --- a/plugins/flake/textshape/kotext/BibliographyGenerator.cpp +++ b/plugins/flake/textshape/kotext/BibliographyGenerator.cpp @@ -1,215 +1,215 @@ /* This file is part of the KDE project * Copyright (C) 2011 Smit Patel * * 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 "BibliographyGenerator.h" #include "KoInlineCite.h" #include #include #include #include #include #include #include static QList sortKeys; BibliographyGenerator::BibliographyGenerator(QTextDocument *bibDocument, const QTextBlock &block, KoBibliographyInfo *bibInfo) : QObject(bibDocument) , m_bibDocument(bibDocument) , m_bibInfo(bibInfo) , m_block(block) { Q_ASSERT(bibDocument); Q_ASSERT(bibInfo); m_bibInfo->setGenerator(this); bibDocument->setUndoRedoEnabled(false); generate(); } BibliographyGenerator::~BibliographyGenerator() { delete m_bibInfo; } static bool compare_on(int keyIndex, KoInlineCite *c1, KoInlineCite *c2) { if ( keyIndex == sortKeys.size() ) return false; else if (sortKeys[keyIndex].second == Qt::AscendingOrder) { if (c1->dataField( sortKeys[keyIndex].first ) < c2->dataField( sortKeys[keyIndex].first )) return true; else if (c1->dataField( sortKeys[keyIndex].first ) > c2->dataField( sortKeys[keyIndex].first )) return false; } else if (sortKeys[keyIndex].second == Qt::DescendingOrder) { if (c1->dataField( sortKeys[keyIndex].first ) < c2->dataField( sortKeys[keyIndex].first )) return false; else if (c1->dataField( sortKeys[keyIndex].first ) > c2->dataField( sortKeys[keyIndex].first )) return true; } else return compare_on( keyIndex + 1, c1, c2 ); return false; } static bool lessThan(KoInlineCite *c1, KoInlineCite *c2) { return compare_on(0, c1, c2); } static QList sort(QList cites, QList keys) { sortKeys = keys; sortKeys << QPair("identifier", Qt::AscendingOrder); - qSort(cites.begin(), cites.end(), lessThan); + std::sort(cites.begin(), cites.end(), lessThan); return cites; } void BibliographyGenerator::generate() { if (!m_bibInfo) return; QTextCursor cursor = m_bibDocument->rootFrame()->lastCursorPosition(); cursor.setPosition(m_bibDocument->rootFrame()->firstPosition(), QTextCursor::KeepAnchor); cursor.beginEditBlock(); KoStyleManager *styleManager = KoTextDocument(m_block.document()).styleManager(); if (!m_bibInfo->m_indexTitleTemplate.text.isNull()) { KoParagraphStyle *titleStyle = styleManager->paragraphStyle(m_bibInfo->m_indexTitleTemplate.styleId); if (!titleStyle) { titleStyle = styleManager->defaultBibliographyTitleStyle(); m_bibInfo->m_indexTitleTemplate.styleName = titleStyle->name(); } QTextBlock titleTextBlock = cursor.block(); titleStyle->applyStyle(titleTextBlock); cursor.insertText(m_bibInfo->m_indexTitleTemplate.text); cursor.insertBlock(); } QTextCharFormat savedCharFormat = cursor.charFormat(); QList citeList; if ( KoTextDocument(m_block.document()).styleManager()->bibliographyConfiguration()->sortByPosition() ) { citeList = KoTextDocument(m_block.document()) .inlineTextObjectManager()->citationsSortedByPosition(false, m_block.document()->firstBlock()); } else { KoTextDocument *doc = new KoTextDocument(m_block.document()); citeList = sort(doc->inlineTextObjectManager()->citationsSortedByPosition(false, m_block.document()->firstBlock()), KoTextDocument(m_block.document()).styleManager()->bibliographyConfiguration()->sortKeys()); } foreach (KoInlineCite *cite, citeList) { KoParagraphStyle *bibTemplateStyle = 0; BibliographyEntryTemplate bibEntryTemplate; if (m_bibInfo->m_entryTemplate.contains(cite->bibliographyType())) { bibEntryTemplate = m_bibInfo->m_entryTemplate[cite->bibliographyType()]; bibTemplateStyle = styleManager->paragraphStyle(bibEntryTemplate.styleId); if (bibTemplateStyle == 0) { bibTemplateStyle = styleManager->defaultBibliographyEntryStyle(bibEntryTemplate.bibliographyType); bibEntryTemplate.styleName = bibTemplateStyle->name(); } } else { continue; } cursor.insertBlock(QTextBlockFormat(),QTextCharFormat()); QTextBlock bibEntryTextBlock = cursor.block(); bibTemplateStyle->applyStyle(bibEntryTextBlock); bool spanEnabled = false; //true if data field is not empty foreach (IndexEntry * entry, bibEntryTemplate.indexEntries) { switch(entry->name) { case IndexEntry::BIBLIOGRAPHY: { IndexEntryBibliography *indexEntry = static_cast(entry); cursor.insertText(QString(((spanEnabled)?" ":"")).append(cite->dataField(indexEntry->dataField))); spanEnabled = !cite->dataField(indexEntry->dataField).isEmpty(); break; } case IndexEntry::SPAN: { if(spanEnabled) { IndexEntrySpan *span = static_cast(entry); cursor.insertText(span->text); } break; } case IndexEntry::TAB_STOP: { IndexEntryTabStop *tabEntry = static_cast(entry); cursor.insertText("\t"); QTextBlockFormat blockFormat = cursor.blockFormat(); QList tabList; if (tabEntry->m_position == "MAX") { tabEntry->tab.position = m_maxTabPosition; } else { tabEntry->tab.position = tabEntry->m_position.toDouble(); } tabList.append(QVariant::fromValue(tabEntry->tab)); blockFormat.setProperty(KoParagraphStyle::TabPositions, QVariant::fromValue >(tabList)); cursor.setBlockFormat(blockFormat); break; } default:{ break; } } }// foreach } cursor.setCharFormat(savedCharFormat); // restore the cursor char format } QMap BibliographyGenerator::defaultBibliographyEntryTemplates() { QMap entryTemplates; foreach (const QString &bibType, KoOdfBibliographyConfiguration::bibTypes) { BibliographyEntryTemplate bibEntryTemplate; //Now creating default IndexEntries for all BibliographyEntryTemplates IndexEntryBibliography *identifier = new IndexEntryBibliography(QString()); IndexEntryBibliography *author = new IndexEntryBibliography(QString()); IndexEntryBibliography *title = new IndexEntryBibliography(QString()); IndexEntryBibliography *year = new IndexEntryBibliography(QString()); IndexEntrySpan *firstSpan = new IndexEntrySpan(QString()); IndexEntrySpan *otherSpan = new IndexEntrySpan(QString()); identifier->dataField = "identifier"; author->dataField = "author"; title->dataField = "title"; year->dataField = "year"; firstSpan->text = ":"; otherSpan->text = ","; bibEntryTemplate.bibliographyType = bibType; bibEntryTemplate.indexEntries.append(static_cast(identifier)); bibEntryTemplate.indexEntries.append(static_cast(firstSpan)); bibEntryTemplate.indexEntries.append(static_cast(author)); bibEntryTemplate.indexEntries.append(static_cast(otherSpan)); bibEntryTemplate.indexEntries.append(static_cast(title)); bibEntryTemplate.indexEntries.append(static_cast(otherSpan)); bibEntryTemplate.indexEntries.append(static_cast(year)); entryTemplates[bibType] = bibEntryTemplate; } return entryTemplates; } diff --git a/plugins/flake/textshape/kotext/KoTextOdfSaveHelper.cpp b/plugins/flake/textshape/kotext/KoTextOdfSaveHelper.cpp index cd5c978754..30b7c7477e 100644 --- a/plugins/flake/textshape/kotext/KoTextOdfSaveHelper.cpp +++ b/plugins/flake/textshape/kotext/KoTextOdfSaveHelper.cpp @@ -1,106 +1,106 @@ /* This file is part of the KDE project * Copyright (C) 2008 Thorsten Zachmann * 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 "KoTextOdfSaveHelper.h" #include #include #include "KoTextWriter.h" #include #include #include struct Q_DECL_HIDDEN KoTextOdfSaveHelper::Private { Private(const QTextDocument *document, int from, int to) : context(0) , document(document) , from(from) , to(to) #ifdef SHOULD_BUILD_RDF , rdfModel(0) #endif { } KoShapeSavingContext *context; const QTextDocument *document; int from; int to; #ifdef SHOULD_BUILD_RDF QSharedPointer rdfModel; //< This is so cut/paste can serialize the relevant RDF to the clipboard #endif }; KoTextOdfSaveHelper::KoTextOdfSaveHelper(const QTextDocument *document, int from, int to) : d(new Private(document, from, to)) { } KoTextOdfSaveHelper::~KoTextOdfSaveHelper() { delete d; } bool KoTextOdfSaveHelper::writeBody() { if (d->to < d->from) { - qSwap(d->to, d->from); + std::swap(d->to, d->from); } Q_ASSERT(d->context); KoXmlWriter & bodyWriter = d->context->xmlWriter(); bodyWriter.startElement("office:body"); bodyWriter.startElement(KoOdf::bodyContentElement(KoOdf::Text, true)); KoTextWriter writer(*d->context, 0); writer.write(d->document, d->from, d->to); bodyWriter.endElement(); // office:element bodyWriter.endElement(); // office:body return true; } KoShapeSavingContext * KoTextOdfSaveHelper::context(KoXmlWriter * bodyWriter, KoGenStyles & mainStyles, KoEmbeddedDocumentSaver & embeddedSaver) { d->context = new KoShapeSavingContext(*bodyWriter, mainStyles, embeddedSaver); return d->context; } #ifdef SHOULD_BUILD_RDF void KoTextOdfSaveHelper::setRdfModel(QSharedPointer m) { d->rdfModel = m; } QSharedPointer KoTextOdfSaveHelper::rdfModel() const { return d->rdfModel; } #endif KoStyleManager *KoTextOdfSaveHelper::styleManager() const { return KoTextDocument(d->document).styleManager(); } diff --git a/plugins/flake/textshape/kotext/commands/DeleteAnchorsCommand.cpp b/plugins/flake/textshape/kotext/commands/DeleteAnchorsCommand.cpp index 967a2fcd9b..6c370847f3 100644 --- a/plugins/flake/textshape/kotext/commands/DeleteAnchorsCommand.cpp +++ b/plugins/flake/textshape/kotext/commands/DeleteAnchorsCommand.cpp @@ -1,107 +1,107 @@ /* 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 "DeleteAnchorsCommand.h" #include #include "KoAnchorInlineObject.h" #include "KoAnchorTextRange.h" #include "KoTextDocument.h" #include "KoInlineTextObjectManager.h" #include "KoTextRangeManager.h" #include "TextDebug.h" bool sortAnchor(KoAnchorInlineObject *a1, KoAnchorInlineObject *a2) { return a1->position() > a2->position(); } DeleteAnchorsCommand::DeleteAnchorsCommand(const QList &anchorObjects, QTextDocument *document, KUndo2Command *parent) : KUndo2Command(parent) , m_document(document) , m_first(true) , m_deleteAnchors(false) { foreach (KoShapeAnchor *anchor, anchorObjects) { KoAnchorInlineObject *anchorObject = dynamic_cast(anchor->textLocation()); KoAnchorTextRange *anchorRange = dynamic_cast(anchor->textLocation()); if (anchorObject && anchorObject->document() == document) { m_anchorObjects.append(anchorObject); } else if (anchorRange && anchorRange->document() == document) { m_anchorRanges.append(anchorRange); } } - qSort(m_anchorObjects.begin(), m_anchorObjects.end(), sortAnchor); + std::sort(m_anchorObjects.begin(), m_anchorObjects.end(), sortAnchor); } DeleteAnchorsCommand::~DeleteAnchorsCommand() { if (m_deleteAnchors) { qDeleteAll(m_anchorRanges); } } void DeleteAnchorsCommand::redo() { KUndo2Command::redo(); m_deleteAnchors = true; if (m_first) { m_first = false; foreach (KoAnchorInlineObject *anchorObject, m_anchorObjects) { QTextCursor cursor(m_document); cursor.setPosition(anchorObject->position()); cursor.deleteChar(); } } KoInlineTextObjectManager *manager = KoTextDocument(m_document).inlineTextObjectManager(); Q_ASSERT(manager); if (manager) { foreach (KoAnchorInlineObject *anchorObject, m_anchorObjects) { manager->removeInlineObject(anchorObject); } } KoTextRangeManager *rangeManager = KoTextDocument(m_document).textRangeManager(); if (rangeManager) { foreach (KoAnchorTextRange *anchorRange, m_anchorRanges) { rangeManager->remove(anchorRange); m_document->markContentsDirty(anchorRange->position(), 0); } } } void DeleteAnchorsCommand::undo() { KoInlineTextObjectManager *manager = KoTextDocument(m_document).inlineTextObjectManager(); Q_ASSERT(manager); if (manager) { foreach (KoAnchorInlineObject *anchorObject, m_anchorObjects) { manager->addInlineObject(anchorObject); } } KUndo2Command::undo(); KoTextRangeManager *rangeManager = KoTextDocument(m_document).textRangeManager(); if (rangeManager) { foreach (KoAnchorTextRange *anchorRange, m_anchorRanges) { rangeManager->insert(anchorRange); m_document->markContentsDirty(anchorRange->position(), 0); } } m_deleteAnchors = false; } diff --git a/plugins/flake/textshape/kotext/commands/DeleteCommand.cpp b/plugins/flake/textshape/kotext/commands/DeleteCommand.cpp index 2c67a2e2ad..d9d2a8e28f 100644 --- a/plugins/flake/textshape/kotext/commands/DeleteCommand.cpp +++ b/plugins/flake/textshape/kotext/commands/DeleteCommand.cpp @@ -1,613 +1,613 @@ /* This file is part of the KDE project * Copyright (C) 2009 Ganesh Paramasivam * Copyright (C) 2009 Pierre Stirnweiss * Copyright (C) 2010 Thomas Zander * Copyright (C) 2012 C. Boemann * Copyright (C) 2014-2015 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA.*/ #include "DeleteCommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include bool DeleteCommand::SectionDeleteInfo::operator<(const DeleteCommand::SectionDeleteInfo &other) const { // At first we remove sections that lays deeper in tree // On one level we delete sections by descending order of their childIdx // That is needed on undo, cuz we want it to be simply done by inserting // sections back in reverse order of their deletion. // Without childIdx compare it is possible that we will want to insert // section on position 2 while the number of children is less than 2. if (section->level() != other.section->level()) { return section->level() > other.section->level(); } return childIdx > other.childIdx; } DeleteCommand::DeleteCommand(DeleteMode mode, QTextDocument *document, KoShapeController *shapeController, KUndo2Command *parent) : KoTextCommandBase (parent) , m_document(document) , m_shapeController(shapeController) , m_first(true) , m_mode(mode) , m_mergePossible(true) { setText(kundo2_i18n("Delete")); } void DeleteCommand::undo() { KoTextCommandBase::undo(); UndoRedoFinalizer finalizer(this); // Look at KoTextCommandBase documentation // KoList updateListChanges(); // KoTextRange KoTextRangeManager *rangeManager = KoTextDocument(m_document).textRangeManager(); foreach (KoTextRange *range, m_rangesToRemove) { rangeManager->insert(range); } // KoInlineObject foreach (KoInlineObject *object, m_invalidInlineObjects) { object->manager()->addInlineObject(object); } // KoSectionModel insertSectionsToModel(); } void DeleteCommand::redo() { if (!m_first) { KoTextCommandBase::redo(); UndoRedoFinalizer finalizer(this); // Look at KoTextCommandBase documentation // KoTextRange KoTextRangeManager *rangeManager = KoTextDocument(m_document).textRangeManager(); foreach (KoTextRange *range, m_rangesToRemove) { rangeManager->remove(range); } // KoSectionModel deleteSectionsFromModel(); // TODO: there is nothing for InlineObjects and Lists. Is it OK? } else { m_first = false; if (m_document) { KoTextEditor *textEditor = KoTextDocument(m_document).textEditor(); if (textEditor) { textEditor->beginEditBlock(); doDelete(); textEditor->endEditBlock(); } } } } // Section handling algorithm: // At first, we go though the all section starts and ends // that are in selection, and delete all pairs, because // they will be deleted. // Then we have multiple cases: selection start split some block // or don't split any block. // In the first case all formatting info will be stored in the // split block(it has startBlockNum number). // In the second case it will be stored in the block pointed by the // selection end(it has endBlockNum number). // Also there is a trivial case, when whole selection is inside // one block, in this case hasEntirelyInsideBlock will be false // and we will do nothing. class DeleteVisitor : public KoTextVisitor { public: DeleteVisitor(KoTextEditor *editor, DeleteCommand *command) : KoTextVisitor(editor) , m_first(true) , m_command(command) , m_startBlockNum(-1) , m_endBlockNum(-1) , m_hasEntirelyInsideBlock(false) { } void visitBlock(QTextBlock &block, const QTextCursor &caret) override { for (QTextBlock::iterator it = block.begin(); it != block.end(); ++it) { QTextCursor fragmentSelection(caret); fragmentSelection.setPosition(qMax(caret.selectionStart(), it.fragment().position())); fragmentSelection.setPosition( qMin(caret.selectionEnd(), it.fragment().position() + it.fragment().length()), QTextCursor::KeepAnchor ); if (fragmentSelection.anchor() >= fragmentSelection.position()) { continue; } visitFragmentSelection(fragmentSelection); } // Section handling below bool doesBeginInside = false; bool doesEndInside = false; if (block.position() >= caret.selectionStart()) { // Begin of the block is inside selection. doesBeginInside = true; QList openList = KoSectionUtils::sectionStartings(block.blockFormat()); foreach (KoSection *sec, openList) { m_curSectionDelimiters.push_back(SectionHandle(sec->name(), sec)); } } if (block.position() + block.length() <= caret.selectionEnd()) { // End of the block is inside selection. doesEndInside = true; QList closeList = KoSectionUtils::sectionEndings(block.blockFormat()); foreach (KoSectionEnd *se, closeList) { if (!m_curSectionDelimiters.empty() && m_curSectionDelimiters.last().name == se->name()) { KoSection *section = se->correspondingSection(); int childIdx = KoTextDocument(m_command->m_document).sectionModel() ->findRowOfChild(section); m_command->m_sectionsToRemove.push_back( DeleteCommand::SectionDeleteInfo( section, childIdx ) ); m_curSectionDelimiters.pop_back(); // This section will die } else { m_curSectionDelimiters.push_back(SectionHandle(se->name(), se)); } } } if (!doesBeginInside && doesEndInside) { m_startBlockNum = block.blockNumber(); } else if (doesBeginInside && !doesEndInside) { m_endBlockNum = block.blockNumber(); } else if (doesBeginInside && doesEndInside) { m_hasEntirelyInsideBlock = true; } } void visitFragmentSelection(QTextCursor &fragmentSelection) override { if (m_first) { m_firstFormat = fragmentSelection.charFormat(); m_first = false; } if (m_command->m_mergePossible && fragmentSelection.charFormat() != m_firstFormat) { m_command->m_mergePossible = false; } // Handling InlineObjects below KoTextDocument textDocument(fragmentSelection.document()); KoInlineTextObjectManager *manager = textDocument.inlineTextObjectManager(); QString selected = fragmentSelection.selectedText(); fragmentSelection.setPosition(fragmentSelection.selectionStart() + 1); int position = fragmentSelection.position(); const QChar *data = selected.constData(); for (int i = 0; i < selected.length(); i++) { if (data->unicode() == QChar::ObjectReplacementCharacter) { fragmentSelection.setPosition(position + i); KoInlineObject *object = manager->inlineTextObject(fragmentSelection); m_command->m_invalidInlineObjects.insert(object); } data++; } } enum SectionHandleAction { SectionClose, ///< Denotes close of the section. SectionOpen ///< Denotes start or beginning of the section. }; /// Helper struct for handling sections. struct SectionHandle { QString name; ///< Name of the section. SectionHandleAction type; ///< Action of a SectionHandle. KoSection *dataSec; ///< Pointer to KoSection. KoSectionEnd *dataSecEnd; ///< Pointer to KoSectionEnd. SectionHandle(QString _name, KoSection *_data) : name(_name) , type(SectionOpen) , dataSec(_data) , dataSecEnd(0) { } SectionHandle(QString _name, KoSectionEnd *_data) : name(_name) , type(SectionClose) , dataSec(0) , dataSecEnd(_data) { } }; bool m_first; DeleteCommand *m_command; QTextCharFormat m_firstFormat; int m_startBlockNum; int m_endBlockNum; bool m_hasEntirelyInsideBlock; QList m_curSectionDelimiters; }; void DeleteCommand::finalizeSectionHandling(QTextCursor *cur, DeleteVisitor &v) { // Lets handle pointers from block formats first // It means that selection isn't within one block. if (v.m_hasEntirelyInsideBlock || v.m_startBlockNum != -1 || v.m_endBlockNum != -1) { QList openList; QList closeList; foreach (const DeleteVisitor::SectionHandle &handle, v.m_curSectionDelimiters) { if (handle.type == v.SectionOpen) { // Start of the section. openList << handle.dataSec; } else { // End of the section. closeList << handle.dataSecEnd; } } // We're expanding ends in affected blocks to the end of the start block, // delete all sections, that are entirely in affected blocks, // and move ends, we have, to the begin of the next after the end block. if (v.m_startBlockNum != -1) { QTextBlockFormat fmt = cur->document()->findBlockByNumber(v.m_startBlockNum).blockFormat(); QTextBlockFormat fmt2 = cur->document()->findBlockByNumber(v.m_endBlockNum + 1).blockFormat(); fmt.clearProperty(KoParagraphStyle::SectionEndings); // m_endBlockNum != -1 in this case. QList closeListEndBlock = KoSectionUtils::sectionEndings( cur->document()->findBlockByNumber(v.m_endBlockNum).blockFormat()); while (!openList.empty() && !closeListEndBlock.empty() && openList.last()->name() == closeListEndBlock.first()->name()) { int childIdx = KoTextDocument(m_document) .sectionModel()->findRowOfChild(openList.back()); m_sectionsToRemove.push_back( DeleteCommand::SectionDeleteInfo( openList.back(), childIdx ) ); openList.pop_back(); closeListEndBlock.pop_front(); } openList << KoSectionUtils::sectionStartings(fmt2); closeList << closeListEndBlock; // We leave open section of start block untouched. KoSectionUtils::setSectionStartings(fmt2, openList); KoSectionUtils::setSectionEndings(fmt, closeList); QTextCursor changer = *cur; changer.setPosition(cur->document()->findBlockByNumber(v.m_startBlockNum).position()); changer.setBlockFormat(fmt); if (v.m_endBlockNum + 1 < cur->document()->blockCount()) { changer.setPosition(cur->document()->findBlockByNumber(v.m_endBlockNum + 1).position()); changer.setBlockFormat(fmt2); } } else { // v.m_startBlockNum == -1 // v.m_endBlockNum != -1 in this case. // We're pushing all new section info to the end block. QTextBlockFormat fmt = cur->document()->findBlockByNumber(v.m_endBlockNum).blockFormat(); QList allStartings = KoSectionUtils::sectionStartings(fmt); fmt.clearProperty(KoParagraphStyle::SectionStartings); QList pairedEndings; QList unpairedEndings; foreach (KoSectionEnd *se, KoSectionUtils::sectionEndings(fmt)) { KoSection *sec = se->correspondingSection(); if (allStartings.contains(sec)) { pairedEndings << se; } else { unpairedEndings << se; } } if (cur->selectionStart()) { QTextCursor changer = *cur; changer.setPosition(cur->selectionStart() - 1); QTextBlockFormat prevFmt = changer.blockFormat(); QList prevEndings = KoSectionUtils::sectionEndings(prevFmt); prevEndings = prevEndings + closeList; KoSectionUtils::setSectionEndings(prevFmt, prevEndings); changer.setBlockFormat(prevFmt); } KoSectionUtils::setSectionStartings(fmt, openList); KoSectionUtils::setSectionEndings(fmt, pairedEndings + unpairedEndings); QTextCursor changer = *cur; changer.setPosition(cur->document()->findBlockByNumber(v.m_endBlockNum).position()); changer.setBlockFormat(fmt); } } // Now lets deal with KoSectionModel - qSort(m_sectionsToRemove.begin(), m_sectionsToRemove.end()); + std::sort(m_sectionsToRemove.begin(), m_sectionsToRemove.end()); deleteSectionsFromModel(); } void DeleteCommand::deleteSectionsFromModel() { KoSectionModel *model = KoTextDocument(m_document).sectionModel(); foreach (const SectionDeleteInfo &info, m_sectionsToRemove) { model->deleteFromModel(info.section); } } void DeleteCommand::insertSectionsToModel() { KoSectionModel *model = KoTextDocument(m_document).sectionModel(); QList::iterator it = m_sectionsToRemove.end(); while (it != m_sectionsToRemove.begin()) { it--; model->insertToModel(it->section, it->childIdx); } } void DeleteCommand::doDelete() { KoTextEditor *textEditor = KoTextDocument(m_document).textEditor(); Q_ASSERT(textEditor); QTextCursor *caret = textEditor->cursor(); QTextCharFormat charFormat = caret->charFormat(); bool caretAtBeginOfBlock = (caret->position() == caret->block().position()); if (!textEditor->hasSelection()) { if (m_mode == PreviousChar) { caret->movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); } else { caret->movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); } } DeleteVisitor visitor(textEditor, this); textEditor->recursivelyVisitSelection(m_document.data()->rootFrame()->begin(), visitor); // Sections Model finalizeSectionHandling(caret, visitor); // Finalize section handling routine. // InlineObjects foreach (KoInlineObject *object, m_invalidInlineObjects) { deleteInlineObject(object); } // Ranges KoTextRangeManager *rangeManager = KoTextDocument(m_document).textRangeManager(); m_rangesToRemove = rangeManager->textRangesChangingWithin( textEditor->document(), textEditor->selectionStart(), textEditor->selectionEnd(), textEditor->selectionStart(), textEditor->selectionEnd() ); foreach (KoTextRange *range, m_rangesToRemove) { KoAnchorTextRange *anchorRange = dynamic_cast(range); KoAnnotation *annotation = dynamic_cast(range); if (anchorRange) { // we should only delete the anchor if the selection is covering it... not if the selection is // just adjecent to the anchor. This is more in line with what other wordprocessors do if (anchorRange->position() != textEditor->selectionStart() && anchorRange->position() != textEditor->selectionEnd()) { KoShape *shape = anchorRange->anchor()->shape(); if (m_shapeController) { KUndo2Command *shapeDeleteCommand = m_shapeController->removeShape(shape, this); shapeDeleteCommand->redo(); } // via m_shapeController->removeShape a DeleteAnchorsCommand should be created that // also calls rangeManager->remove(range), so we shouldn't do that here aswell } } else if (annotation) { KoShape *shape = annotation->annotationShape(); if (m_shapeController) { KUndo2Command *shapeDeleteCommand = m_shapeController->removeShape(shape, this); shapeDeleteCommand->redo(); } // via m_shapeController->removeShape a DeleteAnnotationsCommand should be created that // also calls rangeManager->remove(range), so we shouldn't do that here aswell } else { rangeManager->remove(range); } } // Check: is merge possible? if (textEditor->hasComplexSelection()) { m_mergePossible = false; } //FIXME: lets forbid merging of "section affecting" deletions by now if (!m_sectionsToRemove.empty()) { m_mergePossible = false; } if (m_mergePossible) { // Store various info needed for checkMerge m_format = textEditor->charFormat(); m_position = textEditor->selectionStart(); m_length = textEditor->selectionEnd() - textEditor->selectionStart(); } // Actual deletion of text caret->deleteChar(); if (m_mode != PreviousChar || !caretAtBeginOfBlock) { caret->setCharFormat(charFormat); } } void DeleteCommand::deleteInlineObject(KoInlineObject *object) { if (object) { KoAnchorInlineObject *anchorObject = dynamic_cast(object); if (anchorObject) { KoShape *shape = anchorObject->anchor()->shape(); KUndo2Command *shapeDeleteCommand = m_shapeController->removeShape(shape, this); shapeDeleteCommand->redo(); } else { object->manager()->removeInlineObject(object); } } } int DeleteCommand::id() const { // Should be an enum declared somewhere. KoTextCommandBase.h ??? return 56789; } bool DeleteCommand::mergeWith(const KUndo2Command *command) { class UndoTextCommand : public KUndo2Command { public: UndoTextCommand(QTextDocument *document, KUndo2Command *parent = 0) : KUndo2Command(kundo2_i18n("Text"), parent), m_document(document) {} void undo() override { QTextDocument *doc = m_document.data(); if (doc) doc->undo(KoTextDocument(doc).textEditor()->cursor()); } void redo() override { QTextDocument *doc = m_document.data(); if (doc) doc->redo(KoTextDocument(doc).textEditor()->cursor()); } QWeakPointer m_document; }; KoTextEditor *textEditor = KoTextDocument(m_document).textEditor(); if (textEditor == 0) return false; if (command->id() != id()) return false; if (!checkMerge(command)) return false; DeleteCommand *other = const_cast(static_cast(command)); m_invalidInlineObjects += other->m_invalidInlineObjects; other->m_invalidInlineObjects.clear(); for (int i=0; i < command->childCount(); i++) new UndoTextCommand(const_cast(textEditor->document()), this); return true; } bool DeleteCommand::checkMerge(const KUndo2Command *command) { DeleteCommand *other = const_cast(static_cast(command)); if (!(m_mergePossible && other->m_mergePossible)) return false; if (m_position == other->m_position && m_format == other->m_format) { m_length += other->m_length; return true; } if ((other->m_position + other->m_length == m_position) && (m_format == other->m_format)) { m_position = other->m_position; m_length += other->m_length; return true; } return false; } void DeleteCommand::updateListChanges() { KoTextEditor *textEditor = KoTextDocument(m_document).textEditor(); if (textEditor == 0) return; QTextDocument *document = const_cast(textEditor->document()); QTextCursor tempCursor(document); QTextBlock startBlock = document->findBlock(m_position); QTextBlock endBlock = document->findBlock(m_position + m_length); if (endBlock != document->end()) endBlock = endBlock.next(); QTextList *currentList; for (QTextBlock currentBlock = startBlock; currentBlock != endBlock; currentBlock = currentBlock.next()) { tempCursor.setPosition(currentBlock.position()); currentList = tempCursor.currentList(); if (currentList) { KoListStyle::ListIdType listId; if (sizeof(KoListStyle::ListIdType) == sizeof(uint)) listId = currentList->format().property(KoListStyle::ListId).toUInt(); else listId = currentList->format().property(KoListStyle::ListId).toULongLong(); if (!KoTextDocument(document).list(currentBlock)) { KoList *list = KoTextDocument(document).list(listId); if (list) { list->updateStoredList(currentBlock); } } } } } DeleteCommand::~DeleteCommand() { } diff --git a/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp b/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp index 8412988323..a1e9a2843c 100644 --- a/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp +++ b/plugins/flake/textshape/kotext/opendocument/KoTextWriter_p.cpp @@ -1,1116 +1,1116 @@ /* * 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* , TagInformation* ) { } /* 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); + std::sort(subSpanTos.begin(), subSpanTos.end()); // 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 &, KoXmlElement &) { } 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") && !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/KoParagraphStyle.cpp b/plugins/flake/textshape/kotext/styles/KoParagraphStyle.cpp index cc3c51dc57..88bc31dc64 100644 --- a/plugins/flake/textshape/kotext/styles/KoParagraphStyle.cpp +++ b/plugins/flake/textshape/kotext/styles/KoParagraphStyle.cpp @@ -1,2365 +1,2367 @@ /* This file is part of the KDE project * Copyright (C) 2006-2009 Thomas Zander * Copyright (C) 2007,2008 Sebastian Sauer * Copyright (C) 2007-2011 Pierre Ducroquet * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Roopesh Chander * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2011 Gopalakrishna Bhat A * * 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 "KoParagraphStyle.h" #include "KoList.h" #include "KoListStyle.h" #include "KoTextBlockData.h" #include "KoStyleManager.h" #include "KoListLevelProperties.h" #include "KoTextSharedLoadingData.h" #include #include #include #include #include "Styles_p.h" #include "KoTextDocument.h" #include "TextDebug.h" #include #include #include #include #include #include #include #include #include #include #include //already defined in KoRulerController.cpp #ifndef KDE_USE_FINAL -static int compareTabs(KoText::Tab &tab1, KoText::Tab &tab2) -{ - return tab1.position < tab2.position; -} +struct { + bool operator()(KoText::Tab tab1, KoText::Tab tab2) const + { + return tab1.position < tab2.position; + } +} compareTabs; #endif class Q_DECL_HIDDEN KoParagraphStyle::Private { public: Private() : parentStyle(0), defaultStyle(0), list(0), m_inUse(false) {} ~Private() { } void setProperty(int key, const QVariant &value) { stylesPrivate.add(key, value); } void ensureDefaults(QTextBlockFormat &format) { if (defaultStyle) { QMap props = defaultStyle->d->stylesPrivate.properties(); QMap::const_iterator it = props.constBegin(); while (it != props.constEnd()) { if (!it.value().isNull() && !format.hasProperty(it.key())) { format.setProperty(it.key(), it.value()); } ++it; } } } QString name; KoParagraphStyle *parentStyle; KoParagraphStyle *defaultStyle; KoList *list; StylePrivate stylesPrivate; bool m_inUse; }; KoParagraphStyle::KoParagraphStyle(QObject *parent) : KoCharacterStyle(parent), d(new Private()) { } KoParagraphStyle::KoParagraphStyle(const QTextBlockFormat &blockFormat, const QTextCharFormat &blockCharFormat, QObject *parent) : KoCharacterStyle(blockCharFormat, parent), d(new Private()) { d->stylesPrivate = blockFormat.properties(); } KoParagraphStyle *KoParagraphStyle::fromBlock(const QTextBlock &block, QObject *parent) { QTextBlockFormat blockFormat = block.blockFormat(); QTextCursor cursor(block); KoParagraphStyle *answer = new KoParagraphStyle(blockFormat, cursor.blockCharFormat(), parent); int listStyleId = blockFormat.intProperty(ListStyleId); KoStyleManager *sm = KoTextDocument(block.document()).styleManager(); if (KoListStyle *listStyle = sm->listStyle(listStyleId)) { answer->setListStyle(listStyle->clone(answer)); } else if (block.textList()) { KoListLevelProperties llp = KoListLevelProperties::fromTextList(block.textList()); KoListStyle *listStyle = new KoListStyle(answer); listStyle->setLevelProperties(llp); answer->setListStyle(listStyle); } return answer; } KoParagraphStyle::~KoParagraphStyle() { delete d; } KoCharacterStyle::Type KoParagraphStyle::styleType() const { return KoCharacterStyle::ParagraphStyle; } void KoParagraphStyle::setDefaultStyle(KoParagraphStyle *defaultStyle) { d->defaultStyle = defaultStyle; KoCharacterStyle::setDefaultStyle(defaultStyle); } void KoParagraphStyle::setParentStyle(KoParagraphStyle *parent) { d->parentStyle = parent; KoCharacterStyle::setParentStyle(parent); } void KoParagraphStyle::setProperty(int key, const QVariant &value) { 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 KoParagraphStyle::remove(int key) { d->stylesPrivate.remove(key); } QVariant KoParagraphStyle::value(int key) const { QVariant var = d->stylesPrivate.value(key); if (var.isNull()) { if (d->parentStyle) return d->parentStyle->value(key); else if (d->defaultStyle) return d->defaultStyle->value(key); } return var; } bool KoParagraphStyle::hasProperty(int key) const { return d->stylesPrivate.contains(key); } qreal KoParagraphStyle::propertyDouble(int key) const { QVariant variant = value(key); if (variant.isNull()) return 0.0; return variant.toDouble(); } QTextLength KoParagraphStyle::propertyLength(int key) const { QVariant variant = value(key); if (variant.isNull()) return QTextLength(QTextLength::FixedLength, 0.0); if (!variant.canConvert()) { // Fake support, for compatibility sake if (variant.canConvert()) { return QTextLength(QTextLength::FixedLength, variant.toReal()); } warnText << "This should never happen : requested property can't be converted to QTextLength"; return QTextLength(QTextLength::FixedLength, 0.0); } return variant.value(); } int KoParagraphStyle::propertyInt(int key) const { QVariant variant = value(key); if (variant.isNull()) return 0; return variant.toInt(); } bool KoParagraphStyle::propertyBoolean(int key) const { QVariant variant = value(key); if (variant.isNull()) return false; return variant.toBool(); } QColor KoParagraphStyle::propertyColor(int key) const { QVariant variant = value(key); if (variant.isNull()) { return QColor(); } return qvariant_cast(variant); } void KoParagraphStyle::applyStyle(QTextBlockFormat &format) const { if (d->parentStyle) { d->parentStyle->applyStyle(format); } const QMap props = d->stylesPrivate.properties(); QMap::const_iterator it = props.begin(); while (it != props.end()) { if (it.key() == QTextBlockFormat::BlockLeftMargin) { format.setLeftMargin(leftMargin()); } else if (it.key() == QTextBlockFormat::BlockRightMargin) { format.setRightMargin(rightMargin()); } else if (it.key() == QTextBlockFormat::TextIndent) { format.setTextIndent(textIndent()); } else { format.setProperty(it.key(), it.value()); } ++it; } if ((hasProperty(DefaultOutlineLevel)) && (!format.hasProperty(OutlineLevel))) { format.setProperty(OutlineLevel, defaultOutlineLevel()); } emit styleApplied(this); d->m_inUse = true; } void KoParagraphStyle::applyStyle(QTextBlock &block, bool applyListStyle) const { QTextCursor cursor(block); QTextBlockFormat format = cursor.blockFormat(); applyStyle(format); d->ensureDefaults(format); cursor.setBlockFormat(format); KoCharacterStyle::applyStyle(block); if (applyListStyle) { applyParagraphListStyle(block, format); } } bool KoParagraphStyle::isApplied() const { return d->m_inUse; } void KoParagraphStyle::applyParagraphListStyle(QTextBlock &block, const QTextBlockFormat &blockFormat) const { //gopalakbhat: We need to differentiate between normal styles and styles with outline(part of heading) //Styles part of outline: We ignore the listStyle()( even if this is a valid in ODF; even LibreOffice does the same) // since we can specify all info required in text:outline-style //Normal styles: we use the listStyle() if (blockFormat.hasProperty(OutlineLevel)) { if (! d->list) { if (! KoTextDocument(block.document()).headingList()) { if (KoTextDocument(block.document()).styleManager() && KoTextDocument(block.document()).styleManager()->outlineStyle()) { d->list = new KoList(block.document(), KoTextDocument(block.document()).styleManager()->outlineStyle()); KoTextDocument(block.document()).setHeadingList(d->list); } } else { d->list = KoTextDocument(block.document()).headingList(); } } if (d->list) { d->list->applyStyle(block, KoTextDocument(block.document()).styleManager()->outlineStyle(), blockFormat.intProperty(OutlineLevel)); } } else { if (listStyle()) { if (!d->list) { d->list = new KoList(block.document(), listStyle()); } //FIXME: Gopalakrishna Bhat A: This condition should never happen. // i.e. d->list->style() should always be in sync with the listStyle() if (d->list->style() != listStyle()) { d->list->setStyle(listStyle()); } d->list->add(block, listLevel()); } else { if (block.textList()) block.textList()->remove(block); KoTextBlockData data(block); data.setCounterWidth(-1); } } } void KoParagraphStyle::unapplyStyle(QTextBlock &block) const { if (d->parentStyle) d->parentStyle->unapplyStyle(block); QTextCursor cursor(block); QTextBlockFormat format = cursor.blockFormat(); QList keys = d->stylesPrivate.keys(); for (int i = 0; i < keys.count(); i++) { QVariant variant = d->stylesPrivate.value(keys[i]); if (keys[i] == QTextBlockFormat::BlockLeftMargin) { if (leftMargin() == format.property(keys[i])) format.clearProperty(keys[i]); } else if (keys[i] == QTextBlockFormat::BlockRightMargin) { if (rightMargin() == format.property(keys[i])) format.clearProperty(keys[i]); } else if (keys[i] == QTextBlockFormat::TextIndent) { if (textIndent() == format.property(keys[i])) format.clearProperty(keys[i]); } else { if (variant == format.property(keys[i])) format.clearProperty(keys[i]); } } format.clearProperty(KoParagraphStyle::OutlineLevel); cursor.setBlockFormat(format); KoCharacterStyle::unapplyStyle(block); if (listStyle() && block.textList()) { // TODO check its the same one? KoList::remove(block); } if (d->list && block.textList()) { // TODO check its the same one? KoList::remove(block); } } void KoParagraphStyle::setLineHeightPercent(qreal lineHeight) { setProperty(PercentLineHeight, lineHeight); setProperty(FixedLineHeight, 0.0); setProperty(MinimumLineHeight, QTextLength(QTextLength::FixedLength, 0.0)); remove(NormalLineHeight); } qreal KoParagraphStyle::lineHeightPercent() const { return propertyInt(PercentLineHeight); } void KoParagraphStyle::setLineHeightAbsolute(qreal height) { setProperty(FixedLineHeight, height); setProperty(PercentLineHeight, 0); setProperty(MinimumLineHeight, QTextLength(QTextLength::FixedLength, 0.0)); remove(NormalLineHeight); } qreal KoParagraphStyle::lineHeightAbsolute() const { return propertyDouble(FixedLineHeight); } void KoParagraphStyle::setMinimumLineHeight(const QTextLength &height) { setProperty(FixedLineHeight, 0.0); setProperty(PercentLineHeight, 0); setProperty(MinimumLineHeight, height); remove(NormalLineHeight); } qreal KoParagraphStyle::minimumLineHeight() const { if (parentStyle()) return propertyLength(MinimumLineHeight).value(parentStyle()->minimumLineHeight()); else return propertyLength(MinimumLineHeight).value(0); } void KoParagraphStyle::setLineSpacing(qreal spacing) { setProperty(LineSpacing, spacing); remove(NormalLineHeight); } qreal KoParagraphStyle::lineSpacing() const { return propertyDouble(LineSpacing); } void KoParagraphStyle::setLineSpacingFromFont(bool on) { setProperty(LineSpacingFromFont, on); remove(NormalLineHeight); } bool KoParagraphStyle::lineSpacingFromFont() const { return propertyBoolean(LineSpacingFromFont); } void KoParagraphStyle::setNormalLineHeight() { setProperty(NormalLineHeight, true); setProperty(PercentLineHeight, 0); setProperty(FixedLineHeight, 0.0); setProperty(MinimumLineHeight, QTextLength(QTextLength::FixedLength, 0.0)); setProperty(LineSpacing, 0.0); } bool KoParagraphStyle::hasNormalLineHeight() const { return propertyBoolean(NormalLineHeight); } void KoParagraphStyle::setAlignLastLine(Qt::Alignment alignment) { setProperty(AlignLastLine, (int) alignment); } Qt::Alignment KoParagraphStyle::alignLastLine() const { if (hasProperty(AlignLastLine)) return static_cast(propertyInt(AlignLastLine)); // Hum, that doesn't sound right ! return alignment(); } void KoParagraphStyle::setWidowThreshold(int lines) { setProperty(WidowThreshold, lines); } int KoParagraphStyle::widowThreshold() const { return propertyInt(WidowThreshold); } void KoParagraphStyle::setOrphanThreshold(int lines) { setProperty(OrphanThreshold, lines); } int KoParagraphStyle::orphanThreshold() const { return propertyInt(OrphanThreshold); } void KoParagraphStyle::setDropCaps(bool on) { setProperty(DropCaps, on); } bool KoParagraphStyle::dropCaps() const { return propertyBoolean(DropCaps); } void KoParagraphStyle::setDropCapsLength(int characters) { setProperty(DropCapsLength, characters); } int KoParagraphStyle::dropCapsLength() const { return propertyInt(DropCapsLength); } void KoParagraphStyle::setDropCapsLines(int lines) { setProperty(DropCapsLines, lines); } int KoParagraphStyle::dropCapsLines() const { return propertyInt(DropCapsLines); } void KoParagraphStyle::setDropCapsDistance(qreal distance) { setProperty(DropCapsDistance, distance); } qreal KoParagraphStyle::dropCapsDistance() const { return propertyDouble(DropCapsDistance); } void KoParagraphStyle::setDropCapsTextStyleId(int id) { setProperty(KoParagraphStyle::DropCapsTextStyle, id); } int KoParagraphStyle::dropCapsTextStyleId() const { return propertyInt(KoParagraphStyle::DropCapsTextStyle); } void KoParagraphStyle::setFollowDocBaseline(bool on) { setProperty(FollowDocBaseline, on); } bool KoParagraphStyle::followDocBaseline() const { return propertyBoolean(FollowDocBaseline); } void KoParagraphStyle::setBreakBefore(KoText::KoTextBreakProperty value) { setProperty(BreakBefore, value); } KoText::KoTextBreakProperty KoParagraphStyle::breakBefore() const { return static_cast(propertyInt(BreakBefore)); } void KoParagraphStyle::setBreakAfter(KoText::KoTextBreakProperty value) { setProperty(BreakAfter, value); } KoText::KoTextBreakProperty KoParagraphStyle::breakAfter() const { return static_cast(propertyInt(BreakAfter)); } void KoParagraphStyle::setLeftPadding(qreal padding) { setProperty(LeftPadding, padding); } qreal KoParagraphStyle::leftPadding() const { return propertyDouble(LeftPadding); } void KoParagraphStyle::setTopPadding(qreal padding) { setProperty(TopPadding, padding); } qreal KoParagraphStyle::topPadding() const { return propertyDouble(TopPadding); } void KoParagraphStyle::setRightPadding(qreal padding) { setProperty(RightPadding, padding); } qreal KoParagraphStyle::rightPadding() const { return propertyDouble(RightPadding); } void KoParagraphStyle::setBottomPadding(qreal padding) { setProperty(BottomPadding, padding); } qreal KoParagraphStyle::bottomPadding() const { return propertyDouble(BottomPadding); } void KoParagraphStyle::setPadding(qreal padding) { setBottomPadding(padding); setTopPadding(padding); setRightPadding(padding); setLeftPadding(padding); } void KoParagraphStyle::setLeftBorderWidth(qreal width) { setProperty(LeftBorderWidth, width); } qreal KoParagraphStyle::leftBorderWidth() const { return propertyDouble(LeftBorderWidth); } void KoParagraphStyle::setLeftInnerBorderWidth(qreal width) { setProperty(LeftInnerBorderWidth, width); } qreal KoParagraphStyle::leftInnerBorderWidth() const { return propertyDouble(LeftInnerBorderWidth); } void KoParagraphStyle::setLeftBorderSpacing(qreal width) { setProperty(LeftBorderSpacing, width); } qreal KoParagraphStyle::leftBorderSpacing() const { return propertyDouble(LeftBorderSpacing); } void KoParagraphStyle::setLeftBorderStyle(KoBorder::BorderStyle style) { setProperty(LeftBorderStyle, style); } KoBorder::BorderStyle KoParagraphStyle::leftBorderStyle() const { return static_cast(propertyInt(LeftBorderStyle)); } void KoParagraphStyle::setLeftBorderColor(const QColor &color) { setProperty(LeftBorderColor, color); } QColor KoParagraphStyle::leftBorderColor() const { return propertyColor(LeftBorderColor); } void KoParagraphStyle::setTopBorderWidth(qreal width) { setProperty(TopBorderWidth, width); } qreal KoParagraphStyle::topBorderWidth() const { return propertyDouble(TopBorderWidth); } void KoParagraphStyle::setTopInnerBorderWidth(qreal width) { setProperty(TopInnerBorderWidth, width); } qreal KoParagraphStyle::topInnerBorderWidth() const { return propertyDouble(TopInnerBorderWidth); } void KoParagraphStyle::setTopBorderSpacing(qreal width) { setProperty(TopBorderSpacing, width); } qreal KoParagraphStyle::topBorderSpacing() const { return propertyDouble(TopBorderSpacing); } void KoParagraphStyle::setTopBorderStyle(KoBorder::BorderStyle style) { setProperty(TopBorderStyle, style); } KoBorder::BorderStyle KoParagraphStyle::topBorderStyle() const { return static_cast(propertyInt(TopBorderStyle)); } void KoParagraphStyle::setTopBorderColor(const QColor &color) { setProperty(TopBorderColor, color); } QColor KoParagraphStyle::topBorderColor() const { return propertyColor(TopBorderColor); } void KoParagraphStyle::setRightBorderWidth(qreal width) { setProperty(RightBorderWidth, width); } qreal KoParagraphStyle::rightBorderWidth() const { return propertyDouble(RightBorderWidth); } void KoParagraphStyle::setRightInnerBorderWidth(qreal width) { setProperty(RightInnerBorderWidth, width); } qreal KoParagraphStyle::rightInnerBorderWidth() const { return propertyDouble(RightInnerBorderWidth); } void KoParagraphStyle::setRightBorderSpacing(qreal width) { setProperty(RightBorderSpacing, width); } qreal KoParagraphStyle::rightBorderSpacing() const { return propertyDouble(RightBorderSpacing); } void KoParagraphStyle::setRightBorderStyle(KoBorder::BorderStyle style) { setProperty(RightBorderStyle, style); } KoBorder::BorderStyle KoParagraphStyle::rightBorderStyle() const { return static_cast(propertyInt(RightBorderStyle)); } void KoParagraphStyle::setRightBorderColor(const QColor &color) { setProperty(RightBorderColor, color); } QColor KoParagraphStyle::rightBorderColor() const { return propertyColor(RightBorderColor); } void KoParagraphStyle::setBottomBorderWidth(qreal width) { setProperty(BottomBorderWidth, width); } qreal KoParagraphStyle::bottomBorderWidth() const { return propertyDouble(BottomBorderWidth); } void KoParagraphStyle::setBottomInnerBorderWidth(qreal width) { setProperty(BottomInnerBorderWidth, width); } qreal KoParagraphStyle::bottomInnerBorderWidth() const { return propertyDouble(BottomInnerBorderWidth); } void KoParagraphStyle::setBottomBorderSpacing(qreal width) { setProperty(BottomBorderSpacing, width); } qreal KoParagraphStyle::bottomBorderSpacing() const { return propertyDouble(BottomBorderSpacing); } void KoParagraphStyle::setBottomBorderStyle(KoBorder::BorderStyle style) { setProperty(BottomBorderStyle, style); } KoBorder::BorderStyle KoParagraphStyle::bottomBorderStyle() const { return static_cast(propertyInt(BottomBorderStyle)); } void KoParagraphStyle::setBottomBorderColor(const QColor &color) { setProperty(BottomBorderColor, color); } QColor KoParagraphStyle::bottomBorderColor() const { return propertyColor(BottomBorderColor); } void KoParagraphStyle::setTopMargin(QTextLength topMargin) { setProperty(QTextFormat::BlockTopMargin, topMargin); } qreal KoParagraphStyle::topMargin() const { if (parentStyle()) return propertyLength(QTextFormat::BlockTopMargin).value(parentStyle()->topMargin()); else return propertyLength(QTextFormat::BlockTopMargin).value(0); } void KoParagraphStyle::setBottomMargin(QTextLength margin) { setProperty(QTextFormat::BlockBottomMargin, margin); } qreal KoParagraphStyle::bottomMargin() const { if (parentStyle()) return propertyLength(QTextFormat::BlockBottomMargin).value(parentStyle()->bottomMargin()); else return propertyLength(QTextFormat::BlockBottomMargin).value(0); } void KoParagraphStyle::setLeftMargin(QTextLength margin) { setProperty(QTextFormat::BlockLeftMargin, margin); } qreal KoParagraphStyle::leftMargin() const { if (parentStyle()) return propertyLength(QTextFormat::BlockLeftMargin).value(parentStyle()->leftMargin()); else return propertyLength(QTextFormat::BlockLeftMargin).value(0); } void KoParagraphStyle::setRightMargin(QTextLength margin) { setProperty(QTextFormat::BlockRightMargin, margin); } qreal KoParagraphStyle::rightMargin() const { if (parentStyle()) return propertyLength(QTextFormat::BlockRightMargin).value(parentStyle()->rightMargin()); else return propertyLength(QTextFormat::BlockRightMargin).value(0); } void KoParagraphStyle::setMargin(QTextLength margin) { setTopMargin(margin); setBottomMargin(margin); setLeftMargin(margin); setRightMargin(margin); } void KoParagraphStyle::setAlignment(Qt::Alignment alignment) { setProperty(QTextFormat::BlockAlignment, (int) alignment); } Qt::Alignment KoParagraphStyle::alignment() const { return static_cast(propertyInt(QTextFormat::BlockAlignment)); } void KoParagraphStyle::setTextIndent(QTextLength margin) { setProperty(QTextFormat::TextIndent, margin); } qreal KoParagraphStyle::textIndent() const { if (parentStyle()) return propertyLength(QTextFormat::TextIndent).value(parentStyle()->textIndent()); else return propertyLength(QTextFormat::TextIndent).value(0); } void KoParagraphStyle::setAutoTextIndent(bool on) { setProperty(KoParagraphStyle::AutoTextIndent, on); } bool KoParagraphStyle::autoTextIndent() const { return propertyBoolean(KoParagraphStyle::AutoTextIndent); } void KoParagraphStyle::setNonBreakableLines(bool on) { setProperty(QTextFormat::BlockNonBreakableLines, on); } bool KoParagraphStyle::nonBreakableLines() const { return propertyBoolean(QTextFormat::BlockNonBreakableLines); } void KoParagraphStyle::setKeepWithNext(bool value) { setProperty(KeepWithNext, value); } bool KoParagraphStyle::keepWithNext() const { if (hasProperty(KeepWithNext)) return propertyBoolean(KeepWithNext); return false; } bool KoParagraphStyle::punctuationWrap() const { if (hasProperty(PunctuationWrap)) return propertyBoolean(PunctuationWrap); return false; } void KoParagraphStyle::setPunctuationWrap(bool value) { setProperty(PunctuationWrap, value); } KoParagraphStyle *KoParagraphStyle::parentStyle() const { return d->parentStyle; } void KoParagraphStyle::setNextStyle(int next) { setProperty(NextStyle, next); } int KoParagraphStyle::nextStyle() const { return propertyInt(NextStyle); } QString KoParagraphStyle::name() const { return d->name; } void KoParagraphStyle::setName(const QString &name) { if (name == d->name) return; d->name = name; KoCharacterStyle::setName(name); emit nameChanged(name); } int KoParagraphStyle::styleId() const { // duplicate some code to avoid getting the parents style id QVariant variant = d->stylesPrivate.value(StyleId); if (variant.isNull()) return 0; return variant.toInt(); } void KoParagraphStyle::setStyleId(int id) { setProperty(StyleId, id); if (nextStyle() == 0) setNextStyle(id); KoCharacterStyle::setStyleId(id); } QString KoParagraphStyle::masterPageName() const { return value(MasterPageName).toString(); } void KoParagraphStyle::setMasterPageName(const QString &name) { setProperty(MasterPageName, name); } void KoParagraphStyle::setListStartValue(int value) { setProperty(ListStartValue, value); } int KoParagraphStyle::listStartValue() const { return propertyInt(ListStartValue); } void KoParagraphStyle::setRestartListNumbering(bool on) { setProperty(RestartListNumbering, on); } bool KoParagraphStyle::restartListNumbering() { return propertyBoolean(RestartListNumbering); } void KoParagraphStyle::setListLevel(int value) { setProperty(ListLevel, value); } int KoParagraphStyle::listLevel() const { return propertyInt(ListLevel); } void KoParagraphStyle::setOutlineLevel(int outline) { setProperty(OutlineLevel, outline); } int KoParagraphStyle::outlineLevel() const { return propertyInt(OutlineLevel); } void KoParagraphStyle::setDefaultOutlineLevel(int outline) { setProperty(DefaultOutlineLevel, outline); } int KoParagraphStyle::defaultOutlineLevel() const { return propertyInt(DefaultOutlineLevel); } bool KoParagraphStyle::lineNumbering() const { return propertyBoolean(LineNumbering); } void KoParagraphStyle::setLineNumbering(bool lineNumbering) { setProperty(LineNumbering, lineNumbering); } int KoParagraphStyle::lineNumberStartValue() const { return propertyInt(LineNumberStartValue); } void KoParagraphStyle::setLineNumberStartValue(int lineNumberStartValue) { setProperty(LineNumberStartValue, lineNumberStartValue); } void KoParagraphStyle::setIsListHeader(bool on) { setProperty(IsListHeader, on); } bool KoParagraphStyle::isListHeader() const { return propertyBoolean(IsListHeader); } KoListStyle *KoParagraphStyle::listStyle() const { QVariant variant = value(ParagraphListStyleId); if (variant.isNull()) return 0; return variant.value(); } void KoParagraphStyle::setListStyle(KoListStyle *style) { if (listStyle() == style) return; if (listStyle() && listStyle()->parent() == this) delete listStyle(); QVariant variant; KoListStyle *cloneStyle = 0; if (style) { cloneStyle = style->clone(); variant.setValue(cloneStyle); setProperty(ParagraphListStyleId, variant); } else { d->stylesPrivate.remove(ParagraphListStyleId); } } KoText::Direction KoParagraphStyle::textProgressionDirection() const { return static_cast(propertyInt(TextProgressionDirection)); } void KoParagraphStyle::setTextProgressionDirection(KoText::Direction dir) { setProperty(TextProgressionDirection, dir); } bool KoParagraphStyle::keepHyphenation() const { if (hasProperty(KeepHyphenation)) return propertyBoolean(KeepHyphenation); return false; } void KoParagraphStyle::setKeepHyphenation(bool value) { setProperty(KeepHyphenation, value); } int KoParagraphStyle::hyphenationLadderCount() const { if (hasProperty(HyphenationLadderCount)) return propertyInt(HyphenationLadderCount); return 0; } void KoParagraphStyle::setHyphenationLadderCount(int value) { setProperty(HyphenationLadderCount, value); } void KoParagraphStyle::setBackground(const QBrush &brush) { d->setProperty(QTextFormat::BackgroundBrush, brush); } void KoParagraphStyle::clearBackground() { d->stylesPrivate.remove(QTextCharFormat::BackgroundBrush); } QBrush KoParagraphStyle::background() const { QVariant variant = d->stylesPrivate.value(QTextFormat::BackgroundBrush); if (variant.isNull()) { return QBrush(); } return qvariant_cast(variant); } qreal KoParagraphStyle::backgroundTransparency() const { if (hasProperty(BackgroundTransparency)) return propertyDouble(BackgroundTransparency); return 0.0; } void KoParagraphStyle::setBackgroundTransparency(qreal transparency) { setProperty(BackgroundTransparency, transparency); } void KoParagraphStyle::setSnapToLayoutGrid(bool value) { setProperty(SnapToLayoutGrid, value); } bool KoParagraphStyle::snapToLayoutGrid() const { if (hasProperty(SnapToLayoutGrid)) return propertyBoolean(SnapToLayoutGrid); return false; } bool KoParagraphStyle::joinBorder() const { if (hasProperty(JoinBorder)) return propertyBoolean(JoinBorder); return true; //default is true } void KoParagraphStyle::setJoinBorder(bool value) { setProperty(JoinBorder, value); } int KoParagraphStyle::pageNumber() const { return propertyInt(PageNumber); } void KoParagraphStyle::setPageNumber(int pageNumber) { if (pageNumber >= 0) setProperty(PageNumber, pageNumber); } bool KoParagraphStyle::automaticWritingMode() const { if (hasProperty(AutomaticWritingMode)) return propertyBoolean(AutomaticWritingMode); return true; } void KoParagraphStyle::setAutomaticWritingMode(bool value) { setProperty(AutomaticWritingMode, value); } void KoParagraphStyle::setVerticalAlignment(KoParagraphStyle::VerticalAlign value) { setProperty(VerticalAlignment, value); } KoParagraphStyle::VerticalAlign KoParagraphStyle::verticalAlignment() const { if (hasProperty(VerticalAlignment)) return (VerticalAlign) propertyInt(VerticalAlignment); return VAlignAuto; } void KoParagraphStyle::setShadow(const KoShadowStyle &shadow) { d->setProperty(Shadow, QVariant::fromValue(shadow)); } KoShadowStyle KoParagraphStyle::shadow() const { if (hasProperty(Shadow)) return value(Shadow).value(); return KoShadowStyle(); } void KoParagraphStyle::loadOdf(const KoXmlElement *element, KoShapeLoadingContext &scontext, bool loadParents) { KoOdfLoadingContext &context = scontext.odfLoadingContext(); const QString name(element->attributeNS(KoXmlNS::style, "display-name", QString())); if (!name.isEmpty()) { setName(name); } else { setName(element->attributeNS(KoXmlNS::style, "name", QString())); } QString family = element->attributeNS(KoXmlNS::style, "family", "paragraph"); context.styleStack().save(); if (loadParents) { context.addStyles(element, family.toLocal8Bit().constData()); // Load all parent } else { context.styleStack().push(*element); } context.styleStack().setTypeProperties("text"); // load the style:text-properties KoCharacterStyle::loadOdfProperties(scontext); QString masterPage = element->attributeNS(KoXmlNS::style, "master-page-name", QString()); if (! masterPage.isEmpty()) { setMasterPageName(masterPage); } if (element->hasAttributeNS(KoXmlNS::style, "default-outline-level")) { bool ok = false; int level = element->attributeNS(KoXmlNS::style, "default-outline-level").toInt(&ok); if (ok) setDefaultOutlineLevel(level); } context.styleStack().setTypeProperties("paragraph"); // load all style attributes from "style:paragraph-properties" loadOdfProperties(scontext); // load the KoParagraphStyle from the stylestack context.styleStack().restore(); } struct ParagraphBorderData { enum Values {Style = 1, Color = 2, Width = 4}; ParagraphBorderData() : values(0) {} ParagraphBorderData(const ParagraphBorderData &other) : values(other.values), style(other.style), color(other.color), width(other.width) {} // flag defining which data is set int values; KoBorder::BorderStyle style; QColor color; qreal width; ///< in pt }; /// Parses the @p dataString as value defined by CSS2 §7.29.3 "border" /// Adds parsed data to the data as set for @p defaultParagraphBorderData. /// Returns the enriched border data on success, the original @p defaultParagraphBorderData on a parsing error static ParagraphBorderData parseParagraphBorderData(const QString &dataString, const ParagraphBorderData &defaultParagraphBorderData) { const QStringList bv = dataString.split(QLatin1Char(' '), QString::SkipEmptyParts); // too many items? ignore complete value if (bv.count() > 3) { return defaultParagraphBorderData; } ParagraphBorderData borderData = defaultParagraphBorderData; int parsedValues = 0; ///< used to track what is read from the given string Q_FOREACH (const QString &v, bv) { // try style if (! (parsedValues & ParagraphBorderData::Style)) { bool success = false; KoBorder::BorderStyle style = KoBorder::odfBorderStyle(v, &success); // workaround for not yet supported "hidden" if (! success && (v == QLatin1String("hidden"))) { // map to "none" for now TODO: KoBorder needs to support "hidden" style = KoBorder::BorderNone; success = true; } if (success) { borderData.style = style; borderData.values |= ParagraphBorderData::Style; parsedValues |= ParagraphBorderData::Style; continue; } } // try color if (! (parsedValues & ParagraphBorderData::Color)) { const QColor color(v); if (color.isValid()) { borderData.color = color; borderData.values |= ParagraphBorderData::Color; parsedValues |= ParagraphBorderData::Color; continue; } } // try width if (! (parsedValues & ParagraphBorderData::Width)) { const qreal width = KoUnit::parseValue(v); if (width >= 0.0) { borderData.width = width; borderData.values |= ParagraphBorderData::Width; parsedValues |= ParagraphBorderData::Width; continue; } } // still here? found a value which cannot be parsed return defaultParagraphBorderData; } return borderData; } void KoParagraphStyle::loadOdfProperties(KoShapeLoadingContext &scontext) { KoStyleStack &styleStack = scontext.odfLoadingContext().styleStack(); // in 1.6 this was defined at KoParagLayout::loadOasisParagLayout(KoParagLayout&, KoOasisContext&) const QString writingMode(styleStack.property(KoXmlNS::style, "writing-mode")); if (!writingMode.isEmpty()) { setTextProgressionDirection(KoText::directionFromString(writingMode)); } // Alignment const QString textAlign(styleStack.property(KoXmlNS::fo, "text-align")); if (!textAlign.isEmpty()) { setAlignment(KoText::alignmentFromString(textAlign)); } // Spacing (padding) const QString padding(styleStack.property(KoXmlNS::fo, "padding")); if (!padding.isEmpty()) { setPadding(KoUnit::parseValue(padding)); } const QString paddingLeft(styleStack.property(KoXmlNS::fo, "padding-left" )); if (!paddingLeft.isEmpty()) { setLeftPadding(KoUnit::parseValue(paddingLeft)); } const QString paddingRight(styleStack.property(KoXmlNS::fo, "padding-right" )); if (!paddingRight.isEmpty()) { setRightPadding(KoUnit::parseValue(paddingRight)); } const QString paddingTop(styleStack.property(KoXmlNS::fo, "padding-top" )); if (!paddingTop.isEmpty()) { setTopPadding(KoUnit::parseValue(paddingTop)); } const QString paddingBottom(styleStack.property(KoXmlNS::fo, "padding-bottom" )); if (!paddingBottom.isEmpty()) { setBottomPadding(KoUnit::parseValue(paddingBottom)); } // Indentation (margin) const QString margin(styleStack.property(KoXmlNS::fo, "margin")); if (!margin.isEmpty()) { setMargin(KoText::parseLength(margin)); } const QString marginLeft(styleStack.property(KoXmlNS::fo, "margin-left" )); if (!marginLeft.isEmpty()) { setLeftMargin(KoText::parseLength(marginLeft)); } const QString marginRight(styleStack.property(KoXmlNS::fo, "margin-right" )); if (!marginRight.isEmpty()) { setRightMargin(KoText::parseLength(marginRight)); } const QString marginTop(styleStack.property(KoXmlNS::fo, "margin-top")); if (!marginTop.isEmpty()) { setTopMargin(KoText::parseLength(marginTop)); } const QString marginBottom(styleStack.property(KoXmlNS::fo, "margin-bottom")); if (!marginBottom.isEmpty()) { setBottomMargin(KoText::parseLength(marginBottom)); } // Automatic Text indent // OOo is not assuming this. Commenting this line thus allow more OpenDocuments to be supported, including a // testcase from the ODF test suite. See §15.5.18 in the spec. //if ( hasMarginLeft || hasMarginRight ) { // style:auto-text-indent takes precedence const QString autoTextIndent(styleStack.property(KoXmlNS::style, "auto-text-indent")); if (!autoTextIndent.isEmpty()) { setAutoTextIndent(autoTextIndent == "true"); } if (autoTextIndent != "true" || autoTextIndent.isEmpty()) { const QString textIndent(styleStack.property(KoXmlNS::fo, "text-indent")); if (!textIndent.isEmpty()) { setTextIndent(KoText::parseLength(textIndent)); } } //} // Line spacing QString lineHeight(styleStack.property(KoXmlNS::fo, "line-height")); if (!lineHeight.isEmpty()) { if (lineHeight != "normal") { if (lineHeight.indexOf('%') > -1) { bool ok; const qreal percent = lineHeight.remove('%').toDouble(&ok); if (ok) { setLineHeightPercent(percent); } } else { // fixed value is between 0.0201in and 3.9402in const qreal value = KoUnit::parseValue(lineHeight, -1.0); if (value >= 0.0) { setLineHeightAbsolute(value); } } } else { setNormalLineHeight(); } } else { const QString lineSpacing(styleStack.property(KoXmlNS::style, "line-spacing")); if (!lineSpacing.isEmpty()) { // 3.11.3 setLineSpacing(KoUnit::parseValue(lineSpacing)); } } // 15.5.30 - 31 if (styleStack.hasProperty(KoXmlNS::text, "number-lines")) { setLineNumbering(styleStack.property(KoXmlNS::text, "number-lines", "false") == "true"); } if (styleStack.hasProperty(KoXmlNS::text, "line-number")) { bool ok; int startValue = styleStack.property(KoXmlNS::text, "line-number").toInt(&ok); if (ok) { setLineNumberStartValue(startValue); } } const QString lineHeightAtLeast(styleStack.property(KoXmlNS::style, "line-height-at-least")); if (!lineHeightAtLeast.isEmpty() && !propertyBoolean(NormalLineHeight) && lineHeightAbsolute() == 0) { // 3.11.2 setMinimumLineHeight(KoText::parseLength(lineHeightAtLeast)); } // Line-height-at-least is mutually exclusive with absolute line-height const QString fontIndependentLineSpacing(styleStack.property(KoXmlNS::style, "font-independent-line-spacing")); if (!fontIndependentLineSpacing.isEmpty() && !propertyBoolean(NormalLineHeight) && lineHeightAbsolute() == 0) { setLineSpacingFromFont(fontIndependentLineSpacing == "true"); } // Tabulators const QString tabStopDistance(styleStack.property(KoXmlNS::style, "tab-stop-distance")); if (!tabStopDistance.isEmpty()) { qreal stopDistance = KoUnit::parseValue(tabStopDistance); if (stopDistance >= 0) setTabStopDistance(stopDistance); } KoXmlElement tabStops(styleStack.childNode(KoXmlNS::style, "tab-stops")); if (!tabStops.isNull()) { // 3.11.10 QList tabList; KoXmlElement tabStop; forEachElement(tabStop, tabStops) { if(tabStop.localName() != "tab-stop") continue; // Tab position KoText::Tab tab; tab.position = KoUnit::parseValue(tabStop.attributeNS(KoXmlNS::style, "position", QString())); //debugText << "tab position " << tab.position; // Tab stop positions in the XML are relative to the left-margin // Equivalently, relative to the left end of our textshape // Tab type (left/right/center/char) const QString type = tabStop.attributeNS(KoXmlNS::style, "type", QString()); if (type == "center") tab.type = QTextOption::CenterTab; else if (type == "right") tab.type = QTextOption::RightTab; else if (type == "char") { tab.type = QTextOption::DelimiterTab; tab.delimiter = QChar('.'); } else //if ( type == "left" ) tab.type = QTextOption::LeftTab; // Tab delimiter char if (tab.type == QTextOption::DelimiterTab) { QString delimiterChar = tabStop.attributeNS(KoXmlNS::style, "char", QString()); // single character if (!delimiterChar.isEmpty()) { tab.delimiter = delimiterChar[0]; } else { // this is invalid. fallback to left-tabbing. tab.type = QTextOption::LeftTab; } } QString leaderType = tabStop.attributeNS(KoXmlNS::style, "leader-type", QString()); if (leaderType.isEmpty() || leaderType == "none") { tab.leaderType = KoCharacterStyle::NoLineType; } else { if (leaderType == "single") tab.leaderType = KoCharacterStyle::SingleLine; else if (leaderType == "double") tab.leaderType = KoCharacterStyle::DoubleLine; // change default leaderStyle tab.leaderStyle = KoCharacterStyle::SolidLine; } QString leaderStyle = tabStop.attributeNS(KoXmlNS::style, "leader-style", QString()); if (leaderStyle == "none") tab.leaderStyle = KoCharacterStyle::NoLineStyle; else if (leaderStyle == "solid") tab.leaderStyle = KoCharacterStyle::SolidLine; else if (leaderStyle == "dotted") tab.leaderStyle = KoCharacterStyle::DottedLine; else if (leaderStyle == "dash") tab.leaderStyle = KoCharacterStyle::DashLine; else if (leaderStyle == "long-dash") tab.leaderStyle = KoCharacterStyle::LongDashLine; else if (leaderStyle == "dot-dash") tab.leaderStyle = KoCharacterStyle::DotDashLine; else if (leaderStyle == "dot-dot-dash") tab.leaderStyle = KoCharacterStyle::DotDotDashLine; else if (leaderStyle == "wave") tab.leaderStyle = KoCharacterStyle::WaveLine; if (tab.leaderType == KoCharacterStyle::NoLineType && tab.leaderStyle != KoCharacterStyle::NoLineStyle) { if (leaderType == "none") // if leaderType was explicitly specified as none, but style was not none, // make leaderType override (ODF1.1 §15.5.11) tab.leaderStyle = KoCharacterStyle::NoLineStyle; else // if leaderType was implicitly assumed none, but style was not none, // make leaderStyle override tab.leaderType = KoCharacterStyle::SingleLine; } QString leaderColor = tabStop.attributeNS(KoXmlNS::style, "leader-color", QString()); if (leaderColor != "font-color") tab.leaderColor = QColor(leaderColor); // if invalid color (the default), will use text color QString width = tabStop.attributeNS(KoXmlNS::style, "leader-width", QString()); if (width.isEmpty() || width == "auto") tab.leaderWeight = KoCharacterStyle::AutoLineWeight; else if (width == "normal") tab.leaderWeight = KoCharacterStyle::NormalLineWeight; else if (width == "bold") tab.leaderWeight = KoCharacterStyle::BoldLineWeight; else if (width == "thin") tab.leaderWeight = KoCharacterStyle::ThinLineWeight; else if (width == "dash") tab.leaderWeight = KoCharacterStyle::DashLineWeight; else if (width == "medium") tab.leaderWeight = KoCharacterStyle::MediumLineWeight; else if (width == "thick") tab.leaderWeight = KoCharacterStyle::ThickLineWeight; else if (width.endsWith('%')) { tab.leaderWeight = KoCharacterStyle::PercentLineWeight; tab.leaderWidth = width.mid(0, width.length() - 1).toDouble(); } else if (width[width.length()-1].isNumber()) { tab.leaderWeight = KoCharacterStyle::PercentLineWeight; tab.leaderWidth = 100 * width.toDouble(); } else { tab.leaderWeight = KoCharacterStyle::LengthLineWeight; tab.leaderWidth = KoUnit::parseValue(width); } tab.leaderText = tabStop.attributeNS(KoXmlNS::style, "leader-text", QString()); #if 0 else { // Fallback: convert leaderChar's unicode value QString leaderChar = tabStop.attributeNS(KoXmlNS::style, "leader-text", QString()); if (!leaderChar.isEmpty()) { QChar ch = leaderChar[0]; switch (ch.latin1()) { case '.': tab.filling = TF_DOTS; break; case '-': case '_': // TODO in Words: differentiate --- and ___ tab.filling = TF_LINE; break; default: // Words doesn't have support for "any char" as filling. break; } } } #endif tabList.append(tab); } //for setTabPositions(tabList); } #if 0 layout.joinBorder = !(styleStack.property(KoXmlNS::style, "join-border") == "false"); #endif // Borders // The border attribute is actually three attributes in one string, all optional // and with no given order. Also there is a hierachy, first the common for all // sides and then overwrites per side, while in the code only the sides are stored. // So first the common data border is fetched, then this is overwritten per // side and the result stored. const QString border(styleStack.property(KoXmlNS::fo, "border")); const ParagraphBorderData borderData = parseParagraphBorderData(border, ParagraphBorderData()); const QString borderLeft(styleStack.property(KoXmlNS::fo, "border-left")); const ParagraphBorderData leftParagraphBorderData = parseParagraphBorderData(borderLeft, borderData); if (leftParagraphBorderData.values & ParagraphBorderData::Width) { setLeftBorderWidth(leftParagraphBorderData.width); } if (leftParagraphBorderData.values & ParagraphBorderData::Style) { setLeftBorderStyle(leftParagraphBorderData.style); } if (leftParagraphBorderData.values & ParagraphBorderData::Color) { setLeftBorderColor(leftParagraphBorderData.color); } const QString borderTop(styleStack.property(KoXmlNS::fo, "border-top")); const ParagraphBorderData topParagraphBorderData = parseParagraphBorderData(borderTop, borderData); if (topParagraphBorderData.values & ParagraphBorderData::Width) { setTopBorderWidth(topParagraphBorderData.width); } if (topParagraphBorderData.values & ParagraphBorderData::Style) { setTopBorderStyle(topParagraphBorderData.style); } if (topParagraphBorderData.values & ParagraphBorderData::Color) { setTopBorderColor(topParagraphBorderData.color); } const QString borderRight(styleStack.property(KoXmlNS::fo, "border-right")); const ParagraphBorderData rightParagraphBorderData = parseParagraphBorderData(borderRight, borderData); if (rightParagraphBorderData.values & ParagraphBorderData::Width) { setRightBorderWidth(rightParagraphBorderData.width); } if (rightParagraphBorderData.values & ParagraphBorderData::Style) { setRightBorderStyle(rightParagraphBorderData.style); } if (rightParagraphBorderData.values & ParagraphBorderData::Color) { setRightBorderColor(rightParagraphBorderData.color); } const QString borderBottom(styleStack.property(KoXmlNS::fo, "border-bottom")); const ParagraphBorderData bottomParagraphBorderData = parseParagraphBorderData(borderBottom, borderData); if (bottomParagraphBorderData.values & ParagraphBorderData::Width) { setBottomBorderWidth(bottomParagraphBorderData.width); } if (bottomParagraphBorderData.values & ParagraphBorderData::Style) { setBottomBorderStyle(bottomParagraphBorderData.style); } if (bottomParagraphBorderData.values & ParagraphBorderData::Color) { setBottomBorderColor(bottomParagraphBorderData.color); } const QString borderLineWidthLeft(styleStack.property(KoXmlNS::style, "border-line-width", "left")); if (!borderLineWidthLeft.isEmpty()) { QStringList blw = borderLineWidthLeft.split(' ', QString::SkipEmptyParts); setLeftInnerBorderWidth(KoUnit::parseValue(blw.value(0), 0.1)); setLeftBorderSpacing(KoUnit::parseValue(blw.value(1), 1.0)); setLeftBorderWidth(KoUnit::parseValue(blw.value(2), 0.1)); } const QString borderLineWidthTop(styleStack.property(KoXmlNS::style, "border-line-width", "top")); if (!borderLineWidthTop.isEmpty()) { QStringList blw = borderLineWidthTop.split(' ', QString::SkipEmptyParts); setTopInnerBorderWidth(KoUnit::parseValue(blw.value(0), 0.1)); setTopBorderSpacing(KoUnit::parseValue(blw.value(1), 1.0)); setTopBorderWidth(KoUnit::parseValue(blw.value(2), 0.1)); } const QString borderLineWidthRight(styleStack.property(KoXmlNS::style, "border-line-width", "right")); if (!borderLineWidthRight.isEmpty()) { QStringList blw = borderLineWidthRight.split(' ', QString::SkipEmptyParts); setRightInnerBorderWidth(KoUnit::parseValue(blw.value(0), 0.1)); setRightBorderSpacing(KoUnit::parseValue(blw.value(1), 1.0)); setRightBorderWidth(KoUnit::parseValue(blw.value(2), 0.1)); } const QString borderLineWidthBottom(styleStack.property(KoXmlNS::style, "border-line-width", "bottom")); if (!borderLineWidthBottom.isEmpty()) { QStringList blw = borderLineWidthBottom.split(' ', QString::SkipEmptyParts); setBottomInnerBorderWidth(KoUnit::parseValue(blw.value(0), 0.1)); setBottomBorderSpacing(KoUnit::parseValue(blw.value(1), 1.0)); setBottomBorderWidth(KoUnit::parseValue(blw.value(2), 0.1)); } // drop caps KoXmlElement dropCap(styleStack.childNode(KoXmlNS::style, "drop-cap")); if (!dropCap.isNull()) { setDropCaps(true); const QString length = dropCap.attributeNS(KoXmlNS::style, "length", QString("1")); if (length.toLower() == "word") { setDropCapsLength(0); // 0 indicates drop caps of the whole first word } else { int l = length.toInt(); if (l > 0) // somefiles may use this to turn dropcaps off setDropCapsLength(length.toInt()); else setDropCaps(false); } const QString lines = dropCap.attributeNS(KoXmlNS::style, "lines", QString("1")); setDropCapsLines(lines.toInt()); const qreal distance = KoUnit::parseValue(dropCap.attributeNS(KoXmlNS::style, "distance", QString())); setDropCapsDistance(distance); const QString dropstyle = dropCap.attributeNS(KoXmlNS::style, "style-name"); if (! dropstyle.isEmpty()) { KoSharedLoadingData *sharedData = scontext.sharedData(KOTEXT_SHARED_LOADING_ID); KoTextSharedLoadingData *textSharedData = 0; textSharedData = dynamic_cast(sharedData); if (textSharedData) { KoCharacterStyle *cs = textSharedData->characterStyle(dropstyle, true); if (cs) setDropCapsTextStyleId(cs->styleId()); } } } // The fo:break-before and fo:break-after attributes insert a page or column break before or after a paragraph. const QString breakBefore(styleStack.property(KoXmlNS::fo, "break-before")); if (!breakBefore.isEmpty()) { setBreakBefore(KoText::textBreakFromString(breakBefore)); } const QString breakAfter(styleStack.property(KoXmlNS::fo, "break-after")); if (!breakAfter.isEmpty()) { setBreakAfter(KoText::textBreakFromString(breakAfter)); } const QString keepTogether(styleStack.property(KoXmlNS::fo, "keep-together")); if (!keepTogether.isEmpty()) { setNonBreakableLines(keepTogether == "always"); } const QString rawPageNumber(styleStack.property(KoXmlNS::style, "page-number")); if (!rawPageNumber.isEmpty()) { if (rawPageNumber == "auto") { setPageNumber(0); } else { bool ok; int number = rawPageNumber.toInt(&ok); if (ok) setPageNumber(number); } } // The fo:background-color attribute specifies the background color of a paragraph. const QString bgcolor(styleStack.property(KoXmlNS::fo, "background-color")); if (!bgcolor.isEmpty()) { const QString bgcolor = styleStack.property(KoXmlNS::fo, "background-color"); QBrush brush = background(); if (bgcolor == "transparent") brush.setStyle(Qt::NoBrush); else { if (brush.style() == Qt::NoBrush) brush.setStyle(Qt::SolidPattern); brush.setColor(bgcolor); // #rrggbb format } setBackground(brush); } if (styleStack.hasProperty(KoXmlNS::style, "background-transparency")) { QString transparency = styleStack.property(KoXmlNS::style, "background-transparency"); bool ok = false; qreal transparencyValue = transparency.remove('%').toDouble(&ok); if (ok) { setBackgroundTransparency(transparencyValue/100); } } if (styleStack.hasProperty(KoXmlNS::style, "snap-to-layout-grid")) { setSnapToLayoutGrid(styleStack.property(KoXmlNS::style, "snap-to-layout-grid") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "register-true")) { setRegisterTrue(styleStack.property(KoXmlNS::style, "register-true") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "join-border")) { setJoinBorder(styleStack.property(KoXmlNS::style, "join-border") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "line-break")) { setStrictLineBreak(styleStack.property(KoXmlNS::style, "line-break") == "strict"); } // Support for an old non-standard OpenOffice attribute that we still find in too many documents... if (styleStack.hasProperty(KoXmlNS::text, "enable-numbering")) { setProperty(ForceDisablingList, styleStack.property(KoXmlNS::text, "enable-numbering") == "false"); } if (styleStack.hasProperty(KoXmlNS::fo, "orphans")) { bool ok = false; int orphans = styleStack.property(KoXmlNS::fo, "orphans").toInt(&ok); if (ok) setOrphanThreshold(orphans); } if (styleStack.hasProperty(KoXmlNS::fo, "widows")) { bool ok = false; int widows = styleStack.property(KoXmlNS::fo, "widows").toInt(&ok); if (ok) setWidowThreshold(widows); } if (styleStack.hasProperty(KoXmlNS::style, "justify-single-word")) { setJustifySingleWord(styleStack.property(KoXmlNS::style, "justify-single-word") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "writing-mode-automatic")) { setAutomaticWritingMode(styleStack.property(KoXmlNS::style, "writing-mode-automatic") == "true"); } if (styleStack.hasProperty(KoXmlNS::fo, "text-align-last")) { setAlignLastLine(KoText::alignmentFromString(styleStack.property(KoXmlNS::fo, "text-align-last"))); } if (styleStack.hasProperty(KoXmlNS::fo, "keep-with-next")) { setKeepWithNext(styleStack.property(KoXmlNS::fo, "keep-with-next") == "always"); } if (styleStack.hasProperty(KoXmlNS::style, "text-autospace")) { const QString autoSpace = styleStack.property(KoXmlNS::style, "text-autospace"); if (autoSpace == "none") setTextAutoSpace(NoAutoSpace); else if (autoSpace == "ideograph-alpha") setTextAutoSpace(IdeographAlpha); } if (styleStack.hasProperty(KoXmlNS::fo, "hyphenation-keep")) { setKeepHyphenation(styleStack.property(KoXmlNS::fo, "hyphenation-keep") == "page"); } if (styleStack.hasProperty(KoXmlNS::fo, "hyphenation-ladder-count")) { QString ladderCount = styleStack.property(KoXmlNS::fo, "hyphenation-ladder-count"); if (ladderCount == "no-limit") setHyphenationLadderCount(0); else { bool ok; int value = ladderCount.toInt(&ok); if ((ok) && (value > 0)) setHyphenationLadderCount(value); } } if (styleStack.hasProperty(KoXmlNS::style, "punctuation-wrap")) { setPunctuationWrap(styleStack.property(KoXmlNS::style, "punctuation-wrap") == "simple"); } if (styleStack.hasProperty(KoXmlNS::style, "vertical-align")) { const QString valign = styleStack.property(KoXmlNS::style, "vertical-align"); if (valign == "auto") setVerticalAlignment(VAlignAuto); else if (valign == "baseline") setVerticalAlignment(VAlignBaseline); else if (valign == "bottom") setVerticalAlignment(VAlignBottom); else if (valign == "middle") setVerticalAlignment(VAlignMiddle); else if (valign == "top") setVerticalAlignment(VAlignTop); } if (styleStack.hasProperty(KoXmlNS::style, "shadow")) { KoShadowStyle shadow; if (shadow.loadOdf(styleStack.property(KoXmlNS::style, "shadow"))) setShadow(shadow); } //following properties KoParagraphStyle provides us are not handled now; // LineSpacingFromFont, // FollowDocBaseline, } void KoParagraphStyle::setTabPositions(const QList &tabs) { QList newTabs = tabs; - qSort(newTabs.begin(), newTabs.end(), compareTabs); + std::sort(newTabs.begin(), newTabs.end(), compareTabs); QList list; Q_FOREACH (const KoText::Tab &tab, tabs) { QVariant v; v.setValue(tab); list.append(v); } setProperty(TabPositions, list); } QList KoParagraphStyle::tabPositions() const { QVariant variant = value(TabPositions); if (variant.isNull()) return QList(); QList answer; Q_FOREACH (const QVariant &tab, qvariant_cast >(variant)) { answer.append(tab.value()); } return answer; } void KoParagraphStyle::setTabStopDistance(qreal value) { setProperty(TabStopDistance, value); } qreal KoParagraphStyle::tabStopDistance() const { return propertyDouble(TabStopDistance); } bool KoParagraphStyle::registerTrue() const { if (hasProperty(RegisterTrue)) return propertyBoolean(RegisterTrue); return false; } void KoParagraphStyle::setRegisterTrue(bool value) { setProperty(RegisterTrue, value); } bool KoParagraphStyle::strictLineBreak() const { if (hasProperty(StrictLineBreak)) return propertyBoolean(StrictLineBreak); return false; } void KoParagraphStyle::setStrictLineBreak(bool value) { setProperty(StrictLineBreak, value); } bool KoParagraphStyle::justifySingleWord() const { if (hasProperty(JustifySingleWord)) return propertyBoolean(JustifySingleWord); return false; } void KoParagraphStyle::setJustifySingleWord(bool value) { setProperty(JustifySingleWord, value); } void KoParagraphStyle::setTextAutoSpace(KoParagraphStyle::AutoSpace value) { setProperty(TextAutoSpace, value); } KoParagraphStyle::AutoSpace KoParagraphStyle::textAutoSpace() const { if (hasProperty(TextAutoSpace)) return static_cast(propertyInt(TextAutoSpace)); return NoAutoSpace; } void KoParagraphStyle::copyProperties(const KoParagraphStyle *style) { d->stylesPrivate = style->d->stylesPrivate; setName(style->name()); // make sure we emit property change KoCharacterStyle::copyProperties(style); d->parentStyle = style->d->parentStyle; d->defaultStyle = style->d->defaultStyle; } KoParagraphStyle *KoParagraphStyle::clone(QObject *parent) const { KoParagraphStyle *newStyle = new KoParagraphStyle(parent); newStyle->copyProperties(this); return newStyle; } bool KoParagraphStyle::compareParagraphProperties(const KoParagraphStyle &other) const { return other.d->stylesPrivate == d->stylesPrivate; } bool KoParagraphStyle::operator==(const KoParagraphStyle &other) const { if (!compareParagraphProperties(other)) return false; if (!compareCharacterProperties(other)) return false; return true; } void KoParagraphStyle::removeDuplicates(const KoParagraphStyle &other) { d->stylesPrivate.removeDuplicates(other.d->stylesPrivate); KoCharacterStyle::removeDuplicates(other); } void KoParagraphStyle::saveOdf(KoGenStyle &style, KoShapeSavingContext &context) const { bool writtenLineSpacing = false; KoCharacterStyle::saveOdf(style); if (listStyle()) { KoGenStyle liststyle(KoGenStyle::ListStyle); listStyle()->saveOdf(liststyle, context); QString name(QString(QUrl::toPercentEncoding(listStyle()->name(), "", " ")).replace('%', '_')); if (name.isEmpty()) name = 'L'; style.addAttribute("style:list-style-name", context.mainStyles().insert(liststyle, name, KoGenStyles::DontAddNumberToName)); } // only custom style have a displayname. automatic styles don't have a name set. if (!d->name.isEmpty() && !style.isDefaultStyle()) { style.addAttribute("style:display-name", d->name); } QList keys = d->stylesPrivate.keys(); if (keys.contains(KoParagraphStyle::LeftPadding) && keys.contains(KoParagraphStyle::RightPadding) && keys.contains(KoParagraphStyle::TopPadding) && keys.contains(KoParagraphStyle::BottomPadding)) { if ((leftPadding() == rightPadding()) && (topPadding() == bottomPadding()) && (rightPadding() == topPadding())) { style.addPropertyPt("fo:padding", leftPadding(), KoGenStyle::ParagraphType); keys.removeOne(KoParagraphStyle::LeftPadding); keys.removeOne(KoParagraphStyle::RightPadding); keys.removeOne(KoParagraphStyle::TopPadding); keys.removeOne(KoParagraphStyle::BottomPadding); } } if (keys.contains(QTextFormat::BlockLeftMargin) && keys.contains(QTextFormat::BlockRightMargin) && keys.contains(QTextFormat::BlockBottomMargin) && keys.contains(QTextFormat::BlockTopMargin)) { if ((leftMargin() == rightMargin()) && (topMargin() == bottomMargin()) && (rightMargin() == topMargin())) { style.addPropertyLength("fo:margin", propertyLength(QTextFormat::BlockLeftMargin), KoGenStyle::ParagraphType); keys.removeOne(QTextFormat::BlockLeftMargin); keys.removeOne(QTextFormat::BlockRightMargin); keys.removeOne(QTextFormat::BlockTopMargin); keys.removeOne(QTextFormat::BlockBottomMargin); } } foreach (int key, keys) { if (key == QTextFormat::BlockAlignment) { int alignValue = 0; bool ok = false; alignValue = d->stylesPrivate.value(key).toInt(&ok); if (ok) { Qt::Alignment alignment = (Qt::Alignment) alignValue; QString align = KoText::alignmentToString(alignment); if (!align.isEmpty()) style.addProperty("fo:text-align", align, KoGenStyle::ParagraphType); } } else if (key == KoParagraphStyle::AlignLastLine) { QString align = KoText::alignmentToString(alignLastLine()); if (!align.isEmpty()) style.addProperty("fo:text-align-last", align, KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::TextProgressionDirection) { style.addProperty("style:writing-mode", KoText::directionToString(textProgressionDirection()), KoGenStyle::ParagraphType); } else if (key == LineNumbering) { style.addProperty("text:number-lines", lineNumbering()); } else if (key == PageNumber) { if (pageNumber() == 0) style.addProperty("style:page-number", "auto", KoGenStyle::ParagraphType); else style.addProperty("style:page-number", pageNumber(), KoGenStyle::ParagraphType); } else if (key == LineNumberStartValue) { style.addProperty("text:line-number", lineNumberStartValue()); } else if (key == BreakAfter) { style.addProperty("fo:break-after", KoText::textBreakToString(breakAfter()), KoGenStyle::ParagraphType); } else if (key == BreakBefore) { style.addProperty("fo:break-before", KoText::textBreakToString(breakBefore()), KoGenStyle::ParagraphType); } else if (key == QTextFormat::BlockNonBreakableLines) { if (nonBreakableLines()) { style.addProperty("fo:keep-together", "always", KoGenStyle::ParagraphType); } else { style.addProperty("fo:keep-together", "auto", KoGenStyle::ParagraphType); } } else if (key == QTextFormat::BackgroundBrush) { QBrush backBrush = background(); if (backBrush.style() != Qt::NoBrush) style.addProperty("fo:background-color", backBrush.color().name(), KoGenStyle::ParagraphType); else style.addProperty("fo:background-color", "transparent", KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::BackgroundTransparency) { style.addProperty("style:background-transparency", QString("%1%").arg(backgroundTransparency() * 100), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::SnapToLayoutGrid) { style.addProperty("style:snap-to-layout-grid", snapToLayoutGrid(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::JustifySingleWord) { style.addProperty("style:justify-single-word", justifySingleWord(), KoGenStyle::ParagraphType); } else if (key == RegisterTrue) { style.addProperty("style:register-true", registerTrue(), KoGenStyle::ParagraphType); } else if (key == StrictLineBreak) { if (strictLineBreak()) style.addProperty("style:line-break", "strict", KoGenStyle::ParagraphType); else style.addProperty("style:line-break", "normal", KoGenStyle::ParagraphType); } else if (key == JoinBorder) { style.addProperty("style:join-border", joinBorder(), KoGenStyle::ParagraphType); } else if (key == OrphanThreshold) { style.addProperty("fo:orphans", orphanThreshold(), KoGenStyle::ParagraphType); } else if (key == WidowThreshold) { style.addProperty("fo:widows", widowThreshold(), KoGenStyle::ParagraphType); // Padding } else if (key == KoParagraphStyle::LeftPadding) { style.addPropertyPt("fo:padding-left", leftPadding(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::RightPadding) { style.addPropertyPt("fo:padding-right", rightPadding(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::TopPadding) { style.addPropertyPt("fo:padding-top", topPadding(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::BottomPadding) { style.addPropertyPt("fo:padding-bottom", bottomPadding(), KoGenStyle::ParagraphType); // Margin } else if (key == QTextFormat::BlockLeftMargin) { style.addPropertyLength("fo:margin-left", propertyLength(QTextFormat::BlockLeftMargin), KoGenStyle::ParagraphType); } else if (key == QTextFormat::BlockRightMargin) { style.addPropertyLength("fo:margin-right", propertyLength(QTextFormat::BlockRightMargin), KoGenStyle::ParagraphType); } else if (key == QTextFormat::BlockTopMargin) { style.addPropertyLength("fo:margin-top", propertyLength(QTextFormat::BlockTopMargin), KoGenStyle::ParagraphType); } else if (key == QTextFormat::BlockBottomMargin) { style.addPropertyLength("fo:margin-bottom", propertyLength(QTextFormat::BlockBottomMargin), KoGenStyle::ParagraphType); // Line spacing } else if ( key == KoParagraphStyle::MinimumLineHeight || key == KoParagraphStyle::LineSpacing || key == KoParagraphStyle::PercentLineHeight || key == KoParagraphStyle::FixedLineHeight || key == KoParagraphStyle::LineSpacingFromFont) { if (key == KoParagraphStyle::MinimumLineHeight && propertyLength(MinimumLineHeight).rawValue() != 0) { style.addPropertyLength("style:line-height-at-least", propertyLength(MinimumLineHeight), KoGenStyle::ParagraphType); writtenLineSpacing = true; } else if (key == KoParagraphStyle::LineSpacing && lineSpacing() != 0) { style.addPropertyPt("style:line-spacing", lineSpacing(), KoGenStyle::ParagraphType); writtenLineSpacing = true; } else if (key == KoParagraphStyle::PercentLineHeight && lineHeightPercent() != 0) { style.addProperty("fo:line-height", QString("%1%").arg(lineHeightPercent()), KoGenStyle::ParagraphType); writtenLineSpacing = true; } else if (key == KoParagraphStyle::FixedLineHeight && lineHeightAbsolute() != 0) { style.addPropertyPt("fo:line-height", lineHeightAbsolute(), KoGenStyle::ParagraphType); writtenLineSpacing = true; } else if (key == KoParagraphStyle::LineSpacingFromFont && lineHeightAbsolute() == 0) { style.addProperty("style:font-independent-line-spacing", lineSpacingFromFont(), KoGenStyle::ParagraphType); writtenLineSpacing = true; } // } else if (key == QTextFormat::TextIndent) { style.addPropertyLength("fo:text-indent", propertyLength(QTextFormat::TextIndent), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::AutoTextIndent) { style.addProperty("style:auto-text-indent", autoTextIndent(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::TabStopDistance) { style.addPropertyPt("style:tab-stop-distance", tabStopDistance(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::MasterPageName) { style.addAttribute("style:master-page-name", masterPageName()); } else if (key == KoParagraphStyle::DefaultOutlineLevel) { style.addAttribute("style:default-outline-level", defaultOutlineLevel()); } else if (key == KoParagraphStyle::AutomaticWritingMode) { style.addProperty("style:writing-mode-automatic", automaticWritingMode(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::TextAutoSpace) { if (textAutoSpace() == NoAutoSpace) style.addProperty("style:text-autospace", "none", KoGenStyle::ParagraphType); else if (textAutoSpace() == IdeographAlpha) style.addProperty("style:text-autospace", "ideograph-alpha", KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::KeepWithNext) { if (keepWithNext()) style.addProperty("fo:keep-with-next", "always", KoGenStyle::ParagraphType); else style.addProperty("fo:keep-with-next", "auto", KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::KeepHyphenation) { if (keepHyphenation()) style.addProperty("fo:hyphenation-keep", "page", KoGenStyle::ParagraphType); else style.addProperty("fo:hyphenation-keep", "auto", KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::HyphenationLadderCount) { int value = hyphenationLadderCount(); if (value == 0) style.addProperty("fo:hyphenation-ladder-count", "no-limit", KoGenStyle::ParagraphType); else style.addProperty("fo:hyphenation-ladder-count", value, KoGenStyle::ParagraphType); } else if (key == PunctuationWrap) { if (punctuationWrap()) style.addProperty("style:punctuation-wrap", "simple", KoGenStyle::ParagraphType); else style.addProperty("style:punctuation-wrap", "hanging", KoGenStyle::ParagraphType); } else if (key == VerticalAlignment) { VerticalAlign valign = verticalAlignment(); if (valign == VAlignAuto) style.addProperty("style:vertical-align", "auto", KoGenStyle::ParagraphType); else if (valign == VAlignBaseline) style.addProperty("style:vertical-align", "baseline", KoGenStyle::ParagraphType); else if (valign == VAlignBottom) style.addProperty("style:vertical-align", "bottom", KoGenStyle::ParagraphType); else if (valign == VAlignMiddle) style.addProperty("style:vertical-align", "middle", KoGenStyle::ParagraphType); else if (valign == VAlignTop) style.addProperty("style:vertical-align", "top", KoGenStyle::ParagraphType); } else if (key == Shadow) { style.addProperty("style:shadow", shadow().saveOdf()); } } if (!writtenLineSpacing && propertyBoolean(NormalLineHeight)) style.addProperty("fo:line-height", QString("normal"), KoGenStyle::ParagraphType); // save border stuff QString leftBorder = QString("%1pt %2 %3").arg(QString::number(leftBorderWidth()), KoBorder::odfBorderStyleString(leftBorderStyle()), leftBorderColor().name()); QString rightBorder = QString("%1pt %2 %3").arg(QString::number(rightBorderWidth()), KoBorder::odfBorderStyleString(rightBorderStyle()), rightBorderColor().name()); QString topBorder = QString("%1pt %2 %3").arg(QString::number(topBorderWidth()), KoBorder::odfBorderStyleString(topBorderStyle()), topBorderColor().name()); QString bottomBorder = QString("%1pt %2 %3").arg(QString::number(bottomBorderWidth()), KoBorder::odfBorderStyleString(bottomBorderStyle()), bottomBorderColor().name()); if (leftBorder == rightBorder && leftBorder == topBorder && leftBorder == bottomBorder) { if (leftBorderWidth() > 0 && leftBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border", leftBorder, KoGenStyle::ParagraphType); } else { if (leftBorderWidth() > 0 && leftBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border-left", leftBorder, KoGenStyle::ParagraphType); if (rightBorderWidth() > 0 && rightBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border-right", rightBorder, KoGenStyle::ParagraphType); if (topBorderWidth() > 0 && topBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border-top", topBorder, KoGenStyle::ParagraphType); if (bottomBorderWidth() > 0 && bottomBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border-bottom", bottomBorder, KoGenStyle::ParagraphType); } QString leftBorderLineWidth, rightBorderLineWidth, topBorderLineWidth, bottomBorderLineWidth; if (leftBorderStyle() == KoBorder::BorderDouble) leftBorderLineWidth = QString("%1pt %2pt %3pt").arg(QString::number(leftInnerBorderWidth()), QString::number(leftBorderSpacing()), QString::number(leftBorderWidth())); if (rightBorderStyle() == KoBorder::BorderDouble) rightBorderLineWidth = QString("%1pt %2pt %3pt").arg(QString::number(rightInnerBorderWidth()), QString::number(rightBorderSpacing()), QString::number(rightBorderWidth())); if (topBorderStyle() == KoBorder::BorderDouble) topBorderLineWidth = QString("%1pt %2pt %3pt").arg(QString::number(topInnerBorderWidth()), QString::number(topBorderSpacing()), QString::number(topBorderWidth())); if (bottomBorderStyle() == KoBorder::BorderDouble) bottomBorderLineWidth = QString("%1pt %2pt %3pt").arg(QString::number(bottomInnerBorderWidth()), QString::number(bottomBorderSpacing()), QString::number(bottomBorderWidth())); if (leftBorderLineWidth == rightBorderLineWidth && leftBorderLineWidth == topBorderLineWidth && leftBorderLineWidth == bottomBorderLineWidth && !leftBorderLineWidth.isEmpty()) { style.addProperty("style:border-line-width", leftBorderLineWidth, KoGenStyle::ParagraphType); } else { if (!leftBorderLineWidth.isEmpty()) style.addProperty("style:border-line-width-left", leftBorderLineWidth, KoGenStyle::ParagraphType); if (!rightBorderLineWidth.isEmpty()) style.addProperty("style:border-line-width-right", rightBorderLineWidth, KoGenStyle::ParagraphType); if (!topBorderLineWidth.isEmpty()) style.addProperty("style:border-line-width-top", topBorderLineWidth, KoGenStyle::ParagraphType); if (!bottomBorderLineWidth.isEmpty()) style.addProperty("style:border-line-width-bottom", bottomBorderLineWidth, KoGenStyle::ParagraphType); } const int indentation = 4; // indentation for children of office:styles/style:style/style:paragraph-properties // drop-caps if (dropCaps()) { QBuffer buf; buf.open(QIODevice::WriteOnly); KoXmlWriter elementWriter(&buf, indentation); elementWriter.startElement("style:drop-cap"); elementWriter.addAttribute("style:lines", QString::number(dropCapsLines())); elementWriter.addAttribute("style:length", dropCapsLength() == 0 ? "word" : QString::number(dropCapsLength())); if (dropCapsDistance()) elementWriter.addAttributePt("style:distance", dropCapsDistance()); elementWriter.endElement(); QString elementContents = QString::fromUtf8(buf.buffer(), buf.buffer().size()); style.addChildElement("style:drop-cap", elementContents, KoGenStyle::ParagraphType); } if (tabPositions().count() > 0) { QMap tabTypeMap, leaderTypeMap, leaderStyleMap, leaderWeightMap; tabTypeMap[QTextOption::LeftTab] = "left"; tabTypeMap[QTextOption::RightTab] = "right"; tabTypeMap[QTextOption::CenterTab] = "center"; tabTypeMap[QTextOption::DelimiterTab] = "char"; leaderTypeMap[KoCharacterStyle::NoLineType] = "none"; leaderTypeMap[KoCharacterStyle::SingleLine] = "single"; leaderTypeMap[KoCharacterStyle::DoubleLine] = "double"; leaderStyleMap[KoCharacterStyle::NoLineStyle] = "none"; leaderStyleMap[KoCharacterStyle::SolidLine] = "solid"; leaderStyleMap[KoCharacterStyle::DottedLine] = "dotted"; leaderStyleMap[KoCharacterStyle::DashLine] = "dash"; leaderStyleMap[KoCharacterStyle::LongDashLine] = "long-dash"; leaderStyleMap[KoCharacterStyle::DotDashLine] = "dot-dash"; leaderStyleMap[KoCharacterStyle::DotDotDashLine] = "dot-dot-dash"; leaderStyleMap[KoCharacterStyle::WaveLine] = "wave"; leaderWeightMap[KoCharacterStyle::AutoLineWeight] = "auto"; leaderWeightMap[KoCharacterStyle::NormalLineWeight] = "normal"; leaderWeightMap[KoCharacterStyle::BoldLineWeight] = "bold"; leaderWeightMap[KoCharacterStyle::ThinLineWeight] = "thin"; leaderWeightMap[KoCharacterStyle::DashLineWeight] = "dash"; leaderWeightMap[KoCharacterStyle::MediumLineWeight] = "medium"; leaderWeightMap[KoCharacterStyle::ThickLineWeight] = "thick"; QBuffer buf; buf.open(QIODevice::WriteOnly); KoXmlWriter elementWriter(&buf, indentation); elementWriter.startElement("style:tab-stops"); Q_FOREACH (const KoText::Tab &tab, tabPositions()) { elementWriter.startElement("style:tab-stop"); elementWriter.addAttributePt("style:position", tab.position); if (!tabTypeMap[tab.type].isEmpty()) elementWriter.addAttribute("style:type", tabTypeMap[tab.type]); if (tab.type == QTextOption::DelimiterTab && !tab.delimiter.isNull()) elementWriter.addAttribute("style:char", tab.delimiter); if (!leaderTypeMap[tab.leaderType].isEmpty()) elementWriter.addAttribute("style:leader-type", leaderTypeMap[tab.leaderType]); if (!leaderStyleMap[tab.leaderStyle].isEmpty()) elementWriter.addAttribute("style:leader-style", leaderStyleMap[tab.leaderStyle]); if (!leaderWeightMap[tab.leaderWeight].isEmpty()) elementWriter.addAttribute("style:leader-width", leaderWeightMap[tab.leaderWeight]); else if (tab.leaderWeight == KoCharacterStyle::PercentLineWeight) elementWriter.addAttribute("style:leader-width", QString("%1%").arg(QString::number(tab.leaderWidth))); else if (tab.leaderWeight == KoCharacterStyle::LengthLineWeight) elementWriter.addAttributePt("style:leader-width", tab.leaderWidth); if (tab.leaderColor.isValid()) elementWriter.addAttribute("style:leader-color", tab.leaderColor.name()); else elementWriter.addAttribute("style:leader-color", "font-color"); if (!tab.leaderText.isEmpty()) elementWriter.addAttribute("style:leader-text", tab.leaderText); elementWriter.endElement(); } elementWriter.endElement(); buf.close(); QString elementContents = QString::fromUtf8(buf.buffer(), buf.buffer().size()); style.addChildElement("style:tab-stops", elementContents, KoGenStyle::ParagraphType); } } bool KoParagraphStyle::hasDefaults() const { int size=d->stylesPrivate.properties().size(); if ((size == 0) || (size==1 && d->stylesPrivate.properties().contains(StyleId))) { return true; } return false; } KoList *KoParagraphStyle::list() const { return d->list; } diff --git a/plugins/flake/textshape/textlayout/KoTextLayoutArea.cpp b/plugins/flake/textshape/textlayout/KoTextLayoutArea.cpp index 54ac31dc74..e56e69d107 100644 --- a/plugins/flake/textshape/textlayout/KoTextLayoutArea.cpp +++ b/plugins/flake/textshape/textlayout/KoTextLayoutArea.cpp @@ -1,2092 +1,2092 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008,2011 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2008 Roopesh Chander * Copyright (C) 2007-2008 Pierre Ducroquet * Copyright (C) 2009-2011 KO GmbH * Copyright (C) 2009-2011 C. Boemann * Copyright (C) 2010 Nandita Suri * Copyright (C) 2010 Ajay Pundhir * Copyright (C) 2011 Lukáš Tvrdý * Copyright (C) 2011 Gopalakrishna Bhat A * Copyright (C) 2011 Stuart Dickson * * 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 "KoTextLayoutArea.h" #include "KoTextLayoutArea_p.h" #include "TableIterator.h" #include "ListItemsHelper.h" #include "RunAroundHelper.h" #include "KoTextDocumentLayout.h" #include "FrameIterator.h" #include "KoPointedAt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern int qt_defaultDpiY(); Q_DECLARE_METATYPE(QTextDocument *) #define DropCapsAdditionalFormattingId 25602902 #define PresenterFontStretch 1.2 KoTextLayoutArea::KoTextLayoutArea(KoTextLayoutArea *p, KoTextDocumentLayout *documentLayout) : d (new Private) { d->parent = p; d->documentLayout = documentLayout; } KoTextLayoutArea::~KoTextLayoutArea() { qDeleteAll(d->tableAreas); qDeleteAll(d->footNoteAreas); qDeleteAll(d->preregisteredFootNoteAreas); delete d->startOfArea; delete d->endOfArea; delete d; } KoPointedAt KoTextLayoutArea::hitTest(const QPointF &p, Qt::HitTestAccuracy accuracy) const { QPointF point = p - QPointF(0, d->verticalAlignOffset); if (d->startOfArea == 0) // We have not been layouted yet return KoPointedAt(); KoPointedAt pointedAt; bool basicallyFound = false; QTextFrame::iterator it = d->startOfArea->it; QTextFrame::iterator stop = d->endOfArea->it; if (!stop.atEnd()) { if(!stop.currentBlock().isValid() || d->endOfArea->lineTextStart >= 0) { // Last thing we contain is a frame (table) or first part of a paragraph split in two // The stop point should be the object after that // However if stop is already atEnd we shouldn't increment further ++stop; } } int tableAreaIndex = 0; int tocIndex = 0; int footNoteIndex = 0; for (; it != stop && !it.atEnd(); ++it) { QTextBlock block = it.currentBlock(); QTextTable *table = qobject_cast(it.currentFrame()); QTextFrame *subFrame = it.currentFrame(); QTextBlockFormat format = block.blockFormat(); if (table) { if (tableAreaIndex >= d->tableAreas.size()) { continue; } if (point.y() > d->tableAreas[tableAreaIndex]->top() && point.y() < d->tableAreas[tableAreaIndex]->bottom()) { return d->tableAreas[tableAreaIndex]->hitTest(point, accuracy); } ++tableAreaIndex; continue; } else if (subFrame) { if (it.currentFrame()->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { if (point.y() > d->endNotesArea->top() && point.y() < d->endNotesArea->bottom()) { pointedAt = d->endNotesArea->hitTest(point, accuracy); return pointedAt; } } break; } else { if (!block.isValid()) continue; } if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) { // check if p is over table of content if (point.y() > d->generatedDocAreas[tocIndex]->top() && point.y() < d->generatedDocAreas[tocIndex]->bottom()) { pointedAt = d->generatedDocAreas[tocIndex]->hitTest(point, accuracy); pointedAt.position = block.position(); return pointedAt; } ++tocIndex; continue; } if (basicallyFound) // a subsequent table or lines have now had their chance return pointedAt; QTextLayout *layout = block.layout(); QTextFrame::iterator next = it; ++next; if (next != stop && next.currentFrame() == 0 && point.y() > layout->boundingRect().bottom()) { // just skip this block. continue; } for (int i = 0; i < layout->lineCount(); i++) { QTextLine line = layout->lineAt(i); if (block == d->startOfArea->it.currentBlock() && line.textStart() < d->startOfArea->lineTextStart) { continue; // this line is part of a previous layoutArea } QRectF lineRect = line.naturalTextRect(); if (point.y() > line.y() + line.height()) { pointedAt.position = block.position() + line.textStart() + line.textLength(); if (block == d->endOfArea->it.currentBlock() && line.textStart() + line.textLength() >= d->endOfArea->lineTextStart) { pointedAt.position = block.position() + line.xToCursor(point.x()); break; // this and following lines are part of a next layoutArea } continue; } if (accuracy == Qt::ExactHit && point.y() < line.y()) { // between lines return KoPointedAt(); } if (accuracy == Qt::ExactHit && // left or right of line (point.x() < line.naturalTextRect().left() || point.x() > line.naturalTextRect().right())) { return KoPointedAt(); } if (point.x() > lineRect.x() + lineRect.width() && layout->textOption().textDirection() == Qt::RightToLeft) { // totally right of RTL text means the position is the start of the text. //TODO how about the other side? pointedAt.position = block.position() + line.textStart(); return pointedAt; } if (basicallyFound && point.y() < lineRect.y()) { // This was not same baseline so basicallyFound was correct return pointedAt; } if (point.x() > lineRect.x() + lineRect.width()) { // right of line basicallyFound = true; pointedAt.position = block.position() + line.textStart() + line.textLength(); continue; // don't break as next line may be on same baseline } pointedAt.position = block.position() + line.xToCursor(point.x()); QTextCursor tmpCursor(block); tmpCursor.setPosition(block.position() + line.xToCursor(point.x(), QTextLine::CursorOnCharacter) + 1); pointedAt.fillInLinks(tmpCursor, d->documentLayout->inlineTextObjectManager(), d->documentLayout->textRangeManager()); return pointedAt; } } //and finally test the footnotes point -= QPointF(0, bottom() - d->footNotesHeight); while (footNoteIndex < d->footNoteAreas.length()) { // check if p is over foot notes area if (point.y() > 0 && point.y() < d->footNoteAreas[footNoteIndex]->bottom() - d->footNoteAreas[footNoteIndex]->top()) { pointedAt = d->footNoteAreas[footNoteIndex]->hitTest(point, accuracy); return pointedAt; } point -= QPointF(0, d->footNoteAreas[footNoteIndex]->bottom() - d->footNoteAreas[footNoteIndex]->top()); ++footNoteIndex; } return pointedAt; } QRectF KoTextLayoutArea::selectionBoundingBox(QTextCursor &cursor) const { QRectF retval(-5E6, top(), 105E6, 0); if (d->startOfArea == 0) // We have not been layouted yet return QRectF(); if (d->endOfArea == 0) // no end area yet return QRectF(); QTextFrame::iterator it = d->startOfArea->it; QTextFrame::iterator stop = d->endOfArea->it; if (!stop.atEnd()) { if(!stop.currentBlock().isValid() || d->endOfArea->lineTextStart >= 0) { // Last thing we show is a frame (table) or first part of a paragraph split in two // The stop point should be the object after that // However if stop is already atEnd we shouldn't increment further ++stop; } } QTextFrame *subFrame; int footNoteIndex = 0; qreal offset = bottom() - d->footNotesHeight; while (footNoteIndex < d->footNoteAreas.length()) { subFrame = d->footNoteFrames[footNoteIndex]; if (cursor.selectionStart() >= subFrame->firstPosition() && cursor.selectionEnd() <= subFrame->lastPosition()) { return d->footNoteAreas[footNoteIndex]->selectionBoundingBox(cursor).translated(0, offset) ; } offset += d->footNoteAreas[footNoteIndex]->bottom() - d->footNoteAreas[footNoteIndex]->top(); ++footNoteIndex; } int tableAreaIndex = 0; int tocIndex = 0; for (; it != stop && !it.atEnd(); ++it) { QTextBlock block = it.currentBlock(); QTextTable *table = qobject_cast(it.currentFrame()); QTextFrame *subFrame = it.currentFrame(); QTextBlockFormat format = block.blockFormat(); if (table) { if (tableAreaIndex >= d->tableAreas.size()) { continue; } if (cursor.selectionEnd() < table->firstPosition()) { return retval.translated(0, d->verticalAlignOffset); } if (cursor.selectionStart() > table->lastPosition()) { ++tableAreaIndex; continue; } if (cursor.selectionStart() >= table->firstPosition() && cursor.selectionEnd() <= table->lastPosition()) { return d->tableAreas[tableAreaIndex]->selectionBoundingBox(cursor).translated(0, d->verticalAlignOffset); } if (cursor.selectionStart() >= table->firstPosition()) { retval = d->tableAreas[tableAreaIndex]->boundingRect(); } else { retval |= d->tableAreas[tableAreaIndex]->boundingRect(); } ++tableAreaIndex; continue; } else if (subFrame) { if (it.currentFrame()->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { if (cursor.selectionEnd() < subFrame->firstPosition()) { return retval.translated(0, d->verticalAlignOffset); } if (cursor.selectionStart() > subFrame->lastPosition()) { break; } if (cursor.selectionStart() >= subFrame->firstPosition() && cursor.selectionEnd() <= subFrame->lastPosition()) { return d->endNotesArea->selectionBoundingBox(cursor).translated(0, d->verticalAlignOffset); } break; } } else { if (!block.isValid()) continue; } if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) { if (cursor.selectionStart() <= block.position() && cursor.selectionEnd() >= block.position()) { retval |= d->generatedDocAreas[tocIndex]->boundingRect(); } ++tocIndex; continue; } if(cursor.selectionEnd() < block.position()) { return retval.translated(0, d->verticalAlignOffset); } if(cursor.selectionStart() >= block.position() && cursor.selectionStart() < block.position() + block.length()) { QTextLine line = block.layout()->lineForTextPosition(cursor.selectionStart() - block.position()); if (line.isValid()) { retval.setTop(line.y()); retval.setBottom(line.y()); } } if(cursor.selectionEnd() >= block.position() && cursor.selectionEnd() < block.position() + block.length()) { QTextLine line = block.layout()->lineForTextPosition(cursor.selectionEnd() - block.position()); if (line.isValid()) { retval.setBottom(line.y() + line.height()); if (line.ascent()==0) { // Block is empty from any visible content and has as such no height // but in that case the block font defines line height retval.setBottom(line.y() + 24); } if (cursor.selectionStart() == cursor.selectionEnd()) { // We only have a caret so let's set the rect a bit more narrow retval.setX(line.cursorToX(cursor.position() - block.position())); retval.setWidth(1); } } } // if the full paragraph is selected to add it to the rect. This makes sure we get a rect for the case // where the end of the selection lies is a different area. if (cursor.selectionEnd() >= block.position() + block.length() && cursor.selectionStart() <= block.position()) { QTextLine line = block.layout()->lineForTextPosition(block.length()-1); if (line.isValid()) { retval.setBottom(line.y() + line.height()); if (line.ascent()==0) { // Block is empty from any visible content and has as such no height // but in that case the block font defines line height retval.setBottom(line.y() + 24); } } } } return retval.translated(0, d->verticalAlignOffset); } bool KoTextLayoutArea::isStartingAt(FrameIterator *cursor) const { if (d->startOfArea) { return *d->startOfArea == *cursor; } return false; } QTextFrame::iterator KoTextLayoutArea::startTextFrameIterator() const { return d->startOfArea->it; } QTextFrame::iterator KoTextLayoutArea::endTextFrameIterator() const { return d->endOfArea->it; } void KoTextLayoutArea::backtrackKeepWithNext(FrameIterator *cursor) { QTextFrame::iterator it = cursor->it; while (!(it == d->startOfArea->it)) { --it; QTextBlock block = it.currentBlock(); QTextTable *table = qobject_cast(it.currentFrame()); QTextFrame *subFrame = it.currentFrame(); bool keepWithNext = false; if (table) { keepWithNext = table->format().boolProperty(KoTableStyle::KeepWithNext); //setBottom(tableArea->bottom() + d->footNotesHeight); } else if (subFrame) { Q_ASSERT(false); // there should never be an aux frame before normal layouted stuff } else if (block.isValid()) { keepWithNext = block.blockFormat().boolProperty(KoParagraphStyle::KeepWithNext); //setBottom(d->blockRects.last()->bottom() + d->footNotesHeight); } if (!keepWithNext) { cursor->it = ++it; break; } } } bool KoTextLayoutArea::layout(FrameIterator *cursor) { qDeleteAll(d->tableAreas); d->tableAreas.clear(); qDeleteAll(d->footNoteAreas); d->footNoteAreas.clear(); qDeleteAll(d->preregisteredFootNoteAreas); d->preregisteredFootNoteAreas.clear(); d->footNoteFrames.clear(); d->preregisteredFootNoteFrames.clear(); qDeleteAll(d->generatedDocAreas); d->generatedDocAreas.clear(); d->blockRects.clear(); delete d->endNotesArea; d->endNotesArea=0; if (d->endOfArea) { delete d->copyEndOfArea; d->copyEndOfArea = new FrameIterator(d->endOfArea); } delete d->startOfArea; delete d->endOfArea; d->dropCapsWidth = 0; d->dropCapsDistance = 0; d->startOfArea = new FrameIterator(cursor); d->endOfArea = 0; d->y = top(); d->neededWidth = 0; setBottom(top()); d->bottomSpacing = 0; d->footNoteAutoCount = 0; d->footNotesHeight = 0; d->preregisteredFootNotesHeight = 0; d->prevBorder = 0; d->prevBorderPadding = 0; if (d->footNoteCursorFromPrevious) { KoTextLayoutNoteArea *footNoteArea = new KoTextLayoutNoteArea(d->continuedNoteFromPrevious, this, d->documentLayout); d->footNoteFrames.append(d->continuedNoteFromPrevious->textFrame()); footNoteArea->setReferenceRect(left(), right(), 0, maximumAllowedBottom()); footNoteArea->setAsContinuedArea(true); footNoteArea->layout(d->footNoteCursorFromPrevious); d->footNotesHeight += footNoteArea->bottom() - footNoteArea->top(); d->footNoteAreas.append(footNoteArea); } while (!cursor->it.atEnd()) { QTextBlock block = cursor->it.currentBlock(); QTextTable *table = qobject_cast(cursor->it.currentFrame()); QTextFrame *subFrame = cursor->it.currentFrame(); if (table) { QString masterPageName = table->frameFormat().property(KoTableStyle::MasterPageName).toString(); bool masterPageNameChanged = !masterPageName.isEmpty(); if (masterPageNameChanged) { cursor->masterPageName = masterPageName; } if (!virginPage()) { int breaktype = table->frameFormat().intProperty(KoTableStyle::BreakBefore); if ((acceptsPageBreak() && (masterPageNameChanged || (breaktype == KoText::PageBreak))) || (acceptsColumnBreak() && (breaktype == KoText::ColumnBreak))) { d->endOfArea = new FrameIterator(cursor); setBottom(d->y + d->footNotesHeight); if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } return false; } } // Let's create KoTextLayoutTableArea and let that handle the table KoTextLayoutTableArea *tableArea = new KoTextLayoutTableArea(table, this, d->documentLayout); d->tableAreas.append(tableArea); d->y += d->bottomSpacing; if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } tableArea->setVirginPage(virginPage()); tableArea->setReferenceRect(left(), right(), d->y, maximumAllowedBottom()); if (tableArea->layoutTable(cursor->tableIterator(table)) == false) { d->endOfArea = new FrameIterator(cursor); d->y = tableArea->bottom(); setBottom(d->y + d->footNotesHeight); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(tableArea->boundingRect().left()); expandBoundingRight(tableArea->boundingRect().right()); return false; } setVirginPage(false); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(tableArea->boundingRect().left()); expandBoundingRight(tableArea->boundingRect().right()); d->bottomSpacing = 0; d->y = tableArea->bottom(); delete cursor->currentTableIterator; cursor->currentTableIterator = 0; } else if (subFrame) { if (subFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { Q_ASSERT(d->endNotesArea == 0); d->endNotesArea = new KoTextLayoutEndNotesArea(this, d->documentLayout); d->y += d->bottomSpacing; if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } d->endNotesArea->setVirginPage(virginPage()); d->endNotesArea->setReferenceRect(left(), right(), d->y, maximumAllowedBottom()); if (d->endNotesArea->layout(cursor->subFrameIterator(subFrame)) == false) { d->endOfArea = new FrameIterator(cursor); d->y = d->endNotesArea->bottom(); setBottom(d->y + d->footNotesHeight); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(d->endNotesArea->boundingRect().left()); expandBoundingRight(d->endNotesArea->boundingRect().right()); return false; } setVirginPage(false); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(d->endNotesArea->boundingRect().left()); expandBoundingRight(d->endNotesArea->boundingRect().right()); d->bottomSpacing = 0; d->y = d->endNotesArea->bottom(); delete cursor->currentSubFrameIterator; cursor->currentSubFrameIterator = 0; // we have layouted till the end of the document except for a blank block // which we should ignore ++(cursor->it); ++(cursor->it); break; } } else if (block.isValid()) { if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) { QVariant data = block.blockFormat().property(KoParagraphStyle::GeneratedDocument); QTextDocument *generatedDocument = data.value(); // Let's create KoTextLayoutArea and let it handle the generated document KoTextLayoutArea *area = new KoTextLayoutArea(this, documentLayout()); d->generatedDocAreas.append(area); d->y += d->bottomSpacing; if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } area->setVirginPage(virginPage()); area->setAcceptsPageBreak(acceptsPageBreak()); area->setAcceptsColumnBreak(acceptsColumnBreak()); area->setReferenceRect(left(), right(), d->y, maximumAllowedBottom()); QTextLayout *blayout = block.layout(); blayout->beginLayout(); QTextLine line = blayout->createLine(); line.setNumColumns(0); line.setPosition(QPointF(left(), d->y)); blayout->endLayout(); if (area->layout(cursor->subFrameIterator(generatedDocument->rootFrame())) == false) { cursor->lineTextStart = 1; // fake we are not done d->endOfArea = new FrameIterator(cursor); d->y = area->bottom(); setBottom(d->y + d->footNotesHeight); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(area->boundingRect().left()); expandBoundingRight(area->boundingRect().right()); return false; } setVirginPage(false); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(area->boundingRect().left()); expandBoundingRight(area->boundingRect().right()); d->bottomSpacing = 0; d->y = area->bottom(); delete cursor->currentSubFrameIterator; cursor->lineTextStart = -1; // fake we are done cursor->currentSubFrameIterator = 0; } else { // FIXME this doesn't work for cells inside tables. We probably should make it more // generic to handle such cases too. QString masterPageName = block.blockFormat().property(KoParagraphStyle::MasterPageName).toString(); bool masterPageNameChanged = !masterPageName.isEmpty(); if (masterPageNameChanged) { cursor->masterPageName = masterPageName; } if (!virginPage()) { int breaktype = block.blockFormat().intProperty(KoParagraphStyle::BreakBefore); if ((acceptsPageBreak() && (masterPageNameChanged || (breaktype == KoText::PageBreak))) ||(acceptsColumnBreak() && (breaktype == KoText::ColumnBreak))) { d->endOfArea = new FrameIterator(cursor); setBottom(d->y + d->footNotesHeight); if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } return false; } } if (layoutBlock(cursor) == false) { if (cursor->lineTextStart == -1) { //Nothing was added so lets backtrack keep-with-next backtrackKeepWithNext(cursor); } d->endOfArea = new FrameIterator(cursor); setBottom(d->y + d->footNotesHeight); d->blockRects.last().setBottom(d->y); return false; } d->extraTextIndent = 0; int breaktype = block.blockFormat().intProperty(KoParagraphStyle::BreakAfter); if ((acceptsPageBreak() && (breaktype & KoText::PageBreak)) || (acceptsColumnBreak() && (breaktype & KoText::ColumnBreak))) { Q_ASSERT(!cursor->it.atEnd()); QTextFrame::iterator nextIt = cursor->it; ++nextIt; bool wasIncremented = !nextIt.currentFrame(); if (wasIncremented) cursor->it = nextIt; d->endOfArea = new FrameIterator(cursor); if (!wasIncremented) ++(cursor->it); setBottom(d->y + d->footNotesHeight); d->blockRects.last().setBottom(d->y); return false; } } } bool atEnd = cursor->it.atEnd(); if (!atEnd) { ++(cursor->it); } } d->endOfArea = new FrameIterator(cursor); d->y = qMin(maximumAllowedBottom(), d->y + d->bottomSpacing); setBottom(d->y + d->footNotesHeight); if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } if (d->maximumAllowedWidth>0) { d->right += d->neededWidth - d->width; d->maximumAllowedWidth = 0; setVirginPage(true); KoTextLayoutArea::layout(new FrameIterator(d->startOfArea)); } return true; // we have layouted till the end of the frame } QTextLine KoTextLayoutArea::Private::restartLayout(QTextBlock &block, int lineTextStartOfLastKeep) { QTextLayout *layout = block.layout(); KoTextBlockData blockData(block); QPointF stashedCounterPosition = blockData.counterPosition(); QList stashedLines; QTextLine line; for(int i = 0; i < layout->lineCount(); i++) { QTextLine l = layout->lineAt(i); if (l.textStart() >= lineTextStartOfLastKeep) { break; } LineKeeper lk; lk.lineWidth = l.width(); lk.columns = l.textLength(); lk.position = l.position(); stashedLines.append(lk); } layout->clearLayout(); layout->beginLayout(); line = layout->createLine(); return recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); } void KoTextLayoutArea::Private::stashRemainingLayout(QTextBlock &block, int lineTextStartOfFirstKeep, QList &stashedLines, QPointF &stashedCounterPosition) { QTextLayout *layout = block.layout(); KoTextBlockData blockData(block); stashedCounterPosition = blockData.counterPosition(); QTextLine line; for(int i = 0; i < layout->lineCount(); i++) { QTextLine l = layout->lineAt(i); if (l.textStart() < lineTextStartOfFirstKeep) { continue; } LineKeeper lk; lk.lineWidth = l.width(); lk.columns = l.textLength(); lk.position = l.position(); stashedLines.append(lk); } } QTextLine KoTextLayoutArea::Private::recreatePartialLayout(QTextBlock &block, QList stashedLines, QPointF &stashedCounterPosition, QTextLine &line) { QTextLayout *layout = block.layout(); KoTextBlockData blockData(block); documentLayout->allowPositionInlineObject(false); if (layout->lineCount() == 1) { blockData.setCounterPosition(stashedCounterPosition); } Q_FOREACH (const LineKeeper &lk, stashedLines) { line.setLineWidth(lk.lineWidth); if (lk.columns != line.textLength()) { // As setNumColumns might break differently we only use it if setLineWidth doesn't give // the same textLength as we had before line.setNumColumns(lk.columns, lk.lineWidth); } line.setPosition(lk.position); line = layout->createLine(); if (!line.isValid()) break; } documentLayout->allowPositionInlineObject(true); return line; } static bool compareTab(const QTextOption::Tab &tab1, const QTextOption::Tab &tab2) { return tab1.position < tab2.position; } // layoutBlock() method is structured like this: // // 1) Setup various helper values // a) related to or influenced by lists // b) related to or influenced by dropcaps // c) related to or influenced by margins // d) related to or influenced by tabs // e) related to or influenced by borders // f) related to or influenced by list counters // 2)layout each line (possibly restarting where we stopped earlier) // a) fit line into sub lines with as needed for text runaround // b) break if we encounter softbreak // c) make sure we keep above maximumAllowedBottom // d) calls addLine() // e) update dropcaps related variables bool KoTextLayoutArea::layoutBlock(FrameIterator *cursor) { QTextBlock block(cursor->it.currentBlock()); KoTextBlockData blockData(block); KoParagraphStyle pStyle(block.blockFormat(), block.charFormat()); int dropCapsAffectsNMoreLines = 0; qreal dropCapsPositionAdjust = 0.0; bool lastOfPreviousRun = (d->copyEndOfArea && d->copyEndOfArea->it.currentBlock() == block); KoText::Direction dir = pStyle.textProgressionDirection(); if (dir == KoText::InheritDirection) dir = parentTextDirection(); if (dir == KoText::AutoDirection) d->isRtl = block.text().isRightToLeft(); else d->isRtl = dir == KoText::RightLeftTopBottom; // initialize list item stuff for this parag. QTextList *textList = block.textList(); QTextListFormat listFormat; QTextCharFormat labelFormat; if (textList) { listFormat = textList->format(); if (block.text().size() == 0 || d->documentLayout->wordprocessingMode()) { labelFormat = block.charFormat(); } else { labelFormat = block.begin().fragment().charFormat(); } if (d->documentLayout->styleManager()) { const int id = listFormat.intProperty(KoListStyle::CharacterStyleId); KoCharacterStyle *cs = d->documentLayout->styleManager()->characterStyle(id); if (cs) { cs->applyStyle(labelFormat); cs->ensureMinimalProperties(labelFormat); } } // fetch the text-properties of the label if (listFormat.hasProperty(KoListStyle::CharacterProperties)) { QVariant v = listFormat.property(KoListStyle::CharacterProperties); QSharedPointer textPropertiesCharStyle = v.value< QSharedPointer >(); if (!textPropertiesCharStyle.isNull()) { textPropertiesCharStyle->applyStyle(labelFormat); textPropertiesCharStyle->ensureMinimalProperties(labelFormat); } } // Calculate the correct font point size taking into account the current // block format and the relative font size percent if the size is not absolute if (listFormat.hasProperty(KoListStyle::RelativeBulletSize)) { qreal percent = listFormat.property(KoListStyle::RelativeBulletSize).toDouble(); labelFormat.setFontPointSize((percent*labelFormat.fontPointSize())/100.00); } QFont font(labelFormat.font(), d->documentLayout->paintDevice()); if (!blockData.hasCounterData()) { ListItemsHelper lih(textList, font); lih.recalculateBlock(block); } blockData.setLabelFormat(labelFormat); } else { // make sure it is empty blockData.clearCounter(); } QTextLayout *layout = block.layout(); QTextOption option = layout->textOption(); option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); option.setAlignment(QStyle::visualAlignment(d->isRtl ? Qt::RightToLeft : Qt::LeftToRight, pStyle.alignment())); if (d->isRtl) { option.setTextDirection(Qt::RightToLeft); // For right-to-left we need to make sure that trailing spaces are included into the QTextLine naturalTextWidth // and naturalTextRect calculation so they are proper handled in the RunAroundHelper. For left-to-right we do // not like to include trailing spaces in the calculations cause else justified text would not look proper // justified. Seems for right-to-left we have to accept that justified text will not look proper justified then. // only set it for justified text as otherwise we will cut of text at the beginning of the line if (pStyle.alignment() == Qt::AlignJustify) { option.setFlags(QTextOption::IncludeTrailingSpaces); } } else { option.setFlags(0); option.setTextDirection(Qt::LeftToRight); } option.setUseDesignMetrics(true); //========== // Drop caps //========== d->dropCapsNChars = 0; if (cursor->lineTextStart == -1) { // first remove any drop-caps related formatting that's already there in the layout. // we'll do it all afresh now. QList formatRanges = layout->additionalFormats(); for (QList< QTextLayout::FormatRange >::Iterator iter = formatRanges.begin(); iter != formatRanges.end(); ) { if (iter->format.boolProperty(DropCapsAdditionalFormattingId)) { iter = formatRanges.erase(iter); } else { ++iter; } } if (formatRanges.count() != layout->additionalFormats().count()) layout->setAdditionalFormats(formatRanges); bool dropCaps = pStyle.dropCaps(); int dropCapsLength = pStyle.dropCapsLength(); int dropCapsLines = pStyle.dropCapsLines(); if (dropCaps && dropCapsLines > 1 && block.length() > 1) { QString blockText = block.text(); d->dropCapsDistance = pStyle.dropCapsDistance(); if (dropCapsLength == 0) { // means whole word is to be dropped int firstNonSpace = blockText.indexOf(QRegExp("[^ ]")); dropCapsLength = blockText.indexOf(QRegExp("\\W"), firstNonSpace); } else { // LibreOffice skips softbreaks but not spaces. We will do the same QTextCursor c1(block); c1.setPosition(block.position()); c1.setPosition(c1.position() + 1, QTextCursor::KeepAnchor); KoTextSoftPageBreak *softPageBreak = dynamic_cast(d->documentLayout->inlineTextObjectManager()->inlineTextObject(c1)); if (softPageBreak) { dropCapsLength++; } } dropCapsLength = qMin(dropCapsLength, blockText.length() - 1); if (dropCapsLength > 0) { // increase the size of the dropped chars QTextCursor blockStart(block); QTextLayout::FormatRange dropCapsFormatRange; dropCapsFormatRange.format = blockStart.charFormat(); // find out lineHeight for this block. QTextBlock::iterator it = block.begin(); QTextFragment lineRepresentative = it.fragment(); qreal lineHeight = pStyle.lineHeightAbsolute(); qreal dropCapsHeight = 0; if (lineHeight == 0) { lineHeight = lineRepresentative.charFormat().fontPointSize(); qreal linespacing = pStyle.lineSpacing(); if (linespacing == 0) { // unset qreal percent = pStyle.lineHeightPercent(); if (percent != 0) linespacing = lineHeight * ((percent - 100) / 100.0); else if (linespacing == 0) linespacing = lineHeight * 0.2; // default } dropCapsHeight = linespacing * (dropCapsLines-1); } const qreal minimum = pStyle.minimumLineHeight(); if (minimum > 0.0) { lineHeight = qMax(lineHeight, minimum); } dropCapsHeight += lineHeight * dropCapsLines; int dropCapsStyleId = pStyle.dropCapsTextStyleId(); KoCharacterStyle *dropCapsCharStyle = 0; if (dropCapsStyleId > 0 && d->documentLayout->styleManager()) { dropCapsCharStyle = d->documentLayout->styleManager()->characterStyle(dropCapsStyleId); dropCapsCharStyle->applyStyle(dropCapsFormatRange.format); } QFont f(dropCapsFormatRange.format.font(), d->documentLayout->paintDevice()); QString dropCapsText(block.text().left(dropCapsLength)); f.setPointSizeF(dropCapsHeight); for (int i=0; i < 5; ++i) { QTextLayout tmplayout(dropCapsText, f); tmplayout.setTextOption(option); tmplayout.beginLayout(); QTextLine tmpline = tmplayout.createLine(); tmplayout.endLayout(); d->dropCapsWidth = tmpline.naturalTextWidth(); QFontMetricsF fm(f, documentLayout()->paintDevice()); QRectF rect = fm.tightBoundingRect(dropCapsText); const qreal diff = dropCapsHeight - rect.height(); dropCapsPositionAdjust = rect.top() + fm.ascent(); if (qAbs(diff) < 0.5) // good enough break; const qreal adjustment = diff * (f.pointSizeF() / rect.height()); // warnTextLayout << "adjusting with" << adjustment; f.setPointSizeF(f.pointSizeF() + adjustment); } dropCapsFormatRange.format.setFontPointSize(f.pointSizeF()); dropCapsFormatRange.format.setProperty(DropCapsAdditionalFormattingId, (QVariant) true); dropCapsFormatRange.start = 0; dropCapsFormatRange.length = dropCapsLength; formatRanges.append(dropCapsFormatRange); layout->setAdditionalFormats(formatRanges); d->dropCapsNChars = dropCapsLength; dropCapsAffectsNMoreLines = (d->dropCapsNChars > 0) ? dropCapsLines : 0; } } } //======== // Margins //======== qreal startMargin = block.blockFormat().leftMargin(); qreal endMargin = block.blockFormat().rightMargin(); if (d->isRtl) { - qSwap(startMargin, endMargin); + std::swap(startMargin, endMargin); } d->indent = textIndent(block, textList, pStyle) + d->extraTextIndent; qreal labelBoxWidth = 0; qreal labelBoxIndent = 0; if (textList) { if (listFormat.boolProperty(KoListStyle::AlignmentMode)) { // according to odf 1.2 17.20 list margin should be used when paragraph margin is // not specified by the auto style (additionally LO/OO uses 0 as condition so we do too) int id = pStyle.styleId(); bool set = false; if (id && d->documentLayout->styleManager()) { KoParagraphStyle *originalParagraphStyle = d->documentLayout->styleManager()->paragraphStyle(id); if (originalParagraphStyle->leftMargin() != startMargin) { set = (startMargin != 0); } } else { set = (startMargin != 0); } if (! set) { startMargin = listFormat.doubleProperty(KoListStyle::Margin); } labelBoxWidth = blockData.counterWidth(); Qt::Alignment align = static_cast(listFormat.intProperty(KoListStyle::Alignment)); if (align == 0) { align = Qt::AlignLeft; } if (align & Qt::AlignLeft) { d->indent += labelBoxWidth; } else if (align & Qt::AlignHCenter) { d->indent += labelBoxWidth/2; } labelBoxIndent = d->indent - labelBoxWidth; } else { labelBoxWidth = blockData.counterSpacing() + blockData.counterWidth(); } } d->width = right() - left(); d->width -= startMargin + endMargin; d->x = left() + (d->isRtl ? 0.0 : startMargin); d->documentLayout->clearInlineObjectRegistry(block); //======== // Tabs //======== QList tabs = pStyle.tabPositions(); // Handle tabs relative to startMargin qreal tabOffset = -d->indent; if (!d->documentLayout->relativeTabs(block)) { tabOffset -= startMargin; } // Make a list of tabs that Qt can use QList qTabs; // Note: Converting to Qt tabs is needed as long as we use Qt for layout, but we // loose the possibility to do leader chars. foreach (const KoText::Tab &kTab, tabs) { qreal value = kTab.position; if (value == MaximumTabPos) { // MaximumTabPos is used in index generators // note: we subtract right margin as this is where the tab should be // note: we subtract indent so tab is not relative to it // note: we subtract left margin so tab is not relative to it // if rtl the above left/right reasons swap but formula stays the same // -tabOfset is just to cancel that we add it next // -2 is to avoid wrap at right edge to the next line value = right() - left() - startMargin - endMargin - d->indent - tabOffset - 2; } // conversion here is required because Qt thinks in device units and we don't value *= qt_defaultDpiY() / 72.0; value += tabOffset * qt_defaultDpiY() / 72.0; QTextOption::Tab tab; tab.position = value; tab.type = kTab.type; tab.delimiter = kTab.delimiter; qTabs.append(tab); } qreal presentationListTabValue(0.0); // for use in presentationListTabWorkaround // For some lists we need to add a special list tab according to odf 1.2 19.830 if (textList && listFormat.intProperty(KoListStyle::LabelFollowedBy) == KoListStyle::ListTab) { qreal listTab = 0; if (listFormat.hasProperty(KoListStyle::TabStopPosition)) { listTab = listFormat.doubleProperty(KoListStyle::TabStopPosition); if (!d->documentLayout->relativeTabs(block)) { // How list tab is defined if fixed tabs: // listTab //|>-------------------------| // d->indent // |---------<| // LABEL TEXT STARTS HERE AND GOES ON // TO THE NEXT LINE //|>------------------| // startMargin listTab -= startMargin; } else { // How list tab is defined if relative tabs: // It's relative to startMargin - list.startMargin // listTab // |>-------------------| // d->indent // |---------<| // LABEL TEXT STARTS HERE AND GOES ON // TO THE NEXT LINE //|>--------------------| // startMargin | // |>-------------| // list.margin listTab -= listFormat.doubleProperty(KoListStyle::Margin); } } // How list tab is defined now: // listTab // |>-----| // d->indent // |---------<| // LABEL TEXT STARTS HERE AND GOES ON // TO THE NEXT LINE //|>------------------| // startMargin presentationListTabValue = listTab; listTab -= d->indent; // And now listTab is like this: // x() // | listTab // |>---------------| // d->indent // |---------<| // LABEL TEXT STARTS HERE AND GOES ON // TO THE NEXT LINE //|>------------------| // startMargin // conversion here is required because Qt thinks in device units and we don't listTab *= qt_defaultDpiY() / 72.0; QTextOption::Tab tab; tab.position = listTab; tab.type = d->isRtl ? QTextOption::RightTab : QTextOption::LeftTab; qTabs.append(tab); } // We need to sort as the MaximumTabPos may be converted to a value that really // should be in the middle, and listtab needs to be sorted in too - qSort(qTabs.begin(), qTabs.end(), compareTab); + std::sort(qTabs.begin(), qTabs.end(), compareTab); // Regular interval tabs. Since Qt doesn't handle regular interval tabs offset // by a fixed number we need to create the regular tabs ourselves. qreal tabStopDistance = pStyle.tabStopDistance() * qt_defaultDpiY() / 72.0; if (tabStopDistance <= 0) { tabStopDistance = d->documentLayout->defaultTabSpacing() * qt_defaultDpiY() / 72.0; } qreal regularSpacedTabPos = -d->indent * qt_defaultDpiY() / 72.0 -0.1; // first possible position if (!qTabs.isEmpty()) { regularSpacedTabPos = qTabs.last().position; } regularSpacedTabPos -= tabOffset * qt_defaultDpiY() / 72.0; if (regularSpacedTabPos < 0) { regularSpacedTabPos = -int(-regularSpacedTabPos / tabStopDistance) * tabStopDistance; } else { regularSpacedTabPos = (int(regularSpacedTabPos / tabStopDistance) + 1) * tabStopDistance; } regularSpacedTabPos += tabOffset * qt_defaultDpiY() / 72.0; while (regularSpacedTabPos < MaximumTabPos) { QTextOption::Tab tab; tab.position = regularSpacedTabPos; qTabs.append(tab); regularSpacedTabPos += tabStopDistance; } option.setTabs(qTabs); // conversion here is required because Qt thinks in device units and we don't option.setTabStop(tabStopDistance * qt_defaultDpiY() / 72.); layout->setTextOption(option); // ============== // Possibly store the old layout of lines in case we end up splitting the paragraph at the same position // ============== QList stashedLines; QPointF stashedCounterPosition; if (lastOfPreviousRun) { // we have been layouted before, and the block ended on the following page so better // stash the layout for later d->stashRemainingLayout(block, d->copyEndOfArea->lineTextStart, stashedLines, stashedCounterPosition); } // ============== // Setup line and possibly restart paragraph continuing from previous other area // ============== QTextLine line; if (cursor->lineTextStart == -1) { layout->beginLayout(); line = layout->createLine(); cursor->fragmentIterator = block.begin(); } else { line = d->restartLayout(block, cursor->lineTextStart); d->indent = d->extraTextIndent; } if (block.blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) { // Unnumbered list items act like "following lines" in a numbered block d->indent = 0; } // ============== // List label/counter positioning // ============== if (textList && block.layout()->lineCount() == 1 && ! block.blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) { // If first line in a list then set the counterposition. Following lines in the same // list-item have nothing to do with the counter. if (listFormat.boolProperty(KoListStyle::AlignmentMode) == false) { qreal minLabelWidth = listFormat.doubleProperty(KoListStyle::MinimumWidth); if (!d->isRtl) { d->x += listFormat.doubleProperty(KoListStyle::Indent) + minLabelWidth; } d->width -= listFormat.doubleProperty(KoListStyle::Indent) + minLabelWidth; d->indent += labelBoxWidth - minLabelWidth; blockData.setCounterPosition(QPointF(d->x + d->indent - labelBoxWidth, d->y)); } else if (labelBoxWidth > 0.0 || blockData.counterText().length() > 0) { // Alignmentmode and there is a label (double check needed to account for both // picture bullets and non width chars) blockData.setCounterPosition(QPointF(d->x + labelBoxIndent, d->y)); if (listFormat.intProperty(KoListStyle::LabelFollowedBy) == KoListStyle::ListTab && !presentationListTabWorkaround(textIndent(block, textList, pStyle), labelBoxWidth, presentationListTabValue)) { Q_FOREACH (QTextOption::Tab tab, qTabs) { qreal position = tab.position * 72. / qt_defaultDpiY(); if (position > 0.0) { d->indent += position; break; } } //And finally it's like this: // x() // d->indent // |>-----| // LABEL TEXT STARTS HERE AND GOES ON // TO THE NEXT LINE //|>------------------| // startMargin } else if (listFormat.intProperty(KoListStyle::LabelFollowedBy) == KoListStyle::Space) { QFontMetrics fm(labelFormat.font(), d->documentLayout->paintDevice()); d->indent += fm.width(' '); } // default needs to be no space so presentationListTabWorkaround above makes us go here } } // Whenever we relayout the markup layout becomes invalid blockData.setMarkupsLayoutValidity(KoTextBlockData::Misspell, false); blockData.setMarkupsLayoutValidity(KoTextBlockData::Grammar, false); // ============== // Now once we know the physical context we can work on the borders of the paragraph // ============== if (block.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } d->y += d->bottomSpacing; d->bottomSpacing = 0; d->blockRects.append(QRectF(d->x, d->y, d->width, 10.0)); } else { handleBordersAndSpacing(blockData, &block); } // Expand bounding rect so if we have content outside we show it expandBoundingLeft(d->blockRects.last().x()); expandBoundingRight(d->blockRects.last().right()); // ============== // Create the lines of this paragraph // ============== RunAroundHelper runAroundHelper; runAroundHelper.setObstructions(documentLayout()->currentObstructions()); qreal maxLineHeight = 0; qreal y_justBelowDropCaps = 0; bool anyLineAdded = false; int numBaselineShifts = 0; while (line.isValid()) { runAroundHelper.setLine(this, line); runAroundHelper.setObstructions(documentLayout()->currentObstructions()); QRectF anchoringRect = d->blockRects.last(); anchoringRect.setTop(d->anchoringParagraphContentTop); documentLayout()->setAnchoringParagraphContentRect(anchoringRect); anchoringRect.setLeft(left()); anchoringRect.setWidth(right() - left()); anchoringRect.setTop(d->anchoringParagraphTop); documentLayout()->setAnchoringParagraphRect(anchoringRect); documentLayout()->setAnchoringLayoutEnvironmentRect(layoutEnvironmentRect()); runAroundHelper.fit( /* resetHorizontalPosition */ false, /* rightToLeft */ d->isRtl, QPointF(x(), d->y)); documentLayout()->positionAnchorTextRanges(block.position()+line.textStart(), line.textLength(), block.document()); qreal bottomOfText = line.y() + line.height(); bool softBreak = false; bool moreInMiddle = d->y > maximumAllowedBottom() - 150; if (acceptsPageBreak() && !pStyle.nonBreakableLines() && moreInMiddle) { int softBreakPos = -1; QString text = block.text(); int pos = text.indexOf(QChar::ObjectReplacementCharacter, line.textStart()); while (pos >= 0 && pos <= line.textStart() + line.textLength()) { QTextCursor c1(block); c1.setPosition(block.position() + pos); c1.setPosition(c1.position() + 1, QTextCursor::KeepAnchor); KoTextSoftPageBreak *softPageBreak = dynamic_cast(d->documentLayout->inlineTextObjectManager()->inlineTextObject(c1)); if (softPageBreak) { softBreakPos = pos; break; } pos = text.indexOf(QChar::ObjectReplacementCharacter, pos + 1); } if (softBreakPos >= 0 && softBreakPos < line.textStart() + line.textLength()) { line.setNumColumns(softBreakPos - line.textStart() + 1, line.width()); softBreak = true; // if the softBreakPos is at the start of the line stop here so // we don't add a line here. That fixes the problem that e.g. the counter is before // the page break and the text is after the page break if (!virginPage() && softBreakPos == 0) { d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); return false; } } } if (documentLayout()->anchoringSoftBreak() <= block.position() + line.textStart() + line.textLength()) { //don't add an anchor that has been moved away line.setNumColumns(documentLayout()->anchoringSoftBreak() - block.position() - line.textStart(), line.width()); softBreak = true; // if the softBreakPos is at the start of the block stop here so // we don't add a line here. That fixes the problem that e.g. the counter is before // the page break and the text is after the page break if (!virginPage() && documentLayout()->anchoringSoftBreak() == block.position()) { d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); return false; } } findFootNotes(block, line, bottomOfText); if (bottomOfText > maximumAllowedBottom()) { // We can not fit line within our allowed space // in case we resume layout on next page the line is reused later // but if not then we need to make sure the line becomes invisible // we use d->maximalAllowedBottom because we want to be below // footnotes too. if (!virginPage() && pStyle.nonBreakableLines()) { line.setPosition(QPointF(x(), d->maximalAllowedBottom)); cursor->lineTextStart = -1; d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); clearPreregisteredFootNotes(); return false; //to indicate block was not done! } if (!virginPage() && pStyle.orphanThreshold() != 0 && pStyle.orphanThreshold() > numBaselineShifts) { line.setPosition(QPointF(x(), d->maximalAllowedBottom)); cursor->lineTextStart = -1; d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); clearPreregisteredFootNotes(); return false; //to indicate block was not done! } if (!virginPage() || anyLineAdded) { line.setPosition(QPointF(x(), d->maximalAllowedBottom)); d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); clearPreregisteredFootNotes(); return false; //to indicate block was not done! } } confirmFootNotes(); anyLineAdded = true; maxLineHeight = qMax(maxLineHeight, addLine(line, cursor, blockData)); d->neededWidth = qMax(d->neededWidth, line.naturalTextWidth() + d->indent); if (!runAroundHelper.stayOnBaseline() && !(block.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable) && block.length() <= 1)) { d->y += maxLineHeight; maxLineHeight = 0; d->indent = 0; d->extraTextIndent = 0; ++numBaselineShifts; } // drop caps if (d->dropCapsNChars > 0) { // we just laid out the dropped chars y_justBelowDropCaps = d->y; // save the y position just below the dropped characters d->y = line.y(); // keep the same y for the next line line.setPosition(line.position() - QPointF(0, dropCapsPositionAdjust)); d->dropCapsNChars -= line.textLength(); } else if (dropCapsAffectsNMoreLines > 0) { // we just laid out a drop-cap-affected line dropCapsAffectsNMoreLines--; if (dropCapsAffectsNMoreLines == 0) { // no more drop-cap-affected lines if (d->y < y_justBelowDropCaps) d->y = y_justBelowDropCaps; // make sure d->y is below the dropped characters y_justBelowDropCaps = 0; d->dropCapsWidth = 0; d->dropCapsDistance = 0; } } documentLayout()->positionAnchoredObstructions(); // line fitted so try and do the next one line = layout->createLine(); if (!line.isValid()) { break; // no more line means our job is done } cursor->lineTextStart = line.textStart(); if (softBreak) { d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); return false; // page-break means we need to start again on the next page } } d->bottomSpacing = pStyle.bottomMargin(); layout->endLayout(); setVirginPage(false); cursor->lineTextStart = -1; //set lineTextStart to -1 and returning true indicate new block block.setLineCount(layout->lineCount()); return true; } bool KoTextLayoutArea::presentationListTabWorkaround(qreal indent, qreal labelBoxWidth, qreal presentationListTabValue) { if (!d->documentLayout->wordprocessingMode() && indent < 0.0) { // Impress / Powerpoint expects the label to be before the text if (indent + labelBoxWidth >= presentationListTabValue) { // but here is an unforseen overlap with normal text return true; } } return false; } qreal KoTextLayoutArea::textIndent(const QTextBlock &block, QTextList *textList, const KoParagraphStyle &pStyle) const { if (pStyle.autoTextIndent()) { // if auto-text-indent is set, // return an indent approximately 3-characters wide as per current font QTextCursor blockCursor(block); qreal guessGlyphWidth = QFontMetricsF(blockCursor.charFormat().font()).width('x'); return guessGlyphWidth * 3; } qreal blockTextIndent = block.blockFormat().textIndent(); if (textList && textList->format().boolProperty(KoListStyle::AlignmentMode)) { // according to odf 1.2 17.20 list text indent should be used when paragraph text indent is // not specified (additionally LO/OO uses 0 as condition so we do too) int id = pStyle.styleId(); bool set = false; if (id && d->documentLayout->styleManager()) { KoParagraphStyle *originalParagraphStyle = d->documentLayout->styleManager()->paragraphStyle(id); if (originalParagraphStyle->textIndent() != blockTextIndent) { set = (blockTextIndent != 0); } } else { set = (blockTextIndent != 0); } if (! set) { return textList->format().doubleProperty(KoListStyle::TextIndent); } } return blockTextIndent; } void KoTextLayoutArea::setExtraTextIndent(qreal extraTextIndent) { d->extraTextIndent = extraTextIndent; } qreal KoTextLayoutArea::x() const { if (d->isRtl) { return d->x; } else { if (d->dropCapsNChars > 0 || d->dropCapsWidth == 0) return d->x + d->indent ; else return d->x + d->indent + d->dropCapsWidth + d->dropCapsDistance; } } qreal KoTextLayoutArea::width() const { if (d->dropCapsNChars > 0) { return d->dropCapsWidth; } qreal width = d->width; if (d->maximumAllowedWidth > 0) { // lets use that instead but remember all the indent stuff we have calculated width = d->width - (d->right - d->left) + d->maximumAllowedWidth; } return width - d->indent - d->dropCapsWidth - d->dropCapsDistance; } void KoTextLayoutArea::setAcceptsPageBreak(bool accept) { d->acceptsPageBreak = accept; } bool KoTextLayoutArea::acceptsPageBreak() const { return d->acceptsPageBreak; } void KoTextLayoutArea::setAcceptsColumnBreak(bool accept) { d->acceptsColumnBreak = accept; } bool KoTextLayoutArea::acceptsColumnBreak() const { return d->acceptsColumnBreak; } void KoTextLayoutArea::setVirginPage(bool virgin) { d->virginPage = virgin; } bool KoTextLayoutArea::virginPage() const { return d->virginPage; } void KoTextLayoutArea::setVerticalAlignOffset(qreal offset) { d->boundingRect.setTop(d->top + qMin(qreal(0.0), offset)); d->boundingRect.setBottom(d->bottom + qMax(qreal(0.0), offset)); Q_ASSERT_X(d->boundingRect.top() <= d->boundingRect.bottom(), __FUNCTION__, "Bounding-rect is not normalized"); d->verticalAlignOffset = offset; } qreal KoTextLayoutArea::verticalAlignOffset() const { return d->verticalAlignOffset; } qreal KoTextLayoutArea::addLine(QTextLine &line, FrameIterator *cursor, KoTextBlockData &blockData) { QTextBlock block = cursor->it.currentBlock(); QTextBlockFormat format = block.blockFormat(); KoParagraphStyle style(format, block.charFormat()); if (block.textList() && block.layout()->lineCount() == 1) { Qt::Alignment alignment = format.alignment(); if (d->isRtl && (alignment & Qt::AlignAbsolute) == 0) { if (alignment & Qt::AlignLeft) { alignment = Qt::AlignRight; } else if (alignment & Qt::AlignRight) { alignment = Qt::AlignLeft; } } alignment &= Qt::AlignRight | Qt::AlignLeft | Qt::AlignHCenter; // First line, lets check where the line ended up and adjust the positioning of the counter. qreal newX; if (alignment & Qt::AlignHCenter) { const qreal padding = (line.width() - line.naturalTextWidth()) / 2; newX = blockData.counterPosition().x() + (d->isRtl ? -padding : padding); } else if (alignment & Qt::AlignRight) { const qreal padding = line.width() - line.naturalTextWidth(); newX = blockData.counterPosition().x() + (d->isRtl ? -padding : padding); } else { newX = blockData.counterPosition().x(); } if (d->isRtl) { newX = line.x() + line.naturalTextWidth() + line.x() + d->indent - newX; } blockData.setCounterPosition(QPointF(newX, blockData.counterPosition().y())); } qreal height = 0; qreal breakHeight = 0.0; qreal ascent = 0.0; qreal descent = 0.0; const bool useFontProperties = format.boolProperty(KoParagraphStyle::LineSpacingFromFont); if (cursor->fragmentIterator.atEnd()) {// no text in parag. qreal fontStretch = 1; QTextCharFormat charFormat = block.charFormat(); if (block.blockFormat().hasProperty(KoParagraphStyle::EndCharStyle)) { QVariant v = block.blockFormat().property(KoParagraphStyle::EndCharStyle); QSharedPointer endCharStyle = v.value< QSharedPointer >(); if (!endCharStyle.isNull()) { endCharStyle->applyStyle(charFormat); endCharStyle->ensureMinimalProperties(charFormat); } } if (useFontProperties) { //stretch line height to powerpoint size fontStretch = PresenterFontStretch; } else if (block.charFormat().hasProperty(KoCharacterStyle::FontYStretch)) { // stretch line height to ms-word size fontStretch = charFormat.property(KoCharacterStyle::FontYStretch).toDouble(); } height = charFormat.fontPointSize() * fontStretch; } else { qreal fontStretch = 1; QTextFragment fragment = cursor->fragmentIterator.fragment(); if (useFontProperties) { //stretch line height to powerpoint size fontStretch = PresenterFontStretch; } else if (fragment.charFormat().hasProperty(KoCharacterStyle::FontYStretch)) { // stretch line height to ms-word size fontStretch = fragment.charFormat().property(KoCharacterStyle::FontYStretch).toDouble(); } // read max font height height = qMax(height, fragment.charFormat().fontPointSize() * fontStretch); KoInlineObjectExtent pos = d->documentLayout->inlineObjectExtent(fragment); ascent = qMax(ascent, pos.m_ascent); descent = qMax(descent, pos.m_descent); bool lineBreak = false; int lastCharPos = block.position() + line.textStart() + line.textLength() - 1; int blockLastCharWithoutPreedit = line.textStart() + line.textLength() - 1; if (block.layout()->preeditAreaPosition() >= block.position() + line.textStart() && block.layout()->preeditAreaPosition() <= lastCharPos) { blockLastCharWithoutPreedit -= block.layout()->preeditAreaText().length(); } if (block.text().at(blockLastCharWithoutPreedit) == QChar(0x2028)) { // Was a line with line-break if (line.textLength() != 1) { //unless empty line we should ignore the format of it --lastCharPos; } lineBreak = true; } while (!(fragment.contains(lastCharPos))) { cursor->fragmentIterator++; if (cursor->fragmentIterator.atEnd()) { break; } fragment = cursor->fragmentIterator.fragment(); if (!d->documentLayout->changeTracker() || !d->documentLayout->changeTracker()->displayChanges() || !d->documentLayout->changeTracker()->containsInlineChanges(fragment.charFormat()) || !d->documentLayout->changeTracker()->elementById(fragment.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt()) || !d->documentLayout->changeTracker()->elementById(fragment.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt())->isEnabled() || (d->documentLayout->changeTracker()->elementById(fragment.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt())->getChangeType() != KoGenChange::DeleteChange) || d->documentLayout->changeTracker()->displayChanges()) { qreal fontStretch = 1; if (useFontProperties) { //stretch line height to powerpoint size fontStretch = PresenterFontStretch; } else if (fragment.charFormat().hasProperty(KoCharacterStyle::FontYStretch)) { // stretch line height to ms-word size fontStretch = fragment.charFormat().property(KoCharacterStyle::FontYStretch).toDouble(); } // read max font height height = qMax(height, fragment.charFormat().fontPointSize() * fontStretch); KoInlineObjectExtent pos = d->documentLayout->inlineObjectExtent(fragment); ascent = qMax(ascent, pos.m_ascent); descent = qMax(descent, pos.m_descent); } } if (lineBreak) { // Was a line with line-break - the format of the line-break should not be // considered for the next line either. So we may have to advance the fragmentIterator. while (!cursor->fragmentIterator.atEnd() && lastCharPos > fragment.position() + fragment.length()-1) { cursor->fragmentIterator++; fragment = cursor->fragmentIterator.fragment(); } qreal breakAscent = ascent; qreal breakDescent = descent; breakHeight = height; int firstPos = block.position() + line.textStart() + line.textLength(); // Was a line with line-break - the format of the line-break should not be // considered for the next line either. So we may have to advance the fragmentIterator. while (!cursor->fragmentIterator.atEnd() && firstPos > fragment.position() + fragment.length()-1) { cursor->fragmentIterator++; if (!cursor->fragmentIterator.atEnd()) { fragment = cursor->fragmentIterator.fragment(); // read max font height breakHeight = qMax(breakHeight, fragment.charFormat().fontPointSize() * fontStretch); KoInlineObjectExtent pos = d->documentLayout->inlineObjectExtent(fragment); breakAscent = qMax(breakAscent, pos.m_ascent); breakDescent = qMax(breakDescent, pos.m_descent); } } breakHeight = qMax(breakHeight, breakAscent + breakDescent); } } height = qMax(height, ascent + descent); if (height < 0.01) { height = 12; // default size for uninitialized styles. } // Calculate adjustment to the height due to line height calculated by qt which shouldn't be // there in reality. We will just move the line qreal lineAdjust = 0.0; if (breakHeight > height) { lineAdjust = height - breakHeight; } // Adjust the line-height according to a probably defined fixed line height, // a proportional (percent) line-height and/or the line-spacing. Together // with the line-height we maybe also need to adjust the position of the // line. This is for example needed if the line needs to shrink in height // so the line-text stays on the baseline. If the line grows in height then // we don't need to do anything. if (d->dropCapsNChars <= 0) { // linespacing rules doesn't apply to drop caps qreal fixedLineHeight = format.doubleProperty(KoParagraphStyle::FixedLineHeight); if (fixedLineHeight != 0.0) { qreal prevHeight = height; height = fixedLineHeight; lineAdjust += height - prevHeight; } else { qreal lineSpacing = format.doubleProperty(KoParagraphStyle::LineSpacing); if (lineSpacing == 0.0) { // unset qreal percent = format.doubleProperty(KoParagraphStyle::PercentLineHeight); if (percent != 0) { height *= percent / 100.0; } else { height *= 1.2; // default } } height += lineSpacing; } qreal minimum = style.minimumLineHeight(); if (minimum > 0.0) { height = qMax(height, minimum); } } else { // for drop caps we just work with a basic linespacing for the dropped characters height *= 1.2; } //rounding problems due to Qt-scribe internally using ints. //also used when line was moved down because of intersections with other shapes if (qAbs(d->y - line.y()) >= 0.126) { d->y = line.y(); } if (lineAdjust) { // Adjust the position of the line itself. line.setPosition(QPointF(line.x(), line.y() + lineAdjust)); // Adjust the position of the block-rect for this line which is used later // to proper clip the line while drawing. If we would not adjust it here // then we could end with text-lines being partly cutoff. if (lineAdjust < 0.0) { d->blockRects.last().moveTop(d->blockRects.last().top() + lineAdjust); } if (block.textList() && block.layout()->lineCount() == 1) { // If this is the first line in a list (aka the first line after the list- // item) then we also need to adjust the counter to match to the line again. blockData.setCounterPosition(QPointF(blockData.counterPosition().x(), blockData.counterPosition().y() + lineAdjust)); } } return height; } void KoTextLayoutArea::setLayoutEnvironmentResctictions(bool isLayoutEnvironment, bool actsHorizontally) { d->isLayoutEnvironment = isLayoutEnvironment; d->actsHorizontally = actsHorizontally; } QRectF KoTextLayoutArea::layoutEnvironmentRect() const { QRectF rect(-5e10, -5e10, 10e10, 10e20); // large values that never really restrict anything if (d->parent) { rect = d->parent->layoutEnvironmentRect(); } if (d->isLayoutEnvironment) { if (d->actsHorizontally) { rect.setLeft(left()); rect.setRight(right()); } rect.setTop(top()); rect.setBottom(maximumAllowedBottom()); } return rect; } QRectF KoTextLayoutArea::boundingRect() const { return d->boundingRect; } qreal KoTextLayoutArea::maximumAllowedBottom() const { return d->maximalAllowedBottom - d->footNotesHeight - d->preregisteredFootNotesHeight; } FrameIterator *KoTextLayoutArea::footNoteCursorToNext() const { return d->footNoteCursorToNext; } KoInlineNote *KoTextLayoutArea::continuedNoteToNext() const { return d->continuedNoteToNext; } int KoTextLayoutArea::footNoteAutoCount() const { return d->footNoteAutoCount; } void KoTextLayoutArea::setFootNoteCountInDoc(int count) { d->footNoteCountInDoc = count; } void KoTextLayoutArea::setFootNoteFromPrevious(FrameIterator *footNoteCursor, KoInlineNote *note) { d->footNoteCursorFromPrevious = footNoteCursor; d->continuedNoteFromPrevious = note; } void KoTextLayoutArea::setNoWrap(qreal maximumAllowedWidth) { d->maximumAllowedWidth = maximumAllowedWidth; } KoText::Direction KoTextLayoutArea::parentTextDirection() const { Q_ASSERT(d->parent); //Root areas should overload this method return d->parent->parentTextDirection(); } KoTextLayoutArea *KoTextLayoutArea::parent() const { return d->parent; } KoTextDocumentLayout *KoTextLayoutArea::documentLayout() const { return d->documentLayout; } void KoTextLayoutArea::setReferenceRect(qreal left, qreal right, qreal top, qreal maximumAllowedBottom) { d->left = left; d->right = right; d->top = top; d->boundingRect = QRectF(left, top, right - left, 0.0); Q_ASSERT_X(d->boundingRect.top() <= d->boundingRect.bottom() && d->boundingRect.left() <= d->boundingRect.right(), __FUNCTION__, "Bounding-rect is not normalized"); d->maximalAllowedBottom = maximumAllowedBottom; } QRectF KoTextLayoutArea::referenceRect() const { return QRectF(d->left, d->top, d->right - d->left, d->bottom - d->top); } qreal KoTextLayoutArea::left() const { return d->left; } qreal KoTextLayoutArea::right() const { return d->right; } qreal KoTextLayoutArea::top() const { return d->top; } qreal KoTextLayoutArea::bottom() const { return d->bottom; } void KoTextLayoutArea::setBottom(qreal bottom) { d->boundingRect.setBottom(bottom + qMax(qreal(0.0), d->verticalAlignOffset)); Q_ASSERT_X(d->boundingRect.top() <= d->boundingRect.bottom(), __FUNCTION__, "Bounding-rect is not normalized"); d->bottom = bottom; } void KoTextLayoutArea::findFootNotes(const QTextBlock &block, const QTextLine &line, qreal bottomOfText) { if (d->documentLayout->inlineTextObjectManager() == 0) { return; } QString text = block.text(); int pos = text.indexOf(QChar::ObjectReplacementCharacter, line.textStart()); while (pos >= 0 && pos <= line.textStart() + line.textLength()) { QTextCursor c1(block); c1.setPosition(block.position() + pos); c1.setPosition(c1.position() + 1, QTextCursor::KeepAnchor); KoInlineNote *note = dynamic_cast(d->documentLayout->inlineTextObjectManager()->inlineTextObject(c1)); if (note && note->type() == KoInlineNote::Footnote) { preregisterFootNote(note, bottomOfText); } pos = text.indexOf(QChar::ObjectReplacementCharacter, pos + 1); } } qreal KoTextLayoutArea::preregisterFootNote(KoInlineNote *note, qreal bottomOfText) { if (d->parent == 0) { // TODO to support footnotes at end of document this is // where we need to add some extra condition if (note->autoNumbering()) { KoOdfNotesConfiguration *notesConfig = d->documentLayout->styleManager()->notesConfiguration(KoOdfNotesConfiguration::Footnote); if (notesConfig->numberingScheme() == KoOdfNotesConfiguration::BeginAtDocument) { note->setAutoNumber(d->footNoteCountInDoc + (d->footNoteAutoCount++)); } else if (notesConfig->numberingScheme() == KoOdfNotesConfiguration::BeginAtPage) { note->setAutoNumber(d->footNoteAutoCount++); } } if (maximumAllowedBottom() - bottomOfText > 0) { QTextFrame *subFrame = note->textFrame(); d->footNoteCursorToNext = new FrameIterator(subFrame); KoTextLayoutNoteArea *footNoteArea = new KoTextLayoutNoteArea(note, this, d->documentLayout); d->preregisteredFootNoteFrames.append(subFrame); footNoteArea->setReferenceRect(left(), right(), 0, maximumAllowedBottom() - bottomOfText); bool contNotNeeded = footNoteArea->layout(d->footNoteCursorToNext); if (contNotNeeded) { delete d->footNoteCursorToNext; d->footNoteCursorToNext = 0; d->continuedNoteToNext = 0; } else { d->continuedNoteToNext = note; //layout again now it has set up a continuationObstruction delete d->footNoteCursorToNext; d->footNoteCursorToNext = new FrameIterator(subFrame); footNoteArea->setReferenceRect(left(), right(), 0, maximumAllowedBottom() - bottomOfText); footNoteArea->layout(d->footNoteCursorToNext); documentLayout()->setContinuationObstruction(0); // remove it again } d->preregisteredFootNotesHeight += footNoteArea->bottom() - footNoteArea->top(); d->preregisteredFootNoteAreas.append(footNoteArea); return footNoteArea->bottom() - footNoteArea->top(); } return 0.0; } qreal h = d->parent->preregisterFootNote(note, bottomOfText); d->preregisteredFootNotesHeight += h; return h; } void KoTextLayoutArea::confirmFootNotes() { d->footNotesHeight += d->preregisteredFootNotesHeight; d->footNoteAreas.append(d->preregisteredFootNoteAreas); d->footNoteFrames.append(d->preregisteredFootNoteFrames); d->preregisteredFootNotesHeight = 0; d->preregisteredFootNoteAreas.clear(); d->preregisteredFootNoteFrames.clear(); if (d->parent) { d->parent->confirmFootNotes(); } } void KoTextLayoutArea::expandBoundingLeft(qreal x) { d->boundingRect.setLeft(qMin(x, d->boundingRect.x())); } void KoTextLayoutArea::expandBoundingRight(qreal x) { d->boundingRect.setRight(qMax(x, d->boundingRect.right())); } void KoTextLayoutArea::clearPreregisteredFootNotes() { d->preregisteredFootNotesHeight = 0; d->preregisteredFootNoteAreas.clear(); d->preregisteredFootNoteFrames.clear(); if (d->parent) { d->parent->clearPreregisteredFootNotes(); } } void KoTextLayoutArea::handleBordersAndSpacing(KoTextBlockData &blockData, QTextBlock *block) { QTextBlockFormat format = block->blockFormat(); KoParagraphStyle formatStyle(format, block->charFormat()); // The AddParaTableSpacingAtStart config-item is used to be able to optionally prevent that // defined fo:margin-top are applied to the first paragraph. If true then the fo:margin-top // is applied to all except the first paragraph. If false fo:margin-top is applied to all // paragraphs. bool paraTableSpacingAtStart = KoTextDocument(d->documentLayout->document()).paraTableSpacingAtStart(); bool paddingExpandsBorders = false;//KoTextDocument(d->documentLayout->document()).paddingExpandsBorders(); qreal topMargin = 0; if (paraTableSpacingAtStart || block->previous().isValid()) { topMargin = formatStyle.topMargin(); } qreal spacing = qMax(d->bottomSpacing, topMargin); qreal dx = 0.0; qreal x = d->x; qreal width = d->width; if (d->indent < 0) { x += d->indent; width -= d->indent; } if (blockData.hasCounterData() && blockData.counterPosition().x() < x) { width += x - blockData.counterPosition().x(); x = blockData.counterPosition().x(); } KoTextBlockBorderData border(QRectF(x, d->y, width, 1)); border.setEdge(border.Left, format, KoParagraphStyle::LeftBorderStyle, KoParagraphStyle::LeftBorderWidth, KoParagraphStyle::LeftBorderColor, KoParagraphStyle::LeftBorderSpacing, KoParagraphStyle::LeftInnerBorderWidth); border.setEdge(border.Right, format, KoParagraphStyle::RightBorderStyle, KoParagraphStyle::RightBorderWidth, KoParagraphStyle::RightBorderColor, KoParagraphStyle::RightBorderSpacing, KoParagraphStyle::RightInnerBorderWidth); border.setEdge(border.Top, format, KoParagraphStyle::TopBorderStyle, KoParagraphStyle::TopBorderWidth, KoParagraphStyle::TopBorderColor, KoParagraphStyle::TopBorderSpacing, KoParagraphStyle::TopInnerBorderWidth); border.setEdge(border.Bottom, format, KoParagraphStyle::BottomBorderStyle, KoParagraphStyle::BottomBorderWidth, KoParagraphStyle::BottomBorderColor, KoParagraphStyle::BottomBorderSpacing, KoParagraphStyle::BottomInnerBorderWidth); border.setMergeWithNext(formatStyle.joinBorder()); if (border.hasBorders()) { // check if we can merge with the previous parags border. if (d->prevBorder && d->prevBorder->equals(border)) { blockData.setBorder(d->prevBorder); // Merged mean we don't have inserts inbetween the blocks d->anchoringParagraphTop = d->y; if (d->bottomSpacing + topMargin) { d->anchoringParagraphTop += spacing * d->bottomSpacing / (d->bottomSpacing + topMargin); } if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->anchoringParagraphTop); } d->anchoringParagraphTop = d->y; d->y += spacing; d->blockRects.append(QRectF(x, d->anchoringParagraphTop, width, 1.0)); } else { // can't merge; then these are our new borders. KoTextBlockBorderData *newBorder = new KoTextBlockBorderData(border); blockData.setBorder(newBorder); if (d->prevBorder) { d->y += d->prevBorderPadding; d->y += d->prevBorder->inset(KoTextBlockBorderData::Bottom); } if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } d->anchoringParagraphTop = d->y; if (d->bottomSpacing + topMargin) { d->anchoringParagraphTop += spacing * d->bottomSpacing / (d->bottomSpacing + topMargin); } d->y += spacing; if (paddingExpandsBorders) { d->blockRects.append(QRectF(x - format.doubleProperty(KoParagraphStyle::LeftPadding), d->y, width + format.doubleProperty(KoParagraphStyle::LeftPadding) + format.doubleProperty(KoParagraphStyle::RightPadding), 1.0)); } else { d->blockRects.append(QRectF(x, d->y, width, 1.0)); } d->y += newBorder->inset(KoTextBlockBorderData::Top); d->y += format.doubleProperty(KoParagraphStyle::TopPadding); } // finally, horizontal components of the borders dx = border.inset(KoTextBlockBorderData::Left); d->x += dx; d->width -= border.inset(KoTextBlockBorderData::Left); d->width -= border.inset(KoTextBlockBorderData::Right); } else { // this parag has no border. if (d->prevBorder) { d->y += d->prevBorderPadding; d->y += d->prevBorder->inset(KoTextBlockBorderData::Bottom); } blockData.setBorder(0); // remove an old one, if there was one. if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } d->anchoringParagraphTop = d->y; if (d->bottomSpacing + topMargin) { d->anchoringParagraphTop += spacing * d->bottomSpacing / (d->bottomSpacing + topMargin); } d->y += spacing; d->blockRects.append(QRectF(x, d->y, width, 1.0)); } if (!paddingExpandsBorders) { // add padding inside the border dx += format.doubleProperty(KoParagraphStyle::LeftPadding); d->x += format.doubleProperty(KoParagraphStyle::LeftPadding); d->width -= format.doubleProperty(KoParagraphStyle::LeftPadding); d->width -= format.doubleProperty(KoParagraphStyle::RightPadding); } if (block->layout()->lineCount() == 1 && blockData.hasCounterData()) { blockData.setCounterPosition(QPointF(blockData.counterPosition().x() + dx, d->y)); } d->prevBorder = blockData.border(); d->prevBorderPadding = format.doubleProperty(KoParagraphStyle::BottomPadding); d->anchoringParagraphContentTop = d->y; } diff --git a/plugins/flake/textshape/textlayout/KoTextLayoutArea_paint.cpp b/plugins/flake/textshape/textlayout/KoTextLayoutArea_paint.cpp index dbe749e633..2a9b787cab 100644 --- a/plugins/flake/textshape/textlayout/KoTextLayoutArea_paint.cpp +++ b/plugins/flake/textshape/textlayout/KoTextLayoutArea_paint.cpp @@ -1,1200 +1,1200 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2008 Roopesh Chander * Copyright (C) 2007-2008 Pierre Ducroquet * Copyright (C) 2009-2011 KO GmbH * Copyright (C) 2009-2012 C. Boemann * Copyright (C) 2010 Nandita Suri * Copyright (C) 2010 Ajay Pundhir * Copyright (C) 2011 Lukáš Tvrdý * Copyright (C) 2011 Gopalakrishna Bhat A * Copyright (C) 2011 Stuart Dickson * Copyright (C) 2014 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoTextLayoutArea.h" #include "KoTextLayoutEndNotesArea.h" #include "KoTextLayoutTableArea.h" #include "KoTextLayoutNoteArea.h" #include "TableIterator.h" #include "ListItemsHelper.h" #include "RunAroundHelper.h" #include "KoTextDocumentLayout.h" #include "FrameIterator.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 "kis_painting_tweaks.h" extern int qt_defaultDpiY(); Q_DECLARE_METATYPE(QTextDocument *) #define DropCapsAdditionalFormattingId 25602902 #include "KoTextLayoutArea_p.h" void KoTextLayoutArea::paint(QPainter *painter, const KoTextDocumentLayout::PaintContext &context) { if (d->startOfArea == 0 || d->endOfArea == 0) // We have not been layouted yet return; /* struct Timer { QTime d->time; Timer() { d->time.start(); } ~Timer() { warnTextLayout << "elapsed=" << d->time.elapsed(); } }; Timer timer; */ painter->save(); painter->translate(0, d->verticalAlignOffset); painter->setPen(context.textContext.palette.color(QPalette::Text)); // for text that has no color. const QRegion clipRegion = KisPaintingTweaks::safeClipRegion(*painter); // fetch after painter->translate so the clipRegion is correct KoTextBlockBorderData *lastBorder = 0; QRectF lastBorderRect; QTextFrame::iterator it = d->startOfArea->it; QTextFrame::iterator stop = d->endOfArea->it; if (!stop.atEnd()) { if (!stop.currentBlock().isValid() || d->endOfArea->lineTextStart >= 0) { // Last thing we show is a frame (table) or first part of a paragraph split in two // The stop point should be the object after that // However if stop is already atEnd we shouldn't increment further ++stop; } } int tableAreaIndex = 0; int blockIndex = 0; int tocIndex = 0; for (; it != stop && !it.atEnd(); ++it) { QTextBlock block = it.currentBlock(); QTextTable *table = qobject_cast(it.currentFrame()); QTextFrame *subFrame = it.currentFrame(); QTextBlockFormat format = block.blockFormat(); if (!block.isValid()) { if (lastBorder) { // draw previous block's border lastBorder->paint(*painter, lastBorderRect); lastBorder = 0; } } if (table) { if (tableAreaIndex >= d->tableAreas.size()) { continue; } d->tableAreas[tableAreaIndex]->paint(painter, context); ++tableAreaIndex; continue; } else if (subFrame) { if (subFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { d->endNotesArea->paint(painter, context); } continue; } else { if (!block.isValid()) { continue; } } if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) { // Possibly paint the selection of the entire Table of Contents // but since it's a secondary document we need to create a fake selection QVariant data = block.blockFormat().property(KoParagraphStyle::GeneratedDocument); QTextDocument *generatedDocument = data.value(); KoTextDocumentLayout::PaintContext tocContext = context; tocContext.textContext.selections = QVector(); bool pure = true; Q_FOREACH (const QAbstractTextDocumentLayout::Selection &selection, context.textContext.selections) { if (selection.cursor.selectionStart() <= block.position() && selection.cursor.selectionEnd() >= block.position()) { painter->fillRect(d->generatedDocAreas[tocIndex]->boundingRect(), selection.format.background()); if (pure) { tocContext.textContext.selections.append(QAbstractTextDocumentLayout::Selection()); tocContext.textContext.selections[0].cursor = QTextCursor(generatedDocument); tocContext.textContext.selections[0].cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); tocContext.textContext.selections[0].format = selection.format; pure = false; } } } d->generatedDocAreas[tocIndex]->paint(painter, tocContext); ++tocIndex; continue; } QTextLayout *layout = block.layout(); KoTextBlockBorderData *border = 0; if (blockIndex >= d->blockRects.count()) break; QRectF br = d->blockRects[blockIndex]; ++blockIndex; if (!painter->hasClipping() || clipRegion.intersects(br.toRect())) { KoTextBlockData blockData(block); border = blockData.border(); KoTextBlockPaintStrategyBase *paintStrategy = blockData.paintStrategy(); KoTextBlockPaintStrategyBase dummyPaintStrategy; if (paintStrategy == 0) { paintStrategy = &dummyPaintStrategy; } if (!paintStrategy->isVisible()) { if (lastBorder) { // draw previous block's border lastBorder->paint(*painter, lastBorderRect); lastBorder = 0; } continue; // this paragraph shouldn't be shown so just skip it } // Check and update border drawing code if (lastBorder == 0) { lastBorderRect = br; } else if (lastBorder != border || lastBorderRect.width() != br.width() || lastBorderRect.x() != br.x()) { lastBorder->paint(*painter, lastBorderRect); lastBorderRect = br; } else { lastBorderRect = lastBorderRect.united(br); } lastBorder = border; painter->save(); QBrush bg = paintStrategy->background(block.blockFormat().background()); if (bg != Qt::NoBrush ) { painter->fillRect(br, bg); } else { bg = context.background; } paintStrategy->applyStrategy(painter); painter->save(); drawListItem(painter, block); painter->restore(); QVector selections; if (context.showSelections) { Q_FOREACH (const QAbstractTextDocumentLayout::Selection & selection, context.textContext.selections) { QTextCursor cursor = selection.cursor; int begin = cursor.position(); int end = cursor.anchor(); if (begin > end) - qSwap(begin, end); + std::swap(begin, end); if (end < block.position() || begin > block.position() + block.length()) continue; // selection does not intersect this block. if (selection.cursor.hasComplexSelection()) { continue; // selections of several table cells are covered by the within drawBorders above. } if (d->documentLayout->changeTracker() && !d->documentLayout->changeTracker()->displayChanges() && d->documentLayout->changeTracker()->containsInlineChanges(selection.format) && d->documentLayout->changeTracker()->elementById(selection.format.property(KoCharacterStyle::ChangeTrackerId).toInt())->isEnabled() && d->documentLayout->changeTracker()->elementById(selection.format.property(KoCharacterStyle::ChangeTrackerId).toInt())->getChangeType() == KoGenChange::DeleteChange) { continue; // Deletions should not be shown. } QTextLayout::FormatRange fr; fr.start = begin - block.position(); fr.length = end - begin; fr.format = selection.format; selections.append(fr); } } // this is a workaround to fix text getting cut of when format ranges are used. There // is a bug in Qt that can hit when text lines overlap each other. In case a format range // is used for formating it can clip the lines above/below as Qt creates a clip rect for // the places it already painted for the format range which results in clippling. So use // the format range always to paint the text. QVector workaroundFormatRanges; for (QTextBlock::iterator it = block.begin(); !(it.atEnd()); ++it) { QTextFragment currentFragment = it.fragment(); if (currentFragment.isValid()) { bool formatChanged = false; QTextCharFormat format = currentFragment.charFormat(); int changeId = format.intProperty(KoCharacterStyle::ChangeTrackerId); if (changeId && d->documentLayout->changeTracker() && d->documentLayout->changeTracker()->displayChanges()) { KoChangeTrackerElement *changeElement = d->documentLayout->changeTracker()->elementById(changeId); switch(changeElement->getChangeType()) { case (KoGenChange::InsertChange): format.setBackground(QBrush(d->documentLayout->changeTracker()->getInsertionBgColor())); break; case (KoGenChange::FormatChange): format.setBackground(QBrush(d->documentLayout->changeTracker()->getFormatChangeBgColor())); break; case (KoGenChange::DeleteChange): format.setBackground(QBrush(d->documentLayout->changeTracker()->getDeletionBgColor())); break; case (KoGenChange::UNKNOWN): break; } formatChanged = true; } if (format.isAnchor()) { if (!format.hasProperty(KoCharacterStyle::UnderlineStyle)) format.setFontUnderline(true); if (!format.hasProperty(QTextFormat::ForegroundBrush)) format.setForeground(Qt::blue); formatChanged = true; } if (format.boolProperty(KoCharacterStyle::UseWindowFontColor)) { QBrush backbrush = bg; if (format.background() != Qt::NoBrush) { backbrush = format.background(); } QBrush frontBrush; frontBrush.setStyle(Qt::SolidPattern); // use the same luma calculation and threshold as msoffice // see http://social.msdn.microsoft.com/Forums/en-US/os_binaryfile/thread/a02a9a24-efb6-4ba0-a187-0e3d2704882b int luma = ((5036060/2) * backbrush.color().red() + (9886846/2) * backbrush.color().green() + (1920103/2) * backbrush.color().blue()) >> 23; if (luma > 60) { frontBrush.setColor(QColor(Qt::black)); } else { frontBrush.setColor(QColor(Qt::white)); } format.setForeground(frontBrush); formatChanged = true; } if (formatChanged) { QTextLayout::FormatRange fr; fr.start = currentFragment.position() - block.position(); fr.length = currentFragment.length(); if (!format.hasProperty(KoCharacterStyle::InlineInstanceId)) { if (format.background().style() == Qt::NoBrush) { format.setBackground(QBrush(QColor(0, 0, 0, 0))); } if (format.foreground().style() == Qt::NoBrush) { format.setForeground(QBrush(QColor(0, 0, 0))); } } fr.format = format; // the prepend is done so the selections are at the end. selections.prepend(fr); } else { if (!format.hasProperty(KoCharacterStyle::InlineInstanceId)) { QTextLayout::FormatRange fr; fr.start = currentFragment.position() - block.position(); fr.length = currentFragment.length(); QTextCharFormat f; if (format.background().style() == Qt::NoBrush) { f.setBackground(QBrush(QColor(0, 0, 0, 0))); } else { f.setBackground(format.background()); } if (format.foreground().style() == Qt::NoBrush) { f.setForeground(QBrush(QColor(0, 0, 0))); } else { f.setForeground(format.foreground()); } fr.format = f; workaroundFormatRanges.append(fr); } } } } if (!selections.isEmpty()) { selections = workaroundFormatRanges + selections; } //We set clip because layout-draw doesn't clip text to it correctly after all //and adjust to make sure we don't clip edges of glyphs. The clipping is //important for paragraph split across two pages. //20pt enlargement seems safe as pages is split by 50pt and this helps unwanted //glyph cutting painter->setClipRect(br.adjusted(-20,-20,20,20), Qt::IntersectClip); layout->draw(painter, QPointF(0, 0), selections); if (context.showSectionBounds) { decorateParagraphSections(painter, block); } decorateParagraph(painter, block, context.showFormattingCharacters, context.showSpellChecking); painter->restore(); } else { if (lastBorder) { lastBorder->paint(*painter, lastBorderRect); lastBorder = 0; } } } if (lastBorder) { lastBorder->paint(*painter, lastBorderRect); } painter->translate(0, -d->verticalAlignOffset); painter->translate(0, bottom() - d->footNotesHeight); Q_FOREACH (KoTextLayoutNoteArea *footerArea, d->footNoteAreas) { footerArea->paint(painter, context); painter->translate(0, footerArea->bottom() - footerArea->top()); } painter->restore(); } void KoTextLayoutArea::drawListItem(QPainter *painter, QTextBlock &block) { KoTextBlockData blockData(block); QTextList *list = block.textList(); if (list && blockData.hasCounterData()) { QTextListFormat listFormat = list->format(); if (! blockData.counterText().isEmpty()) { QFont font(blockData.labelFormat().font(), d->documentLayout->paintDevice()); KoListStyle::Style listStyle = static_cast(listFormat.style()); QString result = blockData.counterText(); QTextLayout layout(result, font, d->documentLayout->paintDevice()); QList layouts; QTextLayout::FormatRange format; format.start = 0; format.length = blockData.counterText().length(); format.format = blockData.labelFormat(); layouts.append(format); layout.setAdditionalFormats(layouts); Qt::Alignment alignment = static_cast(listFormat.intProperty(KoListStyle::Alignment)); if (alignment == 0) { alignment = Qt::AlignLeft | Qt::AlignAbsolute; } if (d->isRtl && (alignment & Qt::AlignAbsolute) == 0) { if (alignment & Qt::AlignLeft) { alignment = Qt::AlignRight; } else if (alignment & Qt::AlignRight) { alignment = Qt::AlignLeft; } } alignment |= Qt::AlignAbsolute; QTextOption option(alignment); option.setTextDirection(block.layout()->textOption().textDirection()); /* if (option.textDirection() == Qt::RightToLeft || blockData.counterText().isRightToLeft()) { option.setAlignment(Qt::AlignRight); } */ layout.setTextOption(option); layout.beginLayout(); QTextLine line = layout.createLine(); line.setLineWidth(blockData.counterWidth()); layout.endLayout(); QPointF counterPosition = blockData.counterPosition(); if (block.layout()->lineCount() > 0) { // if there is text, then baseline align the counter. QTextLine firstParagLine = block.layout()->lineAt(0); if (KoListStyle::isNumberingStyle(listStyle)) { //if numbered list baseline align counterPosition += QPointF(0, firstParagLine.ascent() - layout.lineAt(0).ascent()); } else { //for unnumbered list center align counterPosition += QPointF(0, (firstParagLine.height() - layout.lineAt(0).height())/2.0); } } layout.draw(painter, counterPosition); //decorate the list label iff it is a numbered list if (KoListStyle::isNumberingStyle(listStyle)) { painter->save(); decorateListLabel(painter, blockData, layout.lineAt(0), block); painter->restore(); } } KoListStyle::Style listStyle = static_cast(listFormat.style()); if (listStyle == KoListStyle::ImageItem) { QFontMetricsF fm(blockData.labelFormat().font(), d->documentLayout->paintDevice()); qreal x = qMax(qreal(1), blockData.counterPosition().x()); qreal width = qMax(listFormat.doubleProperty(KoListStyle::Width), (qreal)1.0); qreal height = qMax(listFormat.doubleProperty(KoListStyle::Height), (qreal)1.0); qreal y = blockData.counterPosition().y() + fm.ascent() - fm.xHeight()/2 - height/2; // centered KoImageData *idata = listFormat.property(KoListStyle::BulletImage).value(); if (idata) { painter->drawPixmap(x, y, width, height, idata->pixmap()); } } } } void KoTextLayoutArea::decorateListLabel(QPainter *painter, const KoTextBlockData &blockData, const QTextLine &listLabelLine, const QTextBlock &listItem) { const QTextCharFormat listLabelCharFormat = blockData.labelFormat(); painter->setFont(listLabelCharFormat.font()); int startOfFragmentInBlock = 0; Q_ASSERT_X(listLabelLine.isValid(), __FUNCTION__, QString("Invalid list label").toLocal8Bit()); if (!listLabelLine.isValid()) { return; } int fragmentToLineOffset = 0; qreal x1 = blockData.counterPosition().x(); qreal x2 = listItem.layout()->lineAt(0).x(); if (x2 != x1) { drawStrikeOuts(painter, listLabelCharFormat, blockData.counterText(), listItem.layout()->lineAt(0), x1, x2, startOfFragmentInBlock, fragmentToLineOffset); drawOverlines(painter, listLabelCharFormat, blockData.counterText(), listItem.layout()->lineAt(0), x1, x2, startOfFragmentInBlock, fragmentToLineOffset); drawUnderlines(painter, listLabelCharFormat, blockData.counterText(), listItem.layout()->lineAt(0), x1, x2, startOfFragmentInBlock, fragmentToLineOffset); } } /** * Draw a line. Typically meant to underline text or similar. * @param painter the painter to paint on. * @painter color the pen color to for the decoratoin line * @param type The type * @param style the type of line to draw. * @param width The thickness of the line, in pixels (the painter will be prescaled to points coordinate system). * @param x1 we are always drawing horizontal lines, this is the start point. * @param x2 we are always drawing horizontal lines, this is the end point. * @param y the y-offset to paint on. */ static void drawDecorationLine(QPainter *painter, const QColor &color, KoCharacterStyle::LineType type, KoCharacterStyle::LineStyle style, qreal width, const qreal x1, const qreal x2, const qreal y) { QPen penBackup = painter->pen(); QPen pen = painter->pen(); pen.setColor(color); pen.setWidthF(width); if (style == KoCharacterStyle::WaveLine) { // Ok, try the waves :) pen.setStyle(Qt::SolidLine); painter->setPen(pen); qreal x = x1; const qreal halfWaveWidth = 0.5 * width; const qreal halfWaveLength = 2 * width; const int startAngle = 0 * 16; const int middleAngle = 180 * 16; const int endAngle = 180 * 16; while (x < x2) { QRectF rectangle1(x, y, halfWaveLength, 2*halfWaveWidth); if (type == KoCharacterStyle::DoubleLine) { painter->translate(0, -pen.width()); painter->drawArc(rectangle1, startAngle, middleAngle); painter->translate(0, 2*pen.width()); painter->drawArc(rectangle1, startAngle, middleAngle); painter->translate(0, -pen.width()); } else { painter->drawArc(rectangle1, startAngle, middleAngle); } if (x + halfWaveLength > x2) break; QRectF rectangle2(x + halfWaveLength, y, halfWaveLength, 2*halfWaveWidth); if (type == KoCharacterStyle::DoubleLine) { painter->translate(0, -pen.width()); painter->drawArc(rectangle2, middleAngle, endAngle); painter->translate(0, 2*pen.width()); painter->drawArc(rectangle2, middleAngle, endAngle); painter->translate(0, -pen.width()); } else { painter->drawArc(rectangle2, middleAngle, endAngle); } x = x + 2 * halfWaveLength; } } else { if (style == KoCharacterStyle::LongDashLine) { QVector dashes; dashes << 12 << 2; pen.setDashPattern(dashes); } else { pen.setStyle((Qt::PenStyle)style); } painter->setPen(pen); if (type == KoCharacterStyle::DoubleLine) { painter->translate(0, -pen.width()); painter->drawLine(QPointF(x1, y), QPointF(x2, y)); painter->translate(0, 2*pen.width()); painter->drawLine(QPointF(x1, y), QPointF(x2, y)); painter->translate(0, -pen.width()); } else { painter->drawLine(QPointF(x1, y), QPointF(x2, y)); } } painter->setPen(penBackup); } static void drawDecorationText(QPainter *painter, const QTextLine &line, const QColor &color, const QString& decorText, qreal x1, qreal x2) { qreal y = line.position().y(); QPen oldPen = painter->pen(); painter->setPen(QPen(color)); do { QRectF br; painter->drawText(QRectF(QPointF(x1, y), QPointF(x2, y + line.height())), Qt::AlignLeft | Qt::AlignVCenter, decorText, &br); x1 = br.right(); } while (x1 <= x2); painter->setPen(oldPen); } static void drawDecorationWords(QPainter *painter, const QTextLine &line, const QString &text, const QColor &color, KoCharacterStyle::LineType type, KoCharacterStyle::LineStyle style, const QString& decorText, qreal width, const qreal y, const int fragmentToLineOffset, const int startOfFragmentInBlock) { qreal wordBeginX = -1; int j = line.textStart()+fragmentToLineOffset; while (j < line.textLength() + line.textStart() && j-startOfFragmentInBlockpen(); QPen pen = painter->pen(); pen.setWidth(1); pen.setColor(Qt::gray); painter->setPen(pen); qreal xl = layout->boundingRect().left(); qreal xr = qMax(layout->boundingRect().right(), layout->boundingRect().left() + width()); qreal yu = layout->boundingRect().top(); qreal yd = layout->boundingRect().bottom(); qreal bracketSize = painter->fontMetrics().height() / 2; const qreal levelShift = 3; QList openList = KoSectionUtils::sectionStartings(bf); for (int i = 0; i < openList.size(); i++) { int sectionLevel = openList[i]->level(); if (i == 0) { painter->drawLine(xl + sectionLevel * levelShift, yu, xr - sectionLevel * levelShift, yu); } painter->drawLine(xl + sectionLevel * levelShift, yu, xl + sectionLevel * levelShift, yu + bracketSize); painter->drawLine(xr - sectionLevel * levelShift, yu, xr - sectionLevel * levelShift, yu + bracketSize); } QList closeList = KoSectionUtils::sectionEndings(bf); for (int i = 0; i < closeList.size(); i++) { int sectionLevel = closeList[i]->correspondingSection()->level(); if (i == closeList.count() - 1) { painter->drawLine(xl + sectionLevel * levelShift, yd, xr - sectionLevel * levelShift, yd); } painter->drawLine(xl + sectionLevel * levelShift, yd, xl + sectionLevel * levelShift, yd - bracketSize); painter->drawLine(xr - sectionLevel * levelShift, yd, xr - sectionLevel * levelShift, yd - bracketSize); } painter->setPen(penBackup); } void KoTextLayoutArea::decorateParagraph(QPainter *painter, QTextBlock &block, bool showFormattingCharacters, bool showSpellChecking) { QTextLayout *layout = block.layout(); QTextBlockFormat bf = block.blockFormat(); QVariantList tabList = bf.property(KoParagraphStyle::TabPositions).toList(); QFont oldFont = painter->font(); QTextBlock::iterator it; int startOfBlock = -1; int currentTabStop = 0; // qDebug() << "\n-------------------" // << "\nGoing to decorate block\n" // << block.text() // << "\n-------------------"; // loop over text fragments in this paragraph and draw the underline and line through. for (it = block.begin(); !it.atEnd(); ++it) { QTextFragment currentFragment = it.fragment(); if (currentFragment.isValid()) { // qDebug() << "\tGoing to layout fragment:" << currentFragment.text(); QTextCharFormat fmt = currentFragment.charFormat(); painter->setFont(fmt.font()); // a block doesn't have a real start position, so use our own counter. Initialize // it with the position of the first text fragment in the block. if (startOfBlock == -1) { startOfBlock = currentFragment.position(); // start of this block w.r.t. the document } // the start of our fragment in the block is the absolute position of the fragment // in the document minus the start of the block in the document. int startOfFragmentInBlock = currentFragment.position() - startOfBlock; // a fragment can span multiple lines, but we paint the decorations per line. int firstLine = layout->lineForTextPosition(currentFragment.position() - startOfBlock).lineNumber(); int lastLine = layout->lineForTextPosition(currentFragment.position() + currentFragment.length() - startOfBlock).lineNumber(); // qDebug() << "\tfirst line:" << firstLine << "last line:" << lastLine; for (int i = firstLine ; i <= lastLine ; ++i) { QTextLine line = layout->lineAt(i); // qDebug() << "\n\t\tcurrent line:" << i // << "\n\t\tline length:" << line.textLength() << "width:"<< line.width() << "natural width" << line.naturalTextWidth() // << "\n\t\tvalid:" << layout->isValidCursorPosition(currentFragment.position() - startOfBlock) // << "\n\t\tcurrentFragment.position:" << currentFragment.position() // << "\n\t\tstartOfBlock:" << startOfBlock // << "\n\t\tstartOfFragmentInBlock:" << startOfFragmentInBlock; if (layout->isValidCursorPosition(currentFragment.position() - startOfBlock)) { // the start position for painting the decoration is the position of the fragment // inside, but after the first line, the decoration always starts at the beginning // of the line. See bug: 264471 int p1 = startOfFragmentInBlock; if (i > firstLine) { p1 = line.textStart(); } // qDebug() << "\n\t\tblock.text.length:" << block.text().length() << "p1" << p1; if (block.text().length() > p1 && block.text().at(p1) != QChar::ObjectReplacementCharacter) { Q_ASSERT_X(line.isValid(), __FUNCTION__, QString("Invalid line=%1 first=%2 last=%3").arg(i).arg(firstLine).arg(lastLine).toLocal8Bit()); // see bug 278682 if (!line.isValid()) continue; // end position: note that x2 can be smaller than x1 when we are handling RTL int p2 = startOfFragmentInBlock + currentFragment.length(); int lineEndWithoutPreedit = line.textStart() + line.textLength(); if (block.layout()->preeditAreaPosition() >= block.position() + line.textStart() && block.layout()->preeditAreaPosition() <= block.position() + line.textStart() + line.textLength()) { lineEndWithoutPreedit -= block.layout()->preeditAreaText().length(); } while (lineEndWithoutPreedit > line.textStart() && block.text().at(lineEndWithoutPreedit - 1) == ' ') { --lineEndWithoutPreedit; } if (lineEndWithoutPreedit < p2) { //line caps p2 = lineEndWithoutPreedit; } int fragmentToLineOffset = qMax(startOfFragmentInBlock - line.textStart(), 0); qreal x1 = line.cursorToX(p1); qreal x2 = line.cursorToX(p2); //qDebug() << "\n\t\t\tp1:" << p1 << "x1:" << x1 // << "\n\t\t\tp2:" << p2 << "x2:" << x2 // << "\n\t\t\tlineEndWithoutPreedit" << lineEndWithoutPreedit; if (x1 != x2) { drawStrikeOuts(painter, fmt, currentFragment.text(), line, x1, x2, startOfFragmentInBlock, fragmentToLineOffset); drawOverlines(painter, fmt, currentFragment.text(), line, x1, x2, startOfFragmentInBlock, fragmentToLineOffset); drawUnderlines(painter, fmt, currentFragment.text(), line, x1, x2, startOfFragmentInBlock, fragmentToLineOffset); } decorateTabsAndFormatting(painter, currentFragment, line, startOfFragmentInBlock, tabList, currentTabStop, showFormattingCharacters); // underline preedit strings if (layout->preeditAreaPosition() > -1) { int start = block.layout()->preeditAreaPosition(); int end = block.layout()->preeditAreaPosition() + block.layout()->preeditAreaText().length(); QTextCharFormat underline; underline.setFontUnderline(true); underline.setUnderlineStyle(QTextCharFormat::DashUnderline); //qDebug() << "underline style" << underline.underlineStyle(); //qDebug() << "line start: " << block.position() << line.textStart(); qreal z1 = 0; qreal z2 = 0; // preedit start in this line, end in this line if ( start >= block.position() + line.textStart() && end <= block.position() + line.textStart() + line.textLength() ) { z1 = line.cursorToX(start); z2 = line.cursorToX(end); } // preedit start in this line, end after this line if ( start >= block.position() + line.textStart() && end > block.position() + line.textStart() + line.textLength() ) { z1 = line.cursorToX(start); z2 = line.cursorToX(block.position() + line.textStart() + line.textLength()); } // preedit start before this line, end in this line if ( start < block.position() + line.textStart() && end <= block.position() + line.textStart() + line.textLength() ) { z1 = line.cursorToX(block.position() + line.textStart()); z2 = line.cursorToX(end); } // preedit start before this line, end after this line if ( start < block.position() + line.textStart() && end > block.position() + line.textStart() + line.textLength() ) { z1 = line.cursorToX(block.position() + line.textStart()); z2 = line.cursorToX(block.position() + line.textStart() + line.textLength()); } if (z2 > z1) { //qDebug() << "z1: " << z1 << "z2: " << z2; KoCharacterStyle::LineStyle fontUnderLineStyle = KoCharacterStyle::DashLine; KoCharacterStyle::LineType fontUnderLineType = KoCharacterStyle::SingleLine; QTextCharFormat::VerticalAlignment valign = fmt.verticalAlignment(); QFont font(fmt.font()); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) font.setPointSize(font.pointSize() * 2 / 3); QFontMetricsF metrics(font, d->documentLayout->paintDevice()); qreal y = line.position().y(); if (valign == QTextCharFormat::AlignSubScript) y += line.height() - metrics.descent() + metrics.underlinePos(); else if (valign == QTextCharFormat::AlignSuperScript) y += metrics.ascent() + metrics.underlinePos(); else y += line.ascent() + metrics.underlinePos(); QColor color = fmt.foreground().color(); qreal width = computeWidth( // line thickness (KoCharacterStyle::LineWeight) underline.intProperty(KoCharacterStyle::UnderlineWeight), underline.doubleProperty(KoCharacterStyle::UnderlineWidth), font); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) // adjust size. width = width * 2 / 3; drawDecorationLine(painter, color, fontUnderLineType, fontUnderLineStyle, width, z1, z2, y); } } } } } } } if (showFormattingCharacters) { QTextLine line = layout->lineForTextPosition(block.length()-1); qreal y = line.position().y() + line.ascent(); qreal x = line.cursorToX(block.length()-1); painter->drawText(QPointF(x, y), QChar((ushort)0x00B6)); } if (showSpellChecking) { // Finally let's paint our own spelling markings // TODO Should we make this optional at this point (right on/off handled by the plugin) // also we might want to provide alternative ways of drawing it KoTextBlockData blockData(block); QPen penBackup = painter->pen(); QPen pen; pen.setColor(QColor(Qt::red)); pen.setWidthF(1.5); QVector pattern; pattern << 1 << 2; pen.setDashPattern(pattern); painter->setPen(pen); QList::Iterator markIt = blockData.markupsBegin(KoTextBlockData::Misspell); QList::Iterator markEnd = blockData.markupsEnd(KoTextBlockData::Misspell); for (int i = 0 ; i < layout->lineCount(); ++i) { if (markIt == markEnd) { break; } QTextLine line = layout->lineAt(i); // the y position is placed half way between baseline and descent of the line // this is fast and sufficient qreal y = line.position().y() + line.ascent() + 0.5 * line.descent(); // first handle all those marking ranges that end on this line while (markIt != markEnd && markIt->lastChar < line.textStart() + line.textLength() && line.textStart() + line.textLength() <= block.length()) { if (!blockData.isMarkupsLayoutValid(KoTextBlockData::Misspell)) { if (markIt->firstChar > line.textStart()) { markIt->startX = line.cursorToX(markIt->firstChar); } markIt->endX = line.cursorToX(qMin(markIt->lastChar, block.length())); } qreal x1 = (markIt->firstChar > line.textStart()) ? markIt->startX : line.cursorToX(0); painter->drawLine(QPointF(x1, y), QPointF(markIt->endX, y)); ++markIt; } // there may be a markup range on this line that extends to the next line if (markIt != markEnd && markIt->firstChar < line.textStart() + line.textLength() && line.textStart() + line.textLength() <= block.length()) { if (!blockData.isMarkupsLayoutValid(KoTextBlockData::Misspell)) { if (markIt->firstChar > line.textStart()) { markIt->startX = line.cursorToX(markIt->firstChar); } } qreal x1 = (markIt->firstChar > line.textStart()) ? markIt->startX : line.cursorToX(0); painter->drawLine(QPointF(x1, y), QPointF(line.cursorToX(line.textStart() + line.textLength()), y)); // since it extends to next line we don't increment the iterator } } blockData.setMarkupsLayoutValidity(KoTextBlockData::Misspell, true); painter->setPen(penBackup); } painter->setFont(oldFont); } void KoTextLayoutArea::drawStrikeOuts(QPainter *painter, const QTextCharFormat ¤tCharFormat, const QString &text, const QTextLine &line, qreal x1, qreal x2, const int startOfFragmentInBlock, const int fragmentToLineOffset) const { KoCharacterStyle::LineStyle strikeOutStyle = (KoCharacterStyle::LineStyle) currentCharFormat.intProperty(KoCharacterStyle::StrikeOutStyle); KoCharacterStyle::LineType strikeOutType = (KoCharacterStyle::LineType) currentCharFormat.intProperty(KoCharacterStyle::StrikeOutType); if ((strikeOutStyle != KoCharacterStyle::NoLineStyle) && (strikeOutType != KoCharacterStyle::NoLineType)) { QTextCharFormat::VerticalAlignment valign = currentCharFormat.verticalAlignment(); QFont font(currentCharFormat.font()); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) font.setPointSize(qRound(font.pointSize() * 2 / 3.)); QFontMetricsF metrics(font, d->documentLayout->paintDevice()); qreal y = line.position().y(); if (valign == QTextCharFormat::AlignSubScript) y += line.height() - metrics.descent() - metrics.strikeOutPos(); else if (valign == QTextCharFormat::AlignSuperScript) y += metrics.ascent() - metrics.strikeOutPos(); else y += line.ascent() - metrics.strikeOutPos(); QColor color = currentCharFormat.colorProperty(KoCharacterStyle::StrikeOutColor); if (!color.isValid()) color = currentCharFormat.foreground().color(); KoCharacterStyle::LineMode strikeOutMode = (KoCharacterStyle::LineMode) currentCharFormat.intProperty(KoCharacterStyle::StrikeOutMode); QString strikeOutText = currentCharFormat.stringProperty(KoCharacterStyle::StrikeOutText); qreal width = 0; // line thickness if (strikeOutText.isEmpty()) { width = computeWidth( (KoCharacterStyle::LineWeight) currentCharFormat.intProperty(KoCharacterStyle::StrikeOutWeight), currentCharFormat.doubleProperty(KoCharacterStyle::StrikeOutWidth), font); } if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) // adjust size. width = width * 2 / 3; if (strikeOutMode == KoCharacterStyle::SkipWhiteSpaceLineMode) { drawDecorationWords(painter, line, text, color, strikeOutType, strikeOutStyle, strikeOutText, width, y, fragmentToLineOffset, startOfFragmentInBlock); } else { if (strikeOutText.isEmpty()) drawDecorationLine(painter, color, strikeOutType, strikeOutStyle, width, x1, x2, y); else drawDecorationText(painter, line, color, strikeOutText, x1, x2); } } } void KoTextLayoutArea::drawOverlines(QPainter *painter, const QTextCharFormat ¤tCharFormat, const QString &text, const QTextLine &line, qreal x1, qreal x2, const int startOfFragmentInBlock, const int fragmentToLineOffset) const { KoCharacterStyle::LineStyle fontOverLineStyle = (KoCharacterStyle::LineStyle) currentCharFormat.intProperty(KoCharacterStyle::OverlineStyle); KoCharacterStyle::LineType fontOverLineType = (KoCharacterStyle::LineType) currentCharFormat.intProperty(KoCharacterStyle::OverlineType); if ((fontOverLineStyle != KoCharacterStyle::NoLineStyle) && (fontOverLineType != KoCharacterStyle::NoLineType)) { QTextCharFormat::VerticalAlignment valign = currentCharFormat.verticalAlignment(); QFont font(currentCharFormat.font()); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) font.setPointSize(font.pointSize() * 2 / 3); QFontMetricsF metrics(font, d->documentLayout->paintDevice()); qreal y = line.position().y(); if (valign == QTextCharFormat::AlignSubScript) y += line.height() - metrics.descent() - metrics.overlinePos(); else if (valign == QTextCharFormat::AlignSuperScript) y += metrics.ascent() - metrics.overlinePos(); else y += line.ascent() - metrics.overlinePos(); QColor color = currentCharFormat.colorProperty(KoCharacterStyle::OverlineColor); if (!color.isValid()) color = currentCharFormat.foreground().color(); KoCharacterStyle::LineMode overlineMode = (KoCharacterStyle::LineMode) currentCharFormat.intProperty(KoCharacterStyle::OverlineMode); qreal width = computeWidth( // line thickness (KoCharacterStyle::LineWeight) currentCharFormat.intProperty(KoCharacterStyle::OverlineWeight), currentCharFormat.doubleProperty(KoCharacterStyle::OverlineWidth), font); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) // adjust size. width = width * 2 / 3; if (overlineMode == KoCharacterStyle::SkipWhiteSpaceLineMode) { drawDecorationWords(painter, line, text, color, fontOverLineType, fontOverLineStyle, QString(), width, y, fragmentToLineOffset, startOfFragmentInBlock); } else { drawDecorationLine(painter, color, fontOverLineType, fontOverLineStyle, width, x1, x2, y); } } } void KoTextLayoutArea::drawUnderlines(QPainter *painter, const QTextCharFormat ¤tCharFormat,const QString &text, const QTextLine &line, qreal x1, qreal x2, const int startOfFragmentInBlock, const int fragmentToLineOffset) const { KoCharacterStyle::LineStyle fontUnderLineStyle = (KoCharacterStyle::LineStyle) currentCharFormat.intProperty(KoCharacterStyle::UnderlineStyle); KoCharacterStyle::LineType fontUnderLineType = (KoCharacterStyle::LineType) currentCharFormat.intProperty(KoCharacterStyle::UnderlineType); if ((fontUnderLineStyle != KoCharacterStyle::NoLineStyle) && (fontUnderLineType != KoCharacterStyle::NoLineType)) { QTextCharFormat::VerticalAlignment valign = currentCharFormat.verticalAlignment(); QFont font(currentCharFormat.font()); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) font.setPointSize(font.pointSize() * 2 / 3); QFontMetricsF metrics(font, d->documentLayout->paintDevice()); qreal y = line.position().y(); if (valign == QTextCharFormat::AlignSubScript) y += line.height() - metrics.descent() + metrics.underlinePos(); else if (valign == QTextCharFormat::AlignSuperScript) y += metrics.ascent() + metrics.underlinePos(); else y += line.ascent() + metrics.underlinePos(); QColor color = currentCharFormat.underlineColor(); if (!color.isValid()) color = currentCharFormat.foreground().color(); KoCharacterStyle::LineMode underlineMode = (KoCharacterStyle::LineMode) currentCharFormat.intProperty(KoCharacterStyle::UnderlineMode); qreal width = computeWidth( // line thickness (KoCharacterStyle::LineWeight) currentCharFormat.intProperty(KoCharacterStyle::UnderlineWeight), currentCharFormat.doubleProperty(KoCharacterStyle::UnderlineWidth), font); if (valign == QTextCharFormat::AlignSubScript || valign == QTextCharFormat::AlignSuperScript) // adjust size. width = width * 2 / 3; if (underlineMode == KoCharacterStyle::SkipWhiteSpaceLineMode) { drawDecorationWords(painter, line, text, color, fontUnderLineType, fontUnderLineStyle, QString(), width, y, fragmentToLineOffset, startOfFragmentInBlock); } else { drawDecorationLine(painter, color, fontUnderLineType, fontUnderLineStyle, width, x1, x2, y); } } } // Decorate any tabs ('\t's) in 'currentFragment' and laid out in 'line'. int KoTextLayoutArea::decorateTabsAndFormatting(QPainter *painter, const QTextFragment& currentFragment, const QTextLine &line, const int startOfFragmentInBlock, const QVariantList& tabList, int currentTabStop, bool showFormattingCharacters) { // If a line in the layout represent multiple text fragments, this function will // be called multiple times on the same line, with different fragments. // Likewise, if a fragment spans two lines, then this function will be called twice // on the same fragment, once for each line. QString fragText = currentFragment.text(); QFontMetricsF fm(currentFragment.charFormat().font(), d->documentLayout->paintDevice()); qreal tabStyleLineMargin = fm.averageCharWidth() / 4; // leave some margin for the tab decoration line // currentFragment.position() : start of this fragment w.r.t. the document // startOfFragmentInBlock : start of this fragment w.r.t. the block // line.textStart() : start of this line w.r.t. the block int searchForCharFrom; // search for \t from this point onwards in fragText int searchForCharTill; // search for \t till this point in fragText if (line.textStart() >= startOfFragmentInBlock) { // fragment starts at or before the start of line // we are concerned with only that part of the fragment displayed in this line searchForCharFrom = line.textStart() - startOfFragmentInBlock; // It's a new line. So we should look at the first tab-stop properties for the next \t. currentTabStop = 0; } else { // fragment starts in the middle of the line searchForCharFrom = 0; } if (line.textStart() + line.textLength() > startOfFragmentInBlock + currentFragment.length()) { // fragment ends before the end of line. need to see only till the end of the fragment. searchForCharTill = currentFragment.length(); } else { // line ends before the fragment ends. need to see only till the end of this line. // but then, we need to convert the end of line to an index into fragText searchForCharTill = line.textLength() + line.textStart() - startOfFragmentInBlock; } for (int i = searchForCharFrom ; i < searchForCharTill; i++) { if (currentTabStop >= tabList.size() && !showFormattingCharacters) // no more decorations break; if (fragText[i] == '\t') { qreal x1(0.0); qreal x2(0.0); if (showFormattingCharacters) { x1 = line.cursorToX(startOfFragmentInBlock + i); x2 = line.cursorToX(startOfFragmentInBlock + i + 1); qreal y = line.position().y() + line.ascent() - fm.xHeight()/2.0; qreal arrowDim = fm.xHeight()/2.0; QPen penBackup = painter->pen(); QPen pen = painter->pen(); pen.setWidthF(fm.ascent()/10.0); pen.setStyle(Qt::SolidLine); painter->setPen(pen); painter->drawLine(QPointF(x1, y), QPointF(x2, y)); painter->drawLine(QPointF(x2 - arrowDim, y - arrowDim), QPointF(x2, y)); painter->drawLine(QPointF(x2 - arrowDim, y + arrowDim), QPointF(x2, y)); painter->setPen(penBackup); } if (currentTabStop < tabList.size()) { // still tabsstops worth examining if (!showFormattingCharacters) { // only then was it not calculated x1 = line.cursorToX(startOfFragmentInBlock + i); } // find a tab-stop decoration for this tab position // for eg., if there's a tab-stop at 1in, but the text before \t already spans 1.2in, // we should look at the next tab-stop KoText::Tab tab; do { tab = qvariant_cast(tabList[currentTabStop]); currentTabStop++; // comparing with x1 should work for all of left/right/center/char tabs } while (tab.position <= x1 && currentTabStop < tabList.size()); if (tab.position > x1) { if (!showFormattingCharacters) { // only then was it not calculated x2 = line.cursorToX(startOfFragmentInBlock + i + 1); } qreal tabStyleLeftLineMargin = tabStyleLineMargin; qreal tabStyleRightLineMargin = tabStyleLineMargin; // no margin if its adjacent char is also a tab if (i > searchForCharFrom && fragText[i-1] == '\t') tabStyleLeftLineMargin = 0; if (i < (searchForCharTill - 1) && fragText[i+1] == '\t') tabStyleRightLineMargin = 0; qreal y = line.position().y() + line.ascent() - 1; x1 += tabStyleLeftLineMargin; x2 -= tabStyleRightLineMargin; QColor tabDecorColor = currentFragment.charFormat().foreground().color(); if (tab.leaderColor.isValid()) tabDecorColor = tab.leaderColor; qreal width = computeWidth(tab.leaderWeight, tab.leaderWidth, painter->font()); if (x1 < x2) { if (tab.leaderText.isEmpty()) { drawDecorationLine(painter, tabDecorColor, tab.leaderType, tab.leaderStyle, width, x1, x2, y); } else { drawDecorationText(painter, line, tabDecorColor, tab.leaderText, x1, x2); } } } } } else if (showFormattingCharacters) { if (fragText[i] == ' ' || fragText[i] == QChar::Nbsp) { qreal x = line.cursorToX(startOfFragmentInBlock + i); qreal y = line.position().y() + line.ascent(); painter->drawText(QPointF(x, y), QChar((ushort)0xb7)); } else if (fragText[i] == QChar::LineSeparator){ qreal x = line.cursorToX(startOfFragmentInBlock + i); qreal y = line.position().y() + line.ascent(); painter->drawText(QPointF(x, y), QChar((ushort)0x21B5)); } } } return currentTabStop; } diff --git a/plugins/flake/textshape/textlayout/KoTextLayoutEndNotesArea.cpp b/plugins/flake/textshape/textlayout/KoTextLayoutEndNotesArea.cpp index 1b4eb6e44d..2e3b3f496f 100644 --- a/plugins/flake/textshape/textlayout/KoTextLayoutEndNotesArea.cpp +++ b/plugins/flake/textshape/textlayout/KoTextLayoutEndNotesArea.cpp @@ -1,153 +1,153 @@ /* This file is part of the KDE project * Copyright (C) 2011 C. Boemann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoTextLayoutEndNotesArea.h" #include "KoTextLayoutNoteArea.h" #include "KoInlineTextObjectManager.h" #include "KoInlineNote.h" #include "KoPointedAt.h" #include "FrameIterator.h" #include #include static bool beforeThan(KoInlineNote *note1, KoInlineNote *note2) { return (note1->getPosInDocument() < note2->getPosInDocument()); } class Q_DECL_HIDDEN KoTextLayoutEndNotesArea::Private { public: Private() : startOfArea(0) { } QList endNoteAreas; QList endNoteFrames; FrameIterator *startOfArea; FrameIterator *endOfArea; int endNoteAutoCount; }; KoTextLayoutEndNotesArea::KoTextLayoutEndNotesArea(KoTextLayoutArea *parent, KoTextDocumentLayout *documentLayout) : KoTextLayoutArea(parent, documentLayout) , d(new Private) { d->endNoteAutoCount = 0; } KoTextLayoutEndNotesArea::~KoTextLayoutEndNotesArea() { qDeleteAll(d->endNoteAreas); delete d; } bool KoTextLayoutEndNotesArea::layout(FrameIterator *cursor) { qDeleteAll(d->endNoteAreas); d->endNoteAreas.clear(); d->endNoteFrames.clear(); d->startOfArea = new FrameIterator(cursor); d->endOfArea = 0; int shiftDown = 15; qreal y = top() + shiftDown; setBottom(y); KoInlineTextObjectManager *manager = KoTextDocument(documentLayout()->document()).inlineTextObjectManager(); QList list = QList(manager->endNotes()); - qSort(list.begin(), list.end(), beforeThan); //making a list of endnotes in the order they appear + std::sort(list.begin(), list.end(), beforeThan); //making a list of endnotes in the order they appear while (cursor->endNoteIndex < list.length()) { KoInlineNote *note = list[cursor->endNoteIndex]; if (note->autoNumbering()) { note->setAutoNumber(d->endNoteAutoCount++); } QTextFrame *subFrame = note->textFrame(); KoTextLayoutNoteArea *noteArea = new KoTextLayoutNoteArea(note, this, documentLayout()); d->endNoteAreas.append(noteArea); d->endNoteFrames.append(subFrame); noteArea->setReferenceRect(left(), right(), y, maximumAllowedBottom()); if (noteArea->layout(cursor->subFrameIterator(subFrame)) == false) { d->endOfArea = new FrameIterator(cursor); setBottom(noteArea->bottom()); return false; } y = noteArea->bottom(); setBottom(y); delete cursor->currentSubFrameIterator; cursor->currentSubFrameIterator = 0; cursor->endNoteIndex++; } if (cursor->endNoteIndex == 0) { setBottom(top() + shiftDown); } d->endOfArea = new FrameIterator(cursor); return true; } KoPointedAt KoTextLayoutEndNotesArea::hitTest(const QPointF &p, Qt::HitTestAccuracy accuracy) const { KoPointedAt pointedAt; int endNoteIndex = 0; while (endNoteIndex < d->endNoteAreas.length()) { // check if p is over end notes area if (p.y() > d->endNoteAreas[endNoteIndex]->top() && p.y() < d->endNoteAreas[endNoteIndex]->bottom()) { pointedAt = d->endNoteAreas[endNoteIndex]->hitTest(p, accuracy); return pointedAt; } ++endNoteIndex; } return KoPointedAt(); } QRectF KoTextLayoutEndNotesArea::selectionBoundingBox(QTextCursor &cursor) const { QTextFrame *subFrame; int endNoteIndex = 0; while (endNoteIndex < d->endNoteFrames.length()) { subFrame = d->endNoteFrames[endNoteIndex]; if (subFrame != 0) { if (cursor.selectionStart() >= subFrame->firstPosition() && cursor.selectionEnd() <= subFrame->lastPosition()) { return d->endNoteAreas[endNoteIndex]->selectionBoundingBox(cursor); } ++endNoteIndex; } } return QRectF(); } void KoTextLayoutEndNotesArea::paint(QPainter *painter, const KoTextDocumentLayout::PaintContext &context) { if (d->startOfArea == 0) // We have not been layouted yet return; if (!d->endNoteAreas.isEmpty()) { int left = 2; int right = 150; int shiftDown = 10; painter->drawLine(left, top()+shiftDown, right, top()+shiftDown); } Q_FOREACH (KoTextLayoutNoteArea *area, d->endNoteAreas) { area->paint(painter, context); } } diff --git a/plugins/flake/textshape/textlayout/KoTextLayoutTableArea.cpp b/plugins/flake/textshape/textlayout/KoTextLayoutTableArea.cpp index 9ef7fce4b9..f0b8925c3d 100644 --- a/plugins/flake/textshape/textlayout/KoTextLayoutTableArea.cpp +++ b/plugins/flake/textshape/textlayout/KoTextLayoutTableArea.cpp @@ -1,1125 +1,1125 @@ /* This file is part of the KDE project * Copyright (C) 2009 Elvis Stansvik * Copyright (C) 2011 C. Boemann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoTextLayoutTableArea.h" #include "KoTextLayoutCellHelper.h" #include "TableIterator.h" #include "KoPointedAt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "FrameIterator.h" class Q_DECL_HIDDEN KoTextLayoutTableArea::Private { public: Private() : startOfArea(0) { } QVector > cellAreas; TableIterator *startOfArea; TableIterator *endOfArea; bool lastRowHasSomething; QTextTable *table; int headerRows; qreal headerOffsetX; qreal headerOffsetY; KoTableColumnAndRowStyleManager carsManager; qreal tableWidth; QVector headerRowPositions; // we will only fill those that this area covers QVector rowPositions; // we will only fill those that this area covers QVector columnWidths; QVector columnPositions; bool collapsing; bool totalMisFit; KoTextDocumentLayout *documentLayout; KoTableCellStyle effectiveCellStyle(const QTextTableCell &tableCell); }; KoTableCellStyle KoTextLayoutTableArea::Private::effectiveCellStyle(const QTextTableCell &tableCell) { QTextTableFormat tableFormat = table->format(); KoTableCellStyle cellStyle(tableCell.format().toTableCellFormat()); if (documentLayout->styleManager() && table->format().hasProperty(KoTableStyle::TableTemplate)) { if (KoTextTableTemplate *tableTemplate = documentLayout->styleManager()->tableTemplate(table->format().intProperty(KoTableStyle::TableTemplate))) { //priorities according to ODF 1.2, 16.18 - table:table-template if (tableCell.column() == 0 && tableTemplate->firstColumn() && tableFormat.boolProperty(KoTableStyle::UseFirstColumnStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->firstColumn())); return cellStyle; } if (tableCell.column() == (table->columns() - 1) && tableTemplate->lastColumn() && tableFormat.boolProperty(KoTableStyle::UseLastColumnStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->lastColumn())); return cellStyle; } if (tableCell.row() == 0 && tableTemplate->firstRow() && tableFormat.boolProperty(KoTableStyle::UseFirstRowStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->firstRow())); return cellStyle; } if (tableCell.row() == (table->rows() - 1) && tableTemplate->lastRow() && tableFormat.boolProperty(KoTableStyle::UseLastRowStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->lastRow())); return cellStyle; } if (((tableCell.row() + 1) % 2) == 0 && tableTemplate->evenRows() && tableFormat.boolProperty(KoTableStyle::UseBandingRowStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->evenRows())); return cellStyle; } if (((tableCell.row() + 1) % 2) != 0 && tableTemplate->oddRows() && tableFormat.boolProperty(KoTableStyle::UseBandingRowStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->oddRows())); return cellStyle; } if (((tableCell.column() + 1) % 2) == 0 && tableTemplate->evenColumns() && tableFormat.boolProperty(KoTableStyle::UseBandingColumnStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->evenColumns())); return cellStyle; } if (((tableCell.column() + 1) % 2) != 0 && tableTemplate->oddColumns() && tableFormat.boolProperty(KoTableStyle::UseBandingColumnStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->oddColumns())); return cellStyle; } if (tableTemplate->body()) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->body())); } } } return cellStyle; } KoTextLayoutTableArea::KoTextLayoutTableArea(QTextTable *table, KoTextLayoutArea *parent, KoTextDocumentLayout *documentLayout) : KoTextLayoutArea(parent, documentLayout) , d(new Private) { Q_ASSERT(table); Q_ASSERT(parent); d->table = table; d->documentLayout = documentLayout; d->carsManager = KoTableColumnAndRowStyleManager::getManager(table); // Resize geometry vectors for the table. d->rowPositions.resize(table->rows() + 1); d->headerRowPositions.resize(table->rows() + 1); d->cellAreas.resize(table->rows()); for (int row = 0; row < table->rows(); ++row) { d->cellAreas[row].resize(table->columns()); } KoTableStyle tableStyle(d->table->format()); d->collapsing = tableStyle.collapsingBorderModel(); } KoTextLayoutTableArea::~KoTextLayoutTableArea() { for (int row = d->startOfArea->row; row < d->cellAreas.size(); ++row) { for (int col = 0; col < d->cellAreas[row].size(); ++col) { delete d->cellAreas[row][col]; } } delete d->startOfArea; delete d->endOfArea; delete d; } KoPointedAt KoTextLayoutTableArea::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const { int firstRow = qMax(d->startOfArea->row, d->headerRows); int lastRow = d->endOfArea->row; if (d->lastRowHasSomething == false) { --lastRow; } if (lastRow < d->startOfArea->row) { return KoPointedAt(); // empty } // Test normal cells. if (point.y() > d->rowPositions[firstRow] - 3.0 && point.y() < d->rowPositions[lastRow + 1] + 3.0) { QVector::const_iterator start = d->rowPositions.constBegin() + firstRow; QVector::const_iterator end = d->rowPositions.constBegin() + lastRow + 1; - int row = qLowerBound(start, end, point.y()) - d->rowPositions.constBegin() - 1; - int column = qLowerBound(d->columnPositions, point.x()) - d->columnPositions.constBegin() - 1; + int row = std::lower_bound(start, end, point.y()) - d->rowPositions.constBegin() - 1; + int column = std::lower_bound(d->columnPositions.begin(), d->columnPositions.end(), point.x()) - d->columnPositions.constBegin() - 1; if (point.y() < d->rowPositions[firstRow]) { ++row; } column = qBound(0, column, d->table->columns() - 1); KoPointedAt pointedAt; if (qAbs(d->columnPositions[column] - point.x()) < 3.0) { pointedAt.tableHit = KoPointedAt::ColumnDivider; } else if (qAbs(d->columnPositions[column+1] - point.x()) < 3.0) { pointedAt.tableHit = KoPointedAt::ColumnDivider; ++column; } else if (d->columnPositions[0] < point.x() && point.x() < d->columnPositions[d->table->columns()] && qAbs(d->rowPositions[row] - point.y()) < 3.0) { pointedAt.tableHit = KoPointedAt::RowDivider; } else if (d->columnPositions[0] < point.x() && point.x() < d->columnPositions[d->table->columns()] && qAbs(d->rowPositions[row+1] - point.y()) < 3.0) { pointedAt.tableHit = KoPointedAt::RowDivider; ++row; } else { QTextTableCell cell = d->table->cellAt(row, column); pointedAt = d->cellAreas[cell.row()][cell.column()]->hitTest(point, accuracy); } if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { if (column > 0) { pointedAt.tableLeadSize = d->columnPositions[column] - d->columnPositions[column-1]; } if (column < d->table->columns()) { pointedAt.tableTrailSize = d->columnPositions[column+1] - d->columnPositions[column]; } } else if (pointedAt.tableHit == KoPointedAt::RowDivider) { if (row > 0) { pointedAt.tableLeadSize = d->rowPositions[row] - d->rowPositions[row-1]; } if (row < d->table->rows()) { pointedAt.tableTrailSize = d->rowPositions[row+1] - d->rowPositions[row]; } } pointedAt.table = d->table; pointedAt.tableRowDivider = row; pointedAt.tableColumnDivider = column; pointedAt.tableDividerPos = QPointF(d->columnPositions[column],d->rowPositions[row]); return pointedAt; } // Test header row cells. QPointF headerPoint = point - QPointF(d->headerOffsetX, d->headerOffsetY); if (headerPoint.y() > d->headerRowPositions.first() && headerPoint.y() < d->headerRowPositions[d->headerRows]) { QVector::const_iterator start = d->headerRowPositions.constBegin(); QVector::const_iterator end = d->headerRowPositions.constBegin() + d->headerRows; - int row = qLowerBound(start, end, headerPoint.y()) - d->headerRowPositions.constBegin() - 1; - int column = qLowerBound(d->columnPositions, headerPoint.x()) - d->columnPositions.constBegin() - 1; + int row = std::lower_bound(start, end, headerPoint.y()) - d->headerRowPositions.constBegin() - 1; + int column = std::lower_bound(d->columnPositions.begin(), d->columnPositions.end(), headerPoint.x()) - d->columnPositions.constBegin() - 1; column = qBound(0, column, d->table->columns() - 1); KoPointedAt pointedAt; if (qAbs(d->columnPositions[column] - headerPoint.x()) < 3.0) { pointedAt.tableHit = KoPointedAt::ColumnDivider; } else if (qAbs(d->columnPositions[column+1] - headerPoint.x()) < 3.0) { pointedAt.tableHit = KoPointedAt::ColumnDivider; ++column; } else { QTextTableCell cell = d->table->cellAt(row, column); pointedAt = d->cellAreas[cell.row()][cell.column()]->hitTest(headerPoint, accuracy); } if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { if (column > 0) { pointedAt.tableLeadSize = d->columnPositions[column] - d->columnPositions[column-1]; } if (column < d->table->columns()) { pointedAt.tableTrailSize = d->columnPositions[column+1] - d->columnPositions[column]; } } pointedAt.table = d->table; pointedAt.tableRowDivider = row; pointedAt.tableColumnDivider = column; pointedAt.tableDividerPos = QPointF(d->columnPositions[column],d->rowPositions[row]); return pointedAt; } return KoPointedAt(); } QRectF KoTextLayoutTableArea::selectionBoundingBox(QTextCursor &cursor) const { int lastRow = d->endOfArea->row; if (d->lastRowHasSomething == false) { --lastRow; } if (lastRow < d->startOfArea->row) { return QRectF(); // empty } int firstRow = qMax(d->startOfArea->row, d->headerRows); QTextTableCell startTableCell = d->table->cellAt(cursor.selectionStart()); QTextTableCell endTableCell = d->table->cellAt(cursor.selectionEnd()); if (startTableCell == endTableCell) { if (startTableCell.row() < d->startOfArea->row || startTableCell.row() > lastRow) { return QRectF(); // cell is not in this area } KoTextLayoutArea *area = d->cellAreas[startTableCell.row()][startTableCell.column()]; Q_ASSERT(area); return area->selectionBoundingBox(cursor); } else { int selectionRow; int selectionColumn; int selectionRowSpan; int selectionColumnSpan; cursor.selectedTableCells(&selectionRow, &selectionRowSpan, &selectionColumn, &selectionColumnSpan); qreal top, bottom; if (selectionRow < d->headerRows) { top = d->headerRowPositions[selectionRow] + d->headerOffsetY; } else { top = d->rowPositions[qMin(qMax(firstRow, selectionRow), lastRow)]; } if (selectionRow + selectionRowSpan < d->headerRows) { bottom = d->headerRowPositions[selectionRow + selectionRowSpan] + d->headerOffsetY; } else { bottom = d->rowPositions[d->headerRows] + d->headerOffsetY; if (selectionRow + selectionRowSpan >= firstRow) { bottom = d->rowPositions[qMin(selectionRow + selectionRowSpan, lastRow + 1)]; } } return QRectF(d->columnPositions[selectionColumn], top, d->columnPositions[selectionColumn + selectionColumnSpan] - d->columnPositions[selectionColumn], bottom - top); } } bool KoTextLayoutTableArea::layoutTable(TableIterator *cursor) { d->startOfArea = new TableIterator(cursor); d->headerRows = cursor->headerRows; d->totalMisFit = false; // If table is done we create an empty area and return true if (cursor->row == d->table->rows()) { setBottom(top()); d->endOfArea = new TableIterator(cursor); return true; } layoutColumns(); bool first = cursor->row == 0 && (d->cellAreas[0][0] == 0); if (first) { // are we at the beginning of the table cursor->row = 0; d->rowPositions[0] = top() + d->table->format().topMargin(); d->headerOffsetX = 0; d->headerOffsetY = 0; } else { for (int row = 0; row < d->headerRows; ++row) { // Copy header rows d->headerRowPositions[row] = cursor->headerRowPositions[row]; for (int col = 0; col < d->table->columns(); ++col) { d->cellAreas[row][col] = cursor->headerCellAreas[row][col]; } } if (d->headerRows) { // Also set the position of the border below headers d->headerRowPositions[d->headerRows] = cursor->headerRowPositions[d->headerRows]; } // If headerRows == 0 then the following reduces to: d->rowPositions[cursor->row] = top() d->headerOffsetY = top() - d->headerRowPositions[0]; d->rowPositions[cursor->row] = d->headerRowPositions[d->headerRows] + d->headerOffsetY; // headerOffsetX should also be set d->headerOffsetX = d->columnPositions[0] - cursor->headerPositionX; } bool complete = first; qreal topBorderWidth = 0; qreal bottomBorderWidth = 0; qreal dummyWidth = 0; collectBorderThicknesss(cursor->row - 1, dummyWidth, topBorderWidth); collectBorderThicknesss(cursor->row, topBorderWidth, bottomBorderWidth); do { qreal nextBottomBorderWidth = 0; collectBorderThicknesss(cursor->row+1, bottomBorderWidth, nextBottomBorderWidth); d->lastRowHasSomething = false; complete = layoutRow(cursor, topBorderWidth, bottomBorderWidth); setBottom(d->rowPositions[cursor->row + 1] + bottomBorderWidth); topBorderWidth = bottomBorderWidth; bottomBorderWidth = nextBottomBorderWidth; if (complete) { setVirginPage(false); cursor->row++; } } while (complete && cursor->row < d->table->rows()); if (cursor->row == d->table->rows()) { d->lastRowHasSomething = false; } if (first) { // were we at the beginning of the table for (int row = 0; row < d->headerRows; ++row) { // Copy header rows cursor->headerRowPositions[row] = d->rowPositions[row]; d->headerRowPositions[row] = d->rowPositions[row]; for (int col = 0; col < d->table->columns(); ++col) { cursor->headerCellAreas[row][col] = d->cellAreas[row][col]; } } if (d->headerRows) { // Also set the position of the border below headers cursor->headerRowPositions[d->headerRows] = d->rowPositions[d->headerRows]; d->headerRowPositions[d->headerRows] = d->rowPositions[d->headerRows]; } cursor->headerPositionX = d->columnPositions[0]; if (!virginPage() && d->totalMisFit) { //if we couldn't fit the header rows plus some then don't even try cursor->row = 0; nukeRow(cursor); } } d->endOfArea = new TableIterator(cursor); return complete; } void KoTextLayoutTableArea::layoutColumns() { QTextTableFormat tableFormat = d->table->format(); d->columnPositions.resize(d->table->columns() + 1); d->columnWidths.resize(d->table->columns() + 1); // Table width. d->tableWidth = 0; qreal parentWidth = right() - left(); if (tableFormat.width().rawValue() == 0 || tableFormat.alignment() == Qt::AlignJustify) { // We got a zero width value or alignment is justify, so use 100% of parent. d->tableWidth = parentWidth - tableFormat.leftMargin() - tableFormat.rightMargin(); } else { if (tableFormat.width().type() == QTextLength::FixedLength) { // Fixed length value, so use the raw value directly. d->tableWidth = tableFormat.width().rawValue(); } else if (tableFormat.width().type() == QTextLength::PercentageLength) { // Percentage length value, so use a percentage of parent width. d->tableWidth = tableFormat.width().rawValue() * (parentWidth / 100) - tableFormat.leftMargin() - tableFormat.rightMargin(); } else { // Unknown length type, so use 100% of parent. d->tableWidth = parentWidth - tableFormat.leftMargin() - tableFormat.rightMargin(); } } // Column widths. qreal availableWidth = d->tableWidth; // Width available for columns. QList fixedWidthColumns; // List of fixed width columns. QList relativeWidthColumns; // List of relative width columns. qreal relativeWidthSum = 0; // Sum of relative column width values. int numNonStyleColumns = 0; for (int col = 0; col < d->table->columns(); ++col) { KoTableColumnStyle columnStyle = d->carsManager.columnStyle(col); if (columnStyle.hasProperty(KoTableColumnStyle::RelativeColumnWidth)) { // Relative width specified. Will be handled in the next loop. d->columnWidths[col] = 0.0; relativeWidthColumns.append(col); relativeWidthSum += columnStyle.relativeColumnWidth(); } else if (columnStyle.hasProperty(KoTableColumnStyle::ColumnWidth)) { // Only width specified, so use it. d->columnWidths[col] = columnStyle.columnWidth(); fixedWidthColumns.append(col); availableWidth -= columnStyle.columnWidth(); } else { // Neither width nor relative width specified. d->columnWidths[col] = 0.0; relativeWidthColumns.append(col); // handle it as a relative width column without asking for anything ++numNonStyleColumns; } } // Handle the case that the fixed size columns are larger then the defined table width if (availableWidth < 0.0) { if (tableFormat.width().rawValue() == 0 && fixedWidthColumns.count() > 0) { // If not table width was defined then we need to scale down the fixed size columns so they match // into the width of the table. qreal diff = (-availableWidth) / qreal(fixedWidthColumns.count()); Q_FOREACH (int col, fixedWidthColumns) { d->columnWidths[col] = qMax(qreal(0.0), d->columnWidths[col] - diff); } } availableWidth = 0.0; } // Calculate width to those columns that don't actually request it qreal widthForNonWidthColumn = ((1.0 - qMin(relativeWidthSum, 1.0)) * availableWidth); availableWidth -= widthForNonWidthColumn; // might as well do this calc before dividing by numNonStyleColumns if (numNonStyleColumns > 0 && widthForNonWidthColumn > 0.0) { widthForNonWidthColumn /= numNonStyleColumns; } // Relative column widths have now been summed up and can be distributed. foreach (int col, relativeWidthColumns) { KoTableColumnStyle columnStyle = d->carsManager.columnStyle(col); if (columnStyle.hasProperty(KoTableColumnStyle::RelativeColumnWidth) || columnStyle.hasProperty(KoTableColumnStyle::ColumnWidth)) { d->columnWidths[col] = qMax(columnStyle.relativeColumnWidth() * availableWidth / relativeWidthSum, 0.0); } else { d->columnWidths[col] = widthForNonWidthColumn; } } // Column positions. qreal columnPosition = left(); qreal columnOffset = tableFormat.leftMargin(); if (tableFormat.alignment() == Qt::AlignRight) { // Table is right-aligned, so add all of the remaining space. columnOffset += parentWidth - d->tableWidth; } if (tableFormat.alignment() == Qt::AlignHCenter) { // Table is centered, so add half of the remaining space. columnOffset += (parentWidth - d->tableWidth) / 2; } for (int col = 0; col < d->columnPositions.size(); ++col) { d->columnPositions[col] = columnPosition + columnOffset; // Increment by this column's width. columnPosition += d->columnWidths[col]; } // Borders can be outside of the cell (outer-borders) in which case it's need // to take them into account to not cut content off. qreal leftBorder = 0.0; qreal rightBorder = 0.0; for (int row = 0; row < d->table->rows(); ++row) { QTextTableCell leftCell = d->table->cellAt(row, 0); KoTableCellStyle leftCellStyle = d->effectiveCellStyle(leftCell); leftBorder = qMax(leftBorder, leftCellStyle.leftOuterBorderWidth()); QTextTableCell rightCell = d->table->cellAt(row, d->table->columns() - 1); KoTableCellStyle rightCellStyle = d->effectiveCellStyle(rightCell); rightBorder = qMax(rightBorder, rightCellStyle.rightOuterBorderWidth()); } expandBoundingLeft(d->columnPositions[0] - leftBorder); expandBoundingRight(d->columnPositions[d->table->columns()] + rightBorder + leftBorder); } void KoTextLayoutTableArea::collectBorderThicknesss(int row, qreal &topBorderWidth, qreal &bottomBorderWidth) { int col = 0; if (d->collapsing && row >= 0 && row < d->table->rows()) { // let's collect the border info while (col < d->table->columns()) { QTextTableCell cell = d->table->cellAt(row, col); if (row == cell.row() + cell.rowSpan() - 1) { /* * This cell ends vertically in this row, and hence should * contribute to the bottom border. */ KoTableCellStyle cellStyle = d->effectiveCellStyle(cell); topBorderWidth = qMax(cellStyle.topBorderWidth(), topBorderWidth); bottomBorderWidth = qMax(cellStyle.bottomBorderWidth(), bottomBorderWidth); } col += cell.columnSpan(); // Skip across column spans. } } } void KoTextLayoutTableArea::nukeRow(TableIterator *cursor) { for (int column = 0; column < d->table->columns(); ++column) { delete d->cellAreas[cursor->row][column]; d->cellAreas[cursor->row][column] = 0; delete cursor->frameIterators[column]; cursor->frameIterators[column] = 0; } d->lastRowHasSomething = false; } bool KoTextLayoutTableArea::layoutRow(TableIterator *cursor, qreal topBorderWidth, qreal bottomBorderWidth) { int row = cursor->row; Q_ASSERT(row >= 0); Q_ASSERT(row < d->table->rows()); QTextTableFormat tableFormat = d->table->format(); /* * Implementation Note: * * An undocumented behavior of QTextTable::cellAt is that requesting a * cell that is covered by a spanning cell will return the cell that * spans over the requested cell. Example: * * +------------+------------+ * | | | * | | | * | +------------+ * | | | * | | | * +------------+------------+ * * table.cellAt(1, 0).row() // Will return 0. * * In the code below, we rely on this behavior to determine wheather * a cell "vertically" ends in the current row, as those are the only * cells that should contribute to the row height. */ KoTableRowStyle rowStyle = d->carsManager.rowStyle(row); qreal rowHeight = rowStyle.rowHeight(); bool rowHasExactHeight = rowStyle.hasProperty(KoTableRowStyle::RowHeight); qreal rowBottom; if (rowHasExactHeight) { rowBottom = d->rowPositions[row] + rowHeight; } else { rowBottom = d->rowPositions[row] + rowStyle.minimumRowHeight(); } if (rowBottom > maximumAllowedBottom()) { d->rowPositions[row+1] = d->rowPositions[row]; if (cursor->row > d->startOfArea->row) { cursor->row--; layoutMergedCellsNotEnding(cursor, topBorderWidth, bottomBorderWidth, rowBottom); cursor->row++; } return false; // we can't honour minimum or fixed height so don't even try } bool allCellsFullyDone = true; bool anyCellTried = false; bool noCellsFitted = true; int col = 0; while (col < d->table->columns()) { // Get the cell format. QTextTableCell cell = d->table->cellAt(row, col); if (row == cell.row() + cell.rowSpan() - 1) { /* * This cell ends vertically in this row, and hence should * contribute to the row height. */ bool ignoreMisFittingCell = false; KoTableCellStyle cellStyle = d->effectiveCellStyle(cell); anyCellTried = true; qreal maxBottom = maximumAllowedBottom(); qreal requiredRowHeight = cellStyle.bottomPadding() + cellStyle.bottomPadding(); if (rowHasExactHeight) { maxBottom = qMin(d->rowPositions[row] + rowHeight, maxBottom); } maxBottom -= cellStyle.bottomPadding(); qreal areaTop = d->rowPositions[qMax(cell.row(), d->startOfArea->row)] + cellStyle.topPadding(); if (d->collapsing) { areaTop += topBorderWidth; maxBottom -= bottomBorderWidth; requiredRowHeight += bottomBorderWidth + topBorderWidth; } else { areaTop += cellStyle.topBorderWidth(); maxBottom -= cellStyle.bottomBorderWidth(); requiredRowHeight += cellStyle.bottomBorderWidth() + cellStyle.topBorderWidth(); } if (rowHasExactHeight && (rowHeight < requiredRowHeight)) { ignoreMisFittingCell = true; } if (maxBottom < areaTop && !ignoreMisFittingCell) { d->rowPositions[row+1] = d->rowPositions[row]; nukeRow(cursor); if (cursor->row > d->startOfArea->row) { cursor->row--; layoutMergedCellsNotEnding(cursor, topBorderWidth, bottomBorderWidth, rowBottom); cursor->row++; } return false; // we can't honour the borders so give up doing row } KoTextLayoutArea *cellArea = new KoTextLayoutArea(this, documentLayout()); d->cellAreas[cell.row()][cell.column()] = cellArea; qreal left = d->columnPositions[col] + cellStyle.leftPadding() + cellStyle.leftInnerBorderWidth(); qreal right = qMax(left, d->columnPositions[col+cell.columnSpan()] - cellStyle.rightPadding() - cellStyle.rightInnerBorderWidth()); cellArea->setReferenceRect( left, right, areaTop, maxBottom); cellArea->setVirginPage(virginPage()); cellArea->setLayoutEnvironmentResctictions(true, true); FrameIterator *cellCursor = cursor->frameIterator(col); bool cellFully = cellArea->layout(cellCursor); allCellsFullyDone = allCellsFullyDone && (cellFully || rowHasExactHeight); noCellsFitted = noCellsFitted && (cellArea->top() >= cellArea->bottom()) && !ignoreMisFittingCell; if (!rowHasExactHeight) { /* * Now we know how much height this cell contributes to the row, * and can determine wheather the row height will grow. */ if (d->collapsing) { rowBottom = qMax(cellArea->bottom() + cellStyle.bottomPadding(), rowBottom); } else { rowBottom = qMax(cellArea->bottom() + cellStyle.bottomPadding() + cellStyle.bottomBorderWidth(), rowBottom); } rowBottom = qMax(rowBottom, documentLayout()->maxYOfAnchoredObstructions(cell.firstCursorPosition().position(), cell.lastCursorPosition().position())); } d->lastRowHasSomething = true; // last row contains something (even if empty) } col += cell.columnSpan(); // Skip across column spans. } if (allCellsFullyDone) { for (col = 0; col < d->table->columns(); ++col) { QTextTableCell cell = d->table->cellAt(row, col); if (row == cell.row() + cell.rowSpan() - 1) { delete cursor->frameIterators[col]; cursor->frameIterators[col] = 0; } } } if (noCellsFitted && row <= d->headerRows) { d->totalMisFit = true; } if (anyCellTried && noCellsFitted && !rowHasExactHeight && !allCellsFullyDone) { d->rowPositions[row+1] = d->rowPositions[row]; nukeRow(cursor); if (cursor->row > d->startOfArea->row) { cursor->row--; layoutMergedCellsNotEnding(cursor, topBorderWidth, bottomBorderWidth, rowBottom); cursor->row++; } return false; // we can't honour the anything inside so give up doing row } if (!allCellsFullyDone) { layoutMergedCellsNotEnding(cursor, topBorderWidth, bottomBorderWidth, rowBottom); } else { // Cells all ended naturally, so we can now do vertical alignment // Stop! Other odf implementors also only do it if all cells are fully done col = 0; while (col < d->table->columns()) { QTextTableCell cell = d->table->cellAt(row, col); if (row == cell.row() + cell.rowSpan() - 1) { // cell ended in this row KoTextLayoutArea *cellArea = d->cellAreas[cell.row()][cell.column()]; KoTableCellStyle cellStyle = d->effectiveCellStyle(cell); if (cellStyle.alignment() & Qt::AlignBottom) { if (true /*FIXME test no page based shapes interfering*/) { cellArea->setVerticalAlignOffset(rowBottom - cellArea->bottom()); } } if (cellStyle.alignment() & Qt::AlignVCenter) { if (true /*FIXME test no page based shapes interfering*/) { cellArea->setVerticalAlignOffset((rowBottom - cellArea->bottom()) / 2); } } } col += cell.columnSpan(); // Skip across column spans. } } // Adjust Y position of NEXT row. // This is nice since the outside layout routine relies on the next row having a correct y position // the first row y position is set in layout() d->rowPositions[row+1] = rowBottom; return allCellsFullyDone; } bool KoTextLayoutTableArea::layoutMergedCellsNotEnding(TableIterator *cursor, qreal topBorderWidth, qreal bottomBorderWidth, qreal rowBottom) { Q_UNUSED(topBorderWidth) Q_UNUSED(bottomBorderWidth) // Let's make sure all merged cells in this row, that don't end in this row get's a layout int row = cursor->row; int col = 0; while (col < d->table->columns()) { QTextTableCell cell = d->table->cellAt(row, col); if (row != cell.row() + cell.rowSpan() - 1) { // TODO do all of the following like in layoutRow() KoTableCellStyle cellStyle = d->effectiveCellStyle(cell); KoTextLayoutArea *cellArea = new KoTextLayoutArea(this, documentLayout()); d->cellAreas[cell.row()][cell.column()] = cellArea; qreal left = d->columnPositions[col] + cellStyle.leftPadding() + cellStyle.leftInnerBorderWidth(); qreal right = qMax(left, d->columnPositions[col+cell.columnSpan()] - cellStyle.rightPadding() - cellStyle.rightInnerBorderWidth()); cellArea->setReferenceRect( left, right, d->rowPositions[qMax(cell.row(), d->startOfArea->row)] + cellStyle.topPadding() + cellStyle.topBorderWidth(), rowBottom - cellStyle.bottomPadding() - cellStyle.bottomBorderWidth()); cellArea->setVirginPage(virginPage()); cellArea->setLayoutEnvironmentResctictions(true, true); FrameIterator *cellCursor = cursor->frameIterator(col); cellArea->layout(cellCursor); if (cellArea->top() < cellArea->bottom() && row == d->headerRows) { d->totalMisFit = false; } } col += cell.columnSpan(); // Skip across column spans. } return true; } void KoTextLayoutTableArea::paint(QPainter *painter, const KoTextDocumentLayout::PaintContext &context) { if (d->startOfArea == 0) // We have not been layouted yet return; int lastRow = d->endOfArea->row; if (d->lastRowHasSomething == false) { --lastRow; } if (lastRow < d->startOfArea->row) { return; // empty } int firstRow = qMax(d->startOfArea->row, d->headerRows); // Draw table background qreal topY = d->headerRows ? d->rowPositions[0] : d->rowPositions[firstRow]; QRectF tableRect(d->columnPositions[0], topY, d->tableWidth, d->headerRowPositions[d->headerRows] - d->headerRowPositions[0] + d->rowPositions[lastRow+1] - d->rowPositions[firstRow]); painter->fillRect(tableRect, d->table->format().background()); KoTextDocumentLayout::PaintContext cellContext = context; QColor tableBackground = context.background; if (d->table->format().hasProperty(QTextFormat::BackgroundBrush)) { tableBackground = d->table->format().background().color(); } // Draw header row backgrounds for (int row = 0; row < d->headerRows; ++row) { QRectF rowRect(d->columnPositions[0], d->headerRowPositions[row], d->tableWidth, d->headerRowPositions[row+1] - d->headerRowPositions[row]); KoTableRowStyle rowStyle = d->carsManager.rowStyle(row); rowRect.translate(0, d->headerOffsetY); painter->fillRect(rowRect, rowStyle.background()); } // Draw plain row backgrounds for (int row = firstRow; row <= lastRow; ++row) { QRectF rowRect(d->columnPositions[0], d->rowPositions[row], d->tableWidth, d->rowPositions[row+1] - d->rowPositions[row]); KoTableRowStyle rowStyle = d->carsManager.rowStyle(row); painter->fillRect(rowRect, rowStyle.background()); } QSet > visitedCells; // Draw cell backgrounds and contents. for (int row = firstRow; row <= lastRow; ++row) { for (int column = 0; column < d->table->columns(); ++column) { QTextTableCell tableCell = d->table->cellAt(row, column); int testRow = (row == firstRow ? tableCell.row() : row); if (d->cellAreas[testRow][column] && !visitedCells.contains(QPair(testRow, column))) { cellContext.background = tableBackground; QBrush bgBrush = d->effectiveCellStyle(tableCell).background(); if (bgBrush != Qt::NoBrush) { cellContext.background = bgBrush.color(); } paintCell(painter, cellContext, tableCell, d->cellAreas[testRow][column]); visitedCells.insert(QPair(testRow, column)); } } } painter->translate(0, d->headerOffsetY); QVector accuBlankBorders; bool hasAntialiasing = painter->testRenderHint(QPainter::Antialiasing); // Draw header row cell backgrounds and contents. for (int row = 0; row < d->headerRows; ++row) { for (int column = 0; column < d->table->columns(); ++column) { QTextTableCell tableCell = d->table->cellAt(row, column); int testRow = row == firstRow ? tableCell.row() : row; if (d->cellAreas[testRow][column]) { cellContext.background = tableBackground; QBrush bgBrush = d->effectiveCellStyle(tableCell).background(); if (bgBrush != Qt::NoBrush) { cellContext.background = bgBrush.color(); } paintCell(painter, cellContext, tableCell, d->cellAreas[testRow][column]); } } } // Draw header row cell borders.(need to be second step so nabour cells don't overwrite) for (int row = 0; row < d->headerRows; ++row) { for (int column = 0; column < d->table->columns(); ++column) { QTextTableCell tableCell = d->table->cellAt(row, column); int testRow = row == firstRow ? tableCell.row() : row; if (d->cellAreas[testRow][column]) { painter->setRenderHint(QPainter::Antialiasing, true); paintCellBorders(painter, context, tableCell, false, lastRow, &accuBlankBorders); painter->setRenderHint(QPainter::Antialiasing, hasAntialiasing); } } } for (int i = 0; i < accuBlankBorders.size(); ++i) { accuBlankBorders[i].translate(0, d->headerOffsetY); } painter->translate(0, -d->headerOffsetY); // Draw cell borders. bool topRow = !d->headerRows && firstRow != 0; // are we top row in this area painter->setRenderHint(QPainter::Antialiasing, true); visitedCells.clear(); for (int row = firstRow; row <= lastRow; ++row) { for (int column = 0; column < d->table->columns(); ++column) { QTextTableCell tableCell = d->table->cellAt(row, column); int testRow = row == firstRow ? tableCell.row() : row; if (d->cellAreas[testRow][column] && !visitedCells.contains(QPair(testRow, column))) { paintCellBorders(painter, context, tableCell, topRow, lastRow, &accuBlankBorders); visitedCells.insert(QPair(testRow, column)); } } topRow = false; } painter->setRenderHint(QPainter::Antialiasing, hasAntialiasing); if (context.showTableBorders) { QPen pen(painter->pen()); painter->setPen(QPen(QColor(0,0,0,96))); painter->drawLines(accuBlankBorders); painter->setPen(pen); } } void KoTextLayoutTableArea::paintCell(QPainter *painter, const KoTextDocumentLayout::PaintContext &context, const QTextTableCell &tableCell, KoTextLayoutArea *frameArea) { int row = tableCell.row(); int column = tableCell.column(); // This is an actual cell we want to draw, and not a covered one. QRectF bRect(cellBoundingRect(tableCell)); painter->save(); painter->setClipRect(bRect, Qt::IntersectClip); // Possibly paint the background of the cell QBrush background(d->effectiveCellStyle(tableCell).background()); if (background != Qt::NoBrush) { painter->fillRect(bRect, background); } // Possibly paint the selection of the entire cell if (context.showSelections) { Q_FOREACH (const QAbstractTextDocumentLayout::Selection & selection, context.textContext.selections) { QTextTableCell startTableCell = d->table->cellAt(selection.cursor.selectionStart()); QTextTableCell endTableCell = d->table->cellAt(selection.cursor.selectionEnd()); if (startTableCell.isValid() && startTableCell != endTableCell) { int selectionRow; int selectionColumn; int selectionRowSpan; int selectionColumnSpan; selection.cursor.selectedTableCells(&selectionRow, &selectionRowSpan, &selectionColumn, &selectionColumnSpan); if (row >= selectionRow && column>=selectionColumn && row < selectionRow + selectionRowSpan && column < selectionColumn + selectionColumnSpan) { painter->fillRect(bRect, selection.format.background()); } } else if (selection.cursor.selectionStart() < d->table->firstPosition() && selection.cursor.selectionEnd() > d->table->lastPosition()) { painter->fillRect(bRect, selection.format.background()); } } } if (row < d->headerRows) { painter->translate(d->headerOffsetX, 0); } // Paint the content of the cellArea frameArea->paint(painter, context); painter->restore(); } void KoTextLayoutTableArea::paintCellBorders(QPainter *painter, const KoTextDocumentLayout::PaintContext &context, const QTextTableCell &tableCell, bool topRow, int lastRow, QVector *accuBlankBorders) { Q_UNUSED(context); int row = tableCell.row(); int column = tableCell.column(); // This is an actual cell we want to draw, and not a covered one. KoTableCellStyle cellStyle = d->effectiveCellStyle(tableCell); KoTextLayoutCellHelper cellStyleHelper(cellStyle); QRectF bRect = cellBoundingRect(tableCell); if (d->collapsing) { // First the horizontal borders if (row == 0) { cellStyleHelper.drawTopHorizontalBorder(*painter, bRect.x(), bRect.y(), bRect.width(), accuBlankBorders); } if (topRow && row != 0) { // in collapsing mode we need to also paint the top border of the area int c = column; while (c < column + tableCell.columnSpan()) { QTextTableCell tableCellAbove = d->table->cellAt(row - 1, c); QRectF aboveBRect = cellBoundingRect(tableCellAbove); qreal x = qMax(bRect.x(), aboveBRect.x()); qreal x2 = qMin(bRect.right(), aboveBRect.right()); KoTableCellStyle cellAboveStyle = d->effectiveCellStyle(tableCellAbove); KoTextLayoutCellHelper cellAboveStyleHelper(cellAboveStyle); cellAboveStyleHelper.drawSharedHorizontalBorder(*painter, cellStyle, x, bRect.y(), x2 - x, accuBlankBorders); c = tableCellAbove.column() + tableCellAbove.columnSpan(); } } if (row + tableCell.rowSpan() == d->table->rows()) { // we hit the bottom of the table so just draw the bottom border cellStyleHelper.drawBottomHorizontalBorder(*painter, bRect.x(), bRect.bottom(), bRect.width(), accuBlankBorders); } else { int c = column; while (c < column + tableCell.columnSpan()) { QTextTableCell tableCellBelow = d->table->cellAt(row == d->headerRows - 1 ? d->startOfArea->row : row + tableCell.rowSpan(), c); QRectF belowBRect = cellBoundingRect(tableCellBelow); qreal x = qMax(bRect.x(), belowBRect.x()); qreal x2 = qMin(bRect.right(), belowBRect.right()); KoTableCellStyle cellBelowStyle = d->effectiveCellStyle(tableCellBelow); cellStyleHelper.drawSharedHorizontalBorder(*painter, cellBelowStyle, x, bRect.bottom(), x2 - x, accuBlankBorders); c = tableCellBelow.column() + tableCellBelow.columnSpan(); } } // And then the same treatment for vertical borders if (column == 0) { cellStyleHelper.drawLeftmostVerticalBorder(*painter, bRect.x(), bRect.y(), bRect.height() + cellStyle.bottomOuterBorderWidth(), accuBlankBorders); } if (column + tableCell.columnSpan() == d->table->columns()) { // we hit the rightmost edge of the table so draw the rightmost border cellStyleHelper.drawRightmostVerticalBorder(*painter, bRect.right(), bRect.y(), bRect.height() + cellStyle.bottomOuterBorderWidth(), accuBlankBorders); } else { // we have cells to the right so draw sharedborders int r = row; while (r < row + tableCell.rowSpan() && r <= lastRow) { QTextTableCell tableCellRight = d->table->cellAt(r, column + tableCell.columnSpan()); KoTableCellStyle cellRightStyle = d->effectiveCellStyle(tableCellRight); QRectF rightBRect = cellBoundingRect(tableCellRight); qreal y = qMax(bRect.y(), rightBRect.y()); qreal y2 = qMin(bRect.bottom() + cellStyle.bottomOuterBorderWidth(), rightBRect.bottom() + cellRightStyle.bottomOuterBorderWidth()); cellStyleHelper.drawSharedVerticalBorder(*painter, cellRightStyle, bRect.right(), y, y2-y, accuBlankBorders); r = tableCellRight.row() + tableCellRight.rowSpan(); } } // Paint diagonal borders for current cell cellStyleHelper.paintDiagonalBorders(*painter, bRect); } else { // separating border model cellStyleHelper.paintBorders(*painter, bRect, accuBlankBorders); } } QRectF KoTextLayoutTableArea::cellBoundingRect(const QTextTableCell &cell) const { int row = cell.row(); int rowSpan = cell.rowSpan(); const int column = cell.column(); const int columnSpan = cell.columnSpan(); const qreal width = d->columnPositions[column + columnSpan] - d->columnPositions[column]; if (row >= d->headerRows) { int lastRow = d->endOfArea->row; if (d->lastRowHasSomething == false) { --lastRow; } if (lastRow < d->startOfArea->row) { return QRectF(); // empty } // Limit cell to within the area if (row < d->startOfArea->row) { rowSpan -= d->startOfArea->row - row; row += d->startOfArea->row - row; } if (row + rowSpan - 1 > lastRow) { rowSpan = lastRow - row + 1; } const qreal height = d->rowPositions[row + rowSpan] - d->rowPositions[row]; return QRectF(d->columnPositions[column], d->rowPositions[row], width, height); } else { return QRectF(d->columnPositions[column], d->headerRowPositions[row], width, d->headerRowPositions[row + rowSpan] - d->headerRowPositions[row]); } } diff --git a/plugins/flake/textshape/textlayout/RunAroundHelper.cpp b/plugins/flake/textshape/textlayout/RunAroundHelper.cpp index 9f9463768a..075632f2e8 100644 --- a/plugins/flake/textshape/textlayout/RunAroundHelper.cpp +++ b/plugins/flake/textshape/textlayout/RunAroundHelper.cpp @@ -1,312 +1,312 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2010-2011 KO Gmbh * * 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 "RunAroundHelper.h" #include "KoTextLayoutObstruction.h" #include "KoTextLayoutArea.h" const qreal RIDICULOUSLY_LARGE_NEGATIVE_INDENT = -5E6; #define MIN_WIDTH 0.01f RunAroundHelper::RunAroundHelper() { m_lineRect = QRectF(); m_updateValidObstructions = false; m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT; m_stayOnBaseline = false; } void RunAroundHelper::setLine(KoTextLayoutArea *area, const QTextLine &l) { m_area = area; line = l; } void RunAroundHelper::setObstructions(const QList &obstructions) { m_obstructions = obstructions; } bool RunAroundHelper::stayOnBaseline() const { return m_stayOnBaseline; } void RunAroundHelper::updateObstruction(KoTextLayoutObstruction *obstruction) { QRectF obstructionLineRect = obstruction->cropToLine(m_lineRect); if (obstructionLineRect.isValid()) { m_updateValidObstructions = true; } } bool RunAroundHelper::fit(const bool resetHorizontalPosition, bool isRightToLeft, const QPointF &position) { Q_ASSERT(line.isValid()); if (resetHorizontalPosition) { m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT; m_stayOnBaseline = false; } const qreal maxLineWidth = m_area->width(); // Make sure at least some text is fitted if the basic width (page, table cell, column) // is too small if (maxLineWidth <= 0.) { // we need to make sure that something like "line.setLineWidth(0.0);" is called here to prevent // the QTextLine from being removed again and leading at a later point to crashes. It seems // following if-condition including the setNumColumns call was added to do exactly that. But // it's not clear for what the if-condition was added. In any case that condition is wrong or // incompleted cause things can still crash with m_state->layout->text().length() == 0 (see the // document attached to bug 244411). //if (m_state->layout->lineCount() > 1 || m_state->layout->text().length() > 0) line.setNumColumns(1); line.setPosition(position); return false; } // Too little width because of wrapping is handled in the remainder of this method line.setLineWidth(maxLineWidth); const qreal maxLineHeight = line.height(); const qreal maxNaturalTextWidth = line.naturalTextWidth(); QRectF lineRect(position, QSizeF(maxLineWidth, maxLineHeight)); QRectF lineRectPart; qreal movedDown = 10; while (!lineRectPart.isValid()) { // The line rect could be split into no further linerectpart, so we have // to move the lineRect down a bit and try again // No line rect part was enough big, to fit the line. Recreate line rect further down // (and that is divided into new line parts). Line rect is at different position to // obstructions, so new parts are completely different. if there are no obstructions, then we // have only one line part which is full line rect lineRectPart = getLineRect(lineRect, maxNaturalTextWidth); if (!lineRectPart.isValid()) { m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT; lineRect = QRectF(position, QSizeF(maxLineWidth, maxLineHeight)); lineRect.setY(lineRect.y() + movedDown); movedDown += 10; } } if (isRightToLeft && line.naturalTextWidth() > m_textWidth) { // This can happen if spaces are added at the end of a line. Those spaces will not result in a // line-break. On left-to-right everything is fine and the spaces at the end are just not visible // but on right-to-left we need to adust the position cause spaces at the end are displayed at // the beginning and we need to make sure that doesn't result in us cutting of text at the right side. qreal diff = line.naturalTextWidth() - m_textWidth; lineRectPart.setX(lineRectPart.x() - diff); } line.setLineWidth(m_textWidth); line.setPosition(QPointF(lineRectPart.x(), lineRectPart.y())); checkEndOfLine(lineRectPart, maxNaturalTextWidth); return true; } void RunAroundHelper::validateObstructions() { m_validObstructions.clear(); foreach (KoTextLayoutObstruction *obstruction, m_obstructions) { validateObstruction(obstruction); } } void RunAroundHelper::validateObstruction(KoTextLayoutObstruction *obstruction) { QRectF obstructionLineRect = obstruction->cropToLine(m_lineRect); if (obstructionLineRect.isValid()) { m_validObstructions.append(obstruction); } } void RunAroundHelper::createLineParts() { m_lineParts.clear(); if (m_validObstructions.isEmpty()) { // Add whole line rect m_lineParts.append(m_lineRect); } else { QList lineParts; QRectF rightLineRect = m_lineRect; bool lastRightRectValid = false; - qSort(m_validObstructions.begin(), m_validObstructions.end(), KoTextLayoutObstruction::compareRectLeft); + std::sort(m_validObstructions.begin(), m_validObstructions.end(), KoTextLayoutObstruction::compareRectLeft); // Divide rect to parts, part can be invalid when obstructions are not disjunct. foreach (KoTextLayoutObstruction *validObstruction, m_validObstructions) { QRectF leftLineRect = validObstruction->getLeftLinePart(rightLineRect); lineParts.append(leftLineRect); QRectF lineRect = validObstruction->getRightLinePart(rightLineRect); if (lineRect.isValid()) { rightLineRect = lineRect; lastRightRectValid = true; } else { lastRightRectValid = false; } } if (lastRightRectValid) { lineParts.append(rightLineRect); } else { lineParts.append(QRect()); } Q_ASSERT(m_validObstructions.size() + 1 == lineParts.size()); // Select invalid parts because of wrap. for (int i = 0; i < m_validObstructions.size(); i++) { KoTextLayoutObstruction *obstruction = m_validObstructions.at(i); if (obstruction->noTextAround()) { lineParts.replace(i, QRectF()); lineParts.replace(i + 1, QRect()); } else if (obstruction->textOnLeft()) { lineParts.replace(i + 1, QRect()); } else if (obstruction->textOnRight()) { lineParts.replace(i, QRectF()); } else if (obstruction->textOnEnoughSides()) { QRectF leftRect = obstruction->getLeftLinePart(m_lineRect); QRectF rightRect = obstruction->getRightLinePart(m_lineRect); if (leftRect.width() < obstruction->runAroundThreshold()) { lineParts.replace(i, QRectF()); } if (rightRect.width() < obstruction->runAroundThreshold()) { lineParts.replace(i + 1, QRectF()); } } else if (obstruction->textOnBiggerSide()) { QRectF leftRect = obstruction->getLeftLinePart(m_lineRect); QRectF rightRect = obstruction->getRightLinePart(m_lineRect); if (leftRect.width() < rightRect.width()) { lineParts.replace(i, QRectF()); } else { lineParts.replace(i + 1, QRectF()); } } } // Filter invalid parts. foreach (const QRectF &rect, lineParts) { if (rect.isValid()) { m_lineParts.append(rect); } } } } QRectF RunAroundHelper::minimizeHeightToLeastNeeded(const QRectF &lineRect) { Q_ASSERT(line.isValid()); QRectF lineRectBase = lineRect; // Get width of one char or shape (as-char). m_textWidth = line.cursorToX(line.textStart() + 1) - line.cursorToX(line.textStart()); // Make sure width is not wider than the area allows. if (m_textWidth > m_area->width()) { m_textWidth = m_area->width(); } line.setLineWidth(m_textWidth); // Base linerect height on the width calculated above. lineRectBase.setHeight(line.height()); return lineRectBase; } void RunAroundHelper::updateLineParts(const QRectF &lineRect) { if (m_lineRect != lineRect || m_updateValidObstructions) { m_lineRect = lineRect; m_updateValidObstructions = false; validateObstructions(); createLineParts(); } } QRectF RunAroundHelper::getLineRectPart() { QRectF retVal; foreach (const QRectF &lineRectPart, m_lineParts) { if (m_horizontalPosition <= lineRectPart.left() && m_textWidth <= lineRectPart.width()) { retVal = lineRectPart; break; } } return retVal; } void RunAroundHelper::setMaxTextWidth(const QRectF &minLineRectPart, const qreal leftIndent, const qreal maxNaturalTextWidth) { Q_ASSERT(line.isValid()); qreal width = m_textWidth; qreal maxWidth = minLineRectPart.width() - leftIndent; qreal height; qreal maxHeight = minLineRectPart.height(); qreal widthDiff = maxWidth - width; widthDiff /= 2; while (width <= maxWidth && width <= maxNaturalTextWidth && widthDiff > MIN_WIDTH) { qreal linewidth = width + widthDiff; line.setLineWidth(linewidth); height = line.height(); if (height <= maxHeight) { width = linewidth; m_textWidth = width; } widthDiff /= 2; } } QRectF RunAroundHelper::getLineRect(const QRectF &lineRect, const qreal maxNaturalTextWidth) { Q_ASSERT(line.isValid()); const qreal leftIndent = lineRect.left(); QRectF minLineRect = minimizeHeightToLeastNeeded(lineRect); updateLineParts(minLineRect); // Get appropriate line rect part, to fit line, // using horizontal position, minimal height and width of line. QRectF lineRectPart = getLineRectPart(); if (lineRectPart.isValid()) { qreal x = lineRectPart.x(); qreal width = lineRectPart.width(); // Limit moved the left edge, keep the indent. if (leftIndent < x) { x += leftIndent; width -= leftIndent; } line.setLineWidth(width); // Check if line rect is big enough to fit line. // Otherwise find shorter width, what means also shorter height of line. // Condition is reverted. if (line.height() > lineRectPart.height()) { setMaxTextWidth(lineRectPart, leftIndent, maxNaturalTextWidth); } else { m_textWidth = width; } } return lineRectPart; } void RunAroundHelper::checkEndOfLine(const QRectF &lineRectPart, const qreal maxNaturalTextWidth) { if (lineRectPart == m_lineParts.last() || maxNaturalTextWidth <= lineRectPart.width()) { m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT; m_stayOnBaseline = false; } else { m_horizontalPosition = lineRectPart.right(); m_stayOnBaseline = true; } } diff --git a/plugins/flake/textshape/textlayout/ToCGenerator.cpp b/plugins/flake/textshape/textlayout/ToCGenerator.cpp index 9d852de526..055112082b 100644 --- a/plugins/flake/textshape/textlayout/ToCGenerator.cpp +++ b/plugins/flake/textshape/textlayout/ToCGenerator.cpp @@ -1,334 +1,334 @@ /* This file is part of the KDE project * Copyright (C) 2010 Thomas Zander * Copyright (C) 2010 Jean Nicolas Artaud * Copyright (C) 2011 Pavol Korinek * Copyright (C) 2011 Lukáš Tvrdý * Copyright (C) 2011 Ko GmbH * * 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 "ToCGenerator.h" #include #include "KoTextDocumentLayout.h" #include "KoTextLayoutRootArea.h" #include "DummyDocumentLayout.h" #include #include #include #include #include #include #include #include #include #include static const QString INVALID_HREF_TARGET = "INVALID_HREF"; ToCGenerator::ToCGenerator(QTextDocument *tocDocument, KoTableOfContentsGeneratorInfo *tocInfo) : QObject(tocDocument) , m_ToCDocument(tocDocument) , m_ToCInfo(tocInfo) , m_document(0) , m_documentLayout(0) { Q_ASSERT(tocDocument); Q_ASSERT(tocInfo); tocDocument->setUndoRedoEnabled(false); tocDocument->setDocumentLayout(new DummyDocumentLayout(tocDocument)); KoTextDocument(tocDocument).setRelativeTabs(tocInfo->m_relativeTabStopPosition); } ToCGenerator::~ToCGenerator() { delete m_ToCInfo; } void ToCGenerator::setBlock(const QTextBlock &block) { m_block = block; m_documentLayout = static_cast(m_block.document()->documentLayout()); m_document = m_documentLayout->document(); } QString ToCGenerator::fetchBookmarkRef(const QTextBlock &block, KoTextRangeManager *textRangeManager) { QHash ranges = textRangeManager->textRangesChangingWithin(block.document(), block.position(), block.position() + block.length(), block.position(), block.position() + block.length()); foreach (KoTextRange *range, ranges) { KoBookmark *bookmark = dynamic_cast(range); if (bookmark) { return bookmark->name(); } } return QString(); } static QString removeWhitespacePrefix(const QString& text) { int firstNonWhitespaceCharIndex = 0; const int length = text.length(); while (firstNonWhitespaceCharIndex < length && text.at(firstNonWhitespaceCharIndex).isSpace()) { firstNonWhitespaceCharIndex++; } return text.right(length - firstNonWhitespaceCharIndex); } bool ToCGenerator::generate() { if (!m_ToCInfo) return true; m_preservePagebreak = m_ToCDocument->begin().blockFormat().intProperty(KoParagraphStyle::BreakBefore) & KoText::PageBreak; m_success = true; QTextCursor cursor = m_ToCDocument->rootFrame()->lastCursorPosition(); cursor.setPosition(m_ToCDocument->rootFrame()->firstPosition(), QTextCursor::KeepAnchor); cursor.beginEditBlock(); cursor.insertBlock(QTextBlockFormat(), QTextCharFormat()); KoStyleManager *styleManager = KoTextDocument(m_document).styleManager(); if (!m_ToCInfo->m_indexTitleTemplate.text.isEmpty()) { KoParagraphStyle *titleStyle = styleManager->paragraphStyle(m_ToCInfo->m_indexTitleTemplate.styleId); // titleStyle == 0? then it might be in unused styles if (!titleStyle) { titleStyle = styleManager->unusedStyle(m_ToCInfo->m_indexTitleTemplate.styleId); // this should return true only for ToC template preview } if (!titleStyle) { titleStyle = styleManager->defaultTableOfcontentsTitleStyle(); } QTextBlock titleTextBlock = cursor.block(); titleStyle->applyStyle(titleTextBlock); cursor.insertText(m_ToCInfo->m_indexTitleTemplate.text); if (m_preservePagebreak) { QTextBlockFormat blockFormat; blockFormat.setProperty(KoParagraphStyle::BreakBefore, KoText::PageBreak); cursor.mergeBlockFormat(blockFormat); m_preservePagebreak = false; } cursor.insertBlock(QTextBlockFormat(), QTextCharFormat()); } // Add TOC // Iterate through all blocks to generate TOC QTextBlock block = m_document->rootFrame()->firstCursorPosition().block(); int blockId = 0; for (; block.isValid(); block = block.next()) { // Choose only TOC blocks if (m_ToCInfo->m_useOutlineLevel) { if (block.blockFormat().hasProperty(KoParagraphStyle::OutlineLevel)) { int level = block.blockFormat().intProperty(KoParagraphStyle::OutlineLevel); generateEntry(level, cursor, block, blockId); continue; } } if (m_ToCInfo->m_useIndexSourceStyles) { bool inserted = false; foreach (const IndexSourceStyles &indexSourceStyles, m_ToCInfo->m_indexSourceStyles) { foreach (const IndexSourceStyle &indexStyle, indexSourceStyles.styles) { if (indexStyle.styleId == block.blockFormat().intProperty(KoParagraphStyle::StyleId)) { generateEntry(indexSourceStyles.outlineLevel, cursor, block, blockId); inserted = true; break; } } if (inserted) break; } if (inserted) continue; } if (m_ToCInfo->m_useIndexMarks) { if (false) { generateEntry(1, cursor, block, blockId); continue; } } } cursor.endEditBlock(); m_documentLayout->documentChanged(m_block.position(),1,1); return m_success; } static bool compareTab(const QVariant &tab1, const QVariant &tab2) { return tab1.value().position < tab2.value().position; } void ToCGenerator::generateEntry(int outlineLevel, QTextCursor &cursor, QTextBlock &block, int &blockId) { KoStyleManager *styleManager = KoTextDocument(m_document).styleManager(); QString tocEntryText = block.text(); tocEntryText.remove(QChar::ObjectReplacementCharacter); // some headings contain tabs, replace all occurrences with spaces tocEntryText.replace('\t',' ').remove(0x200B); tocEntryText = removeWhitespacePrefix(tocEntryText); // Add only blocks with text if (!tocEntryText.isEmpty()) { KoParagraphStyle *tocTemplateStyle = 0; if (outlineLevel >= 1 && (outlineLevel-1) < m_ToCInfo->m_entryTemplate.size() && outlineLevel <= m_ToCInfo->m_outlineLevel) { // List's index starts with 0, outline level starts with 0 const TocEntryTemplate *tocEntryTemplate = &m_ToCInfo->m_entryTemplate.at(outlineLevel - 1); // ensure that we fetched correct entry template Q_ASSERT(tocEntryTemplate->outlineLevel == outlineLevel); if (tocEntryTemplate->outlineLevel != outlineLevel) { qDebug() << "TOC outline level not found correctly " << outlineLevel; } tocTemplateStyle = styleManager->paragraphStyle(tocEntryTemplate->styleId); if (tocTemplateStyle == 0) { tocTemplateStyle = styleManager->defaultTableOfContentsEntryStyle(outlineLevel); } QTextBlockFormat blockFormat; if (m_preservePagebreak) { blockFormat.setProperty(KoParagraphStyle::BreakBefore, KoText::PageBreak); m_preservePagebreak = false; } cursor.insertBlock(blockFormat, QTextCharFormat()); QTextBlock tocEntryTextBlock = cursor.block(); tocTemplateStyle->applyStyle( tocEntryTextBlock ); KoTextBlockData bd(block); // save the current style due to hyperlinks QTextCharFormat savedCharFormat = cursor.charFormat(); foreach (IndexEntry * entry, tocEntryTemplate->indexEntries) { switch(entry->name) { case IndexEntry::LINK_START: { //IndexEntryLinkStart *linkStart = static_cast(entry); QString target = fetchBookmarkRef(block, m_documentLayout->textRangeManager()); if (target.isNull()) { // generate unique name for the bookmark target = tocEntryText + "|outline" + QString::number(blockId); blockId++; // insert new KoBookmark QTextCursor blockCursor(block); KoBookmark *bookmark = new KoBookmark(blockCursor); bookmark->setName(target); m_documentLayout->textRangeManager()->insert(bookmark); } if (!target.isNull()) { // copy it to alter subset of properties QTextCharFormat linkCf(savedCharFormat); linkCf.setAnchor(true); linkCf.setProperty(KoCharacterStyle::AnchorType, KoCharacterStyle::Anchor); linkCf.setAnchorHref('#'+ target); QBrush foreground = linkCf.foreground(); foreground.setColor(Qt::blue); linkCf.setForeground(foreground); linkCf.setProperty(KoCharacterStyle::UnderlineStyle, KoCharacterStyle::SolidLine); linkCf.setProperty(KoCharacterStyle::UnderlineType, KoCharacterStyle::SingleLine); cursor.setCharFormat(linkCf); } break; } case IndexEntry::CHAPTER: { //IndexEntryChapter *chapter = static_cast(entry); cursor.insertText(bd.counterText()); break; } case IndexEntry::SPAN: { IndexEntrySpan *span = static_cast(entry); cursor.insertText(span->text); break; } case IndexEntry::TEXT: { //IndexEntryText *text = static_cast(entry); cursor.insertText(tocEntryText); break; } case IndexEntry::TAB_STOP: { IndexEntryTabStop *tabEntry = static_cast(entry); cursor.insertText("\t"); QTextBlockFormat blockFormat = cursor.blockFormat(); QList tabList = (blockFormat.property(KoParagraphStyle::TabPositions)).value >(); if (tabEntry->m_position.isEmpty()) { tabEntry->tab.position = KoTextLayoutArea::MaximumTabPos; } // else the position is already parsed into tab.position tabList.append(QVariant::fromValue(tabEntry->tab)); - qSort(tabList.begin(), tabList.end(), compareTab); + std::sort(tabList.begin(), tabList.end(), compareTab); blockFormat.setProperty(KoParagraphStyle::TabPositions, QVariant::fromValue >(tabList)); cursor.setBlockFormat(blockFormat); break; } case IndexEntry::PAGE_NUMBER: { //IndexEntryPageNumber *pageNumber = static_cast(entry); cursor.insertText(resolvePageNumber(block)); break; } case IndexEntry::LINK_END: { //IndexEntryLinkEnd *linkEnd = static_cast(entry); cursor.setCharFormat(savedCharFormat); break; } default:{ qDebug() << "New or unknown index entry"; break; } } }// foreach cursor.setCharFormat(savedCharFormat); // restore the cursor char format } } } QString ToCGenerator::resolvePageNumber(const QTextBlock &headingBlock) { KoTextDocumentLayout *layout = qobject_cast(m_document->documentLayout()); KoTextLayoutRootArea *rootArea = layout->rootAreaForPosition(headingBlock.position()); if (rootArea) { if (rootArea->page()) { return QString::number(rootArea->page()->visiblePageNumber()); } else qDebug()<<"had root but no page"; } m_success = false; return "###"; } diff --git a/plugins/impex/exr/kis_exr_layers_sorter.cpp b/plugins/impex/exr/kis_exr_layers_sorter.cpp index a127c467ef..bcfb3a978c 100644 --- a/plugins/impex/exr/kis_exr_layers_sorter.cpp +++ b/plugins/impex/exr/kis_exr_layers_sorter.cpp @@ -1,176 +1,176 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_exr_layers_sorter.h" #include #include #include "kis_image.h" #include "exr_extra_tags.h" #include "kis_kra_savexml_visitor.h" #include "kis_paint_layer.h" struct KisExrLayersSorter::Private { Private(const QDomDocument &_extraData, KisImageSP _image) : extraData(_extraData), image(_image) {} const QDomDocument &extraData; KisImageSP image; QMap pathToElementMap; QMap pathToOrderingMap; QMap nodeToOrderingMap; void createOrderingMap(); void processLayers(KisNodeSP root); void sortLayers(KisNodeSP root); }; QString getNodePath(KisNodeSP node) { KIS_ASSERT_RECOVER(node) { return "UNDEFINED"; } QString path; KisNodeSP parentNode = node->parent(); while(parentNode) { if (!path.isEmpty()) { path.prepend("."); } path.prepend(node->name()); node = parentNode; parentNode = node->parent(); } return path; } void KisExrLayersSorter::Private::createOrderingMap() { int index = 0; QDomElement el = extraData.documentElement().firstChildElement(); while (!el.isNull()) { QString path = el.attribute(EXR_NAME); pathToElementMap.insert(path, el); pathToOrderingMap.insert(path, index); el = el.nextSiblingElement(); index++; } } template T fetchMapValueLazy(const QMap &map, QString path) { if (map.contains(path)) return map[path]; typename QMap::const_iterator it = map.constBegin(); typename QMap::const_iterator end = map.constEnd(); for (; it != end; ++it) { if (it.key().startsWith(path)) { return it.value(); } } return T(); } void KisExrLayersSorter::Private::processLayers(KisNodeSP root) { if (root && root->parent()) { QString path = getNodePath(root); nodeToOrderingMap.insert(root, fetchMapValueLazy(pathToOrderingMap, path)); if (KisPaintLayer *paintLayer = dynamic_cast(root.data())) { KisSaveXmlVisitor::loadPaintLayerAttributes(pathToElementMap[path], paintLayer); } } KisNodeSP child = root->firstChild(); while (child) { processLayers(child); child = child->nextSibling(); } } struct CompareNodesFunctor { CompareNodesFunctor(const QMap &map) : m_nodeToOrderingMap(map) {} bool operator() (KisNodeSP lhs, KisNodeSP rhs) { return m_nodeToOrderingMap[lhs] < m_nodeToOrderingMap[rhs]; } private: const QMap &m_nodeToOrderingMap; }; void KisExrLayersSorter::Private::sortLayers(KisNodeSP root) { QList childNodes; // first move all the children to the list KisNodeSP child = root->firstChild(); while (child) { KisNodeSP lastChild = child; child = child->nextSibling(); childNodes.append(lastChild); image->removeNode(lastChild); } // sort the list - qStableSort(childNodes.begin(), childNodes.end(), CompareNodesFunctor(nodeToOrderingMap)); + std::stable_sort(childNodes.begin(), childNodes.end(), CompareNodesFunctor(nodeToOrderingMap)); // put the children back Q_FOREACH (KisNodeSP node, childNodes) { image->addNode(node, root, root->childCount()); } // recursive calls child = root->firstChild(); while (child) { sortLayers(child); child = child->nextSibling(); } } KisExrLayersSorter::KisExrLayersSorter(const QDomDocument &extraData, KisImageSP image) : m_d(new Private(extraData, image)) { KIS_ASSERT_RECOVER_RETURN(!extraData.isNull()); m_d->createOrderingMap(); m_d->processLayers(image->root()); m_d->sortLayers(image->root()); } KisExrLayersSorter::~KisExrLayersSorter() { } diff --git a/plugins/impex/psd/psd_pixel_utils.cpp b/plugins/impex/psd/psd_pixel_utils.cpp index 27c4627aed..d171c3ee46 100644 --- a/plugins/impex/psd/psd_pixel_utils.cpp +++ b/plugins/impex/psd/psd_pixel_utils.cpp @@ -1,717 +1,717 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "psd_pixel_utils.h" #include #include #include #include #include #include #include #include #include #include "kis_global.h" #include #include #include "psd_layer_record.h" #include #include "kis_iterator_ng.h" #include "config_psd.h" #ifdef HAVE_ZLIB #include "zlib.h" #endif namespace PsdPixelUtils { template typename Traits::channels_type convertByteOrder(typename Traits::channels_type value); // default implementation is undefined for every color space should be added manually template <> inline quint8 convertByteOrder(quint8 value) { return value; } template <> inline quint16 convertByteOrder(quint16 value) { return qFromBigEndian((quint16)value); } template <> inline float convertByteOrder(float value) { return qFromBigEndian((quint32)value); } template <> inline quint8 convertByteOrder(quint8 value) { return value; } template <> inline quint16 convertByteOrder(quint16 value) { return qFromBigEndian((quint16)value); } template <> inline quint32 convertByteOrder(quint32 value) { return qFromBigEndian((quint32)value); } template <> inline quint8 convertByteOrder(quint8 value) { return value; } template <> inline quint16 convertByteOrder(quint16 value) { return qFromBigEndian((quint16)value); } template <> inline quint32 convertByteOrder(quint32 value) { return qFromBigEndian((quint32)value); } template <> inline quint8 convertByteOrder(quint8 value) { return value; } template <> inline quint16 convertByteOrder(quint16 value) { return qFromBigEndian((quint16)value); } template <> inline float convertByteOrder(float value) { return qFromBigEndian((quint32)value); } template <> inline quint8 convertByteOrder(quint8 value) { return value; } template <> inline quint16 convertByteOrder(quint16 value) { return qFromBigEndian((quint16)value); } template <> inline float convertByteOrder(float value) { return qFromBigEndian((quint32)value); } template void readAlphaMaskPixel(const QMap &channelBytes, int col, quint8 *dstPtr); template <> void readAlphaMaskPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { *dstPtr = reinterpret_cast(channelBytes.first().constData())[col]; } template <> void readAlphaMaskPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { *dstPtr = reinterpret_cast(channelBytes.first().constData())[col] >> 8; } template <> void readAlphaMaskPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { *dstPtr = reinterpret_cast(channelBytes.first().constData())[col] * 255; } template inline typename Traits::channels_type readChannelValue(const QMap &channelBytes, quint16 channelId, int col, typename Traits::channels_type defaultValue) { typedef typename Traits::channels_type channels_type; if (channelBytes.contains(channelId)) { const QByteArray &bytes = channelBytes[channelId]; if (col < bytes.size()) { return convertByteOrder(reinterpret_cast(bytes.constData())[col]); } dbgFile << "col index out of range channelId: "<< channelId << " col:" << col; } return defaultValue; } template void readGrayPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { typedef typename Traits::Pixel Pixel; typedef typename Traits::channels_type channels_type; const channels_type unitValue = KoColorSpaceMathsTraits::unitValue; Pixel *pixelPtr = reinterpret_cast(dstPtr); pixelPtr->gray = readChannelValue(channelBytes, 0, col, unitValue);; pixelPtr->alpha = readChannelValue(channelBytes, -1, col, unitValue); } template void readRgbPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { typedef typename Traits::Pixel Pixel; typedef typename Traits::channels_type channels_type; const channels_type unitValue = KoColorSpaceMathsTraits::unitValue; Pixel *pixelPtr = reinterpret_cast(dstPtr); pixelPtr->blue = readChannelValue(channelBytes, 2, col, unitValue); pixelPtr->green = readChannelValue(channelBytes, 1, col, unitValue); pixelPtr->red = readChannelValue(channelBytes, 0, col, unitValue); pixelPtr->alpha = readChannelValue(channelBytes, -1, col, unitValue); } template void readCmykPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { typedef typename Traits::Pixel Pixel; typedef typename Traits::channels_type channels_type; const channels_type unitValue = KoColorSpaceMathsTraits::unitValue; Pixel *pixelPtr = reinterpret_cast(dstPtr); pixelPtr->cyan = unitValue - readChannelValue(channelBytes, 0, col, unitValue); pixelPtr->magenta = unitValue - readChannelValue(channelBytes, 1, col, unitValue); pixelPtr->yellow = unitValue - readChannelValue(channelBytes, 2, col, unitValue); pixelPtr->black = unitValue - readChannelValue(channelBytes, 3, col, unitValue); pixelPtr->alpha = readChannelValue(channelBytes, -1, col, unitValue); } template void readLabPixel(const QMap &channelBytes, int col, quint8 *dstPtr) { typedef typename Traits::Pixel Pixel; typedef typename Traits::channels_type channels_type; const channels_type unitValue = KoColorSpaceMathsTraits::unitValue; Pixel *pixelPtr = reinterpret_cast(dstPtr); pixelPtr->L = readChannelValue(channelBytes, 0, col, unitValue); pixelPtr->a = readChannelValue(channelBytes, 1, col, unitValue); pixelPtr->b = readChannelValue(channelBytes, 2, col, unitValue); pixelPtr->alpha = readChannelValue(channelBytes, -1, col, unitValue); } void readRgbPixelCommon(int channelSize, const QMap &channelBytes, int col, quint8 *dstPtr) { if (channelSize == 1) { readRgbPixel(channelBytes, col, dstPtr); } else if (channelSize == 2) { readRgbPixel(channelBytes, col, dstPtr); } else if (channelSize == 4) { readRgbPixel(channelBytes, col, dstPtr); } } void readGrayPixelCommon(int channelSize, const QMap &channelBytes, int col, quint8 *dstPtr) { if (channelSize == 1) { readGrayPixel(channelBytes, col, dstPtr); } else if (channelSize == 2) { readGrayPixel(channelBytes, col, dstPtr); } else if (channelSize == 4) { readGrayPixel(channelBytes, col, dstPtr); } } void readCmykPixelCommon(int channelSize, const QMap &channelBytes, int col, quint8 *dstPtr) { if (channelSize == 1) { readCmykPixel(channelBytes, col, dstPtr); } else if (channelSize == 2) { readCmykPixel(channelBytes, col, dstPtr); } else if (channelSize == 4) { readCmykPixel(channelBytes, col, dstPtr); } } void readLabPixelCommon(int channelSize, const QMap &channelBytes, int col, quint8 *dstPtr) { if (channelSize == 1) { readLabPixel(channelBytes, col, dstPtr); } else if (channelSize == 2) { readLabPixel(channelBytes, col, dstPtr); } else if (channelSize == 4) { readLabPixel(channelBytes, col, dstPtr); } } void readAlphaMaskPixelCommon(int channelSize, const QMap &channelBytes, int col, quint8 *dstPtr) { if (channelSize == 1) { readAlphaMaskPixel(channelBytes, col, dstPtr); } else if (channelSize == 2) { readAlphaMaskPixel(channelBytes, col, dstPtr); } else if (channelSize == 4) { readAlphaMaskPixel(channelBytes, col, dstPtr); } } /**********************************************************************/ /* Two functions copied from the abandoned PSDParse library (GPL) */ /* See: http://www.telegraphics.com.au/svn/psdparse/trunk/psd_zip.c */ /* Created by Patrick in 2007.02.02, libpsd@graphest.com */ /* Modifications by Toby Thain */ /**********************************************************************/ typedef bool psd_status; typedef quint8 psd_uchar; typedef int psd_int; typedef quint8 Bytef; psd_status psd_unzip_without_prediction(psd_uchar *src_buf, psd_int src_len, psd_uchar *dst_buf, psd_int dst_len) { #ifdef HAVE_ZLIB z_stream stream; psd_int state; memset(&stream, 0, sizeof(z_stream)); stream.data_type = Z_BINARY; stream.next_in = (Bytef *)src_buf; stream.avail_in = src_len; stream.next_out = (Bytef *)dst_buf; stream.avail_out = dst_len; if(inflateInit(&stream) != Z_OK) return 0; do { state = inflate(&stream, Z_PARTIAL_FLUSH); if(state == Z_STREAM_END) break; if(state == Z_DATA_ERROR || state != Z_OK) break; } while (stream.avail_out > 0); if (state != Z_STREAM_END && state != Z_OK) return 0; return 1; #endif /* HAVE_ZLIB */ return 0; } psd_status psd_unzip_with_prediction(psd_uchar *src_buf, psd_int src_len, psd_uchar *dst_buf, psd_int dst_len, psd_int row_size, psd_int color_depth) { psd_status status; int len; psd_uchar * buf; status = psd_unzip_without_prediction(src_buf, src_len, dst_buf, dst_len); if(!status) return status; buf = dst_buf; do { len = row_size; if (color_depth == 16) { while(--len) { buf[2] += buf[0] + ((buf[1] + buf[3]) >> 8); buf[3] += buf[1]; buf += 2; } buf += 2; dst_len -= row_size * 2; } else { while(--len) { *(buf + 1) += *buf; buf ++; } buf ++; dst_len -= row_size; } } while(dst_len > 0); return 1; } /**********************************************************************/ /* End of third party block */ /**********************************************************************/ QMap fetchChannelsBytes(QIODevice *io, QVector channelInfoRecords, int row, int width, int channelSize, bool processMasks) { const int uncompressedLength = width * channelSize; QMap channelBytes; Q_FOREACH (ChannelInfo *channelInfo, channelInfoRecords) { // user supplied masks are ignored here if (!processMasks && channelInfo->channelId < -1) continue; io->seek(channelInfo->channelDataStart + channelInfo->channelOffset); if (channelInfo->compressionType == Compression::Uncompressed) { channelBytes[channelInfo->channelId] = io->read(uncompressedLength); channelInfo->channelOffset += uncompressedLength; } else if (channelInfo->compressionType == Compression::RLE) { int rleLength = channelInfo->rleRowLengths[row]; QByteArray compressedBytes = io->read(rleLength); QByteArray uncompressedBytes = Compression::uncompress(uncompressedLength, compressedBytes, channelInfo->compressionType); channelBytes.insert(channelInfo->channelId, uncompressedBytes); channelInfo->channelOffset += rleLength; } else { QString error = QString("Unsupported Compression mode: %1").arg(channelInfo->compressionType); dbgFile << "ERROR: fetchChannelsBytes:" << error; throw KisAslReaderUtils::ASLParseException(error); } } return channelBytes; } typedef boost::function&, int, quint8*)> PixelFunc; void readCommon(KisPaintDeviceSP dev, QIODevice *io, const QRect &layerRect, QVector infoRecords, int channelSize, PixelFunc pixelFunc, bool processMasks) { KisOffsetKeeper keeper(io); if (layerRect.isEmpty()) { dbgFile << "Empty layer!"; return; } if (infoRecords.first()->compressionType == Compression::ZIP || infoRecords.first()->compressionType == Compression::ZIPWithPrediction) { const int numPixels = channelSize * layerRect.width() * layerRect.height(); QMap channelBytes; Q_FOREACH (ChannelInfo *info, infoRecords) { io->seek(info->channelDataStart); QByteArray compressedBytes = io->read(info->channelDataLength); QByteArray uncompressedBytes(numPixels, 0); bool status = false; if (infoRecords.first()->compressionType == Compression::ZIP) { status = psd_unzip_without_prediction((quint8*)compressedBytes.data(), compressedBytes.size(), (quint8*)uncompressedBytes.data(), uncompressedBytes.size()); } else { status = psd_unzip_with_prediction((quint8*)compressedBytes.data(), compressedBytes.size(), (quint8*)uncompressedBytes.data(), uncompressedBytes.size(), layerRect.width(), channelSize * 8); } if (!status) { QString error = QString("Failed to unzip channel data: id = %1, compression = %2").arg(info->channelId).arg(info->compressionType); dbgFile << "ERROR:" << error; dbgFile << " " << ppVar(info->channelId); dbgFile << " " << ppVar(info->channelDataStart); dbgFile << " " << ppVar(info->channelDataLength); dbgFile << " " << ppVar(info->compressionType); throw KisAslReaderUtils::ASLParseException(error); } channelBytes.insert(info->channelId, uncompressedBytes); } KisSequentialIterator it(dev, layerRect); int col = 0; do { pixelFunc(channelSize, channelBytes, col, it.rawData()); col++; } while(it.nextPixel()); } else { KisHLineIteratorSP it = dev->createHLineIteratorNG(layerRect.left(), layerRect.top(), layerRect.width()); for (int i = 0 ; i < layerRect.height(); i++) { QMap channelBytes; channelBytes = fetchChannelsBytes(io, infoRecords, i, layerRect.width(), channelSize, processMasks); for (qint64 col = 0; col < layerRect.width(); col++){ pixelFunc(channelSize, channelBytes, col, it->rawData()); it->nextPixel(); } it->nextRow(); } } } void readChannels(QIODevice *io, KisPaintDeviceSP device, psd_color_mode colorMode, int channelSize, const QRect &layerRect, QVector infoRecords) { switch (colorMode) { case Grayscale: readCommon(device, io, layerRect, infoRecords, channelSize, &readGrayPixelCommon, false); break; case RGB: readCommon(device, io, layerRect, infoRecords, channelSize, &readRgbPixelCommon, false); break; case CMYK: readCommon(device, io, layerRect, infoRecords, channelSize, &readCmykPixelCommon, false); break; case Lab: readCommon(device, io, layerRect, infoRecords, channelSize, &readLabPixelCommon, false); break; case Bitmap: case Indexed: case MultiChannel: case DuoTone: case COLORMODE_UNKNOWN: default: QString error = QString("Unsupported color mode: %1").arg(colorMode); throw KisAslReaderUtils::ASLParseException(error); } } void readAlphaMaskChannels(QIODevice *io, KisPaintDeviceSP device, int channelSize, const QRect &layerRect, QVector infoRecords) { KIS_SAFE_ASSERT_RECOVER_RETURN(infoRecords.size() == 1); readCommon(device, io, layerRect, infoRecords, channelSize, &readAlphaMaskPixelCommon, true); } void writeChannelDataRLE(QIODevice *io, const quint8 *plane, const int channelSize, const QRect &rc, const qint64 sizeFieldOffset, const qint64 rleBlockOffset, const bool writeCompressionType) { typedef KisAslWriterUtils::OffsetStreamPusher Pusher; QScopedPointer channelBlockSizeExternalTag; if (sizeFieldOffset >= 0) { channelBlockSizeExternalTag.reset(new Pusher(io, 0, sizeFieldOffset)); } if (writeCompressionType) { SAFE_WRITE_EX(io, (quint16)Compression::RLE); } const bool externalRleBlock = rleBlockOffset >= 0; // the start of RLE sizes block const qint64 channelRLESizePos = externalRleBlock ? rleBlockOffset : io->pos(); { QScopedPointer rleOffsetKeeper; if (externalRleBlock) { rleOffsetKeeper.reset(new KisOffsetKeeper(io)); io->seek(rleBlockOffset); } // write zero's for the channel lengths block for(int i = 0; i < rc.height(); ++i) { // XXX: choose size for PSB! const quint16 fakeRLEBLockSize = 0; SAFE_WRITE_EX(io, fakeRLEBLockSize); } } quint32 stride = channelSize * rc.width(); for (qint32 row = 0; row < rc.height(); ++row) { QByteArray uncompressed = QByteArray::fromRawData((const char*)plane + row * stride, stride); QByteArray compressed = Compression::compress(uncompressed, Compression::RLE); KisAslWriterUtils::OffsetStreamPusher rleExternalTag(io, 0, channelRLESizePos + row * sizeof(quint16)); if (io->write(compressed) != compressed.size()) { throw KisAslWriterUtils::ASLWriteException("Failed to write image data"); } } } inline void preparePixelForWrite(quint8 *dataPlane, int numPixels, int channelSize, int channelId, psd_color_mode colorMode) { // if the bitdepth > 8, place the bytes in the right order // if cmyk, invert the pixel value if (channelSize == 1) { if (channelId >= 0 && (colorMode == CMYK || colorMode == CMYK64)) { for (int i = 0; i < numPixels; ++i) { dataPlane[i] = 255 - dataPlane[i]; } } } else if (channelSize == 2) { quint16 val; for (int i = 0; i < numPixels; ++i) { quint16 *pixelPtr = reinterpret_cast(dataPlane) + i; val = *pixelPtr; val = qFromBigEndian(val); if (channelId >= 0 && (colorMode == CMYK || colorMode == CMYK64)) { val = quint16_MAX - val; } *pixelPtr = val; } } else if (channelSize == 4) { quint32 val; for (int i = 0; i < numPixels; ++i) { quint32 *pixelPtr = reinterpret_cast(dataPlane) + i; val = *pixelPtr; val = qFromBigEndian(val); if (channelId >= 0 && (colorMode == CMYK || colorMode == CMYK64)) { val = quint16_MAX - val; } *pixelPtr = val; } } } void writePixelDataCommon(QIODevice *io, KisPaintDeviceSP dev, const QRect &rc, psd_color_mode colorMode, int channelSize, bool alphaFirst, const bool writeCompressionType, QVector &writingInfoList) { // Empty rects must be processed separately on a higher level! KIS_ASSERT_RECOVER_RETURN(!rc.isEmpty()); QVector tmp = dev->readPlanarBytes(rc.x() - dev->x(), rc.y() - dev->y(), rc.width(), rc.height()); const KoColorSpace *colorSpace = dev->colorSpace(); QVector planes; { // prepare 'planes' array quint8 *alphaPlanePtr = 0; QList origChannels = colorSpace->channels(); Q_FOREACH (KoChannelInfo *ch, KoChannelInfo::displayOrderSorted(origChannels)) { int channelIndex = KoChannelInfo::displayPositionToChannelIndex(ch->displayPosition(), origChannels); quint8 *holder = 0; - qSwap(holder, tmp[channelIndex]); + std::swap(holder, tmp[channelIndex]); if (ch->channelType() == KoChannelInfo::ALPHA) { - qSwap(holder, alphaPlanePtr); + std::swap(holder, alphaPlanePtr); } else { planes.append(holder); } } if (alphaPlanePtr) { if (alphaFirst) { planes.insert(0, alphaPlanePtr); KIS_ASSERT_RECOVER_NOOP(writingInfoList.first().channelId == -1); } else { planes.append(alphaPlanePtr); KIS_ASSERT_RECOVER_NOOP( (writingInfoList.size() == planes.size() - 1) || (writingInfoList.last().channelId == -1)); } } // now planes are holding pointers to quint8 arrays tmp.clear(); } KIS_ASSERT_RECOVER_RETURN(planes.size() >= writingInfoList.size()); const int numPixels = rc.width() * rc.height(); // write down the planes try { for (int i = 0; i < writingInfoList.size(); i++) { const ChannelWritingInfo &info = writingInfoList[i]; dbgFile << "\tWriting channel" << i << "psd channel id" << info.channelId; preparePixelForWrite(planes[i], numPixels, channelSize, info.channelId, colorMode); dbgFile << "\t\tchannel start" << ppVar(io->pos()); writeChannelDataRLE(io, planes[i], channelSize, rc, info.sizeFieldOffset, info.rleBlockOffset, writeCompressionType); } } catch (KisAslWriterUtils::ASLWriteException &e) { qDeleteAll(planes); planes.clear(); throw KisAslWriterUtils::ASLWriteException(PREPEND_METHOD(e.what())); } qDeleteAll(planes); planes.clear(); } } diff --git a/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp b/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp index fa347dc9a3..ae9a3a5af9 100644 --- a/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp +++ b/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp @@ -1,987 +1,987 @@ /* This file is part of the KDE project * * Copyright (C) 2009 Thorsten Zachmann * Copyright (C) 2009 Jean-Nicolas Artaud * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ConnectionTool.h" #include #include #include #include "AddConnectionPointCommand.h" #include "RemoveConnectionPointCommand.h" #include "ChangeConnectionPointCommand.h" #include "MoveConnectionPointStrategy.h" #include "ConnectionPointWidget.h" #define TextShape_SHAPEID "TextShapeID" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_document_aware_spin_box_unit_manager.h" #include #include "kis_action_registry.h" #include #include #include #include #include #include ConnectionTool::ConnectionTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_editMode(Idle) , m_connectionType(KoConnectionShape::Standard) , m_currentShape(0) , m_activeHandle(-1) , m_currentStrategy(0) , m_oldSnapStrategies(0) , m_resetPaint(true) { QPixmap connectPixmap; connectPixmap.load(":/cursor_connect.png"); m_connectCursor = QCursor(connectPixmap, 4, 1); KisActionRegistry *actionRegistry = KisActionRegistry::instance(); m_editConnectionPoint = actionRegistry->makeQAction("toggle-edit-mode", this); m_editConnectionPoint->setCheckable(true); addAction("toggle-edit-mode", m_editConnectionPoint); m_alignPercent = actionRegistry->makeQAction("align-relative", this); m_alignPercent->setCheckable(true); addAction("align-relative", m_alignPercent); m_alignLeft = actionRegistry->makeQAction("align-left", this); m_alignLeft->setCheckable(true); addAction("align-left", m_alignLeft); m_alignCenterH = actionRegistry->makeQAction("align-centerh", this); m_alignCenterH->setCheckable(true); addAction("align-centerh", m_alignCenterH); m_alignRight = actionRegistry->makeQAction("align-right", this); m_alignRight->setCheckable(true); addAction("align-right", m_alignRight); m_alignTop = actionRegistry->makeQAction("align-top", this); m_alignTop->setCheckable(true); addAction("align-top", m_alignTop); m_alignCenterV = actionRegistry->makeQAction("align-centerv", this); m_alignCenterV->setCheckable(true); addAction("align-centerv", m_alignCenterV); m_alignBottom = actionRegistry->makeQAction("align-bottom", this); m_alignBottom->setCheckable(true); addAction("align-bottom", m_alignBottom); m_escapeAll = actionRegistry->makeQAction("escape-all", this); m_escapeAll->setCheckable(true); addAction("escape-all", m_escapeAll); m_escapeHorizontal = actionRegistry->makeQAction("escape-horizontal", this); m_escapeHorizontal->setCheckable(true); addAction("escape-horizontal", m_escapeHorizontal); m_escapeVertical = actionRegistry->makeQAction("escape-vertical", this); m_escapeVertical->setCheckable(true); addAction("escape-vertical", m_escapeVertical); m_escapeLeft = actionRegistry->makeQAction("escape-left", this); m_escapeLeft->setCheckable(true); addAction("escape-left", m_escapeLeft); m_escapeRight = actionRegistry->makeQAction("escape-right", this); m_escapeRight->setCheckable(true); addAction("escape-right", m_escapeRight); m_escapeUp = actionRegistry->makeQAction("escape-up", this); m_escapeUp->setCheckable(true); addAction("escape-up", m_escapeUp); m_escapeDown = actionRegistry->makeQAction("escape-down", this); m_escapeDown->setCheckable(true); addAction("escape-down", m_escapeDown); m_alignHorizontal = new QActionGroup(this); m_alignHorizontal->setExclusive(true); m_alignHorizontal->addAction(m_alignLeft); m_alignHorizontal->addAction(m_alignCenterH); m_alignHorizontal->addAction(m_alignRight); connect(m_alignHorizontal, SIGNAL(triggered(QAction*)), this, SLOT(horizontalAlignChanged())); m_alignVertical = new QActionGroup(this); m_alignVertical->setExclusive(true); m_alignVertical->addAction(m_alignTop); m_alignVertical->addAction(m_alignCenterV); m_alignVertical->addAction(m_alignBottom); connect(m_alignVertical, SIGNAL(triggered(QAction*)), this, SLOT(verticalAlignChanged())); m_alignRelative = new QActionGroup(this); m_alignRelative->setExclusive(true); m_alignRelative->addAction(m_alignPercent); connect(m_alignRelative, SIGNAL(triggered(QAction*)), this, SLOT(relativeAlignChanged())); m_escapeDirections = new QActionGroup(this); m_escapeDirections->setExclusive(true); m_escapeDirections->addAction(m_escapeAll); m_escapeDirections->addAction(m_escapeHorizontal); m_escapeDirections->addAction(m_escapeVertical); m_escapeDirections->addAction(m_escapeLeft); m_escapeDirections->addAction(m_escapeRight); m_escapeDirections->addAction(m_escapeUp); m_escapeDirections->addAction(m_escapeDown); connect(m_escapeDirections, SIGNAL(triggered(QAction*)), this, SLOT(escapeDirectionChanged())); connect(this, SIGNAL(connectionPointEnabled(bool)), m_alignHorizontal, SLOT(setEnabled(bool))); connect(this, SIGNAL(connectionPointEnabled(bool)), m_alignVertical, SLOT(setEnabled(bool))); connect(this, SIGNAL(connectionPointEnabled(bool)), m_alignRelative, SLOT(setEnabled(bool))); connect(this, SIGNAL(connectionPointEnabled(bool)), m_escapeDirections, SLOT(setEnabled(bool))); resetEditMode(); } ConnectionTool::~ConnectionTool() { } void ConnectionTool::paint(QPainter &painter, const KoViewConverter &converter) { // get the correctly sized rect for painting handles QRectF handleRect = handlePaintRect(QPointF()); painter.setRenderHint(QPainter::Antialiasing, true); if (m_currentStrategy) { painter.save(); m_currentStrategy->paint(painter, converter); painter.restore(); } QList shapes = canvas()->shapeManager()->shapes(); for (QList::const_iterator end = shapes.constBegin(); end != shapes.constEnd(); ++end) { KoShape *shape = *end; if (!dynamic_cast(shape)) { // only paint connection points of textShapes not inside a tos container and other shapes if (shape->shapeId() == TextShape_SHAPEID && dynamic_cast(shape->parent())) { continue; } painter.save(); painter.setPen(Qt::black); QTransform transform = shape->absoluteTransformation(0); KoShape::applyConversion(painter, converter); // Draw all the connection points of the shape KoConnectionPoints connectionPoints = shape->connectionPoints(); KoConnectionPoints::const_iterator cp = connectionPoints.constBegin(); KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd(); for (; cp != lastCp; ++cp) { if (shape == findNonConnectionShapeAtPosition(transform.map(cp.value().position))) { handleRect.moveCenter(transform.map(cp.value().position)); painter.setBrush(cp.key() == m_activeHandle && shape == m_currentShape ? Qt::red : Qt::white); painter.drawRect(handleRect); } } painter.restore(); } } // paint connection points or connection handles depending // on the shape the mouse is currently if (m_currentShape && m_editMode == EditConnection) { KoConnectionShape *connectionShape = dynamic_cast(m_currentShape); if (connectionShape) { int radius = handleRadius() + 1; int handleCount = connectionShape->handleCount(); for (int i = 0; i < handleCount; ++i) { KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, connectionShape, converter, radius); helper.setHandleStyle(i == m_activeHandle ? KisHandleStyle::highlightedPrimaryHandles() : KisHandleStyle::primarySelection()); connectionShape->paintHandle(helper, i); } } } } void ConnectionTool::repaintDecorations() { const qreal radius = handleRadius(); QRectF repaintRect; if (m_currentShape) { repaintRect = m_currentShape->boundingRect(); canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); KoConnectionShape *connectionShape = dynamic_cast(m_currentShape); if (!m_resetPaint && m_currentShape->isVisible(true) && !connectionShape) { // only paint connection points of textShapes not inside a tos container and other shapes if (!(m_currentShape->shapeId() == TextShape_SHAPEID && dynamic_cast(m_currentShape->parent()))) { KoConnectionPoints connectionPoints = m_currentShape->connectionPoints(); KoConnectionPoints::const_iterator cp = connectionPoints.constBegin(); KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd(); for (; cp != lastCp; ++cp) { repaintRect = handleGrabRect(m_currentShape->shapeToDocument(cp.value().position)); canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); } } } if (m_editMode == EditConnection) { if (connectionShape) { QPointF handlePos = connectionShape->handlePosition(m_activeHandle); handlePos = connectionShape->shapeToDocument(handlePos); repaintRect = handlePaintRect(handlePos); canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); } } } if (m_resetPaint) { QList shapes = canvas()->shapeManager()->shapes(); for (QList::const_iterator end = shapes.constBegin(); end != shapes.constEnd(); ++end) { KoShape *shape = *end; if (!dynamic_cast(shape)) { // only paint connection points of textShapes not inside a tos container and other shapes if (shape->shapeId() == TextShape_SHAPEID && dynamic_cast(shape->parent())) { continue; } KoConnectionPoints connectionPoints = shape->connectionPoints(); KoConnectionPoints::const_iterator cp = connectionPoints.constBegin(); KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd(); for (; cp != lastCp; ++cp) { repaintRect = handleGrabRect(shape->shapeToDocument(cp.value().position)); canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); } } } } m_resetPaint = false; } void ConnectionTool::mousePressEvent(KoPointerEvent *event) { if (!m_currentShape) { return; } KoShape *hitShape = findShapeAtPosition(event->point); int hitHandle = handleAtPoint(m_currentShape, event->point); if (m_editMode == EditConnection && hitHandle >= 0) { // create connection handle change strategy m_currentStrategy = new KoPathConnectionPointStrategy(this, dynamic_cast(m_currentShape), hitHandle); } else if (m_editMode == EditConnectionPoint) { if (hitHandle >= KoConnectionPoint::FirstCustomConnectionPoint) { // start moving custom connection point m_currentStrategy = new MoveConnectionPointStrategy(m_currentShape, hitHandle, this); } } else if (m_editMode == CreateConnection) { // create new connection shape, connect it to the active connection point // and start editing the new connection // create the new connection shape KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("KoConnectionShape"); KoShape *shape = factory->createDefaultShape(canvas()->shapeController()->resourceManager()); KoConnectionShape *connectionShape = dynamic_cast(shape); if (!connectionShape) { delete shape; resetEditMode(); return; } //set connection type connectionShape->setType(m_connectionType); // get the position of the connection point we start our connection from QPointF cp = m_currentShape->shapeToDocument(m_currentShape->connectionPoint(m_activeHandle).position); // move both handles to that point connectionShape->moveHandle(0, cp); connectionShape->moveHandle(1, cp); // connect the first handle of the connection shape to our connection point if (!connectionShape->connectFirst(m_currentShape, m_activeHandle)) { delete shape; resetEditMode(); return; } //add connector label connectionShape->createTextShape(canvas()->shapeController()->resourceManager()); connectionShape->setPlainText(QString()); // create the connection edit strategy from the path tool m_currentStrategy = new KoPathConnectionPointStrategy(this, connectionShape, 1); if (!m_currentStrategy) { delete shape; resetEditMode(); return; } // update our handle data setEditMode(m_editMode, shape, 1); // add connection shape to the shape manager so it gets painted canvas()->shapeManager()->addShape(connectionShape); } else { // pressing on a shape in idle mode switches to corresponding edit mode if (hitShape) { if (dynamic_cast(hitShape)) { int hitHandle = handleAtPoint(hitShape, event->point); setEditMode(EditConnection, hitShape, hitHandle); if (hitHandle >= 0) { // start editing connection shape m_currentStrategy = new KoPathConnectionPointStrategy(this, dynamic_cast(m_currentShape), m_activeHandle); } } } else { resetEditMode(); } } } void ConnectionTool::mouseMoveEvent(KoPointerEvent *event) { if (m_currentStrategy) { repaintDecorations(); if (m_editMode != EditConnection && m_editMode != CreateConnection) { QPointF snappedPos = canvas()->snapGuide()->snap(event->point, event->modifiers()); m_currentStrategy->handleMouseMove(snappedPos, event->modifiers()); } else { m_currentStrategy->handleMouseMove(event->point, event->modifiers()); } repaintDecorations(); } else if (m_editMode == EditConnectionPoint) { KoShape *hoverShape = findNonConnectionShapeAtPosition(event->point);//TODO exclude connectors, need snap guide maybe? if (hoverShape) { m_currentShape = hoverShape; Q_ASSERT(m_currentShape); // check if we should highlight another connection point int handle = handleAtPoint(m_currentShape, event->point); if (handle >= 0) { setEditMode(m_editMode, m_currentShape, handle); useCursor(handle >= KoConnectionPoint::FirstCustomConnectionPoint ? Qt::SizeAllCursor : Qt::ArrowCursor); } else { updateStatusText(); useCursor(Qt::CrossCursor); } } else { m_currentShape = 0; useCursor(Qt::ArrowCursor); } } else if (m_editMode == EditConnection) { Q_ASSERT(m_currentShape); KoShape *hoverShape = findShapeAtPosition(event->point); // check if we should highlight another connection handle int handle = handleAtPoint(m_currentShape, event->point); setEditMode(m_editMode, m_currentShape, handle); if (m_activeHandle == KoConnectionShape::StartHandle || m_activeHandle == KoConnectionShape::EndHandle) { useCursor(Qt::SizeAllCursor); } else if (m_activeHandle >= KoConnectionShape::ControlHandle_1) { } else if (hoverShape && hoverShape != m_currentShape) { useCursor(Qt::PointingHandCursor); } else { useCursor(Qt::ArrowCursor); } } else {// Idle and no current strategy KoShape *hoverShape = findShapeAtPosition(event->point); int hoverHandle = -1; if (hoverShape) { KoConnectionShape *connectionShape = dynamic_cast(hoverShape); if (!connectionShape) { QPointF snappedPos = canvas()->snapGuide()->snap(event->point, event->modifiers()); hoverHandle = handleAtPoint(hoverShape, snappedPos); setEditMode(hoverHandle >= 0 ? CreateConnection : Idle, hoverShape, hoverHandle); } useCursor(hoverHandle >= 0 ? m_connectCursor : Qt::PointingHandCursor); } else { useCursor(Qt::ArrowCursor); } } } void ConnectionTool::mouseReleaseEvent(KoPointerEvent *event) { if (m_currentStrategy) { if (m_editMode == CreateConnection) { // check if connection handles have a minimal distance KoConnectionShape *connectionShape = dynamic_cast(m_currentShape); Q_ASSERT(connectionShape); // get both handle positions in document coordinates QPointF p1 = connectionShape->shapeToDocument(connectionShape->handlePosition(0)); QPointF p2 = connectionShape->shapeToDocument(connectionShape->handlePosition(1)); int grabDistance = grabSensitivity(); // use grabbing sensitivity as minimal distance threshold if (squareDistance(p1, p2) < grabDistance * grabDistance) { // minimal distance was not reached, so we have to undo the started work: // - cleanup and delete the strategy // - remove connection shape from shape manager and delete it // - reset edit mode to last state delete m_currentStrategy; m_currentStrategy = 0; repaintDecorations(); canvas()->shapeManager()->remove(m_currentShape); setEditMode(m_editMode, connectionShape->firstShape(), connectionShape->firstConnectionId()); repaintDecorations(); delete connectionShape; return; } else { // finalize adding the new connection shape with an undo command KUndo2Command *cmd = canvas()->shapeController()->addShape(m_currentShape); canvas()->addCommand(cmd); setEditMode(EditConnection, m_currentShape, KoConnectionShape::StartHandle); } } m_currentStrategy->finishInteraction(event->modifiers()); // TODO: Add parent command to KoInteractionStrategy::createCommand // so that we can have a single command to undo for the user KUndo2Command *command = m_currentStrategy->createCommand(); if (command) { canvas()->addCommand(command); } delete m_currentStrategy; m_currentStrategy = 0; } updateStatusText(); } void ConnectionTool::mouseDoubleClickEvent(KoPointerEvent *event) { if (m_editMode == EditConnectionPoint) { repaintDecorations(); //quit EditConnectionPoint mode when double click blank region on canvas if (!m_currentShape) { resetEditMode(); return; } //add connection point when double click a shape //remove connection point when double click a existed connection point int handleId = handleAtPoint(m_currentShape, event->point); if (handleId < 0) { QPointF mousePos = canvas()->snapGuide()->snap(event->point, event->modifiers()); QPointF point = m_currentShape->documentToShape(mousePos); canvas()->addCommand(new AddConnectionPointCommand(m_currentShape, point)); } else { canvas()->addCommand(new RemoveConnectionPointCommand(m_currentShape, handleId)); } setEditMode(m_editMode, m_currentShape, -1); } else { //deactivate connection tool when double click blank region on canvas KoShape *hitShape = findShapeAtPosition(event->point); if (!hitShape) { deactivate(); emit done(); } else if (dynamic_cast(hitShape)) { repaintDecorations(); setEditMode(EditConnection, m_currentShape, -1); //TODO: temporarily activate text tool to edit connection path } } } void ConnectionTool::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { deactivate(); emit done(); } else if (event->key() == Qt::Key_Backspace) { deleteSelection(); event->accept(); } } void ConnectionTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); // save old enabled snap strategies, set bounding box snap strategy m_oldSnapStrategies = canvas()->snapGuide()->enabledSnapStrategies(); canvas()->snapGuide()->enableSnapStrategies(KoSnapGuide::BoundingBoxSnapping); canvas()->snapGuide()->reset(); m_resetPaint = true; repaintDecorations(); } void ConnectionTool::deactivate() { // Put everything to 0 to be able to begin a new shape properly delete m_currentStrategy; m_currentStrategy = 0; resetEditMode(); m_resetPaint = true; repaintDecorations(); // restore previously set snap strategies canvas()->snapGuide()->enableSnapStrategies(m_oldSnapStrategies); canvas()->snapGuide()->reset(); KoToolBase::deactivate(); } qreal ConnectionTool::squareDistance(const QPointF &p1, const QPointF &p2) const { // Square of the distance const qreal dx = p2.x() - p1.x(); const qreal dy = p2.y() - p1.y(); return dx * dx + dy * dy; } KoShape *ConnectionTool::findShapeAtPosition(const QPointF &position) const { QList shapes = canvas()->shapeManager()->shapesAt(handleGrabRect(position)); if (!shapes.isEmpty()) { - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); // we want to priorize connection shape handles, even if the connection shape // is not at the top of the shape stack at the mouse position KoConnectionShape *connectionShape = nearestConnectionShape(shapes, position); // use best connection shape or first shape from stack (last in the list) if not found if (connectionShape) { return connectionShape; } else { for (QList::const_iterator end = shapes.constEnd() - 1; end >= shapes.constBegin(); --end) { KoShape *shape = *end; if (!dynamic_cast(shape) && shape->shapeId() != TextShape_SHAPEID) { return shape; } } } } return 0; } KoShape *ConnectionTool::findNonConnectionShapeAtPosition(const QPointF &position) const { QList shapes = canvas()->shapeManager()->shapesAt(handleGrabRect(position)); if (!shapes.isEmpty()) { - qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); + std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); for (QList::const_iterator end = shapes.constEnd() - 1; end >= shapes.constBegin(); --end) { KoShape *shape = *end; if (!dynamic_cast(shape) && shape->shapeId() != TextShape_SHAPEID) { return shape; } } } return 0; } int ConnectionTool::handleAtPoint(KoShape *shape, const QPointF &mousePoint) const { if (!shape) { return -1; } const QPointF shapePoint = shape->documentToShape(mousePoint); KoConnectionShape *connectionShape = dynamic_cast(shape); if (connectionShape) { // check connection shape handles return connectionShape->handleIdAt(handleGrabRect(shapePoint)); } else { // check connection points int grabDistance = grabSensitivity(); qreal minDistance = HUGE_VAL; int handleId = -1; KoConnectionPoints connectionPoints = shape->connectionPoints(); KoConnectionPoints::const_iterator cp = connectionPoints.constBegin(); KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd(); for (; cp != lastCp; ++cp) { qreal d = squareDistance(shapePoint, cp.value().position); if (d <= grabDistance && d < minDistance) { handleId = cp.key(); minDistance = d; } } return handleId; } } KoConnectionShape *ConnectionTool::nearestConnectionShape(const QList &shapes, const QPointF &mousePos) const { int grabDistance = grabSensitivity(); KoConnectionShape *nearestConnectionShape = 0; qreal minSquaredDistance = HUGE_VAL; const qreal maxSquaredDistance = grabDistance * grabDistance; Q_FOREACH (KoShape *shape, shapes) { KoConnectionShape *connectionShape = dynamic_cast(shape); if (!connectionShape || !connectionShape->isParametricShape()) { continue; } // convert document point to shape coordinates QPointF p = connectionShape->documentToShape(mousePos); // our region of interest, i.e. a region around our mouse position QRectF roi = handleGrabRect(p); // check all segments of this shape which intersect the region of interest QList segments = connectionShape->segmentsAt(roi); foreach (const KoPathSegment &s, segments) { qreal nearestPointParam = s.nearestPoint(p); QPointF nearestPoint = s.pointAt(nearestPointParam); QPointF diff = p - nearestPoint; qreal squaredDistance = diff.x() * diff.x() + diff.y() * diff.y(); // are we within the allowed distance ? if (squaredDistance > maxSquaredDistance) { continue; } // are we closer to the last closest point ? if (squaredDistance < minSquaredDistance) { nearestConnectionShape = connectionShape; minSquaredDistance = squaredDistance; } } } return nearestConnectionShape; } void ConnectionTool::setEditMode(EditMode mode, KoShape *currentShape, int handle) { repaintDecorations(); m_editMode = mode; if (m_currentShape != currentShape) { KoConnectionShape *connectionShape = dynamic_cast(currentShape); foreach (KoShapeConfigWidgetBase *cw, m_connectionShapeWidgets) { if (connectionShape) { cw->open(currentShape); } } } if (mode == Idle) { emit sendConnectionType(m_connectionType); } m_currentShape = currentShape; m_activeHandle = handle; repaintDecorations(); updateActions(); updateStatusText(); } void ConnectionTool::resetEditMode() { m_connectionType = KoConnectionShape::Standard; setEditMode(Idle, 0, -1); emit sendConnectionPointEditState(false); } void ConnectionTool::updateActions() { const bool connectionPointSelected = m_editMode == EditConnectionPoint && m_activeHandle >= 0; if (connectionPointSelected) { KoConnectionPoint cp = m_currentShape->connectionPoint(m_activeHandle); m_alignPercent->setChecked(false); Q_FOREACH (QAction *action, m_alignHorizontal->actions()) { action->setChecked(false); } Q_FOREACH (QAction *action, m_alignVertical->actions()) { action->setChecked(false); } switch (cp.alignment) { case KoConnectionPoint::AlignNone: m_alignPercent->setChecked(true); break; case KoConnectionPoint::AlignTopLeft: m_alignLeft->setChecked(true); m_alignTop->setChecked(true); break; case KoConnectionPoint::AlignTop: m_alignCenterH->setChecked(true); m_alignTop->setChecked(true); break; case KoConnectionPoint::AlignTopRight: m_alignRight->setChecked(true); m_alignTop->setChecked(true); break; case KoConnectionPoint::AlignLeft: m_alignLeft->setChecked(true); m_alignCenterV->setChecked(true); break; case KoConnectionPoint::AlignCenter: m_alignCenterH->setChecked(true); m_alignCenterV->setChecked(true); break; case KoConnectionPoint::AlignRight: m_alignRight->setChecked(true); m_alignCenterV->setChecked(true); break; case KoConnectionPoint::AlignBottomLeft: m_alignLeft->setChecked(true); m_alignBottom->setChecked(true); break; case KoConnectionPoint::AlignBottom: m_alignCenterH->setChecked(true); m_alignBottom->setChecked(true); break; case KoConnectionPoint::AlignBottomRight: m_alignRight->setChecked(true); m_alignBottom->setChecked(true); break; } Q_FOREACH (QAction *action, m_escapeDirections->actions()) { action->setChecked(false); } switch (cp.escapeDirection) { case KoConnectionPoint::AllDirections: m_escapeAll->setChecked(true); break; case KoConnectionPoint::HorizontalDirections: m_escapeHorizontal->setChecked(true); break; case KoConnectionPoint::VerticalDirections: m_escapeVertical->setChecked(true); break; case KoConnectionPoint::LeftDirection: m_escapeLeft->setChecked(true); break; case KoConnectionPoint::RightDirection: m_escapeRight->setChecked(true); break; case KoConnectionPoint::UpDirection: m_escapeUp->setChecked(true); break; case KoConnectionPoint::DownDirection: m_escapeDown->setChecked(true); break; } } emit connectionPointEnabled(connectionPointSelected); } void ConnectionTool::updateStatusText() { switch (m_editMode) { case Idle: if (m_currentShape) { if (dynamic_cast(m_currentShape)) { if (m_activeHandle >= 0) { emit statusTextChanged(i18n("Drag to edit connection.")); } else { emit statusTextChanged(i18n("Double click connection or press delete to remove it.")); } } else if (m_activeHandle < 0) { emit statusTextChanged(i18n("Click to edit connection points.")); } } else { emit statusTextChanged(QString()); } break; case EditConnection: if (m_activeHandle >= 0) { emit statusTextChanged(i18n("Drag to edit connection.")); } else { emit statusTextChanged(i18n("Double click connection or press delete to remove it.")); } break; case EditConnectionPoint: if (m_activeHandle >= KoConnectionPoint::FirstCustomConnectionPoint) { emit statusTextChanged(i18n("Drag to move connection point. Double click connection or press delete to remove it.")); } else if (m_activeHandle >= 0) { emit statusTextChanged(i18n("Double click connection point or press delete to remove it.")); } else { emit statusTextChanged(i18n("Double click to add connection point.")); } break; case CreateConnection: emit statusTextChanged(i18n("Drag to create new connection.")); break; default: emit statusTextChanged(QString()); } } QList > ConnectionTool::createOptionWidgets() { QList > list; m_connectionShapeWidgets.clear(); KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(KOCONNECTIONSHAPEID); if (factory) { QList widgets = factory->createShapeOptionPanels(); Q_FOREACH (KoShapeConfigWidgetBase *cw, widgets) { if (cw->showOnShapeCreate() || !cw->showOnShapeSelect()) { delete cw; continue; } connect(cw, SIGNAL(propertyChanged()), this, SLOT(connectionChanged())); KoConnectionShapeConfigWidget *cw2 = (KoConnectionShapeConfigWidget *)cw; if (cw2) { connect(cw2, SIGNAL(connectionTypeChanged(int)), this, SLOT(getConnectionType(int))); connect(this, SIGNAL(sendConnectionType(int)), cw2, SLOT(setConnectionType(int))); } m_connectionShapeWidgets.append(cw); cw->setWindowTitle(i18n("Connection")); list.append(cw); } } KoStrokeConfigWidget *strokeWidget = new KoStrokeConfigWidget(canvas(), 0); KisDocumentAwareSpinBoxUnitManager* managerLineWidth = new KisDocumentAwareSpinBoxUnitManager(strokeWidget); KisDocumentAwareSpinBoxUnitManager* managerMitterLimit = new KisDocumentAwareSpinBoxUnitManager(strokeWidget); managerLineWidth->setApparentUnitFromSymbol("px"); managerMitterLimit->setApparentUnitFromSymbol("px"); strokeWidget->setUnitManagers(managerLineWidth, managerMitterLimit); strokeWidget->setWindowTitle(i18n("Line")); list.append(strokeWidget); ConnectionPointWidget *connectPoint = new ConnectionPointWidget(this); connectPoint->setWindowTitle(i18n("Connection Point")); list.append(connectPoint); return list; } void ConnectionTool::horizontalAlignChanged() { if (m_alignPercent->isChecked()) { m_alignPercent->setChecked(false); m_alignTop->setChecked(true); } updateConnectionPoint(); } void ConnectionTool::verticalAlignChanged() { if (m_alignPercent->isChecked()) { m_alignPercent->setChecked(false); m_alignLeft->setChecked(true); } updateConnectionPoint(); } void ConnectionTool::relativeAlignChanged() { Q_FOREACH (QAction *action, m_alignHorizontal->actions()) { action->setChecked(false); } Q_FOREACH (QAction *action, m_alignVertical->actions()) { action->setChecked(false); } m_alignPercent->setChecked(true); updateConnectionPoint(); } void ConnectionTool::updateConnectionPoint() { if (m_editMode == EditConnectionPoint && m_currentShape && m_activeHandle >= 0) { KoConnectionPoint oldPoint = m_currentShape->connectionPoint(m_activeHandle); KoConnectionPoint newPoint = oldPoint; if (m_alignPercent->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignNone; } else if (m_alignLeft->isChecked() && m_alignTop->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignTopLeft; } else if (m_alignCenterH->isChecked() && m_alignTop->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignTop; } else if (m_alignRight->isChecked() && m_alignTop->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignTopRight; } else if (m_alignLeft->isChecked() && m_alignCenterV->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignLeft; } else if (m_alignCenterH->isChecked() && m_alignCenterV->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignCenter; } else if (m_alignRight->isChecked() && m_alignCenterV->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignRight; } else if (m_alignLeft->isChecked() && m_alignBottom->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignBottomLeft; } else if (m_alignCenterH->isChecked() && m_alignBottom->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignBottom; } else if (m_alignRight->isChecked() && m_alignBottom->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignBottomRight; } canvas()->addCommand(new ChangeConnectionPointCommand(m_currentShape, m_activeHandle, oldPoint, newPoint)); } } void ConnectionTool::escapeDirectionChanged() { if (m_editMode == EditConnectionPoint && m_currentShape && m_activeHandle >= 0) { KoConnectionPoint oldPoint = m_currentShape->connectionPoint(m_activeHandle); KoConnectionPoint newPoint = oldPoint; QAction *checkedAction = m_escapeDirections->checkedAction(); if (checkedAction == m_escapeAll) { newPoint.escapeDirection = KoConnectionPoint::AllDirections; } else if (checkedAction == m_escapeHorizontal) { newPoint.escapeDirection = KoConnectionPoint::HorizontalDirections; } else if (checkedAction == m_escapeVertical) { newPoint.escapeDirection = KoConnectionPoint::VerticalDirections; } else if (checkedAction == m_escapeLeft) { newPoint.escapeDirection = KoConnectionPoint::LeftDirection; } else if (checkedAction == m_escapeRight) { newPoint.escapeDirection = KoConnectionPoint::RightDirection; } else if (checkedAction == m_escapeUp) { newPoint.escapeDirection = KoConnectionPoint::UpDirection; } else if (checkedAction == m_escapeDown) { newPoint.escapeDirection = KoConnectionPoint::DownDirection; } canvas()->addCommand(new ChangeConnectionPointCommand(m_currentShape, m_activeHandle, oldPoint, newPoint)); } } void ConnectionTool::connectionChanged() { if (m_editMode != EditConnection) { return; } KoConnectionShape *connectionShape = dynamic_cast(m_currentShape); if (!connectionShape) { return; } Q_FOREACH (KoShapeConfigWidgetBase *cw, m_connectionShapeWidgets) { canvas()->addCommand(cw->createCommand()); } } void ConnectionTool::deleteSelection() { if (m_editMode == EditConnectionPoint && m_currentShape && m_activeHandle >= 0) { repaintDecorations(); canvas()->addCommand(new RemoveConnectionPointCommand(m_currentShape, m_activeHandle)); setEditMode(m_editMode, m_currentShape, -1); } else if (m_editMode == EditConnection && m_currentShape) { repaintDecorations(); canvas()->addCommand(canvas()->shapeController()->removeShape(m_currentShape)); resetEditMode(); } } void ConnectionTool::getConnectionType(int type) { if (m_editMode == Idle) { m_connectionType = (KoConnectionShape::Type)type; } } void ConnectionTool::toggleConnectionPointEditMode(int state) { if (state == Qt::Checked) { setEditMode(EditConnectionPoint, 0, -1); } else if (state == Qt::Unchecked) { setEditMode(Idle, 0, -1); } else { return; } } diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp index b7cdcbef48..ec8ec26a60 100644 --- a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp +++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp @@ -1,1349 +1,1349 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2008-2009 Jan Hambrecht Copyright (C) 2008 C. Boemann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "DefaultTool.h" #include "DefaultToolGeometryWidget.h" #include "DefaultToolTabbedWidget.h" #include "SelectionDecorator.h" #include "ShapeMoveStrategy.h" #include "ShapeRotateStrategy.h" #include "ShapeShearStrategy.h" #include "ShapeResizeStrategy.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_action_registry.h" #include #include "kis_document_aware_spin_box_unit_manager.h" #include #include #include #include #include #include #include #include #include #include #include "kis_assert.h" #include "kis_global.h" #include "kis_debug.h" #include #define HANDLE_DISTANCE 10 #define HANDLE_DISTANCE_SQ (HANDLE_DISTANCE * HANDLE_DISTANCE) #define INNER_HANDLE_DISTANCE_SQ 16 namespace { static const QString EditFillGradientFactoryId = "edit_fill_gradient"; static const QString EditStrokeGradientFactoryId = "edit_stroke_gradient"; } QPolygonF selectionPolygon(KoSelection *selection) { QPolygonF result; QList selectedShapes = selection->selectedShapes(); if (!selectedShapes.size()) { return result; } if (selectedShapes.size() > 1) { QTransform matrix = selection->absoluteTransformation(0); result = matrix.map(QPolygonF(QRectF(QPointF(0, 0), selection->size()))); } else { KoShape *selectedShape = selectedShapes.first(); QTransform matrix = selectedShape->absoluteTransformation(0); result = matrix.map(QPolygonF(QRectF(QPointF(0, 0), selectedShape->size()))); } return result; } class NopInteractionStrategy : public KoInteractionStrategy { public: explicit NopInteractionStrategy(KoToolBase *parent) : KoInteractionStrategy(parent) { } KUndo2Command *createCommand() override { return 0; } void handleMouseMove(const QPointF & /*mouseLocation*/, Qt::KeyboardModifiers /*modifiers*/) override {} void finishInteraction(Qt::KeyboardModifiers /*modifiers*/) override {} void paint(QPainter &painter, const KoViewConverter &converter) override { Q_UNUSED(painter); Q_UNUSED(converter); } }; class SelectionInteractionStrategy : public KoShapeRubberSelectStrategy { public: explicit SelectionInteractionStrategy(KoToolBase *parent, const QPointF &clicked, bool useSnapToGrid) : KoShapeRubberSelectStrategy(parent, clicked, useSnapToGrid) { } void paint(QPainter &painter, const KoViewConverter &converter) override { KoShapeRubberSelectStrategy::paint(painter, converter); } }; #include #include "KoShapeGradientHandles.h" #include "ShapeGradientEditStrategy.h" class DefaultTool::MoveGradientHandleInteractionFactory : public KoInteractionStrategyFactory { public: MoveGradientHandleInteractionFactory(KoFlake::FillVariant fillVariant, int priority, const QString &id, DefaultTool *_q) : KoInteractionStrategyFactory(priority, id), q(_q), m_fillVariant(fillVariant) { } KoInteractionStrategy* createStrategy(KoPointerEvent *ev) override { m_currentHandle = handleAt(ev->point); if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) { KoShape *shape = onlyEditableShape(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, 0); return new ShapeGradientEditStrategy(q, m_fillVariant, shape, m_currentHandle.type, ev->point); } return 0; } bool hoverEvent(KoPointerEvent *ev) override { m_currentHandle = handleAt(ev->point); return false; } bool paintOnHover(QPainter &painter, const KoViewConverter &converter) override { Q_UNUSED(painter); Q_UNUSED(converter); return false; } bool tryUseCustomCursor() override { if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) { q->useCursor(Qt::OpenHandCursor); } return m_currentHandle.type != KoShapeGradientHandles::Handle::None; } private: KoShape* onlyEditableShape() const { KoSelection *selection = q->koSelection(); QList shapes = selection->selectedEditableShapes(); KoShape *shape = 0; if (shapes.size() == 1) { shape = shapes.first(); } return shape; } KoShapeGradientHandles::Handle handleAt(const QPointF &pos) { KoShapeGradientHandles::Handle result; KoShape *shape = onlyEditableShape(); if (shape) { KoFlake::SelectionHandle globalHandle = q->handleAt(pos); const qreal distanceThresholdSq = globalHandle == KoFlake::NoHandle ? HANDLE_DISTANCE_SQ : 0.25 * HANDLE_DISTANCE_SQ; const KoViewConverter *converter = q->canvas()->viewConverter(); const QPointF viewPoint = converter->documentToView(pos); qreal minDistanceSq = std::numeric_limits::max(); KoShapeGradientHandles sh(m_fillVariant, shape); Q_FOREACH (const KoShapeGradientHandles::Handle &handle, sh.handles()) { const QPointF handlePoint = converter->documentToView(handle.pos); const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint); if (distanceSq < distanceThresholdSq && distanceSq < minDistanceSq) { result = handle; minDistanceSq = distanceSq; } } } return result; } private: DefaultTool *q; KoFlake::FillVariant m_fillVariant; KoShapeGradientHandles::Handle m_currentHandle; }; class SelectionHandler : public KoToolSelection { public: SelectionHandler(DefaultTool *parent) : KoToolSelection(parent) , m_selection(parent->koSelection()) { } bool hasSelection() override { if (m_selection) { return m_selection->count(); } return false; } private: QPointer m_selection; }; DefaultTool::DefaultTool(KoCanvasBase *canvas) : KoInteractionTool(canvas) , m_lastHandle(KoFlake::NoHandle) , m_hotPosition(KoFlake::TopLeft) , m_mouseWasInsideHandles(false) , m_selectionHandler(new SelectionHandler(this)) , m_customEventStrategy(0) , m_tabbedOptionWidget(0) { setupActions(); QPixmap rotatePixmap, shearPixmap; rotatePixmap.load(":/cursor_rotate.png"); Q_ASSERT(!rotatePixmap.isNull()); shearPixmap.load(":/cursor_shear.png"); Q_ASSERT(!shearPixmap.isNull()); m_rotateCursors[0] = QCursor(rotatePixmap.transformed(QTransform().rotate(45))); m_rotateCursors[1] = QCursor(rotatePixmap.transformed(QTransform().rotate(90))); m_rotateCursors[2] = QCursor(rotatePixmap.transformed(QTransform().rotate(135))); m_rotateCursors[3] = QCursor(rotatePixmap.transformed(QTransform().rotate(180))); m_rotateCursors[4] = QCursor(rotatePixmap.transformed(QTransform().rotate(225))); m_rotateCursors[5] = QCursor(rotatePixmap.transformed(QTransform().rotate(270))); m_rotateCursors[6] = QCursor(rotatePixmap.transformed(QTransform().rotate(315))); m_rotateCursors[7] = QCursor(rotatePixmap); /* m_rotateCursors[0] = QCursor(Qt::RotateNCursor); m_rotateCursors[1] = QCursor(Qt::RotateNECursor); m_rotateCursors[2] = QCursor(Qt::RotateECursor); m_rotateCursors[3] = QCursor(Qt::RotateSECursor); m_rotateCursors[4] = QCursor(Qt::RotateSCursor); m_rotateCursors[5] = QCursor(Qt::RotateSWCursor); m_rotateCursors[6] = QCursor(Qt::RotateWCursor); m_rotateCursors[7] = QCursor(Qt::RotateNWCursor); */ m_shearCursors[0] = QCursor(shearPixmap); m_shearCursors[1] = QCursor(shearPixmap.transformed(QTransform().rotate(45))); m_shearCursors[2] = QCursor(shearPixmap.transformed(QTransform().rotate(90))); m_shearCursors[3] = QCursor(shearPixmap.transformed(QTransform().rotate(135))); m_shearCursors[4] = QCursor(shearPixmap.transformed(QTransform().rotate(180))); m_shearCursors[5] = QCursor(shearPixmap.transformed(QTransform().rotate(225))); m_shearCursors[6] = QCursor(shearPixmap.transformed(QTransform().rotate(270))); m_shearCursors[7] = QCursor(shearPixmap.transformed(QTransform().rotate(315))); m_sizeCursors[0] = Qt::SizeVerCursor; m_sizeCursors[1] = Qt::SizeBDiagCursor; m_sizeCursors[2] = Qt::SizeHorCursor; m_sizeCursors[3] = Qt::SizeFDiagCursor; m_sizeCursors[4] = Qt::SizeVerCursor; m_sizeCursors[5] = Qt::SizeBDiagCursor; m_sizeCursors[6] = Qt::SizeHorCursor; m_sizeCursors[7] = Qt::SizeFDiagCursor; connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(updateActions())); } DefaultTool::~DefaultTool() { } void DefaultTool::slotActivateEditFillGradient(bool value) { if (value) { addInteractionFactory( new MoveGradientHandleInteractionFactory(KoFlake::Fill, 1, EditFillGradientFactoryId, this)); } else { removeInteractionFactory(EditFillGradientFactoryId); } repaintDecorations(); } void DefaultTool::slotActivateEditStrokeGradient(bool value) { if (value) { addInteractionFactory( new MoveGradientHandleInteractionFactory(KoFlake::StrokeFill, 0, EditStrokeGradientFactoryId, this)); } else { removeInteractionFactory(EditStrokeGradientFactoryId); } repaintDecorations(); } bool DefaultTool::wantsAutoScroll() const { return true; } void DefaultTool::addMappedAction(QSignalMapper *mapper, const QString &actionId, int commandType) { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); QAction *action = actionRegistry->makeQAction(actionId, this); addAction(actionId, action); connect(action, SIGNAL(triggered()), mapper, SLOT(map())); mapper->setMapping(action, commandType); } void DefaultTool::setupActions() { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); QAction *actionBringToFront = actionRegistry->makeQAction("object_order_front", this); addAction("object_order_front", actionBringToFront); connect(actionBringToFront, SIGNAL(triggered()), this, SLOT(selectionBringToFront())); QAction *actionRaise = actionRegistry->makeQAction("object_order_raise", this); addAction("object_order_raise", actionRaise); connect(actionRaise, SIGNAL(triggered()), this, SLOT(selectionMoveUp())); QAction *actionLower = actionRegistry->makeQAction("object_order_lower", this); addAction("object_order_lower", actionLower); connect(actionLower, SIGNAL(triggered()), this, SLOT(selectionMoveDown())); QAction *actionSendToBack = actionRegistry->makeQAction("object_order_back", this); addAction("object_order_back", actionSendToBack); connect(actionSendToBack, SIGNAL(triggered()), this, SLOT(selectionSendToBack())); QSignalMapper *alignSignalsMapper = new QSignalMapper(this); connect(alignSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionAlign(int))); addMappedAction(alignSignalsMapper, "object_align_horizontal_left", KoShapeAlignCommand::HorizontalLeftAlignment); addMappedAction(alignSignalsMapper, "object_align_horizontal_center", KoShapeAlignCommand::HorizontalCenterAlignment); addMappedAction(alignSignalsMapper, "object_align_horizontal_right", KoShapeAlignCommand::HorizontalRightAlignment); addMappedAction(alignSignalsMapper, "object_align_vertical_top", KoShapeAlignCommand::VerticalTopAlignment); addMappedAction(alignSignalsMapper, "object_align_vertical_center", KoShapeAlignCommand::VerticalCenterAlignment); addMappedAction(alignSignalsMapper, "object_align_vertical_bottom", KoShapeAlignCommand::VerticalBottomAlignment); QSignalMapper *distributeSignalsMapper = new QSignalMapper(this); connect(distributeSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionDistribute(int))); addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_left", KoShapeDistributeCommand::HorizontalLeftDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_center", KoShapeDistributeCommand::HorizontalCenterDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_right", KoShapeDistributeCommand::HorizontalRightDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_gaps", KoShapeDistributeCommand::HorizontalGapsDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_vertical_top", KoShapeDistributeCommand::VerticalTopDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_vertical_center", KoShapeDistributeCommand::VerticalCenterDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_vertical_bottom", KoShapeDistributeCommand::VerticalBottomDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_vertical_gaps", KoShapeDistributeCommand::VerticalGapsDistribution); QAction *actionGroupBottom = actionRegistry->makeQAction("object_group", this); addAction("object_group", actionGroupBottom); connect(actionGroupBottom, SIGNAL(triggered()), this, SLOT(selectionGroup())); QAction *actionUngroupBottom = actionRegistry->makeQAction("object_ungroup", this); addAction("object_ungroup", actionUngroupBottom); connect(actionUngroupBottom, SIGNAL(triggered()), this, SLOT(selectionUngroup())); m_contextMenu.reset(new QMenu()); } qreal DefaultTool::rotationOfHandle(KoFlake::SelectionHandle handle, bool useEdgeRotation) { QPointF selectionCenter = koSelection()->absolutePosition(); QPointF direction; switch (handle) { case KoFlake::TopMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::TopLeft); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::TopRight) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::TopRightHandle: direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized()).toPointF(); break; case KoFlake::RightMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::TopRight); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopRight); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::BottomRightHandle: direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized()).toPointF(); break; case KoFlake::BottomMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::BottomRight); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::BottomLeft); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::BottomLeftHandle: direction = koSelection()->absolutePosition(KoFlake::BottomLeft) - selectionCenter; direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized()).toPointF(); break; case KoFlake::LeftMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::BottomLeft); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomLeft) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::TopLeftHandle: direction = koSelection()->absolutePosition(KoFlake::TopLeft) - selectionCenter; direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized()).toPointF(); break; case KoFlake::NoHandle: return 0.0; break; } qreal rotation = atan2(direction.y(), direction.x()) * 180.0 / M_PI; switch (handle) { case KoFlake::TopMiddleHandle: if (useEdgeRotation) { rotation -= 0.0; } else { rotation -= 270.0; } break; case KoFlake::TopRightHandle: rotation -= 315.0; break; case KoFlake::RightMiddleHandle: if (useEdgeRotation) { rotation -= 90.0; } else { rotation -= 0.0; } break; case KoFlake::BottomRightHandle: rotation -= 45.0; break; case KoFlake::BottomMiddleHandle: if (useEdgeRotation) { rotation -= 180.0; } else { rotation -= 90.0; } break; case KoFlake::BottomLeftHandle: rotation -= 135.0; break; case KoFlake::LeftMiddleHandle: if (useEdgeRotation) { rotation -= 270.0; } else { rotation -= 180.0; } break; case KoFlake::TopLeftHandle: rotation -= 225.0; break; case KoFlake::NoHandle: break; } if (rotation < 0.0) { rotation += 360.0; } return rotation; } void DefaultTool::updateCursor() { if (tryUseCustomCursor()) return; QCursor cursor = Qt::ArrowCursor; QString statusText; if (koSelection()->count() > 0) { // has a selection bool editable = !koSelection()->selectedEditableShapes().isEmpty(); if (!m_mouseWasInsideHandles) { m_angle = rotationOfHandle(m_lastHandle, true); int rotOctant = 8 + int(8.5 + m_angle / 45); bool rotateHandle = false; bool shearHandle = false; switch (m_lastHandle) { case KoFlake::TopMiddleHandle: cursor = m_shearCursors[(0 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::TopRightHandle: cursor = m_rotateCursors[(1 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::RightMiddleHandle: cursor = m_shearCursors[(2 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::BottomRightHandle: cursor = m_rotateCursors[(3 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::BottomMiddleHandle: cursor = m_shearCursors[(4 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::BottomLeftHandle: cursor = m_rotateCursors[(5 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::LeftMiddleHandle: cursor = m_shearCursors[(6 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::TopLeftHandle: cursor = m_rotateCursors[(7 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::NoHandle: cursor = Qt::ArrowCursor; break; } if (rotateHandle) { statusText = i18n("Left click rotates around center, right click around highlighted position."); } if (shearHandle) { statusText = i18n("Click and drag to shear selection."); } } else { statusText = i18n("Click and drag to resize selection."); m_angle = rotationOfHandle(m_lastHandle, false); int rotOctant = 8 + int(8.5 + m_angle / 45); bool cornerHandle = false; switch (m_lastHandle) { case KoFlake::TopMiddleHandle: cursor = m_sizeCursors[(0 + rotOctant) % 8]; break; case KoFlake::TopRightHandle: cursor = m_sizeCursors[(1 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::RightMiddleHandle: cursor = m_sizeCursors[(2 + rotOctant) % 8]; break; case KoFlake::BottomRightHandle: cursor = m_sizeCursors[(3 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::BottomMiddleHandle: cursor = m_sizeCursors[(4 + rotOctant) % 8]; break; case KoFlake::BottomLeftHandle: cursor = m_sizeCursors[(5 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::LeftMiddleHandle: cursor = m_sizeCursors[(6 + rotOctant) % 8]; break; case KoFlake::TopLeftHandle: cursor = m_sizeCursors[(7 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::NoHandle: cursor = Qt::SizeAllCursor; statusText = i18n("Click and drag to move selection."); break; } if (cornerHandle) { statusText = i18n("Click and drag to resize selection. Middle click to set highlighted position."); } } if (!editable) { cursor = Qt::ArrowCursor; } } else { // there used to be guides... :'''( } useCursor(cursor); if (currentStrategy() == 0) { emit statusTextChanged(statusText); } } void DefaultTool::paint(QPainter &painter, const KoViewConverter &converter) { SelectionDecorator decorator(canvas()->resourceManager()); decorator.setSelection(koSelection()); decorator.setHandleRadius(handleRadius()); decorator.setShowFillGradientHandles(hasInteractioFactory(EditFillGradientFactoryId)); decorator.setShowStrokeFillGradientHandles(hasInteractioFactory(EditStrokeGradientFactoryId)); decorator.paint(painter, converter); KoInteractionTool::paint(painter, converter); painter.save(); KoShape::applyConversion(painter, converter); canvas()->snapGuide()->paint(painter, converter); painter.restore(); } void DefaultTool::mousePressEvent(KoPointerEvent *event) { KoInteractionTool::mousePressEvent(event); updateCursor(); } void DefaultTool::mouseMoveEvent(KoPointerEvent *event) { KoInteractionTool::mouseMoveEvent(event); if (currentStrategy() == 0 && koSelection() && koSelection()->count() > 0) { QRectF bound = handlesSize(); if (bound.contains(event->point)) { bool inside; KoFlake::SelectionHandle newDirection = handleAt(event->point, &inside); if (inside != m_mouseWasInsideHandles || m_lastHandle != newDirection) { m_lastHandle = newDirection; m_mouseWasInsideHandles = inside; //repaintDecorations(); } } else { /*if (m_lastHandle != KoFlake::NoHandle) repaintDecorations(); */ m_lastHandle = KoFlake::NoHandle; m_mouseWasInsideHandles = false; // there used to be guides... :'''( } } else { // there used to be guides... :'''( } updateCursor(); } QRectF DefaultTool::handlesSize() { KoSelection *selection = koSelection(); if (!selection->count()) return QRectF(); recalcSelectionBox(selection); QRectF bound = m_selectionOutline.boundingRect(); // expansion Border if (!canvas() || !canvas()->viewConverter()) { return bound; } QPointF border = canvas()->viewConverter()->viewToDocument(QPointF(HANDLE_DISTANCE, HANDLE_DISTANCE)); bound.adjust(-border.x(), -border.y(), border.x(), border.y()); return bound; } void DefaultTool::mouseReleaseEvent(KoPointerEvent *event) { KoInteractionTool::mouseReleaseEvent(event); updateCursor(); } void DefaultTool::mouseDoubleClickEvent(KoPointerEvent *event) { KoSelection *selection = canvas()->selectedShapesProxy()->selection(); KoShape *shape = canvas()->shapeManager()->shapeAt(event->point, KoFlake::ShapeOnTop); if (shape && !selection->isSelected(shape)) { if (!(event->modifiers() & Qt::ShiftModifier)) { selection->deselectAll(); } selection->select(shape); } explicitUserStrokeEndRequest(); } bool DefaultTool::moveSelection(int direction, Qt::KeyboardModifiers modifiers) { bool result = false; qreal x = 0.0, y = 0.0; if (direction == Qt::Key_Left) { x = -5; } else if (direction == Qt::Key_Right) { x = 5; } else if (direction == Qt::Key_Up) { y = -5; } else if (direction == Qt::Key_Down) { y = 5; } if (x != 0.0 || y != 0.0) { // actually move if ((modifiers & Qt::ShiftModifier) != 0) { x *= 10; y *= 10; } else if ((modifiers & Qt::AltModifier) != 0) { // more precise x /= 5; y /= 5; } QList shapes = koSelection()->selectedEditableShapes(); if (!shapes.isEmpty()) { canvas()->addCommand(new KoShapeMoveCommand(shapes, QPointF(x, y))); result = true; } } return result; } void DefaultTool::keyPressEvent(QKeyEvent *event) { KoInteractionTool::keyPressEvent(event); if (currentStrategy() == 0) { switch (event->key()) { case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: if (moveSelection(event->key(), event->modifiers())) { event->accept(); } break; case Qt::Key_1: case Qt::Key_2: case Qt::Key_3: case Qt::Key_4: case Qt::Key_5: canvas()->resourceManager()->setResource(HotPosition, event->key() - Qt::Key_1); event->accept(); break; default: return; } } } void DefaultTool::repaintDecorations() { if (koSelection() && koSelection()->count() > 0) { canvas()->updateCanvas(handlesSize()); } } void DefaultTool::copy() const { // all the selected shapes, not only editable! QList shapes = canvas()->selectedShapesProxy()->selection()->selectedShapes(); if (!shapes.isEmpty()) { KoDrag drag; drag.setSvg(shapes); drag.addToClipboard(); } } void DefaultTool::deleteSelection() { QList shapes; foreach (KoShape *s, canvas()->selectedShapesProxy()->selection()->selectedShapes()) { if (s->isGeometryProtected()) { continue; } shapes << s; } if (!shapes.empty()) { canvas()->addCommand(canvas()->shapeController()->removeShapes(shapes)); } } bool DefaultTool::paste() { // we no longer have to do anything as tool Proxy will do it for us return false; } KoSelection *DefaultTool::koSelection() { Q_ASSERT(canvas()); Q_ASSERT(canvas()->selectedShapesProxy()); return canvas()->selectedShapesProxy()->selection(); } KoFlake::SelectionHandle DefaultTool::handleAt(const QPointF &point, bool *innerHandleMeaning) { // check for handles in this order; meaning that when handles overlap the one on top is chosen static const KoFlake::SelectionHandle handleOrder[] = { KoFlake::BottomRightHandle, KoFlake::TopLeftHandle, KoFlake::BottomLeftHandle, KoFlake::TopRightHandle, KoFlake::BottomMiddleHandle, KoFlake::RightMiddleHandle, KoFlake::LeftMiddleHandle, KoFlake::TopMiddleHandle, KoFlake::NoHandle }; const KoViewConverter *converter = canvas()->viewConverter(); KoSelection *selection = koSelection(); if (!selection->count() || !converter) { return KoFlake::NoHandle; } recalcSelectionBox(selection); if (innerHandleMeaning) { QPainterPath path; path.addPolygon(m_selectionOutline); *innerHandleMeaning = path.contains(point) || path.intersects(handlePaintRect(point)); } const QPointF viewPoint = converter->documentToView(point); for (int i = 0; i < KoFlake::NoHandle; ++i) { KoFlake::SelectionHandle handle = handleOrder[i]; const QPointF handlePoint = converter->documentToView(m_selectionBox[handle]); const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint); // if just inside the outline if (distanceSq < HANDLE_DISTANCE_SQ) { if (innerHandleMeaning) { if (distanceSq < INNER_HANDLE_DISTANCE_SQ) { *innerHandleMeaning = true; } } return handle; } } return KoFlake::NoHandle; } void DefaultTool::recalcSelectionBox(KoSelection *selection) { KIS_ASSERT_RECOVER_RETURN(selection->count()); QTransform matrix = selection->absoluteTransformation(0); m_selectionOutline = matrix.map(QPolygonF(selection->outlineRect())); m_angle = 0.0; QPolygonF outline = m_selectionOutline; //shorter name in the following :) m_selectionBox[KoFlake::TopMiddleHandle] = (outline.value(0) + outline.value(1)) / 2; m_selectionBox[KoFlake::TopRightHandle] = outline.value(1); m_selectionBox[KoFlake::RightMiddleHandle] = (outline.value(1) + outline.value(2)) / 2; m_selectionBox[KoFlake::BottomRightHandle] = outline.value(2); m_selectionBox[KoFlake::BottomMiddleHandle] = (outline.value(2) + outline.value(3)) / 2; m_selectionBox[KoFlake::BottomLeftHandle] = outline.value(3); m_selectionBox[KoFlake::LeftMiddleHandle] = (outline.value(3) + outline.value(0)) / 2; m_selectionBox[KoFlake::TopLeftHandle] = outline.value(0); if (selection->count() == 1) { #if 0 // TODO detect mirroring KoShape *s = koSelection()->firstSelectedShape(); if (s->scaleX() < 0) { // vertically mirrored: swap left / right - qSwap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::TopRightHandle]); - qSwap(m_selectionBox[KoFlake::LeftMiddleHandle], m_selectionBox[KoFlake::RightMiddleHandle]); - qSwap(m_selectionBox[KoFlake::BottomLeftHandle], m_selectionBox[KoFlake::BottomRightHandle]); + std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::TopRightHandle]); + std::swap(m_selectionBox[KoFlake::LeftMiddleHandle], m_selectionBox[KoFlake::RightMiddleHandle]); + std::swap(m_selectionBox[KoFlake::BottomLeftHandle], m_selectionBox[KoFlake::BottomRightHandle]); } if (s->scaleY() < 0) { // vertically mirrored: swap top / bottom - qSwap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::BottomLeftHandle]); - qSwap(m_selectionBox[KoFlake::TopMiddleHandle], m_selectionBox[KoFlake::BottomMiddleHandle]); - qSwap(m_selectionBox[KoFlake::TopRightHandle], m_selectionBox[KoFlake::BottomRightHandle]); + std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::BottomLeftHandle]); + std::swap(m_selectionBox[KoFlake::TopMiddleHandle], m_selectionBox[KoFlake::BottomMiddleHandle]); + std::swap(m_selectionBox[KoFlake::TopRightHandle], m_selectionBox[KoFlake::BottomRightHandle]); } #endif } } void DefaultTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); m_mouseWasInsideHandles = false; m_lastHandle = KoFlake::NoHandle; useCursor(Qt::ArrowCursor); repaintDecorations(); updateActions(); if (m_tabbedOptionWidget) { m_tabbedOptionWidget->activate(); } } void DefaultTool::deactivate() { KoToolBase::deactivate(); if (m_tabbedOptionWidget) { m_tabbedOptionWidget->deactivate(); } } void DefaultTool::selectionGroup() { KoSelection *selection = koSelection(); if (!selection) return; QList selectedShapes = selection->selectedEditableShapes(); - qSort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); + std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); KoShapeGroup *group = new KoShapeGroup(); // TODO what if only one shape is left? KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes")); canvas()->shapeController()->addShapeDirect(group, cmd); new KoShapeGroupCommand(group, selectedShapes, false, true, true, cmd); canvas()->addCommand(cmd); // update selection so we can ungroup immediately again selection->deselectAll(); selection->select(group); } void DefaultTool::selectionUngroup() { KoSelection *selection = koSelection(); if (!selection) return; QList selectedShapes = selection->selectedEditableShapes(); - qSort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); + std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); KUndo2Command *cmd = 0; // add a ungroup command for each found shape container to the macro command Q_FOREACH (KoShape *shape, selectedShapes) { KoShapeGroup *group = dynamic_cast(shape); if (group) { cmd = cmd ? cmd : new KUndo2Command(kundo2_i18n("Ungroup shapes")); new KoShapeUngroupCommand(group, group->shapes(), group->parent() ? QList() : canvas()->shapeManager()->topLevelShapes(), cmd); canvas()->shapeController()->removeShape(group, cmd); } } if (cmd) { canvas()->addCommand(cmd); } } void DefaultTool::selectionAlign(int _align) { KoShapeAlignCommand::Align align = static_cast(_align); KoSelection *selection = koSelection(); if (!selection) return; QList editableShapes = selection->selectedEditableShapes(); if (editableShapes.isEmpty()) { return; } // TODO add an option to the widget so that one can align to the page // with multiple selected shapes too QRectF bb; // single selected shape is automatically aligned to document rect if (editableShapes.count() == 1) { if (!canvas()->resourceManager()->hasResource(KoCanvasResourceManager::PageSize)) { return; } bb = QRectF(QPointF(0, 0), canvas()->resourceManager()->sizeResource(KoCanvasResourceManager::PageSize)); } else { bb = KoShape::absoluteOutlineRect(editableShapes); } KoShapeAlignCommand *cmd = new KoShapeAlignCommand(editableShapes, align, bb); canvas()->addCommand(cmd); } void DefaultTool::selectionDistribute(int _distribute) { KoShapeDistributeCommand::Distribute distribute = static_cast(_distribute); KoSelection *selection = koSelection(); if (!selection) return; QList editableShapes = selection->selectedEditableShapes(); if (editableShapes.size() < 3) { return; } QRectF bb = KoShape::absoluteOutlineRect(editableShapes); KoShapeDistributeCommand *cmd = new KoShapeDistributeCommand(editableShapes, distribute, bb); canvas()->addCommand(cmd); } void DefaultTool::selectionBringToFront() { selectionReorder(KoShapeReorderCommand::BringToFront); } void DefaultTool::selectionMoveUp() { selectionReorder(KoShapeReorderCommand::RaiseShape); } void DefaultTool::selectionMoveDown() { selectionReorder(KoShapeReorderCommand::LowerShape); } void DefaultTool::selectionSendToBack() { selectionReorder(KoShapeReorderCommand::SendToBack); } void DefaultTool::selectionReorder(KoShapeReorderCommand::MoveShapeType order) { KoSelection *selection = canvas()->selectedShapesProxy()->selection(); if (!selection) { return; } QList selectedShapes = selection->selectedEditableShapes(); if (selectedShapes.isEmpty()) { return; } KUndo2Command *cmd = KoShapeReorderCommand::createCommand(selectedShapes, canvas()->shapeManager(), order); if (cmd) { canvas()->addCommand(cmd); } } QList > DefaultTool::createOptionWidgets() { QList > widgets; m_tabbedOptionWidget = new DefaultToolTabbedWidget(this); if (isActivated()) { m_tabbedOptionWidget->activate(); } widgets.append(m_tabbedOptionWidget); connect(m_tabbedOptionWidget, SIGNAL(sigSwitchModeEditFillGradient(bool)), SLOT(slotActivateEditFillGradient(bool))); connect(m_tabbedOptionWidget, SIGNAL(sigSwitchModeEditStrokeGradient(bool)), SLOT(slotActivateEditStrokeGradient(bool))); return widgets; } void DefaultTool::canvasResourceChanged(int key, const QVariant &res) { if (key == HotPosition) { m_hotPosition = KoFlake::AnchorPosition(res.toInt()); repaintDecorations(); } } KoInteractionStrategy *DefaultTool::createStrategy(KoPointerEvent *event) { KoShapeManager *shapeManager = canvas()->shapeManager(); KoSelection *selection = koSelection(); bool insideSelection = false; KoFlake::SelectionHandle handle = handleAt(event->point, &insideSelection); bool editableShape = !selection->selectedEditableShapes().isEmpty(); const bool selectMultiple = event->modifiers() & Qt::ShiftModifier; const bool selectNextInStack = event->modifiers() & Qt::ControlModifier; const bool avoidSelection = event->modifiers() & Qt::AltModifier; if (selectNextInStack) { // change the hot selection position when middle clicking on a handle KoFlake::AnchorPosition newHotPosition = m_hotPosition; switch (handle) { case KoFlake::TopMiddleHandle: newHotPosition = KoFlake::Top; break; case KoFlake::TopRightHandle: newHotPosition = KoFlake::TopRight; break; case KoFlake::RightMiddleHandle: newHotPosition = KoFlake::Right; break; case KoFlake::BottomRightHandle: newHotPosition = KoFlake::BottomRight; break; case KoFlake::BottomMiddleHandle: newHotPosition = KoFlake::Bottom; break; case KoFlake::BottomLeftHandle: newHotPosition = KoFlake::BottomLeft; break; case KoFlake::LeftMiddleHandle: newHotPosition = KoFlake::Left; break; case KoFlake::TopLeftHandle: newHotPosition = KoFlake::TopLeft; break; case KoFlake::NoHandle: default: // check if we had hit the center point const KoViewConverter *converter = canvas()->viewConverter(); QPointF pt = converter->documentToView(event->point); // TODO: use calculated values instead! QPointF centerPt = converter->documentToView(selection->absolutePosition()); if (kisSquareDistance(pt, centerPt) < HANDLE_DISTANCE_SQ) { newHotPosition = KoFlake::Center; } break; } if (m_hotPosition != newHotPosition) { canvas()->resourceManager()->setResource(HotPosition, newHotPosition); return new NopInteractionStrategy(this); } } if (!avoidSelection && editableShape) { // manipulation of selected shapes goes first if (handle != KoFlake::NoHandle) { // resizing or shearing only with left mouse button if (insideSelection) { return new ShapeResizeStrategy(this, event->point, handle); } if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle || handle == KoFlake::BottomMiddleHandle || handle == KoFlake::LeftMiddleHandle) { return new ShapeShearStrategy(this, event->point, handle); } // rotating is allowed for rigth mouse button too if (handle == KoFlake::TopLeftHandle || handle == KoFlake::TopRightHandle || handle == KoFlake::BottomLeftHandle || handle == KoFlake::BottomRightHandle) { return new ShapeRotateStrategy(this, event->point, event->buttons()); } } if (!selectMultiple && !selectNextInStack) { if (insideSelection) { return new ShapeMoveStrategy(this, event->point); } } } KoShape *shape = shapeManager->shapeAt(event->point, selectNextInStack ? KoFlake::NextUnselected : KoFlake::ShapeOnTop); if (avoidSelection || (!shape && handle == KoFlake::NoHandle)) { if (!selectMultiple) { repaintDecorations(); selection->deselectAll(); } return new SelectionInteractionStrategy(this, event->point, false); } if (selection->isSelected(shape)) { if (selectMultiple) { repaintDecorations(); selection->deselect(shape); } } else if (handle == KoFlake::NoHandle) { // clicked on shape which is not selected repaintDecorations(); if (!selectMultiple) { shapeManager->selection()->deselectAll(); } selection->select(shape); repaintDecorations(); // tablet selection isn't precise and may lead to a move, preventing that if (event->isTabletEvent()) { return new NopInteractionStrategy(this); } return new ShapeMoveStrategy(this, event->point); } return 0; } void DefaultTool::updateActions() { QList editableShapes; if (koSelection()) { editableShapes = koSelection()->selectedEditableShapes(); } const bool orderingEnabled = !editableShapes.isEmpty(); action("object_order_front")->setEnabled(orderingEnabled); action("object_order_raise")->setEnabled(orderingEnabled); action("object_order_lower")->setEnabled(orderingEnabled); action("object_order_back")->setEnabled(orderingEnabled); const bool alignmentEnabled = editableShapes.size() > 1 || (!editableShapes.isEmpty() && canvas()->resourceManager()->hasResource(KoCanvasResourceManager::PageSize)); action("object_align_horizontal_left")->setEnabled(alignmentEnabled); action("object_align_horizontal_center")->setEnabled(alignmentEnabled); action("object_align_horizontal_right")->setEnabled(alignmentEnabled); action("object_align_vertical_top")->setEnabled(alignmentEnabled); action("object_align_vertical_center")->setEnabled(alignmentEnabled); action("object_align_vertical_bottom")->setEnabled(alignmentEnabled); action("object_group")->setEnabled(editableShapes.size() > 1); const bool distributionEnabled = editableShapes.size() > 2; action("object_distribute_horizontal_left")->setEnabled(distributionEnabled); action("object_distribute_horizontal_center")->setEnabled(distributionEnabled); action("object_distribute_horizontal_right")->setEnabled(distributionEnabled); action("object_distribute_horizontal_gaps")->setEnabled(distributionEnabled); action("object_distribute_vertical_top")->setEnabled(distributionEnabled); action("object_distribute_vertical_center")->setEnabled(distributionEnabled); action("object_distribute_vertical_bottom")->setEnabled(distributionEnabled); action("object_distribute_vertical_gaps")->setEnabled(distributionEnabled); bool hasGroupShape = false; foreach (KoShape *shape, editableShapes) { if (dynamic_cast(shape)) { hasGroupShape = true; break; } } action("object_ungroup")->setEnabled(hasGroupShape); emit selectionChanged(editableShapes.size()); } KoToolSelection *DefaultTool::selection() { return m_selectionHandler; } QMenu* DefaultTool::popupActionsMenu() { if (m_contextMenu) { m_contextMenu->clear(); KActionCollection *collection = this->canvas()->canvasController()->actionCollection(); m_contextMenu->addAction(collection->action("edit_cut")); m_contextMenu->addAction(collection->action("edit_copy")); m_contextMenu->addAction(collection->action("edit_paste")); m_contextMenu->addSeparator(); m_contextMenu->addAction(action("object_order_front")); m_contextMenu->addAction(action("object_order_raise")); m_contextMenu->addAction(action("object_order_lower")); m_contextMenu->addAction(action("object_order_back")); if (action("object_group")->isEnabled() || action("object_ungroup")->isEnabled()) { m_contextMenu->addSeparator(); m_contextMenu->addAction(action("object_group")); m_contextMenu->addAction(action("object_ungroup")); } } return m_contextMenu.data(); } void DefaultTool::explicitUserStrokeEndRequest() { QList shapes = koSelection()->selectedEditableShapesAndDelegates(); emit activateTemporary(KoToolManager::instance()->preferredToolForSelection(shapes)); } diff --git a/plugins/tools/karbonplugins/tools/filterEffectTool/FilterEffectScene.cpp b/plugins/tools/karbonplugins/tools/filterEffectTool/FilterEffectScene.cpp index edb995be3a..c2fc79a429 100644 --- a/plugins/tools/karbonplugins/tools/filterEffectTool/FilterEffectScene.cpp +++ b/plugins/tools/karbonplugins/tools/filterEffectTool/FilterEffectScene.cpp @@ -1,369 +1,369 @@ /* 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. */ #include "FilterEffectScene.h" #include "FilterEffectSceneItems.h" #include "KoShape.h" #include "KoFilterEffect.h" #include "KoFilterEffectStack.h" #include #include #include #include const qreal ItemSpacing = 10.0; const qreal ConnectionDistance = 10.0; ConnectionSource::ConnectionSource() : m_type(Effect) , m_effect(0) { } ConnectionSource::ConnectionSource(KoFilterEffect *effect, SourceType type) : m_type(type) , m_effect(effect) { } ConnectionSource::SourceType ConnectionSource::type() const { return m_type; } KoFilterEffect *ConnectionSource::effect() const { return m_effect; } ConnectionSource::SourceType ConnectionSource::typeFromString(const QString &str) { if (str == "SourceGraphic") { return SourceGraphic; } else if (str == "SourceAlpha") { return SourceAlpha; } else if (str == "BackgroundImage") { return BackgroundImage; } else if (str == "BackgroundAlpha") { return BackgroundAlpha; } else if (str == "FillPaint") { return FillPaint; } else if (str == "StrokePaint") { return StrokePaint; } else { return Effect; } } QString ConnectionSource::typeToString(SourceType type) { if (type == SourceGraphic) { return "SourceGraphic"; } else if (type == SourceAlpha) { return "SourceAlpha"; } else if (type == BackgroundImage) { return "BackgroundImage"; } else if (type == BackgroundAlpha) { return "BackgroundAlpha"; } else if (type == FillPaint) { return "FillPaint"; } else if (type == StrokePaint) { return "StrokePaint"; } else { return ""; } } ConnectionTarget::ConnectionTarget() : m_inputIndex(0) , m_effect(0) { } ConnectionTarget::ConnectionTarget(KoFilterEffect *effect, int inputIndex) : m_inputIndex(inputIndex) , m_effect(effect) { } int ConnectionTarget::inputIndex() const { return m_inputIndex; } KoFilterEffect *ConnectionTarget::effect() const { return m_effect; } FilterEffectScene::FilterEffectScene(QObject *parent) : QGraphicsScene(parent) , m_effectStack(0) { m_defaultInputs << "SourceGraphic" << "SourceAlpha"; m_defaultInputs << "FillPaint" << "StrokePaint"; m_defaultInputs << "BackgroundImage" << "BackgroundAlpha"; connect(this, SIGNAL(selectionChanged()), this, SLOT(selectionChanged())); } FilterEffectScene::~FilterEffectScene() { } void FilterEffectScene::initialize(KoFilterEffectStack *effectStack) { m_items.clear(); m_connectionItems.clear(); m_outputs.clear(); clear(); m_effectStack = effectStack; if (!m_effectStack) { return; } QList filterEffects = m_effectStack->filterEffects(); if (!filterEffects.count()) { return; } Q_FOREACH (KoFilterEffect *effect, filterEffects) { createEffectItems(effect); } layoutEffects(); layoutConnections(); } void FilterEffectScene::createEffectItems(KoFilterEffect *effect) { const bool isFirstItem = m_items.count() == 0; const QString defaultInput = isFirstItem ? "SourceGraphic" : m_items.last()->outputName(); QList inputs = effect->inputs(); for (int i = inputs.count(); i < effect->requiredInputCount(); ++i) { inputs.append(defaultInput); } QSet defaultItems; Q_FOREACH (const QString ¤tInput, inputs) { const QString &input = currentInput.isEmpty() ? defaultInput : currentInput; if (m_defaultInputs.contains(input) && ! defaultItems.contains(input)) { DefaultInputItem *item = new DefaultInputItem(input, effect); addSceneItem(item); m_outputs.insert(item->outputName(), item); defaultItems.insert(input); } } EffectItem *effectItem = new EffectItem(effect); // create connections int index = 0; Q_FOREACH (const QString ¤tInput, inputs) { const QString &input = currentInput.isEmpty() ? defaultInput : currentInput; EffectItemBase *outputItem = m_outputs.value(input, 0); if (outputItem) { ConnectionItem *connectionItem = new ConnectionItem(outputItem, effectItem, index); addSceneItem(connectionItem); } index++; } addSceneItem(effectItem); m_outputs.insert(effectItem->outputName(), effectItem); } void FilterEffectScene::addSceneItem(QGraphicsItem *item) { addItem(item); EffectItemBase *effectItem = dynamic_cast(item); if (effectItem) { m_items.append(effectItem); } else { ConnectionItem *connectionItem = dynamic_cast(item); if (connectionItem) { m_connectionItems.append(connectionItem); } } } void FilterEffectScene::layoutEffects() { QPointF position(25, 25); Q_FOREACH (EffectItemBase *item, m_items) { item->setPos(position); position.ry() += item->rect().height() + ItemSpacing; } } void FilterEffectScene::layoutConnections() { QList > sortedConnections; // calculate connection sizes from item distances int connectionIndex = 0; Q_FOREACH (ConnectionItem *item, m_connectionItems) { int sourceIndex = m_items.indexOf(item->sourceItem()); int targetIndex = m_items.indexOf(item->targetItem()); sortedConnections.append(QPair(targetIndex - sourceIndex, connectionIndex)); connectionIndex++; } - qSort(sortedConnections); + std::sort(sortedConnections.begin(), sortedConnections.end()); qreal distance = ConnectionDistance; int lastSize = -1; int connectionCount = sortedConnections.count(); for (int i = 0; i < connectionCount; ++i) { const QPair &connection = sortedConnections[i]; int size = connection.first; if (size > lastSize) { lastSize = size; distance += ConnectionDistance; } ConnectionItem *connectionItem = m_connectionItems[connection.second]; if (!connectionItem) { continue; } EffectItemBase *sourceItem = connectionItem->sourceItem(); EffectItemBase *targetItem = connectionItem->targetItem(); if (!sourceItem || ! targetItem) { continue; } int targetInput = connectionItem->targetInput(); QPointF sourcePos = sourceItem->mapToScene(sourceItem->outputPosition()); QPointF targetPos = targetItem->mapToScene(targetItem->inputPosition(targetInput)); QPainterPath path; path.moveTo(sourcePos + QPointF(0.5 * sourceItem->connectorSize().width(), 0)); path.lineTo(sourcePos + QPointF(distance, 0)); path.lineTo(targetPos + QPointF(distance, 0)); path.lineTo(targetPos + QPointF(0.5 * targetItem->connectorSize().width(), 0)); connectionItem->setPath(path); } } void FilterEffectScene::selectionChanged() { if (selectedItems().count()) { Q_FOREACH (EffectItemBase *item, m_items) { if (item->isSelected()) { item->setOpacity(1.0); } else { item->setOpacity(0.25); } } } else { Q_FOREACH (EffectItemBase *item, m_items) { item->setOpacity(1); } } } QList FilterEffectScene::selectedEffectItems() const { QList effectItems; QList selectedGraphicsItems = selectedItems(); if (!selectedGraphicsItems.count()) { return effectItems; } if (!m_items.count()) { return effectItems; } Q_FOREACH (QGraphicsItem *item, selectedGraphicsItems) { EffectItemBase *effectItem = dynamic_cast(item); if (!item) { continue; } ConnectionSource::SourceType type = ConnectionSource::Effect; KoFilterEffect *effect = effectItem->effect(); if (dynamic_cast(item)) { type = ConnectionSource::typeFromString(effectItem->outputName()); } effectItems.append(ConnectionSource(effect, type)); } return effectItems; } void FilterEffectScene::dropEvent(QGraphicsSceneDragDropEvent *event) { ConnectorItem *dropTargetItem = 0; QList itemsAtPositon = items(event->scenePos()); Q_FOREACH (QGraphicsItem *item, itemsAtPositon) { dropTargetItem = dynamic_cast(item); if (dropTargetItem) { break; } } if (!dropTargetItem) { return; } const ConnectorMimeData *data = dynamic_cast(event->mimeData()); if (!data) { return; } ConnectorItem *dropSourceItem = data->connector(); if (!dropSourceItem) { return; } EffectItemBase *outputParentItem = 0; KoFilterEffect *inputEffect = 0; KoFilterEffect *outputEffect = 0; int inputIndex = 0; if (dropTargetItem->connectorType() == ConnectorItem::Input) { // dropped output onto an input outputParentItem = dynamic_cast(dropSourceItem->parentItem()); outputEffect = dropSourceItem->effect(); inputEffect = dropTargetItem->effect(); inputIndex = dropTargetItem->connectorIndex(); } else { // dropped input onto an output outputParentItem = dynamic_cast(dropTargetItem->parentItem()); outputEffect = dropTargetItem->effect(); inputEffect = dropSourceItem->effect(); inputIndex = dropSourceItem->connectorIndex(); } ConnectionSource::SourceType outputType = ConnectionSource::Effect; // check if item with the output is a predefined one if (m_defaultInputs.contains(outputParentItem->outputName())) { outputType = ConnectionSource::typeFromString(outputParentItem->outputName()); outputEffect = 0; } ConnectionSource source(outputEffect, outputType); ConnectionTarget target(inputEffect, inputIndex); emit connectionCreated(source, target); } diff --git a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp index 1aec473778..2cd5dab9c5 100644 --- a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp +++ b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp @@ -1,291 +1,294 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_lazy_brush_options_widget.h" #include "ui_kis_tool_lazy_brush_options_widget.h" #include #include "KisPaletteModel.h" #include "kis_config.h" #include #include "kis_canvas_resource_provider.h" #include "kis_signal_auto_connection.h" #include "lazybrush/kis_colorize_mask.h" #include "kis_image.h" #include "kis_signals_blocker.h" #include "kis_signal_compressor.h" #include "kis_layer_properties_icons.h" struct KisToolLazyBrushOptionsWidget::Private { Private() : transparentColorIndex(-1), baseNodeChangedCompressor(500, KisSignalCompressor::FIRST_ACTIVE) { } Ui_KisToolLazyBrushOptionsWidget *ui; KisPaletteModel *colorModel; KisCanvasResourceProvider *provider; KisSignalAutoConnectionsStore providerSignals; KisSignalAutoConnectionsStore maskSignals; KisColorizeMaskSP activeMask; KoColorSet colorSet; int transparentColorIndex = -1; KisSignalCompressor baseNodeChangedCompressor; }; KisToolLazyBrushOptionsWidget::KisToolLazyBrushOptionsWidget(KisCanvasResourceProvider *provider, QWidget *parent) : QWidget(parent), m_d(new Private) { m_d->ui = new Ui_KisToolLazyBrushOptionsWidget(); m_d->ui->setupUi(this); m_d->colorModel = new KisPaletteModel(this); m_d->ui->colorView->setPaletteModel(m_d->colorModel); m_d->ui->colorView->setAllowModification(false); //people proly shouldn't be able to edit the colorentries themselves. m_d->ui->colorView->setCrossedKeyword("transparent"); - connect(m_d->ui->colorView, SIGNAL(clicked(QModelIndex)), this, SLOT(entrySelected(QModelIndex))); + connect(m_d->ui->colorView, SIGNAL(indexEntrySelected(QModelIndex)), this, SLOT(entrySelected(QModelIndex))); connect(m_d->ui->btnTransparent, SIGNAL(toggled(bool)), this, SLOT(slotMakeTransparent(bool))); connect(m_d->ui->btnRemove, SIGNAL(clicked()), this, SLOT(slotRemove())); connect(m_d->ui->chkAutoUpdates, SIGNAL(toggled(bool)), m_d->ui->btnUpdate, SLOT(setDisabled(bool))); connect(m_d->ui->btnUpdate, SIGNAL(clicked()), this, SLOT(slotUpdate())); connect(m_d->ui->chkAutoUpdates, SIGNAL(toggled(bool)), this, SLOT(slotSetAutoUpdates(bool))); connect(m_d->ui->chkShowKeyStrokes, SIGNAL(toggled(bool)), this, SLOT(slotSetShowKeyStrokes(bool))); connect(m_d->ui->chkShowOutput, SIGNAL(toggled(bool)), this, SLOT(slotSetShowOutput(bool))); connect(&m_d->baseNodeChangedCompressor, SIGNAL(timeout()), this, SLOT(slotUpdateNodeProperties())); m_d->provider = provider; const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); m_d->colorSet.add(KoColorSetEntry(KoColor(Qt::red, cs), "color1")); m_d->colorSet.add(KoColorSetEntry(KoColor(Qt::green, cs), "color2")); m_d->colorSet.add(KoColorSetEntry(KoColor(Qt::blue, cs), "color3")); m_d->colorModel->setColorSet(&m_d->colorSet); } KisToolLazyBrushOptionsWidget::~KisToolLazyBrushOptionsWidget() { } void KisToolLazyBrushOptionsWidget::showEvent(QShowEvent *event) { QWidget::showEvent(event); m_d->providerSignals.addConnection( m_d->provider, SIGNAL(sigNodeChanged(KisNodeSP)), this, SLOT(slotCurrentNodeChanged(KisNodeSP))); m_d->providerSignals.addConnection( m_d->provider, SIGNAL(sigFGColorChanged(const KoColor&)), this, SLOT(slotCurrentFgColorChanged(const KoColor&))); slotCurrentNodeChanged(m_d->provider->currentNode()); slotCurrentFgColorChanged(m_d->provider->fgColor()); } void KisToolLazyBrushOptionsWidget::hideEvent(QHideEvent *event) { QWidget::hideEvent(event); m_d->providerSignals.clear(); } void KisToolLazyBrushOptionsWidget::entrySelected(QModelIndex index) { + qDebug()<<"triggered"; if (!index.isValid()) return; + qDebug()<colorModel->idFromIndex(index); + qDebug()<= 0 && i < (int)m_d->colorSet.nColors()) { - KoColorSetEntry entry = m_d->colorSet.getColorGlobal(i); + KoColorSetEntry entry = m_d->colorModel->colorSetEntryFromIndex(index); m_d->provider->setFGColor(entry.color); } const bool transparentChecked = i >= 0 && i == m_d->transparentColorIndex; KisSignalsBlocker b(m_d->ui->btnTransparent); m_d->ui->btnTransparent->setChecked(transparentChecked); } void KisToolLazyBrushOptionsWidget::slotCurrentFgColorChanged(const KoColor &color) { int selectedIndex = -1; for (quint32 i = 0; i < m_d->colorSet.nColors(); i++) { KoColorSetEntry entry = m_d->colorSet.getColorGlobal(i); if (entry.color == color) { selectedIndex = (int)i; break; } } m_d->ui->btnRemove->setEnabled(selectedIndex >= 0); m_d->ui->btnTransparent->setEnabled(selectedIndex >= 0); if (selectedIndex < 0) { KisSignalsBlocker b(m_d->ui->btnTransparent); m_d->ui->btnTransparent->setChecked(false); } QModelIndex newIndex = m_d->colorModel->indexFromId(selectedIndex); if (newIndex != m_d->ui->colorView->currentIndex()) { m_d->ui->colorView->setCurrentIndex(newIndex); } } void KisToolLazyBrushOptionsWidget::slotColorLabelsChanged() { m_d->colorSet.clear(); m_d->transparentColorIndex = -1; if (m_d->activeMask) { KisColorizeMask::KeyStrokeColors colors = m_d->activeMask->keyStrokesColors(); m_d->transparentColorIndex = colors.transparentIndex; for (int i = 0; i < colors.colors.size(); i++) { const QString name = i == m_d->transparentColorIndex ? "transparent" : ""; m_d->colorSet.add(KoColorSetEntry(colors.colors[i], name)); } } m_d->colorModel->setColorSet(&m_d->colorSet); slotCurrentFgColorChanged(m_d->provider->fgColor()); } void KisToolLazyBrushOptionsWidget::slotUpdateNodeProperties() { KisSignalsBlocker b(m_d->ui->chkAutoUpdates, m_d->ui->btnUpdate, m_d->ui->chkShowKeyStrokes, m_d->ui->chkShowOutput); // not implemented yet! //m_d->ui->chkAutoUpdates->setEnabled(m_d->activeMask); m_d->ui->chkAutoUpdates->setEnabled(false); bool value = false; value = m_d->activeMask && !KisLayerPropertiesIcons::nodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeNeedsUpdate, true).toBool(); m_d->ui->btnUpdate->setEnabled(m_d->activeMask && !m_d->ui->chkAutoUpdates->isChecked()); m_d->ui->btnUpdate->setChecked(value); value = m_d->activeMask && KisLayerPropertiesIcons::nodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(); m_d->ui->chkShowKeyStrokes->setEnabled(m_d->activeMask); m_d->ui->chkShowKeyStrokes->setChecked(value); value = m_d->activeMask && KisLayerPropertiesIcons::nodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool(); m_d->ui->chkShowOutput->setEnabled(m_d->activeMask); m_d->ui->chkShowOutput->setChecked(value); } void KisToolLazyBrushOptionsWidget::slotCurrentNodeChanged(KisNodeSP node) { m_d->maskSignals.clear(); KisColorizeMask *mask = dynamic_cast(node.data()); m_d->activeMask = mask; if (m_d->activeMask) { m_d->maskSignals.addConnection( m_d->activeMask, SIGNAL(sigKeyStrokesListChanged()), this, SLOT(slotColorLabelsChanged())); m_d->maskSignals.addConnection( m_d->provider->currentImage(), SIGNAL(sigNodeChanged(KisNodeSP)), this, SLOT(slotUpdateNodeProperties())); } slotColorLabelsChanged(); slotUpdateNodeProperties(); m_d->ui->colorView->setEnabled(m_d->activeMask); } void KisToolLazyBrushOptionsWidget::slotMakeTransparent(bool value) { KIS_ASSERT_RECOVER_RETURN(m_d->activeMask); QModelIndex index = m_d->ui->colorView->currentIndex(); if (!index.isValid()) return; const int activeIndex = m_d->colorModel->idFromIndex(index); KIS_ASSERT_RECOVER_RETURN(activeIndex >= 0); KisColorizeMask::KeyStrokeColors colors; for (quint32 i = 0; i < m_d->colorSet.nColors(); i++) { colors.colors << m_d->colorSet.getColorGlobal(i).color; } colors.transparentIndex = value ? activeIndex : -1; m_d->activeMask->setKeyStrokesColors(colors); } void KisToolLazyBrushOptionsWidget::slotRemove() { KIS_ASSERT_RECOVER_RETURN(m_d->activeMask); QModelIndex index = m_d->ui->colorView->currentIndex(); if (!index.isValid()) return; const int activeIndex = m_d->colorModel->idFromIndex(index); KIS_ASSERT_RECOVER_RETURN(activeIndex >= 0); const KoColor color = m_d->colorSet.getColorGlobal((quint32)activeIndex).color; m_d->activeMask->removeKeyStroke(color); } void KisToolLazyBrushOptionsWidget::slotUpdate() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask); KisLayerPropertiesIcons::setNodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeNeedsUpdate, false, m_d->provider->currentImage()); } void KisToolLazyBrushOptionsWidget::slotSetAutoUpdates(bool value) { ENTER_FUNCTION() << ppVar(value); } void KisToolLazyBrushOptionsWidget::slotSetShowKeyStrokes(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask); KisLayerPropertiesIcons::setNodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, value, m_d->provider->currentImage()); } void KisToolLazyBrushOptionsWidget::slotSetShowOutput(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask); KisLayerPropertiesIcons::setNodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeShowColoring, value, m_d->provider->currentImage()); }