diff --git a/filters/karbon/wmf/WmfExport.cpp b/filters/karbon/wmf/WmfExport.cpp index 1610af84f55..c3a36c48e86 100644 --- a/filters/karbon/wmf/WmfExport.cpp +++ b/filters/karbon/wmf/WmfExport.cpp @@ -1,194 +1,196 @@ /* This file is part of the KDE project * Copyright (c) 2003 thierry lorthiois (lorthioist@wanadoo.fr) * 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 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 "WmfExport.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include + /* TODO: bs.wmf stroke in red with MSword and in brown with Words ?? */ K_PLUGIN_FACTORY_WITH_JSON(WmfExportFactory, "calligra_filter_karbon2wmf.json", registerPlugin();) WmfExport::WmfExport(QObject*parent, const QVariantList&) : KoFilter(parent) { } WmfExport::~WmfExport() { } KoFilter::ConversionStatus WmfExport::convert(const QByteArray& from, const QByteArray& to) { if (to != "image/x-wmf" || from != "application/vnd.oasis.opendocument.graphics") return KoFilter::NotImplemented; KoDocument * doc = m_chain->inputDocument(); if (! doc) return KoFilter::ParsingError; KarbonDocument * karbonPart = dynamic_cast(doc); if (! karbonPart) return KoFilter::WrongFormat; // open Placeable Wmf file mWmf = new Libwmf::WmfWriter(m_chain->outputFile()); if (!mWmf->begin()) { delete mWmf; return KoFilter::WrongFormat; } paintDocument(karbonPart); mWmf->end(); delete mWmf; return KoFilter::OK; } void WmfExport::paintDocument(KarbonDocument* document) { KoPAPageBase *page = document->pages().value(0); if (!page) { return; } // resolution mDpi = 1000; const KoPageLayout &layout = page->pageLayout(); QSizeF pageSize(layout.width, layout.height); int width = static_cast(POINT_TO_INCH(pageSize.width()) * mDpi); int height = static_cast(POINT_TO_INCH(pageSize.height()) * mDpi); mWmf->setDefaultDpi(mDpi); mWmf->setWindow(0, 0, width, height); if ((pageSize.width() != 0) && (pageSize.height() != 0)) { mScaleX = static_cast(width) / pageSize.width(); mScaleY = static_cast(height) / pageSize.height(); } QList shapes = page->shapes(); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); // Export layers. foreach(KoShape * shape, shapes) { if (dynamic_cast(shape)) continue; paintShape(shape); } } void WmfExport::paintShape(KoShape * shape) { QList subpaths = shape->outline().toFillPolygons(shape->absoluteTransformation(0)); if (! subpaths.count()) return; QList polygons; foreach(const QPolygonF & subpath, subpaths) { QPolygon p; uint pointCount = subpath.count(); for (uint i = 0; i < pointCount; ++i) p.append(QPoint(coordX(subpath[i].x()), coordY(subpath[i].y()))); polygons.append(p); } mWmf->setPen(getPen(shape->stroke())); if (polygons.count() == 1 && ! shape->background()) mWmf->drawPolyline(polygons.first()); else { QBrush fill(Qt::NoBrush); QSharedPointer cbg = qSharedPointerDynamicCast(shape->background()); if (cbg) fill = QBrush(cbg->color(), cbg->style()); QSharedPointer gbg = qSharedPointerDynamicCast(shape->background()); if (gbg) { fill = QBrush(*gbg->gradient()); fill.setTransform(gbg->transform()); } QSharedPointer pbg = qSharedPointerDynamicCast(shape->background()); if (pbg) { fill.setTextureImage(pbg->pattern()); fill.setTransform(pbg->transform()); } mWmf->setBrush(fill); if (polygons.count() == 1) mWmf->drawPolygon(polygons.first()); else mWmf->drawPolyPolygon(polygons); } } QPen WmfExport::getPen(const KoShapeStrokeModel * stroke) { const KoShapeStroke * lineStroke = dynamic_cast(stroke); if (! lineStroke) return QPen(Qt::NoPen); QPen pen(lineStroke->lineStyle()); if (pen.style() > Qt::SolidLine) pen.setDashPattern(lineStroke->lineDashes()); pen.setColor(lineStroke->color()); pen.setCapStyle(lineStroke->capStyle()); pen.setJoinStyle(lineStroke->joinStyle()); pen.setWidthF(coordX(lineStroke->lineWidth())); pen.setMiterLimit(lineStroke->miterLimit()); return pen; } int WmfExport::coordX(double left) { return (int)(left * mScaleX); } int WmfExport::coordY(double top) { return (int)(top * mScaleY); } #include diff --git a/filters/libmso/shapes.cpp b/filters/libmso/shapes.cpp index e40c71b1cdd..fc760fed251 100644 --- a/filters/libmso/shapes.cpp +++ b/filters/libmso/shapes.cpp @@ -1,1281 +1,1282 @@ /* This file is part of the KDE project Copyright (C) 2010 KO GmbH Copyright (C) 2011 Lukáš Tvrdý This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * Note that the implementations here are supposed to be defined by DrawingML. * This details the geometry etc. in Appendix D, in the file * presetShapeDefinitions.xml. * * @note Of the {Bent,Curved}Connector[2345] shapes, the MS Office only seems to * support the [23] variants. The remainder have been coded in a manner * consistent with the [23] variants, but have not been tested. */ #include "ODrawToOdf.h" #include "drawstyle.h" #include "msodraw.h" #include "generated/leinputstream.h" #include "writeodf/writeodfdraw.h" #include +#include #include #include #include #include using namespace MSO; using namespace writeodf; qint16 ODrawToOdf::normalizeRotation(qreal rotation) { qint16 angle = ((qint16)rotation) % 360; if (angle < 0) { angle = angle + 360; } return angle; } /** * Return the bounding rectangle for this object. **/ QRectF ODrawToOdf::getRect(const OfficeArtSpContainer &o) { if (o.childAnchor) { const OfficeArtChildAnchor& r = *o.childAnchor; return QRect(r.xLeft, r.yTop, r.xRight - r.xLeft, r.yBottom - r.yTop); } else if (o.clientAnchor && client) { return client->getRect(*o.clientAnchor); } else if (o.shapeProp.fHaveAnchor && client) { return client->getReserveRect(); } else { return QRectF(); } } QRectF ODrawToOdf::processRect(const quint16 shapeType, const qreal rotation, QRectF &rect) { bool transform_anchor = false; qint16 nrotation = normalizeRotation(rotation); //TODO: Add other shapes here! switch (shapeType) { case msosptNotPrimitive: if ( ((nrotation >= 45) && (nrotation < 135)) || ((nrotation >= 225) && (nrotation < 315))) { transform_anchor = true; } break; default: break; } if (transform_anchor) { QPointF center = rect.center(); QTransform transform; transform.rotate(90); rect = transform.mapRect(rect.translated(-center)).translated(center); } return rect; } void ODrawToOdf::processRectangle(const OfficeArtSpContainer& o, Writer& out) { // TODO: Use client->isPlaceholder - might require an update of the // placeholderAllowed function in the PPT filter. Trying to save as many // shapes into draw:text-box at the moment, because vertical alignment in // draw:custom-shape does not work properly (bug 288047). if (o.clientData && client->processRectangleAsTextBox(*o.clientData)) { processTextBox(o, out); } else { const DrawStyle ds(0, 0, &o); if (ds.pib()) { // see bug https://bugs.kde.org/show_bug.cgi?id=285577 processPictureFrame(o, out); } else { draw_custom_shape rect(&out.xml); processStyleAndText(o, out); draw_enhanced_geometry eg(rect.add_draw_enhanced_geometry()); eg.set_svg_viewBox("0 0 21600 21600"); eg.set_draw_enhanced_path("M 0 0 L 21600 0 21600 21600 0 21600 0 0 Z N"); eg.set_draw_type("rectangle"); setShapeMirroring(o, out); } } } void ODrawToOdf::processTextBox(const OfficeArtSpContainer& o, Writer& out) { draw_frame frame(&out.xml); processStyle(o, out); draw_text_box text(frame.add_draw_text_box()); processText(o, out); } void ODrawToOdf::processLine(const OfficeArtSpContainer& o, Writer& out) { const QRectF rect = getRect(o); qreal x1 = rect.x(); qreal y1 = rect.y(); qreal x2 = rect.x() + rect.width(); qreal y2 = rect.y() + rect.height(); // shape mirroring if (o.shapeProp.fFlipV) { qSwap(y1, y2); } if (o.shapeProp.fFlipH) { qSwap(x1, x2); } draw_line line(&out.xml, client->formatPos(out.hOffset(x1)), client->formatPos(out.hOffset(x2)), client->formatPos(out.vOffset(y1)), client->formatPos(out.vOffset(y2))); addGraphicStyleToDrawElement(out, o); line.set_draw_layer("layout"); processText(o, out); } void ODrawToOdf::drawStraightConnector1(qreal l, qreal t, qreal r, qreal b, Writer& out, QPainterPath &shapePath) const { out.xml.addAttribute("draw:type", "line"); shapePath.moveTo(l, t); shapePath.lineTo(r, b); } void ODrawToOdf::drawPathBentConnector2(qreal l, qreal t, qreal r, qreal b, Writer& out, QPainterPath &shapePath) const { Q_UNUSED(out); shapePath.moveTo(l, t); shapePath.lineTo(r, t); shapePath.lineTo(r, b); } void ODrawToOdf::drawPathBentConnector3(qreal l, qreal t, qreal r, qreal b, Writer& out, QPainterPath &shapePath) const { Q_UNUSED(out); qreal w = qAbs(r - l); qreal adj1 = 50000; qreal x1 = w * adj1 / 100000; shapePath.moveTo(l, t); shapePath.lineTo(l + x1, t); shapePath.lineTo(l + x1, b); shapePath.lineTo(r, b); } void ODrawToOdf::drawPathBentConnector4(qreal l, qreal t, qreal r, qreal b, Writer& out, QPainterPath &shapePath) const { Q_UNUSED(out); qreal w = qAbs(r - l); qreal h = qAbs(b - t); qreal adj1 = 50000; qreal adj2 = 50000; qreal x1 = w * adj1 / 100000; // qreal x2 = x1 + r / 2; qreal y2 = h * adj2 / 100000; // qreal y1 = t + y2 / 2; shapePath.moveTo(l, t); shapePath.lineTo(l + x1, t); shapePath.lineTo(l + x1, y2); shapePath.lineTo(r, y2); shapePath.lineTo(r, b); } void ODrawToOdf::drawPathBentConnector5(qreal l, qreal t, qreal r, qreal b, Writer& out, QPainterPath &shapePath) const { Q_UNUSED(out); qreal w = qAbs(r - l); qreal h = qAbs(b - t); qreal adj1 = 50000; qreal adj2 = 50000; qreal adj3 = 50000; qreal x1 = w * adj1 / 100000; qreal x3 = w * adj3 / 100000; // qreal x2 = x1 + x3 / 2; qreal y2 = h * adj2 / 100000; // qreal y1 = t + y2 / 2; // qreal y3 = b + y2 / 2; shapePath.moveTo(l, t); shapePath.lineTo(l + x1, t); shapePath.lineTo(l + x1, y2); shapePath.lineTo(l + x3, y2); shapePath.lineTo(l + x3, b); shapePath.lineTo(r, b); } void ODrawToOdf::drawPathCurvedConnector2(qreal l, qreal t, qreal r, qreal b, Writer& out, QPainterPath &shapePath) const { Q_UNUSED(out); qreal w = qAbs(r - l); qreal h = qAbs(b - t); shapePath.moveTo(l, t); shapePath.cubicTo(l + w / 2, t, r, h / 2, r, b); } void ODrawToOdf::drawPathCurvedConnector3(qreal l, qreal t, qreal r, qreal b, Writer& out, QPainterPath &shapePath) const { Q_UNUSED(out); qreal w = qAbs(r - l); qreal h = qAbs(b - t); qreal adj1 = 50000; qreal x2 = w * adj1 / 100000; qreal x1 = l + x2 /*/ 2*/; // qreal x3 = r + x2 / 2; // qreal y3 = h * 3 / 4; shapePath.moveTo(l, t); shapePath.cubicTo(x1, t, x1, t + h / 2, l + x2, t + h / 2); shapePath.cubicTo(l + x2, t + h / 2, l + x2, b, r, b); } void ODrawToOdf::drawPathCurvedConnector4(qreal l, qreal t, qreal r, qreal b, Writer& out, QPainterPath &shapePath) const { Q_UNUSED(out); qreal w = qAbs(r - l); qreal h = qAbs(b - t); qreal adj1 = 50000; qreal adj2 = 50000; qreal x2 = w * adj1 / 100000; qreal x1 = l + x2 / 2; qreal x3 = r + x2 / 2; qreal x4 = x2 + x3 / 2; qreal x5 = x3 + r / 2; qreal y4 = h * adj2 / 100000; qreal y1 = t + y4 / 2; qreal y2 = t + y1 / 2; qreal y3 = y1 + y4 / 2; qreal y5 = b + y4 / 2; shapePath.moveTo(l, t); shapePath.cubicTo(x1, t, l + x2, y2, l + x2, y1); shapePath.cubicTo(l + x2, y3, x4, y4, x3, y4); shapePath.cubicTo(x5, y4, r, y5, r, b); } void ODrawToOdf::drawPathCurvedConnector5(qreal l, qreal t, qreal r, qreal b, Writer& out, QPainterPath &shapePath) const { Q_UNUSED(out); qreal w = qAbs(r - l); qreal h = qAbs(b - t); qreal adj1 = 50000; qreal adj2 = 50000; qreal adj3 = 50000; qreal x3 = w * adj1 / 100000; qreal x6 = w * adj3 / 100000; qreal x1 = x3 + x6 / 2; qreal x2 = l + x3 / 2; qreal x4 = x3 + x1 / 2; qreal x5 = x6 + x1 / 2; qreal x7 = x6 + r / 2; qreal y4 = h * adj2 / 100000; qreal y1 = t + y4 / 2; qreal y2 = t + y1 / 2; qreal y3 = y1 + y4 / 2; qreal y5 = b + y4 / 2; qreal y6 = y5 + y4 / 2; qreal y7 = y5 + b / 2; shapePath.moveTo(l, t); shapePath.cubicTo(x2, t, l + x3, y2, l + x3, y1); shapePath.cubicTo(x3, y3, x4, y4, x1, y4); shapePath.cubicTo(x5, y4, l + x6, y6, l + x6, y5); shapePath.cubicTo(l + x6, y7, x7, b, r, b); } /** * Common handler for Connectors. */ void ODrawToOdf::processConnector(const OfficeArtSpContainer& o, Writer& out, PathArtist drawPath) { const OfficeArtDggContainer * drawingGroup = 0; if (client) { drawingGroup = client->getOfficeArtDggContainer(); } const OfficeArtSpContainer* master = 0; const DrawStyle ds(drawingGroup, master, &o); qreal rotation = toQReal( ds.rotation() ); const QRectF rect = getRect(o); qreal x1 = rect.x(); qreal y1 = rect.y(); qreal x2 = rect.x() + rect.width(); qreal y2 = rect.y() + rect.height(); QRectF shapeRect = rect; qreal sx1 = x1; qreal sy1 = y1; qreal sx2 = x2; qreal sy2 = y2; if (rotation != 0.0) { QTransform m; m.rotate( -rotation ); shapeRect = m.mapRect(rect.translated(-rect.center())).translated(rect.center()); sx1 = shapeRect.topLeft().x(); sy1 = shapeRect.topLeft().y(); sx2 = shapeRect.bottomRight().x(); sy2 = shapeRect.bottomRight().y(); } // Prepare to transform the path according the shape properties like flip // and rotation. QTransform m; m.reset(); m.translate( -shapeRect.center().x(), -shapeRect.center().y() ); // Mirroring if (o.shapeProp.fFlipH){ m.scale(-1,1); } if (o.shapeProp.fFlipV){ m.scale(1,-1); } if (rotation != 0) { m.rotate(rotation); } m.translate( shapeRect.center().x(), shapeRect.center().y() ); // the viewbox should be set, where is this done for draw:connector? out.xml.startElement("draw:connector"); addGraphicStyleToDrawElement(out, o); out.xml.addAttribute("draw:layer", "layout"); // Compute path and transform it. QPainterPath shapePath; (this->*drawPath)(sx1, sy1, sx2, sy2, out, shapePath); shapePath = m.map(shapePath); // translate the QPainterPath into svg:d attribute QString path = path2svg(shapePath); out.xml.addAttribute("svg:x1", client->formatPos(out.hOffset(x1))); out.xml.addAttribute("svg:y1", client->formatPos(out.vOffset(y1))); out.xml.addAttribute("svg:x2", client->formatPos(out.hOffset(x2))); out.xml.addAttribute("svg:y2", client->formatPos(out.vOffset(y2))); if (!path.isEmpty()) { out.xml.addAttribute("svg:d", path); } processText(o, out); out.xml.endElement(); } void ODrawToOdf::processPictureFrame(const OfficeArtSpContainer& o, Writer& out) { DrawStyle ds(0, &o); // A value of 0x00000000 MUST be ignored. [MS-ODRAW] — v20101219 if (!ds.pib()) return; draw_frame frame(&out.xml); processStyle(o, out); //NOTE: OfficeArtClienData might contain additional information //about a shape. QString url; if (client) { url = client->getPicturePath(ds.pib()); } // if the image cannot be found, just place an empty frame if (url.isEmpty()) { return; } draw_image image(frame.add_draw_image()); image.set_xlink_href(QUrl(url)); image.set_xlink_type("simple"); image.set_xlink_show("embed"); image.set_xlink_actuate("onLoad"); } void ODrawToOdf::processNotPrimitive(const MSO::OfficeArtSpContainer& o, Writer& out) { draw_custom_shape shape(&out.xml); processStyleAndText(o, out); draw_enhanced_geometry eg(shape.add_draw_enhanced_geometry()); setEnhancedGeometry(o, out); } void ODrawToOdf::processDrawingObject(const OfficeArtSpContainer& o, Writer& out) { if (!client) { qWarning() << "Warning: There's no Client!"; return; } quint16 shapeType = o.shapeProp.rh.recInstance; client->m_currentShapeType = o.shapeProp.rh.recInstance; switch (shapeType) { case msosptNotPrimitive: processNotPrimitive(o, out); break; case msosptRectangle: processRectangle(o, out); break; case msosptRoundRectangle: processRoundRectangle(o, out); break; case msosptEllipse: // TODO: Something has to be done here (LukasT). LukasT: // "Great comment", can you provide more details? :) processEllipse(o, out); break; case msosptDiamond: processDiamond(o, out); break; case msosptIsocelesTriangle: processIsocelesTriangle(o, out); break; case msosptRightTriangle: processRightTriangle(o, out); break; case msosptParallelogram: processParallelogram(o, out); break; case msosptTrapezoid: processTrapezoid(o, out); break; case msosptHexagon: processHexagon(o, out); break; case msosptOctagon: processOctagon(o, out); break; case msosptPlus: processPlus(o, out); break; case msosptStar: processStar(o, out); break; case msosptArrow: processArrow(o, out); break; // // TODO: msosptThickArrow // case msosptHomePlate: processHomePlate(o, out); break; case msosptCube: processCube(o, out); break; // // TODO: msosptBaloon, msosptSeal // // NOTE: OpenOffice treats msosptNotchedCircularArrow as msosptArc. The // msosptNotchedCircularArrow value SHOULD NOT be used according to the // MS-ODRAW spec. However it occurs in many Word8 files. case msosptArc: processNotchedCircularArrow(o, out); break; case msosptLine: processLine(o, out); break; case msosptPlaque: processPlaque(o, out); break; case msosptCan: processCan(o, out); break; case msosptDonut: processDonut(o, out); break; // // TODO: msosptTextSimple, msosptTextOctagon, msosptTextHexagon, // msosptTextCurve, msosptTextWave, msosptTextRing, msosptTextOnCurve, // msosptTextOnRing // case msosptStraightConnector1: processConnector(o, out, &ODrawToOdf::drawStraightConnector1); break; case msosptBentConnector2: processConnector(o, out, &ODrawToOdf::drawPathBentConnector2); break; case msosptBentConnector3: processConnector(o, out, &ODrawToOdf::drawPathBentConnector3); break; case msosptBentConnector4: processConnector(o, out, &ODrawToOdf::drawPathBentConnector4); break; case msosptBentConnector5: processConnector(o, out, &ODrawToOdf::drawPathBentConnector5); break; case msosptCurvedConnector2: processConnector(o, out, &ODrawToOdf::drawPathCurvedConnector2); break; case msosptCurvedConnector3: processConnector(o, out, &ODrawToOdf::drawPathCurvedConnector3); break; case msosptCurvedConnector4: processConnector(o, out, &ODrawToOdf::drawPathCurvedConnector4); break; case msosptCurvedConnector5: processConnector(o, out, &ODrawToOdf::drawPathCurvedConnector5); break; case msosptCallout1: processCallout1(o, out); break; case msosptCallout2: processCallout2(o, out); break; case msosptCallout3: processCallout3(o, out); break; case msosptAccentCallout1: processAccentCallout1(o, out); break; case msosptAccentCallout2: processAccentCallout2(o, out); break; case msosptAccentCallout3: processAccentCallout3(o, out); break; case msosptBorderCallout1: processBorderCallout1(o, out); break; case msosptBorderCallout2: processBorderCallout2(o, out); break; case msosptBorderCallout3: processBorderCallout3(o, out); break; case msosptAccentBorderCallout1: processAccentBorderCallout1(o, out); break; case msosptAccentBorderCallout2: processAccentBorderCallout2(o, out); break; case msosptAccentBorderCallout3: processAccentBorderCallout3(o, out); break; case msosptRibbon: processRibbon(o, out); break; case msosptRibbon2: processRibbon2(o, out); break; case msosptChevron: processChevron(o, out); break; case msosptPentagon: processPentagon(o, out); break; case msosptNoSmoking: processNoSmoking(o, out); break; case msosptSeal8: processSeal8(o, out); break; case msosptSeal16: processSeal16(o, out); break; case msosptSeal32: processSeal32(o, out); break; case msosptWedgeRectCallout: processWedgeRectCallout(o, out); break; case msosptWedgeRRectCallout: processWedgeRRectCallout(o, out); break; case msosptWedgeEllipseCallout: processWedgeEllipseCallout(o, out); break; case msosptWave: processWave(o, out); break; case msosptFoldedCorner: processFoldedCorner(o, out); break; case msosptLeftArrow: processLeftArrow(o, out); break; case msosptDownArrow: processDownArrow(o, out); break; case msosptUpArrow: processUpArrow(o, out); break; case msosptLeftRightArrow: processLeftRightArrow(o, out); break; case msosptUpDownArrow: processUpDownArrow(o, out); break; case msosptIrregularSeal1: processIrregularSeal1(o, out); break; case msosptIrregularSeal2: processIrregularSeal2(o, out); break; case msosptLightningBolt: processLightningBolt(o, out); break; case msosptHeart: processHeart(o, out); break; case msosptPictureFrame: processPictureFrame(o, out); break; case msosptQuadArrow: processQuadArrow(o, out); break; case msosptLeftArrowCallout: processLeftArrowCallout(o, out); break; case msosptRightArrowCallout: processRightArrowCallout(o, out); break; case msosptUpArrowCallout: processUpArrowCallout(o, out); break; case msosptDownArrowCallout: processDownArrowCallout(o, out); break; case msosptLeftRightArrowCallout: processLeftRightArrowCallout(o, out); break; case msosptUpDownArrowCallout: processUpDownArrowCallout(o, out); break; case msosptQuadArrowCallout: processQuadArrowCallout(o, out); break; case msosptBevel: processBevel(o, out); break; case msosptLeftBracket: processLeftBracket(o, out); break; case msosptRightBracket: processRightBracket(o, out); break; case msosptLeftBrace: processLeftBrace(o, out); break; case msosptRightBrace: processRightBrace(o, out); break; case msosptLeftUpArrow: processLeftUpArrow(o, out); break; case msosptBentUpArrow: processBentUpArrow(o, out); break; case msosptBentArrow: processBentArrow(o, out); break; case msosptSeal24: processSeal24(o, out); break; case msosptStripedRightArrow: processStripedRightArrow(o, out); break; case msosptNotchedRightArrow: processNotchedRightArrow(o, out); break; case msosptBlockArc: processBlockArc(o, out); break; case msosptSmileyFace: processSmileyFace(o, out); break; case msosptVerticalScroll: processVerticalScroll(o, out); break; case msosptHorizontalScroll: processHorizontalScroll(o, out); break; case msosptCircularArrow: processCircularArrow(o, out); break; case msosptNotchedCircularArrow: processNotchedCircularArrow(o, out); break; case msosptUturnArrow: processUturnArrow(o, out); break; case msosptCurvedRightArrow: processCurvedRightArrow(o, out); break; case msosptCurvedLeftArrow: processCurvedLeftArrow(o, out); break; case msosptCurvedUpArrow: processCurvedUpArrow(o, out); break; case msosptCurvedDownArrow: processCurvedDownArrow(o, out); break; case msosptCloudCallout: processCloudCallout(o, out); break; case msosptEllipseRibbon: processEllipseRibbon(o, out); break; case msosptEllipseRibbon2: processEllipseRibbon2(o, out); break; case msosptFlowChartProcess: processFlowChartProcess(o, out); break; case msosptFlowChartDecision: processFlowChartDecision(o, out); break; case msosptFlowChartInputOutput: processFlowChartInputOutput(o, out); break; case msosptFlowChartPredefinedProcess: processFlowChartPredefinedProcess(o, out); break; case msosptFlowChartInternalStorage: processFlowChartInternalStorage(o, out); break; case msosptFlowChartDocument: processFlowChartDocument(o, out); break; case msosptFlowChartMultidocument: processFlowChartMultidocument(o, out); break; case msosptFlowChartTerminator: processFlowChartTerminator(o, out); break; case msosptFlowChartPreparation: processFlowChartPreparation(o, out); break; case msosptFlowChartManualInput: processFlowChartManualInput(o, out); break; case msosptFlowChartManualOperation: processFlowChartManualOperation(o, out); break; case msosptFlowChartConnector: processFlowChartConnector(o, out); break; case msosptFlowChartPunchedCard: processFlowChartPunchedCard(o, out); break; case msosptFlowChartPunchedTape: processFlowChartPunchedTape(o, out); break; case msosptFlowChartSummingJunction: processFlowChartSummingJunction(o, out); break; case msosptFlowChartOr: processFlowChartOr(o, out); break; case msosptFlowChartCollate: processFlowChartCollate(o, out); break; case msosptFlowChartSort: processFlowChartSort(o, out); break; case msosptFlowChartExtract: processFlowChartExtract(o, out); break; case msosptFlowChartMerge: processFlowChartMerge(o, out); break; // // TODO: msosptFlowChartOfflineStorage // case msosptFlowChartOnlineStorage: processFlowChartOnlineStorage(o, out); break; case msosptFlowChartMagneticTape: processFlowChartMagneticTape(o, out); break; case msosptFlowChartMagneticDisk: processFlowChartMagneticDisk(o, out); break; case msosptFlowChartMagneticDrum: processFlowChartMagneticDrum(o, out); break; case msosptFlowChartDisplay: processFlowChartDisplay(o, out); break; case msosptFlowChartDelay: processFlowChartDelay(o, out); break; // // TODO: msosptTextPlainText, msosptTextStop, msosptTextTriangle, // msosptTextTriangleInverted, msosptTextChevron, // msosptTextChevronInverted, msosptTextRingInside, msosptTextRingOutside, // msosptTextArchUpCurve, msosptTextArchDownCurve, msosptTextCircleCurve, // msosptTextButtonCurve, msosptTextArchUpPour, msosptTextArchDownPour, // msosptTextCirclePour, msosptTextButtonPour, msosptTextCurveUp, // msosptTextCurveDown, msosptTextCascadeUp, msosptTextCascadeDown, // msosptTextWave1, msosptTextWave2, msosptTextWave3, msosptTextWave4, // msosptTextInflate, msosptTextDeflate, msosptTextInflateBottom, // msosptTextDeflateBottom, msosptTextInflateTop, msosptTextDeflateTop, // msosptTextDeflateInflate, msosptTextDeflateInflateDeflate, // msosptTextFadeRight, msosptTextFadeLeft, msosptTextFadeUp, // msosptTextFadeDown, msosptTextSlantUp, msosptTextSlantDown, // msosptTextCanUp, msosptTextCanDown // case msosptFlowChartAlternateProcess: processFlowChartAlternateProcess(o, out); break; case msosptFlowChartOffpageConnector: processFlowChartOffpageConnector(o, out); break; case msosptCallout90: processCallout90(o, out); break; case msosptAccentCallout90: processAccentCallout90(o, out); break; case msosptBorderCallout90: processBorderCallout90(o, out); break; case msosptAccentBorderCallout90: processAccentBorderCallout90(o, out); break; case msosptLeftRightUpArrow: processLeftRightUpArrow(o, out); break; case msosptSun: processSun(o, out); break; case msosptMoon: processMoon(o, out); break; case msosptBracketPair: processBracketPair(o, out); break; case msosptBracePair: processBracePair(o, out); break; case msosptSeal4: processSeal4(o, out); break; case msosptDoubleWave: processDoubleWave(o, out); break; case msosptActionButtonBlank: processActionButtonBlank(o, out); break; case msosptActionButtonHome: processActionButtonHome(o, out); break; case msosptActionButtonHelp: processActionButtonHelp(o, out); break; case msosptActionButtonInformation: processActionButtonInformation(o, out); break; case msosptActionButtonForwardNext: processActionButtonForwardNext(o, out); break; case msosptActionButtonBackPrevious: processActionButtonBackPrevious(o, out); break; case msosptActionButtonEnd: processActionButtonEnd(o, out); break; case msosptActionButtonBeginning: processActionButtonBeginning(o, out); break; case msosptActionButtonReturn: processActionButtonReturn(o, out); break; case msosptActionButtonDocument: processActionButtonDocument(o, out); break; case msosptActionButtonSound: processActionButtonSound(o, out); break; case msosptActionButtonMovie: processActionButtonMovie(o, out); break; case msosptHostControl: processPictureFrame(o, out); break; case msosptTextBox: processTextBox(o, out); break; default: qDebug() << "Cannot handle shape 0x" << hex << shapeType; break; } } void ODrawToOdf::processStyleAndText(const MSO::OfficeArtSpContainer& o, Writer& out) { processStyle(o, out); processText(o, out); } void ODrawToOdf::processStyle(const MSO::OfficeArtSpContainer& o, Writer& out) { addGraphicStyleToDrawElement(out, o); set2dGeometry(o, out); } void ODrawToOdf::processText(const MSO::OfficeArtSpContainer& o, Writer& out) { if (!client) { qWarning() << "Warning: There's no Client!"; return; } if (o.clientData && client->onlyClientData(*o.clientData)) { client->processClientData(o.clientTextbox.data(), *o.clientData, out); } else if (o.clientTextbox) { client->processClientTextBox(*o.clientTextbox, o.clientData.data(), out); } } void ODrawToOdf::processModifiers(const MSO::OfficeArtSpContainer &o, Writer &out, const QList& defaults) { const AdjustValue* val1 = get(o); if (!val1 && defaults.isEmpty()) return; const Adjust2Value* val2 = get(o); const Adjust3Value* val3 = get(o); const Adjust4Value* val4 = get(o); const Adjust5Value* val5 = get(o); const Adjust6Value* val6 = get(o); const Adjust7Value* val7 = get(o); const Adjust8Value* val8 = get(o); QString modifiers = QString::number(val1 ? val1->adjustvalue : defaults[0]); if (val2 || defaults.size() > 1) { modifiers += QString(" %1").arg(val2 ? val2->adjust2value : defaults[1]); if (val3 || defaults.size() > 2) { modifiers += QString(" %1").arg(val3 ? val3->adjust3value : defaults[2]); if (val4 || defaults.size() > 3) { modifiers += QString(" %1").arg(val4 ? val4->adjust4value : defaults[3]); if (val5 || defaults.size() > 4) { modifiers += QString(" %1").arg(val5 ? val5->adjust5value : defaults[4]); if (val6 || defaults.size() > 5) { modifiers += QString(" %1").arg(val6 ? val6->adjust6value : defaults[5]); if (val7 || defaults.size() > 6) { modifiers += QString(" %1").arg(val7 ? val7->adjust7value : defaults[6]); if (val8 || defaults.size() > 7) { modifiers += QString(" %1").arg(val8 ? val8->adjust8value : defaults[7]); } } } } } } } out.xml.addAttribute("draw:modifiers", modifiers); } // Position the shape into the slide or into a group shape void ODrawToOdf::set2dGeometry(const OfficeArtSpContainer& o, Writer& out) { const OfficeArtDggContainer* dgg = 0; const OfficeArtSpContainer* master = 0; const DrawStyle ds(dgg, master, &o); const qreal rotation = toQReal(ds.rotation()); //transform the rectangle into the coordinate system of the group shape QRectF rect = getRect(o); QRectF trect (out.hOffset(rect.x()), out.vOffset(rect.y()), out.hLength(rect.width()), out.vLength(rect.height())); //draw:caption-id //draw:class-names //draw:data //draw:engine //draw:layer out.xml.addAttribute("draw:layer", "layout"); //draw:name //draw:style-name //draw:text-style-name //draw:transform if (rotation) { const quint16 shapeType = o.shapeProp.rh.recInstance; const quint16 nrotation = normalizeRotation(rotation); const qreal angle = (nrotation / (qreal)180) * M_PI; trect = processRect(shapeType, rotation, trect); static const QString transform_str("translate(%1 %2) rotate(%3) translate(%4 %5)"); const QPointF center = trect.center(); const qreal height = trect.height(); const qreal width = trect.width(); out.xml.addAttribute("draw:transform", transform_str.arg(client->formatPos(-width/2)).arg(client->formatPos(-height/2)).arg(-angle).arg(client->formatPos(center.x())).arg(client->formatPos(center.y()))); } //svg:x //svg:y else { out.xml.addAttribute("svg:x", client->formatPos(trect.x())); out.xml.addAttribute("svg:y", client->formatPos(trect.y())); } //NOTE: z-index is set in ODrawToOdf::Client::addTextStyles //draw:z-index //presentation:class-names //presentation:style-name //svg:height out.xml.addAttribute("svg:height", client->formatPos(trect.height())); //svg:width out.xml.addAttribute("svg:width", client->formatPos(trect.width())); //table:end-cell-address //table:end-x //table:end-y //table:table-background //text:anchor-page-number //text:anchor-type //xml:id } void ODrawToOdf::setEnhancedGeometry(const MSO::OfficeArtSpContainer& o, Writer& out) { const OfficeArtDggContainer* drawingGroup = 0; const OfficeArtSpContainer* master = 0; const DrawStyle ds(drawingGroup, master, &o); IMsoArray _v = ds.pVertices_complex(); IMsoArray segmentInfo = ds.pSegmentInfo_complex(); if (!_v.data.isEmpty() && !segmentInfo.data.isEmpty()) { QVector verticesPoints; //_v.data is an array of POINTs, MS-ODRAW, page 89 QByteArray xArray(sizeof(int), 0), yArray(sizeof(int), 0); int step = _v.cbElem; if (step == 0xfff0) { step = 4; } int maxX = 0, minX = INT_MAX, maxY = 0, minY = INT_MAX; int x,y; //get vertice points for (int i = 0, offset = 0; i < _v.nElems; i++, offset += step) { // x coordinate of this point xArray.replace(0, step/2, _v.data.mid(offset, step/2)); x = *(int*) xArray.data(); // y coordinate of this point yArray.replace(0, step/2, _v.data.mid(offset + step/2, step/2)); y = *(int*) yArray.data(); verticesPoints.append(QPoint(x, y)); // find maximum and minimum coordinates if (maxY < y) { maxY = y; } if (minY > y) { minY = y ; } if (maxX < x) { maxX = x; } if (minX > x) { minX = x; } } //TODO: geoLeft, geoTop, geoRight, geoBottom QString viewBox = QString::number(minX) + ' ' + QString::number(minY) + ' ' + QString::number(maxX) + ' ' + QString::number(maxY); // combine segmentationInfoData and verticePoints into enhanced-path string QString enhancedPath; ushort msopathtype; bool nOffRange = false; for (int i = 0, n = 0; ((i < segmentInfo.nElems) && !nOffRange); i++) { msopathtype = (((*(ushort *)(segmentInfo.data.data() + i * 2)) >> 13) & 0x7); switch (msopathtype) { case msopathLineTo: { if (n >= verticesPoints.size()) { qDebug() << "EnhancedGeometry: index into verticesPoints out of range!"; nOffRange = true; break; } enhancedPath = enhancedPath + "L " + QString::number(verticesPoints[n].x()) + ' ' + QString::number(verticesPoints[n].y()) + ' '; n++; break; } case msopathCurveTo: { if (n + 2 >= verticesPoints.size()) { qDebug() << "EnhancedGeometry: index into verticesPoints out of range!"; nOffRange = true; break; } QPoint pt1 = verticesPoints.at(n); QPoint pt2 = verticesPoints.at(n + 1); QPoint pt3 = verticesPoints.at(n + 2); enhancedPath = enhancedPath + "C " + QString::number(pt1.x()) + ' ' + QString::number(pt1.y()) + ' ' + QString::number(pt2.x()) + ' ' + QString::number(pt2.y()) + ' ' + QString::number(pt3.x()) + ' ' + QString::number(pt3.y()) + ' '; n = n + 3; break; } case msopathMoveTo: { if (n >= verticesPoints.size()) { qDebug() << "EnhancedGeometry: index into verticesPoints out of range!"; nOffRange = true; break; } enhancedPath = enhancedPath + "M " + QString::number(verticesPoints[n].x()) + ' ' + QString::number(verticesPoints[n].y()) + ' '; n++; break; } case msopathClose: enhancedPath = enhancedPath + "Z "; break; case msopathEnd: enhancedPath = enhancedPath + "N "; break; case msopathEscape: case msopathClientEscape: break; } } //dr3d:projection //dr3d:shade-mode //draw:concentric-gradient-fill-allowed //draw:enhanced-path out.xml.addAttribute("draw:enhanced-path", enhancedPath); //draw:extrusion //draw:extrusion-allowed //draw:extrusion-brightness //draw:extrusion-color //draw:extrusion-depth //draw:extrusion-diffusion //draw:extrusion-first-light-direction //draw:extrusion-first-light-harsh //draw:extrusion-first-light-level //draw:extrusion-light-face //draw:extrusion-metal //draw:extrusion-number-of-line-segments //draw:extrusion-origin //draw:extrusion-rotation-angle //draw:extrusion-rotation-center //draw:extrusion-second-light-direction //draw:extrusion-second-light-harsh //draw:extrusion-second-light-level //draw:extrusion-shininess //draw:extrusion-skew //draw:extrusion-specularity //draw:extrusion-viewpoint //draw:glue-point-leaving-directions //draw:glue-points //draw:glue-point-type //draw:mirror-horizontal if (o.shapeProp.fFlipH) { out.xml.addAttribute("draw:mirror-horizontal", "true"); } //draw:mirror-vertical if (o.shapeProp.fFlipV) { out.xml.addAttribute("draw:mirror-vertical", "true"); } //draw:modifiers //draw:path-stretchpoint-x //draw:path-stretchpoint-y //draw:text-areas //draw:text-path //draw:text-path-allowed //draw:text-path-mode //draw:text-path-same-letter-heights //draw:text-path-scale //draw:text-rotate-angle //draw:type out.xml.addAttribute("draw:type", "non-primitive"); //svg:viewBox out.xml.addAttribute("svg:viewBox", viewBox); } } QString ODrawToOdf::path2svg(const QPainterPath &path) { QString d; int count = path.elementCount(); for (int i = 0; i < count; i++) { QPainterPath::Element e = path.elementAt(i); switch(e.type) { case QPainterPath::MoveToElement: d.append(QString("M %1 %2").arg(e.x).arg(e.y)); break; case QPainterPath::LineToElement: d.append(QString("L %1 %2").arg(e.x).arg(e.y)); break; case QPainterPath::CurveToElement: d.append(QString("C %1 %2").arg(e.x).arg(e.y)); break; case QPainterPath::CurveToDataElement: d.append(QString(" %1 %2").arg(e.x).arg(e.y)); break; default: qWarning() << "This element unhandled: " << e.type; } } return d; } void ODrawToOdf::setShapeMirroring(const MSO::OfficeArtSpContainer& o, Writer& out) { if (o.shapeProp.fFlipV) { out.xml.addAttribute("draw:mirror-vertical", "true"); } if (o.shapeProp.fFlipH) { out.xml.addAttribute("draw:mirror-horizontal", "true"); } } diff --git a/karbon/common/KarbonOutlinePaintingStrategy.cpp b/karbon/common/KarbonOutlinePaintingStrategy.cpp index 8bb37d874b2..4e6e97d372d 100644 --- a/karbon/common/KarbonOutlinePaintingStrategy.cpp +++ b/karbon/common/KarbonOutlinePaintingStrategy.cpp @@ -1,67 +1,68 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KarbonOutlinePaintingStrategy.h" #include #include #include #include #include +#include class OutlineStroke : public KoShapeStroke { public: OutlineStroke() : m_pen(Qt::black) { } using KoShapeStroke::paint; void paint(KoShape *shape, QPainter &painter, const KoViewConverter &converter) override { KoShape::applyConversion(painter, converter); painter.strokePath(shape->outline(), m_pen); } private: QPen m_pen; }; KarbonOutlinePaintingStrategy::KarbonOutlinePaintingStrategy(KoShapeManager * shapeManager) : KoShapeManagerPaintingStrategy(shapeManager), m_stroke(new OutlineStroke()) { Q_ASSERT(shapeManager); shapeManager->setPaintingStrategy(this); } KarbonOutlinePaintingStrategy::~KarbonOutlinePaintingStrategy() { delete m_stroke; } void KarbonOutlinePaintingStrategy::paint(KoShape * shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &/*paintContext*/) { painter.save(); painter.setTransform(shape->absoluteTransformation(&converter) * painter.transform()); m_stroke->paint(shape, painter, converter); painter.restore(); } diff --git a/karbon/ui/widgets/KarbonSmallStylePreview.cpp b/karbon/ui/widgets/KarbonSmallStylePreview.cpp index 9f70d8e258c..b835f4fce83 100644 --- a/karbon/ui/widgets/KarbonSmallStylePreview.cpp +++ b/karbon/ui/widgets/KarbonSmallStylePreview.cpp @@ -1,243 +1,244 @@ /* This file is part of the KDE project Made by Tomislav Lukman (tomislav.lukman@ck.t-com.hr) Copyright (c) 2005 Tomislav Lukman Copyright (C) 2006 Tim Beaulen Copyright (c) 2008 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KarbonSmallStylePreview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #define FRAMEWIDTH 75 #define FRAMEHEIGHT 15 class KarbonFillStyleWidget : public QPushButton { public: KarbonFillStyleWidget(QWidget * parent) : QPushButton(parent), m_fill(0), m_checkerPainter(5) { setCursor(Qt::PointingHandCursor); setToolTip(i18n("Press to apply fill to selection")); } ~KarbonFillStyleWidget() override { } void setFill(QSharedPointer fill) { m_fill = fill; update(); } protected: void paintEvent(QPaintEvent* event) override { QPainter painter(this); painter.setClipRect(event->rect()); if (m_fill) { m_checkerPainter.paint(painter, rect()); QSharedPointer gradientFill = qSharedPointerDynamicCast(m_fill); if (gradientFill) { const QGradient * gradient = gradientFill->gradient(); QGradient * defGradient = KoGradientHelper::defaultGradient(gradient->type(), gradient->spread(), gradient->stops()); QBrush brush(*defGradient); delete defGradient; painter.setBrush(brush); painter.setPen(Qt::NoPen); painter.drawRect(rect()); } else { // use the background to draw painter.setPen(Qt::NoPen); QPainterPath p; p.addRect(rect()); KoViewConverter converter; KoShapePaintingContext context; m_fill->paint(painter, converter, context, p); } } else { painter.setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); painter.setBrush(Qt::black); painter.setPen(Qt::black); painter.drawText(rect(), Qt::AlignCenter, i18nc("The style has no fill", "None")); } painter.end(); //QPushButton::paintEvent( event ); } private: QSharedPointer m_fill; ///< the fill to preview KoCheckerBoardPainter m_checkerPainter; }; class KarbonStrokeStyleWidget : public QPushButton { public: KarbonStrokeStyleWidget(QWidget * parent) : QPushButton(parent), m_stroke(0), m_checkerPainter(5) { setCursor(Qt::PointingHandCursor); setToolTip(i18n("Press to apply stroke to selection")); } ~KarbonStrokeStyleWidget() override { if (m_stroke && !m_stroke->deref()) delete m_stroke; } void setStroke(KoShapeStrokeModel * stroke) { if (stroke != m_stroke) { if (m_stroke && !m_stroke->deref()) delete m_stroke; m_stroke = stroke; if (m_stroke) m_stroke->ref(); } update(); } protected: void paintEvent(QPaintEvent* event) override { QPainter painter(this); painter.setClipRect(event->rect()); if (m_stroke) { m_checkerPainter.paint(painter, rect()); const KoShapeStroke * line = dynamic_cast(m_stroke); if (line) { painter.setPen(Qt::NoPen); QBrush brush = line->lineBrush(); if (brush.gradient()) { QGradient * defGradient = KoGradientHelper::defaultGradient(brush.gradient()->type(), brush.gradient()->spread(), brush.gradient()->stops()); QBrush brush(*defGradient); delete defGradient; painter.setBrush(brush); painter.setPen(Qt::NoPen); painter.drawRect(rect()); } else if (brush.style() == Qt::TexturePattern) { painter.fillRect(rect(), brush); } else { painter.fillRect(rect(), QBrush(line->color())); } } else { painter.setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); painter.setBrush(Qt::black); painter.setPen(Qt::black); painter.drawText(rect(), Qt::AlignCenter, i18nc("The style has a custom stroking", "Custom")); } } else { painter.setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); painter.setBrush(Qt::black); painter.setPen(Qt::black); painter.drawText(rect(), Qt::AlignCenter, i18nc("The style has no stroking", "None")); } painter.end(); //QPushButton::paintEvent( event ); } private: KoShapeStrokeModel * m_stroke; ///< the stroke to preview KoCheckerBoardPainter m_checkerPainter; }; KarbonSmallStylePreview::KarbonSmallStylePreview(QWidget* parent) : QWidget(parent) { setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); /* Create widget layout */ QHBoxLayout *layout = new QHBoxLayout(this); QLabel * strokeLabel = new QLabel(i18n("Stroke:"), this); strokeLabel->setMinimumHeight(FRAMEHEIGHT); m_strokeFrame = new KarbonStrokeStyleWidget(this); m_strokeFrame->setMinimumSize(QSize(FRAMEWIDTH, FRAMEHEIGHT)); QLabel * fillLabel = new QLabel(i18n("Fill:"), this); fillLabel->setMinimumHeight(FRAMEHEIGHT); m_fillFrame = new KarbonFillStyleWidget(this); m_fillFrame->setMinimumSize(QSize(FRAMEWIDTH, FRAMEHEIGHT)); layout->addWidget(strokeLabel); layout->addWidget(m_strokeFrame); layout->addWidget(fillLabel); layout->addWidget(m_fillFrame); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); connect(KoToolManager::instance(), SIGNAL(changedCanvas(const KoCanvasBase *)), this, SLOT(canvasChanged(const KoCanvasBase *))); connect(m_strokeFrame, SIGNAL(clicked()), this, SIGNAL(strokeApplied())); connect(m_fillFrame, SIGNAL(clicked()), this, SIGNAL(fillApplied())); } KarbonSmallStylePreview::~KarbonSmallStylePreview() { } void KarbonSmallStylePreview::canvasChanged(const KoCanvasBase *canvas) { if (canvas) { connect(canvas->shapeManager(), SIGNAL(selectionChanged()), this, SLOT(selectionChanged())); connect(canvas->shapeManager(), SIGNAL(selectionContentChanged()), this, SLOT(selectionChanged())); } selectionChanged(); } void KarbonSmallStylePreview::selectionChanged() { KoCanvasController * controller = KoToolManager::instance()->activeCanvasController(); if (! controller || ! controller->canvas()) { m_fillFrame->setFill(QSharedPointer(0)); m_strokeFrame->setStroke(0); QWidget::update(); return; } KoShape * shape = controller->canvas()->shapeManager()->selection()->firstSelectedShape(); if (shape) { m_fillFrame->setFill(shape->background()); m_strokeFrame->setStroke(shape->stroke()); } else { m_fillFrame->setFill(QSharedPointer(0)); m_strokeFrame->setStroke(0); } QWidget::update(); } diff --git a/libs/basicflakes/tools/KoCreatePathTool_p.h b/libs/basicflakes/tools/KoCreatePathTool_p.h index d5d10beb5e5..0f275ddfb68 100644 --- a/libs/basicflakes/tools/KoCreatePathTool_p.h +++ b/libs/basicflakes/tools/KoCreatePathTool_p.h @@ -1,445 +1,447 @@ /* This file is part of the KDE project * * Copyright (C) 2006 Thorsten Zachmann * Copyright (C) 2008-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. */ #ifndef KOCREATEPATHTOOL_P_H #define KOCREATEPATHTOOL_P_H #include "KoCreatePathTool.h" #include "KoStrokeConfigWidget.h" #include "KoPathPoint.h" #include "KoPathPointData.h" #include "KoPathPointMergeCommand.h" #include "KoCanvasBase.h" #include "KoParameterShape.h" #include "KoViewConverter.h" #include "KoShapeManager.h" #include "KoSnapStrategy.h" #include "KoToolBase_p.h" +#include + #include "math.h" /// Small helper to keep track of a path point and its parent path shape struct PathConnectionPoint { PathConnectionPoint() : path(0), point(0) { } // reset state to invalid void reset() { path = 0; point = 0; } PathConnectionPoint& operator =(KoPathPoint * pathPoint) { if (!pathPoint || ! pathPoint->parent()) { reset(); } else { path = pathPoint->parent(); point = pathPoint; } return *this; } bool operator != (const PathConnectionPoint &rhs) const { return rhs.path != path || rhs.point != point; } bool operator == (const PathConnectionPoint &rhs) const { return rhs.path == path && rhs.point == point; } bool isValid() const { return path && point; } // checks if the path and point are still valid void validate(KoCanvasBase *canvas) { // no point in validating an already invalid state if (!isValid()) { return; } // we need canvas to validate if (!canvas) { reset(); return; } // check if path is still part of the docment if (!canvas->shapeManager()->shapes().contains(path)) { reset(); return; } // check if point is still part of the path if (path->pathPointIndex(point) == KoPathPointIndex(-1,-1)) { reset(); return; } } KoPathShape * path; KoPathPoint * point; }; inline qreal squareDistance( const QPointF &p1, const QPointF &p2) { qreal dx = p1.x()-p2.x(); qreal dy = p1.y()-p2.y(); return dx*dx + dy*dy; } class AngleSnapStrategy : public KoSnapStrategy { public: explicit AngleSnapStrategy( qreal angleStep, bool active) : KoSnapStrategy(KoSnapGuide::CustomSnapping), m_angleStep(angleStep), m_active(active) { } void setStartPoint(const QPointF &startPoint) { m_startPoint = startPoint; } void setAngleStep(qreal angleStep) { m_angleStep = qAbs(angleStep); } bool snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance) override { Q_UNUSED(proxy); if (!m_active) return false; QLineF line(m_startPoint, mousePosition); qreal currentAngle = line.angle(); int prevStep = qAbs(currentAngle / m_angleStep); int nextStep = prevStep + 1; qreal prevAngle = prevStep*m_angleStep; qreal nextAngle = nextStep*m_angleStep; if (qAbs(currentAngle - prevAngle) <= qAbs(currentAngle - nextAngle)) { line.setAngle(prevAngle); } else { line.setAngle(nextAngle); } qreal maxSquareSnapDistance = maxSnapDistance*maxSnapDistance; qreal snapDistance = squareDistance(mousePosition, line.p2()); if (snapDistance > maxSquareSnapDistance) return false; setSnappedPosition(line.p2()); return true; } QPainterPath decoration(const KoViewConverter &converter) const override { Q_UNUSED(converter); QPainterPath decoration; decoration.moveTo(m_startPoint); decoration.lineTo(snappedPosition()); return decoration; } void deactivate() { m_active = false; } void activate(){ m_active = true; } private: QPointF m_startPoint; qreal m_angleStep; bool m_active; }; class KoCreatePathToolPrivate : public KoToolBasePrivate { KoCreatePathTool * const q; public: KoCreatePathToolPrivate(KoCreatePathTool * const qq, KoCanvasBase* canvas) : KoToolBasePrivate(qq, canvas), q(qq), shape(0), activePoint(0), firstPoint(0), handleRadius(3), mouseOverFirstPoint(false), pointIsDragged(false), finishAfterThisPoint(false), hoveredPoint(0), listeningToModifiers(false), angleSnapStrategy(0), angleSnappingDelta(15), angleSnapStatus(false), strokeWidget(0) {} KoPathShape *shape; KoPathPoint *activePoint; KoPathPoint *firstPoint; int handleRadius; bool mouseOverFirstPoint; bool pointIsDragged; bool finishAfterThisPoint; PathConnectionPoint existingStartPoint; ///< an existing path point we started a new path at PathConnectionPoint existingEndPoint; ///< an existing path point we finished a new path at KoPathPoint *hoveredPoint; ///< an existing path end point the mouse is hovering on bool listeningToModifiers; // Fine tune when to begin processing modifiers at the beginning of a stroke. AngleSnapStrategy *angleSnapStrategy; int angleSnappingDelta; bool angleSnapStatus; KoStrokeConfigWidget *strokeWidget; void repaintActivePoint() const { const bool isFirstPoint = (activePoint == firstPoint); if (!isFirstPoint && !pointIsDragged) return; QRectF rect = activePoint->boundingRect(false); // make sure that we have the second control point inside our // update rect, as KoPathPoint::boundingRect will not include // the second control point of the last path point if the path // is not closed const QPointF &point = activePoint->point(); const QPointF &controlPoint = activePoint->controlPoint2(); rect = rect.united(QRectF(point, controlPoint).normalized()); // when painting the first point we want the // first control point to be painted as well if (isFirstPoint) { const QPointF &controlPoint = activePoint->controlPoint1(); rect = rect.united(QRectF(point, controlPoint).normalized()); } QPointF border = q->canvas()->viewConverter() ->viewToDocument(QPointF(handleRadius, handleRadius)); rect.adjust(-border.x(), -border.y(), border.x(), border.y()); q->canvas()->updateCanvas(rect); } /// returns the nearest existing path point KoPathPoint* endPointAtPosition( const QPointF &position ) const { QRectF roi = q->handleGrabRect(position); QList shapes = q->canvas()->shapeManager()->shapesAt(roi); KoPathPoint * nearestPoint = 0; qreal minDistance = HUGE_VAL; uint grabSensitivity = q->grabSensitivity(); qreal maxDistance = q->canvas()->viewConverter()->viewToDocumentX(grabSensitivity); foreach(KoShape *s, shapes) { KoPathShape * path = dynamic_cast(s); if (!path) continue; KoParameterShape *paramShape = dynamic_cast(s); if (paramShape && paramShape->isParametricShape()) continue; KoPathPoint * p = 0; uint subpathCount = path->subpathCount(); for (uint i = 0; i < subpathCount; ++i) { if (path->isClosedSubpath(i)) continue; p = path->pointByIndex(KoPathPointIndex(i, 0)); // check start of subpath qreal d = squareDistance(position, path->shapeToDocument(p->point())); if (d < minDistance && d < maxDistance) { nearestPoint = p; minDistance = d; } // check end of subpath p = path->pointByIndex(KoPathPointIndex(i, path->subpathPointCount(i)-1)); d = squareDistance(position, path->shapeToDocument(p->point())); if (d < minDistance && d < maxDistance) { nearestPoint = p; minDistance = d; } } } return nearestPoint; } /// Connects given path with the ones we hit when starting/finishing bool connectPaths( KoPathShape *pathShape, const PathConnectionPoint &pointAtStart, const PathConnectionPoint &pointAtEnd ) const { KoPathShape * startShape = 0; KoPathShape * endShape = 0; KoPathPoint * startPoint = 0; KoPathPoint * endPoint = 0; if (pointAtStart.isValid()) { startShape = pointAtStart.path; startPoint = pointAtStart.point; } if (pointAtEnd.isValid()) { endShape = pointAtEnd.path; endPoint = pointAtEnd.point; } // at least one point must be valid if (!startPoint && !endPoint) return false; // do not allow connecting to the same point twice if (startPoint == endPoint) endPoint = 0; // we have hit an existing path point on start/finish // what we now do is: // 1. combine the new created path with the ones we hit on start/finish // 2. merge the endpoints of the corresponding subpaths uint newPointCount = pathShape->subpathPointCount(0); KoPathPointIndex newStartPointIndex(0, 0); KoPathPointIndex newEndPointIndex(0, newPointCount-1); KoPathPoint * newStartPoint = pathShape->pointByIndex(newStartPointIndex); KoPathPoint * newEndPoint = pathShape->pointByIndex(newEndPointIndex); // combine with the path we hit on start KoPathPointIndex startIndex(-1,-1); if (startShape && startPoint) { startIndex = startShape->pathPointIndex(startPoint); pathShape->combine(startShape); pathShape->moveSubpath(0, pathShape->subpathCount()-1); } // combine with the path we hit on finish KoPathPointIndex endIndex(-1,-1); if (endShape && endPoint) { endIndex = endShape->pathPointIndex(endPoint); if (endShape != startShape) { endIndex.first += pathShape->subpathCount(); pathShape->combine(endShape); } } // do we connect twice to a single subpath ? bool connectToSingleSubpath = (startShape == endShape && startIndex.first == endIndex.first); if (startIndex.second == 0 && !connectToSingleSubpath) { pathShape->reverseSubpath(startIndex.first); startIndex.second = pathShape->subpathPointCount(startIndex.first)-1; } if (endIndex.second > 0 && !connectToSingleSubpath) { pathShape->reverseSubpath(endIndex.first); endIndex.second = 0; } // after combining we have a path where with the subpaths in the following // order: // 1. the subpaths of the pathshape we started the new path at // 2. the subpath we just created // 3. the subpaths of the pathshape we finished the new path at // get the path points we want to merge, as these are not going to // change while merging KoPathPoint * existingStartPoint = pathShape->pointByIndex(startIndex); KoPathPoint * existingEndPoint = pathShape->pointByIndex(endIndex); // merge first two points if (existingStartPoint) { KoPathPointData pd1(pathShape, pathShape->pathPointIndex(existingStartPoint)); KoPathPointData pd2(pathShape, pathShape->pathPointIndex(newStartPoint)); KoPathPointMergeCommand cmd1(pd1, pd2); cmd1.redo(); } // merge last two points if (existingEndPoint) { KoPathPointData pd3(pathShape, pathShape->pathPointIndex(newEndPoint)); KoPathPointData pd4(pathShape, pathShape->pathPointIndex(existingEndPoint)); KoPathPointMergeCommand cmd2(pd3, pd4); cmd2.redo(); } return true; } void addPathShape() { if (!shape) return; if (shape->pointCount() < 2) { cleanUp(); return; } // this is done so that nothing happens when the mouseReleaseEvent for the this event is received KoPathShape *pathShape = shape; shape=0; q->addPathShape(pathShape); cleanUp(); return; } void cleanUp() { // reset snap guide q->canvas()->updateCanvas(q->canvas()->snapGuide()->boundingRect()); q->canvas()->snapGuide()->reset(); angleSnapStrategy = 0; delete shape; shape=0; existingStartPoint = 0; existingEndPoint = 0; hoveredPoint = 0; listeningToModifiers = false; } void angleDeltaChanged(int value) { angleSnappingDelta = value; if (angleSnapStrategy) angleSnapStrategy->setAngleStep(angleSnappingDelta); } void angleSnapChanged(int angleSnap){ angleSnapStatus = ! angleSnapStatus; if(angleSnapStrategy) { if(angleSnap == Qt::Checked) angleSnapStrategy->activate(); else angleSnapStrategy->deactivate(); } } }; #endif // KOCREATEPATHTOOL_P_H diff --git a/libs/flake/KoConnectionShape.cpp b/libs/flake/KoConnectionShape.cpp index 02b72ed5508..86dfe487743 100644 --- a/libs/flake/KoConnectionShape.cpp +++ b/libs/flake/KoConnectionShape.cpp @@ -1,763 +1,764 @@ /* This file is part of the KDE project * Copyright (C) 2007 Boudewijn Rempt * Copyright (C) 2007,2009 Thorsten Zachmann * Copyright (C) 2007,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 "KoConnectionShape.h" #include "KoConnectionShape_p.h" #include "KoViewConverter.h" #include "KoShapeLoadingContext.h" #include "KoShapeSavingContext.h" #include "KoConnectionShapeLoadingUpdater.h" #include "KoPathShapeLoader.h" #include "KoPathPoint.h" #include "KoShapeBackground.h" #include #include #include #include #include +#include #include KoConnectionShapePrivate::KoConnectionShapePrivate(KoConnectionShape *q) : KoParameterShapePrivate(q), shape1(0), shape2(0), connectionPointId1(-1), connectionPointId2(-1), connectionType(KoConnectionShape::Standard), forceUpdate(false), hasCustomPath(false) { } QPointF KoConnectionShapePrivate::escapeDirection(int handleId) const { Q_Q(const KoConnectionShape); QPointF direction; if (handleConnected(handleId)) { KoShape *attachedShape = handleId == KoConnectionShape::StartHandle ? shape1 : shape2; int connectionPointId = handleId == KoConnectionShape::StartHandle ? connectionPointId1 : connectionPointId2; KoConnectionPoint::EscapeDirection ed = attachedShape->connectionPoint(connectionPointId).escapeDirection; if (ed == KoConnectionPoint::AllDirections) { QPointF handlePoint = q->shapeToDocument(handles[handleId]); QPointF centerPoint = attachedShape->absolutePosition(KoFlake::CenteredPosition); /* * Determine the best escape direction from the position of the handle point * and the position and orientation of the attached shape. * The idea is to define 4 sectors, one for each edge of the attached shape. * Each sector starts at the center point of the attached shape and has it * left and right edge going through the two points which define the edge. * Then we check which sector contains our handle point, for which we can * simply calculate the corresponding direction which is orthogonal to the * corresponding bounding box edge. * From that we derive the escape direction from looking at the main coordinate * of the orthogonal direction. */ // define our edge points in the right order const KoFlake::Position corners[4] = { KoFlake::BottomRightCorner, KoFlake::BottomLeftCorner, KoFlake::TopLeftCorner, KoFlake::TopRightCorner }; QPointF vHandle = handlePoint-centerPoint; for (int i = 0; i < 4; ++i) { // first point of bounding box edge QPointF p1 = attachedShape->absolutePosition(corners[i]); // second point of bounding box edge QPointF p2 = attachedShape->absolutePosition(corners[(i+1)%4]); // check on which side of the first sector edge our second sector edge is const qreal c0 = crossProd(p1-centerPoint, p2-centerPoint); // check on which side of the first sector edge our handle point is const qreal c1 = crossProd(p1-centerPoint, vHandle); // second egde and handle point must be on the same side of first edge if ((c0 < 0 && c1 > 0) || (c0 > 0 && c1 < 0)) continue; // check on which side of the handle point our second sector edge is const qreal c2 = crossProd(vHandle, p2-centerPoint); // second edge must be on the same side of the handle point as on first edge if ((c0 < 0 && c2 > 0) || (c0 > 0 && c2 < 0)) continue; // now we found the correct edge QPointF vDir = 0.5 *(p1+p2) - centerPoint; // look at coordinate with the greatest absolute value // and construct our escape direction accordingly const qreal xabs = qAbs(vDir.x()); const qreal yabs = qAbs(vDir.y()); if (xabs > yabs) { direction.rx() = vDir.x() > 0 ? 1.0 : -1.0; direction.ry() = 0.0; } else { direction.rx() = 0.0; direction.ry() = vDir.y() > 0 ? 1.0 : -1.0; } break; } } else if (ed == KoConnectionPoint::HorizontalDirections) { QPointF handlePoint = q->shapeToDocument(handles[handleId]); QPointF centerPoint = attachedShape->absolutePosition(KoFlake::CenteredPosition); // use horizontal direction pointing away from center point if (handlePoint.x() < centerPoint.x()) direction = QPointF(-1.0, 0.0); else direction = QPointF(1.0, 0.0); } else if (ed == KoConnectionPoint::VerticalDirections) { QPointF handlePoint = q->shapeToDocument(handles[handleId]); QPointF centerPoint = attachedShape->absolutePosition(KoFlake::CenteredPosition); // use vertical direction pointing away from center point if (handlePoint.y() < centerPoint.y()) direction = QPointF(0.0, -1.0); else direction = QPointF(0.0, 1.0); } else if (ed == KoConnectionPoint::LeftDirection) { direction = QPointF(-1.0, 0.0); } else if (ed == KoConnectionPoint::RightDirection) { direction = QPointF(1.0, 0.0); } else if (ed == KoConnectionPoint::UpDirection) { direction = QPointF(0.0, -1.0); } else if (ed == KoConnectionPoint::DownDirection) { direction = QPointF(0.0, 1.0); } // transform escape direction by using our own transformation matrix QTransform invMatrix = q->absoluteTransformation(0).inverted(); direction = invMatrix.map(direction) - invMatrix.map(QPointF()); direction /= sqrt(direction.x() * direction.x() + direction.y() * direction.y()); } return direction; } bool KoConnectionShapePrivate::intersects(const QPointF &p1, const QPointF &d1, const QPointF &p2, const QPointF &d2, QPointF &isect) { qreal sp1 = scalarProd(d1, p2 - p1); if (sp1 < 0.0) return false; qreal sp2 = scalarProd(d2, p1 - p2); if (sp2 < 0.0) return false; // use cross product to check if rays intersects at all qreal cp = crossProd(d1, d2); if (cp == 0.0) { // rays are parallel or coincident if (p1.x() == p2.x() && d1.x() == 0.0 && d1.y() != d2.y()) { // vertical, coincident isect = 0.5 * (p1 + p2); } else if (p1.y() == p2.y() && d1.y() == 0.0 && d1.x() != d2.x()) { // horizontal, coincident isect = 0.5 * (p1 + p2); } else { return false; } } else { // they are intersecting normally isect = p1 + sp1 * d1; } return true; } QPointF KoConnectionShapePrivate::perpendicularDirection(const QPointF &p1, const QPointF &d1, const QPointF &p2) { QPointF perpendicular(d1.y(), -d1.x()); qreal sp = scalarProd(perpendicular, p2 - p1); if (sp < 0.0) perpendicular *= -1.0; return perpendicular; } void KoConnectionShapePrivate::normalPath(const qreal MinimumEscapeLength) { // Clear the path to build it again. path.clear(); path.append(handles[KoConnectionShape::StartHandle]); QVector edges1; QVector edges2; QPointF direction1 = escapeDirection(KoConnectionShape::StartHandle); QPointF direction2 = escapeDirection(KoConnectionShape::EndHandle); QPointF edgePoint1 = handles[KoConnectionShape::StartHandle] + MinimumEscapeLength * direction1; QPointF edgePoint2 = handles[KoConnectionShape::EndHandle] + MinimumEscapeLength * direction2; edges1.append(edgePoint1); edges2.prepend(edgePoint2); if (handleConnected(KoConnectionShape::StartHandle) && handleConnected(KoConnectionShape::EndHandle)) { QPointF intersection; bool connected = false; do { // first check if directions from current edge points intersect if (intersects(edgePoint1, direction1, edgePoint2, direction2, intersection)) { // directions intersect, we have another edge point and be done edges1.append(intersection); break; } // check if we are going toward the other handle qreal sp = scalarProd(direction1, edgePoint2 - edgePoint1); if (sp >= 0.0) { // if we are having the same direction, go all the way toward // the other handle, else only go half the way if (direction1 == direction2) edgePoint1 += sp * direction1; else edgePoint1 += 0.5 * sp * direction1; edges1.append(edgePoint1); // switch direction direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2); } else { // we are not going into the same direction, so switch direction direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2); } } while (! connected); } path.append(edges1); path.append(edges2); path.append(handles[KoConnectionShape::EndHandle]); } qreal KoConnectionShapePrivate::scalarProd(const QPointF &v1, const QPointF &v2) const { return v1.x() * v2.x() + v1.y() * v2.y(); } qreal KoConnectionShapePrivate::crossProd(const QPointF &v1, const QPointF &v2) const { return v1.x() * v2.y() - v1.y() * v2.x(); } bool KoConnectionShapePrivate::handleConnected(int handleId) const { if (handleId == KoConnectionShape::StartHandle && shape1 && connectionPointId1 >= 0) return true; if (handleId == KoConnectionShape::EndHandle && shape2 && connectionPointId2 >= 0) return true; return false; } void KoConnectionShape::updateConnections() { Q_D(KoConnectionShape); bool updateHandles = false; if (d->handleConnected(StartHandle)) { if (d->shape1->hasConnectionPoint(d->connectionPointId1)) { // map connection point into our shape coordinates QPointF p = documentToShape(d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position)); if (d->handles[StartHandle] != p) { d->handles[StartHandle] = p; updateHandles = true; } } } if (d->handleConnected(EndHandle)) { if (d->shape2->hasConnectionPoint(d->connectionPointId2)) { // map connection point into our shape coordinates QPointF p = documentToShape(d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position)); if (d->handles[EndHandle] != p) { d->handles[EndHandle] = p; updateHandles = true; } } } if (updateHandles || d->forceUpdate) { update(); // ugly, for repainting the connection we just changed updatePath(QSizeF()); update(); // ugly, for repainting the connection we just changed d->forceUpdate = false; } } KoConnectionShape::KoConnectionShape() : KoParameterShape(*(new KoConnectionShapePrivate(this))) { Q_D(KoConnectionShape); d->handles.push_back(QPointF(0, 0)); d->handles.push_back(QPointF(140, 140)); moveTo(d->handles[StartHandle]); lineTo(d->handles[EndHandle]); updatePath(QSizeF(140, 140)); clearConnectionPoints(); } KoConnectionShape::~KoConnectionShape() { Q_D(KoConnectionShape); if (d->shape1) d->shape1->removeDependee(this); if (d->shape2) d->shape2->removeDependee(this); } void KoConnectionShape::saveOdf(KoShapeSavingContext & context) const { Q_D(const KoConnectionShape); context.xmlWriter().startElement("draw:connector"); saveOdfAttributes(context, OdfMandatories | OdfAdditionalAttributes); switch (d->connectionType) { case Lines: context.xmlWriter().addAttribute("draw:type", "lines"); break; case Straight: context.xmlWriter().addAttribute("draw:type", "line"); break; case Curve: context.xmlWriter().addAttribute("draw:type", "curve"); break; default: context.xmlWriter().addAttribute("draw:type", "standard"); break; } if (d->shape1) { context.xmlWriter().addAttribute("draw:start-shape", context.xmlid(d->shape1, "shape", KoElementReference::Counter).toString()); context.xmlWriter().addAttribute("draw:start-glue-point", d->connectionPointId1); } else { QPointF p(shapeToDocument(d->handles[StartHandle]) * context.shapeOffset(this)); context.xmlWriter().addAttributePt("svg:x1", p.x()); context.xmlWriter().addAttributePt("svg:y1", p.y()); } if (d->shape2) { context.xmlWriter().addAttribute("draw:end-shape", context.xmlid(d->shape2, "shape", KoElementReference::Counter).toString()); context.xmlWriter().addAttribute("draw:end-glue-point", d->connectionPointId2); } else { QPointF p(shapeToDocument(d->handles[EndHandle]) * context.shapeOffset(this)); context.xmlWriter().addAttributePt("svg:x2", p.x()); context.xmlWriter().addAttributePt("svg:y2", p.y()); } // write the path data context.xmlWriter().addAttribute("svg:d", toString()); saveOdfAttributes(context, OdfViewbox); saveOdfCommonChildElements(context); saveText(context); context.xmlWriter().endElement(); } bool KoConnectionShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { Q_D(KoConnectionShape); loadOdfAttributes(element, context, OdfMandatories | OdfCommonChildElements | OdfAdditionalAttributes); QString type = element.attributeNS(KoXmlNS::draw, "type", "standard"); if (type == "lines") d->connectionType = Lines; else if (type == "line") d->connectionType = Straight; else if (type == "curve") d->connectionType = Curve; else d->connectionType = Standard; // reset connection point indices d->connectionPointId1 = -1; d->connectionPointId2 = -1; // reset connected shapes d->shape1 = 0; d->shape2 = 0; if (element.hasAttributeNS(KoXmlNS::draw, "start-shape")) { d->connectionPointId1 = element.attributeNS(KoXmlNS::draw, "start-glue-point", QString()).toInt(); QString shapeId1 = element.attributeNS(KoXmlNS::draw, "start-shape", QString()); debugFlake << "references start-shape" << shapeId1 << "at glue-point" << d->connectionPointId1; d->shape1 = context.shapeById(shapeId1); if (d->shape1) { debugFlake << "start-shape was already loaded"; d->shape1->addDependee(this); if (d->shape1->hasConnectionPoint(d->connectionPointId1)) { debugFlake << "connecting to start-shape"; d->handles[StartHandle] = d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position); debugFlake << "start handle position =" << d->handles[StartHandle]; } } else { debugFlake << "start-shape not loaded yet, deferring connection"; context.updateShape(shapeId1, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::First)); } } else { d->handles[StartHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", QString()))); d->handles[StartHandle].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", QString()))); } if (element.hasAttributeNS(KoXmlNS::draw, "end-shape")) { d->connectionPointId2 = element.attributeNS(KoXmlNS::draw, "end-glue-point", "").toInt(); QString shapeId2 = element.attributeNS(KoXmlNS::draw, "end-shape", ""); debugFlake << "references end-shape " << shapeId2 << "at glue-point" << d->connectionPointId2; d->shape2 = context.shapeById(shapeId2); if (d->shape2) { debugFlake << "end-shape was already loaded"; d->shape2->addDependee(this); if (d->shape2->hasConnectionPoint(d->connectionPointId2)) { debugFlake << "connecting to end-shape"; d->handles[EndHandle] = d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position); debugFlake << "end handle position =" << d->handles[EndHandle]; } } else { debugFlake << "end-shape not loaded yet, deferring connection"; context.updateShape(shapeId2, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::Second)); } } else { d->handles[EndHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", QString()))); d->handles[EndHandle].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", QString()))); } QString skew = element.attributeNS(KoXmlNS::draw, "line-skew", QString()); QStringList skewValues = skew.simplified().split(' ', QString::SkipEmptyParts); // TODO apply skew values once we support them // load the path data if there is any d->hasCustomPath = element.hasAttributeNS(KoXmlNS::svg, "d"); if (d->hasCustomPath) { KoPathShapeLoader loader(this); loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); if (m_subpaths.size() > 0) { QRectF viewBox = loadOdfViewbox(element); if (viewBox.isEmpty()) { // there should be a viewBox to transform the path data // if there is none, use the bounding rectangle of the parsed path viewBox = outline().boundingRect(); } // convert path to viewbox coordinates to have a bounding rect of (0,0 1x1) // which can later be fitted back into the target rect once we have all // the required information QTransform viewMatrix; viewMatrix.scale(viewBox.width() ? static_cast(1.0) / viewBox.width() : 1.0, viewBox.height() ? static_cast(1.0) / viewBox.height() : 1.0); viewMatrix.translate(-viewBox.left(), -viewBox.top()); d->map(viewMatrix); // trigger finishing the connections in case we have all data // otherwise it gets called again once the shapes we are // connected to are loaded } else { d->hasCustomPath = false; } finishLoadingConnection(); } else { d->forceUpdate = true; updateConnections(); } loadText(element, context); return true; } void KoConnectionShape::finishLoadingConnection() { Q_D(KoConnectionShape); if (d->hasCustomPath) { const bool loadingFinished1 = d->connectionPointId1 >= 0 ? d->shape1 != 0 : true; const bool loadingFinished2 = d->connectionPointId2 >= 0 ? d->shape2 != 0 : true; if (loadingFinished1 && loadingFinished2) { QPointF p1, p2; if (d->handleConnected(StartHandle)) { if (d->shape1->hasConnectionPoint(d->connectionPointId1)) { p1 = d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position); } } else { p1 = d->handles[StartHandle]; } if (d->handleConnected(EndHandle)) { if (d->shape2->hasConnectionPoint(d->connectionPointId2)) { p2 = d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position); } } else { p2 = d->handles[EndHandle]; } QPointF relativeBegin = m_subpaths.first()->first()->point(); QPointF relativeEnd = m_subpaths.last()->last()->point(); QPointF diffRelative(relativeBegin - relativeEnd); QPointF diffAbsolute(p1 - p2); qreal factorX = diffRelative.x() ? diffAbsolute.x() / diffRelative.x(): 1.0; qreal factorY = diffRelative.y() ? diffAbsolute.y() / diffRelative.y(): 1.0; p1.setX(p1.x() - relativeBegin.x() * factorX); p1.setY(p1.y() - relativeBegin.y() * factorY); p2.setX(p2.x() + (1 - relativeEnd.x()) * factorX); p2.setY(p2.y() + (1 - relativeEnd.y()) * factorY); QRectF targetRect = QRectF(p1, p2).normalized(); // transform the normalized coordinates back to our target rectangle QTransform viewMatrix; viewMatrix.translate(targetRect.x(), targetRect.y()); viewMatrix.scale(targetRect.width(), targetRect.height()); d->map(viewMatrix); // pretend we are during a forced update, so normalize() // will not trigger an updateConnections() call d->forceUpdate = true; normalize(); d->forceUpdate = false; } } else { updateConnections(); } } void KoConnectionShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); Q_D(KoConnectionShape); if (handleId >= d->handles.size()) return; d->handles[handleId] = point; } void KoConnectionShape::updatePath(const QSizeF &size) { Q_UNUSED(size); Q_D(KoConnectionShape); clear(); // Do not create a path when all handles point to the same point. bool equal = true; const QPointF first = d->handles.value(0); for (int i = 1; equal && i < d->handles.count(); ++i) { equal = d->handles[i] == first; } if (equal) { return; } const qreal MinimumEscapeLength = (qreal)20.; switch (d->connectionType) { case Standard: { d->normalPath(MinimumEscapeLength); if (d->path.count() != 0){ moveTo(d->path[0]); for (int index = 1; index < d->path.count(); ++index) lineTo(d->path[index]); } break; } case Lines: { QPointF direction1 = d->escapeDirection(0); QPointF direction2 = d->escapeDirection(d->handles.count() - 1); moveTo(d->handles[StartHandle]); if (! direction1.isNull()) lineTo(d->handles[StartHandle] + MinimumEscapeLength * direction1); if (! direction2.isNull()) lineTo(d->handles[EndHandle] + MinimumEscapeLength * direction2); lineTo(d->handles[EndHandle]); break; } case Straight: moveTo(d->handles[StartHandle]); lineTo(d->handles[EndHandle]); break; case Curve: // TODO QPointF direction1 = d->escapeDirection(0); QPointF direction2 = d->escapeDirection(d->handles.count() - 1); moveTo(d->handles[StartHandle]); if (! direction1.isNull() && ! direction2.isNull()) { QPointF curvePoint1 = d->handles[StartHandle] + 5.0 * MinimumEscapeLength * direction1; QPointF curvePoint2 = d->handles[EndHandle] + 5.0 * MinimumEscapeLength * direction2; curveTo(curvePoint1, curvePoint2, d->handles[EndHandle]); } else { lineTo(d->handles[EndHandle]); } break; } normalize(); } bool KoConnectionShape::connectFirst(KoShape * shape1, int connectionPointId) { Q_D(KoConnectionShape); // refuse to connect to a shape that depends on us (e.g. a artistic text shape) if (hasDependee(shape1)) return false; if (shape1) { // check if the connection point does exist if (!shape1->hasConnectionPoint(connectionPointId)) return false; // do not connect to the same connection point twice if (d->shape2 == shape1 && d->connectionPointId2 == connectionPointId) return false; } if (d->shape1) d->shape1->removeDependee(this); d->shape1 = shape1; if (d->shape1) d->shape1->addDependee(this); d->connectionPointId1 = connectionPointId; return true; } bool KoConnectionShape::connectSecond(KoShape * shape2, int connectionPointId) { Q_D(KoConnectionShape); // refuse to connect to a shape that depends on us (e.g. a artistic text shape) if (hasDependee(shape2)) return false; if (shape2) { // check if the connection point does exist if (!shape2->hasConnectionPoint(connectionPointId)) return false; // do not connect to the same connection point twice if (d->shape1 == shape2 && d->connectionPointId1 == connectionPointId) return false; } if (d->shape2) d->shape2->removeDependee(this); d->shape2 = shape2; if (d->shape2) d->shape2->addDependee(this); d->connectionPointId2 = connectionPointId; return true; } KoShape *KoConnectionShape::firstShape() const { Q_D(const KoConnectionShape); return d->shape1; } int KoConnectionShape::firstConnectionId() const { Q_D(const KoConnectionShape); return d->connectionPointId1; } KoShape *KoConnectionShape::secondShape() const { Q_D(const KoConnectionShape); return d->shape2; } int KoConnectionShape::secondConnectionId() const { Q_D(const KoConnectionShape); return d->connectionPointId2; } KoConnectionShape::Type KoConnectionShape::type() const { Q_D(const KoConnectionShape); return d->connectionType; } void KoConnectionShape::setType(Type connectionType) { Q_D(KoConnectionShape); d->connectionType = connectionType; updatePath(size()); } void KoConnectionShape::shapeChanged(ChangeType type, KoShape *shape) { Q_D(KoConnectionShape); KoTosContainer::shapeChanged(type, shape); // check if we are during a forced update const bool updateIsActive = d->forceUpdate; switch (type) { case PositionChanged: case RotationChanged: case ShearChanged: case ScaleChanged: case GenericMatrixChange: case ParameterChanged: if (isParametricShape() && shape == 0) d->forceUpdate = true; break; case Deleted: if (shape != d->shape1 && shape != d->shape2) return; if (shape == d->shape1) connectFirst(0, -1); if (shape == d->shape2) connectSecond(0, -1); break; case ConnectionPointChanged: if (shape == d->shape1 && !shape->hasConnectionPoint(d->connectionPointId1)) { connectFirst(0, -1); } else if ( shape == d->shape2 && !shape->hasConnectionPoint(d->connectionPointId2)){ connectSecond(0, -1); } else { d->forceUpdate = true; } break; case BackgroundChanged: { // connection shape should not have a background QSharedPointer fill = background(); if (fill) { setBackground(QSharedPointer(0)); } return; } default: return; } // the connection was moved while it is connected to some other shapes const bool connectionChanged = !shape && (d->shape1 || d->shape2); // one of the connected shape has moved const bool connectedShapeChanged = shape && (shape == d->shape1 || shape == d->shape2); if (!updateIsActive && (connectionChanged || connectedShapeChanged) && isParametricShape()) updateConnections(); // reset the forced update flag d->forceUpdate = false; } QString KoConnectionShape::pathShapeId() const { return KOCONNECTIONSHAPEID; } diff --git a/libs/flake/KoHatchBackground.cpp b/libs/flake/KoHatchBackground.cpp index 35f1bb46cda..19e16aaf4e3 100644 --- a/libs/flake/KoHatchBackground.cpp +++ b/libs/flake/KoHatchBackground.cpp @@ -1,233 +1,234 @@ /* This file is part of the KDE project * * Copyright (C) 2012 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoHatchBackground.h" #include "KoColorBackground_p.h" #include #include #include #include #include #include #include #include #include #include #include +#include class KoHatchBackgroundPrivate : public KoColorBackgroundPrivate { public: KoHatchBackgroundPrivate() : angle(0.0) , distance(1.0) , style(KoHatchBackground::Single) {} QColor lineColor; int angle; qreal distance; KoHatchBackground::HatchStyle style; QString name; }; KoHatchBackground::KoHatchBackground() : KoColorBackground(*(new KoHatchBackgroundPrivate())) { } void KoHatchBackground::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const { Q_D(const KoHatchBackground); if (d->color.isValid()) { // paint background color if set by using the color background KoColorBackground::paint(painter, converter, context, fillPath); } const QRectF targetRect = fillPath.boundingRect(); painter.save(); painter.setClipPath(fillPath); QPen pen(d->lineColor); // we set the pen width to 0.5 pt for the hatch. This is not defined in the spec. pen.setWidthF(0.5); painter.setPen(pen); QVector lines; // The different styles are handled by painting the lines multiple times with a different // angel offset as basically it just means we paint the lines also at a different angle. // This are the angle offsets we need to apply to the different lines of a style. // -90 is for single, 0 for the 2nd line in double and -45 for the 3th line in triple. const int angleOffset[] = {-90, 0, -45 }; // The number of loops is defined by the style. int loops = (d->style == Single) ? 1 : (d->style == Double) ? 2 : 3; for (int i = 0; i < loops; ++i) { int angle = d->angle - angleOffset[i]; qreal cosAngle = ::cos(angle/180.0*M_PI); // if cos is nearly 0 the lines are horizontal. Use a special case for that if (qAbs(cosAngle) > 0.00001) { qreal xDiff = tan(angle/180.0*M_PI) * targetRect.height(); // calculate the distance we need to increase x when creating the lines so that the // distance between the lines is also correct for rotated lines. qreal xOffset = qAbs(d->distance / cosAngle); // if the lines go to the right we need to start more to the left. Get the correct start. qreal xStart = 0; while (-xDiff < xStart) { xStart -= xOffset; } // if the lines go to the left we need to stop more at the right. Get the correct end offset qreal xEndOffset = 0; if (xDiff < 0) { while (xDiff < -xEndOffset) { xEndOffset += xOffset; } } // create line objects. lines.reserve(lines.size() + int((targetRect.width() + xEndOffset - xStart) / xOffset) + 1); for (qreal x = xStart; x < targetRect.width() + xEndOffset; x += xOffset) { lines.append(QLineF(x, 0, x + xDiff, targetRect.height())); } } else { // horizontal lines lines.reserve(lines.size() + int(targetRect.height()/d->distance) + 1); for (qreal y = 0; y < targetRect.height(); y += d->distance) { lines.append(QLineF(0, y, targetRect.width(), y)); } } } painter.drawLines(lines); painter.restore(); } void KoHatchBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) { Q_D(KoHatchBackground); KoGenStyle::Type type = style.type(); KoGenStyle::PropertyType propertyType = (type == KoGenStyle::GraphicStyle || type == KoGenStyle::GraphicAutoStyle || type == KoGenStyle::DrawingPageStyle || type == KoGenStyle::DrawingPageAutoStyle ) ? KoGenStyle::DefaultType : KoGenStyle::GraphicType; style.addProperty("draw:fill", "hatch", propertyType); style.addProperty("draw:fill-hatch-name", saveHatchStyle(context), propertyType); bool fillHatchSolid = d->color.isValid(); style.addProperty("draw:fill-hatch-solid", fillHatchSolid, propertyType); if (fillHatchSolid) { style.addProperty("draw:fill-color", d->color.name(), propertyType); } } QString KoHatchBackground::saveHatchStyle(KoShapeSavingContext &context) const { Q_D(const KoHatchBackground); KoGenStyle hatchStyle(KoGenStyle::HatchStyle /*no family name*/); hatchStyle.addAttribute("draw:display-name", d->name); hatchStyle.addAttribute("draw:color", d->lineColor.name()); hatchStyle.addAttributePt("draw:distance", d->distance); hatchStyle.addAttribute("draw:rotation", QString("%1").arg(d->angle * 10)); switch (d->style) { case Single: hatchStyle.addAttribute("draw:style", "single"); break; case Double: hatchStyle.addAttribute("draw:style", "double"); break; case Triple: hatchStyle.addAttribute("draw:style", "triple"); break; } return context.mainStyles().insert(hatchStyle, "hatch"); } bool KoHatchBackground::loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) { // Q_D(KoHatchBackground); Q_UNUSED(shapeSize); KoStyleStack &styleStack = context.styleStack(); QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle == "hatch") { QString style = styleStack.property(KoXmlNS::draw, "fill-hatch-name"); debugFlake << " hatch style is :" << style; KoXmlElement* draw = context.stylesReader().drawStyles("hatch").value(style); if (draw) { debugFlake << "Hatch style found for:" << style; QString angle = draw->attributeNS(KoXmlNS::draw, "rotation", QString("0")); if (angle.at(angle.size()-1).isLetter()) { d->angle = KoUnit::parseAngle(angle); } else { // OO saves the angle value without unit and multiplied by a factor of 10 d->angle = int(angle.toInt() / 10); } debugFlake << "angle :" << d->angle; d->name = draw->attributeNS(KoXmlNS::draw, "display-name"); // use 2mm as default, just in case it is not given in a document so we show something sensible. d->distance = KoUnit::parseValue(draw->attributeNS(KoXmlNS::draw, "distance", "2mm")); bool fillHatchSolid = styleStack.property(KoXmlNS::draw, "fill-hatch-solid") == QLatin1String("true"); if (fillHatchSolid) { QString fillColor = styleStack.property(KoXmlNS::draw, "fill-color"); if (!fillColor.isEmpty()) { d->color.setNamedColor(fillColor); } else { d->color =QColor(); } } else { d->color = QColor(); } d->lineColor.setNamedColor(draw->attributeNS(KoXmlNS::draw, "color", QString("#000000"))); QString style = draw->attributeNS(KoXmlNS::draw, "style", QString()); if (style == "double") { d->style = Double; } else if (style == "triple") { d->style = Triple; } else { d->style = Single; } } return true; } return false; } diff --git a/libs/flake/KoOdfGradientBackground.cpp b/libs/flake/KoOdfGradientBackground.cpp index 837d644204e..bdc7d0b02f8 100644 --- a/libs/flake/KoOdfGradientBackground.cpp +++ b/libs/flake/KoOdfGradientBackground.cpp @@ -1,372 +1,373 @@ /* This file is part of the KDE project * * Copyright (C) 2011 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoOdfGradientBackground.h" #include "KoShapeBackground_p.h" #include "KoShapeSavingContext.h" #include #include #include #include #include #include #include +#include #include #include #include class KoOdfGradientBackgroundPrivate : public KoShapeBackgroundPrivate { public: KoOdfGradientBackgroundPrivate() : style(), cx(0), cy(0), startColor(), endColor(), angle(0), border(0), opacity(1.0) {}; ~KoOdfGradientBackgroundPrivate() override{}; //data QString style; int cx; int cy; QColor startColor; QColor endColor; qreal angle; qreal border; qreal opacity; mutable QImage buffer; }; KoOdfGradientBackground::KoOdfGradientBackground() : KoShapeBackground(*(new KoOdfGradientBackgroundPrivate())) { } KoOdfGradientBackground::~KoOdfGradientBackground() { } bool KoOdfGradientBackground::loadOdf(const KoXmlElement& e) { Q_D(KoOdfGradientBackground); d->style = e.attributeNS(KoXmlNS::draw, "style", QString()); //TODO: support ellipsoid here too if ((d->style != "rectangular") && (d->style != "square")) { return false; } d->cx = KoUnit::parseValue(e.attributeNS(KoXmlNS::draw, "cx", QString()).remove('%')); d->cy = KoUnit::parseValue(e.attributeNS(KoXmlNS::draw, "cy", QString()).remove('%')); d->border = qBound(0.0,0.01 * e.attributeNS(KoXmlNS::draw, "border", "0").remove('%').toDouble(),1.0); d->startColor = QColor(e.attributeNS(KoXmlNS::draw, "start-color", QString())); d->startColor.setAlphaF((0.01 * e.attributeNS(KoXmlNS::draw, "start-intensity", "100").remove('%').toDouble())); d->endColor = QColor(e.attributeNS(KoXmlNS::draw, "end-color", QString())); d->endColor.setAlphaF(0.01 * e.attributeNS(KoXmlNS::draw, "end-intensity", "100").remove('%').toDouble()); d->angle = e.attributeNS(KoXmlNS::draw, "angle", "0").toDouble() / 10; return true; } void KoOdfGradientBackground::saveOdf(KoGenStyle& styleFill, KoGenStyles& mainStyles) const { Q_D(const KoOdfGradientBackground); KoGenStyle::Type type = styleFill.type(); KoGenStyle::PropertyType propertyType = (type == KoGenStyle::GraphicStyle || type == KoGenStyle::GraphicAutoStyle || type == KoGenStyle::DrawingPageStyle || type == KoGenStyle::DrawingPageAutoStyle ) ? KoGenStyle::DefaultType : KoGenStyle::GraphicType; KoGenStyle gradientStyle(KoGenStyle::GradientStyle); gradientStyle.addAttribute("draw:style", d->style); // draw:style="square" gradientStyle.addAttribute("draw:cx", QString("%1%").arg(d->cx)); gradientStyle.addAttribute("draw:cy", QString("%1%").arg(d->cy)); gradientStyle.addAttribute("draw:start-color", d->startColor.name()); gradientStyle.addAttribute("draw:end-color", d->endColor.name()); gradientStyle.addAttribute("draw:start-intensity", QString("%1%").arg(qRound(d->startColor.alphaF() * 100)) ); gradientStyle.addAttribute("draw:end-intensity", QString("%1%").arg(qRound(d->endColor.alphaF() * 100)) ); gradientStyle.addAttribute("draw:angle", QString("%1").arg(d->angle * 10)); gradientStyle.addAttribute("draw:border", QString("%1%").arg(qRound(d->border * 100.0))); QString gradientStyleName = mainStyles.insert(gradientStyle, "gradient"); styleFill.addProperty("draw:fill", "gradient", propertyType); styleFill.addProperty("draw:fill-gradient-name", gradientStyleName, propertyType); if (d->opacity <= 1.0) { styleFill.addProperty("draw:opacity", QString("%1%").arg(d->opacity * 100.0), propertyType); } } void KoOdfGradientBackground::paint(QPainter& painter, const KoViewConverter &/*converter*/, KoShapePaintingContext &/*context*/, const QPainterPath& fillPath) const { Q_D(const KoOdfGradientBackground); QRectF targetRect = fillPath.boundingRect(); QRectF pixels = painter.transform().mapRect(QRectF(0,0,targetRect.width(), targetRect.height())); QSize currentSize( qCeil(pixels.size().width()), qCeil(pixels.size().height()) ); if (d->buffer.isNull() || d->buffer.size() != currentSize){ d->buffer = QImage(currentSize, QImage::Format_ARGB32_Premultiplied); if (d->style == "square") { renderSquareGradient(d->buffer); } else { renderRectangleGradient(d->buffer); } } painter.setClipPath(fillPath); painter.setOpacity(d->opacity); painter.drawImage(targetRect, d->buffer, QRectF(QPointF(0,0), d->buffer.size())); } void KoOdfGradientBackground::fillStyle(KoGenStyle& style, KoShapeSavingContext& context) { saveOdf(style, context.mainStyles()); } bool KoOdfGradientBackground::loadStyle(KoOdfLoadingContext& context, const QSizeF& shapeSize) { Q_UNUSED(shapeSize); Q_D(KoOdfGradientBackground); KoStyleStack &styleStack = context.styleStack(); if (!styleStack.hasProperty(KoXmlNS::draw, "fill")) { return false; } QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle == "gradient") { if (styleStack.hasProperty(KoXmlNS::draw, "opacity")) { QString opacity = styleStack.property(KoXmlNS::draw, "opacity"); if (! opacity.isEmpty() && opacity.right(1) == "%") { d->opacity = qMin(opacity.leftRef(opacity.length() - 1).toDouble(), 100.0) / 100; } } QString styleName = styleStack.property(KoXmlNS::draw, "fill-gradient-name"); auto gradient = context.stylesReader().drawStyles("gradient"); auto it = gradient.constFind(styleName); if (it != gradient.constEnd() && it.value()) { return loadOdf(*it.value()); } } return false; } void KoOdfGradientBackground::renderSquareGradient(QImage& buffer) const { Q_D(const KoOdfGradientBackground); buffer.fill(d->startColor.rgba()); QPainter painter(&buffer); painter.setPen(Qt::NoPen); painter.setRenderHint(QPainter::Antialiasing, false); int width = buffer.width(); int height = buffer.height(); qreal gradientCenterX = qRound(width * d->cx * 0.01); qreal gradientCenterY = qRound(height * d->cy * 0.01); qreal centerX = width * 0.5; qreal centerY = height * 0.5; qreal areaCenterX = qRound(centerX); qreal areaCenterY = qRound(centerY); QTransform m; m.translate(gradientCenterX, gradientCenterY); m.rotate(-d->angle); m.scale(1.0 - d->border, 1.0 - d->border); m.translate(-gradientCenterX, -gradientCenterY); m.translate(gradientCenterX - areaCenterX,gradientCenterY - areaCenterY); painter.setTransform(m); QLinearGradient linearGradient; linearGradient.setColorAt(1, d->startColor); linearGradient.setColorAt(0, d->endColor); // from center going North linearGradient.setStart(centerX, centerY); linearGradient.setFinalStop(centerX, 0); painter.setBrush(linearGradient); painter.drawRect(0, 0, width, centerY); // from center going South linearGradient.setFinalStop(centerX, height); painter.setBrush(linearGradient); painter.drawRect(0, centerY, width, centerY); // clip the East and West portion QPainterPath clip; clip.moveTo(width, 0); clip.lineTo(width, height); clip.lineTo(0, 0); clip.lineTo(0, height); clip.closeSubpath(); painter.setClipPath(clip); // from center going East linearGradient.setFinalStop(width, centerY); painter.setBrush(linearGradient); painter.drawRect(centerX, 0, width, height); // from center going West linearGradient.setFinalStop( 0, centerY); painter.setBrush(linearGradient); painter.drawRect(0, 0, centerX, height); } void KoOdfGradientBackground::renderRectangleGradient(QImage& buffer) const { Q_D(const KoOdfGradientBackground); buffer.fill(d->startColor.rgba()); QPainter painter(&buffer); painter.setPen(Qt::NoPen); painter.setRenderHint(QPainter::Antialiasing, false); int width = buffer.width(); int height = buffer.height(); qreal gradientCenterX = qRound(width * d->cx * 0.01); qreal gradientCenterY = qRound(height * d->cy * 0.01); qreal centerX = width * 0.5; qreal centerY = height * 0.5; qreal areaCenterY = qRound(centerY); qreal areaCenterX = qRound(centerX); QTransform m; m.translate(gradientCenterX, gradientCenterY); // m.rotate(-d->angle); // OOo rotates the gradient differently m.scale(1.0 - d->border, 1.0 - d->border); m.translate(-gradientCenterX, -gradientCenterY); m.translate(gradientCenterX - areaCenterX,gradientCenterY - areaCenterY); painter.setTransform(m); QLinearGradient linearGradient; linearGradient.setColorAt(1, d->startColor); linearGradient.setColorAt(0, d->endColor); // render background QPainterPath clipPath; if (width < height) { QRectF west(0,0,centerX, height); QRectF east(centerX, 0, centerX, height); linearGradient.setStart(centerX, centerY); linearGradient.setFinalStop(0, centerY); painter.setBrush(linearGradient); painter.drawRect(west); linearGradient.setFinalStop(width, centerY); painter.setBrush(linearGradient); painter.drawRect(east); QRectF north(0,0,width, centerX); QRectF south(0,height - centerX, width, centerX); clipPath.moveTo(0,0); clipPath.lineTo(width, 0); clipPath.lineTo(centerX, centerX); clipPath.closeSubpath(); clipPath.moveTo(width, height); clipPath.lineTo(0, height); clipPath.lineTo(centerX, south.y()); clipPath.closeSubpath(); linearGradient.setStart(centerX, centerX); linearGradient.setFinalStop(centerX, 0); painter.setClipPath(clipPath); painter.setBrush(linearGradient); painter.drawRect(north); linearGradient.setStart(centerX, south.y()); linearGradient.setFinalStop(centerX, height); painter.setBrush(linearGradient); painter.drawRect(south); } else { QRectF north(0,0,width, centerY); QRectF south(0, centerY, width, centerY); linearGradient.setStart(centerX, centerY); linearGradient.setFinalStop(centerX, 0); painter.setBrush(linearGradient); painter.drawRect(north); linearGradient.setFinalStop(centerX, height); painter.setBrush(linearGradient); painter.drawRect(south); QRectF west(0,0,centerY, height); QRectF east(width - centerY, 0, centerY, height); clipPath.moveTo(0,0); clipPath.lineTo(centerY, centerY); clipPath.lineTo(0,height); clipPath.closeSubpath(); clipPath.moveTo(width, height); clipPath.lineTo(east.x(), centerY); clipPath.lineTo(width,0); clipPath.closeSubpath(); linearGradient.setStart(centerY, centerY); linearGradient.setFinalStop(0, centerY); painter.setClipPath(clipPath); painter.setBrush(linearGradient); painter.drawRect(west); linearGradient.setStart(east.x(), centerY); linearGradient.setFinalStop(width, centerY); painter.setBrush(linearGradient); painter.drawRect(east); } } void KoOdfGradientBackground::debug() const { Q_D(const KoOdfGradientBackground); qDebug() << "cx,cy: "<< d->cx << d->cy; qDebug() << "style" << d->style; qDebug() << "colors" << d->startColor << d->endColor; qDebug() << "angle:" << d->angle; qDebug() << "border" << d->border; } diff --git a/libs/flake/KoPathShape.cpp b/libs/flake/KoPathShape.cpp index 5a8d0aec501..7c4b3ae8042 100644 --- a/libs/flake/KoPathShape.cpp +++ b/libs/flake/KoPathShape.cpp @@ -1,1740 +1,1741 @@ /* This file is part of the KDE project Copyright (C) 2006-2008, 2010-2011 Thorsten Zachmann Copyright (C) 2006-2011 Jan Hambrecht Copyright (C) 2007-2009 Thomas Zander Copyright (C) 2011 Jean-Nicolas Artaud This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPathShape.h" #include "KoPathShape_p.h" #include "KoPathSegment.h" #include "KoOdfWorkaround.h" #include "KoPathPoint.h" #include "KoShapeStrokeModel.h" #include "KoViewConverter.h" #include "KoPathShapeLoader.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoShapeShadow.h" #include "KoShapeBackground.h" #include "KoShapeContainer.h" #include "KoFilterEffectStack.h" #include "KoMarker.h" #include "KoMarkerSharedLoadingData.h" #include "KoShapeStroke.h" #include "KoInsets.h" #include #include #include #include #include #include #include #include #include +#include #include // for qIsNaN static bool qIsNaNPoint(const QPointF &p) { return qIsNaN(p.x()) || qIsNaN(p.y()); } static const qreal DefaultMarkerWidth = 3.0; KoPathShapePrivate::KoPathShapePrivate(KoPathShape *q) : KoTosContainerPrivate(q), fillRule(Qt::OddEvenFill), startMarker(KoMarkerData::MarkerStart), endMarker(KoMarkerData::MarkerEnd) { } QRectF KoPathShapePrivate::handleRect(const QPointF &p, qreal radius) const { return QRectF(p.x() - radius, p.y() - radius, 2*radius, 2*radius); } void KoPathShapePrivate::applyViewboxTransformation(const KoXmlElement &element) { // apply viewbox transformation const QRect viewBox = KoPathShape::loadOdfViewbox(element); if (! viewBox.isEmpty()) { // load the desired size QSizeF size; size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); // load the desired position QPointF pos; pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); // create matrix to transform original path data into desired size and position QTransform viewMatrix; viewMatrix.translate(-viewBox.left(), -viewBox.top()); viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height()); viewMatrix.translate(pos.x(), pos.y()); // transform the path data map(viewMatrix); } } KoPathShape::KoPathShape() :KoTosContainer(*(new KoPathShapePrivate(this))) { } KoPathShape::KoPathShape(KoPathShapePrivate &dd) : KoTosContainer(dd) { } KoPathShape::~KoPathShape() { clear(); } void KoPathShape::saveContourOdf(KoShapeSavingContext &context, const QSizeF &scaleFactor) const { Q_D(const KoPathShape); if (m_subpaths.length() <= 1) { QTransform matrix; matrix.scale(scaleFactor.width(), scaleFactor.height()); QString points; KoSubpath *subPath = m_subpaths.first(); KoSubpath::const_iterator pointIt(subPath->constBegin()); KoPathPoint *currPoint= 0; // iterate over all points for (; pointIt != subPath->constEnd(); ++pointIt) { currPoint = *pointIt; if (currPoint->activeControlPoint1() || currPoint->activeControlPoint2()) { break; } const QPointF p = matrix.map(currPoint->point()); points += QString("%1,%2 ").arg(qRound(1000*p.x())).arg(qRound(1000*p.y())); } if (currPoint && !(currPoint->activeControlPoint1() || currPoint->activeControlPoint2())) { context.xmlWriter().startElement("draw:contour-polygon"); context.xmlWriter().addAttributePt("svg:width", size().width()); context.xmlWriter().addAttributePt("svg:height", size().height()); const QSizeF s(size()); QString viewBox = QString("0 0 %1 %2").arg(qRound(1000*s.width())).arg(qRound(1000*s.height())); context.xmlWriter().addAttribute("svg:viewBox", viewBox); context.xmlWriter().addAttribute("draw:points", points); context.xmlWriter().addAttribute("draw:recreate-on-edit", "true"); context.xmlWriter().endElement(); return; } } // if we get here we couldn't save as polygon - let-s try contour-path context.xmlWriter().startElement("draw:contour-path"); saveOdfAttributes(context, OdfViewbox); context.xmlWriter().addAttribute("svg:d", toString()); context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes()); context.xmlWriter().addAttribute("draw:recreate-on-edit", "true"); context.xmlWriter().endElement(); } void KoPathShape::saveOdf(KoShapeSavingContext & context) const { Q_D(const KoPathShape); context.xmlWriter().startElement("draw:path"); saveOdfAttributes(context, OdfAllAttributes | OdfViewbox); context.xmlWriter().addAttribute("svg:d", toString()); context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes()); saveOdfCommonChildElements(context); saveText(context); context.xmlWriter().endElement(); } bool KoPathShape::loadContourOdf(const KoXmlElement &element, KoShapeLoadingContext &, const QSizeF &scaleFactor) { Q_D(KoPathShape); // first clear the path data from the default path clear(); if (element.localName() == "contour-polygon") { QString points = element.attributeNS(KoXmlNS::draw, "points").simplified(); points.replace(',', ' '); points.remove('\r'); points.remove('\n'); bool firstPoint = true; const QStringList coordinateList = points.split(' '); for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) { QPointF point; point.setX((*it).toDouble()); ++it; point.setY((*it).toDouble()); if (firstPoint) { moveTo(point); firstPoint = false; } else lineTo(point); } close(); } else if (element.localName() == "contour-path") { KoPathShapeLoader loader(this); loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); d->loadNodeTypes(element); } // apply viewbox transformation const QRect viewBox = KoPathShape::loadOdfViewbox(element); if (! viewBox.isEmpty()) { QSizeF size; size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); // create matrix to transform original path data into desired size and position QTransform viewMatrix; viewMatrix.translate(-viewBox.left(), -viewBox.top()); viewMatrix.scale(scaleFactor.width(), scaleFactor.height()); viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height()); // transform the path data d->map(viewMatrix); } setTransformation(QTransform()); return true; } bool KoPathShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { Q_D(KoPathShape); loadOdfAttributes(element, context, OdfMandatories | OdfAdditionalAttributes | OdfCommonChildElements); // first clear the path data from the default path clear(); if (element.localName() == "line") { QPointF start; start.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", ""))); start.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", ""))); QPointF end; end.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", ""))); end.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", ""))); moveTo(start); lineTo(end); } else if (element.localName() == "polyline" || element.localName() == "polygon") { QString points = element.attributeNS(KoXmlNS::draw, "points").simplified(); points.replace(',', ' '); points.remove('\r'); points.remove('\n'); bool firstPoint = true; const QStringList coordinateList = points.split(' '); for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) { QPointF point; point.setX((*it).toDouble()); ++it; point.setY((*it).toDouble()); if (firstPoint) { moveTo(point); firstPoint = false; } else lineTo(point); } if (element.localName() == "polygon") close(); } else { // path loading KoPathShapeLoader loader(this); loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); d->loadNodeTypes(element); } d->applyViewboxTransformation(element); QPointF pos = normalize(); setTransformation(QTransform()); if (element.hasAttributeNS(KoXmlNS::svg, "x") || element.hasAttributeNS(KoXmlNS::svg, "y")) { pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); } setPosition(pos); loadOdfAttributes(element, context, OdfTransformation); // now that the correct transformation is set up // apply that matrix to the path geometry so that // we don't transform the stroke d->map(transformation()); setTransformation(QTransform()); normalize(); loadText(element, context); return true; } QString KoPathShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const { Q_D(const KoPathShape); style.addProperty("svg:fill-rule", d->fillRule == Qt::OddEvenFill ? "evenodd" : "nonzero"); KoShapeStroke *lineBorder = dynamic_cast(stroke()); qreal lineWidth = 0; if (lineBorder) { lineWidth = lineBorder->lineWidth(); } d->startMarker.saveStyle(style, lineWidth, context); d->endMarker.saveStyle(style, lineWidth, context); return KoTosContainer::saveStyle(style, context); } void KoPathShape::loadStyle(const KoXmlElement & element, KoShapeLoadingContext &context) { Q_D(KoPathShape); KoTosContainer::loadStyle(element, context); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); if (styleStack.hasProperty(KoXmlNS::svg, "fill-rule")) { QString rule = styleStack.property(KoXmlNS::svg, "fill-rule"); d->fillRule = (rule == "nonzero") ? Qt::WindingFill : Qt::OddEvenFill; } else { d->fillRule = Qt::WindingFill; #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixMissingFillRule(d->fillRule, context); #endif } KoShapeStroke *lineBorder = dynamic_cast(stroke()); qreal lineWidth = 0; if (lineBorder) { lineWidth = lineBorder->lineWidth(); } d->startMarker.loadOdf(lineWidth, context); d->endMarker.loadOdf(lineWidth, context); } QRect KoPathShape::loadOdfViewbox(const KoXmlElement & element) { QRect viewbox; QString data = element.attributeNS(KoXmlNS::svg, QLatin1String("viewBox")); if (! data.isEmpty()) { data.replace(QLatin1Char(','), QLatin1Char(' ')); const QStringList coordinates = data.simplified().split(QLatin1Char(' '), QString::SkipEmptyParts); if (coordinates.count() == 4) { viewbox.setRect(coordinates.at(0).toInt(), coordinates.at(1).toInt(), coordinates.at(2).toInt(), coordinates.at(3).toInt()); } } return viewbox; } void KoPathShape::clear() { foreach(KoSubpath *subpath, m_subpaths) { foreach(KoPathPoint *point, *subpath) delete point; delete subpath; } m_subpaths.clear(); } void KoPathShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { Q_D(KoPathShape); applyConversion(painter, converter); QPainterPath path(outline()); path.setFillRule(d->fillRule); if (background()) { background()->paint(painter, converter, paintContext, path); } //d->paintDebug(painter); } #ifndef NDEBUG void KoPathShapePrivate::paintDebug(QPainter &painter) { Q_Q(KoPathShape); KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin()); int i = 0; QPen pen(Qt::black, 0); painter.save(); painter.setPen(pen); for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { ++i; KoPathPoint *point = (*it); QRectF r(point->point(), QSizeF(5, 5)); r.translate(-2.5, -2.5); QPen pen(Qt::black, 0); painter.setPen(pen); if (point->activeControlPoint1() && point->activeControlPoint2()) { QBrush b(Qt::red); painter.setBrush(b); } else if (point->activeControlPoint1()) { QBrush b(Qt::yellow); painter.setBrush(b); } else if (point->activeControlPoint2()) { QBrush b(Qt::darkYellow); painter.setBrush(b); } painter.drawEllipse(r); } } painter.restore(); debugFlake << "nop =" << i; } void KoPathShapePrivate::debugPath() const { Q_Q(const KoPathShape); KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin()); for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { debugFlake << "p:" << (*pathIt) << "," << *it << "," << (*it)->point() << "," << (*it)->properties(); } } } #endif void KoPathShape::paintPoints(QPainter &painter, const KoViewConverter &converter, int handleRadius) { applyConversion(painter, converter); KoSubpathList::const_iterator pathIt(m_subpaths.constBegin()); for (; pathIt != m_subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) (*it)->paint(painter, handleRadius, KoPathPoint::Node); } } QPainterPath KoPathShape::outline() const { QPainterPath path; foreach(KoSubpath * subpath, m_subpaths) { KoPathPoint * lastPoint = subpath->first(); bool activeCP = false; foreach(KoPathPoint * currPoint, *subpath) { KoPathPoint::PointProperties currProperties = currPoint->properties(); if (currPoint == subpath->first()) { if (currProperties & KoPathPoint::StartSubpath) { Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.moveTo(currPoint->point()); } } else if (activeCP && currPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.cubicTo( lastPoint->controlPoint2(), currPoint->controlPoint1(), currPoint->point()); } else if (activeCP || currPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); path.quadTo( activeCP ? lastPoint->controlPoint2() : currPoint->controlPoint1(), currPoint->point()); } else { Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.lineTo(currPoint->point()); } if (currProperties & KoPathPoint::CloseSubpath && currProperties & KoPathPoint::StopSubpath) { // add curve when there is a curve on the way to the first point KoPathPoint * firstPoint = subpath->first(); Q_ASSERT(!qIsNaNPoint(firstPoint->point())); if (currPoint->activeControlPoint2() && firstPoint->activeControlPoint1()) { path.cubicTo( currPoint->controlPoint2(), firstPoint->controlPoint1(), firstPoint->point()); } else if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(currPoint->point())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); path.quadTo( currPoint->activeControlPoint2() ? currPoint->controlPoint2() : firstPoint->controlPoint1(), firstPoint->point()); } path.closeSubpath(); } if (currPoint->activeControlPoint2()) { activeCP = true; } else { activeCP = false; } lastPoint = currPoint; } } return path; } QRectF KoPathShape::boundingRect() const { QTransform transform = absoluteTransformation(0); // calculate the bounding rect of the transformed outline QRectF bb; KoShapeStroke *lineBorder = dynamic_cast(stroke()); QPen pen; if (lineBorder) { pen.setWidthF(lineBorder->lineWidth()); } bb = transform.map(pathStroke(pen)).boundingRect(); if (stroke()) { KoInsets inset; stroke()->strokeInsets(this, inset); // calculate transformed border insets QPointF center = transform.map(QPointF()); QPointF tl = transform.map(QPointF(-inset.left,-inset.top)) - center; QPointF br = transform.map(QPointF(inset.right,inset.bottom)) -center; qreal left = qMin(tl.x(),br.x()); qreal right = qMax(tl.x(),br.x()); qreal top = qMin(tl.y(),br.y()); qreal bottom = qMax(tl.y(),br.y()); bb.adjust(left, top, right, bottom); } if (shadow()) { KoInsets insets; shadow()->insets(insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (filterEffectStack()) { QRectF clipRect = filterEffectStack()->clipRectForBoundingRect(QRectF(QPointF(), size())); bb |= transform.mapRect(clipRect); } return bb; } QSizeF KoPathShape::size() const { // don't call boundingRect here as it uses absoluteTransformation // which itself uses size() -> leads to infinite reccursion return outline().boundingRect().size(); } void KoPathShape::setSize(const QSizeF &newSize) { Q_D(KoPathShape); QTransform matrix(resizeMatrix(newSize)); KoShape::setSize(newSize); d->map(matrix); } QTransform KoPathShape::resizeMatrix(const QSizeF & newSize) const { QSizeF oldSize = size(); if (oldSize.width() == 0.0) { oldSize.setWidth(0.000001); } if (oldSize.height() == 0.0) { oldSize.setHeight(0.000001); } QSizeF sizeNew(newSize); if (sizeNew.width() == 0.0) { sizeNew.setWidth(0.000001); } if (sizeNew.height() == 0.0) { sizeNew.setHeight(0.000001); } return QTransform(sizeNew.width() / oldSize.width(), 0, 0, sizeNew.height() / oldSize.height(), 0, 0); } KoPathPoint * KoPathShape::moveTo(const QPointF &p) { KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StartSubpath | KoPathPoint::StopSubpath); KoSubpath * path = new KoSubpath; path->push_back(point); m_subpaths.push_back(path); return point; } KoPathPoint * KoPathShape::lineTo(const QPointF &p) { Q_D(KoPathShape); if (m_subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); KoPathPoint * lastPoint = m_subpaths.last()->last(); d->updateLast(&lastPoint); m_subpaths.last()->push_back(point); return point; } KoPathPoint * KoPathShape::curveTo(const QPointF &c1, const QPointF &c2, const QPointF &p) { Q_D(KoPathShape); if (m_subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * lastPoint = m_subpaths.last()->last(); d->updateLast(&lastPoint); lastPoint->setControlPoint2(c1); KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); point->setControlPoint1(c2); m_subpaths.last()->push_back(point); return point; } KoPathPoint * KoPathShape::curveTo(const QPointF &c, const QPointF &p) { Q_D(KoPathShape); if (m_subpaths.empty()) moveTo(QPointF(0, 0)); KoPathPoint * lastPoint = m_subpaths.last()->last(); d->updateLast(&lastPoint); lastPoint->setControlPoint2(c); KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); m_subpaths.last()->push_back(point); return point; } KoPathPoint * KoPathShape::arcTo(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle) { if (m_subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * lastPoint = m_subpaths.last()->last(); if (lastPoint->properties() & KoPathPoint::CloseSubpath) { lastPoint = m_subpaths.last()->first(); } QPointF startpoint(lastPoint->point()); KoPathPoint * newEndPoint = lastPoint; QPointF curvePoints[12]; int pointCnt = arcToCurve(rx, ry, startAngle, sweepAngle, startpoint, curvePoints); for (int i = 0; i < pointCnt; i += 3) { newEndPoint = curveTo(curvePoints[i], curvePoints[i+1], curvePoints[i+2]); } return newEndPoint; } int KoPathShape::arcToCurve(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle, const QPointF & offset, QPointF * curvePoints) const { int pointCnt = 0; // check Parameters if (sweepAngle == 0) return pointCnt; if (sweepAngle > 360) sweepAngle = 360; else if (sweepAngle < -360) sweepAngle = - 360; if (rx == 0 || ry == 0) { //TODO } // split angles bigger than 90° so that it gives a good approximation to the circle qreal parts = ceil(qAbs(sweepAngle / 90.0)); qreal sa_rad = startAngle * M_PI / 180.0; qreal partangle = sweepAngle / parts; qreal endangle = startAngle + partangle; qreal se_rad = endangle * M_PI / 180.0; qreal sinsa = sin(sa_rad); qreal cossa = cos(sa_rad); qreal kappa = 4.0 / 3.0 * tan((se_rad - sa_rad) / 4); // startpoint is at the last point is the path but when it is closed // it is at the first point QPointF startpoint(offset); //center berechnen QPointF center(startpoint - QPointF(cossa * rx, -sinsa * ry)); //debugFlake <<"kappa" << kappa <<"parts" << parts; for (int part = 0; part < parts; ++part) { // start tangent curvePoints[pointCnt++] = QPointF(startpoint - QPointF(sinsa * rx * kappa, cossa * ry * kappa)); qreal sinse = sin(se_rad); qreal cosse = cos(se_rad); // end point QPointF endpoint(center + QPointF(cosse * rx, -sinse * ry)); // end tangent curvePoints[pointCnt++] = QPointF(endpoint - QPointF(-sinse * rx * kappa, -cosse * ry * kappa)); curvePoints[pointCnt++] = endpoint; // set the endpoint as next start point startpoint = endpoint; sinsa = sinse; cossa = cosse; endangle += partangle; se_rad = endangle * M_PI / 180.0; } return pointCnt; } void KoPathShape::close() { Q_D(KoPathShape); if (m_subpaths.empty()) { return; } d->closeSubpath(m_subpaths.last()); } void KoPathShape::closeMerge() { Q_D(KoPathShape); if (m_subpaths.empty()) { return; } d->closeMergeSubpath(m_subpaths.last()); } QPointF KoPathShape::normalize() { Q_D(KoPathShape); QPointF tl(outline().boundingRect().topLeft()); QTransform matrix; matrix.translate(-tl.x(), -tl.y()); d->map(matrix); // keep the top left point of the object applyTransformation(matrix.inverted()); d->shapeChanged(ContentChanged); return tl; } void KoPathShapePrivate::map(const QTransform &matrix) { Q_Q(KoPathShape); KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin()); for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { (*it)->map(matrix); } } } void KoPathShapePrivate::updateLast(KoPathPoint **lastPoint) { Q_Q(KoPathShape); // check if we are about to add a new point to a closed subpath if ((*lastPoint)->properties() & KoPathPoint::StopSubpath && (*lastPoint)->properties() & KoPathPoint::CloseSubpath) { // get the first point of the subpath KoPathPoint *subpathStart = q->m_subpaths.last()->first(); // clone the first point of the subpath... KoPathPoint * newLastPoint = new KoPathPoint(*subpathStart); // ... and make it a normal point newLastPoint->setProperties(KoPathPoint::Normal); // now start a new subpath with the cloned start point KoSubpath *path = new KoSubpath; path->push_back(newLastPoint); q->m_subpaths.push_back(path); *lastPoint = newLastPoint; } else { // the subpath was not closed so the formerly last point // of the subpath is no end point anymore (*lastPoint)->unsetProperty(KoPathPoint::StopSubpath); } (*lastPoint)->unsetProperty(KoPathPoint::CloseSubpath); } QList KoPathShape::pointsAt(const QRectF &r) const { QList result; KoSubpathList::const_iterator pathIt(m_subpaths.constBegin()); for (; pathIt != m_subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { if (r.contains((*it)->point())) result.append(*it); else if ((*it)->activeControlPoint1() && r.contains((*it)->controlPoint1())) result.append(*it); else if ((*it)->activeControlPoint2() && r.contains((*it)->controlPoint2())) result.append(*it); } } return result; } QList KoPathShape::segmentsAt(const QRectF &r) const { QList segments; int subpathCount = m_subpaths.count(); for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) { KoSubpath * subpath = m_subpaths[subpathIndex]; int pointCount = subpath->count(); bool subpathClosed = isClosedSubpath(subpathIndex); for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex) { if (pointIndex == (pointCount - 1) && ! subpathClosed) break; KoPathSegment s(subpath->at(pointIndex), subpath->at((pointIndex + 1) % pointCount)); QRectF controlRect = s.controlPointRect(); if (! r.intersects(controlRect) && ! controlRect.contains(r)) continue; QRectF bound = s.boundingRect(); if (! r.intersects(bound) && ! bound.contains(r)) continue; segments.append(s); } } return segments; } KoPathPointIndex KoPathShape::pathPointIndex(const KoPathPoint *point) const { for (int subpathIndex = 0; subpathIndex < m_subpaths.size(); ++subpathIndex) { KoSubpath * subpath = m_subpaths.at(subpathIndex); for (int pointPos = 0; pointPos < subpath->size(); ++pointPos) { if (subpath->at(pointPos) == point) { return KoPathPointIndex(subpathIndex, pointPos); } } } return KoPathPointIndex(-1, -1); } KoPathPoint * KoPathShape::pointByIndex(const KoPathPointIndex &pointIndex) const { Q_D(const KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) return 0; return subpath->at(pointIndex.second); } KoPathSegment KoPathShape::segmentByIndex(const KoPathPointIndex &pointIndex) const { Q_D(const KoPathShape); KoPathSegment segment(0, 0); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath != 0 && pointIndex.second >= 0 && pointIndex.second < subpath->size()) { KoPathPoint * point = subpath->at(pointIndex.second); int index = pointIndex.second; // check if we have a (closing) segment starting from the last point if ((index == subpath->size() - 1) && point->properties() & KoPathPoint::CloseSubpath) index = 0; else ++index; if (index < subpath->size()) { segment = KoPathSegment(point, subpath->at(index)); } } return segment; } int KoPathShape::pointCount() const { int i = 0; KoSubpathList::const_iterator pathIt(m_subpaths.constBegin()); for (; pathIt != m_subpaths.constEnd(); ++pathIt) { i += (*pathIt)->size(); } return i; } int KoPathShape::subpathCount() const { return m_subpaths.count(); } int KoPathShape::subpathPointCount(int subpathIndex) const { Q_D(const KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return -1; return subpath->size(); } bool KoPathShape::isClosedSubpath(int subpathIndex) const { Q_D(const KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return false; const bool firstClosed = subpath->first()->properties() & KoPathPoint::CloseSubpath; const bool lastClosed = subpath->last()->properties() & KoPathPoint::CloseSubpath; return firstClosed && lastClosed; } bool KoPathShape::insertPoint(KoPathPoint* point, const KoPathPointIndex &pointIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second > subpath->size()) return false; KoPathPoint::PointProperties properties = point->properties(); properties &= ~KoPathPoint::StartSubpath; properties &= ~KoPathPoint::StopSubpath; properties &= ~KoPathPoint::CloseSubpath; // check if new point starts subpath if (pointIndex.second == 0) { properties |= KoPathPoint::StartSubpath; // subpath was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep the path closed properties |= KoPathPoint::CloseSubpath; } // old first point does not start the subpath anymore subpath->first()->unsetProperty(KoPathPoint::StartSubpath); } // check if new point stops subpath else if (pointIndex.second == subpath->size()) { properties |= KoPathPoint::StopSubpath; // subpath was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep the path closed properties = properties | KoPathPoint::CloseSubpath; } // old last point does not end subpath anymore subpath->last()->unsetProperty(KoPathPoint::StopSubpath); } point->setProperties(properties); point->setParent(this); subpath->insert(pointIndex.second , point); return true; } KoPathPoint * KoPathShape::removePoint(const KoPathPointIndex &pointIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) return 0; KoPathPoint * point = subpath->takeAt(pointIndex.second); //don't do anything (not even crash), if there was only one point if (pointCount()==0) { return point; } // check if we removed the first point else if (pointIndex.second == 0) { // first point removed, set new StartSubpath subpath->first()->setProperty(KoPathPoint::StartSubpath); // check if path was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep path closed subpath->first()->setProperty(KoPathPoint::CloseSubpath); } } // check if we removed the last point else if (pointIndex.second == subpath->size()) { // use size as point is already removed // last point removed, set new StopSubpath subpath->last()->setProperty(KoPathPoint::StopSubpath); // check if path was closed if (point->properties() & KoPathPoint::CloseSubpath) { // keep path closed subpath->last()->setProperty(KoPathPoint::CloseSubpath); } } return point; } bool KoPathShape::breakAfter(const KoPathPointIndex &pointIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second > subpath->size() - 2 || isClosedSubpath(pointIndex.first)) return false; KoSubpath * newSubpath = new KoSubpath; int size = subpath->size(); for (int i = pointIndex.second + 1; i < size; ++i) { newSubpath->append(subpath->takeAt(pointIndex.second + 1)); } // now make the first point of the new subpath a starting node newSubpath->first()->setProperty(KoPathPoint::StartSubpath); // the last point of the old subpath is now an ending node subpath->last()->setProperty(KoPathPoint::StopSubpath); // insert the new subpath after the broken one m_subpaths.insert(pointIndex.first + 1, newSubpath); return true; } bool KoPathShape::join(int subpathIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); KoSubpath *nextSubpath = d->subPath(subpathIndex + 1); if (!subpath || !nextSubpath || isClosedSubpath(subpathIndex) || isClosedSubpath(subpathIndex+1)) return false; // the last point of the subpath does not end the subpath anymore subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // the first point of the next subpath does not start a subpath anymore nextSubpath->first()->unsetProperty(KoPathPoint::StartSubpath); // append the second subpath to the first foreach(KoPathPoint * p, *nextSubpath) subpath->append(p); // remove the nextSubpath from path m_subpaths.removeAt(subpathIndex + 1); // delete it as it is no longer possible to use it delete nextSubpath; return true; } bool KoPathShape::moveSubpath(int oldSubpathIndex, int newSubpathIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(oldSubpathIndex); if (subpath == 0 || newSubpathIndex >= m_subpaths.size()) return false; if (oldSubpathIndex == newSubpathIndex) return true; m_subpaths.removeAt(oldSubpathIndex); m_subpaths.insert(newSubpathIndex, subpath); return true; } KoPathPointIndex KoPathShape::openSubpath(const KoPathPointIndex &pointIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() || !isClosedSubpath(pointIndex.first)) return KoPathPointIndex(-1, -1); KoPathPoint * oldStartPoint = subpath->first(); // the old starting node no longer starts the subpath oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); // the old end node no longer closes the subpath subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // reorder the subpath for (int i = 0; i < pointIndex.second; ++i) { subpath->append(subpath->takeFirst()); } // make the first point a start node subpath->first()->setProperty(KoPathPoint::StartSubpath); // make the last point an end node subpath->last()->setProperty(KoPathPoint::StopSubpath); return pathPointIndex(oldStartPoint); } KoPathPointIndex KoPathShape::closeSubpath(const KoPathPointIndex &pointIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() || isClosedSubpath(pointIndex.first)) return KoPathPointIndex(-1, -1); KoPathPoint * oldStartPoint = subpath->first(); // the old starting node no longer starts the subpath oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); // the old end node no longer ends the subpath subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // reorder the subpath for (int i = 0; i < pointIndex.second; ++i) { subpath->append(subpath->takeFirst()); } subpath->first()->setProperty(KoPathPoint::StartSubpath); subpath->last()->setProperty(KoPathPoint::StopSubpath); d->closeSubpath(subpath); return pathPointIndex(oldStartPoint); } bool KoPathShape::reverseSubpath(int subpathIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return false; int size = subpath->size(); for (int i = 0; i < size; ++i) { KoPathPoint *p = subpath->takeAt(i); p->reverse(); subpath->prepend(p); } // adjust the position dependent properties KoPathPoint *first = subpath->first(); KoPathPoint *last = subpath->last(); KoPathPoint::PointProperties firstProps = first->properties(); KoPathPoint::PointProperties lastProps = last->properties(); firstProps |= KoPathPoint::StartSubpath; firstProps &= ~KoPathPoint::StopSubpath; lastProps |= KoPathPoint::StopSubpath; lastProps &= ~KoPathPoint::StartSubpath; if (firstProps & KoPathPoint::CloseSubpath) { firstProps |= KoPathPoint::CloseSubpath; lastProps |= KoPathPoint::CloseSubpath; } first->setProperties(firstProps); last->setProperties(lastProps); return true; } KoSubpath * KoPathShape::removeSubpath(int subpathIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); if (subpath != 0) m_subpaths.removeAt(subpathIndex); return subpath; } bool KoPathShape::addSubpath(KoSubpath * subpath, int subpathIndex) { if (subpathIndex < 0 || subpathIndex > m_subpaths.size()) return false; m_subpaths.insert(subpathIndex, subpath); return true; } bool KoPathShape::combine(KoPathShape *path) { if (! path) return false; QTransform pathMatrix = path->absoluteTransformation(0); QTransform myMatrix = absoluteTransformation(0).inverted(); foreach(KoSubpath* subpath, path->m_subpaths) { KoSubpath *newSubpath = new KoSubpath(); foreach(KoPathPoint* point, *subpath) { KoPathPoint *newPoint = new KoPathPoint(*point); newPoint->map(pathMatrix); newPoint->map(myMatrix); newPoint->setParent(this); newSubpath->append(newPoint); } m_subpaths.append(newSubpath); } normalize(); return true; } bool KoPathShape::separate(QList & separatedPaths) { if (! m_subpaths.size()) return false; QTransform myMatrix = absoluteTransformation(0); foreach(KoSubpath* subpath, m_subpaths) { KoPathShape *shape = new KoPathShape(); if (! shape) continue; shape->setStroke(stroke()); shape->setShapeId(shapeId()); KoSubpath *newSubpath = new KoSubpath(); foreach(KoPathPoint* point, *subpath) { KoPathPoint *newPoint = new KoPathPoint(*point); newPoint->map(myMatrix); newSubpath->append(newPoint); } shape->m_subpaths.append(newSubpath); shape->normalize(); separatedPaths.append(shape); } return true; } void KoPathShapePrivate::closeSubpath(KoSubpath *subpath) { if (! subpath) return; subpath->last()->setProperty(KoPathPoint::CloseSubpath); subpath->first()->setProperty(KoPathPoint::CloseSubpath); } void KoPathShapePrivate::closeMergeSubpath(KoSubpath *subpath) { if (! subpath || subpath->size() < 2) return; KoPathPoint * lastPoint = subpath->last(); KoPathPoint * firstPoint = subpath->first(); // check if first and last points are coincident if (lastPoint->point() == firstPoint->point()) { // we are removing the current last point and // reuse its first control point if active firstPoint->setProperty(KoPathPoint::StartSubpath); firstPoint->setProperty(KoPathPoint::CloseSubpath); if (lastPoint->activeControlPoint1()) firstPoint->setControlPoint1(lastPoint->controlPoint1()); // remove last point delete subpath->takeLast(); // the new last point closes the subpath now lastPoint = subpath->last(); lastPoint->setProperty(KoPathPoint::StopSubpath); lastPoint->setProperty(KoPathPoint::CloseSubpath); } else { closeSubpath(subpath); } } KoSubpath *KoPathShapePrivate::subPath(int subpathIndex) const { Q_Q(const KoPathShape); if (subpathIndex < 0 || subpathIndex >= q->m_subpaths.size()) return 0; return q->m_subpaths.at(subpathIndex); } QString KoPathShape::pathShapeId() const { return KoPathShapeId; } QString KoPathShape::toString(const QTransform &matrix) const { QString d; // iterate over all subpaths KoSubpathList::const_iterator pathIt(m_subpaths.constBegin()); for (; pathIt != m_subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator pointIt((*pathIt)->constBegin()); // keep a pointer to the first point of the subpath KoPathPoint *firstPoint(*pointIt); // keep a pointer to the previous point of the subpath KoPathPoint *lastPoint = firstPoint; // keep track if the previous point has an active control point 2 bool activeControlPoint2 = false; // iterate over all points of the current subpath for (; pointIt != (*pathIt)->constEnd(); ++pointIt) { KoPathPoint *currPoint(*pointIt); // first point of subpath ? if (currPoint == firstPoint) { // are we starting a subpath ? if (currPoint->properties() & KoPathPoint::StartSubpath) { const QPointF p = matrix.map(currPoint->point()); d += QString("M%1 %2").arg(p.x()).arg(p.y()); } } // end point of curve segment ? else if (activeControlPoint2 || currPoint->activeControlPoint1()) { // check if we have a cubic or quadratic curve const bool isCubic = activeControlPoint2 && currPoint->activeControlPoint1(); KoPathSegment cubicSeg = isCubic ? KoPathSegment(lastPoint, currPoint) : KoPathSegment(lastPoint, currPoint).toCubic(); const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); const QPointF p = matrix.map(cubicSeg.second()->point()); d += QString("C%1 %2 %3 %4 %5 %6") .arg(cp1.x()).arg(cp1.y()) .arg(cp2.x()).arg(cp2.y()) .arg(p.x()).arg(p.y()); } // end point of line segment! else { const QPointF p = matrix.map(currPoint->point()); d += QString("L%1 %2").arg(p.x()).arg(p.y()); } // last point closes subpath ? if (currPoint->properties() & KoPathPoint::StopSubpath && currPoint->properties() & KoPathPoint::CloseSubpath) { // add curve when there is a curve on the way to the first point if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { // check if we have a cubic or quadratic curve const bool isCubic = currPoint->activeControlPoint2() && firstPoint->activeControlPoint1(); KoPathSegment cubicSeg = isCubic ? KoPathSegment(currPoint, firstPoint) : KoPathSegment(currPoint, firstPoint).toCubic(); const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); const QPointF p = matrix.map(cubicSeg.second()->point()); d += QString("C%1 %2 %3 %4 %5 %6") .arg(cp1.x()).arg(cp1.y()) .arg(cp2.x()).arg(cp2.y()) .arg(p.x()).arg(p.y()); } d += QString("Z"); } activeControlPoint2 = currPoint->activeControlPoint2(); lastPoint = currPoint; } } return d; } char nodeType(const KoPathPoint * point) { if (point->properties() & KoPathPoint::IsSmooth) { return 's'; } else if (point->properties() & KoPathPoint::IsSymmetric) { return 'z'; } else { return 'c'; } } QString KoPathShapePrivate::nodeTypes() const { Q_Q(const KoPathShape); QString types; KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin()); for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { if (it == (*pathIt)->constBegin()) { types.append('c'); } else { types.append(nodeType(*it)); } if ((*it)->properties() & KoPathPoint::StopSubpath && (*it)->properties() & KoPathPoint::CloseSubpath) { KoPathPoint * firstPoint = (*pathIt)->first(); types.append(nodeType(firstPoint)); } } } return types; } void updateNodeType(KoPathPoint * point, const QChar & nodeType) { if (nodeType == 's') { point->setProperty(KoPathPoint::IsSmooth); } else if (nodeType == 'z') { point->setProperty(KoPathPoint::IsSymmetric); } } void KoPathShapePrivate::loadNodeTypes(const KoXmlElement &element) { Q_Q(KoPathShape); if (element.hasAttributeNS(KoXmlNS::calligra, "nodeTypes")) { QString nodeTypes = element.attributeNS(KoXmlNS::calligra, "nodeTypes"); QString::const_iterator nIt(nodeTypes.constBegin()); KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin()); for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it, nIt++) { // be sure not to crash if there are not enough nodes in nodeTypes if (nIt == nodeTypes.constEnd()) { warnFlake << "not enough nodes in calligra:nodeTypes"; return; } // the first node is always of type 'c' if (it != (*pathIt)->constBegin()) { updateNodeType(*it, *nIt); } if ((*it)->properties() & KoPathPoint::StopSubpath && (*it)->properties() & KoPathPoint::CloseSubpath) { ++nIt; updateNodeType((*pathIt)->first(), *nIt); } } } } } Qt::FillRule KoPathShape::fillRule() const { Q_D(const KoPathShape); return d->fillRule; } void KoPathShape::setFillRule(Qt::FillRule fillRule) { Q_D(KoPathShape); d->fillRule = fillRule; } KoPathShape * KoPathShape::createShapeFromPainterPath(const QPainterPath &path) { KoPathShape * shape = new KoPathShape(); int elementCount = path.elementCount(); for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = path.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: shape->moveTo(QPointF(element.x, element.y)); break; case QPainterPath::LineToElement: shape->lineTo(QPointF(element.x, element.y)); break; case QPainterPath::CurveToElement: shape->curveTo(QPointF(element.x, element.y), QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y), QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y)); break; default: continue; } } shape->normalize(); return shape; } bool KoPathShape::hitTest(const QPointF &position) const { if (parent() && parent()->isClipped(this) && ! parent()->hitTest(position)) return false; QPointF point = absoluteTransformation(0).inverted().map(position); const QPainterPath outlinePath = outline(); if (stroke()) { KoInsets insets; stroke()->strokeInsets(this, insets); QRectF roi(QPointF(-insets.left, -insets.top), QPointF(insets.right, insets.bottom)); roi.moveCenter(point); if (outlinePath.intersects(roi) || outlinePath.contains(roi)) return true; } else { if (outlinePath.contains(point)) return true; } // if there is no shadow we can as well just leave if (! shadow()) return false; // the shadow has an offset to the shape, so we simply // check if the position minus the shadow offset hits the shape point = absoluteTransformation(0).inverted().map(position - shadow()->offset()); return outlinePath.contains(point); } void KoPathShape::setMarker(const KoMarkerData &markerData) { Q_D(KoPathShape); if (markerData.position() == KoMarkerData::MarkerStart) { d->startMarker = markerData; } else { d->endMarker = markerData; } } void KoPathShape::setMarker(KoMarker *marker, KoMarkerData::MarkerPosition position) { Q_D(KoPathShape); if (position == KoMarkerData::MarkerStart) { if (!d->startMarker.marker()) { d->startMarker.setWidth(MM_TO_POINT(DefaultMarkerWidth), qreal(0.0)); } d->startMarker.setMarker(marker); } else { if (!d->endMarker.marker()) { d->endMarker.setWidth(MM_TO_POINT(DefaultMarkerWidth), qreal(0.0)); } d->endMarker.setMarker(marker); } } KoMarker *KoPathShape::marker(KoMarkerData::MarkerPosition position) const { Q_D(const KoPathShape); if (position == KoMarkerData::MarkerStart) { return d->startMarker.marker(); } else { return d->endMarker.marker(); } } KoMarkerData KoPathShape::markerData(KoMarkerData::MarkerPosition position) const { Q_D(const KoPathShape); if (position == KoMarkerData::MarkerStart) { return d->startMarker; } else { return d->endMarker; } } QPainterPath KoPathShape::pathStroke(const QPen &pen) const { if (m_subpaths.isEmpty()) { return QPainterPath(); } QPainterPath pathOutline; QPainterPathStroker stroker; stroker.setWidth(0); stroker.setJoinStyle(Qt::MiterJoin); QPair firstSegments; QPair lastSegments; KoPathPoint *firstPoint = 0; KoPathPoint *lastPoint = 0; KoPathPoint *secondPoint = 0; KoPathPoint *preLastPoint = 0; KoSubpath *firstSubpath = m_subpaths.first(); bool twoPointPath = subpathPointCount(0) == 2; bool closedPath = isClosedSubpath(0); /* * The geometry is horizontally centered. It is vertically positioned relative to an offset value which * is specified by a draw:marker-start-center attribute for markers referenced by a * draw:marker-start attribute, and by the draw:marker-end-center attribute for markers * referenced by a draw:marker-end attribute. The attribute value true defines an offset of 0.5 * and the attribute value false defines an offset of 0.3, which is also the default value. The offset * specifies the marker's vertical position in a range from 0.0 to 1.0, where the value 0.0 means the * geometry's bottom bound is aligned to the X axis of the local coordinate system of the marker * geometry, and where the value 1.0 means the top bound to be aligned to the X axis of the local * coordinate system of the marker geometry. * * The shorten factor to use results of the 0.3 which means we need to start at 0.7 * height of the marker */ static const qreal shortenFactor = 0.7; KoMarkerData mdStart = markerData(KoMarkerData::MarkerStart); KoMarkerData mdEnd = markerData(KoMarkerData::MarkerEnd); if (mdStart.marker() && !closedPath) { QPainterPath markerPath = mdStart.marker()->path(mdStart.width(pen.widthF())); KoPathSegment firstSegment = segmentByIndex(KoPathPointIndex(0, 0)); if (firstSegment.isValid()) { QRectF pathBoundingRect = markerPath.boundingRect(); qreal shortenLength = pathBoundingRect.height() * shortenFactor; debugFlake << "length" << firstSegment.length() << shortenLength; qreal t = firstSegment.paramAtLength(shortenLength); firstSegments = firstSegment.splitAt(t); // transform the marker so that it goes from the first point of the first segment to the second point of the first segment QPointF startPoint = firstSegments.first.first()->point(); QPointF newStartPoint = firstSegments.first.second()->point(); QLineF vector(newStartPoint, startPoint); qreal angle = -vector.angle() + 90; QTransform transform; transform.translate(startPoint.x(), startPoint.y()) .rotate(angle) .translate(-pathBoundingRect.width() / 2.0, 0); markerPath = transform.map(markerPath); QPainterPath startOutline = stroker.createStroke(markerPath); startOutline = startOutline.united(markerPath); pathOutline.addPath(startOutline); firstPoint = firstSubpath->first(); if (firstPoint->properties() & KoPathPoint::StartSubpath) { firstSegments.second.first()->setProperty(KoPathPoint::StartSubpath); } debugFlake << "start marker" << angle << startPoint << newStartPoint << firstPoint->point(); if (!twoPointPath) { if (firstSegment.second()->activeControlPoint2()) { firstSegments.second.second()->setControlPoint2(firstSegment.second()->controlPoint2()); } secondPoint = (*firstSubpath)[1]; } else if (!mdEnd.marker()) { // in case it is two point path with no end marker we need to modify the last point via the secondPoint secondPoint = (*firstSubpath)[1]; } } } if (mdEnd.marker() && !closedPath) { QPainterPath markerPath = mdEnd.marker()->path(mdEnd.width(pen.widthF())); KoPathSegment lastSegment; /* * if the path consists only of 2 point and it it has an marker on both ends * use the firstSegments.second as that is the path that needs to be shortened */ if (twoPointPath && firstPoint) { lastSegment = firstSegments.second; } else { lastSegment = segmentByIndex(KoPathPointIndex(0, firstSubpath->count() - 2)); } if (lastSegment.isValid()) { QRectF pathBoundingRect = markerPath.boundingRect(); qreal shortenLength = lastSegment.length() - pathBoundingRect.height() * shortenFactor; qreal t = lastSegment.paramAtLength(shortenLength); lastSegments = lastSegment.splitAt(t); // transform the marker so that it goes from the last point of the first segment to the previous point of the last segment QPointF startPoint = lastSegments.second.second()->point(); QPointF newStartPoint = lastSegments.second.first()->point(); QLineF vector(newStartPoint, startPoint); qreal angle = -vector.angle() + 90; QTransform transform; transform.translate(startPoint.x(), startPoint.y()).rotate(angle).translate(-pathBoundingRect.width() / 2.0, 0); markerPath = transform.map(markerPath); QPainterPath endOutline = stroker.createStroke(markerPath); endOutline = endOutline.united(markerPath); pathOutline.addPath(endOutline); lastPoint = firstSubpath->last(); debugFlake << "end marker" << angle << startPoint << newStartPoint << lastPoint->point(); if (twoPointPath) { if (firstSegments.second.isValid()) { if (lastSegments.first.first()->activeControlPoint2()) { firstSegments.second.first()->setControlPoint2(lastSegments.first.first()->controlPoint2()); } } else { // if there is no start marker we need the first point needs to be changed via the preLastPoint // the flag needs to be set so the moveTo is done lastSegments.first.first()->setProperty(KoPathPoint::StartSubpath); preLastPoint = (*firstSubpath)[firstSubpath->count()-2]; } } else { if (lastSegment.first()->activeControlPoint1()) { lastSegments.first.first()->setControlPoint1(lastSegment.first()->controlPoint1()); } preLastPoint = (*firstSubpath)[firstSubpath->count()-2]; } } } stroker.setWidth(pen.widthF()); stroker.setJoinStyle(pen.joinStyle()); stroker.setMiterLimit(pen.miterLimit()); stroker.setCapStyle(pen.capStyle()); stroker.setDashOffset(pen.dashOffset()); stroker.setDashPattern(pen.dashPattern()); // shortent the path to make it look nice // replace the point temporarily in case there is an arrow // BE AWARE: this changes the content of the path so that outline give the correct values. if (firstPoint) { firstSubpath->first() = firstSegments.second.first(); if (secondPoint) { (*firstSubpath)[1] = firstSegments.second.second(); } } if (lastPoint) { if (preLastPoint) { (*firstSubpath)[firstSubpath->count() - 2] = lastSegments.first.first(); } firstSubpath->last() = lastSegments.first.second(); } QPainterPath path = stroker.createStroke(outline()); if (firstPoint) { firstSubpath->first() = firstPoint; if (secondPoint) { (*firstSubpath)[1] = secondPoint; } } if (lastPoint) { if (preLastPoint) { (*firstSubpath)[firstSubpath->count() - 2] = preLastPoint; } firstSubpath->last() = lastPoint; } pathOutline.addPath(path); pathOutline.setFillRule(Qt::WindingFill); return pathOutline; } diff --git a/libs/flake/KoPatternBackground.cpp b/libs/flake/KoPatternBackground.cpp index 78fff7793df..c17125910f0 100644 --- a/libs/flake/KoPatternBackground.cpp +++ b/libs/flake/KoPatternBackground.cpp @@ -1,485 +1,486 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPatternBackground.h" #include "KoShapeBackground_p.h" #include "KoShapeSavingContext.h" #include "KoImageData.h" #include "KoImageCollection.h" #include #include #include #include #include #include #include #include #include #include #include #include +#include class KoPatternBackgroundPrivate : public KoShapeBackgroundPrivate { public: KoPatternBackgroundPrivate() : repeat(KoPatternBackground::Tiled) , refPoint(KoPatternBackground::TopLeft) , imageCollection(0) , imageData(0) { } ~KoPatternBackgroundPrivate() override { delete imageData; } QSizeF targetSize() const { QSizeF size = imageData->imageSize(); if (targetImageSizePercent.width() > 0.0) size.setWidth(0.01 * targetImageSizePercent.width() * size.width()); else if (targetImageSize.width() > 0.0) size.setWidth(targetImageSize.width()); if (targetImageSizePercent.height() > 0.0) size.setHeight(0.01 * targetImageSizePercent.height() * size.height()); else if (targetImageSize.height() > 0.0) size.setHeight(targetImageSize.height()); return size; } QPointF offsetFromRect(const QRectF &fillRect, const QSizeF &imageSize) const { QPointF offset; switch (refPoint) { case KoPatternBackground::TopLeft: offset = fillRect.topLeft(); break; case KoPatternBackground::Top: offset.setX(fillRect.center().x() - 0.5 * imageSize.width()); offset.setY(fillRect.top()); break; case KoPatternBackground::TopRight: offset.setX(fillRect.right() - imageSize.width()); offset.setY(fillRect.top()); break; case KoPatternBackground::Left: offset.setX(fillRect.left()); offset.setY(fillRect.center().y() - 0.5 * imageSize.height()); break; case KoPatternBackground::Center: offset.setX(fillRect.center().x() - 0.5 * imageSize.width()); offset.setY(fillRect.center().y() - 0.5 * imageSize.height()); break; case KoPatternBackground::Right: offset.setX(fillRect.right() - imageSize.width()); offset.setY(fillRect.center().y() - 0.5 * imageSize.height()); break; case KoPatternBackground::BottomLeft: offset.setX(fillRect.left()); offset.setY(fillRect.bottom() - imageSize.height()); break; case KoPatternBackground::Bottom: offset.setX(fillRect.center().x() - 0.5 * imageSize.width()); offset.setY(fillRect.bottom() - imageSize.height()); break; case KoPatternBackground::BottomRight: offset.setX(fillRect.right() - imageSize.width()); offset.setY(fillRect.bottom() - imageSize.height()); break; default: break; } if (refPointOffsetPercent.x() > 0.0) offset += QPointF(0.01 * refPointOffsetPercent.x() * imageSize.width(), 0); if (refPointOffsetPercent.y() > 0.0) offset += QPointF(0, 0.01 * refPointOffsetPercent.y() * imageSize.height()); return offset; } QTransform matrix; KoPatternBackground::PatternRepeat repeat; KoPatternBackground::ReferencePoint refPoint; QSizeF targetImageSize; QSizeF targetImageSizePercent; QPointF refPointOffsetPercent; QPointF tileRepeatOffsetPercent; KoImageCollection * imageCollection; KoImageData * imageData; }; // ---------------------------------------------------------------- KoPatternBackground::KoPatternBackground(KoImageCollection * imageCollection) : KoShapeBackground(*(new KoPatternBackgroundPrivate())) { Q_D(KoPatternBackground); d->imageCollection = imageCollection; Q_ASSERT(d->imageCollection); } KoPatternBackground::~KoPatternBackground() { //Q_D(KoPatternBackground); } void KoPatternBackground::setTransform(const QTransform &matrix) { Q_D(KoPatternBackground); d->matrix = matrix; } QTransform KoPatternBackground::transform() const { Q_D(const KoPatternBackground); return d->matrix; } void KoPatternBackground::setPattern(const QImage &pattern) { Q_D(KoPatternBackground); delete d->imageData; d->imageData = d->imageCollection->createImageData(pattern); } void KoPatternBackground::setPattern(KoImageData *imageData) { Q_D(KoPatternBackground); delete d->imageData; d->imageData = imageData; } QImage KoPatternBackground::pattern() const { Q_D(const KoPatternBackground); if (d->imageData) return d->imageData->image(); return QImage(); } void KoPatternBackground::setRepeat(PatternRepeat repeat) { Q_D(KoPatternBackground); d->repeat = repeat; } KoPatternBackground::PatternRepeat KoPatternBackground::repeat() const { Q_D(const KoPatternBackground); return d->repeat; } KoPatternBackground::ReferencePoint KoPatternBackground::referencePoint() const { Q_D(const KoPatternBackground); return d->refPoint; } void KoPatternBackground::setReferencePoint(ReferencePoint referencePoint) { Q_D(KoPatternBackground); d->refPoint = referencePoint; } QPointF KoPatternBackground::referencePointOffset() const { Q_D(const KoPatternBackground); return d->refPointOffsetPercent; } void KoPatternBackground::setReferencePointOffset(const QPointF &offset) { Q_D(KoPatternBackground); qreal ox = qMax(qreal(0.0), qMin(qreal(100.0), offset.x())); qreal oy = qMax(qreal(0.0), qMin(qreal(100.0), offset.y())); d->refPointOffsetPercent = QPointF(ox, oy); } QPointF KoPatternBackground::tileRepeatOffset() const { Q_D(const KoPatternBackground); return d->tileRepeatOffsetPercent; } void KoPatternBackground::setTileRepeatOffset(const QPointF &offset) { Q_D(KoPatternBackground); d->tileRepeatOffsetPercent = offset; } QSizeF KoPatternBackground::patternDisplaySize() const { Q_D(const KoPatternBackground); return d->targetSize(); } void KoPatternBackground::setPatternDisplaySize(const QSizeF &size) { Q_D(KoPatternBackground); d->targetImageSizePercent = QSizeF(); d->targetImageSize = size; } QSizeF KoPatternBackground::patternOriginalSize() const { Q_D(const KoPatternBackground); return d->imageData->imageSize(); } void KoPatternBackground::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const { Q_D(const KoPatternBackground); if (! d->imageData) return; painter.save(); if (d->repeat == Tiled) { // calculate scaling of pixmap QSizeF targetSize = d->targetSize(); QSizeF imageSize = d->imageData->imageSize(); qreal scaleX = targetSize.width() / imageSize.width(); qreal scaleY = targetSize.height() / imageSize.height(); QRectF targetRect = fillPath.boundingRect(); // undo scaling on target rectangle targetRect.setWidth(targetRect.width() / scaleX); targetRect.setHeight(targetRect.height() / scaleY); // determine pattern offset QPointF offset = d->offsetFromRect(targetRect, imageSize); // create matrix for pixmap scaling QTransform matrix; matrix.scale(scaleX, scaleY); painter.setClipPath(fillPath); painter.setWorldTransform(matrix, true); painter.drawTiledPixmap(targetRect, d->imageData->pixmap(imageSize.toSize()), -offset); } else if (d->repeat == Original) { QRectF sourceRect(QPointF(0, 0), d->imageData->imageSize()); QRectF targetRect(QPoint(0, 0), d->targetSize()); targetRect.moveCenter(fillPath.boundingRect().center()); painter.setClipPath(fillPath); painter.drawPixmap(targetRect, d->imageData->pixmap(sourceRect.size().toSize()), sourceRect); } else if (d->repeat == Stretched) { painter.setClipPath(fillPath); // undo conversion of the scaling so that we can use a nicely scaled image of the correct size qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); zoomX = zoomX ? 1 / zoomX : zoomX; zoomY = zoomY ? 1 / zoomY : zoomY; painter.scale(zoomX, zoomY); QRectF targetRect = converter.documentToView(fillPath.boundingRect()); painter.drawPixmap(targetRect.topLeft(), d->imageData->pixmap(targetRect.size().toSize())); } painter.restore(); } void KoPatternBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) { Q_D(KoPatternBackground); if (! d->imageData) return; switch (d->repeat) { case Original: style.addProperty("style:repeat", "no-repeat"); break; case Tiled: style.addProperty("style:repeat", "repeat"); break; case Stretched: style.addProperty("style:repeat", "stretch"); break; } if (d->repeat == Tiled) { QString refPointId = "top-left"; switch (d->refPoint) { case TopLeft: refPointId = "top-left"; break; case Top: refPointId = "top"; break; case TopRight: refPointId = "top-right"; break; case Left: refPointId = "left"; break; case Center: refPointId = "center"; break; case Right: refPointId = "right"; break; case BottomLeft: refPointId = "bottom-left"; break; case Bottom: refPointId = "bottom"; break; case BottomRight: refPointId = "bottom-right"; break; } style.addProperty("draw:fill-image-ref-point", refPointId); if (d->refPointOffsetPercent.x() > 0.0) style.addProperty("draw:fill-image-ref-point-x", QString("%1%").arg(d->refPointOffsetPercent.x())); if (d->refPointOffsetPercent.y() > 0.0) style.addProperty("draw:fill-image-ref-point-y", QString("%1%").arg(d->refPointOffsetPercent.y())); } if (d->repeat != Stretched) { QSizeF targetSize = d->targetSize(); QSizeF imageSize = d->imageData->imageSize(); if (targetSize.height() != imageSize.height()) style.addPropertyPt("draw:fill-image-height", targetSize.height()); if (targetSize.width() != imageSize.width()) style.addPropertyPt("draw:fill-image-width", targetSize.width()); } KoGenStyle patternStyle(KoGenStyle::FillImageStyle /*no family name*/); patternStyle.addAttribute("xlink:show", "embed"); patternStyle.addAttribute("xlink:actuate", "onLoad"); patternStyle.addAttribute("xlink:type", "simple"); patternStyle.addAttribute("xlink:href", context.imageHref(d->imageData)); QString patternStyleName = context.mainStyles().insert(patternStyle, "picture"); style.addProperty("draw:fill", "bitmap"); style.addProperty("draw:fill-image-name", patternStyleName); context.addDataCenter(d->imageCollection); } bool KoPatternBackground::loadStyle(KoOdfLoadingContext &context, const QSizeF &) { Q_D(KoPatternBackground); KoStyleStack &styleStack = context.styleStack(); if (! styleStack.hasProperty(KoXmlNS::draw, "fill")) return false; QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle != "bitmap") return false; QString styleName = styleStack.property(KoXmlNS::draw, "fill-image-name"); KoXmlElement* e = context.stylesReader().drawStyles("fill-image").value(styleName); if (! e) return false; const QString href = e->attributeNS(KoXmlNS::xlink, "href", QString()); if (href.isEmpty()) return false; delete d->imageData; d->imageData = d->imageCollection->createImageData(href, context.store()); if (! d->imageData) return false; // read the pattern repeat style QString style = styleStack.property(KoXmlNS::style, "repeat"); if (style == "stretch") d->repeat = Stretched; else if (style == "no-repeat") d->repeat = Original; else d->repeat = Tiled; if (style != "stretch") { // optional attributes which can override original image size if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-height")) { QString height = styleStack.property(KoXmlNS::draw, "fill-image-height"); if (height.endsWith('%')) d->targetImageSizePercent.setHeight(height.remove('%').toDouble()); else d->targetImageSize.setHeight(KoUnit::parseValue(height)); } if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-width")) { QString width = styleStack.property(KoXmlNS::draw, "fill-image-width"); if (width.endsWith('%')) d->targetImageSizePercent.setWidth(width.remove('%').toDouble()); else d->targetImageSize.setWidth(KoUnit::parseValue(width)); } } if (style == "repeat") { if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point")) { // align pattern to the given size QString align = styleStack.property(KoXmlNS::draw, "fill-image-ref-point"); if (align == "top-left") d->refPoint = TopLeft; else if (align == "top") d->refPoint = Top; else if (align == "top-right") d->refPoint = TopRight; else if (align == "left") d->refPoint = Left; else if (align == "center") d->refPoint = Center; else if (align == "right") d->refPoint = Right; else if (align == "bottom-left") d->refPoint = BottomLeft; else if (align == "bottom") d->refPoint = Bottom; else if (align == "bottom-right") d->refPoint = BottomRight; } if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point-x")) { QString pointX = styleStack.property(KoXmlNS::draw, "fill-image-ref-point-x"); d->refPointOffsetPercent.setX(pointX.remove('%').toDouble()); } if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point-y")) { QString pointY = styleStack.property(KoXmlNS::draw, "fill-image-ref-point-y"); d->refPointOffsetPercent.setY(pointY.remove('%').toDouble()); } if (styleStack.hasProperty(KoXmlNS::draw, "tile-repeat-offset")) { QString repeatOffset = styleStack.property(KoXmlNS::draw, "tile-repeat-offset"); QStringList tokens = repeatOffset.split('%'); if (tokens.count() == 2) { QString direction = tokens[1].simplified(); if (direction == "horizontal") d->tileRepeatOffsetPercent.setX(tokens[0].toDouble()); else if (direction == "vertical") d->tileRepeatOffsetPercent.setY(tokens[0].toDouble()); } } } return true; } QRectF KoPatternBackground::patternRectFromFillSize(const QSizeF &size) { Q_D(KoPatternBackground); QRectF rect; switch (d->repeat) { case Tiled: rect.setTopLeft(d->offsetFromRect(QRectF(QPointF(), size), d->targetSize())); rect.setSize(d->targetSize()); break; case Original: rect.setLeft(0.5 * (size.width() - d->targetSize().width())); rect.setTop(0.5 * (size.height() - d->targetSize().height())); rect.setSize(d->targetSize()); break; case Stretched: rect.setTopLeft(QPointF(0.0, 0.0)); rect.setSize(size); break; } return rect; } diff --git a/libs/flake/KoShapeManager.cpp b/libs/flake/KoShapeManager.cpp index 8b8a910b3e0..45c33c64b85 100644 --- a/libs/flake/KoShapeManager.cpp +++ b/libs/flake/KoShapeManager.cpp @@ -1,592 +1,593 @@ /* 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 "KoShapeManagerPaintingStrategy.h" #include "KoShapeShadow.h" #include "KoShapeLayer.h" #include "KoFilterEffect.h" #include "KoFilterEffectStack.h" #include "KoFilterEffectRenderContext.h" #include "KoShapeBackground.h" #include #include "KoClipPath.h" #include "KoShapePaintingContext.h" #include +#include #include #include #include void KoShapeManager::Private::updateTree() { // for detecting collisions between shapes. DetectCollision detector; bool selectionModified = false; bool anyModified = false; 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) { tree.remove(shape); QRectF br(shape->boundingRect()); strategy->adapt(shape, br); 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) { selection->updateSizeAndPosition(); 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(); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); 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(); strategy->paint(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() { foreach(KoShape *shape, d->shapes) { shape->priv()->removeShapeManager(this); } foreach(KoShape *shape, d->additionalShapes) { shape->priv()->removeShapeManager(this); } delete d; } void KoShapeManager::setShapes(const QList &shapes, Repaint repaint) { //clear selection d->selection->deselectAll(); foreach(KoShape *shape, d->shapes) { shape->priv()->removeShapeManager(this); } d->aggregate4update.clear(); d->tree.clear(); d->shapes.clear(); 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 (! dynamic_cast(shape) && ! dynamic_cast(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::addAdditional(KoShape *shape) { if (shape) { if (d->additionalShapes.contains(shape)) { return; } shape->priv()->addShapeManager(this); d->additionalShapes.append(shape); } } 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); 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); } } // This signal is used in the annotation shape. // FIXME: Is this really what we want? (and shouldn't it be called shapeDeleted()?) emit shapeRemoved(shape); } void KoShapeManager::removeAdditional(KoShape *shape) { if (shape) { shape->priv()->removeShapeManager(this); d->additionalShapes.removeAll(shape); } } 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(painter.clipRegion().boundingRect()); 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); } } std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); foreach (KoShape *shape, sortedShapes) { if (shape->parent() != 0 && shape->parent()->isClipped(shape)) continue; painter.save(); // apply shape clipping KoClipPath::applyClipping(shape, painter, converter); // let the painting strategy paint the shape KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME d->strategy->paint(shape, painter, converter, paintContext); painter.restore(); } #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::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()) { painter.save(); shape->paint(painter, converter, paintContext); painter.restore(); if (shape->stroke()) { painter.save(); shape->stroke()->paint(shape, painter, converter); painter.restore(); } } else { // 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); d->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 { QVector inputImages; 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)); 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) { d->updateTree(); QList intersectedShapes(d->tree.intersects(rect)); for (int count = intersectedShapes.count() - 1; count >= 0; count--) { KoShape *shape = intersectedShapes.at(count); if (omitHiddenShapes && ! shape->isVisible(true)) { intersectedShapes.removeAt(count); } else { const QPainterPath outline = shape->absoluteTransformation(0).map(shape->outline()); if (! outline.intersects(rect) && ! outline.contains(rect)) { intersectedShapes.removeAt(count); } } } return intersectedShapes; } void KoShapeManager::update(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) { 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 foreach(KoShape *shape, d->shapes) { if (shape->parent() == 0) { shapes.append(shape); } } return shapes; } KoSelection *KoShapeManager::selection() const { return d->selection; } void KoShapeManager::suggestChangeTool(KoPointerEvent *event) { QList shapes; KoShape *clicked = shapeAt(event->point); if (clicked) { if (! selection()->isSelected(clicked)) { selection()->deselectAll(); selection()->select(clicked); } shapes.append(clicked); } QList shapes2; foreach (KoShape *shape, shapes) { QSet delegates = shape->toolDelegates(); if (delegates.isEmpty()) { shapes2.append(shape); } else { foreach (KoShape *delegatedShape, delegates) { shapes2.append(delegatedShape); } } } KoToolManager::instance()->switchToolRequested( KoToolManager::instance()->preferredToolForSelection(shapes2)); } void KoShapeManager::setPaintingStrategy(KoShapeManagerPaintingStrategy *strategy) { delete d->strategy; d->strategy = strategy; } KoCanvasBase *KoShapeManager::canvas() { return d->canvas; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoShapeManager.cpp" diff --git a/libs/flake/KoShapeShadow.cpp b/libs/flake/KoShapeShadow.cpp index efe8ee6eab4..903198fa4c7 100644 --- a/libs/flake/KoShapeShadow.cpp +++ b/libs/flake/KoShapeShadow.cpp @@ -1,357 +1,358 @@ /* This file is part of the KDE project * Copyright (C) 2008-2009 Jan Hambrecht * Copyright (C) 2010 Thomas Zander * Copyright (C) 2010 Ariya Hidayat * Copyright (C) 2010-2011 Yue Liu * * 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 "KoShapeShadow.h" #include "KoShapeGroup.h" #include "KoSelection.h" #include "KoShapeSavingContext.h" #include "KoShapeStrokeModel.h" #include "KoShape.h" #include "KoInsets.h" #include "KoPathShape.h" #include #include #include #include +#include #include #include #include class Q_DECL_HIDDEN KoShapeShadow::Private { public: Private() : offset(2, 2), color(Qt::black), blur(8), visible(true), refCount(0) { } QPointF offset; QColor color; qreal blur; bool visible; QAtomicInt refCount; /** * Paints the shadow of the shape group to the buffer image. * @param group the shape group to paint around * @param painter the painter to paint on the image * @param converter to convert between internal and view coordinates. */ void paintGroupShadow(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter); /** * Paints the shadow of the shape to the buffer image. * @param shape the shape to paint around * @param painter the painter to paint on the image * @param converter to convert between internal and view coordinates. */ void paintShadow(KoShape *shape, QPainter &painter, const KoViewConverter &converter); void blurShadow(QImage &image, int radius, const QColor& shadowColor); }; void KoShapeShadow::Private::paintGroupShadow(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter) { QList shapes = group->shapes(); foreach(KoShape *child, shapes) { // we paint recursively here, so we do not have to check recursively for visibility if (!child->isVisible()) continue; painter.save(); //apply group child's transformation painter.setTransform(child->absoluteTransformation(&converter), true); paintShadow(child, painter, converter); painter.restore(); } } void KoShapeShadow::Private::paintShadow(KoShape *shape, QPainter &painter, const KoViewConverter &converter) { QPainterPath path(shape->shadowOutline()); if (!path.isEmpty()) { painter.save(); KoShape::applyConversion(painter, converter); painter.setBrush(QBrush(color)); // Make sure the shadow has the same fill rule as the shape. KoPathShape * pathShape = dynamic_cast(shape); if (pathShape) path.setFillRule(pathShape->fillRule()); painter.drawPath(path); painter.restore(); } if (shape->stroke()) { shape->stroke()->paint(shape, painter, converter); } } /* You can also find a BSD version to this method from * http://gitorious.org/ofi-labs/x2/blobs/master/graphics/shadowblur/ */ void KoShapeShadow::Private::blurShadow(QImage &image, int radius, const QColor& shadowColor) { static const int BlurSumShift = 15; // Check http://www.w3.org/TR/SVG/filters.html# // As noted in the SVG filter specification, ru // approximates a real gaussian blur nicely. // See comments in http://webkit.org/b/40793, it seems sensible // to follow Skia's limit of 128 pixels for the blur radius. if (radius > 128) radius = 128; int channels[4] = { 3, 0, 1, 3 }; int dmax = radius >> 1; int dmin = dmax - 1 + (radius & 1); if (dmin < 0) dmin = 0; // Two stages: horizontal and vertical for (int k = 0; k < 2; ++k) { unsigned char* pixels = image.bits(); int stride = (k == 0) ? 4 : image.bytesPerLine(); int delta = (k == 0) ? image.bytesPerLine() : 4; int jfinal = (k == 0) ? image.height() : image.width(); int dim = (k == 0) ? image.width() : image.height(); for (int j = 0; j < jfinal; ++j, pixels += delta) { // For each step, we blur the alpha in a channel and store the result // in another channel for the subsequent step. // We use sliding window algorithm to accumulate the alpha values. // This is much more efficient than computing the sum of each pixels // covered by the box kernel size for each x. for (int step = 0; step < 3; ++step) { int side1 = (step == 0) ? dmin : dmax; int side2 = (step == 1) ? dmin : dmax; int pixelCount = side1 + 1 + side2; int invCount = ((1 << BlurSumShift) + pixelCount - 1) / pixelCount; int ofs = 1 + side2; int alpha1 = pixels[channels[step]]; int alpha2 = pixels[(dim - 1) * stride + channels[step]]; unsigned char* ptr = pixels + channels[step + 1]; unsigned char* prev = pixels + stride + channels[step]; unsigned char* next = pixels + ofs * stride + channels[step]; int i; int sum = side1 * alpha1 + alpha1; int limit = (dim < side2 + 1) ? dim : side2 + 1; for (i = 1; i < limit; ++i, prev += stride) sum += *prev; if (limit <= side2) sum += (side2 - limit + 1) * alpha2; limit = (side1 < dim) ? side1 : dim; for (i = 0; i < limit; ptr += stride, next += stride, ++i, ++ofs) { *ptr = (sum * invCount) >> BlurSumShift; sum += ((ofs < dim) ? *next : alpha2) - alpha1; } prev = pixels + channels[step]; for (; ofs < dim; ptr += stride, prev += stride, next += stride, ++i, ++ofs) { *ptr = (sum * invCount) >> BlurSumShift; sum += (*next) - (*prev); } for (; i < dim; ptr += stride, prev += stride, ++i) { *ptr = (sum * invCount) >> BlurSumShift; sum += alpha2 - (*prev); } } } } // "Colorize" with the right shadow color. QPainter p(&image); p.setCompositionMode(QPainter::CompositionMode_SourceIn); p.fillRect(image.rect(), shadowColor); p.end(); } // ---------------------------------------------------------------- // KoShapeShadow KoShapeShadow::KoShapeShadow() : d(new Private()) { } KoShapeShadow::~KoShapeShadow() { delete d; } KoShapeShadow::KoShapeShadow(const KoShapeShadow &rhs) : d(new Private(*rhs.d)) { d->refCount = 0; } KoShapeShadow& KoShapeShadow::operator=(const KoShapeShadow &rhs) { *d = *rhs.d; d->refCount = 0; return *this; } void KoShapeShadow::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) { Q_UNUSED(context); style.addProperty("draw:shadow", d->visible ? "visible" : "hidden", KoGenStyle::GraphicType); style.addProperty("draw:shadow-color", d->color.name(), KoGenStyle::GraphicType); if (d->color.alphaF() != 1.0) style.addProperty("draw:shadow-opacity", QString("%1%").arg(d->color.alphaF() * 100.0), KoGenStyle::GraphicType); style.addProperty("draw:shadow-offset-x", QString("%1pt").arg(d->offset.x()), KoGenStyle::GraphicType); style.addProperty("draw:shadow-offset-y", QString("%1pt").arg(d->offset.y()), KoGenStyle::GraphicType); if (d->blur != 0) style.addProperty("calligra:shadow-blur-radius", QString("%1pt").arg(d->blur), KoGenStyle::GraphicType); } void KoShapeShadow::paint(KoShape *shape, QPainter &painter, const KoViewConverter &converter) { if (! d->visible) return; // So the approach we are taking here is to draw into a buffer image the size of boundingRect // We offset by the shadow offset at the time we draw into the buffer // Then we filter the image and draw it at the position of the bounding rect on canvas //the boundingRect of the shape or the KoSelection boundingRect of the group QRectF shadowRect = shape->boundingRect(); QRectF zoomedClipRegion = converter.documentToView(shadowRect); // Init the buffer image QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied); sourceGraphic.fill(qRgba(0,0,0,0)); // Init the buffer painter QPainter imagePainter(&sourceGraphic); imagePainter.setPen(Qt::NoPen); imagePainter.setBrush(Qt::NoBrush); imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing)); // Since our imagebuffer and the canvas don't align we need to offset our drawings imagePainter.translate(-1.0f*converter.documentToView(shadowRect.topLeft())); // Handle the shadow offset imagePainter.translate(converter.documentToView(offset())); KoShapeGroup *group = dynamic_cast(shape); if (group) { d->paintGroupShadow(group, imagePainter, converter); } else { //apply shape's transformation imagePainter.setTransform(shape->absoluteTransformation(&converter), true); d->paintShadow(shape, imagePainter, converter); } imagePainter.end(); // Blur the shadow (well the entire buffer) d->blurShadow(sourceGraphic, converter.documentToViewX(d->blur), d->color); // Paint the result painter.save(); // The painter is initialized for us with canvas transform 'plus' shape transform // we are only interested in the canvas transform so 'subtract' the shape transform part painter.setTransform(shape->absoluteTransformation(&converter).inverted() * painter.transform()); painter.drawImage(zoomedClipRegion.topLeft(), sourceGraphic); painter.restore(); } void KoShapeShadow::setOffset(const QPointF & offset) { d->offset = offset; } QPointF KoShapeShadow::offset() const { return d->offset; } void KoShapeShadow::setColor(const QColor &color) { d->color = color; } QColor KoShapeShadow::color() const { return d->color; } void KoShapeShadow::setBlur(qreal blur) { // force positive blur radius d->blur = qAbs(blur); } qreal KoShapeShadow::blur() const { return d->blur; } void KoShapeShadow::setVisible(bool visible) { d->visible = visible; } bool KoShapeShadow::isVisible() const { return d->visible; } void KoShapeShadow::insets(KoInsets &insets) const { if (!d->visible) { insets.top = 0; insets.bottom = 0; insets.left = 0; insets.right = 0; return; } qreal expand = d->blur; insets.left = (d->offset.x() < 0.0) ? qAbs(d->offset.x()) : 0.0; insets.top = (d->offset.y() < 0.0) ? qAbs(d->offset.y()) : 0.0; insets.right = (d->offset.x() > 0.0) ? d->offset.x() : 0.0; insets.bottom = (d->offset.y() > 0.0) ? d->offset.y() : 0.0; insets.left += expand; insets.top += expand; insets.right += expand; insets.bottom += expand; } bool KoShapeShadow::ref() { return d->refCount.ref(); } bool KoShapeShadow::deref() { return d->refCount.deref(); } int KoShapeShadow::useCount() const { return d->refCount; } diff --git a/libs/flake/KoSnapGuide.cpp b/libs/flake/KoSnapGuide.cpp index 4c80b3a2afc..5b16f8fd0f4 100644 --- a/libs/flake/KoSnapGuide.cpp +++ b/libs/flake/KoSnapGuide.cpp @@ -1,236 +1,237 @@ /* This file is part of the KDE project * Copyright (C) 2008-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 "KoSnapGuide.h" #include "KoSnapProxy.h" #include "KoSnapStrategy.h" #include #include #include #include #include +#include #include class Q_DECL_HIDDEN KoSnapGuide::Private { public: Private(KoCanvasBase *parentCanvas) : canvas(parentCanvas), editedShape(0), currentStrategy(0), active(true), snapDistance(10) { } ~Private() { qDeleteAll(strategies); strategies.clear(); } KoCanvasBase *canvas; KoShape *editedShape; QList strategies; KoSnapStrategy *currentStrategy; KoSnapGuide::Strategies usedStrategies; bool active; int snapDistance; QList ignoredPoints; QList ignoredShapes; }; KoSnapGuide::KoSnapGuide(KoCanvasBase *canvas) : d(new Private(canvas)) { d->strategies.append(new GridSnapStrategy()); d->strategies.append(new NodeSnapStrategy()); d->strategies.append(new OrthogonalSnapStrategy()); d->strategies.append(new ExtensionSnapStrategy()); d->strategies.append(new IntersectionSnapStrategy()); d->strategies.append(new BoundingBoxSnapStrategy()); d->strategies.append(new LineGuideSnapStrategy()); } KoSnapGuide::~KoSnapGuide() { delete d; } void KoSnapGuide::setEditedShape(KoShape *shape) { d->editedShape = shape; } KoShape *KoSnapGuide::editedShape() const { return d->editedShape; } void KoSnapGuide::enableSnapStrategies(Strategies strategies) { d->usedStrategies = strategies; } KoSnapGuide::Strategies KoSnapGuide::enabledSnapStrategies() const { return d->usedStrategies; } bool KoSnapGuide::addCustomSnapStrategy(KoSnapStrategy *customStrategy) { if (!customStrategy || customStrategy->type() != CustomSnapping) return false; d->strategies.append(customStrategy); return true; } void KoSnapGuide::enableSnapping(bool on) { d->active = on; } bool KoSnapGuide::isSnapping() const { return d->active; } void KoSnapGuide::setSnapDistance(int distance) { d->snapDistance = qAbs(distance); } int KoSnapGuide::snapDistance() const { return d->snapDistance; } QPointF KoSnapGuide::snap(const QPointF &mousePosition, Qt::KeyboardModifiers modifiers) { d->currentStrategy = 0; if (! d->active || (modifiers & Qt::ShiftModifier)) return mousePosition; KoSnapProxy proxy(this); qreal minDistance = HUGE_VAL; qreal maxSnapDistance = d->canvas->viewConverter()->viewToDocument(QSizeF(d->snapDistance, d->snapDistance)).width(); foreach (KoSnapStrategy *strategy, d->strategies) { if (d->usedStrategies & strategy->type() || strategy->type() == GridSnapping || strategy->type() == CustomSnapping) { if (! strategy->snap(mousePosition, &proxy, maxSnapDistance)) continue; QPointF snapCandidate = strategy->snappedPosition(); qreal distance = KoSnapStrategy::squareDistance(snapCandidate, mousePosition); if (distance < minDistance) { d->currentStrategy = strategy; minDistance = distance; } } } if (! d->currentStrategy) return mousePosition; return d->currentStrategy->snappedPosition(); } QRectF KoSnapGuide::boundingRect() const { QRectF rect; if (d->currentStrategy) { rect = d->currentStrategy->decoration(*d->canvas->viewConverter()).boundingRect(); return rect.adjusted(-2, -2, 2, 2); } else { return rect; } } void KoSnapGuide::paint(QPainter &painter, const KoViewConverter &converter) { if (! d->currentStrategy || ! d->active) return; QPainterPath decoration = d->currentStrategy->decoration(converter); painter.setBrush(Qt::NoBrush); QPen whitePen(Qt::white, 0); whitePen.setStyle(Qt::SolidLine); painter.setPen(whitePen); painter.drawPath(decoration); QPen redPen(Qt::red, 0); redPen.setStyle(Qt::DotLine); painter.setPen(redPen); painter.drawPath(decoration); } KoCanvasBase *KoSnapGuide::canvas() const { return d->canvas; } void KoSnapGuide::setIgnoredPathPoints(const QList &ignoredPoints) { d->ignoredPoints = ignoredPoints; } QList KoSnapGuide::ignoredPathPoints() const { return d->ignoredPoints; } void KoSnapGuide::setIgnoredShapes(const QList &ignoredShapes) { d->ignoredShapes = ignoredShapes; } QList KoSnapGuide::ignoredShapes() const { return d->ignoredShapes; } void KoSnapGuide::reset() { d->currentStrategy = 0; d->editedShape = 0; d->ignoredPoints.clear(); d->ignoredShapes.clear(); // remove all custom strategies int strategyCount = d->strategies.count(); for (int i = strategyCount-1; i >= 0; --i) { if (d->strategies[i]->type() == CustomSnapping) { delete d->strategies[i]; d->strategies.removeAt(i); } } } diff --git a/libs/flake/KoSnapStrategy.cpp b/libs/flake/KoSnapStrategy.cpp index 9bfbea3d81c..8b1120b808d 100644 --- a/libs/flake/KoSnapStrategy.cpp +++ b/libs/flake/KoSnapStrategy.cpp @@ -1,634 +1,635 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoSnapStrategy.h" #include "KoSnapProxy.h" #include "KoSnapGuide.h" #include #include #include #include #include #include #include +#include #include KoSnapStrategy::KoSnapStrategy(KoSnapGuide::Strategy type) : m_snapType(type) { } QPointF KoSnapStrategy::snappedPosition() const { return m_snappedPosition; } void KoSnapStrategy::setSnappedPosition(const QPointF &position) { m_snappedPosition = position; } KoSnapGuide::Strategy KoSnapStrategy::type() const { return m_snapType; } qreal KoSnapStrategy::squareDistance(const QPointF &p1, const QPointF &p2) { const qreal dx = p1.x() - p2.x(); const qreal dy = p1.y() - p2.y(); return dx*dx + dy*dy; } qreal KoSnapStrategy::scalarProduct(const QPointF &p1, const QPointF &p2) { return p1.x() * p2.x() + p1.y() * p2.y(); } OrthogonalSnapStrategy::OrthogonalSnapStrategy() : KoSnapStrategy(KoSnapGuide::OrthogonalSnapping) { } bool OrthogonalSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance) { Q_ASSERT(std::isfinite(maxSnapDistance)); QPointF horzSnap, vertSnap; qreal minVertDist = HUGE_VAL; qreal minHorzDist = HUGE_VAL; QList shapes = proxy->shapes(); for (KoShape * shape : shapes) { QVector points = proxy->pointsFromShape(shape); // There exists a problem on msvc with for(each) and QVector for (int i = 0; i < points.count(); ++i) { const QPointF point(points[i]); qreal dx = fabs(point.x() - mousePosition.x()); if (dx < minHorzDist && dx < maxSnapDistance) { minHorzDist = dx; horzSnap = point; } qreal dy = fabs(point.y() - mousePosition.y()); if (dy < minVertDist && dy < maxSnapDistance) { minVertDist = dy; vertSnap = point; } } } QPointF snappedPoint = mousePosition; if (minHorzDist < HUGE_VAL) snappedPoint.setX(horzSnap.x()); if (minVertDist < HUGE_VAL) snappedPoint.setY(vertSnap.y()); if (minHorzDist < HUGE_VAL) m_hLine = QLineF(horzSnap, snappedPoint); else m_hLine = QLineF(); if (minVertDist < HUGE_VAL) m_vLine = QLineF(vertSnap, snappedPoint); else m_vLine = QLineF(); setSnappedPosition(snappedPoint); return (minHorzDist < HUGE_VAL || minVertDist < HUGE_VAL); } QPainterPath OrthogonalSnapStrategy::decoration(const KoViewConverter &/*converter*/) const { QPainterPath decoration; if (! m_hLine.isNull()) { decoration.moveTo(m_hLine.p1()); decoration.lineTo(m_hLine.p2()); } if (! m_vLine.isNull()) { decoration.moveTo(m_vLine.p1()); decoration.lineTo(m_vLine.p2()); } return decoration; } NodeSnapStrategy::NodeSnapStrategy() : KoSnapStrategy(KoSnapGuide::NodeSnapping) { } bool NodeSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance) { Q_ASSERT(std::isfinite(maxSnapDistance)); const qreal maxDistance = maxSnapDistance * maxSnapDistance; qreal minDistance = HUGE_VAL; QRectF rect(-maxSnapDistance, -maxSnapDistance, maxSnapDistance, maxSnapDistance); rect.moveCenter(mousePosition); QVector points = proxy->pointsInRect(rect); QPointF snappedPoint = mousePosition; // There exists a problem on msvc with for(each) and QVector for (int i = 0; i < points.count(); ++i) { const QPointF point(points[i]); qreal distance = squareDistance(mousePosition, point); if (distance < maxDistance && distance < minDistance) { snappedPoint = point; minDistance = distance; } } setSnappedPosition(snappedPoint); return (minDistance < HUGE_VAL); } QPainterPath NodeSnapStrategy::decoration(const KoViewConverter &converter) const { QRectF unzoomedRect = converter.viewToDocument(QRectF(0, 0, 11, 11)); unzoomedRect.moveCenter(snappedPosition()); QPainterPath decoration; decoration.addEllipse(unzoomedRect); return decoration; } ExtensionSnapStrategy::ExtensionSnapStrategy() : KoSnapStrategy(KoSnapGuide::ExtensionSnapping) { } bool ExtensionSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance) { Q_ASSERT(std::isfinite(maxSnapDistance)); const qreal maxDistance = maxSnapDistance * maxSnapDistance; // NOTE: HUGE_VAL with qreal, which can be either float or double, // is not necessarily ideal, but seems to work good enough qreal minDistances[2] = { (qreal)HUGE_VAL, (qreal)HUGE_VAL }; QPointF snappedPoints[2] = { mousePosition, mousePosition }; QPointF startPoints[2]; QList shapes = proxy->shapes(true); for (KoShape * shape : shapes) { KoPathShape * path = dynamic_cast(shape); if (! path) { continue; } QTransform matrix = path->absoluteTransformation(0); const int subpathCount = path->subpathCount(); for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) { if (path->isClosedSubpath(subpathIndex)) continue; int pointCount = path->subpathPointCount(subpathIndex); // check the extension from the start point KoPathPoint * first = path->pointByIndex(KoPathPointIndex(subpathIndex, 0)); QPointF firstSnapPosition = mousePosition; if (snapToExtension(firstSnapPosition, first, matrix)) { qreal distance = squareDistance(firstSnapPosition, mousePosition); if (distance < maxDistance) { if (distance < minDistances[0]) { minDistances[1] = minDistances[0]; snappedPoints[1] = snappedPoints[0]; startPoints[1] = startPoints[0]; minDistances[0] = distance; snappedPoints[0] = firstSnapPosition; startPoints[0] = matrix.map(first->point()); } else if (distance < minDistances[1]) { minDistances[1] = distance; snappedPoints[1] = firstSnapPosition; startPoints[1] = matrix.map(first->point()); } } } // now check the extension from the last point KoPathPoint * last = path->pointByIndex(KoPathPointIndex(subpathIndex, pointCount - 1)); QPointF lastSnapPosition = mousePosition; if (snapToExtension(lastSnapPosition, last, matrix)) { qreal distance = squareDistance(lastSnapPosition, mousePosition); if (distance < maxDistance) { if (distance < minDistances[0]) { minDistances[1] = minDistances[0]; snappedPoints[1] = snappedPoints[0]; startPoints[1] = startPoints[0]; minDistances[0] = distance; snappedPoints[0] = lastSnapPosition; startPoints[0] = matrix.map(last->point()); } else if (distance < minDistances[1]) { minDistances[1] = distance; snappedPoints[1] = lastSnapPosition; startPoints[1] = matrix.map(last->point()); } } } } } m_lines.clear(); // if we have to extension near our mouse position, they might have an intersection // near our mouse position which we want to use as the snapped position if (minDistances[0] < HUGE_VAL && minDistances[1] < HUGE_VAL) { // check if intersection of extension lines is near mouse position KoPathSegment s1(startPoints[0], snappedPoints[0] + snappedPoints[0]-startPoints[0]); KoPathSegment s2(startPoints[1], snappedPoints[1] + snappedPoints[1]-startPoints[1]); QVector isects = s1.intersections(s2); if (isects.count() == 1 && squareDistance(isects[0], mousePosition) < maxDistance) { // add both extension lines m_lines.append(QLineF(startPoints[0], isects[0])); m_lines.append(QLineF(startPoints[1], isects[0])); setSnappedPosition(isects[0]); } else { // only add nearest extension line of both uint index = minDistances[0] < minDistances[1] ? 0 : 1; m_lines.append(QLineF(startPoints[index], snappedPoints[index])); setSnappedPosition(snappedPoints[index]); } } else if (minDistances[0] < HUGE_VAL) { m_lines.append(QLineF(startPoints[0], snappedPoints[0])); setSnappedPosition(snappedPoints[0]); } else if (minDistances[1] < HUGE_VAL) { m_lines.append(QLineF(startPoints[1], snappedPoints[1])); setSnappedPosition(snappedPoints[1]); } else { // none of the extension lines is near our mouse position return false; } return true; } QPainterPath ExtensionSnapStrategy::decoration(const KoViewConverter &/*converter*/) const { QPainterPath decoration; for (const QLineF &line : m_lines) { decoration.moveTo(line.p1()); decoration.lineTo(line.p2()); } return decoration; } bool ExtensionSnapStrategy::snapToExtension(QPointF &position, KoPathPoint * point, const QTransform &matrix) { Q_ASSERT(point); QPointF direction = extensionDirection(point, matrix); if (direction.isNull()) return false; QPointF extensionStart = matrix.map(point->point()); QPointF extensionStop = matrix.map(point->point()) + direction; float posOnExtension = project(extensionStart, extensionStop, position); if (posOnExtension < 0.0) return false; position = extensionStart + posOnExtension * direction; return true; } qreal ExtensionSnapStrategy::project(const QPointF &lineStart, const QPointF &lineEnd, const QPointF &point) { // This is how the returned value should be used to get the // projectionPoint: ProjectionPoint = lineStart(1-resultingReal) + resultingReal*lineEnd; QPointF diff = lineEnd - lineStart; QPointF relPoint = point - lineStart; qreal diffLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y()); if (diffLength == 0.0) return 0.0; diff /= diffLength; // project mouse position relative to stop position on extension line qreal scalar = relPoint.x() * diff.x() + relPoint.y() * diff.y(); return scalar /= diffLength; } QPointF ExtensionSnapStrategy::extensionDirection(KoPathPoint * point, const QTransform &matrix) { Q_ASSERT(point); KoPathShape * path = point->parent(); KoPathPointIndex index = path->pathPointIndex(point); // check if it is a start point if (point->properties() & KoPathPoint::StartSubpath) { if (point->activeControlPoint2()) { return matrix.map(point->point()) - matrix.map(point->controlPoint2()); } else { KoPathPoint * next = path->pointByIndex(KoPathPointIndex(index.first, index.second + 1)); if (! next){ return QPointF(); } else if (next->activeControlPoint1()) { return matrix.map(point->point()) - matrix.map(next->controlPoint1()); } else { return matrix.map(point->point()) - matrix.map(next->point()); } } } else { if (point->activeControlPoint1()) { return matrix.map(point->point()) - matrix.map(point->controlPoint1()); } else { KoPathPoint * prev = path->pointByIndex(KoPathPointIndex(index.first, index.second - 1)); if (! prev){ return QPointF(); } else if (prev->activeControlPoint2()) { return matrix.map(point->point()) - matrix.map(prev->controlPoint2()); } else { return matrix.map(point->point()) - matrix.map(prev->point()); } } } } IntersectionSnapStrategy::IntersectionSnapStrategy() : KoSnapStrategy(KoSnapGuide::IntersectionSnapping) { } bool IntersectionSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy *proxy, qreal maxSnapDistance) { Q_ASSERT(std::isfinite(maxSnapDistance)); const qreal maxDistance = maxSnapDistance * maxSnapDistance; qreal minDistance = HUGE_VAL; QRectF rect(-maxSnapDistance, -maxSnapDistance, maxSnapDistance, maxSnapDistance); rect.moveCenter(mousePosition); QPointF snappedPoint = mousePosition; QList segments = proxy->segmentsInRect(rect); int segmentCount = segments.count(); for (int i = 0; i < segmentCount; ++i) { const KoPathSegment &s1 = segments[i]; for (int j = i + 1; j < segmentCount; ++j) { QVector isects = s1.intersections(segments[j]); // There exists a problem on msvc with for(each) and QVector for (int a = 0; a < isects.count(); ++a) { const QPointF& point(isects[a]); if (! rect.contains(point)) continue; qreal distance = squareDistance(mousePosition, point); if (distance < maxDistance && distance < minDistance) { snappedPoint = point; minDistance = distance; } } } } setSnappedPosition(snappedPoint); return (minDistance < HUGE_VAL); } QPainterPath IntersectionSnapStrategy::decoration(const KoViewConverter &converter) const { QRectF unzoomedRect = converter.viewToDocument(QRectF(0, 0, 11, 11)); unzoomedRect.moveCenter(snappedPosition()); QPainterPath decoration; decoration.addRect(unzoomedRect); return decoration; } GridSnapStrategy::GridSnapStrategy() : KoSnapStrategy(KoSnapGuide::GridSnapping) { } bool GridSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy *proxy, qreal maxSnapDistance) { Q_ASSERT(std::isfinite(maxSnapDistance)); if (! proxy->canvas()->snapToGrid()) return false; // The 1e-10 here is a workaround for some weird division problem. // 360.00062366 / 2.83465058 gives 127 'exactly' when shown as a qreal, // but when casting into an int, we get 126. In fact it's 127 - 5.64e-15 ! qreal gridX, gridY; proxy->canvas()->gridSize(&gridX, &gridY); // we want to snap to the nearest grid point, so calculate // the grid rows/columns before and after the points position int col = static_cast(mousePosition.x() / gridX + 1e-10); int nextCol = col + 1; int row = static_cast(mousePosition.y() / gridY + 1e-10); int nextRow = row + 1; // now check which grid line has less distance to the point qreal distToCol = qAbs(col * gridX - mousePosition.x()); qreal distToNextCol = qAbs(nextCol * gridX - mousePosition.x()); if (distToCol > distToNextCol) { col = nextCol; distToCol = distToNextCol; } qreal distToRow = qAbs(row * gridY - mousePosition.y()); qreal distToNextRow = qAbs(nextRow * gridY - mousePosition.y()); if (distToRow > distToNextRow) { row = nextRow; distToRow = distToNextRow; } QPointF snappedPoint = mousePosition; const qreal distance = distToCol * distToCol + distToRow * distToRow; const qreal maxDistance = maxSnapDistance * maxSnapDistance; // now check if we are inside the snap distance if (distance < maxDistance) { snappedPoint = QPointF(col * gridX, row * gridY); } setSnappedPosition(snappedPoint); return (distance < maxDistance); } QPainterPath GridSnapStrategy::decoration(const KoViewConverter &converter) const { QSizeF unzoomedSize = converter.viewToDocument(QSizeF(5, 5)); QPainterPath decoration; decoration.moveTo(snappedPosition() - QPointF(unzoomedSize.width(), 0)); decoration.lineTo(snappedPosition() + QPointF(unzoomedSize.width(), 0)); decoration.moveTo(snappedPosition() - QPointF(0, unzoomedSize.height())); decoration.lineTo(snappedPosition() + QPointF(0, unzoomedSize.height())); return decoration; } BoundingBoxSnapStrategy::BoundingBoxSnapStrategy() : KoSnapStrategy(KoSnapGuide::BoundingBoxSnapping) { } bool BoundingBoxSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy *proxy, qreal maxSnapDistance) { Q_ASSERT(std::isfinite(maxSnapDistance)); const qreal maxDistance = maxSnapDistance * maxSnapDistance; qreal minDistance = HUGE_VAL; QRectF rect(-maxSnapDistance, -maxSnapDistance, maxSnapDistance, maxSnapDistance); rect.moveCenter(mousePosition); QPointF snappedPoint = mousePosition; KoFlake::Position pointId[5] = { KoFlake::TopLeftCorner, KoFlake::TopRightCorner, KoFlake::BottomRightCorner, KoFlake::BottomLeftCorner, KoFlake::CenteredPosition }; QList shapes = proxy->shapesInRect(rect, true); for (KoShape * shape : shapes) { qreal shapeMinDistance = HUGE_VAL; // first check the corner and center points for (int i = 0; i < 5; ++i) { m_boxPoints[i] = shape->absolutePosition(pointId[i]); qreal d = squareDistance(mousePosition, m_boxPoints[i]); if (d < minDistance && d < maxDistance) { shapeMinDistance = d; minDistance = d; snappedPoint = m_boxPoints[i]; } } // prioritize points over edges if (shapeMinDistance < maxDistance) continue; // now check distances to edges of bounding box for (int i = 0; i < 4; ++i) { QPointF pointOnLine; qreal d = squareDistanceToLine(m_boxPoints[i], m_boxPoints[(i+1)%4], mousePosition, pointOnLine); if (d < minDistance && d < maxDistance) { minDistance = d; snappedPoint = pointOnLine; } } } setSnappedPosition(snappedPoint); return (minDistance < maxDistance); } qreal BoundingBoxSnapStrategy::squareDistanceToLine(const QPointF &lineA, const QPointF &lineB, const QPointF &point, QPointF &pointOnLine) { QPointF diff = lineB - lineA; if(lineA == lineB) return HUGE_VAL; const qreal diffLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y()); // project mouse position relative to start position on line const qreal scalar = KoSnapStrategy::scalarProduct(point - lineA, diff / diffLength); if (scalar < 0.0 || scalar > diffLength) return HUGE_VAL; // calculate vector between relative mouse position and projected mouse position pointOnLine = lineA + scalar / diffLength * diff; QPointF distVec = pointOnLine - point; return distVec.x()*distVec.x() + distVec.y()*distVec.y(); } QPainterPath BoundingBoxSnapStrategy::decoration(const KoViewConverter &converter) const { QSizeF unzoomedSize = converter.viewToDocument(QSizeF(5, 5)); QPainterPath decoration; decoration.moveTo(snappedPosition() - QPointF(unzoomedSize.width(), unzoomedSize.height())); decoration.lineTo(snappedPosition() + QPointF(unzoomedSize.width(), unzoomedSize.height())); decoration.moveTo(snappedPosition() - QPointF(unzoomedSize.width(), -unzoomedSize.height())); decoration.lineTo(snappedPosition() + QPointF(unzoomedSize.width(), -unzoomedSize.height())); return decoration; } LineGuideSnapStrategy::LineGuideSnapStrategy() : KoSnapStrategy(KoSnapGuide::GuideLineSnapping) { } bool LineGuideSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance) { Q_ASSERT(std::isfinite(maxSnapDistance)); KoGuidesData * guidesData = proxy->canvas()->guidesData(); if (!guidesData || !guidesData->showGuideLines()) return false; QPointF snappedPoint = mousePosition; m_orientation = 0; qreal minHorzDistance = maxSnapDistance; for (qreal guidePos : guidesData->horizontalGuideLines()) { qreal distance = qAbs(guidePos - mousePosition.y()); if (distance < minHorzDistance) { snappedPoint.setY(guidePos); minHorzDistance = distance; m_orientation |= Qt::Horizontal; } } qreal minVertSnapDistance = maxSnapDistance; for (qreal guidePos : guidesData->verticalGuideLines()) { qreal distance = qAbs(guidePos - mousePosition.x()); if (distance < minVertSnapDistance) { snappedPoint.setX(guidePos); minVertSnapDistance = distance; m_orientation |= Qt::Vertical; } } setSnappedPosition(snappedPoint); return (minHorzDistance < maxSnapDistance || minVertSnapDistance < maxSnapDistance); } QPainterPath LineGuideSnapStrategy::decoration(const KoViewConverter &converter) const { QSizeF unzoomedSize = converter.viewToDocument(QSizeF(5, 5)); Q_ASSERT(unzoomedSize.isValid()); QPainterPath decoration; if (m_orientation & Qt::Horizontal) { decoration.moveTo(snappedPosition() - QPointF(unzoomedSize.width(), 0)); decoration.lineTo(snappedPosition() + QPointF(unzoomedSize.width(), 0)); } if (m_orientation & Qt::Vertical) { decoration.moveTo(snappedPosition() - QPointF(0, unzoomedSize.height())); decoration.lineTo(snappedPosition() + QPointF(0, unzoomedSize.height())); } return decoration; } diff --git a/libs/flake/KoUnavailShape.cpp b/libs/flake/KoUnavailShape.cpp index 6b39d333ae0..391e8a22262 100644 --- a/libs/flake/KoUnavailShape.cpp +++ b/libs/flake/KoUnavailShape.cpp @@ -1,656 +1,657 @@ /* This file is part of the KDE project * * Copyright (C) 2010-2011 Inge Wallin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // Own #include "KoUnavailShape.h" // Qt #include #include +#include #include #include #include #include #include #include #include // Calligra #include #include #include #include #include #include #include #include #include "KoShapeLoadingContext.h" #include "KoShapeSavingContext.h" #include "KoShapeContainerDefaultModel.h" #include "KoShapeBackground.h" #include // The XML of a frame looks something like this: // // 1. // 2. // 3. // 4. // // or // // 1. // 2. ...inline xml here... // 3. // 4. // // We define each Xml statement on lines 2 and 3 above as an "object". // (Strictly only the first child element is an object in the ODF sense, // but we have to have some terminology here.) // // In an ODF frame, only the first line, i.e. the first object // contains the real contents. All the rest of the objects are used / // shown if we cannot handle the first one. The most common cases are // that there is only one object inside the frame OR that there are 2 // and the 2nd is a picture. // // Sometimes, e.g. in the case of an embedded document, the reference // points not to a file but to a directory structure inside the ODF // store. // // When we load and save in the UnavailShape, we have to be general // enough to cover all possible cases of references and inline XML, // embedded files and embedded directory structures. // // We also have to be careful because we cannot reuse the object names // that are in the original files when saving. Instead we need to // create new object names because the ones that were used in the // original file may already be used by other embedded files/objects // that are saved by other shapes. // // FIXME: There should only be ONE place where new object / file names // are generated, not 2(?) like there are now: // KoEmbeddedDocumentSaver and the KoImageCollection. // // An ObjectEntry is used to store information about objects in the // frame, as defined above. struct ObjectEntry { QByteArray objectXmlContents; // the XML tree in the object QString objectName; // object name in the frame without "./" // This is extracted from objectXmlContents. bool isDir; KoOdfManifestEntry *manifestEntry; // manifest entry for the above. }; // A FileEntry is used to store information about embedded files // inside (i.e. referred to by) an object. struct FileEntry { QString path; // Normalized filename, i.e. without "./". QString mimeType; bool isDir; QByteArray contents; }; class KoUnavailShape::Private { public: Private(KoUnavailShape* qq); ~Private(); void draw(QPainter &painter) const; void drawNull(QPainter &painter) const; void storeObjects(const KoXmlElement &element); void storeXmlRecursive(const KoXmlElement &el, KoXmlWriter &writer, ObjectEntry *object, QHash &unknownNamespaces); void storeFile(const QString &filename, KoShapeLoadingContext &context); QByteArray loadFile(const QString &filename, KoShapeLoadingContext &context); // Objects inside the frame. For each file, we store: // - The XML code for the object // - Any embedded files (names, contents) that are referenced by xlink:href // - Whether they are directories, i.e. if they contain a file tree and not just one file. // - The manifest entries QList objectEntries; // Embedded files QList embeddedFiles; // List of embedded files. // Some cached values. QPixmap questionMark; QPixmap pixmapPreview; QSvgRenderer *scalablePreview; KoUnavailShape* q; }; KoUnavailShape::Private::Private(KoUnavailShape* qq) : scalablePreview(new QSvgRenderer()) , q(qq) { // Get the question mark "icon". questionMark.load(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "calligra/pics/questionmark.png")); } KoUnavailShape::Private::~Private() { qDeleteAll(objectEntries); qDeleteAll(embeddedFiles); // It's a QObject, but we haven't parented it. delete(scalablePreview); } // ---------------------------------------------------------------- // The main class KoUnavailShape::KoUnavailShape() : KoFrameShape( "", "" ) , KoShapeContainer(new KoShapeContainerDefaultModel()) , d(new Private(this)) { setShapeId(KoUnavailShape_SHAPEID); // Default size of the shape. KoShape::setSize( QSizeF( CM_TO_POINT( 5 ), CM_TO_POINT( 3 ) ) ); } KoUnavailShape::~KoUnavailShape() { delete d; } void KoUnavailShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { applyConversion(painter, converter); // If the frame is empty, just draw a background. debugFlake << "Number of objects:" << d->objectEntries.size(); if (d->objectEntries.isEmpty()) { // But... only try to draw the background if there's one such if (background()) { QPainterPath p; p.addRect(QRectF(QPointF(), size())); background()->paint(painter, converter, paintContext, p); } } else { if(shapes().isEmpty()) { d->draw(painter); } } } void KoUnavailShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); } void KoUnavailShape::Private::draw(QPainter &painter) const { painter.save(); painter.setRenderHint(QPainter::Antialiasing); // Run through the previews in order of preference. Draw a placeholder // questionmark if there is no preview available for rendering. if (scalablePreview->isValid()) { QRect bounds(0, 0, q->boundingRect().width(), q->boundingRect().height()); scalablePreview->render(&painter, bounds); } else if (!pixmapPreview.isNull()) { QRect bounds(0, 0, q->boundingRect().width(), q->boundingRect().height()); painter.setRenderHint(QPainter::SmoothPixmapTransform); painter.drawPixmap(bounds, pixmapPreview); } else if (q->shapes().isEmpty()) { // Draw a nice question mark with a frame around it if there // is no other preview image. If there is a contained image // shape, we don't need to draw anything. // Get the question mark "icon". // FIXME: We should be able to use d->questionMark here. QPixmap questionMark; questionMark.load(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "calligra/pics/questionmark.png")); // The size of the image is: // - the size of the shape if shapesize < 2cm // - 2 cm if 2cm <= shapesize <= 8cm // - shapesize / 4 if shapesize > 8cm qreal width = q->size().width(); qreal height = q->size().height(); qreal picSize = CM_TO_POINT(2); // Default size is 2 cm. if (width < CM_TO_POINT(2) || height < CM_TO_POINT(2)) picSize = qMin(width, height); else if (width > CM_TO_POINT(8) && height > CM_TO_POINT(8)) picSize = qMin(width, height) / qreal(4.0); painter.drawPixmap((width - picSize) / qreal(2.0), (height - picSize) / qreal(2.0), picSize, picSize, questionMark); // Draw a gray rectangle around the shape. painter.setPen(QPen(QColor(172, 196, 206), 0)); painter.drawRect(QRectF(QPointF(0,0), q->size())); } painter.restore(); } void KoUnavailShape::Private::drawNull(QPainter &painter) const { QRectF rect(QPointF(0,0), q->size()); painter.save(); // Draw a simple cross in a rectangle just to indicate that there is something here. painter.drawLine(rect.topLeft(), rect.bottomRight()); painter.drawLine(rect.bottomLeft(), rect.topRight()); painter.restore(); } // ---------------------------------------------------------------- // Loading and Saving void KoUnavailShape::saveOdf(KoShapeSavingContext & context) const { debugFlake << "START SAVING ##################################################"; KoEmbeddedDocumentSaver &fileSaver = context.embeddedSaver(); KoXmlWriter &writer = context.xmlWriter(); writer.startElement("draw:frame"); // See also loadOdf() in loadOdfAttributes. saveOdfAttributes( context, OdfAllAttributes ); // Write the stored XML to the file, but don't reuse object names. int lap = 0; QString newName; foreach (const ObjectEntry *object, d->objectEntries) { QByteArray xmlArray(object->objectXmlContents); QString objectName(object->objectName); // Possibly empty. KoOdfManifestEntry *manifestEntry(object->manifestEntry); // Create a name for this object. If this is not the first // object, i.e. a replacement object (most likely a picture), // then reuse the name but put it in ReplacementObjects. if (++lap == 1) { // The first lap in the loop is the actual object. All // other laps are replacement objects. newName = fileSaver.getFilename("Object "); } else if (lap == 2) { newName = "ObjectReplacements/" + newName; } else // FIXME: what should replacement 2 and onwards be called? newName = newName + "_"; // If there was a previous object name, replace it with the new one. if (!objectName.isEmpty() && manifestEntry) { // FIXME: We must make a copy of the byte array here because // otherwise we won't be able to save > 1 time. xmlArray.replace(objectName.toLatin1(), newName.toLatin1()); } writer.addCompleteElement(xmlArray.data()); // If the objectName is empty, this may be inline XML. // If so, we are done now. if (objectName.isEmpty() || !manifestEntry) { continue; } // Save embedded files for this object. foreach (FileEntry *entry, d->embeddedFiles) { QString fileName(entry->path); // If we found a file for this object, we need to write it // but with the new object name instead of the old one. if (!fileName.startsWith(objectName)) continue; debugFlake << "Object name: " << objectName << "newName: " << newName << "filename: " << fileName << "isDir: " << entry->isDir; fileName.replace(objectName, newName); fileName.prepend("./"); debugFlake << "New filename: " << fileName; // FIXME: Check if we need special treatment of directories. fileSaver.saveFile(fileName, entry->mimeType.toLatin1(), entry->contents); } // Write the manifest entry for the object itself. If it's a // file, the manifest is already written by saveFile, so skip // it here. if (object->isDir) { fileSaver.saveManifestEntry(newName + '/', manifestEntry->mediaType(), manifestEntry->version()); } } writer.endElement(); // draw:frame } bool KoUnavailShape::loadOdf(const KoXmlElement &frameElement, KoShapeLoadingContext &context) { debugFlake << "START LOADING ##################################################"; //debugFlake << "Loading ODF frame in the KoUnavailShape. Element = " // << frameElement.tagName(); loadOdfAttributes(frameElement, context, OdfAllAttributes); // NOTE: We cannot use loadOdfFrame() because we want to save all // the things inside the frame, not just one of them, like // loadOdfFrame() provides. // Get the manifest. QList manifest = context.odfLoadingContext().manifestEntries(); #if 0 // Enable to show all manifest entries. debugFlake << "MANIFEST: "; foreach (KoOdfManifestEntry *entry, manifest) { debugFlake << entry->mediaType() << entry->fullPath() << entry->version(); } #endif // 1. Get the XML contents of the objects from the draw:frame. As // a side effect, this extracts the object names from all // xlink:href and stores them into d->objectNames. The saved // xml contents itself is saved into d->objectXmlContents // (QByteArray) so we can save it back from saveOdf(). d->storeObjects(frameElement); #if 1 // Debug only debugFlake << "----------------------------------------------------------------"; debugFlake << "After storeObjects():"; foreach (ObjectEntry *object, d->objectEntries) { debugFlake << "objectXmlContents: " << object->objectXmlContents << "objectName: " << object->objectName; // Note: at this point, isDir and manifestEntry are not set. #endif } // 2. Loop through the objects that were found in the frame and // save all the files associated with them. Some of the // objects are files, and some are directories. The // directories are searched and the files within are saved as // well. // // In this loop, isDir and manifestEntry of each ObjectEntry are set. bool foundPreview = false; foreach (ObjectEntry *object, d->objectEntries) { QString objectName = object->objectName; if (objectName.isEmpty()) continue; debugFlake << "Storing files for object named:" << objectName; // Try to find out if the entry is a directory. // If the object is a directory, then save all the files // inside it, otherwise save the file as it is. QString dirName = objectName + '/'; bool isDir = !context.odfLoadingContext().mimeTypeForPath(dirName).isEmpty(); if (isDir) { // A directory: the files can be found in the manifest. foreach (KoOdfManifestEntry *entry, manifest) { if (entry->fullPath() == dirName) continue; if (entry->fullPath().startsWith(dirName)) { d->storeFile(entry->fullPath(), context); } } } else { // A file: save it. d->storeFile(objectName, context); } // Get the manifest entry for this object. KoOdfManifestEntry *entry = 0; QString entryName = isDir ? dirName : objectName; for (int j = 0; j < manifest.size(); ++j) { KoOdfManifestEntry *temp = manifest.value(j); if (temp->fullPath() == entryName) { entry = new KoOdfManifestEntry(*temp); break; } } object->isDir = isDir; object->manifestEntry = entry; // If we have not already found a preview in previous times // through the loop, then see if this one may be a preview. if (!foundPreview) { debugFlake << "Attempting to load preview from " << objectName; QByteArray previewData = d->loadFile(objectName, context); // Check to see if we know the mimetype for this entry. Specifically: // 1. Check to see if the item is a loadable SVG file // FIXME: Perhaps check in the manifest first? But this // seems to work well. d->scalablePreview->load(previewData); if (d->scalablePreview->isValid()) { debugFlake << "Found scalable preview image!"; d->scalablePreview->setViewBox(d->scalablePreview->boundsOnElement("svg")); foundPreview = true; continue; } // 2. Otherwise check to see if it's a loadable pixmap file d->pixmapPreview.loadFromData(previewData); if (!d->pixmapPreview.isNull()) { debugFlake << "Found pixel based preview image!"; foundPreview = true; } } } #if 0 // Enable to get more detailed debug messages debugFlake << "Object manifest entries:"; for (int i = 0; i < d->manifestEntries.size(); ++i) { KoOdfManifestEntry *entry = d->manifestEntries.value(i); debugFlake << i << ":" << entry; if (entry) debugFlake << entry->fullPath() << entry->mediaType() << entry->version(); else debugFlake << "--"; } debugFlake << "END LOADING ####################################################"; #endif return true; } // Load the actual contents inside the frame. bool KoUnavailShape::loadOdfFrameElement(const KoXmlElement & /*element*/, KoShapeLoadingContext &/*context*/) { return true; } // ---------------------------------------------------------------- // Private functions void KoUnavailShape::Private::storeObjects(const KoXmlElement &element) { // Loop through all the child elements of the draw:frame and save them. KoXmlNode n = element.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { debugFlake << "In draw:frame, node =" << n.nodeName(); // This disregards #text, but that's not in the spec anyway so // it doesn't need to be saved. if (!n.isElement()) continue; KoXmlElement el = n.toElement(); ObjectEntry *object = new ObjectEntry; QByteArray contentsTmp; QBuffer buffer(&contentsTmp); // the member KoXmlWriter writer(&buffer); // 1. Find out the objectName // Save the normalized filename, i.e. without a starting "./". // An empty string is saved if no name is found. QString name = el.attributeNS(KoXmlNS::xlink, "href", QString()); if (name.startsWith(QLatin1String("./"))) name.remove(0, 2); object->objectName = name; // 2. Copy the XML code. QHash unknownNamespaces; storeXmlRecursive(el, writer, object, unknownNamespaces); object->objectXmlContents = contentsTmp; // 3, 4: the isDir and manifestEntry members are not set here, // but initialize them anyway. . object->isDir = false; // Has to be initialized to something. object->manifestEntry = 0; objectEntries.append(object); } } void KoUnavailShape::Private::storeXmlRecursive(const KoXmlElement &el, KoXmlWriter &writer, ObjectEntry *object, QHash &unknownNamespaces) { // Start the element; // keep the name in a QByteArray so that it stays valid until end element is called. const QByteArray name(el.nodeName().toLatin1()); writer.startElement(name.constData()); // Copy all the attributes, including namespaces. QList< QPair > attributeNames = el.attributeFullNames(); for (int i = 0; i < attributeNames.size(); ++i) { QPair attrPair(attributeNames.value(i)); if (attrPair.first.isEmpty()) { writer.addAttribute(attrPair.second.toLatin1(), el.attribute(attrPair.second)); } else { // This somewhat convoluted code is because we need the // namespace, not the namespace URI. QString nsShort = KoXmlNS::nsURI2NS(attrPair.first.toLatin1()); // in case we don't find the namespace in our list create a own one and use that // so the document created on saving is valid. if (nsShort.isEmpty()) { nsShort = unknownNamespaces.value(attrPair.first); if (nsShort.isEmpty()) { nsShort = QString("ns%1").arg(unknownNamespaces.size() + 1); unknownNamespaces.insert(attrPair.first, nsShort); } QString name = QString("xmlns:") + nsShort; writer.addAttribute(name.toLatin1(), attrPair.first.toLatin1()); } QString attr(nsShort + ':' + attrPair.second); writer.addAttribute(attr.toLatin1(), el.attributeNS(attrPair.first, attrPair.second)); } } // Child elements // Loop through all the child elements of the draw:frame. KoXmlNode n = el.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { if (n.isElement()) { storeXmlRecursive(n.toElement(), writer, object, unknownNamespaces); } else if (n.isText()) { writer.addTextNode(n.toText().data()/*.toUtf8()*/); } } // End the element writer.endElement(); } /** * This function stores the embedded file in an internal store - it does not save files to disk, * and thus it is named in this manner, to avoid the function being confused with functions which * save files to disk. */ void KoUnavailShape::Private::storeFile(const QString &fileName, KoShapeLoadingContext &context) { debugFlake << "Saving file: " << fileName; // Directories need to be saved too, but they don't have any file contents. if (fileName.endsWith('/')) { FileEntry *entry = new FileEntry; entry->path = fileName; entry->mimeType = context.odfLoadingContext().mimeTypeForPath(entry->path); entry->isDir = true; embeddedFiles.append(entry); } QByteArray fileContent = loadFile(fileName, context); if (fileContent.isNull()) return; // Actually store the file in the list. FileEntry *entry = new FileEntry; entry->path = fileName; if (entry->path.startsWith(QLatin1String("./"))) entry->path.remove(0, 2); entry->mimeType = context.odfLoadingContext().mimeTypeForPath(entry->path); entry->isDir = false; entry->contents = fileContent; embeddedFiles.append(entry); debugFlake << "File length: " << fileContent.size(); } QByteArray KoUnavailShape::Private::loadFile(const QString &fileName, KoShapeLoadingContext &context) { // Can't load a file which is a directory, return an invalid QByteArray if (fileName.endsWith('/')) return QByteArray(); KoStore *store = context.odfLoadingContext().store(); QByteArray fileContent; if (!store->open(fileName)) { store->close(); return QByteArray(); } int fileSize = store->size(); fileContent = store->read(fileSize); store->close(); //debugFlake << "File content: " << fileContent; return fileContent; } diff --git a/libs/flake/svg/SvgParser.cpp b/libs/flake/svg/SvgParser.cpp index bc3faabd485..a1e7839b141 100644 --- a/libs/flake/svg/SvgParser.cpp +++ b/libs/flake/svg/SvgParser.cpp @@ -1,1337 +1,1338 @@ /* This file is part of the KDE project * Copyright (C) 2002-2005,2007 Rob Buis * Copyright (C) 2002-2004 Nicolas Goutte * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005-2009 Jan Hambrecht * Copyright (C) 2005,2007 Thomas Zander * Copyright (C) 2006-2007 Inge Wallin * Copyright (C) 2007-2008,2010 Thorsten Zachmann * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SvgParser.h" #include "SvgUtil.h" #include "SvgShape.h" #include "SvgGraphicContext.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoFilterEffectStack.h" #include "KoFilterEffectLoadingContext.h" #include #include #include #include +#include SvgParser::SvgParser(KoDocumentResourceManager *documentResourceManager) : m_context(documentResourceManager) , m_documentResourceManager(documentResourceManager) { } SvgParser::~SvgParser() { } void SvgParser::setXmlBaseDir(const QString &baseDir) { m_context.setInitialXmlBaseDir(baseDir); } QList SvgParser::shapes() const { return m_shapes; } // Helper functions // --------------------------------------------------------------------------------------- SvgGradientHelper* SvgParser::findGradient(const QString &id, const QString &href) { // check if gradient was already parsed, and return it if (m_gradients.contains(id)) return &m_gradients[ id ]; // check if gradient was stored for later parsing if (!m_context.hasDefinition(id)) return 0; const KoXmlElement &e = m_context.definition(id); if (!e.tagName().contains("Gradient")) return 0; if (e.childNodesCount() == 0) { QString mhref = e.attribute("xlink:href").mid(1); if (m_context.hasDefinition(mhref)) return findGradient(mhref, id); else return 0; } else { // ok parse gradient now if (! parseGradient(m_context.definition(id), m_context.definition(href))) return 0; } // return successfully parsed gradient or NULL QString n; if (href.isEmpty()) n = id; else n = href; if (m_gradients.contains(n)) return &m_gradients[ n ]; else return 0; } SvgPatternHelper* SvgParser::findPattern(const QString &id) { // check if pattern was already parsed, and return it if (m_patterns.contains(id)) return &m_patterns[ id ]; // check if pattern was stored for later parsing if (!m_context.hasDefinition(id)) return 0; SvgPatternHelper pattern; const KoXmlElement &e = m_context.definition(id); if (e.tagName() != "pattern") return 0; // are we referencing another pattern ? if (e.hasAttribute("xlink:href")) { QString mhref = e.attribute("xlink:href").mid(1); SvgPatternHelper *refPattern = findPattern(mhref); // inherit attributes of referenced pattern if (refPattern) pattern = *refPattern; } // ok parse pattern now parsePattern(pattern, m_context.definition(id)); // add to parsed pattern list m_patterns.insert(id, pattern); return &m_patterns[ id ]; } SvgFilterHelper* SvgParser::findFilter(const QString &id, const QString &href) { // check if filter was already parsed, and return it if (m_filters.contains(id)) return &m_filters[ id ]; // check if filter was stored for later parsing if (!m_context.hasDefinition(id)) return 0; const KoXmlElement &e = m_context.definition(id); if (e.childNodesCount() == 0) { QString mhref = e.attribute("xlink:href").mid(1); if (m_context.hasDefinition(mhref)) return findFilter(mhref, id); else return 0; } else { // ok parse filter now if (! parseFilter(m_context.definition(id), m_context.definition(href))) return 0; } // return successfully parsed filter or NULL QString n; if (href.isEmpty()) n = id; else n = href; if (m_filters.contains(n)) return &m_filters[ n ]; else return 0; } SvgClipPathHelper* SvgParser::findClipPath(const QString &id, const QString &href) { // check if clip path was already parsed, and return it if (m_clipPaths.contains(id)) return &m_clipPaths[ id ]; // check if clip path was stored for later parsing if (!m_context.hasDefinition(id)) return 0; const KoXmlElement &e = m_context.definition(id); if (e.childNodesCount() == 0) { QString mhref = e.attribute("xlink:href").mid(1); if (m_context.hasDefinition(mhref)) return findClipPath(mhref, id); else return 0; } else { // ok clip path filter now if (! parseClipPath(m_context.definition(id), m_context.definition(href))) return 0; } // return successfully parsed clip path or NULL const QString n = href.isEmpty() ? id : href; if (m_clipPaths.contains(n)) return &m_clipPaths[ n ]; else return 0; } // Parsing functions // --------------------------------------------------------------------------------------- qreal SvgParser::parseUnit(const QString &unit, bool horiz, bool vert, const QRectF &bbox) { return SvgUtil::parseUnit(m_context.currentGC(), unit, horiz, vert, bbox); } qreal SvgParser::parseUnitX(const QString &unit) { return SvgUtil::parseUnitX(m_context.currentGC(), unit); } qreal SvgParser::parseUnitY(const QString &unit) { return SvgUtil::parseUnitY(m_context.currentGC(), unit); } qreal SvgParser::parseUnitXY(const QString &unit) { return SvgUtil::parseUnitXY(m_context.currentGC(), unit); } bool SvgParser::parseGradient(const KoXmlElement &e, const KoXmlElement &referencedBy) { // IMPROVEMENTS: // - Store the parsed colorstops in some sort of a cache so they don't need to be parsed again. // - A gradient inherits attributes it does not have from the referencing gradient. // - Gradients with no color stops have no fill or stroke. // - Gradients with one color stop have a solid color. SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return false; SvgGradientHelper gradhelper; if (e.hasAttribute("xlink:href")) { QString href = e.attribute("xlink:href").mid(1); if (! href.isEmpty()) { // copy the referenced gradient if found SvgGradientHelper *pGrad = findGradient(href); if (pGrad) gradhelper = *pGrad; } else { //gc->fillType = SvgGraphicsContext::None; // <--- TODO Fill OR Stroke are none return false; } } // Use the gradient that is referencing, or if there isn't one, the original gradient. KoXmlElement b; if (!referencedBy.isNull()) b = referencedBy; else b = e; QString gradientId = b.attribute("id"); if (! gradientId.isEmpty()) { // check if we have this gradient already parsed // copy existing gradient if it exists if (m_gradients.find(gradientId) != m_gradients.end()) gradhelper.copyGradient(m_gradients[ gradientId ].gradient()); } if (b.attribute("gradientUnits") == "userSpaceOnUse") gradhelper.setGradientUnits(SvgGradientHelper::UserSpaceOnUse); // parse color prop QColor c = gc->currentColor; if (!b.attribute("color").isEmpty()) { m_context.styleParser().parseColor(c, b.attribute("color")); } else { // try style attr QString style = b.attribute("style").simplified(); const QStringList substyles = style.split(';', QString::SkipEmptyParts); for (QStringList::ConstIterator it = substyles.begin(); it != substyles.end(); ++it) { QStringList substyle = it->split(':'); QString command = substyle[0].trimmed(); QString params = substyle[1].trimmed(); if (command == "color") m_context.styleParser().parseColor(c, params); } } gc->currentColor = c; if (b.tagName() == "linearGradient") { QLinearGradient *g = new QLinearGradient(); if (gradhelper.gradientUnits() == SvgGradientHelper::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setStart(QPointF(SvgUtil::fromPercentage(b.attribute("x1", "0%")), SvgUtil::fromPercentage(b.attribute("y1", "0%")))); g->setFinalStop(QPointF(SvgUtil::fromPercentage(b.attribute("x2", "100%")), SvgUtil::fromPercentage(b.attribute("y2", "0%")))); } else { g->setStart(QPointF(SvgUtil::fromUserSpace(b.attribute("x1").toDouble()), SvgUtil::fromUserSpace(b.attribute("y1").toDouble()))); g->setFinalStop(QPointF(SvgUtil::fromUserSpace(b.attribute("x2").toDouble()), SvgUtil::fromUserSpace(b.attribute("y2").toDouble()))); } // preserve color stops if (gradhelper.gradient()) g->setStops(gradhelper.gradient()->stops()); gradhelper.setGradient(g); } else if (b.tagName() == "radialGradient") { QRadialGradient *g = new QRadialGradient(); if (gradhelper.gradientUnits() == SvgGradientHelper::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setCenter(QPointF(SvgUtil::fromPercentage(b.attribute("cx", "50%")), SvgUtil::fromPercentage(b.attribute("cy", "50%")))); g->setRadius(SvgUtil::fromPercentage(b.attribute("r", "50%"))); g->setFocalPoint(QPointF(SvgUtil::fromPercentage(b.attribute("fx", "50%")), SvgUtil::fromPercentage(b.attribute("fy", "50%")))); } else { g->setCenter(QPointF(SvgUtil::fromUserSpace(b.attribute("cx").toDouble()), SvgUtil::fromUserSpace(b.attribute("cy").toDouble()))); g->setFocalPoint(QPointF(SvgUtil::fromUserSpace(b.attribute("fx").toDouble()), SvgUtil::fromUserSpace(b.attribute("fy").toDouble()))); g->setRadius(SvgUtil::fromUserSpace(b.attribute("r").toDouble())); } // preserve color stops if (gradhelper.gradient()) g->setStops(gradhelper.gradient()->stops()); gradhelper.setGradient(g); } else if (b.tagName() == "conicalGradient") { QConicalGradient *g = new QConicalGradient(); if (gradhelper.gradientUnits() == SvgGradientHelper::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setCenter(QPointF(SvgUtil::fromPercentage(b.attribute("cx", "50%")), SvgUtil::fromPercentage(b.attribute("cy", "50%")))); g->setAngle(SvgUtil::fromPercentage(b.attribute("a", "50%"))); } else { g->setCenter(QPointF(SvgUtil::fromUserSpace(b.attribute("cx").toDouble()), SvgUtil::fromUserSpace(b.attribute("cy").toDouble()))); g->setAngle(SvgUtil::fromUserSpace(b.attribute("a").toDouble())); } // preserve color stops if (gradhelper.gradient()) g->setStops(gradhelper.gradient()->stops()); gradhelper.setGradient(g); } else { return false; } // handle spread method QString spreadMethod = b.attribute("spreadMethod"); if (!spreadMethod.isEmpty()) { if (spreadMethod == "reflect") gradhelper.gradient()->setSpread(QGradient::ReflectSpread); else if (spreadMethod == "repeat") gradhelper.gradient()->setSpread(QGradient::RepeatSpread); else gradhelper.gradient()->setSpread(QGradient::PadSpread); } else gradhelper.gradient()->setSpread(QGradient::PadSpread); // Parse the color stops. The referencing gradient does not have colorstops, // so use the stops from the gradient it references to (e in this case and not b) m_context.styleParser().parseColorStops(gradhelper.gradient(), e); gradhelper.setTransform(SvgUtil::parseTransform(b.attribute("gradientTransform"))); m_gradients.insert(gradientId, gradhelper); return true; } void SvgParser::parsePattern(SvgPatternHelper &pattern, const KoXmlElement &e) { if (e.attribute("patternUnits") == "userSpaceOnUse") { pattern.setPatternUnits(SvgPatternHelper::UserSpaceOnUse); } if (e.attribute("patternContentUnits") == "objectBoundingBox") { pattern.setPatternContentUnits(SvgPatternHelper::ObjectBoundingBox); } const QString viewBox = e.attribute("viewBox"); if (!viewBox.isEmpty()) { pattern.setPatternContentViewbox(SvgUtil::parseViewBox(viewBox)); } const QString transform = e.attribute("patternTransform"); if (!transform.isEmpty()) { pattern.setTransform(SvgUtil::parseTransform(transform)); } const QString x = e.attribute("x"); const QString y = e.attribute("y"); const QString w = e.attribute("width"); const QString h = e.attribute("height"); // parse tile reference rectangle if (pattern.patternUnits() == SvgPatternHelper::UserSpaceOnUse) { if (!x.isEmpty() && !y.isEmpty()) { pattern.setPosition(QPointF(parseUnitX(x), parseUnitY(y))); } if (!w.isEmpty() && !h.isEmpty()) { pattern.setSize(QSizeF(parseUnitX(w), parseUnitY(h))); } } else { // x, y, width, height are in percentages of the object referencing the pattern // so we just parse the percentages if (!x.isEmpty() && !y.isEmpty()) { pattern.setPosition(QPointF(SvgUtil::fromPercentage(x), SvgUtil::fromPercentage(y))); } if (!w.isEmpty() && !h.isEmpty()) { pattern.setSize(QSizeF(SvgUtil::fromPercentage(w), SvgUtil::fromPercentage(h))); } } if (e.hasChildNodes()) { pattern.setContent(e); } } bool SvgParser::parseFilter(const KoXmlElement &e, const KoXmlElement &referencedBy) { SvgFilterHelper filter; // Use the filter that is referencing, or if there isn't one, the original filter KoXmlElement b; if (!referencedBy.isNull()) b = referencedBy; else b = e; // check if we are referencing another filter if (e.hasAttribute("xlink:href")) { QString href = e.attribute("xlink:href").mid(1); if (! href.isEmpty()) { // copy the referenced filter if found SvgFilterHelper *refFilter = findFilter(href); if (refFilter) filter = *refFilter; } } else { filter.setContent(b); } if (b.attribute("filterUnits") == "userSpaceOnUse") filter.setFilterUnits(SvgFilterHelper::UserSpaceOnUse); if (b.attribute("primitiveUnits") == "objectBoundingBox") filter.setPrimitiveUnits(SvgFilterHelper::ObjectBoundingBox); // parse filter region rectangle if (filter.filterUnits() == SvgFilterHelper::UserSpaceOnUse) { filter.setPosition(QPointF(parseUnitX(b.attribute("x")), parseUnitY(b.attribute("y")))); filter.setSize(QSizeF(parseUnitX(b.attribute("width")), parseUnitY(b.attribute("height")))); } else { // x, y, width, height are in percentages of the object referencing the filter // so we just parse the percentages filter.setPosition(QPointF(SvgUtil::fromPercentage(b.attribute("x", "-0.1")), SvgUtil::fromPercentage(b.attribute("y", "-0.1")))); filter.setSize(QSizeF(SvgUtil::fromPercentage(b.attribute("width", "1.2")), SvgUtil::fromPercentage(b.attribute("height", "1.2")))); } m_filters.insert(b.attribute("id"), filter); return true; } bool SvgParser::parseClipPath(const KoXmlElement &e, const KoXmlElement &referencedBy) { SvgClipPathHelper clipPath; // Use the filter that is referencing, or if there isn't one, the original filter KoXmlElement b; if (!referencedBy.isNull()) b = referencedBy; else b = e; // check if we are referencing another clip path if (e.hasAttribute("xlink:href")) { QString href = e.attribute("xlink:href").mid(1); if (! href.isEmpty()) { // copy the referenced clip path if found SvgClipPathHelper *refClipPath = findClipPath(href); if (refClipPath) clipPath = *refClipPath; } } else { clipPath.setContent(b); } if (b.attribute("clipPathUnits") == "objectBoundingBox") clipPath.setClipPathUnits(SvgClipPathHelper::ObjectBoundingBox); m_clipPaths.insert(b.attribute("id"), clipPath); return true; } void SvgParser::applyStyle(KoShape *obj, const KoXmlElement &e) { applyStyle(obj, m_context.styleParser().collectStyles(e)); } void SvgParser::applyStyle(KoShape *obj, const SvgStyles &styles) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; m_context.styleParser().parseStyle(styles); if (!obj) return; if (!dynamic_cast(obj)) { applyFillStyle(obj); applyStrokeStyle(obj); } applyFilter(obj); applyClipping(obj); if (! gc->display) obj->setVisible(false); obj->setTransparency(1.0 - gc->opacity); } void SvgParser::applyFillStyle(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->fillType == SvgGraphicsContext::None) { shape->setBackground(QSharedPointer(0)); } else if (gc->fillType == SvgGraphicsContext::Solid) { shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor))); } else if (gc->fillType == SvgGraphicsContext::Complex) { // try to find referenced gradient SvgGradientHelper *gradient = findGradient(gc->fillId); if (gradient) { // great, we have a gradient fill QSharedPointer bg; if (gradient->gradientUnits() == SvgGradientHelper::ObjectBoundingBox) { bg = QSharedPointer(new KoGradientBackground(*gradient->gradient())); bg->setTransform(gradient->transform()); } else { QGradient *convertedGradient = SvgGradientHelper::convertGradient(gradient->gradient(), shape->size()); bg = QSharedPointer(new KoGradientBackground(convertedGradient)); QTransform invShapematrix = shape->transformation().inverted(); bg->setTransform(gradient->transform() * gc->matrix * invShapematrix); } shape->setBackground(bg); } else { // try to find referenced pattern SvgPatternHelper *pattern = findPattern(gc->fillId); KoImageCollection *imageCollection = m_documentResourceManager->imageCollection(); if (pattern && imageCollection) { // great we have a pattern fill QRectF objectBound = QRectF(QPoint(), shape->size()); QRectF currentBoundbox = gc->currentBoundbox; // properties from the object are not inherited // so we are creating a new context without copying SvgGraphicsContext *gc = m_context.pushGraphicsContext(pattern->content(), false); // the pattern establishes a new coordinate system with its // origin at the patterns x and y attributes gc->matrix = QTransform(); // object bounding box units are relative to the object the pattern is applied if (pattern->patternContentUnits() == SvgPatternHelper::ObjectBoundingBox) { gc->currentBoundbox = objectBound; gc->forcePercentage = true; } else { // inherit the current bounding box gc->currentBoundbox = currentBoundbox; } applyStyle(0, pattern->content()); // parse the pattern content elements QList patternContent = parseContainer(pattern->content()); // generate the pattern image from the shapes and the object bounding rect QImage image = pattern->generateImage(objectBound, patternContent); m_context.popGraphicsContext(); // delete the shapes created from the pattern content qDeleteAll(patternContent); if (!image.isNull()) { QSharedPointer bg(new KoPatternBackground(imageCollection)); bg->setPattern(image); QPointF refPoint = shape->documentToShape(pattern->position(objectBound)); QSizeF tileSize = pattern->size(objectBound); bg->setPatternDisplaySize(tileSize); if (pattern->patternUnits() == SvgPatternHelper::ObjectBoundingBox) { if (tileSize == objectBound.size()) bg->setRepeat(KoPatternBackground::Stretched); } // calculate pattern reference point offset in percent of tileSize // and relative to the topleft corner of the shape qreal fx = refPoint.x() / tileSize.width(); qreal fy = refPoint.y() / tileSize.height(); if (fx < 0.0) fx = floor(fx); else if (fx > 1.0) fx = ceil(fx); else fx = 0.0; if (fy < 0.0) fy = floor(fy); else if (fx > 1.0) fy = ceil(fy); else fy = 0.0; qreal offsetX = 100.0 * (refPoint.x() - fx * tileSize.width()) / tileSize.width(); qreal offsetY = 100.0 * (refPoint.y() - fy * tileSize.height()) / tileSize.height(); bg->setReferencePointOffset(QPointF(offsetX, offsetY)); shape->setBackground(bg); } } else { // no referenced fill found, use fallback color shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor))); } } } KoPathShape *path = dynamic_cast(shape); if (path) path->setFillRule(gc->fillRule); } void SvgParser::applyStrokeStyle(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->strokeType == SvgGraphicsContext::None) { shape->setStroke(0); } else if (gc->strokeType == SvgGraphicsContext::Solid) { double lineWidth = gc->stroke.lineWidth(); QVector dashes = gc->stroke.lineDashes(); KoShapeStroke *stroke = new KoShapeStroke(gc->stroke); // apply line width to dashes and dash offset if (dashes.count() && lineWidth > 0.0) { QVector dashes = stroke->lineDashes(); for (int i = 0; i < dashes.count(); ++i) dashes[i] /= lineWidth; double dashOffset = stroke->dashOffset(); stroke->setLineStyle(Qt::CustomDashLine, dashes); stroke->setDashOffset(dashOffset / lineWidth); } else { stroke->setLineStyle(Qt::SolidLine, QVector()); } shape->setStroke(stroke); } else if (gc->strokeType == SvgGraphicsContext::Complex) { // try to find referenced gradient SvgGradientHelper *gradient = findGradient(gc->strokeId); if (gradient) { // great, we have a gradient stroke QBrush brush; if (gradient->gradientUnits() == SvgGradientHelper::ObjectBoundingBox) { brush = *gradient->gradient(); brush.setTransform(gradient->transform()); } else { QGradient *convertedGradient(SvgGradientHelper::convertGradient(gradient->gradient(), shape->size())); brush = *convertedGradient; delete convertedGradient; brush.setTransform(gradient->transform() * gc->matrix * shape->transformation().inverted()); } KoShapeStroke *stroke = new KoShapeStroke(gc->stroke); stroke->setLineBrush(brush); stroke->setLineStyle(Qt::SolidLine, QVector()); shape->setStroke(stroke); } else { // no referenced stroke found, use fallback color KoShapeStroke *stroke = new KoShapeStroke(gc->stroke); stroke->setLineStyle(Qt::SolidLine, QVector()); shape->setStroke(stroke); } } } void SvgParser::applyFilter(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->filterId.isEmpty()) return; SvgFilterHelper *filter = findFilter(gc->filterId); if (! filter) return; KoXmlElement content = filter->content(); // parse filter region QRectF bound(shape->position(), shape->size()); // work on bounding box without viewbox transformation applied // so user space coordinates of bounding box and filter region match up bound = gc->viewboxTransform.inverted().mapRect(bound); QRectF filterRegion(filter->position(bound), filter->size(bound)); // convert filter region to boundingbox units QRectF objectFilterRegion; objectFilterRegion.setTopLeft(SvgUtil::userSpaceToObject(filterRegion.topLeft(), bound)); objectFilterRegion.setSize(SvgUtil::userSpaceToObject(filterRegion.size(), bound)); KoFilterEffectLoadingContext context(m_context.xmlBaseDir()); context.setShapeBoundingBox(bound); // enable units conversion context.enableFilterUnitsConversion(filter->filterUnits() == SvgFilterHelper::UserSpaceOnUse); context.enableFilterPrimitiveUnitsConversion(filter->primitiveUnits() == SvgFilterHelper::UserSpaceOnUse); KoFilterEffectRegistry *registry = KoFilterEffectRegistry::instance(); KoFilterEffectStack *filterStack = 0; QSet stdInputs; stdInputs << "SourceGraphic" << "SourceAlpha"; stdInputs << "BackgroundImage" << "BackgroundAlpha"; stdInputs << "FillPaint" << "StrokePaint"; QMap inputs; // create the filter effects and add them to the shape for (KoXmlNode n = content.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement primitive = n.toElement(); KoFilterEffect *filterEffect = registry->createFilterEffectFromXml(primitive, context); if (!filterEffect) { debugFlake << "filter effect" << primitive.tagName() << "is not implemented yet"; continue; } const QString input = primitive.attribute("in"); if (!input.isEmpty()) { filterEffect->setInput(0, input); } const QString output = primitive.attribute("result"); if (!output.isEmpty()) { filterEffect->setOutput(output); } QRectF subRegion; // parse subregion if (filter->primitiveUnits() == SvgFilterHelper::UserSpaceOnUse) { const QString xa = primitive.attribute("x"); const QString ya = primitive.attribute("y"); const QString wa = primitive.attribute("width"); const QString ha = primitive.attribute("height"); if (xa.isEmpty() || ya.isEmpty() || wa.isEmpty() || ha.isEmpty()) { bool hasStdInput = false; bool isFirstEffect = filterStack == 0; // check if one of the inputs is a standard input foreach(const QString &input, filterEffect->inputs()) { if ((isFirstEffect && input.isEmpty()) || stdInputs.contains(input)) { hasStdInput = true; break; } } if (hasStdInput || primitive.tagName() == "feImage") { // default to 0%, 0%, 100%, 100% subRegion.setTopLeft(QPointF(0, 0)); subRegion.setSize(QSizeF(1, 1)); } else { // defaults to bounding rect of all referenced nodes foreach(const QString &input, filterEffect->inputs()) { if (!inputs.contains(input)) continue; KoFilterEffect *inputFilter = inputs[input]; if (inputFilter) subRegion |= inputFilter->filterRect(); } } } else { const qreal x = parseUnitX(xa); const qreal y = parseUnitY(ya); const qreal w = parseUnitX(wa); const qreal h = parseUnitY(ha); subRegion.setTopLeft(SvgUtil::userSpaceToObject(QPointF(x, y), bound)); subRegion.setSize(SvgUtil::userSpaceToObject(QSizeF(w, h), bound)); } } else { // x, y, width, height are in percentages of the object referencing the filter // so we just parse the percentages const qreal x = SvgUtil::fromPercentage(primitive.attribute("x", "0")); const qreal y = SvgUtil::fromPercentage(primitive.attribute("y", "0")); const qreal w = SvgUtil::fromPercentage(primitive.attribute("width", "1")); const qreal h = SvgUtil::fromPercentage(primitive.attribute("height", "1")); subRegion = QRectF(QPointF(x, y), QSizeF(w, h)); } filterEffect->setFilterRect(subRegion); if (!filterStack) filterStack = new KoFilterEffectStack(); filterStack->appendFilterEffect(filterEffect); inputs[filterEffect->output()] = filterEffect; } if (filterStack) { filterStack->setClipRect(objectFilterRegion); shape->setFilterEffectStack(filterStack); } } void SvgParser::applyClipping(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->clipPathId.isEmpty()) return; SvgClipPathHelper *clipPath = findClipPath(gc->clipPathId); if (! clipPath) return; debugFlake << "applying clip path" << gc->clipPathId << "clip rule" << gc->clipRule; const bool boundingBoxUnits = clipPath->clipPathUnits() == SvgClipPathHelper::ObjectBoundingBox; debugFlake << "using" << (boundingBoxUnits ? "boundingBoxUnits" : "userSpaceOnUse"); QTransform shapeMatrix = shape->absoluteTransformation(0); // TODO: // clip path element can have a clip-path property // -> clip-path = intersection of children with referenced clip-path // any of its children can have a clip-path property // -> child element is clipped and the ORed with other children m_context.pushGraphicsContext(); if (boundingBoxUnits) { SvgGraphicsContext *gc = m_context.currentGC(); gc->matrix.reset(); gc->viewboxTransform.reset(); gc->currentBoundbox = shape->boundingRect(); gc->forcePercentage = true; } QList clipShapes = parseContainer(clipPath->content()); QList pathShapes; while (!clipShapes.isEmpty()) { KoShape *clipShape = clipShapes.first(); clipShapes.removeFirst(); // remove clip shape from list of all parsed shapes m_shapes.removeOne(clipShape); // check if we have a path shape KoPathShape *path = dynamic_cast(clipShape); if (!path) { // if shape is a group, ungroup and add children to lits of clip shapes KoShapeGroup *group = dynamic_cast(clipShape); if (group) { QList groupedShapes = group->shapes(); KoShapeUngroupCommand cmd(group, groupedShapes); cmd.redo(); clipShapes.append(groupedShapes); } else { // shape is not a group shape, use its outline as clip path QPainterPath outline = clipShape->absoluteTransformation(0).map(clipShape->outline()); path = KoPathShape::createShapeFromPainterPath(outline); } delete clipShape; } if (path) { debugFlake << "using shape" << path->name() << "as clip path"; pathShapes.append(path); if (boundingBoxUnits) path->applyAbsoluteTransformation(shapeMatrix); } } m_context.popGraphicsContext(); if (pathShapes.count()) { QTransform transformToShape; if (!boundingBoxUnits) transformToShape = shape->absoluteTransformation(0).inverted(); KoClipData *clipData = new KoClipData(pathShapes); KoClipPath *clipPath = new KoClipPath(shape, clipData); clipPath->setClipRule(gc->clipRule); shape->setClipPath(clipPath); } } QList SvgParser::parseUse(const KoXmlElement &e) { QList shapes; QString id = e.attribute("xlink:href"); // if (!id.isEmpty()) { SvgGraphicsContext *gc = m_context.pushGraphicsContext(e); // TODO: use width and height attributes too gc->matrix.translate(parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0"))); QString key = id.mid(1); if (m_context.hasDefinition(key)) { const KoXmlElement &a = m_context.definition(key); SvgStyles styles = m_context.styleParser().mergeStyles(e, a); if (a.tagName() == "g" || a.tagName() == "a" || a.tagName() == "symbol") { m_context.pushGraphicsContext(a); KoShapeGroup *group = new KoShapeGroup(); group->setZIndex(m_context.nextZIndex()); applyStyle(0, styles); m_context.styleParser().parseFont(styles); QList childShapes = parseContainer(a); // handle id applyId(a.attribute("id"), group); addToGroup(childShapes, group); applyStyle(group, styles); // apply style to group after size is set shapes.append(group); m_context.popGraphicsContext(); } else { // Create the object with the merged styles. // The object inherits all style attributes from the use tag, but keeps it's own attributes. // So, not just use the style attributes of the use tag, but merge them first. KoShape *shape = createObject(a, styles); if (shape) shapes.append(shape); } } else { // TODO: any named object can be referenced too } m_context.popGraphicsContext(); } return shapes; } void SvgParser::addToGroup(QList shapes, KoShapeGroup *group) { m_shapes += shapes; if (! group) return; KoShapeGroupCommand cmd(group, shapes); cmd.redo(); } QList SvgParser::parseSvg(const KoXmlElement &e, QSizeF *fragmentSize) { // check if we are the root svg element const bool isRootSvg = !m_context.currentGC(); SvgGraphicsContext *gc = m_context.pushGraphicsContext(); applyStyle(0, e); QRectF viewBox; const QString viewBoxStr = e.attribute("viewBox"); if (!viewBoxStr.isEmpty()) { viewBox = SvgUtil::parseViewBox(viewBoxStr); } const QString w = e.attribute("width"); const QString h = e.attribute("height"); const qreal width = w.isEmpty() ? 550.0 : parseUnit(w, true, false, viewBox); const qreal height = h.isEmpty() ? 841.0 : parseUnit(h, false, true, viewBox); QSizeF svgFragmentSize(QSizeF(width, height)); if (fragmentSize) *fragmentSize = svgFragmentSize; gc->currentBoundbox = QRectF(QPointF(0, 0), svgFragmentSize); if (! isRootSvg) { QTransform move; // x and y attribute has no meaning for outermost svg elements const qreal x = parseUnit(e.attribute("x", "0")); const qreal y = parseUnit(e.attribute("y", "0")); move.translate(x, y); gc->matrix = move * gc->matrix; gc->viewboxTransform = move *gc->viewboxTransform; } if (!viewBoxStr.isEmpty()) { QTransform viewTransform; viewTransform.translate(viewBox.x(), viewBox.y()); viewTransform.scale(width / viewBox.width() , height / viewBox.height()); gc->matrix = viewTransform * gc->matrix; gc->viewboxTransform = viewTransform *gc->viewboxTransform; gc->currentBoundbox.setWidth(gc->currentBoundbox.width() * (viewBox.width() / width)); gc->currentBoundbox.setHeight(gc->currentBoundbox.height() * (viewBox.height() / height)); } QList shapes = parseContainer(e); m_context.popGraphicsContext(); return shapes; } QList SvgParser::parseContainer(const KoXmlElement &e) { QList shapes; // are we parsing a switch container bool isSwitch = e.tagName() == "switch"; for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement b = n.toElement(); if (b.isNull()) continue; if (isSwitch) { // if we are parsing a switch check the requiredFeatures, requiredExtensions // and systemLanguage attributes // TODO: evaluate feature list if (b.hasAttribute("requiredFeatures")) { continue; } if (b.hasAttribute("requiredExtensions")) { // we do not support any extensions continue; } if (b.hasAttribute("systemLanguage")) { // not implemented yet } } if (b.tagName() == "svg") { shapes += parseSvg(b); } else if (b.tagName() == "g" || b.tagName() == "a" || b.tagName() == "symbol") { // treat svg link as group so we don't miss its child elements m_context.pushGraphicsContext(b); KoShapeGroup *group = new KoShapeGroup(); group->setZIndex(m_context.nextZIndex()); SvgStyles styles = m_context.styleParser().collectStyles(b); m_context.styleParser().parseFont(styles); applyStyle(0, styles); // parse style for inheritance QList childShapes = parseContainer(b); // handle id applyId(b.attribute("id"), group); addToGroup(childShapes, group); const QString viewBoxStr = b.attribute("viewBox"); if (!viewBoxStr.isEmpty()) { QRectF viewBox = SvgUtil::parseViewBox(viewBoxStr); QTransform viewTransform; viewTransform.translate(viewBox.x(), viewBox.y()); viewTransform.scale(group->size().width() / viewBox.width() , group->size().height() / viewBox.height()); group->applyAbsoluteTransformation(viewTransform); } applyStyle(group, styles); // apply style to this group after size is set shapes.append(group); m_context.popGraphicsContext(); } else if (b.tagName() == "switch") { m_context.pushGraphicsContext(b); shapes += parseContainer(b); m_context.popGraphicsContext(); } else if (b.tagName() == "defs") { parseDefs(b); } else if (b.tagName() == "linearGradient" || b.tagName() == "radialGradient") { parseGradient(b); } else if (b.tagName() == "pattern") { m_context.addDefinition(b); } else if (b.tagName() == "filter") { parseFilter(b); } else if (b.tagName() == "clipPath") { parseClipPath(b); } else if (b.tagName() == "style") { m_context.addStyleSheet(b); } else if (b.tagName() == "rect" || b.tagName() == "ellipse" || b.tagName() == "circle" || b.tagName() == "line" || b.tagName() == "polyline" || b.tagName() == "polygon" || b.tagName() == "path" || b.tagName() == "image" || b.tagName() == "text") { KoShape *shape = createObject(b); if (shape) shapes.append(shape); } else if (b.tagName() == "use") { shapes += parseUse(b); } else { // this is an unknown element, so try to load it anyway // there might be a shape that handles that element KoShape *shape = createObject(b); if (shape) { shapes.append(shape); } else { continue; } } // if we are parsing a switch, stop after the first supported element if (isSwitch) break; } return shapes; } void SvgParser::parseDefs(const KoXmlElement &e) { for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement b = n.toElement(); if (b.isNull()) continue; if (b.tagName() == "style") { m_context.addStyleSheet(b); } else if (b.tagName() == "defs") { parseDefs(b); } else { m_context.addDefinition(b); } } } // Creating functions // --------------------------------------------------------------------------------------- KoShape * SvgParser::createPath(const KoXmlElement &element) { KoShape *obj = 0; if (element.tagName() == "line") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { double x1 = element.attribute("x1").isEmpty() ? 0.0 : parseUnitX(element.attribute("x1")); double y1 = element.attribute("y1").isEmpty() ? 0.0 : parseUnitY(element.attribute("y1")); double x2 = element.attribute("x2").isEmpty() ? 0.0 : parseUnitX(element.attribute("x2")); double y2 = element.attribute("y2").isEmpty() ? 0.0 : parseUnitY(element.attribute("y2")); path->clear(); path->moveTo(QPointF(x1, y1)); path->lineTo(QPointF(x2, y2)); path->normalize(); obj = path; } } else if (element.tagName() == "polyline" || element.tagName() == "polygon") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { path->clear(); bool bFirst = true; QString points = element.attribute("points").simplified(); points.replace(',', ' '); points.remove('\r'); points.remove('\n'); const QStringList pointList = points.split(' ', QString::SkipEmptyParts); for (QStringList::ConstIterator it = pointList.begin(); it != pointList.end(); ++it) { QPointF point; point.setX(SvgUtil::fromUserSpace((*it).toDouble())); ++it; if (it == pointList.end()) break; point.setY(SvgUtil::fromUserSpace((*it).toDouble())); if (bFirst) { path->moveTo(point); bFirst = false; } else path->lineTo(point); } if (element.tagName() == "polygon") path->close(); path->setPosition(path->normalize()); obj = path; } } else if (element.tagName() == "path") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { path->clear(); KoPathShapeLoader loader(path); loader.parseSvg(element.attribute("d"), true); path->setPosition(path->normalize()); QPointF newPosition = QPointF(SvgUtil::fromUserSpace(path->position().x()), SvgUtil::fromUserSpace(path->position().y())); QSizeF newSize = QSizeF(SvgUtil::fromUserSpace(path->size().width()), SvgUtil::fromUserSpace(path->size().height())); path->setSize(newSize); path->setPosition(newPosition); obj = path; } } return obj; } KoShape * SvgParser::createObject(const KoXmlElement &b, const SvgStyles &style) { m_context.pushGraphicsContext(b); KoShape *obj = createShapeFromElement(b, m_context); if (obj) { obj->applyAbsoluteTransformation(m_context.currentGC()->matrix); SvgStyles objStyle = style.isEmpty() ? m_context.styleParser().collectStyles(b) : style; m_context.styleParser().parseFont(objStyle); applyStyle(obj, objStyle); // handle id applyId(b.attribute("id"), obj); obj->setZIndex(m_context.nextZIndex()); } m_context.popGraphicsContext(); return obj; } KoShape * SvgParser::createShapeFromElement(const KoXmlElement &element, SvgLoadingContext &context) { KoShape *object = 0; QList factories = KoShapeRegistry::instance()->factoriesForElement(KoXmlNS::svg, element.tagName()); foreach (KoShapeFactoryBase *f, factories) { KoShape *shape = f->createDefaultShape(m_documentResourceManager); if (!shape) continue; SvgShape *svgShape = dynamic_cast(shape); if (!svgShape) { delete shape; continue; } // reset transformation that might come from the default shape shape->setTransformation(QTransform()); // reset border KoShapeStrokeModel *oldStroke = shape->stroke(); shape->setStroke(0); delete oldStroke; // reset fill shape->setBackground(QSharedPointer(0)); if (!svgShape->loadSvg(element, context)) { delete shape; continue; } object = shape; break; } if (!object) { object = createPath(element); } return object; } KoShape * SvgParser::createShape(const QString &shapeID) { KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(shapeID); if (!factory) { debugFlake << "Could not find factory for shape id" << shapeID; return 0; } KoShape *shape = factory->createDefaultShape(m_documentResourceManager); if (!shape) { debugFlake << "Could not create Default shape for shape id" << shapeID; return 0; } if (shape->shapeId().isEmpty()) shape->setShapeId(factory->id()); // reset transformation that might come from the default shape shape->setTransformation(QTransform()); // reset border KoShapeStrokeModel *oldStroke = shape->stroke(); shape->setStroke(0); delete oldStroke; // reset fill shape->setBackground(QSharedPointer(0)); return shape; } void SvgParser::applyId(const QString &id, KoShape *shape) { if (id.isEmpty()) return; shape->setName(id); m_context.registerShape(id, shape); } diff --git a/libs/flake/svg/SvgStyleWriter.cpp b/libs/flake/svg/SvgStyleWriter.cpp index d81093772f5..3f6d86d9c5c 100644 --- a/libs/flake/svg/SvgStyleWriter.cpp +++ b/libs/flake/svg/SvgStyleWriter.cpp @@ -1,357 +1,358 @@ /* 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 "SvgStyleWriter.h" #include "SvgSavingContext.h" #include "SvgUtil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include void SvgStyleWriter::saveSvgStyle(KoShape *shape, SvgSavingContext &context) { saveSvgFill(shape, context); saveSvgStroke(shape, context); saveSvgEffects(shape, context); saveSvgClipping(shape, context); if (! shape->isVisible()) context.shapeWriter().addAttribute("display", "none"); if (shape->transparency() > 0.0) context.shapeWriter().addAttribute("opacity", 1.0 - shape->transparency()); } void SvgStyleWriter::saveSvgFill(KoShape *shape, SvgSavingContext &context) { if (! shape->background()) { context.shapeWriter().addAttribute("fill", "none"); } QSharedPointer cbg = qSharedPointerDynamicCast(shape->background()); if (cbg) { context.shapeWriter().addAttribute("fill", cbg->color().name()); if (cbg->color().alphaF() < 1.0) context.shapeWriter().addAttribute("fill-opacity", cbg->color().alphaF()); } QSharedPointer gbg = qSharedPointerDynamicCast(shape->background()); if (gbg) { QString gradientId = saveSvgGradient(gbg->gradient(), gbg->transform(), context); context.shapeWriter().addAttribute("fill", "url(#" + gradientId + ")"); } QSharedPointer pbg = qSharedPointerDynamicCast(shape->background()); if (pbg) { const QString patternId = saveSvgPattern(pbg, shape, context); context.shapeWriter().addAttribute("fill", "url(#" + patternId + ")"); } KoPathShape * path = dynamic_cast(shape); if (path && shape->background()) { // non-zero is default, so only write fillrule if evenodd is set if (path->fillRule() == Qt::OddEvenFill) context.shapeWriter().addAttribute("fill-rule", "evenodd"); } } void SvgStyleWriter::saveSvgStroke(KoShape *shape, SvgSavingContext &context) { const KoShapeStroke * line = dynamic_cast(shape->stroke()); if (! line) return; QString strokeStr("none"); if (line->lineBrush().gradient()) { QString gradientId = saveSvgGradient(line->lineBrush().gradient(), line->lineBrush().transform(), context); strokeStr = "url(#" + gradientId + ")"; } else { strokeStr = line->color().name(); } if (!strokeStr.isEmpty()) context.shapeWriter().addAttribute("stroke", strokeStr); if (line->color().alphaF() < 1.0) context.shapeWriter().addAttribute("stroke-opacity", line->color().alphaF()); context.shapeWriter().addAttribute("stroke-width", SvgUtil::toUserSpace(line->lineWidth())); if (line->capStyle() == Qt::FlatCap) context.shapeWriter().addAttribute("stroke-linecap", "butt"); else if (line->capStyle() == Qt::RoundCap) context.shapeWriter().addAttribute("stroke-linecap", "round"); else if (line->capStyle() == Qt::SquareCap) context.shapeWriter().addAttribute("stroke-linecap", "square"); if (line->joinStyle() == Qt::MiterJoin) { context.shapeWriter().addAttribute("stroke-linejoin", "miter"); context.shapeWriter().addAttribute("stroke-miterlimit", line->miterLimit()); } else if (line->joinStyle() == Qt::RoundJoin) context.shapeWriter().addAttribute("stroke-linejoin", "round"); else if (line->joinStyle() == Qt::BevelJoin) context.shapeWriter().addAttribute("stroke-linejoin", "bevel"); // dash if (line->lineStyle() > Qt::SolidLine) { qreal dashFactor = line->lineWidth(); if (line->dashOffset() != 0) context.shapeWriter().addAttribute("stroke-dashoffset", dashFactor * line->dashOffset()); QString dashStr; const QVector dashes = line->lineDashes(); int dashCount = dashes.size(); for (int i = 0; i < dashCount; ++i) { if (i > 0) dashStr += ","; dashStr += QString("%1").arg(dashes[i] * dashFactor); } context.shapeWriter().addAttribute("stroke-dasharray", dashStr); } } void SvgStyleWriter::saveSvgEffects(KoShape *shape, SvgSavingContext &context) { KoFilterEffectStack * filterStack = shape->filterEffectStack(); if (!filterStack) return; QList filterEffects = filterStack->filterEffects(); if (!filterEffects.count()) return; const QString uid = context.createUID("filter"); filterStack->save(context.styleWriter(), uid); context.shapeWriter().addAttribute("filter", "url(#" + uid + ")"); } void SvgStyleWriter::saveSvgClipping(KoShape *shape, SvgSavingContext &context) { KoClipPath *clipPath = shape->clipPath(); if (!clipPath) return; const QSizeF shapeSize = shape->outlineRect().size(); KoPathShape *path = KoPathShape::createShapeFromPainterPath(clipPath->pathForSize(shapeSize)); if (!path) return; path->close(); const QString uid = context.createUID("clippath"); context.styleWriter().startElement("clipPath"); context.styleWriter().addAttribute("id", uid); context.styleWriter().addAttribute("clipPathUnits", "userSpaceOnUse"); context.styleWriter().startElement("path"); context.styleWriter().addAttribute("d", path->toString(path->absoluteTransformation(0)*context.userSpaceTransform())); context.styleWriter().endElement(); // path context.styleWriter().endElement(); // clipPath context.shapeWriter().addAttribute("clip-path", "url(#" + uid + ")"); if (clipPath->clipRule() != Qt::WindingFill) context.shapeWriter().addAttribute("clip-rule", "evenodd"); } void SvgStyleWriter::saveSvgColorStops(const QGradientStops &colorStops, SvgSavingContext &context) { foreach(const QGradientStop &stop, colorStops) { context.styleWriter().startElement("stop"); context.styleWriter().addAttribute("stop-color", stop.second.name()); context.styleWriter().addAttribute("offset", stop.first); context.styleWriter().addAttribute("stop-opacity", stop.second.alphaF()); context.styleWriter().endElement(); } } QString SvgStyleWriter::saveSvgGradient(const QGradient *gradient, const QTransform &gradientTransform, SvgSavingContext &context) { if (! gradient) return QString(); Q_ASSERT(gradient->coordinateMode() == QGradient::ObjectBoundingMode); const QString spreadMethod[3] = { QString("pad"), QString("reflect"), QString("repeat") }; const QString uid = context.createUID("gradient"); if (gradient->type() == QGradient::LinearGradient) { const QLinearGradient * g = static_cast(gradient); context.styleWriter().startElement("linearGradient"); context.styleWriter().addAttribute("id", uid); context.styleWriter().addAttribute("gradientTransform", SvgUtil::transformToString(gradientTransform)); context.styleWriter().addAttribute("gradientUnits", "objectBoundingBox"); context.styleWriter().addAttribute("x1", g->start().x()); context.styleWriter().addAttribute("y1", g->start().y()); context.styleWriter().addAttribute("x2", g->finalStop().x()); context.styleWriter().addAttribute("y2", g->finalStop().y()); context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]); // color stops saveSvgColorStops(gradient->stops(), context); context.styleWriter().endElement(); } else if (gradient->type() == QGradient::RadialGradient) { const QRadialGradient * g = static_cast(gradient); context.styleWriter().startElement("radialGradient"); context.styleWriter().addAttribute("id", uid); context.styleWriter().addAttribute("gradientTransform", SvgUtil::transformToString(gradientTransform)); context.styleWriter().addAttribute("gradientUnits", "objectBoundingBox"); context.styleWriter().addAttribute("cx", g->center().x()); context.styleWriter().addAttribute("cy", g->center().y()); context.styleWriter().addAttribute("fx", g->focalPoint().x()); context.styleWriter().addAttribute("fy", g->focalPoint().y()); context.styleWriter().addAttribute("r", g->radius()); context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]); // color stops saveSvgColorStops(gradient->stops(), context); context.styleWriter().endElement(); } else if (gradient->type() == QGradient::ConicalGradient) { const QConicalGradient * g = static_cast(gradient); context.styleWriter().startElement("conicalGradient"); context.styleWriter().addAttribute("id", uid); context.styleWriter().addAttribute("gradientTransform", SvgUtil::transformToString(gradientTransform)); context.styleWriter().addAttribute("gradientUnits", "objectBoundingBox"); context.styleWriter().addAttribute("cx", g->center().x()); context.styleWriter().addAttribute("cy", g->center().y()); context.styleWriter().addAttribute("a", g->angle()); context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]); // color stops saveSvgColorStops(gradient->stops(), context); context.styleWriter().endElement(); } return uid; } QString SvgStyleWriter::saveSvgPattern(QSharedPointer pattern, KoShape *shape, SvgSavingContext &context) { const QString uid = context.createUID("pattern"); const QSizeF shapeSize = shape->size(); const QSizeF patternSize = pattern->patternDisplaySize(); const QSize imageSize = pattern->pattern().size(); // calculate offset in point QPointF offset = pattern->referencePointOffset(); offset.rx() = 0.01 * offset.x() * patternSize.width(); offset.ry() = 0.01 * offset.y() * patternSize.height(); // now take the reference point into account switch (pattern->referencePoint()) { case KoPatternBackground::TopLeft: break; case KoPatternBackground::Top: offset += QPointF(0.5 * shapeSize.width(), 0.0); break; case KoPatternBackground::TopRight: offset += QPointF(shapeSize.width(), 0.0); break; case KoPatternBackground::Left: offset += QPointF(0.0, 0.5 * shapeSize.height()); break; case KoPatternBackground::Center: offset += QPointF(0.5 * shapeSize.width(), 0.5 * shapeSize.height()); break; case KoPatternBackground::Right: offset += QPointF(shapeSize.width(), 0.5 * shapeSize.height()); break; case KoPatternBackground::BottomLeft: offset += QPointF(0.0, shapeSize.height()); break; case KoPatternBackground::Bottom: offset += QPointF(0.5 * shapeSize.width(), shapeSize.height()); break; case KoPatternBackground::BottomRight: offset += QPointF(shapeSize.width(), shapeSize.height()); break; } offset = shape->absoluteTransformation(0).map(offset); context.styleWriter().startElement("pattern"); context.styleWriter().addAttribute("id", uid); context.styleWriter().addAttribute("x", SvgUtil::toUserSpace(offset.x())); context.styleWriter().addAttribute("y", SvgUtil::toUserSpace(offset.y())); if (pattern->repeat() == KoPatternBackground::Stretched) { context.styleWriter().addAttribute("width", "100%"); context.styleWriter().addAttribute("height", "100%"); context.styleWriter().addAttribute("patternUnits", "objectBoundingBox"); } else { context.styleWriter().addAttribute("width", SvgUtil::toUserSpace(patternSize.width())); context.styleWriter().addAttribute("height", SvgUtil::toUserSpace(patternSize.height())); context.styleWriter().addAttribute("patternUnits", "userSpaceOnUse"); } context.styleWriter().addAttribute("viewBox", QString("0 0 %1 %2").arg(imageSize.width()).arg(imageSize.height())); //*m_defs << " patternContentUnits=\"userSpaceOnUse\""; context.styleWriter().startElement("image"); context.styleWriter().addAttribute("x", "0"); context.styleWriter().addAttribute("y", "0"); context.styleWriter().addAttribute("width", QString("%1px").arg(imageSize.width())); context.styleWriter().addAttribute("height", QString("%1px").arg(imageSize.height())); QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); if (pattern->pattern().save(&buffer, "PNG")) { QMimeDatabase db; const QString mimeType = db.mimeTypeForData(ba).name(); context.styleWriter().addAttribute("xlink:href", "data:"+ mimeType + ";base64," + ba.toBase64()); } context.styleWriter().endElement(); // image context.styleWriter().endElement(); // pattern return uid; } diff --git a/libs/flake/tests/TestShapeBackgroundCommand.cpp b/libs/flake/tests/TestShapeBackgroundCommand.cpp index a3a44cd46db..a8a8c164a6d 100644 --- a/libs/flake/tests/TestShapeBackgroundCommand.cpp +++ b/libs/flake/tests/TestShapeBackgroundCommand.cpp @@ -1,72 +1,73 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TestShapeBackgroundCommand.h" #include #include "KoShapeBackgroundCommand.h" #include "KoColorBackground.h" #include "KoShapePaintingContext.h" #include "KoViewConverter.h" +#include #include void TestShapeBackgroundCommand::refCounting() { MockShape * shape1 = new MockShape(); QSharedPointer whiteFill(new KoColorBackground(QColor(Qt::white))); QSharedPointer blackFill(new KoColorBackground(QColor(Qt::black))); QSharedPointer redFill (new KoColorBackground(QColor(Qt::red))); shape1->setBackground(whiteFill); QVERIFY(shape1->background() == whiteFill); // old fill is white, new fill is black KUndo2Command *cmd1 = new KoShapeBackgroundCommand(shape1, blackFill); cmd1->redo(); QVERIFY(shape1->background() == blackFill); // change fill back to white fill cmd1->undo(); QVERIFY(shape1->background() == whiteFill); // old fill is white, new fill is red KUndo2Command *cmd2 = new KoShapeBackgroundCommand(shape1, redFill); cmd2->redo(); QVERIFY(shape1->background() == redFill); // this command has the white fill as the old fill delete cmd1; // set fill back to white fill cmd2->undo(); QVERIFY(shape1->background() == whiteFill); // if white is deleted when deleting cmd1 this will crash QPainter p; QPainterPath path; path.addRect( QRectF(0,0,100,100) ); KoViewConverter converter; KoShapePaintingContext context; whiteFill->paint( p, converter, context, path ); delete cmd2; delete shape1; } QTEST_MAIN(TestShapeBackgroundCommand) diff --git a/libs/flake/tests/TestSnapStrategy.cpp b/libs/flake/tests/TestSnapStrategy.cpp index 16015c2b2e2..ac86c23a7c4 100644 --- a/libs/flake/tests/TestSnapStrategy.cpp +++ b/libs/flake/tests/TestSnapStrategy.cpp @@ -1,805 +1,807 @@ /* Copyright (C) 2012 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 "TestSnapStrategy.h" #include #include "KoSnapStrategy.h" #include "KoPathShape.h" #include "KoSnapProxy.h" #include "KoShapeBasedDocumentBase.h" #include "MockShapes.h" #include "KoPathPoint.h" //#include #include #include +#include + void TestSnapStrategy::testOrthogonalSnap() { //Test case one - expected not to snap OrthogonalSnapStrategy toTest; const QPointF paramMousePosition; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); //the shapeManager() function of this will be called KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); //the call that will be made to the snap guide created is m_snapGuide->canvas()->shapeManager()->shapes(); KoSnapProxy paramProxy(&aKoSnapGuide); //param proxy will have no shapes hence it will not snap qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePosition, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Second test case - makes sure the there are shapes in the fakeShapeControllerBase thus it should snap OrthogonalSnapStrategy toTestTwo; //paramMousePosition must be within paramSnapDistance of the points in firstSnapPointList const QPointF paramMousePositionTwo(3,3); MockShapeController fakeShapeControllerBaseTwo; //This call will be made on the paramProxy: proxy->pointsFromShape(shape) which in turn //will make this call shape->snapData().snapPoints(); so the shapes have to have snapPoints //In order to have snapPoints we have to use the call //shape->snapData().setSnapPoints() for each fakeShape, where we send in a const //QVector &snapPoints in order to have snapPoints to iterate - which is the only //way to change the value of minHorzDist and minVertDist in KoSnapStrategy.cpp so it //differs from HUGE_VAL - i.e. gives us the true value for the snap function. //creating the lists of points //example QVector pts; pts.push_back(QPointF(0.2, 0.3)); pts.push_back(QPointF(0.5, 0.7)); MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); //the shapeManager() function of this will be called KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); MockShape fakeShapeOne; QVector firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); //the call that will be made to the snap guide created is m_snapGuide->canvas()->shapeManager()->shapes(); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); //param proxy will have shapes now //Make sure at least one point in firstSnapPointList is within this distance of //paramMousePoint to trigger the branches if (dx < minHorzDist && dx < maxSnapDistance) //and if (dy < minVertDist && dy < maxSnapDistance) WHICH IS WHERE minVertDist and minHorzDist //ARE CHANGED FROM HUGE_VAL qreal paramSnapDistanceTwo = 4; bool didSnapTwo = toTestTwo.snap(paramMousePositionTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); } void TestSnapStrategy::testNodeSnap() { //Test case one - expected to not snap NodeSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Test case two - exercising the branches by putting a shape and snap points into the ShapeManager NodeSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); MockShape fakeShapeOne; QVector firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); qreal paramSnapDistanceTwo = 4; fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); } void TestSnapStrategy::testExtensionSnap() { //bool ExtensionSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance) ExtensionSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Second test case - testing the snap by providing ShapeManager with a shape that has snap points and a path //fakeShapeOne needs at least one subpath that is open in order to change the values of minDistances //which in turn opens the path where it is possible to get a true bool value back from the snap function // KoPathPointIndex openSubpath(const KoPathPointIndex &pointIndex); in KoPathShape needs to be called ExtensionSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); KoPathShape fakeShapeOne; QVector firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); qreal paramSnapDistanceTwo = 4; fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); QPointF firstPoint(0,2); QPointF secondPoint(1,2); QPointF thirdPoint(2,3); QPointF fourthPoint(3,4); fakeShapeOne.moveTo(firstPoint); fakeShapeOne.lineTo(secondPoint); fakeShapeOne.lineTo(thirdPoint); fakeShapeOne.lineTo(fourthPoint); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTest.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); } void TestSnapStrategy::testIntersectionSnap() { //Testing so it does not work without a path IntersectionSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Exercising the working snap by providing the shape manager with three path shapes //In order for this test to work the shapeManager has to have more than one fakeShape in it //(requirement in QList KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes) //And both shapes have to be not-visible IntersectionSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *ShapeManager = fakeKoCanvasBaseTwo.shapeManager(); qreal paramSnapDistanceTwo = 8; KoPathShape pathShapeOne; pathShapeOne.moveTo(QPointF(1,2)); pathShapeOne.lineTo(QPointF(2,2)); pathShapeOne.lineTo(QPointF(3,2)); pathShapeOne.lineTo(QPointF(4,2)); //pathShapeOne.snapData().setSnapPoints(firstSnapPointList); pathShapeOne.isVisible(true); ShapeManager->addShape(&pathShapeOne); KoPathShape pathShapeTwo; pathShapeTwo.moveTo(QPointF(1,1)); pathShapeTwo.lineTo(QPointF(2,2)); pathShapeTwo.lineTo(QPointF(3,3)); pathShapeTwo.lineTo(QPointF(4,4)); //pathShapeTwo.snapData().setSnapPoints(secondSnapPointList); pathShapeTwo.isVisible(true); ShapeManager->addShape(&pathShapeTwo); KoPathShape pathShapeThree; pathShapeThree.moveTo(QPointF(5,5)); pathShapeThree.lineTo(QPointF(6,6)); pathShapeThree.lineTo(QPointF(7,7)); pathShapeThree.lineTo(QPointF(8,8)); pathShapeThree.isVisible(true); ShapeManager->addShape(&pathShapeThree); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); } void TestSnapStrategy::testGridSnap() { //This test is the default case - meant to fail since the grid of the SnapGuide is not set GridSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //This test tests the snapping by providing the SnapGuide with a grid to snap against GridSnapStrategy toTestTwo; const QPointF paramMousePosTwo(40,60); MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); fakeKoCanvasBaseTwo.setHorz(10); fakeKoCanvasBaseTwo.setVert(8); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); qreal paramSnapDistanceTwo = 8; bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); } void TestSnapStrategy::testBoundingBoxSnap() { //Tests so the snap does not work when there is no shape with a path BoundingBoxSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //tests the snap by providing three path shapes to the shape manager BoundingBoxSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *ShapeManager = fakeKoCanvasBaseTwo.shapeManager(); qreal paramSnapDistanceTwo = 8; KoPathShape pathShapeOne; pathShapeOne.moveTo(QPointF(1,2)); pathShapeOne.lineTo(QPointF(2,2)); pathShapeOne.lineTo(QPointF(3,2)); pathShapeOne.lineTo(QPointF(4,2)); pathShapeOne.isVisible(true); ShapeManager->addShape(&pathShapeOne); KoPathShape pathShapeTwo; pathShapeTwo.moveTo(QPointF(1,1)); pathShapeTwo.lineTo(QPointF(2,2)); pathShapeTwo.lineTo(QPointF(3,3)); pathShapeTwo.lineTo(QPointF(4,4)); pathShapeTwo.isVisible(true); ShapeManager->addShape(&pathShapeTwo); KoPathShape pathShapeThree; pathShapeThree.moveTo(QPointF(5,5)); pathShapeThree.lineTo(QPointF(6,6)); pathShapeThree.lineTo(QPointF(7,7)); pathShapeThree.lineTo(QPointF(8,8)); pathShapeThree.isVisible(true); ShapeManager->addShape(&pathShapeThree); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); } void TestSnapStrategy::testLineGuideSnap() { //Testing so the snap does not work without horizontal and vertial lines LineGuideSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Test case that covers the path of the snap by providing horizontal and vertical lines for the GuidesData LineGuideSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoGuidesData guidesData; QList horzLines; horzLines.push_back(2); horzLines.push_back(3); horzLines.push_back(4); horzLines.push_back(5); QList vertLines; vertLines.push_back(1); vertLines.push_back(2); vertLines.push_back(3); vertLines.push_back(4); guidesData.setHorizontalGuideLines(horzLines); guidesData.setVerticalGuideLines(vertLines); fakeKoCanvasBaseTwo.setGuidesData(&guidesData); qreal paramSnapDistanceTwo = 8; KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); } void TestSnapStrategy::testOrhogonalDecoration() { //Making sure the decoration is created but is empty OrthogonalSnapStrategy toTestTwo; const QPointF paramMousePositionTwo(3,3); MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); MockShape fakeShapeOne; QVector firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); //Make sure at least one point in firstSnapPointList is within this distance of //paramMousePoint to trigger the branches if (dx < minHorzDist && dx < maxSnapDistance) //and if (dy < minVertDist && dy < maxSnapDistance) WHICH IS WHERE minVertDist and minHorzDist //ARE CHANGED FROM HUGE_VAL qreal paramSnapDistanceTwo = 4; toTestTwo.snap(paramMousePositionTwo, ¶mProxyTwo, paramSnapDistanceTwo); KoViewConverter irrelevantParameter; QPainterPath resultingDecoration = toTestTwo.decoration(irrelevantParameter); QVERIFY( resultingDecoration.isEmpty() ); } void TestSnapStrategy::testNodeDecoration() { //Tests so the decoration returns a rect which is inside the "standard outer rect" NodeSnapStrategy toTest; KoViewConverter irrelevantParameter; QRectF originalRect = QRectF(-5.5, -5.5, 11, 11); QPainterPath resultingDecoration = toTest.decoration(irrelevantParameter); QRectF rectInsidePath = resultingDecoration.boundingRect(); QVERIFY(originalRect==rectInsidePath); } void TestSnapStrategy::testExtensionDecoration() { //Tests the decoration is exercised by providing it with path //fakeShapeOne needs at least one subpath that is open in order to change the values of minDistances //which in turn opens the path where it is possible to get a true bool value back from the snap function // KoPathPointIndex openSubpath(const KoPathPointIndex &pointIndex); in KoPathShape needs to be called ExtensionSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); KoPathShape fakeShapeOne; QVector firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); qreal paramSnapDistanceTwo = 4; fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); QPointF firstPoint(0,2); QPointF secondPoint(1,2); QPointF thirdPoint(2,3); QPointF fourthPoint(3,4); fakeShapeOne.moveTo(firstPoint); fakeShapeOne.lineTo(secondPoint); fakeShapeOne.lineTo(thirdPoint); fakeShapeOne.lineTo(fourthPoint); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); const KoViewConverter aConverter; QPainterPath resultingDecoration = toTestTwo.decoration(aConverter); QPointF resultDecorationLastPoint = resultingDecoration.currentPosition(); QVERIFY( resultDecorationLastPoint == QPointF(0,2) ); } void TestSnapStrategy::testIntersectionDecoration() { //Tests the decoration by making sure that the returned rect is within the "standard outer rect" IntersectionSnapStrategy toTest; KoViewConverter irrelevantParameter; QRectF originalRect = QRectF(-5.5,-5.5,11,11); //std outer rect QPainterPath resultingDecoration = toTest.decoration(irrelevantParameter); QRectF rectInsidePath = resultingDecoration.boundingRect(); QVERIFY(originalRect==rectInsidePath); } void TestSnapStrategy::testGridDecoration() { //Tests the decoration by making sure the path returned has the calculated endpoint GridSnapStrategy toTest; const QPointF paramMousePosTwo(40,60); MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); fakeKoCanvasBaseTwo.setHorz(10); fakeKoCanvasBaseTwo.setVert(8); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); qreal paramSnapDistanceTwo = 8; toTest.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); KoViewConverter viewConverter; QSizeF unzoomedSize = viewConverter.viewToDocument(QSizeF(5, 5)); QPointF snappedPos(40, 56); //the snapped position is 40, 56 because horz 10 - so 40 is right on the gridline, and 56 because 7*8 = 56 which is within 8 of 60 QPointF originalEndPoint(snappedPos + QPointF(0, unzoomedSize.height())); QPainterPath resultingDecoration = toTest.decoration(viewConverter); QVERIFY( resultingDecoration.currentPosition() == originalEndPoint ); } void TestSnapStrategy::testBoundingBoxDecoration() { //tests the decoration by making sure the returned path has the pre-calculated end point BoundingBoxSnapStrategy toTest; KoViewConverter viewConverter; QSizeF unzoomedSize = viewConverter.viewToDocument(QSizeF(5, 5)); QPointF snappedPos(0,0); QPointF originalEndPoint(snappedPos + QPointF(unzoomedSize.width(), -unzoomedSize.height())); QPainterPath resultingDecoration = toTest.decoration(viewConverter); QVERIFY( resultingDecoration.currentPosition() == originalEndPoint ); } void TestSnapStrategy::testLineGuideDecoration() { //tests the decoration by making sure there are horizontal and vertical lines in the guidesData LineGuideSnapStrategy toTest; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoGuidesData guidesData; //firstSnapPointList.push_back( QList horzLines; horzLines.push_back(2); horzLines.push_back(3); horzLines.push_back(4); horzLines.push_back(5); QList vertLines; vertLines.push_back(1); vertLines.push_back(2); vertLines.push_back(3); vertLines.push_back(4); guidesData.setHorizontalGuideLines(horzLines); guidesData.setVerticalGuideLines(vertLines); fakeKoCanvasBaseTwo.setGuidesData(&guidesData); qreal paramSnapDistanceTwo = 8; KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); toTest.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); KoViewConverter parameterConverter; QSizeF unzoomedSize = parameterConverter.viewToDocument(QSizeF(5, 5)); QPointF snappedPos(1,2); QPointF originalEndPointOne(snappedPos + QPointF(unzoomedSize.width(), 0)); QPointF originalEndPointTwo(snappedPos + QPointF(0, unzoomedSize.height())); QPainterPath resultingDecoration = toTest.decoration(parameterConverter); QVERIFY( (resultingDecoration.currentPosition() == originalEndPointOne) || (resultingDecoration.currentPosition() == originalEndPointTwo ) ); } void TestSnapStrategy::testSquareDistance() { //tests that it does not work without setting the points OrthogonalSnapStrategy toTest; QPointF p1; QPointF p2; qreal resultingRealOne = toTest.squareDistance(p1, p2); QVERIFY(resultingRealOne == 0); //tests that the returned value is as expected for positive values OrthogonalSnapStrategy toTestTwo; QPointF p1_2(2,2); QPointF p2_2(1,1); qreal resultingRealTwo = toTestTwo.squareDistance(p1_2, p2_2); QVERIFY(resultingRealTwo == 2); //tests that the returned value is as expected for positive and negative values OrthogonalSnapStrategy toTestThree; QPointF p1_3(2,2); QPointF p2_3(-2,-2); qreal resultingRealThree = toTestThree.squareDistance(p1_3, p2_3); QVERIFY(resultingRealThree == 32); //tests that the returned value is 0 when the points are the same OrthogonalSnapStrategy toTestFour; QPointF p1_4(2,2); QPointF p2_4(2,2); qreal resultingRealFour = toTestFour.squareDistance(p1_4, p2_4); QVERIFY(resultingRealFour == 0); } void TestSnapStrategy::testScalarProduct() { //Tests so the scalarProduct cannot be calculated unless the points are set OrthogonalSnapStrategy toTest; QPointF p1_5; QPointF p2_5; qreal resultingRealOne = toTest.squareDistance(p1_5, p2_5); QVERIFY(resultingRealOne == 0 ); //tests that the product is correctly calculated for positive point values OrthogonalSnapStrategy toTestTwo; QPointF p1_6(2,2); QPointF p2_6(3,3); qreal resultingRealTwo = toTestTwo.squareDistance(p1_6, p2_6); QVERIFY(resultingRealTwo == 2 ); //tests that the product is correctly calculated for positive and negative point values OrthogonalSnapStrategy toTestThree; QPointF p1_7(2,2); QPointF p2_7(-2,-2); qreal resultingRealThree = toTestThree.squareDistance(p1_7, p2_7); QVERIFY(resultingRealThree == 32); //tests so the product is 0 when the points are the same OrthogonalSnapStrategy toTestFour; QPointF p1_8(1,1); QPointF p2_8(1,1); qreal resultingRealFour = toTestFour.squareDistance(p1_8, p2_8); QVERIFY(resultingRealFour == 0); //tests so there is nothing fishy when using origo OrthogonalSnapStrategy toTestFive; QPointF p1_9(1,1); QPointF p2_9(0,0); qreal resultingRealFive = toTestFive.squareDistance(p1_9, p2_9); QVERIFY(resultingRealFive == 2); } //------------------------------------------------------------------ void TestSnapStrategy::testSnapToExtension() { /* toTest.snapToExtension(paramPosition, ¶mPoint, paramMatrix); qDebug() << direction << " is the returned direction for this point in TestSnapStrategy::testSnapToExtension()"; QCOMPARE(direction, ); */ } void TestSnapStrategy::testProject() { //tests for positive point values but backwards leaning line ExtensionSnapStrategy toTestOne; qreal toCompWithOne = -1; QPointF lineStart(4,4); QPointF lineEnd(2,2); QPointF comparisonPoint(6,6); qreal resultingRealOne = toTestOne.project(lineStart, lineEnd, comparisonPoint); QCOMPARE(resultingRealOne, toCompWithOne); //testing for for negative point values ExtensionSnapStrategy toTestTwo; qreal toCompWithTwo = -4; QPointF lineStart_2(-2,-2); QPointF lineEnd_2(-4,-4); QPointF comparisonPoint_2(6,6); qreal resultingRealTwo = toTestTwo.project(lineStart_2, lineEnd_2, comparisonPoint_2); QCOMPARE(resultingRealTwo, toCompWithTwo); //testing for negative and positive point values ExtensionSnapStrategy toTestThree; qreal toCompWithThree = (10*(6/sqrt(72.0)) + 10*(6/sqrt(72.0))) / sqrt(72.0); //diffLength = sqrt(72), scalar = (10*(6/sqrt(72)) + 10*(6/sqrt(72))) QPointF lineStart_3(-2,-2); QPointF lineEnd_3(4, 4); QPointF comparisonPoint_3(8,8); qreal resultingRealThree = toTestThree.project(lineStart_3, lineEnd_3, comparisonPoint_3); QCOMPARE(resultingRealThree, toCompWithThree); //Below we test the formula itself for the dot-product by using values we know return t=0.5 //Formula for how to use the t value is: //ProjectionPoint = lineStart*(1-resultingReal) + resultingReal*lineEnd; (this is the formula used in BoundingBoxSnapStrategy::squareDistanceToLine()) //Note: The angle of the line from projection point to comparison point is always 90 degrees ExtensionSnapStrategy toTestFour; qreal toCompWithFour = 0.5; QPointF lineStart_4(2,1); QPointF lineEnd_4(6,3); QPointF comparisonPoint_4(3,4); qreal resultingRealFour = toTestFour.project(lineStart_4, lineEnd_4, comparisonPoint_4); QCOMPARE(resultingRealFour, toCompWithFour); } void TestSnapStrategy::testExtensionDirection() { /* TEST CASE 0 Supposed to return null */ ExtensionSnapStrategy toTestOne; KoPathShape uninitiatedPathShape; KoPathPoint::PointProperties normal = KoPathPoint::Normal; const QPointF initiatedPoint0(0,0); KoPathPoint initiatedPoint(&uninitiatedPathShape, initiatedPoint0, normal); QMatrix initiatedMatrixParam(1,1,1,1,1,1); const QTransform initiatedMatrix(initiatedMatrixParam); QPointF direction2 = toTestOne.extensionDirection( &initiatedPoint, initiatedMatrix); QVERIFY(direction2.isNull()); /* TEST CASE 1 tests a point that: - is the first in a subpath, - does not have the firstSubpath property set, - it has no activeControlPoint1, - is has no previous point = expected returning an empty QPointF */ ExtensionSnapStrategy toTestTwo; QPointF expectedPointTwo(0,0); KoPathShape shapeOne; QPointF firstPoint(0,1); QPointF secondPoint(1,2); QPointF thirdPoint(2,3); QPointF fourthPoint(3,4); shapeOne.moveTo(firstPoint); shapeOne.lineTo(secondPoint); shapeOne.lineTo(thirdPoint); shapeOne.lineTo(fourthPoint); QPointF paramPositionTwo(0,1); KoPathPoint paramPointTwo; paramPointTwo.setPoint(paramPositionTwo); paramPointTwo.setParent(&shapeOne); const QTransform paramTransMatrix(1,2,3,4,5,6); QPointF directionTwo = toTestTwo.extensionDirection( ¶mPointTwo, paramTransMatrix); QCOMPARE(directionTwo, expectedPointTwo); /* TEST CASE 2 tests a point that: - is the second in a subpath, - does not have the firstSubpath property set, - it has no activeControlPoint1, - is has a previous point = expected returning an */ ExtensionSnapStrategy toTestThree; QPointF expectedPointThree(0,0); QPointF paramPositionThree(1,1); KoPathPoint paramPointThree; paramPointThree.setPoint(paramPositionThree); paramPointThree.setParent(&shapeOne); QPointF directionThree = toTestThree.extensionDirection( ¶mPointThree, paramTransMatrix); QCOMPARE(directionThree, expectedPointThree); } void TestSnapStrategy::testSquareDistanceToLine() { BoundingBoxSnapStrategy toTestOne; const QPointF lineA(4,1); const QPointF lineB(6,3); const QPointF point(5,8); QPointF pointOnLine(0,0); qreal result = toTestOne.squareDistanceToLine(lineA, lineB, point, pointOnLine); //Should be HUGE_VAL because scalar > diffLength QVERIFY(result == HUGE_VAL); BoundingBoxSnapStrategy toTestTwo; QPointF lineA2(4,4); QPointF lineB2(4,4); QPointF point2(5,8); QPointF pointOnLine2(0,0); qreal result2 = toTestTwo.squareDistanceToLine(lineA2, lineB2, point2, pointOnLine2); //Should be HUGE_VAL because lineA2 == lineB2 QVERIFY(result2 == HUGE_VAL); BoundingBoxSnapStrategy toTestThree; QPointF lineA3(6,4); QPointF lineB3(8,6); QPointF point3(2,2); QPointF pointOnLine3(0,0); qreal result3 = toTestThree.squareDistanceToLine(lineA3, lineB3, point3, pointOnLine3); //Should be HUGE_VAL because scalar < 0.0 QVERIFY(result3 == HUGE_VAL); BoundingBoxSnapStrategy toTestFour; QPointF lineA4(2,2); QPointF lineB4(8,6); QPointF point4(3,4); QPointF pointOnLine4(0,0); QPointF diff(6,4); //diff = lineB3 - point3 = 6,4 //diffLength = sqrt(52) //scalar = (1*(6/sqrt(52)) + 2*(4/sqrt(52))); //pointOnLine = lineA + scalar / diffLength * diff; lineA + ((1*(6/sqrt(52)) + 2*(4/sqrt(52))) / sqrt(52)) * 6,4; QPointF distToPointOnLine = (lineA4 + ((1*(6/sqrt(52.0)) + 2*(4/sqrt(52.0))) / sqrt(52.0)) * diff)-point4; qreal toCompWithFour = distToPointOnLine.x()*distToPointOnLine.x()+distToPointOnLine.y()*distToPointOnLine.y(); qreal result4 = toTestFour.squareDistanceToLine(lineA4, lineB4, point4, pointOnLine4); //Normal case with example data QVERIFY(qFuzzyCompare(result4, toCompWithFour)); } QTEST_MAIN(TestSnapStrategy) diff --git a/libs/flake/tools/KoPathTool.cpp b/libs/flake/tools/KoPathTool.cpp index 30ab5ab2b7a..811917a2ace 100644 --- a/libs/flake/tools/KoPathTool.cpp +++ b/libs/flake/tools/KoPathTool.cpp @@ -1,968 +1,969 @@ /* This file is part of the KDE project * Copyright (C) 2006-2012 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * Copyright (C) 2007, 2010 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 "KoPathTool.h" #include "KoToolBase_p.h" #include "KoPathShape_p.h" #include "KoPathToolHandle.h" #include "KoCanvasBase.h" #include "KoShapeManager.h" #include "KoDocumentResourceManager.h" #include "KoViewConverter.h" #include "KoSelection.h" #include "KoPointerEvent.h" #include "commands/KoPathPointTypeCommand.h" #include "commands/KoPathPointInsertCommand.h" #include "commands/KoPathPointRemoveCommand.h" #include "commands/KoPathSegmentTypeCommand.h" #include "commands/KoPathBreakAtPointCommand.h" #include "commands/KoPathSegmentBreakCommand.h" #include "commands/KoParameterToPathCommand.h" #include "commands/KoSubpathJoinCommand.h" #include "commands/KoPathPointMergeCommand.h" #include "KoParameterShape.h" #include "KoPathPoint.h" #include "KoPathPointRubberSelectStrategy.h" #include "KoPathSegmentChangeStrategy.h" #include "KoPathConnectionPointStrategy.h" #include "KoParameterChangeStrategy.h" #include "PathToolOptionWidget.h" #include "KoConnectionShape.h" #include "KoSnapGuide.h" #include "KoShapeController.h" #include #include #include #include #include +#include #include #include #include static const unsigned char needle_bits[] = { 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x60, 0x00, 0xc0, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x80, 0x07, 0x00, 0x0f, 0x00, 0x1f, 0x00, 0x3e, 0x00, 0x7e, 0x00, 0x7c, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x00 }; static const unsigned char needle_move_bits[] = { 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x60, 0x00, 0xc0, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x80, 0x07, 0x10, 0x0f, 0x38, 0x1f, 0x54, 0x3e, 0xfe, 0x7e, 0x54, 0x7c, 0x38, 0x1c, 0x10, 0x18, 0x00, 0x00 }; // helper function to calculate the squared distance between two points qreal squaredDistance(const QPointF& p1, const QPointF &p2) { qreal dx = p1.x()-p2.x(); qreal dy = p1.y()-p2.y(); return dx*dx + dy*dy; } struct KoPathTool::PathSegment { PathSegment() : path(0), segmentStart(0), positionOnSegment(0) { } bool isValid() { return path && segmentStart; } KoPathShape *path; KoPathPoint *segmentStart; qreal positionOnSegment; }; KoPathTool::KoPathTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_pointSelection(this) , m_activeHandle(0) , m_handleRadius(3) , m_activeSegment(0) , m_currentStrategy(0) { QActionGroup *points = new QActionGroup(this); // m_pointTypeGroup->setExclusive(true); m_actionPathPointCorner = new QAction(koIcon("node-type-cusp"), i18n("Corner point"), this); addAction("pathpoint-corner", m_actionPathPointCorner); m_actionPathPointCorner->setData(KoPathPointTypeCommand::Corner); points->addAction(m_actionPathPointCorner); m_actionPathPointSmooth = new QAction(koIcon("node-type-smooth"), i18n("Smooth point"), this); addAction("pathpoint-smooth", m_actionPathPointSmooth); m_actionPathPointSmooth->setData(KoPathPointTypeCommand::Smooth); points->addAction(m_actionPathPointSmooth); m_actionPathPointSymmetric = new QAction(koIcon("node-type-symmetric"), i18n("Symmetric Point"), this); addAction("pathpoint-symmetric", m_actionPathPointSymmetric); m_actionPathPointSymmetric->setData(KoPathPointTypeCommand::Symmetric); points->addAction(m_actionPathPointSymmetric); m_actionCurvePoint = new QAction(koIcon("format-node-curve"), i18n("Make curve point"), this); addAction("pathpoint-curve", m_actionCurvePoint); connect(m_actionCurvePoint, SIGNAL(triggered()), this, SLOT(pointToCurve())); m_actionLinePoint = new QAction(koIcon("format-node-line"), i18n("Make line point"), this); addAction("pathpoint-line", m_actionLinePoint); connect(m_actionLinePoint, SIGNAL(triggered()), this, SLOT(pointToLine())); m_actionLineSegment = new QAction(koIcon("format-segment-line"), i18n("Segment to Line"), this); m_actionLineSegment->setShortcut(Qt::Key_F); addAction("pathsegment-line", m_actionLineSegment); connect(m_actionLineSegment, SIGNAL(triggered()), this, SLOT(segmentToLine())); m_actionCurveSegment = new QAction(koIcon("format-segment-curve"), i18n("Segment to Curve"), this); m_actionCurveSegment->setShortcut(Qt::Key_C); addAction("pathsegment-curve", m_actionCurveSegment); connect(m_actionCurveSegment, SIGNAL(triggered()), this, SLOT(segmentToCurve())); m_actionAddPoint = new QAction(koIcon("format-insert-node"), i18n("Insert point"), this); addAction("pathpoint-insert", m_actionAddPoint); m_actionAddPoint->setShortcut(Qt::Key_Insert); connect(m_actionAddPoint, SIGNAL(triggered()), this, SLOT(insertPoints())); m_actionRemovePoint = new QAction(koIcon("format-remove-node"), i18n("Remove point"), this); m_actionRemovePoint->setShortcut(Qt::Key_Backspace); addAction("pathpoint-remove", m_actionRemovePoint); connect(m_actionRemovePoint, SIGNAL(triggered()), this, SLOT(removePoints())); m_actionBreakPoint = new QAction(koIcon("format-break-node"), i18n("Break at point"), this); addAction("path-break-point", m_actionBreakPoint); connect(m_actionBreakPoint, SIGNAL(triggered()), this, SLOT(breakAtPoint())); m_actionBreakSegment = new QAction(koIcon("format-disconnect-node"), i18n("Break at segment"), this); addAction("path-break-segment", m_actionBreakSegment); connect(m_actionBreakSegment, SIGNAL(triggered()), this, SLOT(breakAtSegment())); m_actionJoinSegment = new QAction(koIcon("format-connect-node"), i18n("Join with segment"), this); m_actionJoinSegment->setShortcut(Qt::Key_J); addAction("pathpoint-join", m_actionJoinSegment); connect(m_actionJoinSegment, SIGNAL(triggered()), this, SLOT(joinPoints())); m_actionMergePoints = new QAction(koIcon("format-join-node"), i18n("Merge points"), this); addAction("pathpoint-merge", m_actionMergePoints); connect(m_actionMergePoints, SIGNAL(triggered()), this, SLOT(mergePoints())); m_actionConvertToPath = new QAction(koIcon("format-convert-to-path"), i18n("To Path"), this); m_actionConvertToPath->setShortcut(Qt::Key_P); addAction("convert-to-path", m_actionConvertToPath); connect(m_actionConvertToPath, SIGNAL(triggered()), this, SLOT(convertToPath())); connect(points, SIGNAL(triggered(QAction*)), this, SLOT(pointTypeChanged(QAction*))); connect(&m_pointSelection, SIGNAL(selectionChanged()), this, SLOT(pointSelectionChanged())); QBitmap b = QBitmap::fromData(QSize(16, 16), needle_bits); QBitmap m = b.createHeuristicMask(false); m_selectCursor = QCursor(b, m, 2, 0); b = QBitmap::fromData(QSize(16, 16), needle_move_bits); m = b.createHeuristicMask(false); m_moveCursor = QCursor(b, m, 2, 0); } KoPathTool::~KoPathTool() { delete m_activeHandle; delete m_activeSegment; delete m_currentStrategy; } QList > KoPathTool::createOptionWidgets() { QList > list; PathToolOptionWidget * toolOptions = new PathToolOptionWidget(this); connect(this, SIGNAL(typeChanged(int)), toolOptions, SLOT(setSelectionType(int))); updateOptionsWidget(); toolOptions->setWindowTitle(i18n("Line/Curve")); list.append(toolOptions); return list; } void KoPathTool::pointTypeChanged(QAction *type) { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { QList selectedPoints = m_pointSelection.selectedPointsData(); QList pointToChange; QList::const_iterator it(selectedPoints.constBegin()); for (; it != selectedPoints.constEnd(); ++it) { KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex); if (point) { if (point->activeControlPoint1() && point->activeControlPoint2()) { pointToChange.append(*it); } } } if (!pointToChange.isEmpty()) { KoPathPointTypeCommand *cmd = new KoPathPointTypeCommand(pointToChange, static_cast(type->data().toInt())); d->canvas->addCommand(cmd); updateActions(); } } } void KoPathTool::insertPoints() { Q_D(KoToolBase); if (m_pointSelection.size() > 1) { QList segments(m_pointSelection.selectedSegmentsData()); if (!segments.isEmpty()) { KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, 0.5); d->canvas->addCommand(cmd); foreach (KoPathPoint * p, cmd->insertedPoints()) { m_pointSelection.add(p, false); } updateActions(); } } } void KoPathTool::removePoints() { Q_D(KoToolBase); // TODO finish current action or should this not possible during actions??? if (m_pointSelection.size() > 0) { KUndo2Command *cmd = KoPathPointRemoveCommand::createCommand(m_pointSelection.selectedPointsData(), d->canvas->shapeController()); PointHandle *pointHandle = dynamic_cast(m_activeHandle); if (pointHandle && m_pointSelection.contains(pointHandle->activePoint())) { delete m_activeHandle; m_activeHandle = 0; } m_pointSelection.clear(); d->canvas->addCommand(cmd); } } void KoPathTool::pointToLine() { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { QList selectedPoints = m_pointSelection.selectedPointsData(); QList pointToChange; QList::const_iterator it(selectedPoints.constBegin()); for (; it != selectedPoints.constEnd(); ++it) { KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex); if (point && (point->activeControlPoint1() || point->activeControlPoint2())) pointToChange.append(*it); } if (! pointToChange.isEmpty()) { d->canvas->addCommand(new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Line)); updateActions(); } } } void KoPathTool::pointToCurve() { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { QList selectedPoints = m_pointSelection.selectedPointsData(); QList pointToChange; QList::const_iterator it(selectedPoints.constBegin()); for (; it != selectedPoints.constEnd(); ++it) { KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex); if (point && (! point->activeControlPoint1() || ! point->activeControlPoint2())) pointToChange.append(*it); } if (! pointToChange.isEmpty()) { d->canvas->addCommand(new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Curve)); updateActions(); } } } void KoPathTool::segmentToLine() { Q_D(KoToolBase); if (m_pointSelection.size() > 1) { QList segments(m_pointSelection.selectedSegmentsData()); if (segments.size() > 0) { d->canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Line)); updateActions(); } } } void KoPathTool::segmentToCurve() { Q_D(KoToolBase); if (m_pointSelection.size() > 1) { QList segments(m_pointSelection.selectedSegmentsData()); if (segments.size() > 0) { d->canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Curve)); updateActions(); } } } void KoPathTool::convertToPath() { Q_D(KoToolBase); QList shapesToConvert; foreach(KoShape *shape, m_pointSelection.selectedShapes()) { KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) shapesToConvert.append(parameterShape); } if (shapesToConvert.count()) d->canvas->addCommand(new KoParameterToPathCommand(shapesToConvert)); updateOptionsWidget(); } void KoPathTool::joinPoints() { Q_D(KoToolBase); if (m_pointSelection.objectCount() == 1 && m_pointSelection.size() == 2) { QList pd(m_pointSelection.selectedPointsData()); const KoPathPointData & pd1 = pd.at(0); const KoPathPointData & pd2 = pd.at(1); KoPathShape * pathShape = pd1.pathShape; if (!pathShape->isClosedSubpath(pd1.pointIndex.first) && (pd1.pointIndex.second == 0 || pd1.pointIndex.second == pathShape->subpathPointCount(pd1.pointIndex.first) - 1) && !pathShape->isClosedSubpath(pd2.pointIndex.first) && (pd2.pointIndex.second == 0 || pd2.pointIndex.second == pathShape->subpathPointCount(pd2.pointIndex.first) - 1)) { KoSubpathJoinCommand *cmd = new KoSubpathJoinCommand(pd1, pd2); d->canvas->addCommand(cmd); } updateActions(); } } void KoPathTool::mergePoints() { Q_D(KoToolBase); if (m_pointSelection.objectCount() != 1 || m_pointSelection.size() != 2) return; QList pointData = m_pointSelection.selectedPointsData(); const KoPathPointData & pd1 = pointData.at(0); const KoPathPointData & pd2 = pointData.at(1); const KoPathPointIndex & index1 = pd1.pointIndex; const KoPathPointIndex & index2 = pd2.pointIndex; KoPathShape * path = pd1.pathShape; // check if subpaths are already closed if (path->isClosedSubpath(index1.first) || path->isClosedSubpath(index2.first)) return; // check if first point is an endpoint if (index1.second != 0 && index1.second != path->subpathPointCount(index1.first)-1) return; // check if second point is an endpoint if (index2.second != 0 && index2.second != path->subpathPointCount(index2.first)-1) return; // now we can start merging the endpoints KoPathPointMergeCommand *cmd = new KoPathPointMergeCommand(pd1, pd2); d->canvas->addCommand(cmd); updateActions(); } void KoPathTool::breakAtPoint() { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { d->canvas->addCommand(new KoPathBreakAtPointCommand(m_pointSelection.selectedPointsData())); updateActions(); } } void KoPathTool::breakAtSegment() { Q_D(KoToolBase); // only try to break a segment when 2 points of the same object are selected if (m_pointSelection.objectCount() == 1 && m_pointSelection.size() == 2) { QList segments(m_pointSelection.selectedSegmentsData()); if (segments.size() == 1) { d->canvas->addCommand(new KoPathSegmentBreakCommand(segments.at(0))); updateActions(); } } } void KoPathTool::paint(QPainter &painter, const KoViewConverter &converter) { Q_D(KoToolBase); painter.setRenderHint(QPainter::Antialiasing, true); // use different colors so that it is also visible on a background of the same color painter.setBrush(Qt::white); //TODO make configurable painter.setPen(QPen(Qt::blue, 0)); foreach(KoPathShape *shape, m_pointSelection.selectedShapes()) { painter.save(); painter.setTransform(shape->absoluteTransformation(&converter) * painter.transform()); KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) { parameterShape->paintHandles(painter, converter, m_handleRadius); } else { shape->paintPoints(painter, converter, m_handleRadius); } painter.restore(); } if (m_currentStrategy) { painter.save(); m_currentStrategy->paint(painter, converter); painter.restore(); } painter.setBrush(Qt::green); // TODO make color configurable painter.setPen(QPen(Qt::blue, 0)); m_pointSelection.paint(painter, converter); painter.setBrush(Qt::red); // TODO make color configurable painter.setPen(QPen(Qt::blue, 0)); if (m_activeHandle) { if (m_activeHandle->check(m_pointSelection.selectedShapes())) { m_activeHandle->paint(painter, converter); } else { delete m_activeHandle; m_activeHandle = 0; } } if (m_currentStrategy) { painter.save(); KoShape::applyConversion(painter, converter); d->canvas->snapGuide()->paint(painter, converter); painter.restore(); } } void KoPathTool::repaintDecorations() { foreach(KoShape *shape, m_pointSelection.selectedShapes()) { repaint(shape->boundingRect()); } m_pointSelection.repaint(); updateOptionsWidget(); } void KoPathTool::mousePressEvent(KoPointerEvent *event) { // we are moving if we hit a point and use the left mouse button event->ignore(); if (m_activeHandle) { m_currentStrategy = m_activeHandle->handleMousePress(event); event->accept(); } else { if (event->button() & Qt::LeftButton) { // check if we hit a path segment if (m_activeSegment && m_activeSegment->isValid()) { KoPathPointIndex index = m_activeSegment->path->pathPointIndex(m_activeSegment->segmentStart); KoPathPointData data(m_activeSegment->path, index); m_currentStrategy = new KoPathSegmentChangeStrategy(this, event->point, data, m_activeSegment->positionOnSegment); event->accept(); delete m_activeSegment; m_activeSegment = 0; } else { if ((event->modifiers() & Qt::ControlModifier) == 0) { m_pointSelection.clear(); } // start rubberband selection Q_ASSERT(m_currentStrategy == 0); m_currentStrategy = new KoPathPointRubberSelectStrategy(this, event->point); event->accept(); } } } } void KoPathTool::mouseMoveEvent(KoPointerEvent *event) { if (event->button() & Qt::RightButton) return; if (m_currentStrategy) { m_lastPoint = event->point; m_currentStrategy->handleMouseMove(event->point, event->modifiers()); // repaint new handle positions m_pointSelection.repaint(); if (m_activeHandle) m_activeHandle->repaint(); return; } delete m_activeSegment; m_activeSegment = 0; foreach(KoPathShape *shape, m_pointSelection.selectedShapes()) { QRectF roi = handleGrabRect(shape->documentToShape(event->point)); KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) { int handleId = parameterShape->handleIdAt(roi); if (handleId != -1) { useCursor(m_moveCursor); emit statusTextChanged(i18n("Drag to move handle.")); if (m_activeHandle) m_activeHandle->repaint(); delete m_activeHandle; if (KoConnectionShape * connectionShape = dynamic_cast(parameterShape)) { //qDebug() << "handleId" << handleId; m_activeHandle = new ConnectionHandle(this, connectionShape, handleId); m_activeHandle->repaint(); return; } else { //qDebug() << "handleId" << handleId; m_activeHandle = new ParameterHandle(this, parameterShape, handleId); m_activeHandle->repaint(); return; } } } else { QList points = shape->pointsAt(roi); if (! points.empty()) { // find the nearest control point from all points within the roi KoPathPoint * bestPoint = 0; KoPathPoint::PointType bestPointType = KoPathPoint::Node; qreal minDistance = HUGE_VAL; foreach(KoPathPoint *p, points) { // the node point must be hit if the point is not selected yet if (! m_pointSelection.contains(p) && ! roi.contains(p->point())) continue; // check for the control points first as otherwise it is no longer // possible to change the control points when they are the same as the point if (p->activeControlPoint1() && roi.contains(p->controlPoint1())) { qreal dist = squaredDistance(roi.center(), p->controlPoint1()); if (dist < minDistance) { bestPoint = p; bestPointType = KoPathPoint::ControlPoint1; minDistance = dist; } } if (p->activeControlPoint2() && roi.contains(p->controlPoint2())) { qreal dist = squaredDistance(roi.center(), p->controlPoint2()); if (dist < minDistance) { bestPoint = p; bestPointType = KoPathPoint::ControlPoint2; minDistance = dist; } } // check the node point at last qreal dist = squaredDistance(roi.center(), p->point()); if (dist < minDistance) { bestPoint = p; bestPointType = KoPathPoint::Node; minDistance = dist; } } if (! bestPoint) return; useCursor(m_moveCursor); if (bestPointType == KoPathPoint::Node) emit statusTextChanged(i18n("Drag to move point. Shift click to change point type.")); else emit statusTextChanged(i18n("Drag to move control point.")); PointHandle *prev = dynamic_cast(m_activeHandle); if (prev && prev->activePoint() == bestPoint && prev->activePointType() == bestPointType) return; // no change; if (m_activeHandle) m_activeHandle->repaint(); delete m_activeHandle; m_activeHandle = new PointHandle(this, bestPoint, bestPointType); m_activeHandle->repaint(); return; } } } useCursor(m_selectCursor); if (m_activeHandle) m_activeHandle->repaint(); delete m_activeHandle; m_activeHandle = 0; PathSegment *hoveredSegment = segmentAtPoint(event->point); if(hoveredSegment) { useCursor(Qt::PointingHandCursor); emit statusTextChanged(i18n("Drag to change curve directly. Double click to insert new path point.")); m_activeSegment = hoveredSegment; } else { uint selectedPointCount = m_pointSelection.size(); if (selectedPointCount == 0) emit statusTextChanged(""); else if (selectedPointCount == 1) emit statusTextChanged(i18n("Press B to break path at selected point.")); else emit statusTextChanged(i18n("Press B to break path at selected segments.")); } } void KoPathTool::mouseReleaseEvent(KoPointerEvent *event) { Q_D(KoToolBase); if (m_currentStrategy) { const bool hadNoSelection = !m_pointSelection.hasSelection(); m_currentStrategy->finishInteraction(event->modifiers()); KUndo2Command *command = m_currentStrategy->createCommand(); if (command) d->canvas->addCommand(command); if (hadNoSelection && dynamic_cast(m_currentStrategy) && !m_pointSelection.hasSelection()) { // the click didn't do anything at all. Allow it to be used by others. event->ignore(); } delete m_currentStrategy; m_currentStrategy = 0; if (m_pointSelection.selectedShapes().count() == 1) emit pathChanged(m_pointSelection.selectedShapes().constFirst()); else emit pathChanged(0); } } void KoPathTool::keyPressEvent(QKeyEvent *event) { Q_D(KoToolBase); if (m_currentStrategy) { switch (event->key()) { case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_Shift: case Qt::Key_Meta: if (! event->isAutoRepeat()) { m_currentStrategy->handleMouseMove(m_lastPoint, event->modifiers()); } break; case Qt::Key_Escape: m_currentStrategy->cancelInteraction(); delete m_currentStrategy; m_currentStrategy = 0; break; default: event->ignore(); return; } } else { switch (event->key()) { // TODO move these to the actions in the constructor. case Qt::Key_I: { KoDocumentResourceManager *rm = d->canvas->shapeController()->resourceManager(); int handleRadius = rm->handleRadius(); if (event->modifiers() & Qt::ControlModifier) handleRadius--; else handleRadius++; rm->setHandleRadius(handleRadius); break; } #ifndef NDEBUG case Qt::Key_D: if (m_pointSelection.objectCount() == 1) { QList selectedPoints = m_pointSelection.selectedPointsData(); KoPathShapePrivate *p = static_cast(selectedPoints[0].pathShape->priv()); p->debugPath(); } break; #endif case Qt::Key_B: if (m_pointSelection.size() == 1) breakAtPoint(); else if (m_pointSelection.size() >= 2) breakAtSegment(); break; default: event->ignore(); return; } } event->accept(); } void KoPathTool::keyReleaseEvent(QKeyEvent *event) { if (m_currentStrategy) { switch (event->key()) { case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_Shift: case Qt::Key_Meta: if (! event->isAutoRepeat()) { m_currentStrategy->handleMouseMove(m_lastPoint, Qt::NoModifier); } break; default: break; } } event->accept(); } void KoPathTool::mouseDoubleClickEvent(KoPointerEvent *event) { Q_D(KoToolBase); event->ignore(); // check if we are doing something else at the moment if (m_currentStrategy) return; PathSegment *s = segmentAtPoint(event->point); if (!s) return; if (s->isValid()) { QList segments; segments.append(KoPathPointData(s->path, s->path->pathPointIndex(s->segmentStart))); KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, s->positionOnSegment); d->canvas->addCommand(cmd); foreach (KoPathPoint * p, cmd->insertedPoints()) { m_pointSelection.add(p, false); } updateActions(); event->accept(); } delete s; } KoPathTool::PathSegment* KoPathTool::segmentAtPoint(const QPointF &point) { Q_D(KoToolBase); // TODO: use global click proximity once added to the canvas resource provider const int clickProximity = 5; // convert click proximity to point using the current zoom level QPointF clickOffset = d->canvas->viewConverter()->viewToDocument(QPointF(clickProximity, clickProximity)); // the max allowed distance from a segment const qreal maxSquaredDistance = clickOffset.x()*clickOffset.x(); PathSegment *segment = new PathSegment; foreach(KoPathShape *shape, m_pointSelection.selectedShapes()) { KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) continue; // convert document point to shape coordinates QPointF p = shape->documentToShape(point); // our region of interest, i.e. a region around our mouse position QRectF roi(p - clickOffset, p + clickOffset); qreal minSqaredDistance = HUGE_VAL; // check all segments of this shape which intersect the region of interest QList segments = shape->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 < minSqaredDistance) { segment->path = shape; segment->segmentStart = s.first(); segment->positionOnSegment = nearestPointParam; } } } if (!segment->isValid()) { delete segment; segment = 0; } return segment; } void KoPathTool::activate(ToolActivation toolActivation, const QSet &shapes) { Q_D(KoToolBase); Q_UNUSED(toolActivation); // retrieve the actual global handle radius m_handleRadius = handleRadius(); d->canvas->snapGuide()->reset(); repaintDecorations(); QList selectedShapes; foreach(KoShape *shape, shapes) { KoPathShape *pathShape = dynamic_cast(shape); if (shape->isEditable() && pathShape) { // as the tool is just in activation repaintDecorations does not yet get called // so we need to use repaint of the tool and it is only needed to repaint the // current canvas repaint(pathShape->boundingRect()); selectedShapes.append(pathShape); } } if (selectedShapes.isEmpty()) { emit done(); return; } m_pointSelection.setSelectedShapes(selectedShapes); useCursor(m_selectCursor); connect(d->canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SLOT(activate())); updateOptionsWidget(); updateActions(); } void KoPathTool::activate() { Q_D(KoToolBase); QSet shapes; foreach(KoShape *shape, d->canvas->shapeManager()->selection()->selectedShapes()) { QSet delegates = shape->toolDelegates(); if (delegates.isEmpty()) { shapes << shape; } else { shapes += delegates; } } activate(DefaultActivation, shapes); } void KoPathTool::updateOptionsWidget() { PathToolOptionWidget::Types type; QList selectedShapes = m_pointSelection.selectedShapes(); foreach(KoPathShape *shape, selectedShapes) { KoParameterShape * parameterShape = dynamic_cast(shape); type |= parameterShape && parameterShape->isParametricShape() ? PathToolOptionWidget::ParametricShape : PathToolOptionWidget::PlainPath; } if (selectedShapes.count() == 1) emit pathChanged(selectedShapes.first()); else emit pathChanged(0); emit typeChanged(type); } void KoPathTool::updateActions() { const bool hasPointsSelected = m_pointSelection.hasSelection(); m_actionPathPointCorner->setEnabled(hasPointsSelected); m_actionPathPointSmooth->setEnabled(hasPointsSelected); m_actionPathPointSymmetric->setEnabled(hasPointsSelected); m_actionRemovePoint->setEnabled(hasPointsSelected); m_actionBreakPoint->setEnabled(hasPointsSelected); m_actionCurvePoint->setEnabled(hasPointsSelected); m_actionLinePoint->setEnabled(hasPointsSelected); bool hasSegmentsSelected = false; if (hasPointsSelected && m_pointSelection.size() > 1) hasSegmentsSelected = !m_pointSelection.selectedSegmentsData().isEmpty(); m_actionAddPoint->setEnabled(hasSegmentsSelected); m_actionLineSegment->setEnabled(hasSegmentsSelected); m_actionCurveSegment->setEnabled(hasSegmentsSelected); const uint objectCount = m_pointSelection.objectCount(); const uint pointCount = m_pointSelection.size(); m_actionBreakSegment->setEnabled(objectCount == 1 && pointCount == 2); m_actionJoinSegment->setEnabled(objectCount == 1 && pointCount == 2); m_actionMergePoints->setEnabled(objectCount == 1 && pointCount == 2); } void KoPathTool::deactivate() { Q_D(KoToolBase); disconnect(d->canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SLOT(activate())); m_pointSelection.clear(); m_pointSelection.setSelectedShapes(QList()); delete m_activeHandle; m_activeHandle = 0; delete m_activeSegment; m_activeSegment = 0; delete m_currentStrategy; m_currentStrategy = 0; d->canvas->snapGuide()->reset(); } void KoPathTool::documentResourceChanged(int key, const QVariant & res) { if (key == KoDocumentResourceManager::HandleRadius) { int oldHandleRadius = m_handleRadius; m_handleRadius = res.toUInt(); // repaint with the bigger of old and new handle radius int maxRadius = qMax(m_handleRadius, oldHandleRadius); foreach(KoPathShape *shape, m_pointSelection.selectedShapes()) { QRectF controlPointRect = shape->absoluteTransformation(0).map(shape->outline()).controlPointRect(); repaint(controlPointRect.adjusted(-maxRadius, -maxRadius, maxRadius, maxRadius)); } } } void KoPathTool::pointSelectionChanged() { Q_D(KoToolBase); updateActions(); d->canvas->snapGuide()->setIgnoredPathPoints(m_pointSelection.selectedPoints().toList()); emit selectionChanged(m_pointSelection.hasSelection()); } void KoPathTool::repaint(const QRectF &repaintRect) { Q_D(KoToolBase); //debugFlake <<"KoPathTool::repaint(" << repaintRect <<")" << m_handleRadius; // widen border to take antialiasing into account qreal radius = m_handleRadius + 1; d->canvas->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); } void KoPathTool::deleteSelection() { removePoints(); } KoToolSelection * KoPathTool::selection() { return &m_pointSelection; } diff --git a/libs/pageapp/KoPADocumentModel.cpp b/libs/pageapp/KoPADocumentModel.cpp index d5fd933f7ae..aaca026b71b 100644 --- a/libs/pageapp/KoPADocumentModel.cpp +++ b/libs/pageapp/KoPADocumentModel.cpp @@ -1,767 +1,768 @@ /* This file is part of the KDE project * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 2008 Fredy Yanardi * * 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 "KoPADocumentModel.h" #include "KoPADocument.h" #include "KoPAPageBase.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 "commands/KoPAPageMoveCommand.h" KoPADocumentModel::KoPADocumentModel( QObject* parent, KoPADocument *document ) : KoDocumentSectionModel( parent ) , m_document(0) , m_master(false) , m_lastContainer( 0 ) { setDocument( document ); } Qt::DropActions KoPADocumentModel::supportedDragActions() const { return Qt::MoveAction; } void KoPADocumentModel::update() { emit layoutAboutToBeChanged(); emit layoutChanged(); if (m_document) { dataChanged(index(0, 0), index(m_document->pageCount() - 1, columnCount() - 1)); } } int KoPADocumentModel::rowCount( const QModelIndex &parent ) const { if (!m_document) { return 0; } // check if parent is root node if ( ! parent.isValid() ) { return m_document->pages(m_master).count(); } Q_ASSERT(parent.model() == this); Q_ASSERT(parent.internalPointer()); KoShapeContainer *parentShape = dynamic_cast( (KoShape*)parent.internalPointer() ); if ( ! parentShape ) { return 0; } return parentShape->shapeCount(); } int KoPADocumentModel::columnCount( const QModelIndex & ) const { return 1; } QModelIndex KoPADocumentModel::index( int row, int column, const QModelIndex &parent ) const { if ( !m_document ) { return QModelIndex(); } // check if parent is root node if ( ! parent.isValid() ) { if ( row >= 0 && row < m_document->pages(m_master).count() ) { return createIndex( row, column, m_document->pages(m_master).at(row) ); } else { return QModelIndex(); } } Q_ASSERT(parent.model() == this); Q_ASSERT(parent.internalPointer()); KoShapeContainer *parentShape = dynamic_cast( (KoShape*)parent.internalPointer() ); if ( ! parentShape ) { return QModelIndex(); } if ( row < parentShape->shapeCount() ) { return createIndex( row, column, childFromIndex( parentShape, row ) ); } else { return QModelIndex(); } } QModelIndex KoPADocumentModel::parent( const QModelIndex &child ) const { // check if child is root node if ( ! child.isValid() || !m_document ) { return QModelIndex(); } Q_ASSERT(child.model() == this); Q_ASSERT(child.internalPointer()); KoShape *childShape = static_cast( child.internalPointer() ); if ( ! childShape ) { return QModelIndex(); } // get the children's parent shape KoShapeContainer *parentShape = childShape->parent(); if ( ! parentShape ) { return QModelIndex(); } // get the grandparent to determine the row of the parent shape KoShapeContainer *grandParentShape = parentShape->parent(); if ( ! grandParentShape ) { KoPAPageBase* page = dynamic_cast( parentShape); return createIndex( m_document->pages(m_master).indexOf( page ), 0, parentShape ); } return createIndex( indexFromChild( grandParentShape, parentShape ), 0, parentShape ); } QVariant KoPADocumentModel::data( const QModelIndex &index, int role ) const { if ( ! index.isValid() || !m_document ) { return QVariant(); } Q_ASSERT(index.model() == this); Q_ASSERT(index.internalPointer()); KoShape *shape = static_cast( index.internalPointer() ); switch (role) { case Qt::DisplayRole: { QString name = shape->name(); if ( name.isEmpty() ) { if ( dynamic_cast( shape ) ) { if (m_document->pageType() == KoPageApp::Slide ) { name = i18n("Slide %1", m_document->pageIndex(dynamic_cast(shape)) + 1); } else { name = i18n("Page %1", m_document->pageIndex(dynamic_cast(shape)) + 1); } } else if ( dynamic_cast( shape ) ) { name = i18n("Layer") + QString(" (%1)").arg(shape->zIndex()); } else if ( dynamic_cast( shape ) ) { name = i18n("Group") + QString(" (%1)").arg(shape->zIndex()); } else { name = i18n("Shape") + QString(" (%1)").arg(shape->zIndex()); } } return name; } case Qt::DecorationRole: return QVariant();//return shape->icon(); case Qt::EditRole: return shape->name(); case Qt::SizeHintRole: { KoPAPageBase *page = dynamic_cast(shape); if (page) { // return actual page size for page KoPageLayout layout = page->pageLayout(); return QSize(layout.width, layout.height); } else return shape->size(); } case ActiveRole: { KoCanvasController * canvasController = KoToolManager::instance()->activeCanvasController(); KoSelection * selection = canvasController->canvas()->shapeManager()->selection(); if ( ! selection ) { return false; } /* KoShapeLayer *layer = dynamic_cast( shape ); if ( layer ) return (layer == selection->activeLayer() ); else */ return selection->isSelected( shape ); } case PropertiesRole: return QVariant::fromValue( properties( shape ) ); case AspectRatioRole: { QTransform matrix = shape->absoluteTransformation( 0 ); QRectF bbox = matrix.mapRect( shape->outline().boundingRect() ); KoShapeContainer *container = dynamic_cast( shape ); if ( container ) { bbox = QRectF(); foreach( KoShape* shape, container->shapes() ) { bbox = bbox.united( shape->outline().boundingRect() ); } } return qreal(bbox.width()) / bbox.height(); } default: if (role >= int(BeginThumbnailRole)) { return createThumbnail( shape, QSize( role - int(BeginThumbnailRole), role - int(BeginThumbnailRole) ) ); } else { return QVariant(); } } } Qt::ItemFlags KoPADocumentModel::flags(const QModelIndex &index) const { if ( !m_document ) { return 0; } if ( ! index.isValid() ) { return Qt::ItemIsEnabled | Qt::ItemIsDropEnabled; } Q_ASSERT(index.model() == this); Q_ASSERT(index.internalPointer()); Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEditable; //if ( dynamic_cast( (KoShape*)index.internalPointer() ) ) flags |= Qt::ItemIsDropEnabled; return flags; } bool KoPADocumentModel::setData(const QModelIndex &index, const QVariant &value, int role ) { if ( ! index.isValid() || !m_document ) { return false; } Q_ASSERT(index.model() == this); Q_ASSERT(index.internalPointer()); KoShape *shape = static_cast( index.internalPointer() ); switch (role) { case Qt::DisplayRole: case Qt::EditRole: { KUndo2Command * cmd = new KoShapeRenameCommand( shape, value.toString() ); if (dynamic_cast(shape)) { if (m_document->pageType() == KoPageApp::Slide) { cmd->setText(kundo2_i18n("Rename Slide")); } else { cmd->setText(kundo2_i18n("Rename Page")); } } else if (dynamic_cast(shape)) { cmd->setText(kundo2_i18n("Rename Layer")); } m_document->addCommand( cmd ); } break; case PropertiesRole: setProperties( shape, value.value()); break; case ActiveRole: /* if (value.toBool()) { KoCanvasController * canvasController = KoToolManager::instance()->activeCanvasController(); KoSelection * selection = canvasController->canvas()->shapeManager()->selection(); KoShapeLayer *layer = dynamic_cast( shape ); if ( layer && selection ) { selection->setActiveLayer( layer ); } } */ break; default: return false; } emit dataChanged( index, index ); return true; } KoDocumentSectionModel::PropertyList KoPADocumentModel::properties( KoShape* shape ) const { PropertyList l; if (KoPAPageBase *page = dynamic_cast(shape)) { // The idea is to display the page-number so users know what page-number/slide-number // the shape has also in the case the slide has a name (in which case it's not named // "Slide [slide-number]" any longer. // Maybe we should better use KoTextPage::visiblePageNumber here? l << Property(i18n("Slide"), QString::number(m_document->pageIndex(page) + 1)); } l << Property(i18n("Visible"), koIcon("layer-visible-on"), koIcon("layer-visible-off"), shape->isVisible()); l << Property(i18n("Locked"), koIcon("object-locked"), koIcon("object-unlocked"), shape->isGeometryProtected()); return l; } void KoPADocumentModel::setProperties( KoShape* shape, const PropertyList &properties ) { bool oldVisibleState = shape->isVisible(); bool oldLockedState = shape->isGeometryProtected(); shape->setVisible( properties.at( 0 ).state.toBool() ); shape->setGeometryProtected( properties.at( 1 ).state.toBool() ); if ( ( oldVisibleState != shape->isVisible() ) || ( oldLockedState != shape->isGeometryProtected() ) ) { shape->update(); } } QImage KoPADocumentModel::createThumbnail( KoShape* shape, const QSize &thumbSize ) const { QSize size(thumbSize.width(), thumbSize.height()); KoShapePainter shapePainter; KoPAPageBase *page = dynamic_cast(shape); if (page) { // We create a thumbnail with actual width / height ratio for page KoZoomHandler zoomHandler; KoPageLayout layout = page->pageLayout(); qreal ratio = (zoomHandler.resolutionX() * layout.width) / (zoomHandler.resolutionY() * layout.height); if ( ratio > 1 ) { size.setHeight( size.width() / ratio ); } else { size.setWidth( size.height() * ratio ); } QPixmap pixmap = m_document->pageThumbnail( page, size ); return pixmap.toImage(); } QList shapes; KoShapeContainer *container = dynamic_cast(shape); if (container) { shapes = container->shapes(); } shapes.append(shape); shapePainter.setShapes( shapes ); QImage thumb( size, QImage::Format_RGB32 ); // draw the background of the thumbnail thumb.fill( QColor( Qt::white ).rgb() ); shapePainter.paint(thumb); return thumb; } KoShape * KoPADocumentModel::childFromIndex( KoShapeContainer *parent, int row ) const { return parent->shapes().at(row); } int KoPADocumentModel::indexFromChild( KoShapeContainer *parent, KoShape *child ) const { if ( !m_document ) { return 0; } return parent->shapes().indexOf( child ); } Qt::DropActions KoPADocumentModel::supportedDropActions () const { return Qt::MoveAction | Qt::CopyAction; } QStringList KoPADocumentModel::mimeTypes() const { QStringList types; types << QLatin1String("application/x-kopalayermodeldatalist"); return types; } QMimeData * KoPADocumentModel::mimeData( const QModelIndexList & indexes ) const { // check if there is data to encode if ( ! indexes.count() ) { return 0; } // check if we support a format QStringList types = mimeTypes(); if ( types.isEmpty() ) { return 0; } QMimeData *data = new QMimeData(); QString format = types[0]; QByteArray encoded; QDataStream stream(&encoded, QIODevice::WriteOnly); // encode the data QModelIndexList::ConstIterator it = indexes.begin(); for( ; it != indexes.end(); ++it) { stream << QVariant::fromValue( qulonglong( it->internalPointer() ) ); } data->setData(format, encoded); return data; } bool KoPADocumentModel::dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent ) { Q_UNUSED( row ); Q_UNUSED( column ); // check if the action is supported if ( ! data || action != Qt::MoveAction ) { return false; } // check if the format is supported QStringList types = mimeTypes(); if ( types.isEmpty() ) { return false; } QString format = types[0]; if ( ! data->hasFormat(format) ) { return false; } QByteArray encoded = data->data( format ); QDataStream stream(&encoded, QIODevice::ReadOnly); QList shapes; // decode the data while( ! stream.atEnd() ) { QVariant v; stream >> v; shapes.append( static_cast( (void*)v.value() ) ); } QList toplevelShapes; QList layers; QList pages; // remove shapes having its parent in the list // and separate the layers foreach( KoShape * shape, shapes ) { // check whether the selection contains page // by the UI rules, the selection should contains page only KoPAPageBase *page = dynamic_cast( shape ); if ( page ) { pages.append( page ); continue; } KoShapeContainer *parentShape = shape->parent(); bool hasParentInList = false; while ( parentShape ) { if ( shapes.contains( parentShape ) ) { hasParentInList = true; break; } parentShape = parentShape->parent(); } if ( hasParentInList ) { continue; } KoShapeLayer * layer = dynamic_cast( shape ); if ( layer ) { layers.append( layer ); } else { toplevelShapes.append( shape ); } } // dropping to root, only page(s) is allowed if (!parent.isValid()) { if ( !pages.isEmpty() ) { if ( row < 0 ) { return false; } KoPAPageBase *after = (row != 0) ? m_document->pageByIndex(row - 1, false) : 0; debugPageApp << "KoPADocumentModel::dropMimeData parent = root, dropping page(s) as root, moving page(s)"; return doDrop(pages, after, action); } else { debugPageApp << "KoPADocumentModel::dropMimeData parent = root, dropping non-page as root, returning false"; return false; } } else if (parent.isValid() && !pages.isEmpty()){ if (parent.row() < 0) { return false; } KoPAPageBase *after; if ((m_document->pageIndex(pages.first()) - 1) == parent.row()) { after = (parent.row() != 0) ? m_document->pageByIndex(parent.row() - 1, false) : 0; } else { after = (parent.row() > -1) ? m_document->pageByIndex(parent.row(), false) : 0; } return doDrop(pages, after, action); } KoShape *shape = static_cast( parent.internalPointer() ); KoShapeContainer * container = dynamic_cast( shape ); if ( container ) { KoShapeGroup * group = dynamic_cast( container ); if ( group ) { debugPageApp <<"KoPADocumentModel::dropMimeData parent = group"; if ( ! toplevelShapes.count() ) { return false; } emit layoutAboutToBeChanged(); beginInsertRows( parent, group->shapeCount(), group->shapeCount()+toplevelShapes.count() ); KUndo2Command * cmd = new KUndo2Command(); cmd->setText( kundo2_i18n("Reparent shapes") ); foreach( KoShape * shape, toplevelShapes ) { new KoShapeUngroupCommand( shape->parent(), QList() << shape, QList(), cmd ); } new KoShapeGroupCommand( group, toplevelShapes, cmd ); KoCanvasController * canvasController = KoToolManager::instance()->activeCanvasController(); canvasController->canvas()->addCommand( cmd ); endInsertRows(); emit layoutChanged(); } else { debugPageApp <<"KoPADocumentModel::dropMimeData parent = container"; if ( toplevelShapes.count() ) { emit layoutAboutToBeChanged(); beginInsertRows( parent, container->shapeCount(), container->shapeCount()+toplevelShapes.count() ); KUndo2Command * cmd = new KUndo2Command(); cmd->setText( kundo2_i18n("Reparent shapes") ); QList clipped; QList inheritsTransform; foreach( KoShape * shape, toplevelShapes ) { if ( ! shape->parent() ) { clipped.append( false ); inheritsTransform.append(false); continue; } clipped.append( shape->parent()->isClipped( shape ) ); inheritsTransform.append(shape->parent()->inheritsTransform(shape)); new KoShapeUngroupCommand( shape->parent(), QList() << shape, QList(), cmd ); } // shapes are dropped on a container, so add them to the container new KoShapeGroupCommand(container, toplevelShapes, clipped, inheritsTransform, cmd); KoCanvasController * canvasController = KoToolManager::instance()->activeCanvasController(); canvasController->canvas()->addCommand( cmd ); endInsertRows(); emit layoutChanged(); } else if ( layers.count() ) { KoShapeLayer * layer = dynamic_cast( container ); if ( ! layer ) { return false; } // TODO layers are dropped on a layer, so change layer ordering return false; } } } else { debugPageApp <<"KoPADocumentModel::dropMimeData parent = shape"; if ( ! toplevelShapes.count() ) { return false; } // TODO shapes are dropped on a shape, reorder them return false; } return true; } QModelIndex KoPADocumentModel::parentIndexFromShape( const KoShape * child ) { if ( !m_document ) { return QModelIndex(); } // check if child shape is a layer, and return invalid model index if it is const KoShapeLayer *childlayer = dynamic_cast( child ); if ( childlayer ) { return QModelIndex(); } // get the children's parent shape KoShapeContainer *parentShape = child->parent(); if ( ! parentShape ) { return QModelIndex(); } // check if the parent is a layer KoShapeLayer *parentLayer = dynamic_cast( parentShape ); if ( parentLayer ) { KoPAPageBase * page = dynamic_cast( parentLayer->parent() ); if ( page ) { return createIndex( m_document->pages(m_master).count() - 1 - m_document->pages(m_master).indexOf( page ), 0, parentLayer ); } } // get the grandparent to determine the row of the parent shape KoShapeContainer *grandParentShape = parentShape->parent(); if ( ! grandParentShape ) { return QModelIndex(); } return createIndex( indexFromChild( grandParentShape, parentShape ), 0, parentShape ); } void KoPADocumentModel::setDocument( KoPADocument* document ) { if (m_document == document) { return; } if (m_document) { disconnect( m_document, SIGNAL(pageAdded(KoPAPageBase*)), this, SLOT(update()) ); disconnect( m_document, SIGNAL(pageRemoved(KoPAPageBase*)), this, SLOT(update()) ); disconnect( m_document, SIGNAL(update(KoPAPageBase*)), this, SLOT(update()) ); disconnect( m_document, SIGNAL(shapeAdded(KoShape*)), this, SLOT(update()) ); disconnect( m_document, SIGNAL(shapeRemoved(KoShape*)), this, SLOT(update()) ); } beginResetModel(); m_document = document; endResetModel(); if ( m_document ) { connect( m_document, SIGNAL(pageAdded(KoPAPageBase*)), this, SLOT(update()) ); connect( m_document, SIGNAL(pageRemoved(KoPAPageBase*)), this, SLOT(update()) ); connect( m_document, SIGNAL(update(KoPAPageBase*)), this, SLOT(update()) ); connect( m_document, SIGNAL(shapeAdded(KoShape*)), this, SLOT(update()) ); connect( m_document, SIGNAL(shapeRemoved(KoShape*)), this, SLOT(update()) ); } } void KoPADocumentModel::setMasterMode(bool master) { m_master = master; update(); // Rebuild the model } bool KoPADocumentModel::doDrop(QList pages, KoPAPageBase *pageAfter, Qt::DropAction action) { Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); bool enableMove = true; foreach (KoPAPageBase *page, pages) { if (!m_document->pages(false).contains(page)) { KoPAPageBase *newPage = page; pages.replace(pages.indexOf(page), newPage); enableMove = false; break; } } if (((modifiers & Qt::ControlModifier) == 0) && ((modifiers & Qt::ShiftModifier) == 0)) { QMenu popup; QString seq = QKeySequence(Qt::ShiftModifier).toString(); seq.chop(1); QAction *popupMoveAction = new QAction(i18n("&Move Here") + '\t' + seq, this); popupMoveAction->setIcon(koIcon("go-jump")); seq = QKeySequence(Qt::ControlModifier).toString(); seq.chop(1); QAction *popupCopyAction = new QAction(i18n("&Copy Here") + '\t' + seq, this); popupCopyAction->setIcon(koIcon("edit-copy")); seq = QKeySequence( Qt::ControlModifier + Qt::ShiftModifier ).toString(); seq.chop(1); QAction *popupCancelAction = new QAction(i18n("C&ancel") + '\t' + QKeySequence(Qt::Key_Escape).toString(), this); popupCancelAction->setIcon(koIcon("process-stop")); if (enableMove) { popup.addAction(popupMoveAction); } popup.addAction(popupCopyAction); popup.addSeparator(); popup.addAction(popupCancelAction); QAction *result = popup.exec(QCursor::pos()); if (result == popupCopyAction) { action = Qt::CopyAction; } else if (result == popupMoveAction) { action = Qt::MoveAction; } else { return false; } } else if ((modifiers & Qt::ControlModifier) != 0) { action = Qt::CopyAction; } else if ((modifiers & Qt::ShiftModifier) != 0) { action = Qt::MoveAction; } else { return false; } switch (action) { case Qt::MoveAction: { KoPAPageMoveCommand *command = new KoPAPageMoveCommand(m_document, pages, pageAfter); m_document->addCommand( command ); if ((m_document->pageIndex(pageAfter) + pages.count()) < m_document->pageCount()) { emit requestPageSelection(m_document->pageIndex(pageAfter) + 1, pages.count()); } return true; } case Qt::CopyAction: { // Copy Pages KoPAOdfPageSaveHelper saveHelper(m_document, pages); KoDrag drag; drag.setOdf(KoOdf::mimeType(m_document->documentType()), saveHelper); drag.addToClipboard(); //Paste Pages const QMimeData * data = QApplication::clipboard()->mimeData(); static const KoOdf::DocumentType documentTypes[] = { KoOdf::Graphics, KoOdf::Presentation }; for (unsigned int i = 0; i < sizeof(documentTypes) / sizeof(KoOdf::DocumentType); ++i) { if (data->hasFormat( KoOdf::mimeType(documentTypes[i]))) { KoPAPastePage paste(m_document, pageAfter); paste.paste(documentTypes[i], data); break; } } emit requestPageSelection(m_document->pageIndex(pageAfter) + 1, sizeof(documentTypes) / sizeof(KoOdf::DocumentType) - 1); return true; } default: qDebug("Unknown action: %d ", (int)action); return false; } return false; } diff --git a/libs/pageapp/KoPAPageBase.cpp b/libs/pageapp/KoPAPageBase.cpp index 92ece17c6b6..3ecdc44dea0 100644 --- a/libs/pageapp/KoPAPageBase.cpp +++ b/libs/pageapp/KoPAPageBase.cpp @@ -1,352 +1,353 @@ /* This file is part of the KDE project Copyright (C) 2006-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 "KoPAPageBase.h" #include "KoPASavingContext.h" #include "KoPALoadingContext.h" #include "KoPAPixmapCache.h" #include "KoPAPageContainerModel.h" #include "KoPAUtil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include KoPAPageBase::KoPAPageBase() : KoShapeContainer( new KoPAPageContainerModel() ) { // Add a default layer KoShapeLayer* layer = new KoShapeLayer; addShape(layer); } KoPAPageBase::~KoPAPageBase() { } void KoPAPageBase::paintComponent(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); } void KoPAPageBase::paintBackground( QPainter & painter, const KoViewConverter & converter, KoShapePaintingContext &paintContext ) { painter.save(); applyConversion( painter, converter ); KoPageLayout layout = pageLayout(); painter.setPen(QPen(Qt::black, 0)); if (background()) { QPainterPath p; p.addRect( QRectF( 0.0, 0.0, layout.width, layout.height ) ); background()->paint( painter, converter, paintContext, p ); } else { painter.setBrush(Qt::white); painter.drawRect(QRectF(0.0, 0.0, layout.width, layout.height)); } painter.restore(); } void KoPAPageBase::saveOdfPageContent( KoPASavingContext & paContext ) const { saveOdfLayers(paContext); saveOdfShapes( paContext ); saveOdfAnimations( paContext ); saveOdfPresentationNotes( paContext ); } void KoPAPageBase::saveOdfLayers(KoPASavingContext &paContext) const { QList shapes(this->shapes()); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); foreach(KoShape* shape, shapes) { KoShapeLayer *layer = dynamic_cast(shape); if (layer) { paContext.addLayerForSaving(layer); } else { Q_ASSERT(layer); warnPageApp << "Page contains non layer where a layer is expected"; } } paContext.saveLayerSet(paContext.xmlWriter()); paContext.clearLayers(); } void KoPAPageBase::saveOdfShapes( KoShapeSavingContext &context ) const { QList shapes(this->shapes()); QList tlshapes( shapes ); std::sort( tlshapes.begin(), tlshapes.end(), KoShape::compareShapeZIndex ); foreach( KoShape *shape, tlshapes ) { shape->saveOdf( context ); } } QString KoPAPageBase::saveOdfPageStyle( KoPASavingContext &paContext ) const { KoGenStyle style( KoGenStyle::DrawingPageAutoStyle, "drawing-page" ); if ( paContext.isSet( KoShapeSavingContext::AutoStyleInStyleXml ) ) { style.setAutoStyleInStylesDotXml( true ); } saveOdfPageStyleData( style, paContext ); return paContext.mainStyles().insert( style, "dp" ); } void KoPAPageBase::saveOdfPageStyleData( KoGenStyle &style, KoPASavingContext &paContext ) const { QSharedPointer bg = background(); if( bg ) bg->fillStyle( style, paContext ); } bool KoPAPageBase::saveOdfAnimations( KoPASavingContext & paContext ) const { Q_UNUSED( paContext ); return true; } bool KoPAPageBase::saveOdfPresentationNotes(KoPASavingContext &paContext) const { Q_UNUSED( paContext ); return true; } bool KoPAPageBase::loadOdf( const KoXmlElement &element, KoShapeLoadingContext & loadingContext ) { KoPALoadingContext &paContext = static_cast( loadingContext ); KoStyleStack& styleStack = loadingContext.odfLoadingContext().styleStack(); styleStack.save(); loadingContext.odfLoadingContext().fillStyleStack( element, KoXmlNS::draw, "style-name", "drawing-page" ); styleStack.setTypeProperties( "drawing-page" ); loadOdfPageTag(element, paContext); styleStack.restore(); // load layers and shapes const KoXmlElement & pageLayerSet = KoXml::namedItemNS( element, KoXmlNS::draw, "layer-set" ); const KoXmlElement & usedPageLayerSet = pageLayerSet.isNull() ? loadingContext.odfLoadingContext().stylesReader().layerSet(): pageLayerSet; int layerZIndex = 0; bool first = true; KoXmlElement layerElement; forEachElement( layerElement, usedPageLayerSet ) { KoShapeLayer * layer = 0; if ( first ) { first = false; layer = dynamic_cast( shapes().first() ); Q_ASSERT( layer ); } else { layer = new KoShapeLayer(); addShape( layer ); } if ( layer ) { layer->setZIndex( layerZIndex++ ); layer->loadOdf( layerElement, loadingContext ); } } KoShapeLayer * layer = dynamic_cast( shapes().first() ); if ( layer ) { KoXmlElement child; forEachElement( child, element ) { debugPageApp <<"loading shape" << child.localName(); KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf( child, loadingContext ); if ( shape ) { if( ! shape->parent() ) { layer->addShape( shape ); } } } } loadOdfPageExtra(element, paContext); return true; } void KoPAPageBase::loadOdfPageTag( const KoXmlElement &element, KoPALoadingContext &loadingContext ) { Q_UNUSED(element); KoStyleStack& styleStack = loadingContext.odfLoadingContext().styleStack(); if ( styleStack.hasProperty( KoXmlNS::draw, "fill" ) ) { setBackground(loadOdfFill(loadingContext)); } } void KoPAPageBase::loadOdfPageExtra( const KoXmlElement &element, KoPALoadingContext & loadingContext ) { Q_UNUSED( element ); Q_UNUSED( loadingContext ); } QSizeF KoPAPageBase::size() const { const KoPageLayout layout = pageLayout(); return QSize( layout.width, layout.height ); } QRectF KoPAPageBase::boundingRect() const { //return KoShapeContainer::boundingRect(); return contentRect().united(QRectF(QPointF(0, 0), size() )); } QRectF KoPAPageBase::contentRect() const { QRectF bb; foreach (KoShape* layer, shapes()) { if (bb.isNull()) { bb = layer->boundingRect(); } else { bb = bb.united(layer->boundingRect()); } } return bb; } void KoPAPageBase::shapeAdded( KoShape * shape ) { Q_UNUSED( shape ); } void KoPAPageBase::shapeRemoved( KoShape * shape ) { Q_UNUSED( shape ); } KoPageApp::PageType KoPAPageBase::pageType() const { return KoPageApp::Page; } QPixmap KoPAPageBase::thumbnail( const QSize& size ) { #ifdef CACHE_PAGE_THUMBNAILS QString key = thumbnailKey(); QPixmap pm; if ( !KoPAPixmapCache::instance()->find( key, size, pm ) ) { pm = generateThumbnail( size ); KoPAPixmapCache::instance()->insert( key, pm, size ); debugPageApp << "create thumbnail" << this << key << size; } else { //debugPageApp << "thumbnail in cache " << this; } return pm; #else return generateThumbnail( size ); #endif } QPixmap KoPAPageBase::generateThumbnail(const QSize &size) { // don't paint null pixmap if ( size.isEmpty() ) // either width or height is <= 0 return QPixmap(); KoZoomHandler zoomHandler; QSize thumbnailSize(size); KoPAUtil::setSizeAndZoom(pageLayout(), thumbnailSize, zoomHandler); QPixmap pixmap(thumbnailSize); // paint white as default page background pixmap.fill(Qt::white); QPainter painter(&pixmap); painter.setClipRect(QRect(QPoint(0, 0), thumbnailSize)); painter.setRenderHint(QPainter::Antialiasing); paintPage(painter, zoomHandler); return pixmap; } QImage KoPAPageBase::thumbImage(const QSize &size) { if (size.isEmpty()) { return QImage(); } KoZoomHandler zoomHandler; QSize thumbnailSize(size); KoPAUtil::setSizeAndZoom(pageLayout(), thumbnailSize, zoomHandler); QImage image(thumbnailSize, QImage::Format_RGB32); // paint white as default page background image.fill(QColor(Qt::white).rgb()); QPainter painter(&image); painter.setClipRect(QRect(QPoint(0, 0), thumbnailSize)); painter.setRenderHint(QPainter::Antialiasing); paintPage(painter, zoomHandler); return image; } void KoPAPageBase::pageUpdated() { KoPAPixmapCache::instance()->remove( thumbnailKey() ); } QString KoPAPageBase::thumbnailKey() const { QString key; key.sprintf( "%p", static_cast( this ) ); return key; } KoShapeManagerPaintingStrategy * KoPAPageBase::getPaintingStrategy() const { return 0; } diff --git a/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp b/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp index 60fddd99dca..6175951d34f 100644 --- a/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp +++ b/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp @@ -1,1472 +1,1474 @@ /* Copyright 2008 Brad Hards Copyright 2009 - 2010 Inge Wallin 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, see . */ #include "EmfOutputPainterStrategy.h" #include +#include + #include #include "EmfObjects.h" #define DEBUG_EMFPAINT 0 #define DEBUG_PAINTER_TRANSFORM 0 namespace Libemf { static QPainter::CompositionMode rasteropToQtComposition(long rop); // ================================================================ // Class OutputPainterStrategy OutputPainterStrategy::OutputPainterStrategy() : m_header( 0 ) , m_path( 0 ) , m_currentlyBuildingPath( false ) , m_fillRule(Qt::OddEvenFill) , m_mapMode(MM_TEXT) , m_textAlignMode(TA_NOUPDATECP) // == TA_TOP == TA_LEFT , m_currentCoords() { m_painter = 0; m_painterSaves = 0; m_outputSize = QSize(); m_keepAspectRatio = true; } OutputPainterStrategy::OutputPainterStrategy(QPainter &painter, QSize &size, bool keepAspectRatio) : m_header( 0 ) , m_path( 0 ) , m_currentlyBuildingPath( false ) , m_windowExtIsSet(false) , m_viewportExtIsSet(false) , m_windowViewportIsSet(false) , m_fillRule(Qt::OddEvenFill) , m_mapMode(MM_TEXT) , m_textAlignMode(TA_NOUPDATECP) // == TA_TOP == TA_LEFT , m_currentCoords() { m_painter = &painter; m_painterSaves = 0; m_outputSize = size; m_keepAspectRatio = keepAspectRatio; } OutputPainterStrategy::~OutputPainterStrategy() { delete m_header; delete m_path; } void OutputPainterStrategy::paintBounds(const Header *header) { // The rectangle is in device coordinates. QRectF rect(header->bounds()); m_painter->save(); // Draw a simple cross in a rectangle to show the bounds. m_painter->setPen(QPen(QColor(172, 196, 206), 0)); m_painter->drawRect(rect); m_painter->drawLine(rect.topLeft(), rect.bottomRight()); m_painter->drawLine(rect.bottomLeft(), rect.topRight()); m_painter->restore(); } void OutputPainterStrategy::init( const Header *header ) { // Save the header since we need the frame and bounds inside the drawing. m_header = new Header(*header); QSize headerBoundsSize = header->bounds().size(); #if DEBUG_EMFPAINT debugVectorImage << "----------------------------------------------------------------------"; debugVectorImage << "Shape size =" << m_outputSize.width() << m_outputSize.height() << " pt"; debugVectorImage << "----------------------------------------------------------------------"; debugVectorImage << "Boundary box (dev units) =" << header->bounds().x() << header->bounds().y() << header->bounds().width() << header->bounds().height(); debugVectorImage << "Frame (phys size) =" << header->frame().x() << header->frame().y() << header->frame().width() << header->frame().height() << " *0.01 mm"; debugVectorImage << "Device =" << header->device().width() << header->device().height(); debugVectorImage << "Millimeters =" << header->millimeters().width() << header->millimeters().height(); #endif #if DEBUG_PAINTER_TRANSFORM printPainterTransform("In init, before save:"); #endif // This is restored in cleanup(). m_painter->save(); // Calculate how much the painter should be resized to fill the // outputSize with output. qreal scaleX = qreal( m_outputSize.width() ) / headerBoundsSize.width(); qreal scaleY = qreal( m_outputSize.height() ) / headerBoundsSize.height(); if ( m_keepAspectRatio ) { // Use the smaller value so that we don't get an overflow in // any direction. if ( scaleX > scaleY ) scaleX = scaleY; else scaleY = scaleX; } #if DEBUG_EMFPAINT debugVectorImage << "scale = " << scaleX << ", " << scaleY; #endif // Transform the EMF object so that it fits in the shape. The // topleft of the EMF will be the top left of the shape. m_painter->scale( scaleX, scaleY ); m_painter->translate(-header->bounds().left(), -header->bounds().top()); #if DEBUG_PAINTER_TRANSFORM printPainterTransform("after fitting into shape"); #endif // Save the scale so that we can use it when setting lineWidth. m_outputScale = (scaleX + scaleY) / 2; // Calculate translation if we should center the EMF in the // area and keep the aspect ratio. #if 0 // Should apparently be upper left. See bug 265868 if ( m_keepAspectRatio ) { m_painter->translate((m_outputSize.width() / scaleX - headerBoundsSize.width()) / 2, (m_outputSize.height() / scaleY - headerBoundsSize.height()) / 2); #if DEBUG_PAINTER_TRANSFORM printPainterTransform("after translation for keeping center in the shape"); #endif } #endif m_outputTransform = m_painter->transform(); m_worldTransform = QTransform(); // For calculations of window / viewport during the painting m_windowOrg = QPoint(0, 0); m_viewportOrg = QPoint(0, 0); m_windowExtIsSet = false; m_viewportExtIsSet = false; m_windowViewportIsSet = false; #if DEBUG_EMFPAINT paintBounds(header); #endif } void OutputPainterStrategy::cleanup( const Header *header ) { Q_UNUSED( header ); #if DEBUG_EMFPAINT if (m_painterSaves > 0) debugVectorImage << "WARNING: UNRESTORED DC's:" << m_painterSaves; #endif // Restore all the save()s that were done during the processing. for (int i = 0; i < m_painterSaves; ++i) m_painter->restore(); m_painterSaves = 0; // Restore the painter to what it was before init() was called. m_painter->restore(); } void OutputPainterStrategy::eof() { } void OutputPainterStrategy::setPixelV( QPoint &point, quint8 red, quint8 green, quint8 blue, quint8 reserved ) { Q_UNUSED( reserved ); #if DEBUG_EMFPAINT debugVectorImage << point << red << green << blue; #endif m_painter->save(); QPen pen; pen.setColor( QColor( red, green, blue ) ); m_painter->setPen( pen ); m_painter->drawPoint( point ); m_painter->restore(); } void OutputPainterStrategy::beginPath() { #if DEBUG_EMFPAINT debugVectorImage; #endif delete( m_path ); m_path = new QPainterPath; m_currentlyBuildingPath = true; } void OutputPainterStrategy::closeFigure() { #if DEBUG_EMFPAINT debugVectorImage; #endif m_path->closeSubpath(); } void OutputPainterStrategy::endPath() { #if DEBUG_EMFPAINT debugVectorImage; #endif m_path->setFillRule( m_fillRule ); m_currentlyBuildingPath = false; } void OutputPainterStrategy::saveDC() { #if DEBUG_EMFPAINT debugVectorImage; #endif // A little trick here: Save the worldTransform in the painter. // If we didn't do this, we would have to create a separate stack // for these. // // FIXME: We should collect all the parts of the DC that are not // stored in the painter and save them separately. QTransform savedTransform = m_painter->worldTransform(); m_painter->setWorldTransform(m_worldTransform); m_painter->save(); ++m_painterSaves; m_painter->setWorldTransform(savedTransform); } void OutputPainterStrategy::restoreDC( const qint32 savedDC ) { #if DEBUG_EMFPAINT debugVectorImage << savedDC; #endif // Note that savedDC is always negative for (int i = 0; i < -savedDC; ++i) { if (m_painterSaves > 0) { m_painter->restore(); --m_painterSaves; } else { debugVectorImage << "restoreDC(): try to restore painter without save" << savedDC - i; break; } } // We used a trick in saveDC() and stored the worldTransform in // the painter. Now restore the full transformation. m_worldTransform = m_painter->worldTransform(); QTransform newMatrix = m_worldTransform * m_outputTransform; m_painter->setWorldTransform( newMatrix ); } void OutputPainterStrategy::setMetaRgn() { debugVectorImage << "EMR_SETMETARGN not yet implemented"; } // ---------------------------------------------------------------- // World Transform, Window and Viewport // General note about coordinate spaces and transforms: // // There are several coordinate spaces in use when drawing an EMF file: // 1. The object space, in which the objects' coordinates are expressed inside the EMF. // In general there are several of these. // 2. The page space, which is where they end up being painted in the EMF picture. // The union of these form the bounding box of the EMF. // 3. (possibly) the output space, where the EMF picture itself is placed // and/or scaled, rotated, etc // // The transform between spaces 1. and 2. is called the World Transform. // The world transform can be changed either through calls to change // the window or viewport or through calls to setWorldTransform() or // modifyWorldTransform(). // // The transform between spaces 2. and 3. is the transform that the QPainter // already contains when it is given to us. We need to save this and reapply // it after the world transform has changed. We call this transform the Output // Transform in lack of a better word. (Some sources call it the Device Transform.) // // An unanswered question: // // The file mp07_embedded_ppt.pptx in the test files contains the // following sequence of records: // - SetWorldTransform // - ModifyWorldTransform // - SetViewportOrg <-- doesn't change anything // - SetWindowOrg <-- doesn't change anything // - ExtTextOutw // // I was previously under the impression that whenever a // Set{Window,Viewport}{Org,Ext} record was encountered, the world // transform was supposed to be recalculated. But in this file, it // destroys the world transform. The question is which of the // following alternatives is true: // // 1. The world transform should only be recalculated if the // Set{Window,Viewport}{Org,Ext} record actually changes anything. // // 2. The transformations set by {Set,Modify}WorldTransform records // should always be reapplied after a change in window or viewport. // // 3. Something else // // I have for now implemented option 1. See the FIXME's in // SetWindowOrg et al. // // Set Window and Viewport void OutputPainterStrategy::recalculateWorldTransform() { m_worldTransform = QTransform(); // If neither the window nor viewport extension is set, then there // is no way to perform the calculation. Just give up. if (!m_windowExtIsSet && !m_viewportExtIsSet) return; // Negative window extensions mean flip the picture. Handle this here. bool flip = false; qreal midpointX = 0.0; qreal midpointY = 0.0; qreal scaleX = 1.0; qreal scaleY = 1.0; if (m_windowExt.width() < 0) { midpointX = m_windowOrg.x() + m_windowExt.width() / qreal(2.0); scaleX = -1.0; flip = true; } if (m_windowExt.height() < 0) { midpointY = m_windowOrg.y() + m_windowExt.height() / qreal(2.0); scaleY = -1.0; flip = true; } if (flip) { //debugVectorImage << "Flipping" << midpointX << midpointY << scaleX << scaleY; m_worldTransform.translate(midpointX, midpointY); m_worldTransform.scale(scaleX, scaleY); m_worldTransform.translate(-midpointX, -midpointY); //debugVectorImage << "After flipping for window" << mWorldTransform; } // Update the world transform if both window and viewport are set... // FIXME: Check windowExt == 0 in any direction if (m_windowExtIsSet && m_viewportExtIsSet) { // Both window and viewport are set. qreal windowViewportScaleX = qreal(m_viewportExt.width()) / qreal(m_windowExt.width()); qreal windowViewportScaleY = qreal(m_viewportExt.height()) / qreal(m_windowExt.height()); m_worldTransform.translate(-m_windowOrg.x(), -m_windowOrg.y()); m_worldTransform.scale(windowViewportScaleX, windowViewportScaleY); m_worldTransform.translate(m_viewportOrg.x(), m_viewportOrg.y()); } // ...and apply it to the painter m_painter->setWorldTransform(m_worldTransform); m_windowViewportIsSet = true; // Apply the output transform. QTransform newMatrix = m_worldTransform * m_outputTransform; m_painter->setWorldTransform( newMatrix ); } void OutputPainterStrategy::setWindowOrgEx( const QPoint &origin ) { #if DEBUG_EMFPAINT debugVectorImage << origin; #endif // FIXME: See unanswered question at the start of this section. if (m_windowOrg == origin) { //debugVectorImage << "same origin as before"; return; } m_windowOrg = origin; recalculateWorldTransform(); } void OutputPainterStrategy::setWindowExtEx( const QSize &size ) { #if DEBUG_EMFPAINT debugVectorImage << size; #endif // FIXME: See unanswered question at the start of this section. if (m_windowExt == size) { //debugVectorImage << "same extension as before"; return; } m_windowExt = size; m_windowExtIsSet = true; recalculateWorldTransform(); } void OutputPainterStrategy::setViewportOrgEx( const QPoint &origin ) { #if DEBUG_EMFPAINT debugVectorImage << origin; #endif // FIXME: See unanswered question at the start of this section. if (m_viewportOrg == origin) { //debugVectorImage << "same origin as before"; return; } m_viewportOrg = origin; recalculateWorldTransform(); } void OutputPainterStrategy::setViewportExtEx( const QSize &size ) { #if DEBUG_EMFPAINT debugVectorImage << size; #endif // FIXME: See unanswered question at the start of this section. if (m_viewportExt == size) { //debugVectorImage << "same extension as before"; return; } m_viewportExt = size; m_viewportExtIsSet = true; recalculateWorldTransform(); } void OutputPainterStrategy::modifyWorldTransform( quint32 mode, float M11, float M12, float M21, float M22, float Dx, float Dy ) { #if DEBUG_EMFPAINT if (mode == MWT_IDENTITY) debugVectorImage << "Identity matrix"; else debugVectorImage << mode << M11 << M12 << M21 << M22 << Dx << Dy; #endif QTransform matrix( M11, M12, M21, M22, Dx, Dy); if ( mode == MWT_IDENTITY ) { m_worldTransform = QTransform(); } else if ( mode == MWT_LEFTMULTIPLY ) { m_worldTransform = matrix * m_worldTransform; } else if ( mode == MWT_RIGHTMULTIPLY ) { m_worldTransform = m_worldTransform * matrix; } else if ( mode == MWT_SET ) { m_worldTransform = matrix; } else { warnVectorImage << "Unimplemented transform mode" << mode; } // Apply the output transform. QTransform newMatrix = m_worldTransform * m_outputTransform; m_painter->setWorldTransform( newMatrix ); } void OutputPainterStrategy::setWorldTransform( float M11, float M12, float M21, float M22, float Dx, float Dy ) { #if DEBUG_EMFPAINT debugVectorImage << M11 << M12 << M21 << M22 << Dx << Dy; #endif QTransform matrix( M11, M12, M21, M22, Dx, Dy); m_worldTransform = matrix; // Apply the output transform. QTransform newMatrix = m_worldTransform * m_outputTransform; m_painter->setWorldTransform( newMatrix ); } // ---------------------------------------------------------------- void OutputPainterStrategy::createPen( quint32 ihPen, quint32 penStyle, quint32 x, quint32 y, quint8 red, quint8 green, quint8 blue, quint8 reserved ) { Q_UNUSED( y ); Q_UNUSED( reserved ); #if DEBUG_EMFPAINT debugVectorImage << ihPen << hex << penStyle << dec << x << y << red << green << blue << reserved; #endif QPen pen; pen.setColor( QColor( red, green, blue ) ); if ( penStyle & PS_GEOMETRIC ) { pen.setCosmetic( false ); } else { pen.setCosmetic( true ); } switch ( penStyle & 0xF ) { case PS_SOLID: pen.setStyle( Qt::SolidLine ); break; case PS_DASH: pen.setStyle( Qt::DashLine ); break; case PS_DOT: pen.setStyle( Qt::DotLine ); break; case PS_DASHDOT: pen.setStyle( Qt::DashDotLine ); break; case PS_DASHDOTDOT: pen.setStyle( Qt::DashDotDotLine ); break; case PS_NULL: pen.setStyle( Qt::NoPen ); break; case PS_INSIDEFRAME: // FIXME: We don't properly support this pen.setStyle( Qt::SolidLine ); break; case PS_USERSTYLE: debugVectorImage << "UserStyle pen not yet supported, using SolidLine"; pen.setStyle( Qt::SolidLine ); break; case PS_ALTERNATE: debugVectorImage << "Alternate pen not yet supported, using DashLine"; pen.setStyle( Qt::DashLine ); break; default: debugVectorImage << "unexpected pen type, using SolidLine" << (penStyle & 0xF); pen.setStyle( Qt::SolidLine ); } switch ( penStyle & PS_ENDCAP_FLAT ) { case PS_ENDCAP_ROUND: pen.setCapStyle( Qt::RoundCap ); break; case PS_ENDCAP_SQUARE: pen.setCapStyle( Qt::SquareCap ); break; case PS_ENDCAP_FLAT: pen.setCapStyle( Qt::FlatCap ); break; default: debugVectorImage << "unexpected cap style, using SquareCap" << (penStyle & PS_ENDCAP_FLAT); pen.setCapStyle( Qt::SquareCap ); } pen.setWidthF(x * m_outputScale); m_objectTable.insert( ihPen, pen ); } void OutputPainterStrategy::createBrushIndirect( quint32 ihBrush, quint32 brushStyle, quint8 red, quint8 green, quint8 blue, quint8 reserved, quint32 brushHatch ) { Q_UNUSED( reserved ); Q_UNUSED( brushHatch ); #if DEBUG_EMFPAINT debugVectorImage << ihBrush << hex << brushStyle << dec << red << green << blue << reserved << brushHatch; #endif QBrush brush; switch ( brushStyle ) { case BS_SOLID: brush.setStyle( Qt::SolidPattern ); break; case BS_NULL: brush.setStyle( Qt::NoBrush ); break; case BS_HATCHED: brush.setStyle( Qt::CrossPattern ); break; case BS_PATTERN: Q_ASSERT( 0 ); break; case BS_INDEXED: Q_ASSERT( 0 ); break; case BS_DIBPATTERN: Q_ASSERT( 0 ); break; case BS_DIBPATTERNPT: Q_ASSERT( 0 ); break; case BS_PATTERN8X8: Q_ASSERT( 0 ); break; case BS_DIBPATTERN8X8: Q_ASSERT( 0 ); break; case BS_MONOPATTERN: Q_ASSERT( 0 ); break; default: Q_ASSERT( 0 ); } brush.setColor( QColor( red, green, blue ) ); // TODO: Handle the BrushHatch enum. m_objectTable.insert( ihBrush, brush ); } void OutputPainterStrategy::createMonoBrush( quint32 ihBrush, Bitmap *bitmap ) { QImage pattern(bitmap->image()); QBrush brush(pattern); m_objectTable.insert( ihBrush, brush ); } void OutputPainterStrategy::extCreateFontIndirectW( const ExtCreateFontIndirectWRecord &extCreateFontIndirectW ) { QFont font( extCreateFontIndirectW.fontFace() ); font.setWeight( convertFontWeight( extCreateFontIndirectW.weight() ) ); if ( extCreateFontIndirectW.height() < 0 ) { font.setPixelSize( -1 * extCreateFontIndirectW.height() ); } else if ( extCreateFontIndirectW.height() > 0 ) { font.setPixelSize( extCreateFontIndirectW.height() ); } // zero is "use a default size" which is effectively no-op here. // .snp files don't always provide 0x01 for italics if ( extCreateFontIndirectW.italic() != 0x00 ) { font.setItalic( true ); } if ( extCreateFontIndirectW.underline() != 0x00 ) { font.setUnderline( true ); } m_objectTable.insert( extCreateFontIndirectW.ihFonts(), font ); } void OutputPainterStrategy::selectStockObject( const quint32 ihObject ) { #if DEBUG_EMFPAINT debugVectorImage << ihObject; #endif switch ( ihObject ) { case WHITE_BRUSH: m_painter->setBrush( QBrush( Qt::white ) ); break; case LTGRAY_BRUSH: m_painter->setBrush( QBrush( Qt::lightGray ) ); break; case GRAY_BRUSH: m_painter->setBrush( QBrush( Qt::gray ) ); break; case DKGRAY_BRUSH: m_painter->setBrush( QBrush( Qt::darkGray ) ); break; case BLACK_BRUSH: m_painter->setBrush( QBrush( Qt::black ) ); break; case NULL_BRUSH: m_painter->setBrush( QBrush() ); break; case WHITE_PEN: m_painter->setPen( QPen( Qt::white ) ); break; case BLACK_PEN: m_painter->setPen( QPen( Qt::black ) ); break; case NULL_PEN: m_painter->setPen( QPen( Qt::NoPen ) ); break; case OEM_FIXED_FONT: case ANSI_FIXED_FONT: case SYSTEM_FIXED_FONT: { QFont font(QString("Fixed")); m_painter->setFont(font); break; } case ANSI_VAR_FONT: case DEFAULT_GUI_FONT: // Not sure if this is true, but it should work well { QFont font(QString("Helvetica")); // Could also be "System" m_painter->setFont(font); break; } break; case SYSTEM_FONT: // TODO: handle this break; case DEVICE_DEFAULT_FONT: // TODO: handle this break; case DEFAULT_PALETTE: break; case DC_BRUSH: // FIXME break; case DC_PEN: // FIXME break; default: warnVectorImage << "Unexpected stock object:" << ( ihObject & 0x8000000 ); } } void OutputPainterStrategy::selectObject( const quint32 ihObject ) { #if DEBUG_EMFPAINT debugVectorImage << hex << ihObject << dec; #endif if ( ihObject & 0x80000000 ) { selectStockObject( ihObject ); } else { QVariant obj = m_objectTable.value( ihObject ); switch ( obj.type() ) { case QVariant::Pen : m_painter->setPen( obj.value() ); break; case QVariant::Brush : m_painter->setBrush( obj.value() ); break; case QVariant::Font : m_painter->setFont( obj.value() ); break; default: debugVectorImage << "Unexpected type:" << obj.typeName(); } } } void OutputPainterStrategy::deleteObject( const quint32 ihObject ) { m_objectTable.take( ihObject ); } void OutputPainterStrategy::arc( const QRect &box, const QPoint &start, const QPoint &end ) { #if DEBUG_EMFPAINT debugVectorImage << box << start << end; #endif QPoint centrePoint = box.center(); qreal startAngle = angleFromArc( centrePoint, start ); qreal endAngle = angleFromArc( centrePoint, end ); qreal spanAngle = angularSpan( startAngle, endAngle ); m_painter->drawArc( box, startAngle*16, spanAngle*16 ); } void OutputPainterStrategy::chord( const QRect &box, const QPoint &start, const QPoint &end ) { #if DEBUG_EMFPAINT debugVectorImage << box << start << end; #endif QPoint centrePoint = box.center(); qreal startAngle = angleFromArc( centrePoint, start ); qreal endAngle = angleFromArc( centrePoint, end ); qreal spanAngle = angularSpan( startAngle, endAngle ); m_painter->drawChord( box, startAngle*16, spanAngle*16 ); } void OutputPainterStrategy::pie( const QRect &box, const QPoint &start, const QPoint &end ) { #if DEBUG_EMFPAINT debugVectorImage << box << start << end; #endif QPoint centrePoint = box.center(); qreal startAngle = angleFromArc( centrePoint, start ); qreal endAngle = angleFromArc( centrePoint, end ); qreal spanAngle = angularSpan( startAngle, endAngle ); m_painter->drawPie( box, startAngle*16, spanAngle*16 ); } void OutputPainterStrategy::ellipse( const QRect &box ) { #if DEBUG_EMFPAINT debugVectorImage << box; #endif m_painter->drawEllipse( box ); } void OutputPainterStrategy::rectangle( const QRect &box ) { #if DEBUG_EMFPAINT debugVectorImage << box; #endif m_painter->drawRect( box ); } void OutputPainterStrategy::setMapMode( const quint32 mapMode ) { #if DEBUG_EMFPAINT debugVectorImage << "Set map mode:" << mapMode; #endif m_mapMode = (MapMode)mapMode; } void OutputPainterStrategy::setBkMode( const quint32 backgroundMode ) { #if DEBUG_EMFPAINT debugVectorImage << backgroundMode; #endif if ( backgroundMode == TRANSPARENT ) { m_painter->setBackgroundMode( Qt::TransparentMode ); } else if ( backgroundMode == OPAQUE ) { m_painter->setBackgroundMode( Qt::OpaqueMode ); } else { debugVectorImage << "EMR_SETBKMODE: Unexpected value -" << backgroundMode; Q_ASSERT( 0 ); } } void OutputPainterStrategy::setPolyFillMode( const quint32 polyFillMode ) { #if DEBUG_EMFPAINT debugVectorImage << polyFillMode; #endif if ( polyFillMode == ALTERNATE ) { m_fillRule = Qt::OddEvenFill; } else if ( polyFillMode == WINDING ) { m_fillRule = Qt::WindingFill; } else { debugVectorImage << "EMR_SETPOLYFILLMODE: Unexpected value -" << polyFillMode; Q_ASSERT( 0 ); } } void OutputPainterStrategy::setLayout( const quint32 layoutMode ) { #if DEBUG_EMFPAINT debugVectorImage << layoutMode; #endif if ( layoutMode == LAYOUT_LTR ) { m_painter->setLayoutDirection( Qt::LeftToRight ); } else if ( layoutMode == LAYOUT_RTL ) { m_painter->setLayoutDirection( Qt::RightToLeft ); } else { debugVectorImage << "EMR_SETLAYOUT: Unexpected value -" << layoutMode; Q_ASSERT( 0 ); } } void OutputPainterStrategy::setTextAlign( const quint32 textAlignMode ) { #if DEBUG_EMFPAINT debugVectorImage << textAlignMode; #endif m_textAlignMode = textAlignMode; } void OutputPainterStrategy::setTextColor( const quint8 red, const quint8 green, const quint8 blue, const quint8 reserved ) { Q_UNUSED( reserved ); #if DEBUG_EMFPAINT debugVectorImage << red << green << blue << reserved; #endif m_textPen.setColor( QColor( red, green, blue ) ); } void OutputPainterStrategy::setBkColor( const quint8 red, const quint8 green, const quint8 blue, const quint8 reserved ) { Q_UNUSED( reserved ); #if DEBUG_EMFPAINT debugVectorImage << red << green << blue << reserved; #endif m_painter->setBackground( QBrush( QColor( red, green, blue ) ) ); } #define DEBUG_TEXTOUT 0 void OutputPainterStrategy::extTextOut( const QRect &bounds, const EmrTextObject &textObject ) { const QPoint &referencePoint = textObject.referencePoint(); const QString &text = textObject.textString(); #if DEBUG_EMFPAINT debugVectorImage << "Ref point: " << referencePoint << "options: " << hex << textObject.options() << dec << "rectangle: " << textObject.rectangle() << "text: " << textObject.textString(); #endif int x = referencePoint.x(); int y = referencePoint.y(); // The TA_UPDATECP flag tells us to use the current position if (m_textAlignMode & TA_UPDATECP) { // (left, top) position = current logical position #if DEBUG_EMFPAINT debugVectorImage << "TA_UPDATECP: use current logical position"; #endif x = m_currentCoords.x(); y = m_currentCoords.y(); } QFontMetrics fm = m_painter->fontMetrics(); int textWidth = fm.width(text) + fm.descent(); // fm.width(text) isn't right with Italic text int textHeight = fm.height(); // Make (x, y) be the coordinates of the upper left corner of the // rectangle surrounding the text. // // FIXME: Handle RTL text. // Horizontal align. Default is TA_LEFT. if ((m_textAlignMode & TA_HORZMASK) == TA_CENTER) x -= (textWidth / 2); else if ((m_textAlignMode & TA_HORZMASK) == TA_RIGHT) x -= textWidth; // Vertical align. Default is TA_TOP if ((m_textAlignMode & TA_VERTMASK) == TA_BASELINE) y -= (textHeight - fm.descent()); else if ((m_textAlignMode & TA_VERTMASK) == TA_BOTTOM) { y -= textHeight; } #if DEBUG_EMFPAINT debugVectorImage << "textWidth = " << textWidth << "height = " << textHeight; debugVectorImage << "font = " << m_painter->font() << "pointSize = " << m_painter->font().pointSize() << "ascent = " << fm.ascent() << "descent = " << fm.descent() << "height = " << fm.height() << "leading = " << fm.leading(); debugVectorImage << "actual point = " << x << y; #endif // Debug code that paints a rectangle around the output area. #if DEBUG_TEXTOUT m_painter->save(); m_painter->setWorldTransform(m_outputTransform); m_painter->setPen(QPen(Qt::black, 0)); m_painter->drawRect(bounds); m_painter->restore(); #endif // Actual painting starts here. m_painter->save(); // Find out how much we have to scale the text to make it fit into // the output rectangle. Normally this wouldn't be necessary, but // when fonts are switched, the replacement fonts are sometimes // wider than the original fonts. QRect worldRect(m_worldTransform.mapRect(QRect(x, y, textWidth, textHeight))); //debugVectorImage << "rects:" << QRect(x, y, textWidth, textHeight) << worldRect; qreal scaleX = qreal(1.0); qreal scaleY = qreal(1.0); if (bounds.width() < worldRect.width()) scaleX = qreal(bounds.width()) / qreal(worldRect.width()); if (bounds.height() < worldRect.height()) scaleY = qreal(bounds.height()) / qreal(worldRect.height()); //debugVectorImage << "scale:" << scaleX << scaleY; if (scaleX < qreal(1.0) || scaleY < qreal(1.0)) { m_painter->translate(-x, -y); m_painter->scale(scaleX, scaleY); m_painter->translate(x / scaleX, y / scaleY); } // Use the special pen defined by mTextPen for text. QPen savePen = m_painter->pen(); m_painter->setPen(m_textPen); m_painter->drawText(int(x / scaleX), int(y / scaleY), textWidth, textHeight, Qt::AlignLeft|Qt::AlignTop, text); m_painter->setPen(savePen); m_painter->restore(); } void OutputPainterStrategy::moveToEx( const qint32 x, const qint32 y ) { #if DEBUG_EMFPAINT debugVectorImage << x << y; #endif if ( m_currentlyBuildingPath ) m_path->moveTo( QPoint( x, y ) ); else m_currentCoords = QPoint( x, y ); } void OutputPainterStrategy::lineTo( const QPoint &finishPoint ) { #if DEBUG_EMFPAINT debugVectorImage << finishPoint; #endif if ( m_currentlyBuildingPath ) m_path->lineTo( finishPoint ); else { m_painter->drawLine( m_currentCoords, finishPoint ); m_currentCoords = finishPoint; } } void OutputPainterStrategy::arcTo( const QRect &box, const QPoint &start, const QPoint &end ) { #if DEBUG_EMFPAINT debugVectorImage << box << start << end; #endif QPoint centrePoint = box.center(); qreal startAngle = angleFromArc( centrePoint, start ); qreal endAngle = angleFromArc( centrePoint, end ); qreal spanAngle = angularSpan( startAngle, endAngle ); m_path->arcTo( box, startAngle, spanAngle ); } void OutputPainterStrategy::polygon16( const QRect &bounds, const QList points ) { Q_UNUSED( bounds ); #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif QVector pointVector = points.toVector(); m_painter->drawPolygon( pointVector.constData(), pointVector.size(), m_fillRule ); } void OutputPainterStrategy::polyLine( const QRect &bounds, const QList points ) { Q_UNUSED( bounds ); #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif QVector pointVector = points.toVector(); m_painter->drawPolyline( pointVector.constData(), pointVector.size() ); } void OutputPainterStrategy::polyLine16( const QRect &bounds, const QList points ) { #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif polyLine( bounds, points ); } void OutputPainterStrategy::polyPolygon16( const QRect &bounds, const QList< QVector< QPoint > > &points ) { Q_UNUSED( bounds ); #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif for ( int i = 0; i < points.size(); ++i ) { m_painter->drawPolygon( points[i].constData(), points[i].size(), m_fillRule ); } } void OutputPainterStrategy::polyPolyLine16( const QRect &bounds, const QList< QVector< QPoint > > &points ) { Q_UNUSED( bounds ); #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif for ( int i = 0; i < points.size(); ++i ) { m_painter->drawPolyline( points[i].constData(), points[i].size() ); } } void OutputPainterStrategy::polyLineTo16( const QRect &bounds, const QList points ) { Q_UNUSED( bounds ); #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif for ( int i = 0; i < points.count(); ++i ) { m_path->lineTo( points[i] ); } } void OutputPainterStrategy::polyBezier16( const QRect &bounds, const QList points ) { #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif Q_UNUSED( bounds ); QPainterPath path; path.moveTo( points[0] ); for ( int i = 1; i < points.count(); i+=3 ) { path.cubicTo( points[i], points[i+1], points[i+2] ); } m_painter->drawPath( path ); } void OutputPainterStrategy::polyBezierTo16( const QRect &bounds, const QList points ) { #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif Q_UNUSED( bounds ); for ( int i = 0; i < points.count(); i+=3 ) { m_path->cubicTo( points[i], points[i+1], points[i+2] ); } } void OutputPainterStrategy::fillPath( const QRect &bounds ) { #if DEBUG_EMFPAINT debugVectorImage << bounds; #endif Q_UNUSED( bounds ); m_painter->fillPath( *m_path, m_painter->brush() ); } void OutputPainterStrategy::strokeAndFillPath( const QRect &bounds ) { #if DEBUG_EMFPAINT debugVectorImage << bounds; #endif Q_UNUSED( bounds ); m_painter->drawPath( *m_path ); } void OutputPainterStrategy::strokePath( const QRect &bounds ) { #if DEBUG_EMFPAINT debugVectorImage << bounds; #endif Q_UNUSED( bounds ); m_painter->strokePath( *m_path, m_painter->pen() ); } void OutputPainterStrategy::setClipPath( const quint32 regionMode ) { #if DEBUG_EMFPAINT debugVectorImage << hex << regionMode << dec; #endif switch ( regionMode ) { case RGN_AND: m_painter->setClipPath( *m_path, Qt::IntersectClip ); break; // QT5TODO: T477 Qt::UniteClip got removed, see https://bugreports.qt.io/browse/QTBUG-20140 // case RGN_OR: // m_painter->setClipPath( *m_path, Qt::UniteClip ); // break; case RGN_COPY: m_painter->setClipPath( *m_path, Qt::ReplaceClip ); break; default: warnVectorImage << "Unexpected / unsupported clip region mode:" << regionMode; Q_ASSERT( 0 ); } } void OutputPainterStrategy::bitBlt( BitBltRecord &bitBltRecord ) { #if DEBUG_EMFPAINT debugVectorImage << bitBltRecord.xDest() << bitBltRecord.yDest() << bitBltRecord.cxDest() << bitBltRecord.cyDest() << hex << bitBltRecord.rasterOperation() << dec << bitBltRecord.bkColorSrc(); #endif QRect target( bitBltRecord.xDest(), bitBltRecord.yDest(), bitBltRecord.cxDest(), bitBltRecord.cyDest() ); // 0x00f00021 is the PatCopy raster operation which just fills a rectangle with a brush. // This seems to be the most common one. // // FIXME: Implement the rest of the raster operations. if (bitBltRecord.rasterOperation() == 0x00f00021) { // Would have been nice if we didn't have to pull out the // brush to use it with fillRect()... QBrush brush = m_painter->brush(); m_painter->fillRect(target, brush); } else if ( bitBltRecord.hasImage() ) { m_painter->drawImage( target, bitBltRecord.image() ); } } void OutputPainterStrategy::setStretchBltMode( const quint32 stretchMode ) { #if DEBUG_EMFPAINT debugVectorImage << hex << stretchMode << dec; #endif switch ( stretchMode ) { case 0x01: debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_ANDSCANS"; break; case 0x02: debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_ORSCANS"; break; case 0x03: debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_DELETESCANS"; break; case 0x04: debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_HALFTONE"; break; default: debugVectorImage << "EMR_STRETCHBLTMODE - unknown stretch mode:" << stretchMode; } } void OutputPainterStrategy::stretchDiBits( StretchDiBitsRecord &record ) { #if DEBUG_EMFPAINT debugVectorImage << "Bounds: " << record.bounds(); debugVectorImage << "Dest rect: " << record.xDest() << record.yDest() << record.cxDest() << record.cyDest(); debugVectorImage << "Src rect: " << record.xSrc() << record.ySrc() << record.cxSrc() << record.cySrc(); debugVectorImage << "Raster op: " << hex << record.rasterOperation() << dec; //<< record.bkColorSrc(); debugVectorImage << "usageSrc: " << record.usageSrc(); #endif QPoint targetPosition( record.xDest(), record.yDest() ); QSize targetSize( record.cxDest(), record.cyDest() ); QPoint sourcePosition( record.xSrc(), record.ySrc() ); QSize sourceSize( record.cxSrc(), record.cySrc() ); // special cases, from [MS-EMF] Section 2.3.1.7: // "This record specifies a mirror-image copy of the source bitmap to the // destination if the signs of the height or width fields differ. That is, // if cxSrc and cxDest have different signs, this record specifies a mirror // image of the source bitmap along the x-axis. If cySrc and cyDest have // different signs, this record specifies a mirror image of the source // bitmap along the y-axis." QRect target( targetPosition, targetSize ); QRect source( sourcePosition, sourceSize ); #if DEBUG_EMFPAINT //debugVectorImage << "image size" << record.image()->size(); debugVectorImage << "Before transformation:"; debugVectorImage << " target" << target; debugVectorImage << " source" << source; #endif if ( source.width() < 0 && target.width() > 0 ) { sourceSize.rwidth() *= -1; sourcePosition.rx() -= sourceSize.width(); source = QRect( sourcePosition, sourceSize ); } if ( source.width() > 0 && target.width() < 0 ) { targetSize.rwidth() *= -1; targetPosition.rx() -= targetSize.width(); target = QRect( targetPosition, targetSize ); } if ( source.height() < 0 && target.height() > 0 ) { sourceSize.rheight() *= -1; sourcePosition.ry() -= sourceSize.height(); source = QRect( sourcePosition, sourceSize ); } if ( source.height() > 0 && target.height() < 0 ) { targetSize.rheight() *= -1; targetPosition.ry() -= targetSize.height(); target = QRect( targetPosition, targetSize ); } #if DEBUG_EMFPAINT debugVectorImage << "After transformation:"; debugVectorImage << " target" << target; debugVectorImage << " source" << source; QImage image = record.image(); debugVectorImage << "Image" << image.size(); #endif QPainter::RenderHints oldRenderHints = m_painter->renderHints(); QPainter::CompositionMode oldCompMode = m_painter->compositionMode(); m_painter->setRenderHints(0); // Antialiasing makes composition modes invalid m_painter->setCompositionMode(rasteropToQtComposition(record.rasterOperation())); m_painter->drawImage(target, record.image(), source); m_painter->setCompositionMode(oldCompMode); m_painter->setRenderHints(oldRenderHints); } // ---------------------------------------------------------------- // Private functions void OutputPainterStrategy::printPainterTransform(const char *leadText) { QTransform transform; recalculateWorldTransform(); debugVectorImage << leadText << "world transform " << m_worldTransform << "incl output transform: " << m_painter->transform(); } qreal OutputPainterStrategy::angleFromArc( const QPoint ¢rePoint, const QPoint &radialPoint ) { double dX = radialPoint.x() - centrePoint.x(); double dY = centrePoint.y() - radialPoint.y(); // Qt angles are in degrees. atan2 returns radians return ( atan2( dY, dX ) * 180 / M_PI ); } qreal OutputPainterStrategy::angularSpan( const qreal startAngle, const qreal endAngle ) { qreal spanAngle = endAngle - startAngle; if ( spanAngle <= 0 ) { spanAngle += 360; } return spanAngle; } int OutputPainterStrategy::convertFontWeight( quint32 emfWeight ) { // FIXME: See how it's done in the wmf library and check if this is suitable here. if ( emfWeight == 0 ) { return QFont::Normal; } else if ( emfWeight <= 200 ) { return QFont::Light; } else if ( emfWeight <= 450 ) { return QFont::Normal; } else if ( emfWeight <= 650 ) { return QFont::DemiBold; } else if ( emfWeight <= 850 ) { return QFont::Bold; } else { return QFont::Black; } } static QPainter::CompositionMode rasteropToQtComposition(long rop) { // Code copied from filters/libkowmf/qwmf.cc // FIXME: Should be cleaned up /* TODO: Ternary raster operations 0x00C000CA dest = (source AND pattern) 0x00F00021 dest = pattern 0x00FB0A09 dest = DPSnoo 0x005A0049 dest = pattern XOR dest */ static const struct OpTab { long winRasterOp; QPainter::CompositionMode qtRasterOp; } opTab[] = { // ### untested (conversion from Qt::RasterOp) { 0x00CC0020, QPainter::CompositionMode_Source }, // CopyROP { 0x00EE0086, QPainter::RasterOp_SourceOrDestination }, // OrROP { 0x008800C6, QPainter::RasterOp_SourceAndDestination }, // AndROP { 0x00660046, QPainter::RasterOp_SourceXorDestination }, // XorROP // ---------------------------------------------------------------- // FIXME: Checked above this, below is still todo // ---------------------------------------------------------------- { 0x00440328, QPainter::CompositionMode_DestinationOut }, // AndNotROP { 0x00330008, QPainter::CompositionMode_DestinationOut }, // NotCopyROP { 0x001100A6, QPainter::CompositionMode_SourceOut }, // NandROP { 0x00C000CA, QPainter::CompositionMode_Source }, // CopyROP { 0x00BB0226, QPainter::CompositionMode_Destination }, // NotOrROP { 0x00F00021, QPainter::CompositionMode_Source }, // CopyROP { 0x00FB0A09, QPainter::CompositionMode_Source }, // CopyROP { 0x005A0049, QPainter::CompositionMode_Source }, // CopyROP { 0x00550009, QPainter::CompositionMode_DestinationOut }, // NotROP { 0x00000042, QPainter::CompositionMode_Clear }, // ClearROP { 0x00FF0062, QPainter::CompositionMode_Source } // SetROP }; int i; for (i = 0 ; i < 15 ; i++) if (opTab[i].winRasterOp == rop) break; if (i < 15) return opTab[i].qtRasterOp; else return QPainter::CompositionMode_Source; } } // xnamespace... diff --git a/libs/vectorimage/libsvm/SvmPainterBackend.cpp b/libs/vectorimage/libsvm/SvmPainterBackend.cpp index 77d71adab8d..5f35cf31afa 100644 --- a/libs/vectorimage/libsvm/SvmPainterBackend.cpp +++ b/libs/vectorimage/libsvm/SvmPainterBackend.cpp @@ -1,225 +1,226 @@ /* Copyright 2009 Inge Wallin 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, see . */ // Own #include "SvmPainterBackend.h" // Qt #include #include #include #include #include +#include // Libsvm #include "SvmEnums.h" #include "SvmStructs.h" #include "SvmGraphicsContext.h" // lib #include "VectorImageDebug.h" #define DEBUG_SVMPAINT 0 /** Namespace for StarView Metafile (SVM) classes */ namespace Libsvm { SvmPainterBackend::SvmPainterBackend(QPainter *painter, const QSize &outputSize) : m_painter(painter) , m_outputSize(outputSize) { } SvmPainterBackend::~SvmPainterBackend() { } void SvmPainterBackend::init(const SvmHeader &header) { // This is restored in cleanup(). m_painter->save(); qreal scaleX = qreal( m_outputSize.width() ) / header.width; qreal scaleY = qreal( m_outputSize.height() ) / header.height; #if DEBUG_SVMPAINT debugVectorImage << "scale before:" << scaleX << ", " << scaleY; #endif // Keep aspect ratio. Use the smaller value so that we don't get // an overflow in any direction. #if 0 // Set this to 1 to keep aspect ratio. if ( scaleX > scaleY ) scaleX = scaleY; else scaleY = scaleX; #endif #if DEBUG_SVMPAINT debugVectorImage << "shape size:" << m_outputSize; debugVectorImage << "scale after:" << scaleX << ", " << scaleY; #endif // Transform the SVM object so that it fits in the shape as much // as possible. The topleft will be the top left of the shape. m_painter->scale( scaleX, scaleY ); //m_painter->translate(-header->bounds().left(), -header->bounds().top()); m_outputTransform = m_painter->transform(); //m_worldTransform = QTransform(); m_painter->setRenderHint(QPainter::Antialiasing); m_painter->setRenderHint(QPainter::TextAntialiasing); } void SvmPainterBackend::cleanup() { // Restore the painter to what it was before init() was called. m_painter->restore(); } void SvmPainterBackend::eof() { } // ---------------------------------------------------------------- // Graphics output void SvmPainterBackend::rect( SvmGraphicsContext &context, const QRect &rect ) { updateFromGraphicscontext(context); m_painter->drawRect(rect); } void SvmPainterBackend::polyLine( SvmGraphicsContext &context, const QPolygon &polyline ) { updateFromGraphicscontext(context); m_painter->drawPolyline(polyline); } void SvmPainterBackend::polygon( SvmGraphicsContext &context, const QPolygon &polygon ) { updateFromGraphicscontext(context); m_painter->drawPolygon(polygon); } void SvmPainterBackend::polyPolygon(SvmGraphicsContext &context, const QList &polyPolygon) { updateFromGraphicscontext(context); QPainterPath path; path.setFillRule(Qt::OddEvenFill); //path.setFillRule(Qt::WindingFill); foreach (const QPolygon &polygon, polyPolygon) { path.addPolygon(polygon); } m_painter->drawPath(path); } void SvmPainterBackend::textArray(SvmGraphicsContext &context, const QPoint &point, const QString &string, quint16 startIndex, quint16 len, quint32 dxArrayLen, qint32 *dxArray) { updateFromGraphicscontext(context); m_painter->save(); m_painter->setPen(context.textColor); // FIXME: Handle text background color. How do we get the area? A testfile would be nice. m_painter->drawText(point, string.mid(startIndex, len)); // FIXME: DxArray not handled yet. Q_UNUSED(dxArrayLen); Q_UNUSED(dxArray); m_painter->restore(); } // ---------------------------------------------------------------- // Private functions void SvmPainterBackend::updateFromGraphicscontext(SvmGraphicsContext &context) { if (context.changedItems & GCLineColor) { QPen pen = m_painter->pen(); if (context.lineColorSet) { pen.setColor(context.lineColor); pen.setStyle(Qt::SolidLine); } else pen.setStyle(Qt::NoPen); m_painter->setPen(pen); #if DEBUG_SVMPAINT debugVectorImage << "*** Setting line color to" << context.lineColor; #endif } if (context.changedItems & GCFillColor) { QBrush brush(m_painter->brush()); if (context.fillColorSet) { brush.setColor(context.fillColor); brush.setStyle(Qt::SolidPattern); } else brush.setStyle(Qt::NoBrush); m_painter->setBrush(brush); #if DEBUG_SVMPAINT if (context.fillColorSet) debugVectorImage << "*** Setting fill color to" << context.fillColor; else debugVectorImage << "*** Unsetting fill color"; #endif } // GCTextColor: We don't need to do anything here since text color // is set when the text is drawn. // GCTextFillColor: We don't need to do anything here since text // fill color is set when the text is drawn. // GCTextAlign: We don't need to do anything here since text // alignment is only used when the text is drawn. if (context.changedItems & GCMapMode) { // Reset the transform and then apply the new mapmode to it. m_painter->setTransform(m_outputTransform); m_painter->translate(context.mapMode.origin); // FIXME: Do scaling here too. But we need a testfile for that. } if (context.changedItems & GCFont) { m_painter->setFont(context.font); #if DEBUG_SVMPAINT debugVectorImage << "*** Setting font to" << context.font; #endif } if (context.changedItems & GCOverlineColor) { // FIXME } // Reset all changes until next time. context.changedItems = 0; } } diff --git a/libs/widgets/KoMarkerItemDelegate.cpp b/libs/widgets/KoMarkerItemDelegate.cpp index 9385a91ee5a..0d191b8bef4 100644 --- a/libs/widgets/KoMarkerItemDelegate.cpp +++ b/libs/widgets/KoMarkerItemDelegate.cpp @@ -1,72 +1,73 @@ /* This file is part of the KDE project * Copyright (C) 2011 Thorsten Zachmann * Copyright (C) 2011 Jean-Nicolas Artaud * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoMarkerItemDelegate.h" #include #include #include +#include #include KoMarkerItemDelegate::KoMarkerItemDelegate(KoMarkerData::MarkerPosition position, QObject *parent) : QAbstractItemDelegate(parent) , m_position(position) { } void KoMarkerItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { painter->save(); if (option.state & QStyle::State_Selected) painter->fillRect(option.rect, option.palette.highlight()); bool antialiasing = painter->testRenderHint(QPainter::Antialiasing); if (!antialiasing) { painter->setRenderHint(QPainter::Antialiasing, true); } KoPathShape pathShape; pathShape.moveTo(QPointF(option.rect.left(), option.rect.center().y())); pathShape.lineTo(QPointF(option.rect.right(), option.rect.center().y())); KoMarker *marker = index.data(Qt::DecorationRole).value(); if (marker != nullptr) { pathShape.setMarker(marker, m_position); } // paint marker QPen pen(option.palette.text(), 2); QPainterPath path = pathShape.pathStroke(pen); painter->fillPath(path, pen.brush()); if (!antialiasing) { painter->setRenderHint(QPainter::Antialiasing, false); } painter->restore(); } QSize KoMarkerItemDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex &index) const { Q_UNUSED(option) Q_UNUSED(index) return QSize(80,30); } diff --git a/plugins/chartshape/ChartShape.cpp b/plugins/chartshape/ChartShape.cpp index 2628b17bd13..b5b07a1fdba 100644 --- a/plugins/chartshape/ChartShape.cpp +++ b/plugins/chartshape/ChartShape.cpp @@ -1,1301 +1,1302 @@ /* This file is part of the KDE project Copyright 2007 Stefan Nikolaus Copyright 2007-2010 Inge Wallin Copyright 2007-2008 Johannes Simon Copyright 2017 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Own #include "ChartShape.h" // Posix #include // For basic data types characteristics. // Qt #include #include +#include #include #include #include // KF5 #include // KChart #include #include #include #include #include #include "KChartConvertions.h" // Attribute Classes #include #include #include #include #include #include #include // Diagram Classes #include #include #include #include #include // Calligra #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 // KoChart #include "Axis.h" #include "DataSet.h" #include "Legend.h" #include "PlotArea.h" #include "Surface.h" #include "ChartProxyModel.h" #include "TextLabelDummy.h" #include "ChartDocument.h" #include "ChartTableModel.h" #include "ChartLayout.h" #include "TableSource.h" #include "OdfLoadingHelper.h" #include "SingleModelHelper.h" #include "OdfHelper.h" #include "ChartDebug.h" // Define the protocol used here for embedded documents' URL // This used to "store" but KUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" #define INTERNAL_PROTOCOL "intern" namespace KoChart { /// @see ChartShape::setEnableUserInteraction() static bool ENABLE_USER_INTERACTION = true; static const char * const ODF_CHARTTYPES[NUM_CHARTTYPES] = { "chart:bar", "chart:line", "chart:area", "chart:circle", "chart:ring", "chart:scatter", "chart:radar", "chart:filled-radar", "chart:stock", "chart:bubble", "chart:surface", "chart:gantt" }; static const ChartSubtype defaultSubtypes[NUM_CHARTTYPES] = { NormalChartSubtype, // Bar NormalChartSubtype, // Line NormalChartSubtype, // Area NoChartSubtype, // Circle NoChartSubtype, // Ring NoChartSubtype, // Scatter NormalChartSubtype, // Radar NormalChartSubtype, // Filled Radar HighLowCloseChartSubtype, // Stock NoChartSubtype, // Bubble NoChartSubtype, // Surface NoChartSubtype // Gantt }; const char * odfCharttype(int charttype) { Q_ASSERT(charttype < LastChartType); if (charttype >= LastChartType || charttype < 0) { charttype = 0; } return ODF_CHARTTYPES[charttype]; } static const int NUM_DEFAULT_DATASET_COLORS = 12; static const char * const defaultDataSetColors[NUM_DEFAULT_DATASET_COLORS] = { "#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021", "#83caff", "#314004", "#aecf00", "#4b1f6f", "#ff950e", "#c5000b", "#0084d1", }; QColor defaultDataSetColor(int dataSetNum) { dataSetNum %= NUM_DEFAULT_DATASET_COLORS; return QColor(defaultDataSetColors[dataSetNum]); } // ================================================================ // The Private class class ChartShape::Private { public: Private(ChartShape *shape); ~Private(); void setChildVisible(KoShape *label, bool doShow); // The components of a chart KoShape *title; KoShape *subTitle; KoShape *footer; Legend *legend; PlotArea *plotArea; // Data ChartProxyModel *proxyModel; /// What's presented to KChart ChartTableModel *internalModel; TableSource tableSource; SingleModelHelper *internalModelHelper; bool usesInternalModelOnly; /// @see usesInternalModelOnly() ChartDocument *document; ChartShape *shape; // The chart that owns this ChartShape::Private KoDocumentResourceManager *resourceManager; }; ChartShape::Private::Private(ChartShape *shape) : internalModel(0) , internalModelHelper(0) , resourceManager(0) { // Register the owner. this->shape = shape; // Components title = 0; subTitle = 0; footer = 0; legend = 0; plotArea = 0; // Data proxyModel = 0; // If not explicitly set otherwise, this chart provides its own data. usesInternalModelOnly = true; document = 0; } ChartShape::Private::~Private() { } // // Show a child, which means either the Title, Subtitle, Footer or Axis Title. // // If there is too little room, then make space by shrinking the Plotarea. // void ChartShape::Private::setChildVisible(KoShape *child, bool doShow) { Q_ASSERT(child); if (child->isVisible() == doShow) return; child->setVisible(doShow); // FIXME: Shouldn't there be a KoShape::VisibilityChanged for KoShape::shapeChanged()? shape->layout()->scheduleRelayout(); } // ================================================================ // Class ChartShape // ================================================================ ChartShape::ChartShape(KoDocumentResourceManager *resourceManager) : KoFrameShape(KoXmlNS::draw, "object") , KoShapeContainer(new ChartLayout) , d (new Private(this)) { d->resourceManager = resourceManager; setShapeId(ChartShapeId); // Instantiated all children first d->proxyModel = new ChartProxyModel(this, &d->tableSource); d->plotArea = new PlotArea(this); d->document = new ChartDocument(this); d->legend = new Legend(this); // Configure the plotarea. // We need this as the very first step, because some methods // here rely on the d->plotArea pointer. addShape(d->plotArea); d->plotArea->plotAreaInit(); d->plotArea->setZIndex(0); setClipped(d->plotArea, true); setInheritsTransform(d->plotArea, true); d->plotArea->setDeletable(false); d->plotArea->setToolDelegates(QSet()<plotArea->setAllowedInteraction(KoShape::ShearingAllowed, false); // Configure the legend. d->legend->setVisible(true); d->legend->setZIndex(1); setClipped(d->legend, true); setInheritsTransform(d->legend, true); d->legend->setDeletable(false); d->legend->setToolDelegates(QSet()<legend->setAllowedInteraction(KoShape::ShearingAllowed, false); // A few simple defaults (chart type and subtype in this case) setChartType(BarChartType); setChartSubType(NormalChartSubtype); // Create the Title, which is a standard TextShape. KoShapeFactoryBase *textShapeFactory = KoShapeRegistry::instance()->value(TextShapeId); if (textShapeFactory) d->title = textShapeFactory->createDefaultShape(resourceManager); // Potential problem 1) No TextShape installed if (!d->title) { d->title = new TextLabelDummy; if (ENABLE_USER_INTERACTION) KMessageBox::error(0, i18n("The plugin needed for displaying text labels in a chart is not available."), i18n("Plugin Missing")); // Potential problem 2) TextShape incompatible } else if (dynamic_cast(d->title->userData()) == 0 && ENABLE_USER_INTERACTION) KMessageBox::error(0, i18n("The plugin needed for displaying text labels is not compatible with the current version of the chart Flake shape."), i18n("Plugin Incompatible")); // In both cases we need a KoTextShapeData instance to function. This is // enough for unit tests, so there has to be no TextShape plugin doing the // actual text rendering, we just need KoTextShapeData which is in the libs. TextLabelData *labelData = dynamic_cast(d->title->userData()); if (labelData == 0) { labelData = new TextLabelData; KoTextDocumentLayout *documentLayout = new KoTextDocumentLayout(labelData->document()); labelData->document()->setDocumentLayout(documentLayout); d->title->setUserData(labelData); } // Add the title to the shape addShape(d->title); QFont font = titleData()->document()->defaultFont(); font.setPointSizeF(12.0); titleData()->document()->setDefaultFont(font); titleData()->document()->setPlainText(i18n("Title")); // Set a reasonable size, it will be resized automatically d->title->setSize(QSizeF(CM_TO_POINT(5), CM_TO_POINT(0.7))); d->title->setVisible(false); d->title->setZIndex(2); setClipped(d->title, true); setInheritsTransform(d->title, true); d->title->setDeletable(false); d->title->setToolDelegates(QSet()<title); // Enable chart tool labelData->setResizeMethod(KoTextShapeDataBase::AutoResize); d->title->setAdditionalStyleAttribute("chart:auto-position", "true"); d->title->setAllowedInteraction(KoShape::ShearingAllowed, false); // Create the Subtitle and add it to the shape. if (textShapeFactory) d->subTitle = textShapeFactory->createDefaultShape(resourceManager); if (!d->subTitle) { d->subTitle = new TextLabelDummy; } labelData = dynamic_cast(d->subTitle->userData()); if (labelData == 0) { labelData = new TextLabelData; KoTextDocumentLayout *documentLayout = new KoTextDocumentLayout(labelData->document()); labelData->document()->setDocumentLayout(documentLayout); d->subTitle->setUserData(labelData); } addShape(d->subTitle); font = subTitleData()->document()->defaultFont(); font.setPointSizeF(10.0); subTitleData()->document()->setDefaultFont(font); subTitleData()->document()->setPlainText(i18n("Subtitle")); // Set a reasonable size, it will be resized automatically d->subTitle->setSize(QSizeF(CM_TO_POINT(5), CM_TO_POINT(0.7))); d->subTitle->setVisible(false); d->subTitle->setZIndex(3); setClipped(d->subTitle, true); setInheritsTransform(d->subTitle, true); d->subTitle->setDeletable(false); d->subTitle->setToolDelegates(QSet()<subTitle); // Enable chart tool labelData->setResizeMethod(KoTextShapeDataBase::AutoResize); d->subTitle->setAdditionalStyleAttribute("chart:auto-position", "true"); d->subTitle->setAllowedInteraction(KoShape::ShearingAllowed, false); // Create the Footer and add it to the shape. if (textShapeFactory) d->footer = textShapeFactory->createDefaultShape(resourceManager); if (!d->footer) { d->footer = new TextLabelDummy; } labelData = dynamic_cast(d->footer->userData()); if (labelData == 0) { labelData = new TextLabelData; KoTextDocumentLayout *documentLayout = new KoTextDocumentLayout(labelData->document()); labelData->document()->setDocumentLayout(documentLayout); d->footer->setUserData(labelData); } addShape(d->footer); font = footerData()->document()->defaultFont(); font.setPointSizeF(10.0); footerData()->document()->setDefaultFont(font); footerData()->document()->setPlainText(i18n("Footer")); // Set a reasonable size, it will be resized automatically d->footer->setSize(QSizeF(CM_TO_POINT(5), CM_TO_POINT(0.7))); d->footer->setVisible(false); d->footer->setZIndex(4); setClipped(d->footer, true); setInheritsTransform(d->footer, true); d->footer->setDeletable(false); d->footer->setToolDelegates(QSet()<footer); // Enable chart tool labelData->setResizeMethod(KoTextShapeDataBase::AutoResize); d->footer->setAdditionalStyleAttribute("chart:auto-position", "true"); d->footer->setAllowedInteraction(KoShape::ShearingAllowed, false); // Set default contour (for how text run around is done around this shape) // to prevent a crash in LO setTextRunAroundContour(KoShape::ContourBox); QSharedPointer background(new KoColorBackground(Qt::white)); setBackground(background); KoShapeStroke *stroke = new KoShapeStroke(0, Qt::black); setStroke(stroke); setSize(QSizeF(CM_TO_POINT(8), CM_TO_POINT(5))); // Tell layout about item types ChartLayout *l = layout(); l->setItemType(d->plotArea, PlotAreaType); l->setItemType(d->title, TitleLabelType); l->setItemType(d->subTitle, SubTitleLabelType); l->setItemType(d->footer, FooterLabelType); l->setItemType(d->legend, LegendType); l->layout(); requestRepaint(); } ChartShape::~ChartShape() { delete d->title; delete d->subTitle; delete d->footer; delete d->legend; delete d->plotArea; delete d->proxyModel; delete d->document; delete d; } ChartProxyModel *ChartShape::proxyModel() const { return d->proxyModel; } KoShape *ChartShape::title() const { return d->title; } TextLabelData *ChartShape::titleData() const { TextLabelData *data = qobject_cast(d->title->userData()); return data; } KoShape *ChartShape::subTitle() const { return d->subTitle; } TextLabelData *ChartShape::subTitleData() const { TextLabelData *data = qobject_cast(d->subTitle->userData()); return data; } KoShape *ChartShape::footer() const { return d->footer; } TextLabelData *ChartShape::footerData() const { TextLabelData *data = qobject_cast(d->footer->userData()); return data; } QList ChartShape::labels() const { QList labels; labels.append(d->title); labels.append(d->footer); labels.append(d->subTitle); foreach(Axis *axis, plotArea()->axes()) { labels.append(axis->title()); } return labels; } Legend *ChartShape::legend() const { // There has to be a valid legend even, if it's hidden. Q_ASSERT(d->legend); return d->legend; } PlotArea *ChartShape::plotArea() const { return d->plotArea; } ChartLayout *ChartShape::layout() const { ChartLayout *l = dynamic_cast(KoShapeContainer::model()); Q_ASSERT(l); return l; } void ChartShape::showTitle(bool doShow) { d->setChildVisible(d->title, doShow); } void ChartShape::showSubTitle(bool doShow) { d->setChildVisible(d->subTitle, doShow); } void ChartShape::showFooter(bool doShow) { d->setChildVisible(d->footer, doShow); } ChartTableModel *ChartShape::internalModel() const { return d->internalModel; } void ChartShape::setInternalModel(ChartTableModel *model) { Table *table = d->tableSource.get(model); Q_ASSERT(table); delete d->internalModelHelper; delete d->internalModel; d->internalModel = model; d->internalModelHelper = new SingleModelHelper(table, d->proxyModel); } TableSource *ChartShape::tableSource() const { return &d->tableSource; } bool ChartShape::usesInternalModelOnly() const { return d->usesInternalModelOnly; } void ChartShape::setUsesInternalModelOnly(bool doesSo) { d->usesInternalModelOnly = doesSo; } // ---------------------------------------------------------------- // getters and setters ChartType ChartShape::chartType() const { Q_ASSERT(d->plotArea); return d->plotArea->chartType(); } ChartSubtype ChartShape::chartSubType() const { Q_ASSERT(d->plotArea); return d->plotArea->chartSubType(); } bool ChartShape::isThreeD() const { Q_ASSERT(d->plotArea); return d->plotArea->isThreeD(); } void ChartShape::setSheetAccessModel(QAbstractItemModel *model) { d->tableSource.setSheetAccessModel(model); } void ChartShape::reset(const QString ®ion, bool firstRowIsLabel, bool firstColumnIsLabel, Qt::Orientation dataDirection) { // This method is provided via KoChartInterface, which is // used by embedding applications. d->usesInternalModelOnly = false; d->proxyModel->setFirstRowIsLabel(firstRowIsLabel); d->proxyModel->setFirstColumnIsLabel(firstColumnIsLabel); d->proxyModel->setDataDirection(dataDirection); d->proxyModel->reset(CellRegion(&d->tableSource, region)); } void ChartShape::setChartType(ChartType type) { Q_ASSERT(d->plotArea); ChartType prev = chartType(); d->proxyModel->setDataDimensions(numDimensions(type)); d->plotArea->setChartType(type); emit chartTypeChanged(type, prev); } void ChartShape::setChartSubType(ChartSubtype subType, bool reset) { Q_ASSERT(d->plotArea); ChartSubtype prev = d->plotArea->chartSubType(); d->plotArea->setChartSubType(subType); if (reset && chartType() == StockChartType && prev != subType && d->internalModel && d->usesInternalModelOnly) { // HACK to get reasonable behaviour in most cases // Stock charts are special because subtypes interpretes data differently from another: // - HighLowCloseChartSubtype assumes High = row 0, Low = row 1 and Close = row 2 // - The other types assumes Open = row 0, High = row 1, Low = row 2 and Close = row 3 // This makes switching between them a bit unintuitive. if (subType == HighLowCloseChartSubtype && d->internalModel->rowCount() > 3) { d->proxyModel->removeRows(0, 1); // remove Open } else { // just reset and hope for the best CellRegion region(d->tableSource.get(d->internalModel), QRect(1, 1, d->internalModel->columnCount(), d->internalModel->rowCount())); d->proxyModel->reset(region); } } emit updateConfigWidget(); } void ChartShape::setThreeD(bool threeD) { Q_ASSERT(d->plotArea); d->plotArea->setThreeD(threeD); } // ---------------------------------------------------------------- void ChartShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { // Only does a relayout if scheduled layout()->layout(); // Paint the background applyConversion(painter, converter); if (background()) { // Calculate the clipping rect QRectF paintRect = QRectF(QPointF(0, 0), size()); painter.setClipRect(paintRect, Qt::IntersectClip); QPainterPath p; p.addRect(paintRect); background()->paint(painter, converter, paintContext, p); } // Paint border if showTextShapeOutlines is set // This means that it will be painted in words but not eg in sheets if (paintContext.showTextShapeOutlines) { if (qAbs(rotation()) > 1) { painter.setRenderHint(QPainter::Antialiasing); } QPen pen(QColor(210, 210, 210), 0); // use cosmetic pen QPointF onePixel = converter.viewToDocument(QPointF(1.0, 1.0)); QRectF rect(QPointF(0.0, 0.0), size() - QSizeF(onePixel.x(), onePixel.y())); painter.setPen(pen); painter.drawRect(rect); } } void ChartShape::paintDecorations(QPainter &painter, const KoViewConverter &converter, const KoCanvasBase *canvas) { // This only is a helper decoration, do nothing if we're already // painting handles anyway. Q_ASSERT(canvas); if (canvas->shapeManager()->selection()->selectedShapes().contains(this)) return; if (stroke()) return; QRectF border = QRectF(QPointF(-1.5, -1.5), converter.documentToView(size()) + QSizeF(1.5, 1.5)); painter.setPen(QPen(Qt::lightGray, 0)); painter.drawRect(border); } // ---------------------------------------------------------------- // Loading and Saving bool ChartShape::loadEmbeddedDocument(KoStore *store, const KoXmlElement &objectElement, const KoOdfLoadingContext &loadingContext) { if (!objectElement.hasAttributeNS(KoXmlNS::xlink, "href")) { errorChart << "Object element has no valid xlink:href attribute"; return false; } QString url = objectElement.attributeNS(KoXmlNS::xlink, "href"); // It can happen that the url is empty e.g. when it is a // presentation:placeholder. if (url.isEmpty()) { return true; } QString tmpURL; if (url[0] == '#') url.remove(0, 1); if (QUrl::fromUserInput(url).isRelative()) { if (url.startsWith(QLatin1String("./"))) tmpURL = QString(INTERNAL_PROTOCOL) + ":/" + url.mid(2); else tmpURL = QString(INTERNAL_PROTOCOL) + ":/" + url; } else tmpURL = url; QString path = tmpURL; if (tmpURL.startsWith(INTERNAL_PROTOCOL)) { path = store->currentPath(); if (!path.isEmpty() && !path.endsWith('/')) path += '/'; QString relPath = QUrl::fromUserInput(tmpURL).path(); path += relPath.mid(1); // remove leading '/' } if (!path.endsWith('/')) path += '/'; const QString mimeType = loadingContext.mimeTypeForPath(path); //debugChart << "path for manifest file=" << path << "mimeType=" << mimeType; if (mimeType.isEmpty()) { //debugChart << "Manifest doesn't have media-type for" << path; return false; } const bool isOdf = mimeType.startsWith(QLatin1String("application/vnd.oasis.opendocument")); if (!isOdf) { tmpURL += "/maindoc.xml"; //debugChart << "tmpURL adjusted to" << tmpURL; } //debugChart << "tmpURL=" << tmpURL; bool res = true; if (tmpURL.startsWith(STORE_PROTOCOL) || tmpURL.startsWith(INTERNAL_PROTOCOL) || QUrl::fromUserInput(tmpURL).isRelative()) { if (isOdf) { store->pushDirectory(); Q_ASSERT(tmpURL.startsWith(INTERNAL_PROTOCOL)); QString relPath = QUrl::fromUserInput(tmpURL).path().mid(1); store->enterDirectory(relPath); res = d->document->loadOasisFromStore(store); store->popDirectory(); } else { if (tmpURL.startsWith(INTERNAL_PROTOCOL)) tmpURL = QUrl::fromUserInput(tmpURL).path().mid(1); res = d->document->loadFromStore(store, tmpURL); } d->document->setStoreInternal(true); } else { // Reference to an external document. Hmmm... d->document->setStoreInternal(false); QUrl url = QUrl::fromUserInput(tmpURL); if (!url.isLocalFile()) { //QApplication::restoreOverrideCursor(); // For security reasons we need to ask confirmation if the // url is remote. int result = KMessageBox::warningYesNoCancel( 0, i18n("This document contains an external link to a remote document\n%1", tmpURL), i18n("Confirmation Required"), KGuiItem(i18n("Download")), KGuiItem(i18n("Skip"))); if (result == KMessageBox::Cancel) { //d->m_parent->setErrorMessage("USER_CANCELED"); return false; } if (result == KMessageBox::Yes) res = d->document->openUrl(url); // and if == No, res will still be false so we'll use a kounavail below } else res = d->document->openUrl(url); } if (!res) { QString errorMessage = d->document->errorMessage(); return false; } // Still waiting... //QApplication::setOverrideCursor(Qt::WaitCursor); tmpURL.clear(); //QApplication::restoreOverrideCursor(); return res; } bool ChartShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { //struct Timer{QTime t;Timer(){t.start();} ~Timer(){debugChart<<">>>>>"<"; return false; } bool ChartShape::loadOdfChartElement(const KoXmlElement &chartElement, KoShapeLoadingContext &context) { // Use a helper-class created on the stack to be sure a we always leave // this method with a call to endLoading proxyModel()->endLoading() struct ProxyModelLoadState { ChartProxyModel *m; ChartLayout *l; ProxyModelLoadState(ChartProxyModel *m, ChartLayout *l) : m(m), l(l) { m->beginLoading(); l->setLayoutingEnabled(false); } ~ProxyModelLoadState() { m->endLoading(); l->setLayoutingEnabled(true); } }; ProxyModelLoadState proxyModelLoadState(proxyModel(), layout()); // The shared data will automatically be deleted in the destructor // of KoShapeLoadingContext OdfLoadingHelper *helper = new OdfLoadingHelper; helper->tableSource = &d->tableSource; helper->chartUsesInternalModelOnly = d->usesInternalModelOnly; // Get access to sheets in Calligra Sheets QAbstractItemModel *sheetAccessModel = 0; if (resourceManager() && resourceManager()->hasResource(Sheets::CanvasResource::AccessModel)) { QVariant var = resourceManager()->resource(Sheets::CanvasResource::AccessModel); sheetAccessModel = static_cast(var.value()); if (sheetAccessModel) { // We're embedded in Calligra Sheets, which means Calligra Sheets provides the data d->usesInternalModelOnly = false; d->tableSource.setSheetAccessModel(sheetAccessModel); helper->chartUsesInternalModelOnly = d->usesInternalModelOnly; } } context.addSharedData(OdfLoadingHelperId, helper); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.clear(); if (chartElement.hasAttributeNS(KoXmlNS::chart, "style-name")) { context.odfLoadingContext().fillStyleStack(chartElement, KoXmlNS::chart, "style-name", "chart"); styleStack.setTypeProperties("graphic"); KoInsets padding = layout()->padding(); if (styleStack.hasProperty(KoXmlNS::fo, "padding")) { padding.left = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding")); padding.top = padding.left; padding.right = padding.left; padding.bottom = padding.left; debugChartOdf<<"load padding"<setPadding(padding); } // Also load the size here as it, if specified here, overwrites the frame's size, // See ODF specs for chart:chart element for more details. loadOdfAttributes(chartElement, context, OdfAdditionalAttributes | OdfMandatories | OdfCommonChildElements | OdfStyle | OdfSize); #ifndef NWORKAROUND_ODF_BUGS if (!background()) { const QColor color = KoOdfWorkaround::fixMissingFillColor(chartElement, context); if (color.isValid()) // invalid color means do not set KoColorBackground but be transparent instead setBackground(QSharedPointer(new KoColorBackground(color))); } #endif // Check if we're loading an embedded document if (!chartElement.hasAttributeNS(KoXmlNS::chart, "class")) { debugChart << "Error: Embedded document has no chart:class attribute."; return false; } Q_ASSERT(d->plotArea); // 1. Load the chart type. // NOTE: Chart type and -subtype is a bit tricky as stock charts and bubble charts // needs special treatment. // So we do not call the ChartShape::setChart... methods here in odf code, // but the plot area methods directly. const QString chartClass = chartElement.attributeNS(KoXmlNS::chart, "class", QString()); KoChart::ChartType chartType = KoChart::BarChartType; // Find out what charttype the chart class corresponds to. bool knownType = false; for (int type = 0; type < (int)LastChartType; ++type) { if (chartClass == ODF_CHARTTYPES[(ChartType)type]) { chartType = (ChartType)type; // Set the dimensionality of the data points, we can not call // setChartType here as bubble charts requires that the datasets already exist proxyModel()->setDataDimensions(numDimensions(chartType)); knownType = true; debugChartOdf <<"found chart of type" << chartClass<proxyModel->setDataDimensions(dimensions); // debugChart << d->proxyModel->dataSets().count(); KoXmlElement dataElem = KoXml::namedItemNS(chartElement, KoXmlNS::table, "table"); if (!dataElem.isNull()) { if (!loadOdfData(dataElem, context)) return false; } // 3. Load the plot area (this is where the meat is!). KoXmlElement plotareaElem = KoXml::namedItemNS(chartElement, KoXmlNS::chart, "plot-area"); if (!plotareaElem.isNull()) { d->plotArea->setChartType(chartType); d->plotArea->setChartSubType(chartSubType()); if (!d->plotArea->loadOdf(plotareaElem, context)) { return false; } // d->plotArea->setChartType(chartType); // d->plotArea->setChartSubType(chartSubType()); } // 4. Load the title. KoXmlElement titleElem = KoXml::namedItemNS(chartElement, KoXmlNS::chart, "title"); d->setChildVisible(d->title, !titleElem.isNull()); if (!titleElem.isNull()) { if (!OdfHelper::loadOdfTitle(d->title, titleElem, context)) return false; } // 5. Load the subtitle. KoXmlElement subTitleElem = KoXml::namedItemNS(chartElement, KoXmlNS::chart, "subtitle"); d->setChildVisible(d->subTitle, !subTitleElem.isNull()); if (!subTitleElem.isNull()) { if (!OdfHelper::loadOdfTitle(d->subTitle, subTitleElem, context)) return false; } // 6. Load the footer. KoXmlElement footerElem = KoXml::namedItemNS(chartElement, KoXmlNS::chart, "footer"); d->setChildVisible(d->footer, !footerElem.isNull()); if (!footerElem.isNull()) { if (!OdfHelper::loadOdfTitle(d->footer, footerElem, context)) return false; } // 7. Load the legend. KoXmlElement legendElem = KoXml::namedItemNS(chartElement, KoXmlNS::chart, "legend"); d->setChildVisible(d->legend, !legendElem.isNull()); if (!legendElem.isNull()) { if (!d->legend->loadOdf(legendElem, context)) return false; } // 8. Sets the chart type // since chart type in plot area is already set before axes were loaded, we need to do axes here for (Axis *a : d->plotArea->axes()) { a->plotAreaChartTypeChanged(chartType); } debugChartOdf<<"loaded:"<chartType()<internalModel) { Table *oldInternalTable = d->tableSource.get(d->internalModel); Q_ASSERT(oldInternalTable); d->tableSource.remove(oldInternalTable->name()); } // FIXME: Make model->loadOdf() return a bool, and use it here. // Create a table with data from document, add it as table source // and reset the proxy only with data from this new table. ChartTableModel *internalModel = new ChartTableModel; internalModel->loadOdf(tableElement, context); QString tableName = tableElement.attributeNS(KoXmlNS::table, "name"); d->tableSource.add(tableName, internalModel); // TODO: d->tableSource.setAvoidNameClash(tableName) setInternalModel(internalModel); return true; } void ChartShape::saveOdf(KoShapeSavingContext & context) const { Q_ASSERT(d->plotArea); KoXmlWriter& bodyWriter = context.xmlWriter(); // Check if we're saving to a chart document. If not, embed a // chart document. ChartShape::saveOdf() will then be called // again later, when the current document saves the embedded // documents. // // FIXME: The check isEmpty() fixes a crash that happened when a // chart shape was saved from Words. There are two // problems with this fix: // 1. Checking the tag hierarchy is hardly the right way to do this // 2. The position doesn't seem to be saved yet. // // Also, I have to check with the other apps, e.g. Calligra Sheets, // if it works there too. // QList tagHierarchy = bodyWriter.tagHierarchy(); if (tagHierarchy.isEmpty() || QString(tagHierarchy.last()) != "office:chart") { bodyWriter.startElement("draw:frame"); // See also loadOdf() in loadOdfAttributes. saveOdfAttributes(context, OdfAllAttributes); bodyWriter.startElement("draw:object"); context.embeddedSaver().embedDocument(bodyWriter, d->document); bodyWriter.endElement(); // draw:object bodyWriter.endElement(); // draw:frame return; } bodyWriter.startElement("chart:chart"); saveOdfAttributes(context, OdfSize); context.setStyleFamily("ch"); KoGenStyle style(KoGenStyle::ChartAutoStyle, "chart"); KoInsets padding = layout()->padding(); style.addPropertyPt("fo:padding-left", padding.left, KoGenStyle::GraphicType); style.addPropertyPt("fo:padding-top", padding.top, KoGenStyle::GraphicType); style.addPropertyPt("fo:padding-right", padding.right, KoGenStyle::GraphicType); style.addPropertyPt("fo:padding-bottom", padding.bottom, KoGenStyle::GraphicType); debugChartOdf<<"save padding:"<plotArea->chartType() ]); // 2. Write the title. OdfHelper::saveOdfTitle(d->title, bodyWriter, "chart:title", context); // 3. Write the subtitle. OdfHelper::saveOdfTitle(d->subTitle, bodyWriter, "chart:subtitle", context); // 4. Write the footer. OdfHelper::saveOdfTitle(d->footer, bodyWriter, "chart:footer", context); // 5. Write the legend. if (d->legend->isVisible()) d->legend->saveOdf(context); // 6. Write the plot area (this is where the real action is!). d->plotArea->saveOdf(context); // 7. Save the data saveOdfData(bodyWriter, context.mainStyles()); bodyWriter.endElement(); // chart:chart } static void saveOdfDataRow(KoXmlWriter &bodyWriter, QAbstractItemModel *table, int row) { bodyWriter.startElement("table:table-row"); const int cols = table->columnCount(); for (int col = 0; col < cols; ++col) { //QVariant value(internalModel.cellVal(row, col)); QModelIndex index = table->index(row, col); QVariant value = table->data(index); bool ok; double val = value.toDouble(&ok); if (ok) { value = val; } QString valType; QString valStr; switch (value.type()) { case QVariant::Invalid: break; case QVariant::String: valType = "string"; valStr = value.toString(); break; case QVariant::Double: valType = "float"; valStr = QString::number(value.toDouble(), 'g', DBL_DIG); break; case QVariant::DateTime: valType = "date"; valStr = ""; /* like in saveXML, but why? */ break; default: debugChart <<"ERROR: cell" << row <<"," << col << " has unknown type." << endl; } // Add the value type and the string to the XML tree. bodyWriter.startElement("table:table-cell"); if (!valType.isEmpty()) { bodyWriter.addAttribute("office:value-type", valType); if (value.type() == QVariant::Double) bodyWriter.addAttribute("office:value", valStr); bodyWriter.startElement("text:p"); bodyWriter.addTextNode(valStr); bodyWriter.endElement(); // text:p } bodyWriter.endElement(); // table:table-cell } bodyWriter.endElement(); // table:table-row } void ChartShape::saveOdfData(KoXmlWriter &bodyWriter, KoGenStyles &mainStyles) const { Q_UNUSED(mainStyles); // FIXME: Move this method to a sane place ChartTableModel *internalModel = d->internalModel; Table *internalTable = d->tableSource.get(internalModel); Q_ASSERT(internalTable); // Only save the data if we actually have some. if (!internalModel) return; const int rows = internalModel->rowCount(); const int cols = internalModel->columnCount(); bodyWriter.startElement("table:table"); bodyWriter.addAttribute("table:name", internalTable->name()); // Exactly one header column, always. bodyWriter.startElement("table:table-header-columns"); bodyWriter.startElement("table:table-column"); bodyWriter.endElement(); // table:table-column bodyWriter.endElement(); // table:table-header-columns // Then "cols" columns bodyWriter.startElement("table:table-columns"); bodyWriter.startElement("table:table-column"); bodyWriter.addAttribute("table:number-columns-repeated", cols); bodyWriter.endElement(); // table:table-column bodyWriter.endElement(); // table:table-columns int row = 0; bodyWriter.startElement("table:table-header-rows"); if (rows > 0) saveOdfDataRow(bodyWriter, internalModel, row++); bodyWriter.endElement(); // table:table-header-rows // Here start the actual data rows. bodyWriter.startElement("table:table-rows"); //QStringList::const_iterator rowLabelIt = m_rowLabels.begin(); for (; row < rows ; ++row) saveOdfDataRow(bodyWriter, internalModel, row); bodyWriter.endElement(); // table:table-rows bodyWriter.endElement(); // table:table } void ChartShape::updateAll() { d->legend->update(); d->plotArea->plotAreaUpdate(); relayout(); update(); } void ChartShape::update() const { KoShape::update(); layout()->scheduleRelayout(); emit updateConfigWidget(); } void ChartShape::relayout() const { Q_ASSERT(d->plotArea); d->plotArea->relayout(); KoShape::update(); } void ChartShape::requestRepaint() const { Q_ASSERT(d->plotArea); d->plotArea->requestRepaint(); } KoDocumentResourceManager *ChartShape::resourceManager() const { return d->resourceManager; } void ChartShape::setEnableUserInteraction(bool enable) { ENABLE_USER_INTERACTION = enable; } void ChartShape::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(shape) layout()->containerChanged(this, type); } ChartDocument *ChartShape::document() const { return d->document; } } // Namespace KoChart diff --git a/plugins/chartshape/Legend.cpp b/plugins/chartshape/Legend.cpp index 465f45e031c..0b65f401bd3 100644 --- a/plugins/chartshape/Legend.cpp +++ b/plugins/chartshape/Legend.cpp @@ -1,577 +1,578 @@ /* This file is part of the KDE project Copyright 2007 Johannes Simon Copyright 2010 Inge Wallin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Own #include "Legend.h" // Qt #include #include #include #include #include #include #include +#include // Calligra #include #include #include #include #include #include #include #include #include #include #include // KChart #include #include #include #include #include #include #include "KChartConvertions.h" // KoChart #include "PlotArea.h" #include "ScreenConversions.h" #include "ChartLayout.h" #include "OdfLoadingHelper.h" #include "OdfHelper.h" using namespace KoChart; class Legend::Private { public: Private(); ~Private(); ChartShape *shape; // Properties of the Legend QString title; LegendExpansion expansion; Position position; QFont font; QFont titleFont; QColor fontColor; Qt::Alignment alignment; KoShapeStroke *lineBorder; // The connection to KChart KChart::Legend *kdLegend; QImage image; mutable bool pixmapRepaintRequested; QSizeF lastSize; QPointF lastZoomLevel; }; Legend::Private::Private() { lineBorder = new KoShapeStroke(0.5, Qt::black); expansion = HighLegendExpansion; alignment = Qt::AlignCenter; pixmapRepaintRequested = true; position = EndPosition; } Legend::Private::~Private() { delete lineBorder; } Legend::Legend(ChartShape *parent) : QObject(parent) , d(new Private()) { Q_ASSERT(parent); setShapeId("ChartShapeLegend"); d->shape = parent; d->kdLegend = new KChart::Legend(); d->kdLegend->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); // we use the shape to display frame and background KChart::FrameAttributes frameAttr = d->kdLegend->frameAttributes(); frameAttr.setVisible(false); d->kdLegend->setFrameAttributes(frameAttr); setTitleFontSize(10); setTitle(QString()); setFontSize(8); update(); parent->addShape(this); setAllowedInteraction(KoShape::ResizeAllowed, false); setAllowedInteraction(KoShape::RotationAllowed, false); connect (d->kdLegend, SIGNAL(propertiesChanged()), this, SLOT(slotKdLegendChanged())); connect (parent, SIGNAL(chartTypeChanged(ChartType, ChartType)), this, SLOT(slotChartTypeChanged(ChartType))); } Legend::~Legend() { delete d->kdLegend; delete d; } QString Legend::title() const { return d->title; } void Legend::setTitle(const QString &title) { d->title = title; d->kdLegend->setTitleText(title); d->pixmapRepaintRequested = true; emit updateConfigWidget(); } QFont Legend::font() const { return d->font; } void Legend::setFont(const QFont &font) { d->font = font; // KChart KChart::TextAttributes attributes = d->kdLegend->textAttributes(); attributes.setFont(font); d->kdLegend->setTextAttributes(attributes); d->pixmapRepaintRequested = true; emit updateConfigWidget(); } qreal Legend::fontSize() const { return d->font.pointSizeF(); } void Legend::setFontSize(qreal size) { d->font.setPointSizeF(size); // KChart KChart::TextAttributes attributes = d->kdLegend->textAttributes(); KChart::Measure m = attributes.fontSize(); m.setValue(size); attributes.setFontSize(m); d->kdLegend->setTextAttributes(attributes); d->pixmapRepaintRequested = true; emit updateConfigWidget(); } void Legend::setFontColor(const QColor &color) { KChart::TextAttributes attributes = d->kdLegend->textAttributes(); QPen pen = attributes.pen(); pen.setColor(color); attributes.setPen(pen); d->kdLegend->setTextAttributes(attributes); d->pixmapRepaintRequested = true; } QColor Legend::fontColor() const { KChart::TextAttributes attributes = d->kdLegend->textAttributes(); QPen pen = attributes.pen(); return pen.color(); } QFont Legend::titleFont() const { return d->titleFont; } void Legend::setTitleFont(const QFont &font) { d->titleFont = font; // KChart KChart::TextAttributes attributes = d->kdLegend->titleTextAttributes(); attributes.setFont(font); d->kdLegend->setTitleTextAttributes(attributes); d->pixmapRepaintRequested = true; } qreal Legend::titleFontSize() const { return d->titleFont.pointSizeF(); } void Legend::setTitleFontSize(qreal size) { d->titleFont.setPointSizeF(size); // KChart KChart::TextAttributes attributes = d->kdLegend->titleTextAttributes(); attributes.setFontSize(KChart::Measure(size, KChartEnums::MeasureCalculationModeAbsolute)); d->kdLegend->setTitleTextAttributes(attributes); d->pixmapRepaintRequested = true; } LegendExpansion Legend::expansion() const { return d->expansion; } void Legend::setExpansion(LegendExpansion expansion) { d->expansion = expansion; d->kdLegend->setOrientation(LegendExpansionToQtOrientation(expansion)); d->pixmapRepaintRequested = true; emit updateConfigWidget(); } Qt::Alignment Legend::alignment() const { return d->alignment; } void Legend::setAlignment(Qt::Alignment alignment) { d->alignment = alignment; } Position Legend::legendPosition() const { return d->position; } void Legend::setLegendPosition(Position position) { d->position = position; d->pixmapRepaintRequested = true; } void Legend::setSize(const QSizeF &newSize) { QSize newSizePx = ScreenConversions::scaleFromPtToPx(newSize); d->kdLegend->resize(newSizePx); d->kdLegend->resizeLayout(newSizePx); KoShape::setSize(newSize); } void Legend::paintPixmap(QPainter &painter, const KoViewConverter &converter) { // Adjust the size of the painting area to the current zoom level const QSize paintRectSize = converter.documentToView(d->lastSize).toSize(); d->image = QImage(paintRectSize, QImage::Format_ARGB32); QPainter pixmapPainter(&d->image); pixmapPainter.setRenderHints(painter.renderHints()); pixmapPainter.setRenderHint(QPainter::Antialiasing, false); // Scale the painter's coordinate system to fit the current zoom level. applyConversion(pixmapPainter, converter); d->kdLegend->paint(&pixmapPainter); } void Legend::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { //painter.save(); // First of all, scale the painter's coordinate system to fit the current zoom level applyConversion(painter, converter); // Calculate the clipping rect QRectF paintRect = QRectF(QPointF(0, 0), size()); //clipRect.intersected(paintRect); painter.setClipRect(paintRect, Qt::IntersectClip); // Get the current zoom level QPointF zoomLevel; converter.zoom(&zoomLevel.rx(), &zoomLevel.ry()); // Only repaint the pixmap if it is scheduled, the zoom level changed or the shape was resized /*if ( d->pixmapRepaintRequested || d->lastZoomLevel != zoomLevel || d->lastSize != size()) { // TODO: What if two zoom levels are constantly being requested? // At the moment, this *is* the case, due to the fact // that the shape is also rendered in the page overview // in Stage // Every time the window is hidden and shown again, a repaint is // requested --> laggy performance, especially when quickly // switching through windows d->pixmapRepaintRequested = false; d->lastZoomLevel = zoomLevel; d->lastSize = size(); paintPixmap(painter, converter); }*/ // Paint the background if (background()) { QPainterPath p; p.addRect(paintRect); background()->paint(painter, converter, paintContext, p); } // KChart thinks in pixels, Calligra in pt // KChart also for non-QWidget painting devices cares for the logicalDpi // Other than PlotArea we do not control the output size via the paint method, // so here have to resize the legend temporarily. // Printing should only result in 1 paint call, so this should not happen too often. // TODO: something in KChart seems broken in general on printer output, also seen in kchart examples // so legend in print is still broken :/ const QSize sizePx = d->kdLegend->size(); const QSize newSizePx = ScreenConversions::scaleFromPtToPx(size(), painter); const bool isPainterDifferentDpi = (sizePx != newSizePx); if (isPainterDifferentDpi) { // temporarily set a size matching the painterdevice d->kdLegend->resize(newSizePx); d->kdLegend->resizeLayout(newSizePx); } ScreenConversions::scaleFromPtToPx(painter); d->kdLegend->paint(&painter); if (isPainterDifferentDpi) { // restore screen-dpi size d->kdLegend->resize(sizePx); d->kdLegend->resizeLayout(sizePx); } //painter.restore(); // Paint the cached pixmap //painter.drawImage(0, 0, d->image); } // ---------------------------------------------------------------- // loading and saving bool Legend::loadOdf(const KoXmlElement &legendElement, KoShapeLoadingContext &context) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.clear(); // FIXME: If the style isn't present we shouldn't care about it at all // and move everything related to the legend style in this if clause if (legendElement.hasAttributeNS(KoXmlNS::chart, "style-name")) { context.odfLoadingContext().fillStyleStack(legendElement, KoXmlNS::chart, "style-name", "chart"); styleStack.setTypeProperties("graphic"); } if (!legendElement.isNull()) { int attributesToLoad = OdfAllAttributes; QString lp = legendElement.attributeNS(KoXmlNS::chart, "legend-position", QString()); // Note: load position even if it might not be used loadOdfAttributes(legendElement, context, attributesToLoad); QString lalign = legendElement.attributeNS(KoXmlNS::chart, "legend-align", QString()); if (legendElement.hasAttributeNS(KoXmlNS::style, "legend-expansion")) { QString lexpansion = legendElement.attributeNS(KoXmlNS::style, "legend-expansion", QString()); if (lexpansion == "wide") setExpansion(WideLegendExpansion); else if (lexpansion == "high") setExpansion(HighLegendExpansion); else setExpansion(BalancedLegendExpansion); } if (lalign == "start") { setAlignment(Qt::AlignLeft); } else if (lalign == "end") { setAlignment(Qt::AlignRight); } else { setAlignment(Qt::AlignCenter); // default } if (lp == "start") { setLegendPosition(StartPosition); } else if (lp == "top") { setLegendPosition(TopPosition); } else if (lp == "bottom") { setLegendPosition(BottomPosition); } else if (lp == "end") { setLegendPosition(EndPosition); } else if (lp == "top-start") { setLegendPosition(TopStartPosition); } else if (lp == "bottom-start") { setLegendPosition(BottomStartPosition); } else if (lp == "top-end") { setLegendPosition(TopEndPosition); } else if (lp == "bottom-end") { setLegendPosition(BottomEndPosition); } else { setLegendPosition(FloatingPosition); } if (legendElement.hasAttributeNS(KoXmlNS::office, "title")) { setTitle(legendElement.attributeNS(KoXmlNS::office, "title", QString())); } styleStack.setTypeProperties("text"); if (styleStack.hasProperty(KoXmlNS::fo, "font-family")) { QString fontFamily = styleStack.property(KoXmlNS::fo, "font-family"); QFont font = d->font; font.setFamily(fontFamily); setFont(font); } if (styleStack.hasProperty(KoXmlNS::fo, "font-size")) { qreal fontSize = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "font-size")); setFontSize(fontSize); } if (styleStack.hasProperty(KoXmlNS::fo, "font-color")) { QColor color = styleStack.property(KoXmlNS::fo, "font-color"); if (color.isValid()) { setFontColor(color); } } } else { // No legend element, use default legend. setLegendPosition(EndPosition); setAlignment(Qt::AlignCenter); } d->pixmapRepaintRequested = true; return true; } void Legend::saveOdf(KoShapeSavingContext &context) const { KoXmlWriter &bodyWriter = context.xmlWriter(); bodyWriter.startElement("chart:legend"); saveOdfAttributes(context, OdfPosition); // Legend specific attributes QString lp = PositionToString(d->position); if (!lp.isEmpty()) { bodyWriter.addAttribute("chart:legend-position", lp); } QString lalign; switch (d->alignment) { case Qt::AlignLeft: lalign = "start"; break; case Qt::AlignRight: lalign = "end"; break; case Qt::AlignCenter: lalign = "center"; break; default: break; } if (!lalign.isEmpty()) { bodyWriter.addAttribute("chart:legend-align", lalign); } // Legend style FIXME: Check if more styling then just the font goes here. KoGenStyle style(KoGenStyle::ChartAutoStyle, "chart", 0); OdfHelper::saveOdfFont(style, d->font, d->fontColor); bodyWriter.addAttribute("chart:style-name", saveStyle(style, context)); QString lexpansion; switch (expansion()) { case WideLegendExpansion: lexpansion = "wide"; break; case HighLegendExpansion: lexpansion = "high"; break; case BalancedLegendExpansion: lexpansion = "balanced"; break; }; bodyWriter.addAttribute("style:legend-expansion", lexpansion); if (!title().isEmpty()) bodyWriter.addAttribute("office:title", title()); bodyWriter.endElement(); // chart:legend } KChart::Legend *Legend::kdLegend() const { // There has to be a valid KChart instance of this legend Q_ASSERT(d->kdLegend); return d->kdLegend; } void Legend::rebuild() { d->kdLegend->forceRebuild(); update(); } void Legend::update() const { d->pixmapRepaintRequested = true; KoShape::update(); } void Legend::slotKdLegendChanged() { // FIXME: Update legend properly by implementing all *DataChanged() slots // in KChartModel. Right now, only yDataChanged() is implemented. //d->kdLegend->forceRebuild(); QSize size = d->kdLegend->sizeHint(); setSize(ScreenConversions::scaleFromPxToPt(size)); update(); } void Legend::slotChartTypeChanged(ChartType chartType) { // TODO: Once we support markers, this switch will have to be // more clever. switch (chartType) { case LineChartType: case ScatterChartType: d->kdLegend->setLegendStyle(KChart::Legend::MarkersAndLines); break; default: d->kdLegend->setLegendStyle(KChart::Legend::MarkersOnly); break; } } diff --git a/plugins/chartshape/PlotArea.cpp b/plugins/chartshape/PlotArea.cpp index 31b643cb800..3eb20b864a6 100644 --- a/plugins/chartshape/PlotArea.cpp +++ b/plugins/chartshape/PlotArea.cpp @@ -1,1428 +1,1429 @@ /* This file is part of the KDE project Copyright 2007-2008 Johannes Simon Copyright 2009-2010 Inge Wallin Copyright 2018 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Own #include "PlotArea.h" // Qt #include #include #include #include #include +#include // Calligra #include #include #include #include #include #include #include #include #include #include #include #include #include // KChart #include #include #include #include #include #include #include #include // Attribute Classes #include #include #include #include #include // Diagram Classes #include #include #include #include #include // KoChart #include "Legend.h" #include "Surface.h" #include "Axis.h" #include "DataSet.h" #include "ChartProxyModel.h" #include "ScreenConversions.h" #include "ChartLayout.h" #include "ChartDebug.h" using namespace KoChart; const int MAX_PIXMAP_SIZE = 1000; Q_DECLARE_METATYPE(QPointer) typedef QList CoordinatePlaneList; class PlotArea::Private { public: Private(PlotArea *q, ChartShape *parent); ~Private(); void initAxes(); void updateAxesPosition(); CoordinatePlaneList coordinatePlanesForChartType(ChartType type); void autoHideAxisTitles(); PlotArea *q; // The parent chart shape ChartShape *shape; // ---------------------------------------------------------------- // Parts and properties of the chart ChartType chartType; ChartSubtype chartSubtype; Surface *wall; Surface *floor; // Only used in 3D charts // The axes QList axes; QList automaticallyHiddenAxisTitles; // 3D properties bool threeD; Ko3dScene *threeDScene; // ---------------------------------------------------------------- // Data specific to each chart type // 1. Bar charts // FIXME: OpenOffice stores these attributes in the axes' elements. // The specs don't say anything at all about what elements can have // these style attributes. // chart:vertical attribute: see ODF v1.2,19.63 bool vertical; // 2. Polar charts (pie/ring) qreal angleOffset; // in degrees qreal holeSize; // ---------------------------------------------------------------- // The embedded KD Chart // The KD Chart parts KChart::Chart *const kdChart; KChart::CartesianCoordinatePlane *const kdCartesianPlanePrimary; KChart::CartesianCoordinatePlane *const kdCartesianPlaneSecondary; KChart::PolarCoordinatePlane *const kdPolarPlane; KChart::RadarCoordinatePlane *const kdRadarPlane; QList kdDiagrams; // Caching: We can rerender faster if we cache KChart's output QImage image; bool paintPixmap; QPointF lastZoomLevel; QSizeF lastSize; mutable bool pixmapRepaintRequested; QPen stockRangeLinePen; QBrush stockGainBrush; QBrush stockLossBrush; QString symbolType; QString symbolName; DataSet::ValueLabelType valueLabelType; }; PlotArea::Private::Private(PlotArea *q, ChartShape *parent) : q(q) , shape(parent) // Default type: normal bar chart , chartType(BarChartType) , chartSubtype(NormalChartSubtype) , wall(0) , floor(0) , threeD(false) , threeDScene(0) // By default, x and y axes are not swapped. , vertical(false) // OpenOffice.org's default. It means the first pie slice starts at the // very top (and then going counter-clockwise). , angleOffset(90.0) , holeSize(50.0) // KCharts approx default // KD Chart stuff , kdChart(new KChart::Chart()) , kdCartesianPlanePrimary(new KChart::CartesianCoordinatePlane(kdChart)) , kdCartesianPlaneSecondary(new KChart::CartesianCoordinatePlane(kdChart)) , kdPolarPlane(new KChart::PolarCoordinatePlane(kdChart)) , kdRadarPlane(new KChart::RadarCoordinatePlane(kdChart)) // Cache , paintPixmap(true) , pixmapRepaintRequested(true) , symbolType("automatic") { kdCartesianPlanePrimary->setObjectName("primary"); kdCartesianPlaneSecondary->setObjectName("secondary"); // --- Prepare Primary Cartesian Coordinate Plane --- KChart::GridAttributes gridAttributes; gridAttributes.setGridVisible(false); gridAttributes.setGridGranularitySequence(KChartEnums::GranularitySequence_10_50); kdCartesianPlanePrimary->setGlobalGridAttributes(gridAttributes); // --- Prepare Secondary Cartesian Coordinate Plane --- kdCartesianPlaneSecondary->setGlobalGridAttributes(gridAttributes); // --- Prepare Polar Coordinate Plane --- KChart::GridAttributes polarGridAttributes; polarGridAttributes.setGridVisible(false); kdPolarPlane->setGlobalGridAttributes(polarGridAttributes); // --- Prepare Radar Coordinate Plane --- KChart::GridAttributes radarGridAttributes; polarGridAttributes.setGridVisible(true); kdRadarPlane->setGlobalGridAttributes(radarGridAttributes); // By default we use a cartesian chart (bar chart), so the polar planes // are not needed yet. They will be added on demand in setChartType(). kdChart->takeCoordinatePlane(kdPolarPlane); kdChart->takeCoordinatePlane(kdRadarPlane); shape->proxyModel()->setDataDimensions(1); stockRangeLinePen.setWidthF(2.0); stockGainBrush = QBrush(QColor(Qt::white)); stockLossBrush = QBrush(QColor(Qt::black)); } PlotArea::Private::~Private() { // remove first to avoid crash while (!kdChart->coordinatePlanes().isEmpty()) { kdChart->takeCoordinatePlane(kdChart->coordinatePlanes().last()); } qDeleteAll(axes); delete kdCartesianPlanePrimary; delete kdCartesianPlaneSecondary; delete kdPolarPlane; delete kdRadarPlane; delete kdChart; delete wall; delete floor; delete threeDScene; } void PlotArea::Private::initAxes() { // The category data region is anchored to an axis and will be set on addAxis if the // axis defines the Axis::categoryDataRegion(). So, clear it now. q->proxyModel()->setCategoryDataRegion(CellRegion()); // Remove all old axes while(!axes.isEmpty()) { Axis *axis = axes.takeLast(); Q_ASSERT(axis); if (axis->title()) automaticallyHiddenAxisTitles.removeAll(axis->title()); delete axis; } // There need to be at least these two axes. Their constructor will // automatically add them to the plot area as child shape. new Axis(q, XAxisDimension); Axis *yAxis = new Axis(q, YAxisDimension); yAxis->setShowMajorGrid(true); yAxis->title()->rotate(-90); updateAxesPosition(); } void PlotArea::Private::updateAxesPosition() { debugChartAxis<updateKChartAxisPosition(); } } PlotArea::PlotArea(ChartShape *parent) : QObject() , KoShape() , d(new Private(this, parent)) { setShapeId("ChartShapePlotArea"); // NB! used by defaulttool/ChartResizeStrategy.cpp Q_ASSERT(d->shape); Q_ASSERT(d->shape->proxyModel()); setAdditionalStyleAttribute("chart:auto-position", "true"); setAdditionalStyleAttribute("chart:auto-size", "true"); connect(d->shape->proxyModel(), SIGNAL(modelReset()), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(plotAreaUpdate())); connect(d->shape->proxyModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(plotAreaUpdate())); connect(d->shape->proxyModel(), SIGNAL(dataChanged()), this, SLOT(plotAreaUpdate())); } PlotArea::~PlotArea() { delete d; } void PlotArea::plotAreaInit() { d->kdChart->resize(size().toSize()); d->kdChart->replaceCoordinatePlane(d->kdCartesianPlanePrimary); d->kdCartesianPlaneSecondary->setReferenceCoordinatePlane(d->kdCartesianPlanePrimary); d->kdChart->addCoordinatePlane(d->kdCartesianPlaneSecondary); KChart::FrameAttributes attr = d->kdChart->frameAttributes(); attr.setVisible(false); d->kdChart->setFrameAttributes(attr); d->wall = new Surface(this); //d->floor = new Surface(this); d->initAxes(); addAxesTitlesToLayout(); } void PlotArea::proxyModelStructureChanged() { if (proxyModel()->isLoading()) return; Q_ASSERT(xAxis()); Q_ASSERT(yAxis()); QMap attachedAxes; QList dataSets = proxyModel()->dataSets(); // Remember to what y axis each data set belongs foreach(DataSet *dataSet, dataSets) attachedAxes.insert(dataSet, dataSet->attachedAxis()); // Proxy structure and thus data sets changed, drop old state and // clear all axes of data sets foreach(Axis *axis, axes()) axis->clearDataSets(); // Now add the new list of data sets to the axis they belong to foreach(DataSet *dataSet, dataSets) { xAxis()->attachDataSet(dataSet); // If they weren't assigned to a y axis before, use default y axis if (attachedAxes[dataSet]) attachedAxes[dataSet]->attachDataSet(dataSet); else yAxis()->attachDataSet(dataSet); } } ChartProxyModel *PlotArea::proxyModel() const { return d->shape->proxyModel(); } QList PlotArea::axes() const { return d->axes; } QList PlotArea::dataSets() const { return proxyModel()->dataSets(); } Axis *PlotArea::xAxis() const { foreach(Axis *axis, d->axes) { if (axis->dimension() == XAxisDimension) return axis; } return 0; } Axis *PlotArea::yAxis() const { foreach(Axis *axis, d->axes) { if (axis->dimension() == YAxisDimension) return axis; } return 0; } Axis *PlotArea::secondaryXAxis() const { bool firstXAxisFound = false; foreach(Axis *axis, d->axes) { if (axis->dimension() == XAxisDimension) { if (firstXAxisFound) return axis; else firstXAxisFound = true; } } return 0; } Axis *PlotArea::secondaryYAxis() const { bool firstYAxisFound = false; foreach(Axis *axis, d->axes) { if (axis->dimension() == YAxisDimension) { if (firstYAxisFound) return axis; else firstYAxisFound = true; } } return 0; } ChartType PlotArea::chartType() const { return d->chartType; } ChartSubtype PlotArea::chartSubType() const { return d->chartSubtype; } bool PlotArea::isThreeD() const { return d->threeD; } bool PlotArea::isVertical() const { return d->chartType == BarChartType && d->vertical; } Ko3dScene *PlotArea::threeDScene() const { return d->threeDScene; } qreal PlotArea::angleOffset() const { return d->angleOffset; } qreal PlotArea::holeSize() const { return d->holeSize; } void PlotArea::setHoleSize(qreal value) { d->holeSize = value; } // FIXME: this should add the axxis as a child (set axis->parent()) bool PlotArea::addAxis(Axis *axis) { if (d->axes.contains(axis)) { warnChart << "PlotArea::addAxis(): Trying to add already added axis."; return false; } if (!axis) { warnChart << "PlotArea::addAxis(): Pointer to axis is NULL!"; return false; } d->axes.append(axis); if (axis->dimension() == XAxisDimension) { // let each axis know about the other axis foreach (Axis *_axis, d->axes) { if (_axis->isVisible()) _axis->registerAxis(axis); } } requestRepaint(); return true; } bool PlotArea::removeAxis(Axis *axis) { bool removed = takeAxis(axis); if (removed) { // This also removes the axis' title, which is a shape as well delete axis; } return removed; } // FIXME: this should remove the axis as a child (set axis->parent()) bool PlotArea::takeAxis(Axis *axis) { if (!d->axes.contains(axis)) { warnChart << "PlotArea::takeAxis(): Trying to remove non-added axis."; return false; } if (!axis) { warnChart << "PlotArea::takeAxis(): Pointer to axis is NULL!"; return false; } if (axis->title()) { d->automaticallyHiddenAxisTitles.removeAll(axis->title()); } d->axes.removeAll(axis); axis->removeAxisFromDiagrams(true); requestRepaint(); return true; } CoordinatePlaneList PlotArea::Private::coordinatePlanesForChartType(ChartType type) { CoordinatePlaneList result; switch (type) { case BarChartType: case LineChartType: case AreaChartType: case ScatterChartType: case GanttChartType: case SurfaceChartType: case StockChartType: case BubbleChartType: result.append(kdCartesianPlanePrimary); result.append(kdCartesianPlaneSecondary); break; case CircleChartType: case RingChartType: result.append(kdPolarPlane); break; case RadarChartType: case FilledRadarChartType: result.append(kdRadarPlane); break; case LastChartType: Q_ASSERT("There's no coordinate plane for LastChartType"); break; } Q_ASSERT(!result.isEmpty()); return result; } void PlotArea::Private::autoHideAxisTitles() { automaticallyHiddenAxisTitles.clear(); foreach (Axis *axis, axes) { if (axis->title()->isVisible()) { axis->title()->setVisible(false); automaticallyHiddenAxisTitles.append(axis->title()); } } } void PlotArea::setChartType(ChartType type) { if (d->chartType == type) return; // Lots of things to do if the old and new types of coordinate // systems don't match. if (!isPolar(d->chartType) && isPolar(type)) { d->autoHideAxisTitles(); } else if (isPolar(d->chartType) && !isPolar(type)) { foreach (KoShape *title, d->automaticallyHiddenAxisTitles) { title->setVisible(true); } d->automaticallyHiddenAxisTitles.clear(); } CellRegion region = d->shape->proxyModel()->cellRangeAddress(); if (type == CircleChartType || type == RingChartType) { d->shape->proxyModel()->setManualControl(false); xAxis()->clearDataSets(); yAxis()->clearDataSets(); if (secondaryYAxis()) { secondaryYAxis()->clearDataSets(); } if (secondaryXAxis()) { secondaryXAxis()->clearDataSets(); } } CoordinatePlaneList planesToRemove; // First remove secondary cartesian plane as it references the primary // plane, otherwise KChart will come down crashing on us. Note that // removing a plane that's not in the chart is not a problem. planesToRemove << d->kdCartesianPlaneSecondary << d->kdCartesianPlanePrimary << d->kdPolarPlane << d->kdRadarPlane; foreach(KChart::AbstractCoordinatePlane *plane, planesToRemove) d->kdChart->takeCoordinatePlane(plane); CoordinatePlaneList newPlanes = d->coordinatePlanesForChartType(type); foreach(KChart::AbstractCoordinatePlane *plane, newPlanes) d->kdChart->addCoordinatePlane(plane); Q_ASSERT(d->kdChart->coordinatePlanes() == newPlanes); d->chartType = type; foreach (Axis *axis, d->axes) { axis->plotAreaChartTypeChanged(type); } if (type == CircleChartType || type == RingChartType) { d->shape->proxyModel()->reset(region); } if (type != BarChartType) { setVertical(false); // Only supported by bar charts } requestRepaint(); } void PlotArea::setChartSubType(ChartSubtype subType) { d->chartSubtype = subType; foreach (Axis *axis, d->axes) { axis->plotAreaChartSubTypeChanged(subType); } } void PlotArea::setThreeD(bool threeD) { d->threeD = threeD; foreach(Axis *axis, d->axes) axis->setThreeD(threeD); requestRepaint(); } void PlotArea::setVertical(bool vertical) { d->vertical = vertical; foreach(Axis *axis, d->axes) axis->plotAreaIsVerticalChanged(); } // ---------------------------------------------------------------- // loading and saving bool PlotArea::loadOdf(const KoXmlElement &plotAreaElement, KoShapeLoadingContext &context) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader(); // The exact position defined in ODF overwrites the default layout position // NOTE: Do not do this as it means functionallity changes just because you save and load. // I don't think odf has an element/attribute that can hold this type of info. // Also afaics libreoffice do not do this. // if (plotAreaElement.hasAttributeNS(KoXmlNS::svg, "x") || // plotAreaElement.hasAttributeNS(KoXmlNS::svg, "y") || // plotAreaElement.hasAttributeNS(KoXmlNS::svg, "width") || // plotAreaElement.hasAttributeNS(KoXmlNS::svg, "height")) // { // parent()->layout()->setPosition(this, FloatingPosition); // } bool autoPosition = !(plotAreaElement.hasAttributeNS(KoXmlNS::svg, "x") && plotAreaElement.hasAttributeNS(KoXmlNS::svg, "y")); bool autoSize = !(plotAreaElement.hasAttributeNS(KoXmlNS::svg, "width") && plotAreaElement.hasAttributeNS(KoXmlNS::svg, "height")); context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart"); loadOdfAttributes(plotAreaElement, context, OdfAllAttributes); // First step is to clear all old axis instances. while (!d->axes.isEmpty()) { Axis *axis = d->axes.takeLast(); Q_ASSERT(axis); // Clear this axis of all data sets, deleting any diagram associated with it. axis->clearDataSets(); if (axis->title()) d->automaticallyHiddenAxisTitles.removeAll(axis->title()); delete axis; } // Now find out about things that are in the plotarea style. // // These things include chart subtype, special things for some // chart types like line charts, stock charts, etc. // // Note that this has to happen BEFORE we create a axis and call // there loadOdf method cause the axis will evaluate settings // like the PlotArea::isVertical boolean. bool candleStick = false; if (plotAreaElement.hasAttributeNS(KoXmlNS::chart, "style-name")) { styleStack.clear(); context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart"); styleStack.setTypeProperties("graphic"); styleStack.setTypeProperties("chart"); if (styleStack.hasProperty(KoXmlNS::chart, "auto-position")) { autoPosition |= styleStack.property(KoXmlNS::chart, "auto-position") == "true"; } else { // To be backwards compatible we set auto-position to true as this was the original behaviour // and is the way LO works autoPosition = true; } if (styleStack.hasProperty(KoXmlNS::chart, "auto-size")) { autoSize |= styleStack.property(KoXmlNS::chart, "auto-size") == "true" ; } else { // To be backwards compatible we set auto-size to true as this was the original behaviour // and is the way LO works autoSize = true; } // ring and pie if (styleStack.hasProperty(KoXmlNS::chart, "angle-offset")) { bool ok; const qreal angleOffset = styleStack.property(KoXmlNS::chart, "angle-offset").toDouble(&ok); if (ok) { setAngleOffset(angleOffset); } } // ring if (styleStack.hasProperty(KoXmlNS::chart, "hole-size")) { bool ok; const qreal value = styleStack.property(KoXmlNS::chart, "hole-size").toDouble(&ok); if (ok) { setHoleSize(value); } } // Check for 3D. if (styleStack.hasProperty(KoXmlNS::chart, "three-dimensional")) setThreeD(styleStack.property(KoXmlNS::chart, "three-dimensional") == "true"); d->threeDScene = load3dScene(plotAreaElement); // Set subtypes stacked or percent. // These are valid for Bar, Line, Area and Radar types. if (styleStack.hasProperty(KoXmlNS::chart, "percentage") && styleStack.property(KoXmlNS::chart, "percentage") == "true") { setChartSubType(PercentChartSubtype); } else if (styleStack.hasProperty(KoXmlNS::chart, "stacked") && styleStack.property(KoXmlNS::chart, "stacked") == "true") { setChartSubType(StackedChartSubtype); } // Data specific to bar charts if (styleStack.hasProperty(KoXmlNS::chart, "vertical")) setVertical(styleStack.property(KoXmlNS::chart, "vertical") == "true"); // Data specific to stock charts if (styleStack.hasProperty(KoXmlNS::chart, "japanese-candle-stick")) { candleStick = styleStack.property(KoXmlNS::chart, "japanese-candle-stick") == "true"; } // Special properties for various chart types #if 0 switch () { case BarChartType: if (styleStack) ; } #endif styleStack.clear(); context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart"); } setAdditionalStyleAttribute("chart:auto-position", autoPosition ? "true" : "false"); setAdditionalStyleAttribute("chart:auto-size", autoSize ? "true" : "false"); // Now create and load the axis from the ODF. This needs to happen // AFTER we did set some of the basic settings above so the axis // can use those basic settings to evaluate it's own settings // depending on them. This is especially required for the // PlotArea::isVertical() boolean flag else things will go wrong. KoXmlElement n; forEachElement (n, plotAreaElement) { if (n.namespaceURI() != KoXmlNS::chart) continue; if (n.localName() == "axis") { if (!n.hasAttributeNS(KoXmlNS::chart, "dimension")) // We have to know what dimension the axis is supposed to be.. continue; const QString dimension = n.attributeNS(KoXmlNS::chart, "dimension", QString()); AxisDimension dim; if (dimension == "x") dim = XAxisDimension; else if (dimension == "y") dim = YAxisDimension; else if (dimension == "z") dim = ZAxisDimension; else continue; Axis *axis = new Axis(this, dim); if (dim == YAxisDimension) { if (axis == yAxis()) { axis->title()->rotate(-90); } else if (axis == secondaryYAxis()) { axis->title()->rotate(90); } } axis->loadOdf(n, context); } } // Two axes are mandatory, check that we have them. if (!xAxis()) { Axis *xAxis = new Axis(this, XAxisDimension); xAxis->setVisible(false); } if (!yAxis()) { Axis *yAxis = new Axis(this, YAxisDimension); yAxis->setVisible(false); } // Now, after the axes, load the datasets. // Note that this only contains properties of the datasets, the // actual data is not stored here. // // FIXME: Isn't the proxy model a strange place to store this data? proxyModel()->loadOdf(plotAreaElement, context, d->chartType); // Now load the surfaces (wall and possibly floor) // FIXME: Use named tags instead of looping? forEachElement (n, plotAreaElement) { if (n.namespaceURI() != KoXmlNS::chart) continue; if (n.localName() == "wall") { d->wall->loadOdf(n, context); } else if (n.localName() == "floor") { // The floor is not always present, so allocate it if needed. // FIXME: Load floor, even if we don't really support it yet // and save it back to ODF. //if (!d->floor) // d->floor = new Surface(this); //d->floor->loadOdf(n, context); } else if (n.localName() == "stock-gain-marker") { styleStack.clear(); context.odfLoadingContext().fillStyleStack(n, KoXmlNS::chart, "style-name", "chart"); styleStack.setTypeProperties("graphic"); if (styleStack.hasProperty(KoXmlNS::draw, "fill")) { d->stockGainBrush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, styleStack.property(KoXmlNS::draw, "fill"), stylesReader); debugChartOdf<stockGainBrush; } else { warnChartOdf<stockLossBrush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, styleStack.property(KoXmlNS::draw, "fill"), stylesReader); debugChartOdf<stockLossBrush; } else { warnChartOdf<stockRangeLinePen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, styleStack.property(KoXmlNS::draw, "stroke"), stylesReader); debugChartOdf<stockRangeLinePen; } else { warnChartOdf<chartType == StockChartType) { // The number of data sets determines stock chart subtype if (proxyModel()->rowCount() > 3) { if (candleStick) { setChartSubType(CandlestickChartSubtype); } else { setChartSubType(OpenHighLowCloseChartSubtype); } } } // Connect axes to datasets and cleanup foreach(DataSet *ds, d->shape->proxyModel()->dataSets()) { foreach(Axis *axis, d->axes) { if (axis->name() == ds->axisName()) { axis->attachDataSet(ds); } } } debugChartOdf<chartType<chartSubtype<axes; if (isPolar(d->chartType)) { d->autoHideAxisTitles(); } foreach(Axis *axis, d->axes) { axis->setName(QString()); } // update kchart axis position for all axes d->updateAxesPosition(); // add axes titles to layout addAxesTitlesToLayout(); return true; } void PlotArea::saveOdf(KoShapeSavingContext &context) const { KoXmlWriter &bodyWriter = context.xmlWriter(); //KoGenStyles &mainStyles = context.mainStyles(); bodyWriter.startElement("chart:plot-area"); KoGenStyle plotAreaStyle(KoGenStyle::ChartAutoStyle, "chart"); // Data direction const Qt::Orientation direction = proxyModel()->dataDirection(); plotAreaStyle.addProperty("chart:series-source", (direction == Qt::Horizontal) ? "rows" : "columns"); // Save chart subtype saveOdfSubType(bodyWriter, plotAreaStyle); // Save extra stuff (like auto-position) QMap::const_iterator it(additionalStyleAttributes().constBegin()); for (; it != additionalStyleAttributes().constEnd(); ++it) { plotAreaStyle.addProperty(it.key(), it.value(), KoGenStyle::ChartType); } // save graphic-properties and insert style bodyWriter.addAttribute("chart:style-name", saveStyle(plotAreaStyle, context)); const QSizeF s(size()); const QPointF p(position()); bodyWriter.addAttributePt("svg:width", s.width()); bodyWriter.addAttributePt("svg:height", s.height()); bodyWriter.addAttributePt("svg:x", p.x()); bodyWriter.addAttributePt("svg:y", p.y()); CellRegion cellRangeAddress = d->shape->proxyModel()->cellRangeAddress(); bodyWriter.addAttribute("table:cell-range-address", cellRangeAddress.toString()); // About the data: // Save if the first row / column contain headers. QString dataSourceHasLabels; if (proxyModel()->firstRowIsLabel()) { if (proxyModel()->firstColumnIsLabel()) dataSourceHasLabels = "both"; else dataSourceHasLabels = "row"; } else { if (proxyModel()->firstColumnIsLabel()) dataSourceHasLabels = "column"; else dataSourceHasLabels = "none"; } // Note: this is saved in the plotarea attributes and not the style. bodyWriter.addAttribute("chart:data-source-has-labels", dataSourceHasLabels); if (d->threeDScene) { d->threeDScene->saveOdfAttributes(bodyWriter); } if (d->chartType == StockChartType) { QString styleName; bodyWriter.startElement("chart:stock-gain-marker"); KoGenStyle stockGainStyle(KoGenStyle::ChartAutoStyle, "chart"); KoOdfGraphicStyles::saveOdfFillStyle(stockGainStyle, context.mainStyles(), d->stockGainBrush); styleName = context.mainStyles().insert(stockGainStyle, "ch"); bodyWriter.addAttribute("chart:style-name", styleName); bodyWriter.endElement(); // chart:stock-gain-marker bodyWriter.startElement("chart:stock-loss-marker"); KoGenStyle stockLossStyle(KoGenStyle::ChartAutoStyle, "chart"); KoOdfGraphicStyles::saveOdfFillStyle(stockLossStyle, context.mainStyles(), d->stockLossBrush); styleName = context.mainStyles().insert(stockLossStyle, "ch"); bodyWriter.addAttribute("chart:style-name", styleName); bodyWriter.endElement(); // chart:stock-loss-marker bodyWriter.startElement("chart:stock-range-line"); KoGenStyle stockRangeStyle(KoGenStyle::ChartAutoStyle, "chart"); KoOdfGraphicStyles::saveOdfStrokeStyle(stockRangeStyle, context.mainStyles(), d->stockRangeLinePen); styleName = context.mainStyles().insert(stockRangeStyle, "ch"); bodyWriter.addAttribute("chart:style-name", styleName); bodyWriter.endElement(); // chart:stock-range-line } // Done with the attributes, start writing the children. // Save the axes. foreach(Axis *axis, d->axes) { axis->saveOdf(context); } if (d->threeDScene) { d->threeDScene->saveOdfChildren(bodyWriter); } // Save data series d->shape->proxyModel()->saveOdf(context); // Save the floor and wall of the plotarea. d->wall->saveOdf(context, "chart:wall"); //if (d->floor) // d->floor->saveOdf(context, "chart:floor"); bodyWriter.endElement(); // chart:plot-area } void PlotArea::saveOdfSubType(KoXmlWriter& xmlWriter, KoGenStyle& plotAreaStyle) const { Q_UNUSED(xmlWriter); switch (d->chartType) { case BarChartType: switch(d->chartSubtype) { case NoChartSubtype: case NormalChartSubtype: break; case StackedChartSubtype: plotAreaStyle.addProperty("chart:stacked", "true"); break; case PercentChartSubtype: plotAreaStyle.addProperty("chart:percentage", "true"); break; } if (d->threeD) { plotAreaStyle.addProperty("chart:three-dimensional", "true"); } // Data specific to bar charts if (d->vertical) plotAreaStyle.addProperty("chart:vertical", "true"); // Don't save this if zero, because that's the default. //plotAreaStyle.addProperty("chart:lines-used", 0); // FIXME: for now break; case LineChartType: switch(d->chartSubtype) { case NoChartSubtype: case NormalChartSubtype: break; case StackedChartSubtype: plotAreaStyle.addProperty("chart:stacked", "true"); break; case PercentChartSubtype: plotAreaStyle.addProperty("chart:percentage", "true"); break; } if (d->threeD) { plotAreaStyle.addProperty("chart:three-dimensional", "true"); // FIXME: Save all 3D attributes too. } // FIXME: What does this mean? plotAreaStyle.addProperty("chart:symbol-type", "automatic"); break; case AreaChartType: switch(d->chartSubtype) { case NoChartSubtype: case NormalChartSubtype: break; case StackedChartSubtype: plotAreaStyle.addProperty("chart:stacked", "true"); break; case PercentChartSubtype: plotAreaStyle.addProperty("chart:percentage", "true"); break; } if (d->threeD) { plotAreaStyle.addProperty("chart:three-dimensional", "true"); // FIXME: Save all 3D attributes too. } break; case CircleChartType: plotAreaStyle.addProperty("chart:angle-offset", QString::number(d->angleOffset)); break; case RingChartType: plotAreaStyle.addProperty("chart:angle-offset", QString::number(d->angleOffset)); plotAreaStyle.addProperty("chart:hole-size", QString::number(d->holeSize)); break; case ScatterChartType: // FIXME break; case RadarChartType: case FilledRadarChartType: // Save subtype of the Radar chart. switch(d->chartSubtype) { case NoChartSubtype: case NormalChartSubtype: break; case StackedChartSubtype: plotAreaStyle.addProperty("chart:stacked", "true"); break; case PercentChartSubtype: plotAreaStyle.addProperty("chart:percentage", "true"); break; } break; case StockChartType: { switch(d->chartSubtype) { case NoChartSubtype: case HighLowCloseChartSubtype: case OpenHighLowCloseChartSubtype: plotAreaStyle.addProperty("chart:japanese-candle-stick", "false"); break; case CandlestickChartSubtype: plotAreaStyle.addProperty("chart:japanese-candle-stick", "true"); break; } } case BubbleChartType: case SurfaceChartType: case GanttChartType: // FIXME break; // This is not a valid type, but needs to be handled to avoid // a warning from gcc. case LastChartType: default: // FIXME break; } } void PlotArea::setAngleOffset(qreal angle) { d->angleOffset = angle; emit angleOffsetChanged(angle); } ChartShape *PlotArea::parent() const { // There has to be a valid parent Q_ASSERT(d->shape); return d->shape; } KChart::CartesianCoordinatePlane *PlotArea::kdCartesianPlane(Axis *axis) const { if (axis) { Q_ASSERT(d->axes.contains(axis)); // Only a secondary y axis gets the secondary plane if (axis->dimension() == YAxisDimension && axis != yAxis()) return d->kdCartesianPlaneSecondary; } return d->kdCartesianPlanePrimary; } KChart::PolarCoordinatePlane *PlotArea::kdPolarPlane() const { return d->kdPolarPlane; } KChart::RadarCoordinatePlane *PlotArea::kdRadarPlane() const { return d->kdRadarPlane; } KChart::Chart *PlotArea::kdChart() const { return d->kdChart; } bool PlotArea::registerKdDiagram(KChart::AbstractDiagram *diagram) { if (d->kdDiagrams.contains(diagram)) return false; d->kdDiagrams.append(diagram); return true; } bool PlotArea::deregisterKdDiagram(KChart::AbstractDiagram *diagram) { if (!d->kdDiagrams.contains(diagram)) return false; d->kdDiagrams.removeAll(diagram); return true; } // HACK to get kdChart to recognize secondary planes void PlotArea::registerKdPlane(KChart::AbstractCoordinatePlane *plane) { int pos = d->kdChart->coordinatePlanes().indexOf(plane); if (pos >= 1) { // secondary plane d->kdChart->takeCoordinatePlane(plane); d->kdChart->insertCoordinatePlane(pos, plane); } else if (pos < 0) { d->kdChart->addCoordinatePlane(plane); } } void PlotArea::plotAreaUpdate() { parent()->legend()->update(); if (d->chartType == StockChartType) { updateKChartStockAttributes(); } requestRepaint(); foreach(Axis* axis, d->axes) axis->update(); KoShape::update(); } void PlotArea::requestRepaint() const { d->pixmapRepaintRequested = true; } void PlotArea::paintPixmap(QPainter &painter, const KoViewConverter &converter) { // Adjust the size of the painting area to the current zoom level const QSize paintRectSize = converter.documentToView(size()).toSize(); const QSize plotAreaSize = size().toSize(); const int borderX = 4; const int borderY = 4; // Only use a pixmap with sane sizes d->paintPixmap = false;//paintRectSize.width() < MAX_PIXMAP_SIZE || paintRectSize.height() < MAX_PIXMAP_SIZE; if (d->paintPixmap) { d->image = QImage(paintRectSize, QImage::Format_RGB32); // Copy the painter's render hints, such as antialiasing QPainter pixmapPainter(&d->image); pixmapPainter.setRenderHints(painter.renderHints()); pixmapPainter.setRenderHint(QPainter::Antialiasing, false); // scale the painter's coordinate system to fit the current zoom level applyConversion(pixmapPainter, converter); d->kdChart->paint(&pixmapPainter, QRect(QPoint(borderX, borderY), QSize(plotAreaSize.width() - 2 * borderX, plotAreaSize.height() - 2 * borderY))); } else { d->kdChart->paint(&painter, QRect(QPoint(borderX, borderY), QSize(plotAreaSize.width() - 2 * borderX, plotAreaSize.height() - 2 * borderY))); } } void PlotArea::paint(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &paintContext) { //painter.save(); // First of all, scale the painter's coordinate system to fit the current zoom level applyConversion(painter, converter); // Calculate the clipping rect QRectF paintRect = QRectF(QPointF(0, 0), size()); painter.setClipRect(paintRect, Qt::IntersectClip); // Paint the background if (background()) { QPainterPath p; p.addRect(paintRect); background()->paint(painter, converter, paintContext, p); } // Get the current zoom level QPointF zoomLevel; converter.zoom(&zoomLevel.rx(), &zoomLevel.ry()); // Only repaint the pixmap if it is scheduled, the zoom level // changed or the shape was resized. /*if ( d->pixmapRepaintRequested || d->lastZoomLevel != zoomLevel || d->lastSize != size() || !d->paintPixmap) { // TODO (js): What if two zoom levels are constantly being // requested? At the moment, this *is* the case, // due to the fact that the shape is also rendered // in the page overview in Stage. Every time // the window is hidden and shown again, a repaint // is requested --> laggy performance, especially // when quickly switching through windows. // // ANSWER (iw): what about having a small mapping between size // in pixels and pixmaps? The size could be 2 or // at most 3. We could manage the replacing // using LRU. paintPixmap(painter, converter); d->pixmapRepaintRequested = false; d->lastZoomLevel = zoomLevel; d->lastSize = size(); }*/ painter.setRenderHint(QPainter::Antialiasing, false); // KChart thinks in pixels, Calligra in pt ScreenConversions::scaleFromPtToPx(painter); // Only paint the actual chart if there is a certain minimal size, // because otherwise kdchart will crash. QRect kdchartRect = ScreenConversions::scaleFromPtToPx(paintRect, painter); // Turn off clipping so that border (or "frame") drawn by KChart::Chart // is not not cut off. painter.setClipping(false); if (kdchartRect.width() > 10 && kdchartRect.height() > 10) { d->kdChart->paint(&painter, kdchartRect); } //painter.restore(); // Paint the cached pixmap if we got a GO from paintPixmap() //if (d->paintPixmap) // painter.drawImage(0, 0, d->image); } void PlotArea::relayout() const { d->kdCartesianPlanePrimary->relayout(); d->kdCartesianPlaneSecondary->relayout(); d->kdPolarPlane->relayout(); d->kdRadarPlane->relayout(); update(); } void PlotArea::addTitleToLayout() { addAxesTitlesToLayout(); // for now } void PlotArea::addAxesTitlesToLayout() { ChartLayout *layout = d->shape->layout(); Axis *axis = xAxis(); if (axis) { layout->remove(axis->title()); layout->setItemType(axis->title(), XAxisTitleType); } axis = yAxis(); if (axis) { layout->remove(axis->title()); layout->setItemType(axis->title(), YAxisTitleType); } axis = secondaryXAxis(); if (axis) { layout->remove(axis->title()); layout->setItemType(axis->title(), SecondaryXAxisTitleType); } axis = secondaryYAxis(); if (axis) { layout->remove(axis->title()); layout->setItemType(axis->title(), SecondaryYAxisTitleType); } } void PlotArea::setStockRangeLinePen(const QPen &pen) { d->stockRangeLinePen = pen; } QPen PlotArea::stockRangeLinePen() const { return d->stockRangeLinePen; } void PlotArea::setStockGainBrush(const QBrush &brush) { d->stockGainBrush = brush; } QBrush PlotArea::stockGainBrush() const { return d->stockGainBrush; } void PlotArea::setStockLossBrush(const QBrush &brush) { d->stockLossBrush = brush; } QBrush PlotArea::stockLossBrush() const { return d->stockLossBrush; } void PlotArea::updateKChartStockAttributes() { for (Axis *a : d->axes) { a->updateKChartStockAttributes(); } } DataSet::ValueLabelType PlotArea::valueLabelType() const { return d->valueLabelType; } QString PlotArea::symbolType() const { return d->symbolType; } void PlotArea::setSymbolType(const QString &type) { d->symbolType = type; } QString PlotArea::symbolName() const { return d->symbolName; } void PlotArea::setSymbolName(const QString &name) { d->symbolName = name; } void PlotArea::setValueLabelType(const DataSet::ValueLabelType &type) { d->valueLabelType = type; } diff --git a/plugins/defaultTools/defaulttool/DefaultTool.cpp b/plugins/defaultTools/defaulttool/DefaultTool.cpp index 9962b8a3916..0dac7c355e0 100644 --- a/plugins/defaultTools/defaulttool/DefaultTool.cpp +++ b/plugins/defaultTools/defaulttool/DefaultTool.cpp @@ -1,1407 +1,1408 @@ /* 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 "DefaultToolWidget.h" #include "DefaultToolArrangeWidget.h" #include "SelectionDecorator.h" #include "ShapeMoveStrategy.h" #include "ShapeRotateStrategy.h" #include "ShapeShearStrategy.h" #include "ShapeResizeStrategy.h" #include "guidestool/GuidesTool.h" #include "guidestool/GuidesToolFactory.h" // for the ID #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 #define HANDLE_DISTANCE 10 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 {} }; class SelectionHandler : public KoToolSelection { public: SelectionHandler(DefaultTool *parent) : KoToolSelection(parent), m_selection(parent->koSelection()) { Q_ASSERT(m_selection); } bool hasSelection() override { return m_selection->count(); } private: KoSelection *m_selection; }; class DefaultTool::GuideLine { public: GuideLine() : m_orientation(Qt::Horizontal), m_index(0), m_valid(false), m_selected(false) { } GuideLine(Qt::Orientation orientation, uint index) : m_orientation(orientation), m_index(index), m_valid(true), m_selected(false) { } bool isValid() const { return m_valid; } bool isSelected() const { return m_selected; } void select() { m_selected = true; } uint index() const { return m_index; } Qt::Orientation orientation() const { return m_orientation; } private: Qt::Orientation m_orientation; uint m_index; bool m_valid; bool m_selected; }; DefaultTool::DefaultTool(KoCanvasBase *canvas) : KoInteractionTool(canvas), m_lastHandle(KoFlake::NoHandle), m_hotPosition(KoFlake::TopLeftCorner), m_mouseWasInsideHandles(false), m_moveCommand(0), m_selectionHandler(new SelectionHandler(this)), m_customEventStrategy(0), m_guideLine(new GuideLine()) { setupActions(); QPixmap rotatePixmap, shearPixmap; rotatePixmap.load(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "calligra/cursors/cursor_rotate.png")); Q_ASSERT(!rotatePixmap.isNull()); shearPixmap.load(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "calligra/cursors/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; KoShapeManager * manager = canvas->shapeManager(); connect(manager, SIGNAL(selectionChanged()), this, SLOT(updateActions())); } DefaultTool::~DefaultTool() { delete m_guideLine; } bool DefaultTool::wantsAutoScroll() const { return true; } void DefaultTool::setupActions() { QAction * actionBringToFront = new QAction(koIcon("object-order-front"), i18n("Bring to &Front"), this); addAction("object_order_front", actionBringToFront); actionBringToFront->setShortcut(QKeySequence("Ctrl+Shift+]")); connect(actionBringToFront, SIGNAL(triggered()), this, SLOT(selectionBringToFront())); QAction * actionRaise = new QAction(koIcon("object-order-raise"), i18n("&Raise"), this); addAction("object_order_raise", actionRaise); actionRaise->setShortcut(QKeySequence("Ctrl+]")); connect(actionRaise, SIGNAL(triggered()), this, SLOT(selectionMoveUp())); QAction * actionLower = new QAction(koIcon("object-order-lower"), i18n("&Lower"), this); addAction("object_order_lower", actionLower); actionLower->setShortcut(QKeySequence("Ctrl+[")); connect(actionLower, SIGNAL(triggered()), this, SLOT(selectionMoveDown())); QAction * actionSendToBack = new QAction(koIcon("object-order-back"), i18n("Send to &Back"), this); addAction("object_order_back", actionSendToBack); actionSendToBack->setShortcut(QKeySequence("Ctrl+Shift+[")); connect(actionSendToBack, SIGNAL(triggered()), this, SLOT(selectionSendToBack())); QAction * actionAlignLeft = new QAction(koIcon("align-horizontal-left"), i18n("Align Left"), this); addAction("object_align_horizontal_left", actionAlignLeft); connect(actionAlignLeft, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalLeft())); QAction * actionAlignCenter = new QAction(koIcon("align-horizontal-center"), i18n("Horizontally Center"), this); addAction("object_align_horizontal_center", actionAlignCenter); connect(actionAlignCenter, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalCenter())); QAction * actionAlignRight = new QAction(koIcon("align-horizontal-right"), i18n("Align Right"), this); addAction("object_align_horizontal_right", actionAlignRight); connect(actionAlignRight, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalRight())); QAction * actionAlignTop = new QAction(koIcon("align-vertical-top"), i18n("Align Top"), this); addAction("object_align_vertical_top", actionAlignTop); connect(actionAlignTop, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalTop())); QAction * actionAlignMiddle = new QAction(koIcon("align-vertical-center"), i18n("Vertically Center"), this); addAction("object_align_vertical_center", actionAlignMiddle); connect(actionAlignMiddle, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalCenter())); QAction * actionAlignBottom = new QAction(koIcon("align-vertical-bottom"), i18n("Align Bottom"), this); addAction("object_align_vertical_bottom", actionAlignBottom); connect(actionAlignBottom, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalBottom())); QAction * actionGroupBottom = new QAction(koIcon("object-group"), i18n("Group"), this); addAction("object_group", actionGroupBottom); connect(actionGroupBottom, SIGNAL(triggered()), this, SLOT(selectionGroup())); QAction * actionUngroupBottom = new QAction(koIcon("object-ungroup"), i18n("Ungroup"), this); addAction("object_ungroup", actionUngroupBottom); connect(actionUngroupBottom, SIGNAL(triggered()), this, SLOT(selectionUngroup())); } 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::TopRightCorner) - koSelection()->absolutePosition(KoFlake::TopLeftCorner); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeftCorner); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::TopRightCorner) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::TopRightHandle: direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopRightCorner) - koSelection()->absolutePosition(KoFlake::TopLeftCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopRightCorner) - koSelection()->absolutePosition(KoFlake::BottomRightCorner)).normalized()).toPointF(); break; case KoFlake::RightMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::BottomRightCorner) - koSelection()->absolutePosition(KoFlake::TopRightCorner); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopRightCorner); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRightCorner) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::BottomRightHandle: direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomRightCorner) - koSelection()->absolutePosition(KoFlake::BottomLeftCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomRightCorner) - koSelection()->absolutePosition(KoFlake::TopRightCorner)).normalized()).toPointF(); break; case KoFlake::BottomMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - koSelection()->absolutePosition(KoFlake::BottomRightCorner); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::BottomLeftCorner); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRightCorner) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::BottomLeftHandle: direction = koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - selectionCenter; direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - koSelection()->absolutePosition(KoFlake::BottomRightCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - koSelection()->absolutePosition(KoFlake::TopLeftCorner)).normalized()).toPointF(); break; case KoFlake::LeftMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::TopLeftCorner) - koSelection()->absolutePosition(KoFlake::BottomLeftCorner); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeftCorner); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::TopLeftHandle: direction = koSelection()->absolutePosition(KoFlake::TopLeftCorner) - selectionCenter; direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopLeftCorner) - koSelection()->absolutePosition(KoFlake::TopRightCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopLeftCorner) - koSelection()->absolutePosition(KoFlake::BottomLeftCorner)).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() { QCursor cursor = Qt::ArrowCursor; QString statusText; if (koSelection()->count() > 0) { // has a selection KoShape::AllowedInteractions interactions = allowedInteractions(koSelection()->selectedShapes(KoFlake::StrippedSelection)); 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: if (interactions.testFlag(KoShape::ShearingAllowed)) { cursor = m_shearCursors[(0 +rotOctant)%8]; shearHandle = true; } break; case KoFlake::TopRightHandle: if (interactions.testFlag(KoShape::RotationAllowed)) { cursor = m_rotateCursors[(1 +rotOctant)%8]; rotateHandle = true; } break; case KoFlake::RightMiddleHandle: if (interactions.testFlag(KoShape::ShearingAllowed)) { cursor = m_shearCursors[(2 +rotOctant)%8]; shearHandle = true; } break; case KoFlake::BottomRightHandle: if (interactions.testFlag(KoShape::RotationAllowed)) { cursor = m_rotateCursors[(3 +rotOctant)%8]; rotateHandle = true; } break; case KoFlake::BottomMiddleHandle: if (interactions.testFlag(KoShape::ShearingAllowed)) { cursor = m_shearCursors[(4 +rotOctant)%8]; shearHandle = true; } break; case KoFlake::BottomLeftHandle: if (interactions.testFlag(KoShape::RotationAllowed)) { cursor = m_rotateCursors[(5 +rotOctant)%8]; rotateHandle = true; } break; case KoFlake::LeftMiddleHandle: if (interactions.testFlag(KoShape::ShearingAllowed)) { cursor = m_shearCursors[(6 +rotOctant)%8]; shearHandle = true; } break; case KoFlake::TopLeftHandle: if (interactions.testFlag(KoShape::RotationAllowed)) { cursor = m_rotateCursors[(7 +rotOctant)%8]; rotateHandle = true; } break; case KoFlake::NoHandle: if (m_guideLine->isValid()) { cursor = m_guideLine->orientation() == Qt::Horizontal ? Qt::SizeVerCursor : Qt::SizeHorCursor; statusText = i18n("Click and drag to move guide line."); } else 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: if (interactions.testFlag(KoShape::ResizeAllowed)) { cursor = m_sizeCursors[(0 +rotOctant)%8]; } break; case KoFlake::TopRightHandle: if (interactions.testFlag(KoShape::ResizeAllowed)) { cursor = m_sizeCursors[(1 +rotOctant)%8]; cornerHandle = true; } break; case KoFlake::RightMiddleHandle: if (interactions.testFlag(KoShape::ResizeAllowed)) { cursor = m_sizeCursors[(2 +rotOctant)%8]; } break; case KoFlake::BottomRightHandle: if (interactions.testFlag(KoShape::ResizeAllowed)) { cursor = m_sizeCursors[(3 +rotOctant)%8]; cornerHandle = true; } break; case KoFlake::BottomMiddleHandle: if (interactions.testFlag(KoShape::ResizeAllowed)) { cursor = m_sizeCursors[(4 +rotOctant)%8]; } break; case KoFlake::BottomLeftHandle: if (interactions.testFlag(KoShape::ResizeAllowed)) { cursor = m_sizeCursors[(5 +rotOctant)%8]; cornerHandle = true; } break; case KoFlake::LeftMiddleHandle: if (interactions.testFlag(KoShape::ResizeAllowed)) { cursor = m_sizeCursors[(6 +rotOctant)%8]; } break; case KoFlake::TopLeftHandle: if (interactions.testFlag(KoShape::ResizeAllowed)) { cursor = m_sizeCursors[(7 +rotOctant)%8]; cornerHandle = true; } break; case KoFlake::NoHandle: if (interactions.testFlag(KoShape::MoveAllowed)) { 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."); } } else { if (m_guideLine->isValid()) { cursor = m_guideLine->orientation() == Qt::Horizontal ? Qt::SizeVerCursor : Qt::SizeHorCursor; statusText = i18n("Click and drag to move guide line."); } } useCursor(cursor); if (currentStrategy() == 0) emit statusTextChanged(statusText); } void DefaultTool::paint(QPainter &painter, const KoViewConverter &converter) { KoInteractionTool::paint(painter, converter); if (currentStrategy() == 0 && koSelection()->count() > 0) { SelectionDecorator decorator(m_mouseWasInsideHandles ? m_lastHandle : KoFlake::NoHandle, true, true); decorator.setSelection(koSelection()); decorator.setHandleRadius(handleRadius()); decorator.setHotPosition(m_hotPosition); decorator.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()->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; if (m_guideLine->isSelected()) { GuidesTool *guidesTool = dynamic_cast(KoToolManager::instance()->toolById(canvas(), GuidesToolId)); if (guidesTool) { guidesTool->moveGuideLine(m_guideLine->orientation(), m_guideLine->index()); activateTemporary(guidesTool->toolId()); } } else { selectGuideAtPosition(event->point); } } } else { if (m_guideLine->isSelected()) { GuidesTool *guidesTool = dynamic_cast(KoToolManager::instance()->toolById(canvas(), GuidesToolId)); if (guidesTool) { guidesTool->moveGuideLine(m_guideLine->orientation(), m_guideLine->index()); activateTemporary(guidesTool->toolId()); } } else { selectGuideAtPosition(event->point); } } updateCursor(); } void DefaultTool::selectGuideAtPosition(const QPointF &position) { int index = -1; Qt::Orientation orientation = Qt::Horizontal; // check if we are on a guide line KoGuidesData * guidesData = canvas()->guidesData(); if (guidesData && guidesData->showGuideLines()) { qreal minDistance = canvas()->viewConverter()->viewToDocumentX(grabSensitivity()); uint i = 0; foreach (qreal guidePos, guidesData->horizontalGuideLines()) { qreal distance = qAbs(guidePos - position.y()); if (distance < minDistance) { orientation = Qt::Horizontal; index = i; minDistance = distance; } i++; } i = 0; foreach (qreal guidePos, guidesData->verticalGuideLines()) { qreal distance = qAbs(guidePos - position.x()); if (distance < minDistance) { orientation = Qt::Vertical; index = i; minDistance = distance; } i++; } } delete m_guideLine; if (index >= 0) m_guideLine = new GuideLine(orientation, index); else m_guideLine = new GuideLine(); } QRectF DefaultTool::handlesSize() { QRectF bound = koSelection()->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) { QList shapes; foreach(KoShape *shape, koSelection()->selectedShapes()) { if (shape->boundingRect().contains(event->point) && // first 'cheap' check shape->outline().contains(event->point)) // this is more expensive but weeds out the almost hits shapes.append(shape); } if (shapes.count() == 0) { // nothing in the selection was clicked on. KoShape *shape = canvas()->shapeManager()->shapeAt (event->point, KoFlake::ShapeOnTop); if (shape) { shapes.append(shape); } else if (m_guideLine->isSelected()) { GuidesTool *guidesTool = dynamic_cast(KoToolManager::instance()->toolById(canvas(), GuidesToolId)); if (guidesTool) { guidesTool->editGuideLine(m_guideLine->orientation(), m_guideLine->index()); activateTool(guidesTool->toolId()); return; } } } QList shapes2; foreach (KoShape *shape, shapes) { QSet delegates = shape->toolDelegates(); if (delegates.isEmpty()) { shapes2.append(shape); } else { foreach (KoShape *delegatedShape, delegates) { shapes2.append(delegatedShape); } } } KoToolManager::instance()->switchToolRequested( KoToolManager::instance()->preferredToolForSelection(shapes2)); } bool DefaultTool::moveSelection(int direction, Qt::KeyboardModifiers modifiers) { 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; } QVector prevPos; QVector newPos; QList shapes; foreach(KoShape* shape, koSelection()->selectedShapes(KoFlake::TopLevelSelection)) { if (shape->isGeometryProtected()) { continue; } if (shape->parent()) { QPointF proposed(x, y); shape->parent()->model()->proposeMove(shape, proposed); if (!proposed.isNull()) { shapes.append(shape); QPointF p = shape->position(); prevPos.append(p); p += proposed; newPos.append(p); } } else { shapes.append(shape); QPointF p = shape->position(); prevPos.append(p); p.setX(p.x() + x); p.setY(p.y() + y); newPos.append(p); } } if (shapes.count() > 0) { // use a timeout to make sure we don't reuse a command possibly deleted by the commandHistory if (m_lastUsedMoveCommand.msecsTo(QTime::currentTime()) > 5000) m_moveCommand = 0; if (m_moveCommand && shapes != m_lastUsedShapes) { // We are not moving exactly the same shapes in the same order as last time, // so we cannot reuse the command m_moveCommand = 0; m_lastUsedShapes.clear(); } if (m_moveCommand) { // alter previous instead of creating new one. m_moveCommand->setNewPositions(newPos); m_moveCommand->redo(); } else { m_lastUsedShapes = shapes; m_moveCommand = new KoShapeMoveCommand(shapes, prevPos, newPos); canvas()->addCommand(m_moveCommand); } m_lastUsedMoveCommand = QTime::currentTime(); return true; } } return false; } 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::customMoveEvent(KoPointerEvent * event) { if (! koSelection()->count()) { event->ignore(); return; } int move = qMax(qAbs(event->x()), qAbs(event->y())); int zoom = qAbs(event->z()); int rotate = qAbs(event->rotationZ()); const int threshold = 2; if (move < threshold && zoom < threshold && rotate < threshold) { if (m_customEventStrategy) { m_customEventStrategy->finishInteraction(event->modifiers()); KUndo2Command *command = m_customEventStrategy->createCommand(); if (command) canvas()->addCommand(command); delete m_customEventStrategy; m_customEventStrategy = 0; repaintDecorations(); } event->accept(); return; } // check if the z-movement is dominant if (zoom > move && zoom > rotate) { // zoom if (! m_customEventStrategy) m_customEventStrategy = new ShapeResizeStrategy(this, event->point, KoFlake::TopLeftHandle); } else if (move > zoom && move > rotate) { // check if x-/y-movement is dominant // move if (! m_customEventStrategy) m_customEventStrategy = new ShapeMoveStrategy(this, event->point); } else if (rotate > zoom && rotate > move) // rotation is dominant { // rotate if (! m_customEventStrategy) m_customEventStrategy = new ShapeRotateStrategy(this, event->point, event->buttons()); } if (m_customEventStrategy) m_customEventStrategy->handleCustomEvent(event); event->accept(); } void DefaultTool::repaintDecorations() { Q_ASSERT(koSelection()); if (koSelection()->count() > 0) canvas()->updateCanvas(handlesSize()); } void DefaultTool::copy() const { QList shapes = canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection); if (!shapes.empty()) { KoShapeOdfSaveHelper saveHelper(shapes); KoDrag drag; drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper); drag.addToClipboard(); } } void DefaultTool::deleteSelection() { QList shapes; foreach (KoShape *s, canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection)) { if (!s->isDeletable() || 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; } QStringList DefaultTool::supportedPasteMimeTypes() const { QStringList list; list << KoOdf::mimeType(KoOdf::Text); return list; } KoSelection *DefaultTool::koSelection() { Q_ASSERT(canvas()); Q_ASSERT(canvas()->shapeManager()); return canvas()->shapeManager()->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 }; if (koSelection()->count() == 0) return KoFlake::NoHandle; recalcSelectionBox(); const KoViewConverter *converter = canvas()->viewConverter(); if (!converter) return KoFlake::NoHandle; if (innerHandleMeaning != 0) { QPainterPath path; path.addPolygon(m_selectionOutline); *innerHandleMeaning = path.contains(point) || path.intersects(handlePaintRect(point)); } for (int i = 0; i < KoFlake::NoHandle; ++i) { KoFlake::SelectionHandle handle = handleOrder[i]; QPointF pt = converter->documentToView(point) - converter->documentToView(m_selectionBox[handle]); // if just inside the outline if (qAbs(pt.x()) < HANDLE_DISTANCE && qAbs(pt.y()) < HANDLE_DISTANCE) { if (innerHandleMeaning != 0) { if (qAbs(pt.x()) < 4 && qAbs(pt.y()) < 4) *innerHandleMeaning = true; } return handle; } } return KoFlake::NoHandle; } void DefaultTool::recalcSelectionBox() { if (koSelection()->count()==0) return; if (koSelection()->count()>1) { QTransform matrix = koSelection()->absoluteTransformation(0); m_selectionOutline = matrix.map(QPolygonF(QRectF(QPointF(0, 0), koSelection()->size()))); m_angle = 0.0; //koSelection()->rotation(); } else { QTransform matrix = koSelection()->firstSelectedShape()->absoluteTransformation(0); m_selectionOutline = matrix.map(QPolygonF(QRectF(QPointF(0, 0), koSelection()->firstSelectedShape()->size()))); m_angle = 0.0; //koSelection()->firstSelectedShape()->rotation(); } 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 (koSelection()->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]); } 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]); } #endif } } void DefaultTool::activate(ToolActivation, const QSet &) { m_mouseWasInsideHandles = false; m_lastHandle = KoFlake::NoHandle; useCursor(Qt::ArrowCursor); repaintDecorations(); delete m_guideLine; m_guideLine = new GuideLine(); updateActions(); } void DefaultTool::deactivate() { repaintDecorations(); } void DefaultTool::selectionAlignHorizontalLeft() { selectionAlign(KoShapeAlignCommand::HorizontalLeftAlignment); } void DefaultTool::selectionAlignHorizontalCenter() { selectionAlign(KoShapeAlignCommand::HorizontalCenterAlignment); } void DefaultTool::selectionAlignHorizontalRight() { selectionAlign(KoShapeAlignCommand::HorizontalRightAlignment); } void DefaultTool::selectionAlignVerticalTop() { selectionAlign(KoShapeAlignCommand::VerticalTopAlignment); } void DefaultTool::selectionAlignVerticalCenter() { selectionAlign(KoShapeAlignCommand::VerticalCenterAlignment); } void DefaultTool::selectionAlignVerticalBottom() { selectionAlign(KoShapeAlignCommand::VerticalBottomAlignment); } void DefaultTool::selectionGroup() { KoSelection* selection = koSelection(); if (! selection) return; QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); QList groupedShapes; // only group shapes with an unselected parent foreach (KoShape* shape, selectedShapes) { if (! selectedShapes.contains(shape->parent()) && isEditable(shape)) { groupedShapes << shape; } } 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); KoShapeGroupCommand::createCommand(group, groupedShapes, cmd); canvas()->addCommand(cmd); // update selection so we can ungroup immediately again selection->deselectAll(); selection->select(group); } void DefaultTool::selectionUngroup() { KoSelection* selection = canvas()->shapeManager()->selection(); if (! selection) return; QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); QList containerSet; // only ungroup shape groups with an unselected parent foreach (KoShape* shape, selectedShapes) { if (!selectedShapes.contains(shape->parent()) && isEditable(shape)) { containerSet << shape; } } KUndo2Command *cmd = 0; // add a ungroup command for each found shape container to the macro command foreach(KoShape* shape, containerSet) { 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(KoShapeAlignCommand::Align align) { KoSelection* selection = canvas()->shapeManager()->selection(); if (! selection) return; QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); if (selectedShapes.count() < 1) return; QList editableShapes = filterEditableShapes(selectedShapes); // 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 { foreach( KoShape * shape, editableShapes ) { bb |= shape->boundingRect(); } } KoShapeAlignCommand *cmd = new KoShapeAlignCommand(editableShapes, align, bb); canvas()->addCommand(cmd); selection->updateSizeAndPosition(); } 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()->shapeManager()->selection(); if (! selection) return; QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); if (selectedShapes.count() < 1) return; QList editableShapes = filterEditableShapes(selectedShapes); if (editableShapes.count() < 1) return; KUndo2Command * cmd = KoShapeReorderCommand::createCommand(editableShapes, canvas()->shapeManager(), order); if (cmd) { canvas()->addCommand(cmd); } } QList > DefaultTool::createOptionWidgets() { QList > widgets; DefaultToolArrangeWidget *defaultArrange = new DefaultToolArrangeWidget(this); defaultArrange->setWindowTitle(i18n("Arrange")); widgets.append(defaultArrange); DefaultToolWidget *defaultTool = new DefaultToolWidget(this); defaultTool->setWindowTitle(i18n("Geometry")); widgets.append(defaultTool); KoStrokeConfigWidget *strokeWidget = new KoStrokeConfigWidget(0); strokeWidget->setWindowTitle(i18n("Line")); strokeWidget->setCanvas(canvas()); widgets.append(strokeWidget); KoFillConfigWidget *fillWidget = new KoFillConfigWidget(0); fillWidget->setWindowTitle(i18n("Fill")); fillWidget->setCanvas(canvas()); widgets.append(fillWidget); KoShadowConfigWidget *shadowWidget = new KoShadowConfigWidget(0); shadowWidget->setWindowTitle(i18n("Shadow")); shadowWidget->setCanvas(canvas()); widgets.append(shadowWidget); return widgets; } void DefaultTool::canvasResourceChanged(int key, const QVariant & res) { if (key == HotPosition) { m_hotPosition = static_cast(res.toInt()); repaintDecorations(); } } KoInteractionStrategy *DefaultTool::createStrategy(KoPointerEvent *event) { // reset the move by keys when a new strategy is created otherwise we might change the // command after a new command was added. This happens when you where faster than the timer. m_moveCommand = 0; KoShapeManager *shapeManager = canvas()->shapeManager(); KoSelection *select = shapeManager->selection(); bool insideSelection; KoFlake::SelectionHandle handle = handleAt(event->point, &insideSelection); bool editableShape = editableShapesCount(select->selectedShapes()); KoShape::AllowedInteractions interactions = allowedInteractions(select->selectedShapes()); if (event->buttons() & Qt::MidButton) { // change the hot selection position when middle clicking on a handle KoFlake::Position newHotPosition = m_hotPosition; switch (handle) { case KoFlake::TopLeftHandle: newHotPosition = KoFlake::TopLeftCorner; break; case KoFlake::TopRightHandle: newHotPosition = KoFlake::TopRightCorner; break; case KoFlake::BottomLeftHandle: newHotPosition = KoFlake::BottomLeftCorner; break; case KoFlake::BottomRightHandle: newHotPosition = KoFlake::BottomRightCorner; break; default: { // check if we had hit the center point const KoViewConverter * converter = canvas()->viewConverter(); QPointF pt = converter->documentToView(event->point-select->absolutePosition()); if (qAbs(pt.x()) < HANDLE_DISTANCE && qAbs(pt.y()) < HANDLE_DISTANCE) newHotPosition = KoFlake::CenteredPosition; break; } } if (m_hotPosition != newHotPosition) canvas()->resourceManager()->setResource(HotPosition, newHotPosition); return 0; } bool selectMultiple = event->modifiers() & Qt::ControlModifier; bool selectNextInStack = event->modifiers() & Qt::ShiftModifier; if (editableShape) { // manipulation of selected shapes goes first if (handle != KoFlake::NoHandle) { if (event->buttons() == Qt::LeftButton) { // resizing or shearing only with left mouse button if (insideSelection) { if (interactions.testFlag(KoShape::ResizeAllowed)) { return new ShapeResizeStrategy(this, event->point, handle); } } else if (interactions.testFlag(KoShape::ShearingAllowed)) { if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle || handle == KoFlake::BottomMiddleHandle || handle == KoFlake::LeftMiddleHandle) { return new ShapeShearStrategy(this, event->point, handle); } } } // rotating is allowed for right mouse button too if (interactions.testFlag(KoShape::RotationAllowed)) { if (handle == KoFlake::TopLeftHandle || handle == KoFlake::TopRightHandle || handle == KoFlake::BottomLeftHandle || handle == KoFlake::BottomRightHandle) return new ShapeRotateStrategy(this, event->point, event->buttons()); } } if (! (selectMultiple || selectNextInStack) && event->buttons() == Qt::LeftButton) { const QPainterPath outlinePath = select->transformation().map(select->outline()); if (outlinePath.contains(event->point) || outlinePath.intersects(handlePaintRect(event->point))) return new ShapeMoveStrategy(this, event->point); } } if ((event->buttons() & Qt::LeftButton) == 0) return 0; // Nothing to do for middle/right mouse button KoShape *shape = shapeManager->shapeAt(event->point, selectNextInStack ? KoFlake::NextUnselected : KoFlake::ShapeOnTop); if (!shape && handle == KoFlake::NoHandle) { // check if we have hit a guide if (m_guideLine->isValid()) { m_guideLine->select(); return 0; } if (! selectMultiple) { repaintDecorations(); select->deselectAll(); } return new KoShapeRubberSelectStrategy(this, event->point); } if (select->isSelected(shape)) { if (selectMultiple) { repaintDecorations(); select->deselect(shape); } } else if (handle == KoFlake::NoHandle) { // clicked on shape which is not selected repaintDecorations(); if (! selectMultiple) shapeManager->selection()->deselectAll(); select->select(shape, selectNextInStack ? false : true); 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() { KoSelection * selection(koSelection()); if (!selection) { action("object_order_front")->setEnabled(false); action("object_order_raise")->setEnabled(false); action("object_order_lower")->setEnabled(false); action("object_order_back")->setEnabled(false); action("object_align_horizontal_left")->setEnabled(false); action("object_align_horizontal_center")->setEnabled(false); action("object_align_horizontal_right")->setEnabled(false); action("object_align_vertical_top")->setEnabled(false); action("object_align_vertical_center")->setEnabled(false); action("object_align_vertical_bottom")->setEnabled(false); action("object_group")->setEnabled(false); action("object_ungroup")->setEnabled(false); return; } QList editableShapes = filterEditableShapes(selection->selectedShapes(KoFlake::TopLevelSelection)); bool enable = editableShapes.count () > 0; action("object_order_front")->setEnabled(enable); action("object_order_raise")->setEnabled(enable); action("object_order_lower")->setEnabled(enable); action("object_order_back")->setEnabled(enable); enable = (editableShapes.count () > 1) || (enable && canvas()->resourceManager()->hasResource(KoCanvasResourceManager::PageSize)); action("object_align_horizontal_left")->setEnabled(enable); action("object_align_horizontal_center")->setEnabled(enable); action("object_align_horizontal_right")->setEnabled(enable); action("object_align_vertical_top")->setEnabled(enable); action("object_align_vertical_center")->setEnabled(enable); action("object_align_vertical_bottom")->setEnabled(enable); action("object_group")->setEnabled(editableShapes.count() > 1); bool groupShape = false; foreach (KoShape * shape, editableShapes) { if (dynamic_cast(shape)) { groupShape = true; break; } } action("object_ungroup")->setEnabled(groupShape); emit selectionChanged(selection->count()); } KoToolSelection* DefaultTool::selection() { return m_selectionHandler; } QList DefaultTool::filterEditableShapes( const QList &shapes ) const { QList editableShapes; foreach( KoShape * shape, shapes ) { if (isEditable(shape)) editableShapes.append(shape); } return editableShapes; } uint DefaultTool::editableShapesCount( const QList &shapes ) const { uint count = 0; foreach( KoShape * shape, shapes ) { if (isEditable(shape)) count++; } return count; } bool DefaultTool::isEditable(const KoShape *shape) const { return shape->allowedInteractions(false) & (KoShape::MoveAllowed | KoShape::ResizeAllowed); // TODO: check parents ContentChangeAllowed } KoShape::AllowedInteractions DefaultTool::allowedInteractions(const QList &shapes) const { KoShape::AllowedInteractions interactions; foreach(KoShape *shape, shapes) { interactions |= shape->allowedInteractions(false); } return interactions; } diff --git a/plugins/formulashape/elements/BasicElement.cpp b/plugins/formulashape/elements/BasicElement.cpp index 2cbe039ee4c..485c9ffb0f0 100644 --- a/plugins/formulashape/elements/BasicElement.cpp +++ b/plugins/formulashape/elements/BasicElement.cpp @@ -1,530 +1,531 @@ /* This file is part of the KDE project Copyright (C) 2001 Andrea Rizzi Ulrich Kuettler Copyright (C) 2006 Martin Pfeiffer Copyright (C) 2006 Alfredo Beaumont Sainz 2009 Jeremias Epperlein 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 "BasicElement.h" #include "AttributeManager.h" #include "FormulaCursor.h" #include "TableDataElement.h" #include "FormulaDebug.h" #include #include #include +#include #include BasicElement::BasicElement( BasicElement* p ) : m_parentElement( p ) { m_scaleFactor = 1.0; m_scaleLevel = 1; m_boundingRect.setTopLeft( QPointF( 0.0, 0.0 ) ); m_boundingRect.setWidth( 7.0 ); // standard values m_boundingRect.setHeight( 10.0 ); m_displayStyle = true; setBaseLine( 10.0 ); } BasicElement::~BasicElement() { m_attributes.clear(); } void BasicElement::paint( QPainter& painter, AttributeManager* ) { painter.save(); painter.setBrush( QBrush( Qt::blue ) ); painter.drawRect( QRectF(0.0, 0.0, width(), height()) ); painter.restore(); } void BasicElement::paintEditingHints ( QPainter& painter, AttributeManager* am ) { Q_UNUSED( painter ) Q_UNUSED( am ) } void BasicElement::layout( const AttributeManager* ) { /* do nothing */ } void BasicElement::stretch() { foreach( BasicElement* tmpElement, childElements() ) { tmpElement->stretch(); } } bool BasicElement::acceptCursor( const FormulaCursor& cursor ) { Q_UNUSED( cursor ) return false; } bool BasicElement::moveCursor(FormulaCursor& newcursor, FormulaCursor& oldcursor) { Q_UNUSED( newcursor ) Q_UNUSED( oldcursor ) return false; } QLineF BasicElement::cursorLine(int position) const { Q_UNUSED( position ) QPointF top = absoluteBoundingRect().topLeft(); QPointF bottom = top + QPointF( 0.0, height() ); return QLineF(top,bottom); } QPainterPath BasicElement::selectionRegion(const int pos1, const int pos2) const { QLineF l1=cursorLine(pos1); QLineF l2=cursorLine(pos2); //TODO: find out why doesn't work //QRectF r1(l1.p1(),l1.p2()); //QRectF r2(l2.p1(),l2.p2()); QRectF r1(l1.p1(),l2.p2()); QRectF r2(l2.p1(),l1.p2()); QPainterPath temp; temp.addRect(r1.united(r2)); return temp; } const QRectF BasicElement::absoluteBoundingRect() const { QPointF neworigin = origin(); BasicElement* tmp=parentElement(); while (tmp) { neworigin+=tmp->origin(); tmp=tmp->parentElement(); } return QRectF(neworigin,QSizeF(width(),height())); } bool BasicElement::setCursorTo(FormulaCursor& cursor, QPointF point) { Q_UNUSED( point ) cursor.setPosition(0); cursor.setCurrentElement(this); return true; } bool BasicElement::replaceChild( BasicElement* oldelement, BasicElement* newelement) { Q_UNUSED( oldelement ) Q_UNUSED( newelement ) return false; } const QList BasicElement::childElements() const { warnFormula << "Returning no elements from BasicElement"; return QList(); } BasicElement* BasicElement::childElementAt( const QPointF& p ) { if( !m_boundingRect.contains( p ) ) return 0; if( childElements().isEmpty() ) return this; BasicElement* ownerElement = 0; foreach( BasicElement* tmpElement, childElements() ) { ownerElement = tmpElement->childElementAt( p ); if( ownerElement ) return ownerElement; } return this; // if no child contains the point, it's the FormulaElement itsself } void BasicElement::setAttribute( const QString& name, const QVariant& value ) { if( name.isEmpty() || !value.canConvert( QVariant::String ) ) return; if( value.isNull() ) m_attributes.remove( name ); else m_attributes.insert( name, value.toString() ); } QString BasicElement::attribute( const QString& attribute ) const { QString tmp = m_attributes.value( attribute ); if( tmp.isEmpty() ) return QString(); return tmp; } QString BasicElement::inheritsAttribute( const QString& ) const { return QString(); // do nothing } QString BasicElement::attributesDefaultValue( const QString& ) const { return QString(); // do nothing } bool BasicElement::readMathML( const KoXmlElement& element ) { readMathMLAttributes( element ); return readMathMLContent( element ); } bool BasicElement::readMathMLAttributes( const KoXmlElement& element ) { QStringList attributeList = KoXml::attributeNames( element ); foreach( const QString &attributeName, attributeList ) { m_attributes.insert( attributeName.toLower(), element.attribute( attributeName ).toLower() ); } return true; } bool BasicElement::readMathMLContent( const KoXmlElement& parent ) { Q_UNUSED( parent ) return true; } void BasicElement::writeMathML( KoXmlWriter* writer, const QString& ns ) const { if (elementType() == Basic || elementType() == Unknown) { return; } // Collapse a an with only one child element to the child element itself. if ((elementType() == Row) && (childElements().count()==1)) { foreach( BasicElement* tmp, childElements() ) { tmp->writeMathML( writer, ns ); } } else { const QByteArray name = ns.isEmpty() ? ElementFactory::elementName( elementType() ).toLatin1() : ns.toLatin1() + ':' + ElementFactory::elementName( elementType() ).toLatin1(); writer->startElement( name ); writeMathMLAttributes( writer ); if ( elementType() == Formula ) { /* * This is a hack to make OOo compatible. It's mandatory * for OOo requires a semantics element as math element's * child */ writer->startElement( "math:semantics" ); } writeMathMLContent( writer, ns ); if ( elementType() == Formula ) { writer->endElement(); } writer->endElement(); } } void BasicElement::writeMathMLAttributes( KoXmlWriter* writer ) const { // Ensure consistent ordering of attributes: // create list of attributes pointers sorted by attribute name, with a first, z last, prefix included typedef QHash::ConstIterator ConstAttributeIterator; QVector sits; sits.reserve(m_attributes.size()); // insert iterators by bubble-sorting ConstAttributeIterator it = m_attributes.constBegin(); while (it != m_attributes.constEnd()) { QVector::Iterator sit = sits.begin(); while (sit != sits.end()) { if (sit->key() > it.key()) { break; } ++sit; } sits.insert(sit, it); ++it; } // finally write all attributes, by estimated sorting foreach(ConstAttributeIterator it, sits) { writer->addAttribute( it.key().toLatin1(), it.value() ); } } void BasicElement::writeMathMLContent( KoXmlWriter* writer, const QString& ns ) const { Q_UNUSED( writer ) // this is just to be reimplemented Q_UNUSED( ns ) // this is just to be reimplemented } ElementType BasicElement::elementType() const { return Basic; } const QRectF& BasicElement::boundingRect() const { return m_boundingRect; } const QRectF& BasicElement::childrenBoundingRect() const { return m_childrenBoundingRect; } void BasicElement::setChildrenBoundingRect(const QRectF &rect) { m_childrenBoundingRect = rect; Q_ASSERT(m_childrenBoundingRect.bottom() <= m_boundingRect.height()); Q_ASSERT(m_childrenBoundingRect.right() <= m_boundingRect.width()); } qreal BasicElement::height() const { return m_boundingRect.height(); } qreal BasicElement::width() const { return m_boundingRect.width(); } qreal BasicElement::baseLine() const { return m_baseLine; } QPointF BasicElement::origin() const { return m_boundingRect.topLeft(); } BasicElement* BasicElement::parentElement() const { return m_parentElement; } qreal BasicElement::scaleFactor() const { return m_scaleFactor; } int BasicElement::scaleLevel() const { return m_scaleLevel; } void BasicElement::setWidth( qreal width ) { m_boundingRect.setWidth( width ); } void BasicElement::setHeight( qreal height ) { m_boundingRect.setHeight( height ); } void BasicElement::setOrigin( QPointF origin ) { m_boundingRect.moveTopLeft( origin ); } void BasicElement::setBaseLine( qreal baseLine ) { m_baseLine = baseLine; } int BasicElement::endPosition() const { return 0; } int BasicElement::positionOfChild(BasicElement* child) const { Q_UNUSED( child ) return -1; } void BasicElement::setParentElement( BasicElement* parent ) { m_parentElement = parent; } void BasicElement::setScaleLevel( int scaleLevel ) { if(scaleLevel == m_scaleLevel) { return; } m_scaleLevel = qMax(scaleLevel, 0); int level = scaleLevel; m_scaleFactor = 1.9; while(level-- > 0) { //raise multiplier to the power of level m_scaleFactor *= 0.71; } } BasicElement* BasicElement::elementBefore ( int position ) const { Q_UNUSED( position ) return 0; } BasicElement* BasicElement::elementAfter ( int position ) const { Q_UNUSED( position ) return 0; } QList< BasicElement* > BasicElement::elementsBetween ( int pos1, int pos2 ) const { Q_UNUSED( pos1 ) Q_UNUSED( pos2 ) QList tmp; return tmp; } bool BasicElement::displayStyle() const { return m_displayStyle; } void BasicElement::setDisplayStyle(bool displayStyle) { m_displayStyle = displayStyle; } bool BasicElement::hasDescendant ( BasicElement* other ) const { if (other==this) { return true; } foreach (BasicElement* tmp, childElements()) { if (tmp->hasDescendant(other)) { return true; } } return false; } BasicElement* BasicElement::emptyDescendant() { BasicElement* tmp; if (isEmpty() && parentElement() && parentElement()->isInferredRow()) { return this; } foreach (BasicElement* child, childElements()) { if ( (tmp=child->emptyDescendant()) ) { return tmp; } } return 0; } //TODO: This should be cached BasicElement* BasicElement::formulaElement() { if (parentElement()==0) { return this; } else { return parentElement()->formulaElement(); } } bool BasicElement::isEmpty() const { return false; } void BasicElement::setScaleFactor ( qreal scaleFactor ) { m_scaleFactor=scaleFactor; } void BasicElement::writeElementTree(int indent, bool wrong) const { QString s; for (int i=0; iparentElement()!=this) { tmp->writeElementTree(indent+1,true); } else { tmp->writeElementTree(indent+1,false); } } } const QString BasicElement::writeElementContent() const { return ""; } bool BasicElement::isInferredRow() const { return false; } void BasicElement::cleanElementTree ( BasicElement* element ) { foreach (BasicElement* tmp,element->childElements()) { cleanElementTree(tmp); } if (element->elementType()==Row && element->parentElement() && element->parentElement()->isInferredRow()) { if ( element->childElements().count()==1) { BasicElement* parent=element->parentElement(); parent->replaceChild(element,element->childElements()[0]); } else if ( element->isEmpty()) { RowElement* parent=static_cast(element->parentElement()); parent->removeChild(element); } } } TableDataElement* BasicElement::parentTableData() { if (elementType()==TableData) { return static_cast(this); } else if (parentElement()) { return parentElement()->parentTableData(); } else { return 0; } } diff --git a/plugins/formulashape/elements/FixedElement.cpp b/plugins/formulashape/elements/FixedElement.cpp index cce1fdc4d11..8ae09bba9c7 100644 --- a/plugins/formulashape/elements/FixedElement.cpp +++ b/plugins/formulashape/elements/FixedElement.cpp @@ -1,200 +1,201 @@ /* This file is part of the KDE project Copyright (C) 2009 Jeremias Epperlein 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 "FixedElement.h" #include "FormulaCursor.h" #include "FormulaDebug.h" #include #include +#include FixedElement::FixedElement( BasicElement* parent ) : BasicElement( parent ) { } FixedElement::~FixedElement() { } BasicElement* FixedElement::elementAfter ( int position ) const { if (position % 2 == 0) { return elementNext(position); } else { return 0; } } BasicElement* FixedElement::elementBefore ( int position ) const { if (position % 2 == 1) { return elementNext(position); } else { return 0; } } BasicElement* FixedElement::elementNext ( int position ) const { return childElements()[position/2]; } QPainterPath FixedElement::selectionRegion(const int pos1, const int pos2) const { QPainterPath temp; Q_UNUSED(pos1); Q_UNUSED(pos2); return temp; } bool FixedElement::moveHorSituation(FormulaCursor& newcursor, FormulaCursor& oldcursor, int pos1, int pos2) { if ((newcursor.position()/2==pos1 && newcursor.direction()==MoveUp) || (newcursor.position()/2==pos2 && newcursor.direction()==MoveDown) || (newcursor.position()==2*pos1 && newcursor.direction()==MoveLeft) || (newcursor.position()==2*pos2+1 && newcursor.direction()==MoveRight) ) { return false; } switch (newcursor.direction()) { case MoveLeft: if (newcursor.position()==2*pos2+1) { newcursor.moveTo(newcursor.currentElement()->childElements()[pos2]); } else { newcursor.moveTo(newcursor.currentElement()->childElements()[pos1]); } break; case MoveRight: if (newcursor.position()==2*pos1) { newcursor.moveTo(newcursor.currentElement()->childElements()[pos1]); } else { newcursor.moveTo(newcursor.currentElement()->childElements()[pos2]); } break; case MoveUp: case MoveDown: return newcursor.moveCloseTo(childElements()[newcursor.direction()==MoveUp ? pos1 : pos2],oldcursor); case NoDirection: break; } return true; } bool FixedElement::moveVertSituation(FormulaCursor& newcursor, FormulaCursor& oldcursor, int pos1, int pos2) { if ((newcursor.position()/2==pos1 && newcursor.direction()==MoveUp) || (newcursor.position()/2==pos2 && newcursor.direction()==MoveDown) || (newcursor.position()%2==0 && newcursor.direction()==MoveLeft) || (newcursor.position()%2==1 && newcursor.direction()==MoveRight) ) { return false; } switch (newcursor.direction()) { case MoveLeft: case MoveRight: if (newcursor.position()/2==pos1) { newcursor.moveTo(newcursor.currentElement()->childElements()[pos1]); } else { newcursor.moveTo(newcursor.currentElement()->childElements()[pos2]); } break; case MoveUp: case MoveDown: return newcursor.moveCloseTo(childElements()[newcursor.direction()==MoveUp ? pos1 : pos2],oldcursor); case NoDirection: break; } return true; } bool FixedElement::moveSingleSituation ( FormulaCursor& newcursor, FormulaCursor& oldcursor, int pos ) { Q_UNUSED( oldcursor ) switch (newcursor.direction()) { case MoveLeft: if (newcursor.position()%2==1) { newcursor.moveTo(newcursor.currentElement()->childElements()[pos]); break; } return false; case MoveRight: if (newcursor.position()%2==0) { newcursor.moveTo(newcursor.currentElement()->childElements()[pos]); break; } case MoveUp: case MoveDown: return false; case NoDirection: break; } return true; } bool FixedElement::acceptCursor ( const FormulaCursor& cursor ) { Q_UNUSED (cursor) return false; } QLineF FixedElement::cursorLine ( int position ) const { QRectF tmp; if (position%2==1) { tmp=elementBefore(position)->absoluteBoundingRect(); return QLineF(tmp.topRight(),tmp.bottomRight()); } else { tmp=elementAfter(position)->absoluteBoundingRect(); return QLineF(tmp.topLeft(),tmp.bottomLeft()); } } int FixedElement::positionOfChild ( BasicElement* child ) const { int tmp=childElements().indexOf(child); if (tmp==-1) { return -1; } else { return 2*tmp; } } bool FixedElement::loadElement ( KoXmlElement& tmp, RowElement** child ) { BasicElement *element; element = ElementFactory::createElement( tmp.tagName(), this ); if( !element->readMathML( tmp ) ) { return false; } if (element->elementType()==Row) { delete (*child); (*child)=static_cast(element); } else { (*child)->insertChild(0,element); } return true; } int FixedElement::endPosition() const { return childElements().length()*2-1; } diff --git a/plugins/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp b/plugins/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp index 9ec48a1bf5a..a1b871dfc61 100644 --- a/plugins/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp +++ b/plugins/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp @@ -1,419 +1,420 @@ /* This file is part of the KDE project Copyright (C) 2008 Fela Winkelmolen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KarbonCalligraphicShape.h" #include #include "KarbonSimplifyPath.h" #include #include #include #include +#include #include #include #undef M_PI const qreal M_PI = 3.1415927; KarbonCalligraphicShape::KarbonCalligraphicShape(qreal caps) : m_lastWasFlip(false), m_caps(caps) { setShapeId(KoPathShapeId); setFillRule(Qt::WindingFill); setBackground(QSharedPointer(new KoColorBackground(QColor(Qt::black)))); setStroke(0); } KarbonCalligraphicShape::~KarbonCalligraphicShape() { } void KarbonCalligraphicShape::appendPoint(const QPointF &point, qreal angle, qreal width) { // convert the point from canvas to shape coordinates QPointF p = point - position(); KarbonCalligraphicPoint *calligraphicPoint = new KarbonCalligraphicPoint(p, angle, width); QVector handles = this->handles(); handles.append(p); setHandles(handles); m_points.append(calligraphicPoint); appendPointToPath(*calligraphicPoint); // make the angle of the first point more in line with the actual // direction if (m_points.count() == 4) { m_points[0]->setAngle(angle); m_points[1]->setAngle(angle); m_points[2]->setAngle(angle); } } void KarbonCalligraphicShape:: appendPointToPath(const KarbonCalligraphicPoint &p) { qreal dx = std::cos(p.angle()) * p.width(); qreal dy = std::sin(p.angle()) * p.width(); // find the outline points QPointF p1 = p.point() - QPointF(dx / 2, dy / 2); QPointF p2 = p.point() + QPointF(dx / 2, dy / 2); if (pointCount() == 0) { moveTo(p1); lineTo(p2); normalize(); return; } // pointCount > 0 bool flip = (pointCount() >= 2) ? flipDetected(p1, p2) : false; // if there was a flip add additional points if (flip) { appendPointsToPathAux(p2, p1); if (pointCount() > 4) smoothLastPoints(); } appendPointsToPathAux(p1, p2); if (pointCount() > 4) { smoothLastPoints(); if (flip) { int index = pointCount() / 2; // find the last two points KoPathPoint *last1 = pointByIndex(KoPathPointIndex(0, index - 1)); KoPathPoint *last2 = pointByIndex(KoPathPointIndex(0, index)); last1->removeControlPoint1(); last1->removeControlPoint2(); last2->removeControlPoint1(); last2->removeControlPoint2(); m_lastWasFlip = true; } if (m_lastWasFlip) { int index = pointCount() / 2; // find the previous two points KoPathPoint *prev1 = pointByIndex(KoPathPointIndex(0, index - 2)); KoPathPoint *prev2 = pointByIndex(KoPathPointIndex(0, index + 1)); prev1->removeControlPoint1(); prev1->removeControlPoint2(); prev2->removeControlPoint1(); prev2->removeControlPoint2(); if (! flip) m_lastWasFlip = false; } } normalize(); // add initial cap if it's the fourth added point // this code is here because this function is called from different places // pointCount() == 8 may causes crashes because it doesn't take possible // flips into account if (m_points.count() >= 4 && &p == m_points[3]) { qDebug() << "Adding caps!!!!!!!!!!!!!!!!" << m_points.count(); addCap(3, 0, 0, true); // duplicate the last point to make the points remain "balanced" // needed to keep all indexes code (else I would need to change // everything in the code...) KoPathPoint *last = pointByIndex(KoPathPointIndex(0, pointCount() - 1)); KoPathPoint *newPoint = new KoPathPoint(this, last->point()); insertPoint(newPoint, KoPathPointIndex(0, pointCount())); close(); } } void KarbonCalligraphicShape::appendPointsToPathAux(const QPointF &p1, const QPointF &p2) { KoPathPoint *pathPoint1 = new KoPathPoint(this, p1); KoPathPoint *pathPoint2 = new KoPathPoint(this, p2); // calculate the index of the insertion position int index = pointCount() / 2; insertPoint(pathPoint2, KoPathPointIndex(0, index)); insertPoint(pathPoint1, KoPathPointIndex(0, index)); } void KarbonCalligraphicShape::smoothLastPoints() { int index = pointCount() / 2; smoothPoint(index - 2); smoothPoint(index + 1); } void KarbonCalligraphicShape::smoothPoint(const int index) { if (pointCount() < index + 2) { qDebug() << "index to high"; return; } else if (index < 1) { qDebug() << "index to low"; return; } const KoPathPointIndex PREV(0, index - 1); const KoPathPointIndex INDEX(0, index); const KoPathPointIndex NEXT(0, index + 1); QPointF prev = pointByIndex(PREV)->point(); QPointF point = pointByIndex(INDEX)->point(); QPointF next = pointByIndex(NEXT)->point(); QPointF vector = next - prev; qreal dist = (QLineF(prev, next)).length(); // normalize the vector (make it's size equal to 1) if (! qFuzzyCompare(dist + 1, 1)) vector /= dist; qreal mult = 0.35; // found by trial and error, might not be perfect... // distance of the control points from the point qreal dist1 = (QLineF(point, prev)).length() * mult; qreal dist2 = (QLineF(point, next)).length() * mult; QPointF vector1 = vector * dist1; QPointF vector2 = vector * dist2; QPointF controlPoint1 = point - vector1; QPointF controlPoint2 = point + vector2; pointByIndex(INDEX)->setControlPoint1(controlPoint1); pointByIndex(INDEX)->setControlPoint2(controlPoint2); } const QRectF KarbonCalligraphicShape::lastPieceBoundingRect() { if (pointCount() < 6) return QRectF(); int index = pointCount() / 2; QPointF p1 = pointByIndex(KoPathPointIndex(0, index - 3))->point(); QPointF p2 = pointByIndex(KoPathPointIndex(0, index - 2))->point(); QPointF p3 = pointByIndex(KoPathPointIndex(0, index - 1))->point(); QPointF p4 = pointByIndex(KoPathPointIndex(0, index))->point(); QPointF p5 = pointByIndex(KoPathPointIndex(0, index + 1))->point(); QPointF p6 = pointByIndex(KoPathPointIndex(0, index + 2))->point(); // TODO: also take the control points into account QPainterPath p; p.moveTo(p1); p.lineTo(p2); p.lineTo(p3); p.lineTo(p4); p.lineTo(p5); p.lineTo(p6); return p.boundingRect().translated(position()); } bool KarbonCalligraphicShape::flipDetected(const QPointF &p1, const QPointF &p2) { // detect the flip caused by the angle changing 180 degrees // thus detect the boundary crossing int index = pointCount() / 2; QPointF last1 = pointByIndex(KoPathPointIndex(0, index - 1))->point(); QPointF last2 = pointByIndex(KoPathPointIndex(0, index))->point(); int sum1 = std::abs(ccw(p1, p2, last1) + ccw(p1, last2, last1)); int sum2 = std::abs(ccw(p2, p1, last2) + ccw(p2, last1, last2)); // if there was a flip return sum1 < 2 && sum2 < 2; } int KarbonCalligraphicShape::ccw(const QPointF &p1, const QPointF &p2, const QPointF &p3) { // calculate two times the area of the triangle fomed by the points given qreal area2 = (p2.x() - p1.x()) * (p3.y() - p1.y()) - (p2.y() - p1.y()) * (p3.x() - p1.x()); if (area2 > 0) { return +1; // the points are given in counterclockwise order } else if (area2 < 0) { return -1; // the points are given in clockwise order } else { return 0; // the points form a degenerate triangle } } void KarbonCalligraphicShape::setSize(const QSizeF &newSize) { // QSizeF oldSize = size(); // TODO: check KoParameterShape::setSize(newSize); } QPointF KarbonCalligraphicShape::normalize() { QPointF offset(KoParameterShape::normalize()); QTransform matrix; matrix.translate(-offset.x(), -offset.y()); for (int i = 0; i < m_points.size(); ++i) { m_points[i]->setPoint(matrix.map(m_points[i]->point())); } return offset; } void KarbonCalligraphicShape::moveHandleAction(int handleId, const QPointF & point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); m_points[handleId]->setPoint(point); } void KarbonCalligraphicShape::updatePath(const QSizeF &size) { Q_UNUSED(size); QPointF pos = position(); // remove all points clear(); setPosition(QPoint(0, 0)); foreach(KarbonCalligraphicPoint *p, m_points) appendPointToPath(*p); simplifyPath(); QVector handles; handles.reserve(m_points.count()); foreach(KarbonCalligraphicPoint *p, m_points) { handles.append(p->point()); } setHandles(handles); setPosition(pos); } void KarbonCalligraphicShape::simplifyPath() { if (m_points.count() < 2) return; close(); // add final cap addCap(m_points.count() - 2, m_points.count() - 1, pointCount() / 2); // TODO: the error should be proportional to the width // and it shouldn't be a magic number karbonSimplifyPath(this, 0.3); } void KarbonCalligraphicShape::addCap(int index1, int index2, int pointIndex, bool inverted) { QPointF p1 = m_points[index1]->point(); QPointF p2 = m_points[index2]->point(); // TODO: review why spikes can appear with a lower limit QPointF delta = p2 - p1; if (delta.manhattanLength() < 1.0) return; QPointF direction = QLineF(QPointF(0, 0), delta).unitVector().p2(); qreal width = m_points[index2]->width(); QPointF p = p2 + direction * m_caps * width; KoPathPoint * newPoint = new KoPathPoint(this, p); qreal angle = m_points[index2]->angle(); if (inverted) angle += M_PI; qreal dx = std::cos(angle) * width; qreal dy = std::sin(angle) * width; newPoint->setControlPoint1(QPointF(p.x() - dx / 2, p.y() - dy / 2)); newPoint->setControlPoint2(QPointF(p.x() + dx / 2, p.y() + dy / 2)); insertPoint(newPoint, KoPathPointIndex(0, pointIndex)); } QString KarbonCalligraphicShape::pathShapeId() const { return KarbonCalligraphicShapeId; } void KarbonCalligraphicShape::simplifyGuidePath() { // do not attempt to simplify if there are too few points if (m_points.count() < 3) return; // cumulative data used to determine if the point can be removed qreal widthChange = 0; qreal directionChange = 0; QList::iterator i = m_points.begin() + 2; while (i != m_points.end() - 1) { QPointF point = (*i)->point(); qreal width = (*i)->width(); qreal prevWidth = (*(i - 1))->width(); qreal widthDiff = width - prevWidth; widthDiff /= qMax(width, prevWidth); qreal directionDiff = 0; if ((i + 1) != m_points.end()) { QPointF prev = (*(i - 1))->point(); QPointF next = (*(i + 1))->point(); directionDiff = QLineF(prev, point).angleTo(QLineF(point, next)); if (directionDiff > 180) directionDiff -= 360; } if (directionChange * directionDiff >= 0 && qAbs(directionChange + directionDiff) < 20 && widthChange * widthDiff >= 0 && qAbs(widthChange + widthDiff) < 0.1) { // deleted point delete *i; i = m_points.erase(i); directionChange += directionDiff; widthChange += widthDiff; } else { // keep point directionChange = 0; widthChange = 0; ++i; } } updatePath(QSizeF()); } diff --git a/plugins/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.h b/plugins/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.h index 56d196c30cf..d7080eb5856 100644 --- a/plugins/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.h +++ b/plugins/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.h @@ -1,110 +1,111 @@ /* This file is part of the KDE project * Copyright (C) 2008 Fela Winkelmolen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KARBONCALLIGRAPHYTOOL_H #define KARBONCALLIGRAPHYTOOL_H #include #include +#include #include class KoPathShape; class KarbonCalligraphicShape; class KarbonCalligraphyTool : public KoToolBase { Q_OBJECT public: explicit KarbonCalligraphyTool(KoCanvasBase *canvas); ~KarbonCalligraphyTool() override; void paint(QPainter &painter, const KoViewConverter &converter) override; void mousePressEvent(KoPointerEvent *event) override ; void mouseMoveEvent(KoPointerEvent *event) override; void mouseReleaseEvent(KoPointerEvent *event) override; QList > createOptionWidgets() override; void activate(ToolActivation toolActivation, const QSet &shapes) override; void deactivate() override; Q_SIGNALS: void pathSelectedChanged(bool selection); private Q_SLOTS: void setUsePath(bool usePath); void setUsePressure(bool usePressure); void setUseAngle(bool useAngle); void setStrokeWidth(double width); void setThinning(double thinning); void setAngle(int angle); // set theangle in degrees void setFixation(double fixation); void setCaps(double caps); void setMass(double mass); // set the mass in user friendly format void setDrag(double drag); void updateSelectedPath(); private: void addPoint(KoPointerEvent *event); // auxiliary function that sets m_angle void setAngle(KoPointerEvent *event); // auxiliary functions to calculate the dynamic parameters // returns the new point and sets speed to the speed QPointF calculateNewPoint(const QPointF &mousePos, QPointF *speed); qreal calculateWidth(qreal pressure); qreal calculateAngle(const QPointF &oldSpeed, const QPointF &newSpeed); QPointF m_lastPoint; KarbonCalligraphicShape *m_shape; // used to determine if the device supports tilt bool m_deviceSupportsTilt; bool m_usePath; // follow selected path bool m_usePressure; // use tablet pressure bool m_useAngle; // use tablet angle qreal m_strokeWidth; qreal m_lastWidth; qreal m_customAngle; // angle set by the user qreal m_angle; // angle to use, may use the device angle, in radians!!! qreal m_fixation; qreal m_thinning; qreal m_caps; qreal m_mass; // in raw format (not user friendly) qreal m_drag; // from 0.0 to 1.0 KoPathShape *m_selectedPath; QPainterPath m_selectedPathOutline; qreal m_followPathPosition; bool m_endOfPath; QPointF m_lastMousePos; bool m_isDrawing; int m_pointCount; // dynamic parameters QPointF m_speed; // used as a vector // last calligraphic shape drawn, if any KarbonCalligraphicShape *m_lastShape; }; #endif // KARBONCALLIGRAPHYTOOL_H diff --git a/plugins/musicshape/MusicStyle.cpp b/plugins/musicshape/MusicStyle.cpp index ed52e11580d..1e174c6938d 100644 --- a/plugins/musicshape/MusicStyle.cpp +++ b/plugins/musicshape/MusicStyle.cpp @@ -1,236 +1,239 @@ /* This file is part of the KDE project * Copyright (C) 2007 Marijn Kruisselbrink * * 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 "MusicStyle.h" + +#include + using namespace MusicCore; MusicStyle::MusicStyle() #ifdef Q_WS_MAC : m_font("Emmentaler 14") #else : m_font("Emmentaler") #endif , m_textAsPath(false) { m_font.setPixelSize(20); m_staffLinePen.setWidthF(0.5); m_staffLinePen.setCapStyle(Qt::RoundCap); m_staffLinePen.setColor(Qt::black); m_stemPen.setWidthF(0.7); m_stemPen.setCapStyle(Qt::FlatCap); m_stemPen.setColor(Qt::black); m_noteDotPen.setWidthF(1.9); m_noteDotPen.setCapStyle(Qt::RoundCap); m_noteDotPen.setColor(Qt::black); } MusicStyle::~MusicStyle() { } QPen MusicStyle::staffLinePen(const QColor& color) { m_staffLinePen.setColor(color); return m_staffLinePen; } QPen MusicStyle::stemPen(const QColor& color) { m_stemPen.setColor(color); return m_stemPen; } QPen MusicStyle::noteDotPen(const QColor& color) { m_noteDotPen.setColor(color); return m_noteDotPen; } qreal MusicStyle::beamLineWidth() { return 3.0; } void MusicStyle::renderText(QPainter& painter, qreal x, qreal y, const QString& text) { QPointF p(x, y); if(m_textAsPath) { QPainterPath textPath; textPath.setFillRule(Qt::OddEvenFill); textPath.addText(p, m_font, text); painter.save(); painter.setBrush(painter.pen().brush().color()); painter.drawPath(textPath); painter.restore(); } else { painter.drawText(p, text); } } bool MusicStyle::textAsPath() const { return m_textAsPath; } void MusicStyle::setTextAsPath(bool drawTextAsPath) { m_textAsPath = drawTextAsPath; } void MusicStyle::renderNoteHead(QPainter& painter, qreal x, qreal y, Duration duration, const QColor& color) { painter.setPen(QPen(color, 0)); painter.setFont(m_font); switch (duration) { case HundredTwentyEighthNote: case SixtyFourthNote: case ThirtySecondNote: case SixteenthNote: case EighthNote: case QuarterNote: renderText(painter, x, y, QString(0xE125)); break; case HalfNote: renderText(painter, x, y, QString(0xE124)); break; case WholeNote: renderText(painter, x, y, QString(0xE123)); break; case BreveNote: renderText(painter, x, y, QString(0xE122)); break; } } void MusicStyle::renderRest(QPainter& painter, qreal x, qreal y, Duration duration, const QColor& color) { painter.setPen(QPen(color, 0)); painter.setFont(m_font); QPointF p(x, y); switch (duration) { case HundredTwentyEighthNote: renderText(painter, x, y, QString(0xE10D)); break; case SixtyFourthNote: renderText(painter, x, y, QString(0xE10C)); break; case ThirtySecondNote: renderText(painter, x, y, QString(0xE10B)); break; case SixteenthNote: renderText(painter, x, y, QString(0xE10A)); break; case EighthNote: renderText(painter, x, y, QString(0xE109)); break; case QuarterNote: renderText(painter, x, y, QString(0xE107)); break; case HalfNote: renderText(painter, x, y, QString(0xE101)); break; case WholeNote: renderText(painter, x, y, QString(0xE100)); break; case BreveNote: renderText(painter, x, y, QString(0xE106)); break; } } void MusicStyle::renderClef(QPainter& painter, qreal x, qreal y, Clef::ClefShape shape, const QColor& color) { painter.setPen(QPen(color, 0)); painter.setFont(m_font); QPointF p(x, y); switch (shape) { case Clef::GClef: renderText(painter, x, y, QString(0xE195)); break; case Clef::FClef: renderText(painter, x, y, QString(0xE193)); break; case Clef::CClef: renderText(painter, x, y, QString(0xE191)); break; } } void MusicStyle::renderAccidental(QPainter& painter, qreal x, qreal y, int accidental, const QColor& color) { painter.setPen(QPen(color, 0)); painter.setFont(m_font); QPointF p(x, y); switch (accidental) { case 0: renderText(painter, x, y, QString(0xE111)); break; case 1: renderText(painter, x, y, QString(0xE10E)); break; case 2: renderText(painter, x, y, QString(0xE116)); break; case -1: renderText(painter, x, y, QString(0xE112)); break; case -2: renderText(painter, x, y, QString(0xE114)); break; } } void MusicStyle::renderTimeSignatureNumber(QPainter& painter, qreal x, qreal y, qreal w, int number, const QColor& color) { painter.setPen(QPen(color, 0)); painter.setFont(m_font); QFontMetricsF m(m_font); QString txt = QString::number(number); renderText(painter, x + (w - m.width(txt))/2, y, txt); } void MusicStyle::renderNoteFlags(QPainter& painter, qreal x, qreal y, Duration duration, bool stemsUp, const QColor& color) { painter.setPen(QPen(color, 0)); painter.setFont(m_font); QPointF p(x + 0.4, y); switch (duration) { case HundredTwentyEighthNote: // no 128 flag in emmentaler, so stack 16th and 32nd on top of each other... renderText(painter, x, y, QString(stemsUp ? 0xE189 : 0xE18F)); renderText(painter, p.x(), p.y() + (stemsUp ? 13 : -13), QString(stemsUp ? 0xE188 : 0xE18E)); break; case SixtyFourthNote: renderText(painter, x, y, QString(stemsUp ? 0xE18A : 0xE190)); break; case ThirtySecondNote: renderText(painter, x, y, QString(stemsUp ? 0xE189 : 0xE18F)); break; case SixteenthNote: renderText(painter, x, y, QString(stemsUp ? 0xE188 : 0xE18E)); break; case EighthNote: renderText(painter, x, y, QString(stemsUp ? 0xE187 : 0xE18B)); break; default: // no flags break; } } diff --git a/plugins/musicshape/Renderer.cpp b/plugins/musicshape/Renderer.cpp index 93989288645..b3c062d5a9d 100644 --- a/plugins/musicshape/Renderer.cpp +++ b/plugins/musicshape/Renderer.cpp @@ -1,586 +1,587 @@ /* This file is part of the KDE project * Copyright (C) 2007 Marijn Kruisselbrink * * 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 "Renderer.h" #include "MusicStyle.h" #include "core/Sheet.h" #include "core/Part.h" #include "core/Voice.h" #include "core/Staff.h" #include "core/VoiceBar.h" #include "core/Chord.h" #include "core/Note.h" #include "core/Clef.h" #include "core/Bar.h" #include "core/KeySignature.h" #include "core/TimeSignature.h" #include "core/StaffSystem.h" #include +#include #include #include using namespace MusicCore; MusicRenderer::MusicRenderer(MusicStyle* style) : m_style(style), m_debug(false) { } void MusicRenderer::renderSheet(QPainter& painter, Sheet* sheet, int firstSystem, int lastSystem) { int firstBar = sheet->staffSystem(firstSystem)->firstBar(); int lastBar = INT_MAX; if (lastSystem < sheet->staffSystemCount()-1) { lastBar = sheet->staffSystem(lastSystem+1)->firstBar()-1; } for (int i = 0; i < sheet->partCount(); i++) { renderPart(painter, sheet->part(i), firstBar, lastBar); } for (int i = firstSystem; i <= lastSystem && i < sheet->staffSystemCount(); i++) { StaffSystem* ss = sheet->staffSystem(i); if (ss->indent() == 0) continue; int b = ss->firstBar(); Bar* bar = sheet->bar(b); qreal by = bar->position().y(); qreal ind = ss->indent(); for (int p = 0; p < sheet->partCount(); p++) { Part* part = sheet->part(p); for (int s = 0; s < part->staffCount(); s++) { Staff* staff = part->staff(s); qreal y = staff->top(); qreal dy = staff->lineSpacing(); painter.setPen(m_style->staffLinePen()); for (int l = 0; l < staff->lineCount(); l++) { painter.drawLine(QPointF(0, by + y + l * dy), QPointF(ind, by + y + l * dy)); } Clef* clef = ss->clef(staff); RenderState foo; qreal x = 15; if (clef) { renderClef(painter, clef, QPointF(x, by), foo, Qt::black, true); x += clef->width() + 15; } KeySignature* ks = staff->lastKeySignatureChange(b); if (ks) { renderKeySignature(painter, ks, QPointF(x, by), foo, Qt::black, true); } } } } } void MusicRenderer::renderPart(QPainter& painter, Part* part, int firstBar, int lastBar, const QColor& color) { if (lastBar < firstBar) return; for (int i = 0; i < part->staffCount(); i++) { renderStaff(painter, part->staff(i), firstBar, lastBar, color); } qreal firstStaff = part->staff(0)->top(); int c = part->staffCount()-1; qreal lastStaff = part->staff(c)->bottom(); for (int b = firstBar; b <= lastBar && b < part->sheet()->barCount(); b++) { Bar* bar = part->sheet()->bar(b); QPointF p = bar->position(); painter.drawLine(QPointF(p.x() + bar->size(), p.y() + firstStaff), QPointF(p.x() + bar->size(), p.y() + lastStaff)); if (m_debug) { painter.setPen(QPen(Qt::green, 0)); painter.drawLine(QPointF(p.x(), p.y() + firstStaff - 3), QPointF(p.x(), p.y() + lastStaff + 3)); painter.drawLine(QPointF(p.x() - bar->prefix(), p.y() + firstStaff - 3), QPointF(p.x() - bar->prefix(), p.y() + lastStaff + 3)); } // check if the bar contains any elements, if not render a rest bool hasContents = false; for (int v = 0; v < part->voiceCount(); v++) { if (part->voice(v)->bar(bar)->elementCount() > 0) { hasContents = true; break; } } if (!hasContents) { QPointF pos = bar->position(); qreal w = bar->size(); for (int sid = 0; sid < part->staffCount(); sid++) { Staff* s = part->staff(sid); renderRest(painter, WholeNote, pos + QPointF(w/2, s->top() + s->lineSpacing()), color); } } } for (int i = 0; i < part->voiceCount(); i++) { renderVoice(painter, part->voice(i), firstBar, lastBar, color); } } void MusicRenderer::renderStaff(QPainter& painter, Staff *staff, int firstBar, int lastBar, const QColor& color) { qreal dy = staff->lineSpacing(); qreal y = staff->top(); for (int b = firstBar; b <= lastBar && b < staff->part()->sheet()->barCount(); b++) { Bar* bar = staff->part()->sheet()->bar(b); QPointF p = bar->position(); QPointF prep = bar->prefixPosition() + QPointF(bar->prefix(), 0); painter.setPen(m_style->staffLinePen(color)); for (int i = 0; i < staff->lineCount(); i++) { painter.drawLine(QPointF(p.x(), p.y() + y + i * dy), QPointF(p.x() + bar->size(), p.y() + y + i * dy)); } if (bar->prefix() > 0) { QPointF q = bar->prefixPosition(); for (int i = 0; i < staff->lineCount(); i++) { painter.drawLine(QPointF(q.x(), q.y() + y + i * dy), QPointF(q.x() + bar->prefix(), q.y() + y + i * dy)); } } RenderState state; for (int e = 0; e < bar->staffElementCount(staff); e++) { StaffElement* se = bar->staffElement(staff, e); if (se->startTime() == 0) { renderStaffElement(painter, bar->staffElement(staff, e), prep, state, color); } else { renderStaffElement(painter, bar->staffElement(staff, e), p, state, color); } } } } void MusicRenderer::renderVoice(QPainter& painter, Voice *voice, int firstBar, int lastBar, const QColor& color) { RenderState state; state.clef = 0; for (int b = firstBar; b <= lastBar && b < voice->part()->sheet()->barCount(); b++) { Bar* bar = voice->part()->sheet()->bar(b); QPointF p = bar->position(); VoiceBar* vb = voice->bar(bar); for (int e = 0; e < vb->elementCount(); e++) { if (vb->element(e)->staff()) { state.clef = vb->element(e)->staff()->lastClefChange(b, 0); } renderElement(painter, vb->element(e), voice, p, state, color); } } } void MusicRenderer::renderElement(QPainter& painter, VoiceElement* me, Voice* voice, const QPointF& pos, RenderState& state, const QColor& color) { Q_UNUSED( state ); // unused for now, but will probably be used again in the future qreal top = 0; if (me->staff()) top += me->staff()->top(); if (m_debug) { painter.setPen(QPen(Qt::blue, 0)); painter.drawLine(pos + QPointF(me->x(), top + me->y() - 4), pos + QPointF(me->x(), top + me->y() + me->height() + 4)); painter.drawLine(pos + QPointF(me->x() + me->width(), top + me->y() - 4), pos + QPointF(me->x() + me->width(), top + me->y() + me->height() + 4)); painter.drawLine(pos + QPointF(me->x() - 4, top + me->y()), pos + QPointF(me->x() + me->width() + 4, top + me->y())); painter.drawLine(pos + QPointF(me->x() - 4, top + me->y() + me->height()), pos + QPointF(me->x() + me->width() + 4, top + me->y() + me->height())); painter.setPen(QPen(Qt::red, 0)); painter.drawLine(pos + QPointF(me->x() + me->beatline(), top + me->y() - 10), pos + QPointF(me->x() + me->beatline(), top + me->y() + me->height() + 10)); } // TODO: make this less hacky Chord *c = dynamic_cast(me); if (c) renderChord(painter, c, voice, pos, color); } void MusicRenderer::renderStaffElement(QPainter& painter, MusicCore::StaffElement* se, const QPointF& pos, RenderState& state, const QColor& color) { qreal top = 0; top += se->staff()->top(); if (m_debug) { painter.setPen(QPen(Qt::blue, 0)); painter.drawLine(pos + QPointF(se->x(), top + se->y() - 20), pos + QPointF(se->x(), top + se->y() + 20)); painter.drawLine(pos + QPointF(se->x() + se->width(), top + se->y() - 20), pos + QPointF(se->x() + se->width(), top + se->y() + 20)); painter.drawLine(pos + QPointF(se->x() - 10, top + se->y()), pos + QPointF(se->x() + se->width() + 10, top + se->y())); painter.drawLine(pos + QPointF(se->x() - 10, top + se->y() + se->height()), pos + QPointF(se->x() + se->width() + 10, top + se->y() + se->height())); } Clef *cl = dynamic_cast(se); if (cl) renderClef(painter, cl, pos, state, color); KeySignature *ks = dynamic_cast(se); if (ks) renderKeySignature(painter, ks, pos, state, color); TimeSignature* ts = dynamic_cast(se); if (ts) renderTimeSignature(painter, ts, pos, color); } void MusicRenderer::renderClef(QPainter& painter, Clef *c, const QPointF& pos, RenderState& state, const QColor& color, bool ignoreOwnPos) { Q_UNUSED(color); state.clef = c; Staff* s = c->staff(); m_style->renderClef(painter, pos.x() + (ignoreOwnPos ? 0 : c->x()), pos.y() + s->top() + (s->lineCount() - c->line()) * s->lineSpacing(), c->shape()); } void MusicRenderer::renderKeySignature(QPainter& painter, KeySignature* ks, const QPointF& pos, RenderState& state, const QColor& color, bool ignoreOwnPos) { Q_UNUSED(color); Staff * s = ks->staff(); qreal curx = pos.x() + (ignoreOwnPos ? 0 : ks->x()); // draw naturals for sharps int idx = 3; for (int i = 0; i < 7; i++) { if (ks->cancel(idx) > 0) { int line = 10; if (state.clef) line = state.clef->pitchToLine(idx); while (line < 0) line += 7; while (line >= 6) line -= 7; m_style->renderAccidental( painter, curx, pos.y() + s->top() + line * s->lineSpacing() / 2, 0 ); curx += 6; } idx = (idx + 4) % 7; } // draw naturals for flats idx = 6; for (int i = 0; i < 7; i++) { if (ks->cancel(idx) < 0) { int line = 10; if (state.clef) line = state.clef->pitchToLine(idx); while (line < 0) line += 7; while (line >= 6) line -= 7; m_style->renderAccidental( painter, curx, pos.y() + s->top() + line * s->lineSpacing() / 2, 0 ); curx += 6; } idx = (idx + 3) % 7; } // draw sharps idx = 3; for (int i = 0; i < 7; i++) { if (ks->accidentals(idx) > 0) { int line = 10; if (state.clef) line = state.clef->pitchToLine(idx); while (line < 0) line += 7; while (line >= 6) line -= 7; m_style->renderAccidental( painter, curx, pos.y() + s->top() + line * s->lineSpacing() / 2, 1 ); curx += 6; } idx = (idx + 4) % 7; } // draw flats idx = 6; for (int i = 0; i < 7; i++) { if (ks->accidentals(idx) < 0) { int line = 10; if (state.clef) line = state.clef->pitchToLine(idx); while (line < 0) line += 7; while (line >= 6) line -= 7; m_style->renderAccidental( painter, curx, pos.y() + s->top() + line * s->lineSpacing() / 2, -1 ); curx += 6; } idx = (idx + 3) % 7; } } void MusicRenderer::renderTimeSignature(QPainter& painter, TimeSignature* ts, const QPointF& pos, const QColor& color) { Q_UNUSED(color); Staff* s = ts->staff(); qreal hh = 0.5 * (s->lineCount() - 1) * s->lineSpacing(); m_style->renderTimeSignatureNumber( painter, pos.x() + ts->x(), pos.y() + s->top() + hh, ts->width(), ts->beats()); m_style->renderTimeSignatureNumber( painter, pos.x() + ts->x(), pos.y() + s->top() + 2*hh, ts->width(), ts->beat()); } void MusicRenderer::renderRest(QPainter& painter, Duration duration, const QPointF& pos, const QColor& color) { m_style->renderRest(painter, pos.x(), pos.y(), duration, color); } void MusicRenderer::renderChord(QPainter& painter, Chord* chord, Voice* voice, const QPointF& ref, const QColor& color) { qreal x = chord->x(); if (chord->noteCount() == 0) { // a rest Staff *s = chord->staff(); renderRest(painter, chord->duration(), ref + QPointF(x, s->top() + (2 - (chord->duration() == WholeNote)) * s->lineSpacing()), color); return; } int topLine = 0, bottomLine = 0; VoiceBar* vb = chord->voiceBar(); Bar* bar = vb->bar(); Sheet* sheet = voice->part()->sheet(); int barIdx = bar->sheet()->indexOfBar(bar); qreal topy = 1e9, bottomy = -1e9; Staff* topStaff = 0, *bottomStaff = 0; qreal mainNoteX = (chord->stemDirection() == StemUp ? chord->stemX() - 6 : chord->stemX()); qreal alternateNoteX = mainNoteX + (chord->stemDirection() == StemUp ? 6 : -6); bool prevAlternate = false; qreal maxNoteX = 0; QMultiMap dots; Chord* nextChord = 0; for (int i = 0; i < chord->noteCount(); i++) { Note *n = chord->note(i); Staff * s = n->staff(); Clef* clef = s->lastClefChange(barIdx); int line = 10; if (clef) line = clef->pitchToLine(n->pitch()); qreal noteX = mainNoteX; if (i > 0) { int prevPitch = chord->note(i-1)->pitch(); if (abs(prevPitch - n->pitch()) <= 1 && !prevAlternate) { noteX = alternateNoteX; } } if (i < chord->noteCount()-1 && chord->stemDirection() == StemDown) { int pitch = n->pitch(); int nPitch = chord->note(i+1)->pitch(); if (abs(pitch - nPitch) <= 1 && !prevAlternate) { noteX = alternateNoteX; } } prevAlternate = noteX != mainNoteX; if (noteX > maxNoteX) maxNoteX = noteX; if (line > 9) { // lines under the bar painter.setPen(m_style->staffLinePen(color)); for (int i = 10; i <= line; i+= 2) { qreal y = s->top() + i * s->lineSpacing() / 2; painter.drawLine(ref + QPointF(noteX - 4, y), ref + QPointF(noteX + 10, y)); } } else if (line < -1) { // lines above the bar painter.setPen(m_style->staffLinePen(color)); for (int i = -2; i >= line; i-= 2) { qreal y = s->top() + i * s->lineSpacing() / 2; painter.drawLine(ref + QPointF(noteX - 4, y), ref + QPointF(noteX + 10, y)); } } qreal ypos = s->top() + line * s->lineSpacing() / 2; if (ypos < topy) { topy = ypos; topLine = line; topStaff = s; } if (ypos > bottomy) { bottomy = ypos; bottomLine = line; bottomStaff = s; } m_style->renderNoteHead( painter, ref.x() + noteX, ref.y() + s->top() + line * s->lineSpacing() / 2, chord->duration(), color ); // render accidentals if (n->drawAccidentals()) { m_style->renderAccidental( painter, ref.x() + x, ref.y() + /*chord->y() +*/ s->top() + line * s->lineSpacing() / 2, n->accidentals(), color ); } dots.insert(s, line); if (n->isStartTie()) { // render tie for this note... if (!nextChord) { // figure out what the next chord in this voice is bool afterCurrent = false; for (int e = 0; e < vb->elementCount(); e++) { if (afterCurrent) { nextChord = dynamic_cast(vb->element(e)); if (nextChord) break; } else { if (vb->element(e) == chord) { afterCurrent = true; } } } if (!nextChord) { // check the next bar int nextBar = sheet->indexOfBar(bar)+1; if (nextBar < sheet->barCount()) { VoiceBar* nextVB = voice->bar(nextBar); for (int e = 0; e < nextVB->elementCount(); e++) { nextChord = dynamic_cast(nextVB->element(e)); if (nextChord) break; } } } } // okay, now nextChord is the chord to which the tie should go if (nextChord) { QPointF startPos = bar->position() + QPointF(1 + chord->x() + chord->width(), ypos); QPointF endPos = nextChord->voiceBar()->bar()->position() + QPointF(nextChord->x() - 1, ypos); if (bar->position().y() < nextChord->voiceBar()->bar()->position().y() - 1e-6) { endPos = bar->position() + QPointF(bar->size(), 0); } endPos.setY(startPos.y()); QPointF c1a = startPos + QPointF(2, 4); QPointF c2a = endPos + QPointF(-2, 4); QPointF c1b = startPos + QPointF(2, 5); QPointF c2b = endPos + QPointF(-2, 5); QPainterPath p; p.moveTo(startPos); p.cubicTo(c1a, c2a, endPos); p.cubicTo(c2b, c1b, startPos); painter.setPen(Qt::NoPen);//m_style->slurPen(color)); painter.setBrush(QBrush(color)); painter.drawPath(p); } } } // calculate correct positioning of dots // render dots of notes painter.setPen(m_style->noteDotPen(color)); foreach (Staff* s, dots.keys()) { QList lines = dots.values(s); std::sort(lines.begin(), lines.end()); int lastLine = INT_MIN; bool moveGroupDown = true; for (int i = 0; i < lines.size(); i++) { int line = lines[i]; if (line % 2 == 0) { line--; } if (line == lastLine) { if (moveGroupDown) { lines[i-1] += 2; for (int j = i-2; j >= 0; j--) { if (lines[j] == lines[j+1]) { lines[j] += 2; } else { break; } } } else { line -= 2; } moveGroupDown = !moveGroupDown; } lines[i] = line; lastLine = line; } foreach (int line, lines) { qreal dotX = maxNoteX + 11; for (int i = 0; i < chord->dots(); i++) { painter.drawPoint(ref + QPointF(dotX, s->top() + line * s->lineSpacing() / 2)); dotX += 3; } } } qreal stemLen = chord->stemLength() * 2; if (stemLen != 0.0 && stemLen != -0.0) { qreal stemX = chord->stemX(); bool stemsUp = chord->stemDirection() == StemUp; painter.setPen(m_style->stemPen(color)); if (stemsUp) { painter.drawLine(ref + QPointF(stemX, chord->stemEndY()), ref + QPointF(stemX, bottomStaff->top() + bottomLine * bottomStaff->lineSpacing() / 2)); if (chord->beamType(0) == BeamFlag) { m_style->renderNoteFlags( painter, ref.x() + stemX, ref.y() + chord->stemEndY(), chord->duration(), stemsUp, color ); } } else { painter.drawLine(ref + QPointF(stemX, topStaff->top() + topLine * topStaff->lineSpacing() / 2), ref + QPointF(stemX, chord->stemEndY())); if (chord->beamType(0) == BeamFlag) { m_style->renderNoteFlags( painter, ref.x() + stemX, ref.y() + chord->stemEndY(), chord->duration(), stemsUp, color ); } } painter.setPen(QPen(Qt::NoPen)); painter.setBrush(QBrush(color)); for (int i = 0; i < chord->beamCount(); i++) { if (chord->beamType(i) == BeamStart) { const Chord* endChord = chord->beamEnd(i); QPointF beamStart(chord->stemX(), chord->stemEndY()); QPointF beamEnd(endChord->stemX(), endChord->stemEndY()); if (stemsUp) { beamStart += QPointF(0, topStaff->lineSpacing() * i); beamEnd += QPointF(0, topStaff->lineSpacing() * i); } else { beamStart -= QPointF(0, bottomStaff->lineSpacing() * i); beamEnd -= QPointF(0, bottomStaff->lineSpacing() * i); } QPointF dir(0, (stemsUp ? 1 : -1) * m_style->beamLineWidth()); QPointF p[4]; p[0] = ref + beamStart; p[1] = ref + beamEnd; p[2] = p[1] + dir; p[3] = p[0] + dir; painter.drawConvexPolygon(p, 4); } else if (chord->beamType(i) == BeamForwardHook || chord->beamType(i) == BeamBackwardHook) { QPointF beamStart(chord->stemX(), chord->stemEndY()); qreal dir = 6; if (chord->beamType(i) == BeamBackwardHook) dir = -dir; if (stemsUp) { beamStart += QPointF(0, topStaff->lineSpacing() * i); } else { beamStart -= QPointF(0, bottomStaff->lineSpacing() * i); } QPointF beamEnd = beamStart + QPointF(dir, dir * chord->beamDirection()); QPointF bdir(0, (stemsUp ? 1 : -1) * m_style->beamLineWidth()); QPointF p[4]; p[0] = ref + beamStart; p[1] = ref + beamEnd; p[2] = p[1] + bdir; p[3] = p[0] + bdir; painter.drawConvexPolygon(p, 4); } } } } void MusicRenderer::renderNote(QPainter& painter, Duration duration, const QPointF& pos, qreal stemLength, const QColor& color) { m_style->renderNoteHead(painter, pos.x(), pos.y(), duration, color); if (duration <= HalfNote) { painter.setPen(m_style->stemPen(color)); painter.drawLine(pos + QPointF(6, -stemLength), pos + QPointF(6, 0)); } if (duration <= EighthNote) { m_style->renderNoteFlags(painter, pos.x()+6, pos.y() - stemLength, duration, true, color); } } void MusicRenderer::renderAccidental(QPainter& painter, int accidentals, const QPointF& pos, const QColor& color) { m_style->renderAccidental( painter, pos.x(), pos.y(), accidentals, color ); } diff --git a/plugins/pathshapes/enhancedpath/EnhancedPathShape.cpp b/plugins/pathshapes/enhancedpath/EnhancedPathShape.cpp index 23eca89f4d1..ff365997ad8 100644 --- a/plugins/pathshapes/enhancedpath/EnhancedPathShape.cpp +++ b/plugins/pathshapes/enhancedpath/EnhancedPathShape.cpp @@ -1,701 +1,703 @@ /* This file is part of the KDE project * Copyright (C) 2007,2010,2011 Jan Hambrecht * Copyright (C) 2009-2010 Thomas Zander * Copyright (C) 2010 Carlos Licea * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Contact: Suresh Chande suresh.chande@nokia.com * Copyright (C) 2009-2010 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "EnhancedPathShape.h" #include "EnhancedPathCommand.h" #include "EnhancedPathParameter.h" #include "EnhancedPathHandle.h" #include "EnhancedPathFormula.h" #include #include #include #include #include #include #include +#include + EnhancedPathShape::EnhancedPathShape(const QRect &viewBox) : m_viewBox(viewBox) , m_viewBoxOffset(0.0, 0.0) , m_mirrorVertically(false) , m_mirrorHorizontally(false) , m_pathStretchPointX(-1) , m_pathStretchPointY(-1) , m_cacheResults(false) { } EnhancedPathShape::~EnhancedPathShape() { reset(); } void EnhancedPathShape::reset() { qDeleteAll(m_commands); m_commands.clear(); qDeleteAll(m_enhancedHandles); m_enhancedHandles.clear(); setHandles(QVector()); qDeleteAll(m_formulae); m_formulae.clear(); qDeleteAll(m_parameters); m_parameters.clear(); m_modifiers.clear(); m_viewMatrix.reset(); m_viewBoxOffset = QPointF(); clear(); m_textArea.clear(); } void EnhancedPathShape::moveHandleAction(int handleId, const QPointF & point, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); EnhancedPathHandle *handle = m_enhancedHandles[ handleId ]; if (handle) { handle->changePosition(shapeToViewbox(point)); } } void EnhancedPathShape::updatePath(const QSizeF & size) { if (isParametricShape()) { clear(); enableResultCache(true); foreach (EnhancedPathCommand *cmd, m_commands) cmd->execute(); enableResultCache(false); qreal stretchPointsScale = 1; bool isStretched = useStretchPoints(size, stretchPointsScale); m_viewBound = outline().boundingRect(); m_mirrorMatrix.reset(); m_mirrorMatrix.translate(m_viewBound.center().x(), m_viewBound.center().y()); m_mirrorMatrix.scale(m_mirrorHorizontally ? -1 : 1, m_mirrorVertically ? -1 : 1); m_mirrorMatrix.translate(-m_viewBound.center().x(), -m_viewBound.center().y()); QTransform matrix(1.0, 0.0, 0.0, 1.0, m_viewBoxOffset.x(), m_viewBoxOffset.y()); // if stretch points are set than stretch the path manually if (isStretched) { //if the path was stretched manually the stretch matrix is not more valid //and it has to be recalculated so that stretching in x and y direction is the same matrix.scale(stretchPointsScale, stretchPointsScale); matrix = m_mirrorMatrix * matrix; } else { matrix = m_mirrorMatrix * m_viewMatrix * matrix; } foreach (KoSubpath *subpath, m_subpaths) { foreach (KoPathPoint *point, *subpath) { point->map(matrix); } } const int handleCount = m_enhancedHandles.count(); QVector handles; handles.reserve(handleCount); for (int i = 0; i < handleCount; ++i) handles.append(matrix.map(m_enhancedHandles[i]->position())); setHandles(handles); normalize(); } } void EnhancedPathShape::setSize(const QSizeF &newSize) { // handle offset KoParameterShape::setSize(newSize); // calculate scaling factors from viewbox size to shape size qreal xScale = m_viewBound.width() == 0 ? 1 : newSize.width()/m_viewBound.width(); qreal yScale = m_viewBound.height() == 0 ? 1 : newSize.height()/m_viewBound.height(); // create view matrix, take mirroring into account m_viewMatrix.reset(); m_viewMatrix.scale(xScale, yScale); updatePath(newSize); } QPointF EnhancedPathShape::normalize() { QPointF offset = KoParameterShape::normalize(); m_viewBoxOffset -= offset; return offset; } QPointF EnhancedPathShape::shapeToViewbox(const QPointF &point) const { return (m_mirrorMatrix * m_viewMatrix).inverted().map( point-m_viewBoxOffset ); } void EnhancedPathShape::evaluateHandles() { const int handleCount = m_enhancedHandles.count(); QVector handles; handles.reserve(handleCount); for (int i = 0; i < handleCount; ++i) handles.append(m_enhancedHandles[i]->position()); setHandles(handles); } QRect EnhancedPathShape::viewBox() const { return m_viewBox; } qreal EnhancedPathShape::evaluateReference(const QString &reference) { if (reference.isEmpty()) return 0.0; const char c = reference[0].toLatin1(); qreal res = 0.0; switch(c) { // referenced modifier case '$': { bool success = false; int modifierIndex = reference.mid(1).toInt(&success); res = m_modifiers.value(modifierIndex); break; } // referenced formula case '?': { QString fname = reference.mid(1); if (m_cacheResults && m_resultCache.contains(fname)) { res = m_resultCache.value(fname); } else { FormulaStore::const_iterator formulaIt = m_formulae.constFind(fname); if (formulaIt != m_formulae.constEnd()) { EnhancedPathFormula * formula = formulaIt.value(); if (formula) { res = formula->evaluate(); if (m_cacheResults) m_resultCache.insert(fname, res); } } } break; } // maybe an identifier ? default: EnhancedPathNamedParameter p(reference, this); res = p.evaluate(); break; } return res; } qreal EnhancedPathShape::evaluateConstantOrReference(const QString &val) { bool ok = true; qreal res = val.toDouble(&ok); if (ok) return res; return evaluateReference(val); } void EnhancedPathShape::modifyReference(const QString &reference, qreal value) { if (reference.isEmpty()) return; const char c = reference[0].toLatin1(); if (c == '$') { bool success = false; int modifierIndex = reference.mid(1).toInt(&success); if (modifierIndex >= 0 && modifierIndex < m_modifiers.count()) m_modifiers[modifierIndex] = value; } } EnhancedPathParameter * EnhancedPathShape::parameter(const QString & text) { Q_ASSERT(! text.isEmpty()); ParameterStore::const_iterator parameterIt = m_parameters.constFind(text); if (parameterIt != m_parameters.constEnd()) { return parameterIt.value(); } else { EnhancedPathParameter *parameter = 0; const char c = text[0].toLatin1(); if (c == '$' || c == '?') { parameter = new EnhancedPathReferenceParameter(text, this); } else { bool success = false; qreal constant = text.toDouble(&success); if (success) { parameter = new EnhancedPathConstantParameter(constant, this); } else { Identifier identifier = EnhancedPathNamedParameter::identifierFromString(text); if (identifier != IdentifierUnknown) parameter = new EnhancedPathNamedParameter(identifier, this); } } if (parameter) m_parameters[text] = parameter; return parameter; } } void EnhancedPathShape::addFormula(const QString &name, const QString &formula) { if (name.isEmpty() || formula.isEmpty()) return; m_formulae[name] = new EnhancedPathFormula(formula, this); } void EnhancedPathShape::addHandle(const QMap &handle) { if (handle.isEmpty()) return; if (! handle.contains("draw:handle-position")) return; QVariant position = handle.value("draw:handle-position"); QStringList tokens = position.toString().simplified().split(' '); if (tokens.count() < 2) return; EnhancedPathHandle *newHandle = new EnhancedPathHandle(this); newHandle->setPosition(parameter(tokens[0]), parameter(tokens[1])); // check if we have a polar handle if (handle.contains("draw:handle-polar")) { QVariant polar = handle.value("draw:handle-polar"); QStringList tokens = polar.toString().simplified().split(' '); if (tokens.count() == 2) { newHandle->setPolarCenter(parameter(tokens[0]), parameter(tokens[1])); QVariant minRadius = handle.value("draw:handle-radius-range-minimum"); QVariant maxRadius = handle.value("draw:handle-radius-range-maximum"); if (minRadius.isValid() && maxRadius.isValid()) newHandle->setRadiusRange(parameter(minRadius.toString()), parameter(maxRadius.toString())); } } else { QVariant minX = handle.value("draw:handle-range-x-minimum"); QVariant maxX = handle.value("draw:handle-range-x-maximum"); if (minX.isValid() && maxX.isValid()) newHandle->setRangeX(parameter(minX.toString()), parameter(maxX.toString())); QVariant minY = handle.value("draw:handle-range-y-minimum"); QVariant maxY = handle.value("draw:handle-range-y-maximum"); if (minY.isValid() && maxY.isValid()) newHandle->setRangeY(parameter(minY.toString()), parameter(maxY.toString())); } m_enhancedHandles.append(newHandle); evaluateHandles(); } void EnhancedPathShape::addModifiers(const QString &modifiers) { if (modifiers.isEmpty()) return; QStringList tokens = modifiers.simplified().split(' '); int tokenCount = tokens.count(); for (int i = 0; i < tokenCount; ++i) m_modifiers.append(tokens[i].toDouble()); } void EnhancedPathShape::addCommand(const QString &command) { addCommand(command, true); } void EnhancedPathShape::addCommand(const QString &command, bool triggerUpdate) { QString commandStr = command.simplified(); if (commandStr.isEmpty()) return; // the first character is the command EnhancedPathCommand *cmd = new EnhancedPathCommand(commandStr[0], this); // strip command char commandStr = commandStr.mid(1).simplified(); // now parse the command parameters if (!commandStr.isEmpty()) { QStringList tokens = commandStr.split(' '); for (int i = 0; i < tokens.count(); ++i) { cmd->addParameter(parameter(tokens[i])); } } m_commands.append(cmd); if (triggerUpdate) updatePath(size()); } bool EnhancedPathShape::useStretchPoints(const QSizeF &size, qreal &scale) { bool retval = false; if (m_pathStretchPointX != -1 && m_pathStretchPointY != -1) { qreal scaleX = size.width(); qreal scaleY = size.height(); if (m_viewBox.width() / m_viewBox.height() < scaleX / scaleY) { qreal deltaX = (scaleX * m_viewBox.height()) / scaleY - m_viewBox.width(); foreach (KoSubpath *subpath, m_subpaths) { foreach (KoPathPoint *currPoint, *subpath) { if (currPoint->point().x() >= m_pathStretchPointX && currPoint->controlPoint1().x() >= m_pathStretchPointX && currPoint->controlPoint2().x() >= m_pathStretchPointX) { currPoint->setPoint(QPointF(currPoint->point().x() + deltaX, currPoint->point().y())); currPoint->setControlPoint1(QPointF(currPoint->controlPoint1().x() + deltaX, currPoint->controlPoint1().y())); currPoint->setControlPoint2(QPointF(currPoint->controlPoint2().x() + deltaX, currPoint->controlPoint2().y())); retval = true; } } } scale = scaleY / m_viewBox.height(); } else if (m_viewBox.width() / m_viewBox.height() > scaleX / scaleY) { qreal deltaY = (m_viewBox.width() * scaleY) / scaleX - m_viewBox.height(); foreach (KoSubpath *subpath, m_subpaths) { foreach (KoPathPoint *currPoint, *subpath) { if (currPoint->point().y() >= m_pathStretchPointY && currPoint->controlPoint1().y() >= m_pathStretchPointY && currPoint->controlPoint2().y() >= m_pathStretchPointY) { currPoint->setPoint(QPointF(currPoint->point().x(), currPoint->point().y() + deltaY)); currPoint->setControlPoint1(QPointF(currPoint->controlPoint1().x(), currPoint->controlPoint1().y() + deltaY)); currPoint->setControlPoint2(QPointF(currPoint->controlPoint2().x(), currPoint->controlPoint2().y() + deltaY)); retval = true; } } } scale = scaleX / m_viewBox.width(); } } return retval; } void EnhancedPathShape::saveOdf(KoShapeSavingContext &context) const { if (isParametricShape()) { context.xmlWriter().startElement("draw:custom-shape"); const QSizeF currentSize = outline().boundingRect().size(); // save the right position so that when loading we fit the viewbox // to the right position without getting any wrong scaling // -> calculate the right position from the current 0 position / viewbound ratio // this is e.g. the case when there is a callout that goes into negative viewbound coordinates QPointF topLeft = m_viewBound.topLeft(); QPointF diff; if (qAbs(topLeft.x()) > 1E-5) { diff.setX(topLeft.x()*currentSize.width()/m_viewBound.width()); } if (qAbs(topLeft.y()) > 1E-5) { diff.setY(topLeft.y()*currentSize.height()/m_viewBound.height()); } if (diff.isNull()) { saveOdfAttributes(context, OdfAllAttributes&~OdfSize); } else { // We get here if the path goes outside the viewbox. // Afaics, this is not allowed, see: http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#attribute-svg_viewBox. // However, OO/LO uses this for callouts in order to get a shape size that covers the 'bubble' part only, // excluding the 'arrow' part of the callout. // OO/LO callouts are marked by eg: draw:type="round-rectangular-callout" // so this can be used to distinguish callouts from proper path shapes. // Also there was a bug in stencil database.odg that giave the same problem. // Handling this should not be neccessary anymore as callouts are treated in a separate class, // and the database.odg has been fixe, but we keep it to be able to read old calligra documents. QTransform offset(context.shapeOffset(this)); QTransform newOffset(offset); newOffset.translate(-diff.x(), -diff.y()); context.addShapeOffset(this, newOffset); saveOdfAttributes(context, OdfAllAttributes&~OdfSize); if (offset.isIdentity()) { context.removeShapeOffset(this); } else { context.addShapeOffset(this, offset); } } // save the right size so that when loading we fit the viewbox // to the right size without getting any wrong scaling // -> calculate the right size from the current size/viewbound ratio const QSizeF contentSize = currentSize.boundedTo(m_viewBox.size()); const QSizeF scaledSize = currentSize.scaled(contentSize, Qt::KeepAspectRatio); context.xmlWriter().addAttributePt("svg:width", scaledSize.width()); context.xmlWriter().addAttributePt("svg:height", scaledSize.height()); saveText(context); saveEnhancedGeometry(context); } else { KoPathShape::saveOdf(context); } } void EnhancedPathShape::saveEnhancedGeometry(KoShapeSavingContext &context) const { context.xmlWriter().startElement("draw:enhanced-geometry"); context.xmlWriter().addAttribute("svg:viewBox", QString("%1 %2 %3 %4").arg(m_viewBox.x()).arg(m_viewBox.y()).arg(m_viewBox.width()).arg(m_viewBox.height())); if (m_pathStretchPointX != -1) { context.xmlWriter().addAttribute("draw:path-stretchpoint-x", m_pathStretchPointX); } if (m_pathStretchPointY != -1) { context.xmlWriter().addAttribute("draw:path-stretchpoint-y", m_pathStretchPointY); } if (m_mirrorHorizontally) { context.xmlWriter().addAttribute("draw:mirror-horizontal", "true"); } if (m_mirrorVertically) { context.xmlWriter().addAttribute("draw:mirror-vertical", "true"); } QString modifiers; foreach (qreal modifier, m_modifiers) modifiers += QString::number(modifier) + ' '; context.xmlWriter().addAttribute("draw:modifiers", modifiers.trimmed()); if (m_textArea.size() >= 4) { context.xmlWriter().addAttribute("draw:text-areas", m_textArea.join(" ")); } QString path; foreach (EnhancedPathCommand * c, m_commands) path += c->toString() + ' '; context.xmlWriter().addAttribute("draw:enhanced-path", path.trimmed()); FormulaStore::const_iterator i = m_formulae.constBegin(); for (; i != m_formulae.constEnd(); ++i) { context.xmlWriter().startElement("draw:equation"); context.xmlWriter().addAttribute("draw:name", i.key()); context.xmlWriter().addAttribute("draw:formula", i.value()->toString()); context.xmlWriter().endElement(); // draw:equation } foreach (EnhancedPathHandle * handle, m_enhancedHandles) handle->saveOdf(context); context.xmlWriter().endElement(); // draw:enhanced-geometry saveOdfCommonChildElements(context); context.xmlWriter().endElement(); // draw:custom-shape } bool EnhancedPathShape::loadEnhancedGeometry(const KoXmlElement & enhancedGeometry, KoShapeLoadingContext &context) { setPathStretchPointX(enhancedGeometry.attributeNS(KoXmlNS::draw, "path-stretchpoint-x","-1").toDouble()); setPathStretchPointY(enhancedGeometry.attributeNS(KoXmlNS::draw, "path-stretchpoint-y","-1").toDouble()); // load the modifiers QString modifiers = enhancedGeometry.attributeNS(KoXmlNS::draw, "modifiers", ""); if (! modifiers.isEmpty()) { addModifiers(modifiers); } m_textArea = enhancedGeometry.attributeNS(KoXmlNS::draw, "text-areas", "").split(' '); if (m_textArea.size() >= 4) { setResizeBehavior(TextFollowsPreferredTextRect); } KoXmlElement grandChild; forEachElement(grandChild, enhancedGeometry) { if (grandChild.namespaceURI() != KoXmlNS::draw) continue; if (grandChild.localName() == "equation") { QString name = grandChild.attributeNS(KoXmlNS::draw, "name"); QString formula = grandChild.attributeNS(KoXmlNS::draw, "formula"); addFormula(name, formula); } else if (grandChild.localName() == "handle") { EnhancedPathHandle * handle = new EnhancedPathHandle(this); if (handle->loadOdf(grandChild, context)) { m_enhancedHandles.append(handle); evaluateHandles(); } else { delete handle; } } } setMirrorHorizontally(enhancedGeometry.attributeNS(KoXmlNS::draw, "mirror-horizontal") == "true"); setMirrorVertically(enhancedGeometry.attributeNS(KoXmlNS::draw, "mirror-vertical") == "true"); // load the enhanced path data QString path = enhancedGeometry.attributeNS(KoXmlNS::draw, "enhanced-path", ""); #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixEnhancedPath(path, enhancedGeometry, context); #endif // load the viewbox m_viewBox = loadOdfViewbox(enhancedGeometry); if (!path.isEmpty()) { parsePathData(path); } if (m_viewBox.isEmpty()) { // if there is no view box defined make it is big as the path. m_viewBox = m_viewBound.toAlignedRect(); } return true; } bool EnhancedPathShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { reset(); const KoXmlElement enhancedGeometry(KoXml::namedItemNS(element, KoXmlNS::draw, "enhanced-geometry" ) ); if (!enhancedGeometry.isNull() ) { loadEnhancedGeometry(enhancedGeometry, context); } QSizeF size; size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); // the viewbox is to be fitted into the size of the shape, so before setting // the size we just loaded // we set the viewbox to be the basis to calculate // the viewbox matrix from m_viewBound = m_viewBox; setSize(size); QPointF pos; pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); setPosition(pos - m_viewMatrix.map(QPointF(0, 0)) - m_viewBoxOffset); loadOdfAttributes(element, context, OdfMandatories | OdfTransformation | OdfAdditionalAttributes | OdfCommonChildElements); loadText(element, context); return true; } void EnhancedPathShape::parsePathData(const QString &data) { if (data.isEmpty()) return; int start = -1; bool separator = true; for (int i = 0; i < data.length(); ++i) { QChar ch = data.at(i); ushort uni_ch = ch.unicode(); if (separator && (uni_ch == 'M' || uni_ch == 'L' || uni_ch == 'C' || uni_ch == 'Z' || uni_ch == 'N' || uni_ch == 'F' || uni_ch == 'S' || uni_ch == 'T' || uni_ch == 'U' || uni_ch == 'A' || uni_ch == 'B' || uni_ch == 'W' || uni_ch == 'V' || uni_ch == 'X' || uni_ch == 'Y' || uni_ch == 'Q')) { if (start != -1) { // process last chars addCommand(data.mid(start, i - start), false); } start = i; } separator = ch.isSpace(); } if (start < data.length()) addCommand(data.mid(start)); if (start != -1) updatePath(size()); } void EnhancedPathShape::setMirrorHorizontally(bool mirrorHorizontally) { if( m_mirrorHorizontally != mirrorHorizontally) { m_mirrorHorizontally = mirrorHorizontally; updatePath(size()); } } void EnhancedPathShape::setMirrorVertically(bool mirrorVertically) { if( m_mirrorVertically != mirrorVertically) { m_mirrorVertically = mirrorVertically; updatePath(size()); } } void EnhancedPathShape::shapeChanged(ChangeType type, KoShape *shape) { KoParameterShape::shapeChanged(type, shape); if (!shape || shape == this) { if (type == ParentChanged || type == ParameterChanged) { updateTextArea(); } } } void EnhancedPathShape::updateTextArea() { if (m_textArea.size() >= 4) { QRectF r = m_viewBox; r.setLeft(evaluateConstantOrReference(m_textArea[0])); r.setTop(evaluateConstantOrReference(m_textArea[1])); r.setRight(evaluateConstantOrReference(m_textArea[2])); r.setBottom(evaluateConstantOrReference(m_textArea[3])); r = m_viewMatrix.mapRect(r).translated(m_viewBoxOffset); setPreferredTextRect(r); } } void EnhancedPathShape::enableResultCache(bool enable) { m_resultCache.clear(); m_cacheResults = enable; } void EnhancedPathShape::setPathStretchPointX(qreal pathStretchPointX) { if (m_pathStretchPointX != pathStretchPointX) { m_pathStretchPointX = pathStretchPointX; } } void EnhancedPathShape::setPathStretchPointY(qreal pathStretchPointY) { if (m_pathStretchPointY != pathStretchPointY) { m_pathStretchPointY = pathStretchPointY; } } diff --git a/plugins/textshape/TextShape.cpp b/plugins/textshape/TextShape.cpp index c07bf10ff0d..a939818b632 100644 --- a/plugins/textshape/TextShape.cpp +++ b/plugins/textshape/TextShape.cpp @@ -1,434 +1,435 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008-2010 Thorsten Zachmann * Copyright (C) 2008 Pierre Stirnweiss \pierre.stirnweiss_calligra@gadz.org> * Copyright (C) 2010 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 "TextShape.h" #include "ShrinkToFitShapeContainer.h" #include #include "SimpleRootAreaProvider.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 TextShape::TextShape(KoInlineTextObjectManager *inlineTextObjectManager, KoTextRangeManager *textRangeManager) : KoShapeContainer(new KoTextShapeContainerModel()) , KoFrameShape(KoXmlNS::draw, "text-box") , m_pageProvider(0) , m_imageCollection(0) , m_paragraphStyle(0) , m_clip(true) { setShapeId(TextShape_SHAPEID); m_textShapeData = new KoTextShapeData(); setUserData(m_textShapeData); SimpleRootAreaProvider *provider = new SimpleRootAreaProvider(m_textShapeData, this); KoTextDocument(m_textShapeData->document()).setInlineTextObjectManager(inlineTextObjectManager); KoTextDocument(m_textShapeData->document()).setTextRangeManager(textRangeManager); m_layout = new KoTextDocumentLayout(m_textShapeData->document(), provider); m_textShapeData->document()->setDocumentLayout(m_layout); setCollisionDetection(true); QObject::connect(m_layout, SIGNAL(layoutIsDirty()), m_layout, SLOT(scheduleLayout())); } TextShape::~TextShape() { } void TextShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { painter.save(); applyConversion(painter, converter); KoBorder *border = this->border(); if (border) { paintBorder(painter, converter); } else if (paintContext.showTextShapeOutlines) { // No need to paint the outlines if there is a real border. if (qAbs(rotation()) > 1) painter.setRenderHint(QPainter::Antialiasing); QPen pen(QColor(210, 210, 210), 0); // use cosmetic pen QPointF onePixel = converter.viewToDocument(QPointF(1.0, 1.0)); QRectF rect(QPointF(0.0, 0.0), size() - QSizeF(onePixel.x(), onePixel.y())); painter.setPen(pen); painter.drawRect(rect); } painter.restore(); if (m_textShapeData->isDirty()) { // not layouted yet. return; } QTextDocument *doc = m_textShapeData->document(); Q_ASSERT(doc); KoTextDocumentLayout *lay = qobject_cast(doc->documentLayout()); Q_ASSERT(lay); lay->showInlineObjectVisualization(paintContext.showInlineObjectVisualization); applyConversion(painter, converter); if (background()) { QPainterPath p; p.addRect(QRectF(QPointF(), size())); background()->paint(painter, converter, paintContext, p); } // this enables to use the same shapes on different pages showing different page numbers if (m_pageProvider) { KoTextPage *page = m_pageProvider->page(this); if (page) { // this is used to not trigger repaints if layout during the painting is done m_paintRegion = painter.clipRegion(); if (!m_textShapeData->rootArea()->page() || page->pageNumber() != m_textShapeData->rootArea()->page()->pageNumber()) { m_textShapeData->rootArea()->setPage(page); // takes over ownership of the page } else { delete page; } } } KoTextDocumentLayout::PaintContext pc; QAbstractTextDocumentLayout::Selection selection; KoTextEditor *textEditor = KoTextDocument(m_textShapeData->document()).textEditor(); selection.cursor = *(textEditor->cursor()); QPalette palette = pc.textContext.palette; selection.format.setBackground(palette.brush(QPalette::Highlight)); selection.format.setForeground(palette.brush(QPalette::HighlightedText)); pc.textContext.selections.append(selection); pc.textContext.selections += KoTextDocument(doc).selections(); pc.viewConverter = &converter; pc.imageCollection = m_imageCollection; pc.showFormattingCharacters = paintContext.showFormattingCharacters; pc.showTableBorders = paintContext.showTableBorders; pc.showSectionBounds = paintContext.showSectionBounds; pc.showSpellChecking = paintContext.showSpellChecking; pc.showSelections = paintContext.showSelections; // When clipping the painter we need to make sure not to cutoff cosmetic pens which // may used to draw e.g. table-borders for user convenience when on screen (but not // on e.g. printing). Such cosmetic pens are special cause they will always have the // same pen-width (1 pixel) independent of zoom-factor or painter transformations and // are not taken into account in any border-calculations. QRectF clipRect = outlineRect(); qreal cosmeticPenX = 1 * 72. / painter.device()->logicalDpiX(); qreal cosmeticPenY = 1 * 72. / painter.device()->logicalDpiY(); painter.setClipRect(clipRect.adjusted(-cosmeticPenX, -cosmeticPenY, cosmeticPenX, cosmeticPenY), Qt::IntersectClip); painter.save(); painter.translate(0, -m_textShapeData->documentOffset()); m_textShapeData->rootArea()->paint(&painter, pc); // only need to draw ourselves painter.restore(); m_paintRegion = QRegion(); } QPointF TextShape::convertScreenPos(const QPointF &point) const { QPointF p = absoluteTransformation(0).inverted().map(point); return p + QPointF(0.0, m_textShapeData->documentOffset()); } QPainterPath TextShape::outline() const { QPainterPath path; path.addRect(QRectF(QPointF(0,0), size())); return path; } QRectF TextShape::outlineRect() const { if (m_textShapeData->rootArea()) { QRectF rect = m_textShapeData->rootArea()->boundingRect(); rect.moveTop(rect.top() - m_textShapeData->rootArea()->top()); if (m_clip) { rect.setHeight(size().height()); } return rect | QRectF(QPointF(0, 0), size()); } return QRectF(QPointF(0,0), size()); } void TextShape::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(shape); KoShapeContainer::shapeChanged(type, shape); if (type == PositionChanged || type == SizeChanged || type == CollisionDetected) { m_textShapeData->setDirty(); } } void TextShape::saveOdf(KoShapeSavingContext &context) const { KoXmlWriter & writer = context.xmlWriter(); QString textHeight = additionalAttribute("fo:min-height"); const_cast(this)->removeAdditionalAttribute("fo:min-height"); writer.startElement("draw:frame"); // if the TextShape is wrapped in a shrink to fit container we need to save the geometry of the container as // the geometry of the shape might have been changed. if (ShrinkToFitShapeContainer *stf = dynamic_cast(this->parent())) { stf->saveOdfAttributes(context, OdfSize | OdfPosition | OdfTransformation ); saveOdfAttributes(context, OdfAdditionalAttributes | OdfMandatories | OdfCommonChildElements); } else { saveOdfAttributes(context, OdfAllAttributes); } writer.startElement("draw:text-box"); if (! textHeight.isEmpty()) writer.addAttribute("fo:min-height", textHeight); KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); int index = -1; if (lay) { int i = 0; foreach (KoShape *shape, lay->shapes()) { if (shape == this) { index = i; } else if (index >= 0) { writer.addAttribute("draw:chain-next-name", shape->name()); break; } ++i; } } const bool saveMyText = index == 0; // only save the text once. m_textShapeData->saveOdf(context, 0, 0, saveMyText ? -1 : 0); writer.endElement(); // draw:text-box saveOdfCommonChildElements(context); writer.endElement(); // draw:frame } QString TextShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const { Qt::Alignment vAlign(m_textShapeData->verticalAlignment()); QString verticalAlign = "top"; if (vAlign == Qt::AlignBottom) { verticalAlign = "bottom"; } else if ( vAlign == Qt::AlignVCenter ) { verticalAlign = "middle"; } style.addProperty("draw:textarea-vertical-align", verticalAlign); KoTextShapeData::ResizeMethod resize = m_textShapeData->resizeMethod(); if (resize == KoTextShapeData::AutoGrowWidth || resize == KoTextShapeData::AutoGrowWidthAndHeight) style.addProperty("draw:auto-grow-width", "true"); if (resize != KoTextShapeData::AutoGrowHeight && resize != KoTextShapeData::AutoGrowWidthAndHeight) style.addProperty("draw:auto-grow-height", "false"); if (resize == KoTextShapeData::ShrinkToFitResize) style.addProperty("draw:fit-to-size", "true"); m_textShapeData->saveStyle(style, context); return KoShape::saveStyle(style, context); } void TextShape::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) { KoShape::loadStyle(element, context); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); QString verticalAlign(styleStack.property(KoXmlNS::draw, "textarea-vertical-align")); Qt::Alignment alignment(Qt::AlignTop); if (verticalAlign == "bottom") { alignment = Qt::AlignBottom; } else if (verticalAlign == "justify") { // not yet supported alignment = Qt::AlignVCenter; } else if (verticalAlign == "middle") { alignment = Qt::AlignVCenter; } m_textShapeData->setVerticalAlignment(alignment); const QString fitToSize = styleStack.property(KoXmlNS::draw, "fit-to-size"); KoTextShapeData::ResizeMethod resize = KoTextShapeData::NoResize; if (fitToSize == "true" || fitToSize == "shrink-to-fit") { // second is buggy value from impress resize = KoTextShapeData::ShrinkToFitResize; } else { // An explicit svg:width or svg:height defined do change the default value (means those value // used if not explicit defined otherwise) for auto-grow-height and auto-grow-height. So // they are mutable exclusive. // It is not clear (means we did not test and took care of it) what happens if both are // defined and are in conflict with each other or how the fit-to-size is related to this. QString autoGrowWidth = styleStack.property(KoXmlNS::draw, "auto-grow-width"); if (autoGrowWidth.isEmpty()) { autoGrowWidth = element.hasAttributeNS(KoXmlNS::svg, "width") ? "false" : "true"; } QString autoGrowHeight = styleStack.property(KoXmlNS::draw, "auto-grow-height"); if (autoGrowHeight.isEmpty()) { autoGrowHeight = element.hasAttributeNS(KoXmlNS::svg, "height") ? "false" : "true"; } if (autoGrowWidth == "true") { resize = autoGrowHeight == "true" ? KoTextShapeData::AutoGrowWidthAndHeight : KoTextShapeData::AutoGrowWidth; } else if (autoGrowHeight == "true") { resize = KoTextShapeData::AutoGrowHeight; } } m_textShapeData->setResizeMethod(resize); } bool TextShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { m_textShapeData->document()->setUndoRedoEnabled(false); loadOdfAttributes(element, context, OdfAllAttributes); // this cannot be done in loadStyle as that fill the style stack wrongly and therefor it results // in wrong data to be loaded. m_textShapeData->loadStyle(element, context); #ifndef NWORKAROUND_ODF_BUGS KoTextShapeData::ResizeMethod method = m_textShapeData->resizeMethod(); if (KoOdfWorkaround::fixAutoGrow(method, context)) { KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); if (lay) { SimpleRootAreaProvider *provider = dynamic_cast(lay->provider()); if (provider) { provider->m_fixAutogrow = true; } } } #endif bool answer = loadOdfFrame(element, context); m_textShapeData->document()->setUndoRedoEnabled(true); return answer; } bool TextShape::loadOdfFrame(const KoXmlElement &element, KoShapeLoadingContext &context) { // If the loadOdfFrame from the base class for draw:text-box fails, check // for table:table, because that is a legal child of draw:frame in ODF 1.2. if (!KoFrameShape::loadOdfFrame(element, context)) { const KoXmlElement &possibleTableElement(KoXml::namedItemNS(element, KoXmlNS::table, "table")); if (possibleTableElement.isNull()) { return false; } else { return loadOdfFrameElement(possibleTableElement, context); } } return true; } bool TextShape::loadOdfFrameElement(const KoXmlElement &element, KoShapeLoadingContext &context) { bool ok = m_textShapeData->loadOdf(element, context, 0, this); if (ok) ShrinkToFitShapeContainer::tryWrapShape(this, element, context); return ok; } void TextShape::update() const { KoShapeContainer::update(); } void TextShape::update(const QRectF &shape) const { // this is done to avoid updates which are called during the paint event and not needed. if (!m_paintRegion.contains(shape.toRect())) { KoShape::update(shape); } } void TextShape::waitUntilReady(const KoViewConverter &, bool asynchronous) const { Q_UNUSED(asynchronous); KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); if (m_textShapeData->isDirty()) { // Do a simple layout-call which will make sure to relayout till things are done. If more // layouts are scheduled then we don't need to wait for them here but can just continue. lay->layout(); } } KoImageCollection *TextShape::imageCollection() { return m_imageCollection; } void TextShape::updateDocumentData() { if (m_layout) { KoTextDocument document(m_textShapeData->document()); m_layout->setStyleManager(document.styleManager()); m_layout->setInlineTextObjectManager(document.inlineTextObjectManager()); m_layout->setTextRangeManager(document.textRangeManager()); m_layout->setChangeTracker(document.changeTracker()); } } diff --git a/sheets/ui/SheetView.cpp b/sheets/ui/SheetView.cpp index 6260d1fda25..0620cc16edc 100644 --- a/sheets/ui/SheetView.cpp +++ b/sheets/ui/SheetView.cpp @@ -1,770 +1,771 @@ /* This file is part of the KDE project Copyright 2006 Stefan Nikolaus 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. */ // Local #include "SheetView.h" #include #include #include +#include #ifdef CALLIGRA_SHEETS_MT #include #include #include #include #include #endif #include #include "CellView.h" #include "calligra_sheets_limits.h" #include "PointStorage.h" #include "RectStorage.h" #include "Region.h" #include "RowColumnFormat.h" #include "RowFormatStorage.h" #include "Sheet.h" using namespace Calligra::Sheets; struct CellPaintData { CellPaintData(const CellView &cellView, const Cell &cell, const QPointF &coordinate) : cellView(cellView) , cell(cell) , coordinate(coordinate) {} CellView cellView; Cell cell; QPointF coordinate; }; class Q_DECL_HIDDEN SheetView::Private { public: Private() #ifdef CALLIGRA_SHEETS_MT : cacheMutex(QMutex::Recursive) #endif {} const Sheet* sheet; const KoViewConverter* viewConverter; QRect visibleRect; QCache cache; #ifdef CALLIGRA_SHEETS_MT QMutex cacheMutex; #endif QRegion cachedArea; CellView* defaultCellView; // The maximum accessed cell range used for the scrollbar ranges. QSize accessedCellRange; FusionStorage* obscuredInfo; QSize obscuredRange; // size of the bounding box of obscuredInfo #ifdef CALLIGRA_SHEETS_MT QReadWriteLock obscuredLock; #endif PointStorage highlightedCells; QPoint activeHighlight; #ifdef CALLIGRA_SHEETS_MT QReadWriteLock highlightLock; #endif QColor highlightColor; QColor highlightMaskColor; QColor activeHighlightColor; public: Cell cellToProcess(int col, int row, QPointF& coordinate, QSet& processedMergedCells, const QRect& visRect); #ifdef CALLIGRA_SHEETS_MT CellView cellViewToProcess(Cell& cell, QPointF& coordinate, QSet& processedObscuredCells, SheetView* sheetView, const QRect& visRect); #else const CellView& cellViewToProcess(Cell& cell, QPointF& coordinate, QSet& processedObscuredCells, SheetView* sheetView, const QRect& visRect); #endif }; Cell SheetView::Private::cellToProcess(int col, int row, QPointF& coordinate, QSet& processedMergedCells, const QRect& visRect) { Cell cell(sheet, col, row); if (cell.isPartOfMerged()) { cell = cell.masterCell(); // if the rect of visible cells contains this master cell, it was already painted if (visRect.contains(cell.cellPosition())) { coordinate.setY(coordinate.y() + sheet->rowFormats()->rowHeight(row)); return Cell(); // next row } // if the out of bounds master cell was already painted, there's nothing more to do if (processedMergedCells.contains(cell)) { coordinate.setY(coordinate.y() + sheet->rowFormats()->rowHeight(row)); return Cell(); // next row } processedMergedCells.insert(cell); // take the coordinate of the master cell if (sheet->layoutDirection() == Qt::RightToLeft) { for (int c = cell.column()+1; c <= col; ++c) coordinate.setX(coordinate.x() + sheet->columnFormat(c)->width()); } else { for (int c = cell.column(); c < col; ++c) coordinate.setX(coordinate.x() - sheet->columnFormat(c)->width()); } for (int r = cell.row(); r < row; ++r) coordinate.setY(coordinate.y() - sheet->rowFormats()->rowHeight(r)); } return cell; } #ifdef CALLIGRA_SHEETS_MT CellView SheetView::Private::cellViewToProcess(Cell& cell, QPointF& coordinate, QSet& processedObscuredCells, SheetView* sheetView, const QRect& visRect) #else const CellView& SheetView::Private::cellViewToProcess(Cell& cell, QPointF& coordinate, QSet& processedObscuredCells, SheetView* sheetView, const QRect& visRect) #endif { const int col = cell.column(); const int row = cell.row(); const QPoint cellPos = cell.cellPosition(); #ifdef CALLIGRA_SHEETS_MT CellView cellView = sheetView->cellView(col, row); #else const CellView& cellView = sheetView->cellView(col, row); #endif if (sheetView->isObscured(cellPos)) { // if the rect of visible cells contains the obscuring cell, it was already painted if (visRect.contains(sheetView->obscuringCell(cellPos))) { coordinate.setY(coordinate.y() + sheet->rowFormats()->rowHeight(row)); cell = Cell(); return cellView; // next row } cell = Cell(sheet, sheetView->obscuringCell(cellPos)); if (processedObscuredCells.contains(cell)) { coordinate.setY(coordinate.y() + sheet->rowFormats()->rowHeight(row)); cell = Cell(); return cellView; // next row } processedObscuredCells.insert(cell); // take the coordinate of the obscuring cell if (sheet->layoutDirection() == Qt::RightToLeft) { for (int c = cell.column()+1; c <= col; ++c) coordinate.setX(coordinate.x() + sheet->columnFormat(c)->width()); } else { for (int c = cell.column(); c < col; ++c) coordinate.setX(coordinate.x() - sheet->columnFormat(c)->width()); } for (int r = cell.row(); r < row; ++r) coordinate.setY(coordinate.y() - sheet->rowFormats()->rowHeight(r)); // use the CellView of the obscuring cell return sheetView->cellView(cell.column(), cell.row()); } return cellView; } SheetView::SheetView(const Sheet* sheet) : QObject(const_cast(sheet)) , d(new Private) { d->sheet = sheet; d->viewConverter = 0; d->visibleRect = QRect(1, 1, 0, 0); d->cache.setMaxCost(10000); d->defaultCellView = createDefaultCellView(); d->accessedCellRange = sheet->usedArea().size().expandedTo(QSize(256, 256)); d->obscuredInfo = new FusionStorage(sheet->map()); d->obscuredRange = QSize(0, 0); d->highlightMaskColor = QColor(0, 0, 0, 128); d->activeHighlightColor = QColor(255, 127, 0, 128); } SheetView::~SheetView() { delete d->defaultCellView; delete d->obscuredInfo; delete d; } const Sheet* SheetView::sheet() const { return d->sheet; } void SheetView::setViewConverter(const KoViewConverter* viewConverter) { Q_ASSERT(viewConverter); d->viewConverter = viewConverter; } const KoViewConverter* SheetView::viewConverter() const { Q_ASSERT(d->viewConverter); return d->viewConverter; } #ifdef CALLIGRA_SHEETS_MT CellView SheetView::cellView(const QPoint& pos) #else const CellView& SheetView::cellView(const QPoint& pos) #endif { return cellView(pos.x(), pos.y()); } #ifdef CALLIGRA_SHEETS_MT CellView SheetView::cellView(int col, int row) #else const CellView& SheetView::cellView(int col, int row) #endif { Q_ASSERT(1 <= col && col <= KS_colMax); Q_ASSERT(1 <= row && col <= KS_rowMax); #ifdef CALLIGRA_SHEETS_MT QMutexLocker ml(&d->cacheMutex); #endif CellView *v = d->cache.object(QPoint(col, row)); if (!v) { v = createCellView(col, row); d->cache.insert(QPoint(col, row), v); d->cachedArea += QRect(col, row, 1, 1); } #ifdef CALLIGRA_SHEETS_MT // create a copy as long as the mutex is locked CellView cellViewCopy = *v; return cellViewCopy; #else return *v; #endif } void SheetView::setPaintCellRange(const QRect& rect) { #ifdef CALLIGRA_SHEETS_MT QMutexLocker ml(&d->cacheMutex); #endif d->visibleRect = rect & QRect(1, 1, KS_colMax, KS_rowMax); d->cache.setMaxCost(2 * rect.width() * rect.height()); } QRect SheetView::paintCellRange() const { return d->visibleRect; } void SheetView::invalidateRegion(const Region& region) { QRegion qregion; Region::ConstIterator end(region.constEnd()); for (Region::ConstIterator it(region.constBegin()); it != end; ++it) { qregion += (*it)->rect(); } // reduce to the cached area qregion &= d->cachedArea; QVector rects = qregion.rects(); for (int i = 0; i < rects.count(); ++i) invalidateRange(rects[i]); } void SheetView::invalidate() { #ifdef CALLIGRA_SHEETS_MT QMutexLocker ml(&d->cacheMutex); #endif delete d->defaultCellView; d->defaultCellView = createDefaultCellView(); d->cache.clear(); d->cachedArea = QRegion(); delete d->obscuredInfo; d->obscuredInfo = new FusionStorage(d->sheet->map()); d->obscuredRange = QSize(0, 0); } void SheetView::paintCells(QPainter& painter, const QRectF& paintRect, const QPointF& topLeft, CanvasBase*, const QRect& visibleRect) { const QRect& visRect = visibleRect.isValid() ? visibleRect : d->visibleRect; // paintRect: the canvas area, that should be painted; in document coordinates; // no layout direction consideration; scrolling offset applied; // independent from painter transformations // topLeft: the document coordinate of the top left cell's top left corner; // no layout direction consideration; independent from painter // transformations // NOTE Stefan: The painting is split into several steps. In each of these all cells in // d->visibleRect are traversed. This may appear suboptimal at the first look, but // ensures that the borders are not erased by the background of adjacent cells. // debugSheets << "paintRect:" << paintRect; // debugSheets << "topLeft:" << topLeft; QRegion clipRect(painter.clipRegion()); // 0. Paint the sheet background if (!sheet()->backgroundImage().isNull()) { //TODO support all the different properties Sheet::BackgroundImageProperties properties = sheet()->backgroundImageProperties(); if( properties.repeat == Sheet::BackgroundImageProperties::Repeat ) { const int firstCol = visRect.left(); const int firstRow = visRect.top(); const int firstColPosition = d->sheet->columnPosition(firstCol); const int firstRowPosition = d->sheet->rowPosition(firstRow); const int imageWidth = sheet()->backgroundImage().rect().width(); const int imageHeight = sheet()->backgroundImage().rect().height(); int xBackground = firstColPosition - (firstColPosition % imageWidth); int yBackground = firstRowPosition - (firstRowPosition % imageHeight); const int lastCol = visRect.right(); const int lastRow = visRect.bottom(); const int lastColPosition = d->sheet->columnPosition(lastCol); const int lastRowPosition = d->sheet->rowPosition(lastRow); while( xBackground < lastColPosition ) { int y = yBackground; while( y < lastRowPosition ) { painter.drawImage(QRect(xBackground, y, imageWidth, imageHeight), sheet()->backgroundImage()); y += imageHeight; } xBackground += imageWidth; } } } // 1. Paint the cell background // Handle right-to-left layout. // In an RTL sheet the cells have to be painted at their opposite horizontal // location on the canvas, meaning that column A will be the rightmost column // on screen, column B will be to the left of it and so on. Here we change // the horizontal coordinate at which we start painting the cell in case the // sheet's direction is RTL. const bool rightToLeft = sheet()->layoutDirection() == Qt::RightToLeft; const QPointF startCoordinate(rightToLeft ? paintRect.width() - topLeft.x() : topLeft.x(), topLeft.y()); QPointF coordinate(startCoordinate); // debugSheets << "start coordinate:" << coordinate; QSet processedMergedCells; QSet processedObscuredCells; QList cached_cells; for (int col = visRect.left(); col <= visRect.right(); ++col) { if (d->sheet->columnFormat(col)->isHiddenOrFiltered()) continue; if (rightToLeft) coordinate.setX(coordinate.x() - d->sheet->columnFormat(col)->width()); // debugSheets <<"coordinate:" << coordinate; for (int row = visRect.top(); row <= visRect.bottom(); ++row) { int lastHiddenRow; if (d->sheet->rowFormats()->isHiddenOrFiltered(row, &lastHiddenRow)) { row = lastHiddenRow; continue; } // save the coordinate const QPointF savedCoordinate = coordinate; // figure out, if any and which cell has to be painted (may be a master cell) Cell cell = d->cellToProcess(col, row, coordinate, processedMergedCells, visRect); if (!cell) continue; // figure out, which CellView to use (may be one for an obscuring cell) CellPaintData cpd(d->cellViewToProcess(cell, coordinate, processedObscuredCells, this, visRect), cell, coordinate); if (!cell) continue; cpd.cellView.paintCellBackground(painter, clipRect, coordinate); cached_cells.append(cpd); // restore coordinate coordinate = savedCoordinate; coordinate.setY(coordinate.y() + d->sheet->rowFormats()->rowHeight(row)); } coordinate.setY(topLeft.y()); if (!rightToLeft) coordinate.setX(coordinate.x() + d->sheet->columnFormat(col)->width()); } // 2. Paint the cell content including markers (formula, comment, ...) for (QList::ConstIterator it(cached_cells.constBegin()); it != cached_cells.constEnd(); ++it) { it->cellView.paintCellContents(paintRect, painter, clipRect, it->coordinate, it->cell, this); } // 3. Paint the default borders coordinate = startCoordinate; processedMergedCells.clear(); for (int col = visRect.left(); col <= visRect.right(); ++col) { if (d->sheet->columnFormat(col)->isHiddenOrFiltered()) continue; if (rightToLeft) coordinate.setX(coordinate.x() - d->sheet->columnFormat(col)->width()); for (int row = visRect.top(); row <= visRect.bottom(); ++row) { int lastHiddenRow; if (d->sheet->rowFormats()->isHiddenOrFiltered(row, &lastHiddenRow)) { row = lastHiddenRow; continue; } // For borders even cells, that are merged in, need to be traversed. // Think of a merged cell with a set border and one its neighbours has a thicker border. // but: also the master cell of a merged cell always needs to be processed const QPointF savedCoordinate = coordinate; Cell cell = d->cellToProcess(col, row, coordinate, processedMergedCells, visRect); if (!!cell && (cell.column() != col || cell.row() != row)) { const CellView cellView = this->cellView(cell.cellPosition()); cellView.paintDefaultBorders(painter, clipRect, paintRect, coordinate, CellView::LeftBorder | CellView::RightBorder | CellView::TopBorder | CellView::BottomBorder, visRect, cell, this); } coordinate = savedCoordinate; const CellView cellView = this->cellView(col, row); cellView.paintDefaultBorders(painter, clipRect, paintRect, coordinate, CellView::LeftBorder | CellView::RightBorder | CellView::TopBorder | CellView::BottomBorder, visRect, Cell(d->sheet, col, row), this); coordinate.setY(coordinate.y() + d->sheet->rowFormats()->rowHeight(row)); } coordinate.setY(topLeft.y()); if (!rightToLeft) coordinate.setX(coordinate.x() + d->sheet->columnFormat(col)->width()); } // 4. Paint the custom borders, diagonal lines and page borders coordinate = startCoordinate; processedMergedCells.clear(); processedObscuredCells.clear(); for (int col = visRect.left(); col <= visRect.right(); ++col) { if (d->sheet->columnFormat(col)->isHiddenOrFiltered()) continue; if (rightToLeft) coordinate.setX(coordinate.x() - d->sheet->columnFormat(col)->width()); for (int row = visRect.top(); row <= visRect.bottom(); ++row) { int lastHiddenRow; if (d->sheet->rowFormats()->isHiddenOrFiltered(row, &lastHiddenRow)) { row = lastHiddenRow; continue; } // For borders even cells, that are merged in, need to be traversed. // Think of a merged cell with a set border and one its neighbours has a thicker border. // but: also the master cell of a merged cell always needs to be processed const QPointF savedCoordinate = coordinate; Cell cell = d->cellToProcess(col, row, coordinate, processedMergedCells, visRect); if (!!cell && (cell.column() != col || cell.row() != row)) { const CellView cellView = this->cellView(cell.cellPosition()); cellView.paintCellBorders(paintRect, painter, clipRect, coordinate, visRect, cell, this); } coordinate = savedCoordinate; Cell theCell(sheet(), col, row); const CellView cellView = d->cellViewToProcess(theCell, coordinate, processedObscuredCells, this, visRect); if (!!theCell && (theCell.column() != col || theCell.row() != row)) { cellView.paintCellBorders(paintRect, painter, clipRect, coordinate, visRect, theCell, this); } const CellView cellView2 = this->cellView(col, row); coordinate = savedCoordinate; cellView2.paintCellBorders(paintRect, painter, clipRect, coordinate, visRect, Cell(sheet(), col, row), this); coordinate.setY(coordinate.y() + d->sheet->rowFormats()->rowHeight(row)); } coordinate.setY(topLeft.y()); if (!rightToLeft) coordinate.setX(coordinate.x() + d->sheet->columnFormat(col)->width()); } // 5. Paint cell highlighting if (hasHighlightedCells()) { QPointF active = activeHighlight(); QPainterPath p; const CellPaintData* activeData = 0; for (QList::ConstIterator it(cached_cells.constBegin()); it != cached_cells.constEnd(); ++it) { if (isHighlighted(it->cell.cellPosition())) { p.addRect(it->coordinate.x(), it->coordinate.y(), it->cellView.cellWidth(), it->cellView.cellHeight()); if (it->cell.cellPosition() == active) { activeData = &*it; } } } painter.setPen(Qt::NoPen); if (d->highlightColor.isValid()) { painter.setBrush(QBrush(d->highlightColor)); painter.drawPath(p); } if (d->highlightMaskColor.isValid()) { QPainterPath base; base.addRect(painter.clipPath().boundingRect().adjusted(-5, -5, 5, 5)); p = base.subtracted(p); painter.setBrush(QBrush(d->highlightMaskColor)); painter.drawPath(p); } if (activeData && d->activeHighlightColor.isValid()) { painter.setBrush(QBrush(d->activeHighlightColor)); painter.setPen(QPen(Qt::black, 0)); painter.drawRect(QRectF(activeData->coordinate.x(), activeData->coordinate.y(), activeData->cellView.cellWidth(), activeData->cellView.cellHeight())); } } } void SheetView::invalidateRange(const QRect& range) { #ifdef CALLIGRA_SHEETS_MT QMutexLocker ml(&d->cacheMutex); #endif QRegion obscuredRegion; const int right = range.right(); for (int col = range.left(); col <= right; ++col) { const int bottom = range.bottom(); for (int row = range.top(); row <= bottom; ++row) { const QPoint p(col, row); if (!d->cache.contains(p)) continue; if (obscuresCells(p) || isObscured(p)) { obscuredRegion += obscuredArea(p); obscureCells(p, 0, 0); } d->cache.remove(p); } } d->cachedArea -= range; obscuredRegion &= d->cachedArea; foreach (const QRect& rect, obscuredRegion.rects()) { invalidateRange(rect); } } void SheetView::obscureCells(const QPoint &position, int numXCells, int numYCells) { #ifdef CALLIGRA_SHEETS_MT QWriteLocker(&d->obscuredLock); #endif // Start by un-obscuring cells that we might be obscuring right now const QPair pair = d->obscuredInfo->containedPair(position); if (!pair.first.isNull()) d->obscuredInfo->insert(Region(pair.first.toRect()), false); // Obscure the cells if (numXCells != 0 || numYCells != 0) d->obscuredInfo->insert(Region(position.x(), position.y(), numXCells + 1, numYCells + 1), true); QRect obscuredArea = d->obscuredInfo->usedArea(); QSize newObscuredRange(obscuredArea.right(), obscuredArea.bottom()); if (newObscuredRange != d->obscuredRange) { d->obscuredRange = newObscuredRange; emit obscuredRangeChanged(d->obscuredRange); } } QPoint SheetView::obscuringCell(const QPoint &obscuredCell) const { #ifdef CALLIGRA_SHEETS_MT QReadLocker(&d->obscuredLock); #endif const QPair pair = d->obscuredInfo->containedPair(obscuredCell); if (pair.first.isNull()) return obscuredCell; if (pair.second == false) return obscuredCell; return pair.first.toRect().topLeft(); } QSize SheetView::obscuredRange(const QPoint &obscuringCell) const { #ifdef CALLIGRA_SHEETS_MT QReadLocker(&d->obscuredLock); #endif const QPair pair = d->obscuredInfo->containedPair(obscuringCell); if (pair.first.isNull()) return QSize(0, 0); if (pair.second == false) return QSize(0, 0); // Not the master cell? if (pair.first.toRect().topLeft() != obscuringCell) return QSize(0, 0); return pair.first.toRect().size() - QSize(1, 1); } QRect SheetView::obscuredArea(const QPoint &cell) const { #ifdef CALLIGRA_SHEETS_MT QReadLocker(&d->obscuredLock); #endif const QPair pair = d->obscuredInfo->containedPair(cell); if (pair.first.isNull()) return QRect(cell, QSize(1, 1)); if (pair.second == false) return QRect(cell, QSize(1, 1)); // Not the master cell? return pair.first.toRect(); } bool SheetView::isObscured(const QPoint &cell) const { #ifdef CALLIGRA_SHEETS_MT QReadLocker(&d->obscuredLock); #endif const QPair pair = d->obscuredInfo->containedPair(cell); if (pair.first.isNull()) return false; if (pair.second == false) return false; // master cell? if (pair.first.toRect().topLeft() == cell) return false; return true; } bool SheetView::obscuresCells(const QPoint &cell) const { #ifdef CALLIGRA_SHEETS_MT QReadLocker(&d->obscuredLock); #endif const QPair pair = d->obscuredInfo->containedPair(cell); if (pair.first.isNull()) return false; if (pair.second == false) return false; // master cell? if (pair.first.toRect().topLeft() != cell) return false; return true; } QSize SheetView::totalObscuredRange() const { #ifdef CALLIGRA_SHEETS_MT QReadLocker(&d->obscuredLock); #endif return d->obscuredRange; } #ifdef CALLIGRA_SHEETS_MT CellView SheetView::defaultCellView() const #else const CellView& SheetView::defaultCellView() const #endif { return *d->defaultCellView; } void SheetView::updateAccessedCellRange(const QPoint& location) { const QSize cellRange = d->accessedCellRange.expandedTo(QSize(location.x(), location.y())); if (d->accessedCellRange != cellRange || location.isNull()) { d->accessedCellRange = cellRange; const int col = qMin(KS_colMax, cellRange.width() + 10); const int row = qMin(KS_rowMax, cellRange.height() + 10); const double width = sheet()->columnPosition(col) + sheet()->columnFormat(col)->width(); const double height = sheet()->rowPosition(row) + sheet()->rowFormats()->rowHeight(row); emit visibleSizeChanged(QSizeF(width, height)); } } CellView* SheetView::createDefaultCellView() { return new CellView(this); } CellView* SheetView::createCellView(int col, int row) { return new CellView(this, col, row); } bool SheetView::isHighlighted(const QPoint &cell) const { #ifdef CALLIGRA_SHEETS_MT QReadLocker(&d->highlightLock); #endif return d->highlightedCells.lookup(cell.x(), cell.y()); } void SheetView::setHighlighted(const QPoint &cell, bool isHighlighted) { #ifdef CALLIGRA_SHEETS_MT QWriteLocker(&d->highlightLock); #endif bool oldHadHighlights = d->highlightedCells.count() > 0; bool oldVal; if (isHighlighted) { oldVal = d->highlightedCells.insert(cell.x(), cell.y(), true); } else { oldVal = d->highlightedCells.take(cell.x(), cell.y()); } if (oldHadHighlights != (d->highlightedCells.count() > 0)) { invalidate(); } else if (oldVal != isHighlighted) { invalidateRegion(Region(cell)); } } bool SheetView::hasHighlightedCells() const { #ifdef CALLIGRA_SHEETS_MT QReadLocker(&d->highlightLock); #endif return d->highlightedCells.count() > 0; } void SheetView::clearHighlightedCells() { #ifdef CALLIGRA_SHEETS_MT QWriteLocker(&d->highlightLock); #endif d->activeHighlight = QPoint(); if (d->highlightedCells.count()) { d->highlightedCells.clear(); invalidate(); } } QPoint SheetView::activeHighlight() const { return d->activeHighlight; } void SheetView::setActiveHighlight(const QPoint &cell) { QPoint oldVal = d->activeHighlight; d->activeHighlight = cell; if (oldVal != cell) { Region r; if (!oldVal.isNull()) r.add(oldVal); if (!cell.isNull()) r.add(cell); invalidateRegion(r); } } void SheetView::setHighlightColor(const QColor &color) { d->highlightColor = color; if (hasHighlightedCells()) { invalidate(); } } void SheetView::setHighlightMaskColor(const QColor &color) { d->highlightMaskColor = color; if (hasHighlightedCells()) { invalidate(); } } void SheetView::setActiveHighlightColor(const QColor &color) { d->activeHighlightColor = color; if (hasHighlightedCells()) { invalidate(); } } diff --git a/stage/part/KPrPresentationHighlightWidget.cpp b/stage/part/KPrPresentationHighlightWidget.cpp index ee27dd31b3b..adeaf67c016 100644 --- a/stage/part/KPrPresentationHighlightWidget.cpp +++ b/stage/part/KPrPresentationHighlightWidget.cpp @@ -1,77 +1,78 @@ /* This file is part of the KDE project * Copyright (C) 2009 Alexia Allanic * Copyright (C) 2009 Jean-Nicolas Artaud * Copyright (C) 2009 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KPrPresentationHighlightWidget.h" #include #include #include #include +#include KPrPresentationHighlightWidget::KPrPresentationHighlightWidget( KoPACanvasBase * canvas ) : KPrPresentationToolEventForwarder(canvas) , m_size( canvas->canvasWidget()->size() ) { // The focus and the track for have the mouse position every time setFocusPolicy( Qt::StrongFocus ); setMouseTracking( true ); // Size of the canvas is saved because it's used in the paintEvent resize( m_size ); m_center = QCursor::pos(); update(); } KPrPresentationHighlightWidget::~KPrPresentationHighlightWidget() { } /** paintEvent call with the update in the mouseMoveEvent */ void KPrPresentationHighlightWidget::paintEvent( QPaintEvent * event ) { Q_UNUSED(event); QPainter painter( this ); QPen myPen(Qt::black, 0); QColor c( Qt::black ); // TODO make alpha configurable c.setAlphaF( 0.5 ); // The circle we want QPainterPath ellipse; // TODO make radius configurable ellipse.addEllipse( m_center.x() - 75, m_center.y() - 75, 150, 150 ); // All the 'background' QPainterPath myPath; myPath.addRect( 0, 0, m_size.rwidth(), m_size.rheight() ); // We draw the difference painter.setPen( myPen ); painter.fillPath( myPath.subtracted( ellipse ), c ); } /** Take the mouse position every time the mouse is moving */ void KPrPresentationHighlightWidget::mouseMoveEvent( QMouseEvent* e ) { // Save the position of the mouse m_center = e->pos(); // Update the screen : move the circle with a paint event // TODO maybe only update what has changed update(); } diff --git a/stage/plugins/pageeffects/edgewipe/barndoorwipe/BarnDoorWipeStrategy.cpp b/stage/plugins/pageeffects/edgewipe/barndoorwipe/BarnDoorWipeStrategy.cpp index a646b31a369..7aee18d3fbc 100644 --- a/stage/plugins/pageeffects/edgewipe/barndoorwipe/BarnDoorWipeStrategy.cpp +++ b/stage/plugins/pageeffects/edgewipe/barndoorwipe/BarnDoorWipeStrategy.cpp @@ -1,130 +1,131 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "BarnDoorWipeStrategy.h" #include "BarnDoorWipeEffectFactory.h" #include #include +#include const int StepCount = 250; BarnDoorWipeStrategy::BarnDoorWipeStrategy( int subtype, const char *smilSubType, bool reverse ) : KPrPageEffectStrategy( subtype, "barnDoorWipe", smilSubType, reverse ) { } BarnDoorWipeStrategy::~BarnDoorWipeStrategy() { } void BarnDoorWipeStrategy::setup( const KPrPageEffect::Data &data, QTimeLine &timeLine ) { Q_UNUSED( data ); timeLine.setFrameRange( 0, StepCount ); } void BarnDoorWipeStrategy::paintStep( QPainter &p, int currPos, const KPrPageEffect::Data &data ) { p.drawPixmap( QPoint( 0, 0 ), data.m_oldPage, data.m_widget->rect() ); p.setClipPath( clipPath( currPos, data.m_widget->rect() ) ); p.drawPixmap( QPoint( 0, 0 ), data.m_newPage, data.m_widget->rect() ); } void BarnDoorWipeStrategy::next( const KPrPageEffect::Data &data ) { data.m_widget->update(); } QPainterPath BarnDoorWipeStrategy::clipPath( int step, const QRect &area ) { qreal percent = static_cast(step) / static_cast(StepCount); if( reverse() ) percent = static_cast(StepCount-step) / static_cast(StepCount); int stepx = static_cast( area.width() * percent ); int stepy = static_cast( area.height() * percent ); int width_2 = area.width() >> 1; int height_2 = area.height() >> 1; QPainterPath path; switch( subType() ) { case BarnDoorWipeEffectFactory::Vertical: case BarnDoorWipeEffectFactory::VerticalReverse: { stepx = static_cast( width_2 * percent ); QPoint top( width_2, 0 ); QPoint bottom( width_2, area.height() ); path.moveTo( top - QPoint( stepx, 0 ) ); path.lineTo( top + QPoint( stepx, 0 ) ); path.lineTo( bottom + QPoint( stepx, 0 ) ); path.lineTo( bottom - QPoint( stepx, 0 ) ); break; } case BarnDoorWipeEffectFactory::Horizontal: case BarnDoorWipeEffectFactory::HorizontalReverse: { stepy = static_cast( height_2 * percent ); QPoint left( 0, height_2 ); QPoint right( area.width(), height_2 ); path.moveTo( left + QPoint( 0, stepy ) ); path.lineTo( left - QPoint( 0, stepy ) ); path.lineTo( right - QPoint( 0, stepy ) ); path.lineTo( right + QPoint( 0, stepy ) ); break; } case BarnDoorWipeEffectFactory::DiagonalBottomLeft: case BarnDoorWipeEffectFactory::DiagonalBottomLeftReverse: { path.moveTo( area.bottomLeft() + QPoint( stepx, 0 ) ); path.lineTo( area.bottomLeft() ); path.lineTo( area.bottomLeft() - QPoint( 0, stepy ) ); path.lineTo( area.topRight() - QPoint( stepx, 0 ) ); path.lineTo( area.topRight() ); path.lineTo( area.topRight() + QPoint( 0, stepy ) ); break; } case BarnDoorWipeEffectFactory::DiagonalTopLeft: case BarnDoorWipeEffectFactory::DiagonalTopLeftReverse: { path.moveTo( area.topLeft() + QPoint( 0, stepy ) ); path.lineTo( area.topLeft() ); path.lineTo( area.topLeft() + QPoint( stepx, 0 ) ); path.lineTo( area.bottomRight() - QPoint( 0, stepy ) ); path.lineTo( area.bottomRight() ); path.lineTo( area.bottomRight() - QPoint( stepx, 0 ) ); break; } default: return QPainterPath(); } path.closeSubpath(); if( reverse() ) { QPainterPath areaPath; areaPath.addRect( area ); path = areaPath.subtracted( path ); } return path; } diff --git a/stage/plugins/pageeffects/edgewipe/barnveewipe/BarnVeeWipeStrategy.cpp b/stage/plugins/pageeffects/edgewipe/barnveewipe/BarnVeeWipeStrategy.cpp index f3e0bcaba4d..b4d10ac1ef2 100644 --- a/stage/plugins/pageeffects/edgewipe/barnveewipe/BarnVeeWipeStrategy.cpp +++ b/stage/plugins/pageeffects/edgewipe/barnveewipe/BarnVeeWipeStrategy.cpp @@ -1,130 +1,131 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "BarnVeeWipeStrategy.h" #include "BarnVeeWipeEffectFactory.h" #include #include +#include const int StepCount = 250; BarnVeeWipeStrategy::BarnVeeWipeStrategy( int subtype, const char *smilSubType, bool reverse ) : KPrPageEffectStrategy( subtype, "barnVeeWipe", smilSubType, reverse ) { } BarnVeeWipeStrategy::~BarnVeeWipeStrategy() { } void BarnVeeWipeStrategy::setup( const KPrPageEffect::Data &data, QTimeLine &timeLine ) { Q_UNUSED( data ); timeLine.setFrameRange( 0, StepCount ); } void BarnVeeWipeStrategy::paintStep( QPainter &p, int currPos, const KPrPageEffect::Data &data ) { p.drawPixmap( QPoint( 0, 0 ), data.m_oldPage, data.m_widget->rect() ); p.setClipPath( clipPath( currPos, data.m_widget->rect() ) ); p.drawPixmap( QPoint( 0, 0 ), data.m_newPage, data.m_widget->rect() ); } void BarnVeeWipeStrategy::next( const KPrPageEffect::Data &data ) { data.m_widget->update(); } QPainterPath BarnVeeWipeStrategy::clipPath( int step, const QRect &area ) { qreal percent = static_cast(step) / static_cast(StepCount); if( reverse() ) percent = static_cast(StepCount-step) / static_cast(StepCount); int stepx = static_cast( area.width() * percent ); int stepy = static_cast( area.height() * percent ); int width_2 = area.width() >> 1; int height_2 = area.height() >> 1; int stepx_2 = static_cast( width_2 * percent ); int stepy_2 = static_cast( height_2 * percent ); QPainterPath path; switch( subType() ) { case BarnVeeWipeEffectFactory::FromTop: path.moveTo( area.topLeft() ); path.lineTo( area.topLeft() + QPoint( 0, stepy ) ); path.lineTo( QPoint( width_2, area.height() ) - QPoint( stepx_2, 0 ) ); path.lineTo( QPoint( width_2, area.height() ) + QPoint( stepx_2, 0 ) ); path.lineTo( area.topRight() + QPoint( 0, stepy ) ); path.lineTo( area.topRight() ); path.lineTo( area.topRight() - QPoint( stepx_2, 0 ) ); path.lineTo( QPoint( width_2, area.height() ) - QPoint( 0, stepy ) ); path.lineTo( area.topLeft() + QPoint( stepx_2, 0 ) ); break; case BarnVeeWipeEffectFactory::FromRight: path.moveTo( area.topRight() ); path.lineTo( area.topRight() - QPoint( stepx, 0 ) ); path.lineTo( QPoint( 0, height_2 ) - QPoint( 0, stepy_2 ) ); path.lineTo( QPoint( 0, height_2 ) + QPoint( 0, stepy_2 ) ); path.lineTo( area.bottomRight() - QPoint( stepx, 0 ) ); path.lineTo( area.bottomRight() ); path.lineTo( area.bottomRight() - QPoint( 0, stepy_2 ) ); path.lineTo( QPoint( 0, height_2 ) + QPoint( stepx, 0 ) ); path.lineTo( area.topRight() + QPoint( 0, stepy_2 ) ); break; case BarnVeeWipeEffectFactory::FromBottom: path.moveTo( area.bottomLeft() ); path.lineTo( area.bottomLeft() - QPoint( 0, stepy ) ); path.lineTo( QPoint( width_2, 0 ) - QPoint( stepx_2, 0 ) ); path.lineTo( QPoint( width_2, 0 ) + QPoint( stepx_2, 0 ) ); path.lineTo( area.bottomRight() - QPoint( 0, stepy ) ); path.lineTo( area.bottomRight() ); path.lineTo( area.bottomRight() - QPoint( stepx_2, 0 ) ); path.lineTo( QPoint( width_2, 0 ) + QPoint( 0, stepy ) ); path.lineTo( area.bottomLeft() + QPoint( stepx_2, 0 ) ); break; case BarnVeeWipeEffectFactory::FromLeft: path.moveTo( area.topLeft() ); path.lineTo( area.topLeft() + QPoint( stepx, 0 ) ); path.lineTo( QPoint( area.width(), height_2 ) - QPoint( 0, stepy_2 ) ); path.lineTo( QPoint( area.width(), height_2 ) + QPoint( 0, stepy_2 ) ); path.lineTo( area.bottomLeft() + QPoint( stepx, 0 ) ); path.lineTo( area.bottomLeft() ); path.lineTo( area.bottomLeft() - QPoint( 0, stepy_2 ) ); path.lineTo( QPoint( area.width(), height_2 ) - QPoint( stepx, 0 ) ); path.lineTo( area.topLeft() + QPoint( 0, stepy_2 ) ); break; default: return QPainterPath(); } path.closeSubpath(); if( reverse() ) { QPainterPath areaPath; areaPath.addRect( area ); path = areaPath.subtracted( path ); } return path; } diff --git a/stage/plugins/pageeffects/edgewipe/barnzigzagwipe/BarnZigZagWipeStrategy.cpp b/stage/plugins/pageeffects/edgewipe/barnzigzagwipe/BarnZigZagWipeStrategy.cpp index 3daf68d90cd..c56581e6a76 100644 --- a/stage/plugins/pageeffects/edgewipe/barnzigzagwipe/BarnZigZagWipeStrategy.cpp +++ b/stage/plugins/pageeffects/edgewipe/barnzigzagwipe/BarnZigZagWipeStrategy.cpp @@ -1,134 +1,135 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "BarnZigZagWipeStrategy.h" #include "BarnZigZagWipeEffectFactory.h" #include #include +#include const int StepCount = 250; BarnZigZagWipeStrategy::BarnZigZagWipeStrategy( int subtype, const char *smilSubType, bool reverse ) : KPrPageEffectStrategy( subtype, "BarnZigZagWipe", smilSubType, reverse ) { } BarnZigZagWipeStrategy::~BarnZigZagWipeStrategy() { } void BarnZigZagWipeStrategy::setup( const KPrPageEffect::Data &data, QTimeLine &timeLine ) { Q_UNUSED( data ); timeLine.setFrameRange( 0, StepCount ); } void BarnZigZagWipeStrategy::paintStep( QPainter &p, int currPos, const KPrPageEffect::Data &data ) { p.drawPixmap( QPoint( 0, 0 ), data.m_oldPage, data.m_widget->rect() ); p.setClipPath( clipPath( currPos, data.m_widget->rect() ) ); p.drawPixmap( QPoint( 0, 0 ), data.m_newPage, data.m_widget->rect() ); } void BarnZigZagWipeStrategy::next( const KPrPageEffect::Data &data ) { data.m_widget->update(); } QPainterPath BarnZigZagWipeStrategy::clipPath( int step, const QRect &area ) { const int zigZagCount = 10; const qreal zigZagHeight = area.height() / static_cast( zigZagCount ); const qreal zigZagWidth = area.width() / static_cast( zigZagCount ); qreal percent = static_cast(step) / static_cast(StepCount); if( reverse() ) percent = static_cast(StepCount-step) / static_cast(StepCount); qreal width_2 = 0.5 * area.width(); qreal height_2 = 0.5 * area.height(); int stepx = static_cast( (width_2 + 1.5*zigZagWidth) * percent ); int stepy = static_cast( (height_2 + 1.5*zigZagHeight) * percent ); qreal zigZagHeight_2 = 0.5 * zigZagHeight; qreal zigZagWidth_2 = 0.5 * zigZagWidth; QPainterPath path; switch( subType() ) { case BarnZigZagWipeEffectFactory::Horizontal: case BarnZigZagWipeEffectFactory::HorizontalReversed: { qreal zigZagBase = height_2 - stepy - zigZagHeight_2; qreal zigZagTip = zigZagBase + zigZagHeight; path.moveTo( QPointF( area.left(), height_2 ) ); path.lineTo( QPointF( area.left(), zigZagBase ) ); for( int i = 0; i < zigZagCount; ++i ) { path.lineTo( QPointF( (2*i+1) * zigZagWidth_2, zigZagTip ) ); path.lineTo( QPointF( (i+1) * zigZagWidth, zigZagBase ) ); } zigZagBase = height_2 + stepy + zigZagHeight_2; zigZagTip = zigZagBase - zigZagHeight; path.lineTo( QPointF( area.right(), zigZagTip ) ); for( int i = 0; i < zigZagCount; ++i ) { path.lineTo( QPointF( area.right() - (2*i+1) * zigZagWidth_2, zigZagBase ) ); path.lineTo( QPointF( area.right() - (i+1) * zigZagWidth, zigZagTip ) ); } break; } case BarnZigZagWipeEffectFactory::Vertical: case BarnZigZagWipeEffectFactory::VerticalReversed: { qreal zigZagBase = width_2 - stepx - zigZagWidth_2; qreal zigZagTip = zigZagBase + zigZagWidth; path.moveTo( QPointF( width_2, area.top() ) ); path.lineTo( QPointF( zigZagBase, area.top() ) ); for( int i = 0; i < zigZagCount; ++i ) { path.lineTo( QPointF( zigZagTip, (2*i+1) * zigZagHeight_2 ) ); path.lineTo( QPointF( zigZagBase, (i+1) * zigZagHeight ) ); } zigZagBase = width_2 + stepx + zigZagWidth_2; zigZagTip = zigZagBase - zigZagWidth; path.lineTo( QPointF( zigZagTip, area.bottom() ) ); for( int i = 0; i < zigZagCount; ++i ) { path.lineTo( QPointF( zigZagBase, area.bottom() - (2*i+1) * zigZagHeight_2 ) ); path.lineTo( QPointF( zigZagTip, area.bottom() - (i+1) * zigZagHeight ) ); } break; } default: return QPainterPath(); } path.closeSubpath(); if( reverse() ) { QPainterPath areaPath; areaPath.addRect( area ); path = areaPath.subtracted( path ); } return path; } diff --git a/stage/plugins/pageeffects/edgewipe/boxwipe/BoxWipeStrategy.cpp b/stage/plugins/pageeffects/edgewipe/boxwipe/BoxWipeStrategy.cpp index b73e4a58c2c..3d037b5995f 100644 --- a/stage/plugins/pageeffects/edgewipe/boxwipe/BoxWipeStrategy.cpp +++ b/stage/plugins/pageeffects/edgewipe/boxwipe/BoxWipeStrategy.cpp @@ -1,109 +1,110 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "BoxWipeStrategy.h" #include "BoxWipeEffectFactory.h" #include #include +#include const int StepCount = 250; BoxWipeStrategy::BoxWipeStrategy( int subtype, const char *smilSubType, bool reverse ) : KPrPageEffectStrategy( subtype, "boxWipe", smilSubType, reverse ) { } BoxWipeStrategy::~BoxWipeStrategy() { } void BoxWipeStrategy::setup( const KPrPageEffect::Data &data, QTimeLine &timeLine ) { Q_UNUSED( data ); timeLine.setFrameRange( 0, StepCount ); } void BoxWipeStrategy::paintStep( QPainter &p, int currPos, const KPrPageEffect::Data &data ) { p.drawPixmap( QPoint( 0, 0 ), data.m_oldPage, data.m_widget->rect() ); p.setClipPath( clipPath( currPos, data.m_widget->rect() ) ); p.drawPixmap( QPoint( 0, 0 ), data.m_newPage, data.m_widget->rect() ); } void BoxWipeStrategy::next( const KPrPageEffect::Data &data ) { data.m_widget->update(); } QPainterPath BoxWipeStrategy::clipPath( int step, const QRect &area ) { qreal percent = static_cast(step) / static_cast(StepCount); if( reverse() ) percent = static_cast(StepCount-step) / static_cast(StepCount); int stepx = static_cast( area.width() * percent ); int stepy = static_cast( area.height() * percent ); QRect clipRect( QPoint( 0, 0 ), QSize( stepx, stepy ) ); switch( subType() ) { case BoxWipeEffectFactory::FromTopLeft: clipRect.moveTopLeft( area.topLeft() ); break; case BoxWipeEffectFactory::FromTopRight: clipRect.moveTopRight( area.topRight() ); break; case BoxWipeEffectFactory::FromBottomLeft: clipRect.moveBottomLeft( area.bottomLeft() ); break; case BoxWipeEffectFactory::FromBottomRight: clipRect.moveBottomRight( area.bottomRight() ); break; case BoxWipeEffectFactory::CenterTop: clipRect.moveCenter( area.center() ); clipRect.moveTop( 0 ); break; case BoxWipeEffectFactory::CenterRight: clipRect.moveCenter( area.center() ); clipRect.moveRight( area.width() ); break; case BoxWipeEffectFactory::CenterBottom: clipRect.moveCenter( area.center() ); clipRect.moveBottom( area.height() ); break; case BoxWipeEffectFactory::CenterLeft: clipRect.moveCenter( area.center() ); clipRect.moveLeft( 0 ); break; default: return QPainterPath(); } QPainterPath path; path.addRect( clipRect ); if( reverse() ) { QPainterPath areaPath; areaPath.addRect( area ); path = areaPath.subtracted( path ); } return path; } diff --git a/stage/plugins/pageeffects/edgewipe/diagonalwipe/DiagonalWipeStrategy.cpp b/stage/plugins/pageeffects/edgewipe/diagonalwipe/DiagonalWipeStrategy.cpp index 4d3fc68dae0..9a7d8841108 100644 --- a/stage/plugins/pageeffects/edgewipe/diagonalwipe/DiagonalWipeStrategy.cpp +++ b/stage/plugins/pageeffects/edgewipe/diagonalwipe/DiagonalWipeStrategy.cpp @@ -1,92 +1,93 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "DiagonalWipeStrategy.h" #include "DiagonalWipeEffectFactory.h" #include #include +#include const int StepCount = 250; DiagonalWipeStrategy::DiagonalWipeStrategy( int subtype, const char *smilSubType, bool reverse ) : KPrPageEffectStrategy( subtype, "diagonalWipe", smilSubType, reverse ) { } DiagonalWipeStrategy::~DiagonalWipeStrategy() { } void DiagonalWipeStrategy::setup( const KPrPageEffect::Data &data, QTimeLine &timeLine ) { Q_UNUSED( data ); timeLine.setFrameRange( 0, StepCount ); } void DiagonalWipeStrategy::paintStep( QPainter &p, int currPos, const KPrPageEffect::Data &data ) { p.drawPixmap( QPoint( 0, 0 ), data.m_oldPage, data.m_widget->rect() ); p.setClipPath( clipPath( currPos, data.m_widget->rect() ) ); p.drawPixmap( QPoint( 0, 0 ), data.m_newPage, data.m_widget->rect() ); } void DiagonalWipeStrategy::next( const KPrPageEffect::Data &data ) { data.m_widget->update(); } QPainterPath DiagonalWipeStrategy::clipPath( int step, const QRect &area ) { qreal percent = static_cast(step) / static_cast(StepCount); QPoint vecx( static_cast(2.0 * area.width() * percent), 0 ) ; QPoint vecy( 0, static_cast( 2.0 * area.height() * percent ) ); QPainterPath path; switch( subType() ) { case DiagonalWipeEffectFactory::FromTopLeft: path.moveTo( area.topLeft() ); path.lineTo( area.topLeft() + vecx ); path.lineTo( area.topLeft() + vecy ); break; case DiagonalWipeEffectFactory::FromTopRight: path.moveTo( area.topRight() ); path.lineTo( area.topRight() - vecx ); path.lineTo( area.topRight() + vecy ); break; case DiagonalWipeEffectFactory::FromBottomLeft: path.moveTo( area.bottomLeft() ); path.lineTo( area.bottomLeft() + vecx ); path.lineTo( area.bottomLeft() - vecy ); break; case DiagonalWipeEffectFactory::FromBottomRight: path.moveTo( area.bottomRight() ); path.lineTo( area.bottomRight() - vecx ); path.lineTo( area.bottomRight() - vecy ); break; default: return QPainterPath(); } path.closeSubpath(); return path; } diff --git a/stage/plugins/pageeffects/edgewipe/fourboxwipe/CornersInWipeStrategy.cpp b/stage/plugins/pageeffects/edgewipe/fourboxwipe/CornersInWipeStrategy.cpp index 99f7297ca13..06c8bcbc449 100644 --- a/stage/plugins/pageeffects/edgewipe/fourboxwipe/CornersInWipeStrategy.cpp +++ b/stage/plugins/pageeffects/edgewipe/fourboxwipe/CornersInWipeStrategy.cpp @@ -1,92 +1,94 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "CornersInWipeStrategy.h" #include "FourBoxWipeEffectFactory.h" #include #include +#include +#include const int StepCount = 250; CornersInWipeStrategy::CornersInWipeStrategy( bool reverse ) : KPrPageEffectStrategy( reverse ? FourBoxWipeEffectFactory::CornersInReverse : FourBoxWipeEffectFactory::CornersIn, "fourBoxWipe", "cornersIn", reverse ) { } CornersInWipeStrategy::~CornersInWipeStrategy() { } void CornersInWipeStrategy::setup( const KPrPageEffect::Data &data, QTimeLine &timeLine ) { Q_UNUSED( data ); timeLine.setFrameRange( 0, StepCount ); } void CornersInWipeStrategy::paintStep( QPainter &p, int currPos, const KPrPageEffect::Data &data ) { p.drawPixmap( QPoint( 0, 0 ), data.m_oldPage, data.m_widget->rect() ); p.setClipPath( clipPath( currPos, data.m_widget->rect() ) ); p.drawPixmap( QPoint( 0, 0 ), data.m_newPage, data.m_widget->rect() ); } void CornersInWipeStrategy::next( const KPrPageEffect::Data &data ) { data.m_widget->update(); } QPainterPath CornersInWipeStrategy::clipPath( int step, const QRect &area ) { int width_2 = area.width() >> 1; int height_2 = area.height() >> 1; qreal percent = static_cast(step) / static_cast(StepCount); int stepx = static_cast( width_2 * percent ); int stepy = static_cast( height_2 * percent ); QPainterPath path; if( ! reverse() ) { QSize rectSize( stepx, stepy ); QRect topLeft( area.topLeft(), rectSize ); QRect topRight( area.topRight() - QPoint( stepx, 0 ), rectSize ); QRect bottomRight( area.bottomRight() - QPoint( stepx, stepy), rectSize ); QRect bottomLeft( area.bottomLeft() - QPoint( 0, stepy ), rectSize ); path.addRect( topLeft ); path.addRect( topRight ); path.addRect( bottomRight ); path.addRect( bottomLeft ); } else { QRect horzRect( QPoint( 0, 0 ), QSize( 2 * stepx, area.height() ) ); horzRect.moveCenter( area.center() ); QRect vertRect( QPoint( 0, 0 ), QSize( area.width(), 2 * stepy ) ); vertRect.moveCenter( area.center() ); path.addRect( horzRect ); path.addRect( vertRect ); path.setFillRule( Qt::WindingFill ); } return path; } diff --git a/stage/plugins/pageeffects/edgewipe/fourboxwipe/CornersOutWipeStrategy.cpp b/stage/plugins/pageeffects/edgewipe/fourboxwipe/CornersOutWipeStrategy.cpp index 1e5a7da69ab..35a5c9dbccb 100644 --- a/stage/plugins/pageeffects/edgewipe/fourboxwipe/CornersOutWipeStrategy.cpp +++ b/stage/plugins/pageeffects/edgewipe/fourboxwipe/CornersOutWipeStrategy.cpp @@ -1,95 +1,96 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "CornersOutWipeStrategy.h" #include "FourBoxWipeEffectFactory.h" #include #include +#include const int StepCount = 250; CornersOutWipeStrategy::CornersOutWipeStrategy( bool reverse ) : KPrPageEffectStrategy( reverse ? FourBoxWipeEffectFactory::CornersOutReverse : FourBoxWipeEffectFactory::CornersOut, "fourBoxWipe", "cornersOut", reverse ) { } CornersOutWipeStrategy::~CornersOutWipeStrategy() { } void CornersOutWipeStrategy::setup( const KPrPageEffect::Data &data, QTimeLine &timeLine ) { Q_UNUSED( data ); timeLine.setFrameRange( 0, StepCount ); } void CornersOutWipeStrategy::paintStep( QPainter &p, int currPos, const KPrPageEffect::Data &data ) { p.drawPixmap( QPoint( 0, 0 ), data.m_oldPage, data.m_widget->rect() ); p.setClipPath( clipPath( currPos, data.m_widget->rect() ) ); p.drawPixmap( QPoint( 0, 0 ), data.m_newPage, data.m_widget->rect() ); } void CornersOutWipeStrategy::next( const KPrPageEffect::Data &data ) { data.m_widget->update(); } QPainterPath CornersOutWipeStrategy::clipPath( int step, const QRect &area ) { int width_2 = area.width() >> 1; int height_2 = area.height() >> 1; int width_4 = width_2 >> 1; int height_4 = height_2 >> 1; qreal percent = static_cast(step) / static_cast(StepCount); int stepx = static_cast( width_2 * percent ); int stepy = static_cast( height_2 * percent ); QRect templateRect; if( reverse() ) templateRect = QRect( QPoint(0,0), QSize( width_2 - stepx, height_2 - stepy ) ); else templateRect = QRect( QPoint(0,0), QSize( stepx, stepy ) ); QRect topLeft = templateRect; topLeft.moveCenter( QPoint( width_4, height_4 ) ); QRect topRight = templateRect; topRight.moveCenter( QPoint( width_2 + width_4, height_4 ) ); QRect bottomRight = templateRect; bottomRight.moveCenter( QPoint( width_2+width_4, height_2 + height_4 ) ); QRect bottomLeft = templateRect; bottomLeft.moveCenter( QPoint( width_4, height_2 + height_4 ) ); QPainterPath path; path.addRect( topLeft ); path.addRect( topRight ); path.addRect( bottomRight ); path.addRect( bottomLeft ); if( reverse() ) { QPainterPath areaPath; areaPath.addRect( area ); path = areaPath.subtracted( path ); } return path; } diff --git a/stage/plugins/pageeffects/edgewipe/miscdiagonalwipe/DoubleBarnDoorWipeStrategy.cpp b/stage/plugins/pageeffects/edgewipe/miscdiagonalwipe/DoubleBarnDoorWipeStrategy.cpp index 600e78f66c0..1b5879b6213 100644 --- a/stage/plugins/pageeffects/edgewipe/miscdiagonalwipe/DoubleBarnDoorWipeStrategy.cpp +++ b/stage/plugins/pageeffects/edgewipe/miscdiagonalwipe/DoubleBarnDoorWipeStrategy.cpp @@ -1,83 +1,84 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "DoubleBarnDoorWipeStrategy.h" #include "MiscDiagonalWipeEffectFactory.h" #include #include +#include const int StepCount = 250; DoubleBarnDoorWipeStrategy::DoubleBarnDoorWipeStrategy() : KPrPageEffectStrategy( MiscDiagonalWipeEffectFactory::DoubleBarnDoor, "miscDiagonalWipe", "doubleBarnDoor", false ) { } DoubleBarnDoorWipeStrategy::~DoubleBarnDoorWipeStrategy() { } void DoubleBarnDoorWipeStrategy::setup( const KPrPageEffect::Data &data, QTimeLine &timeLine ) { Q_UNUSED( data ); timeLine.setFrameRange( 0, StepCount ); } void DoubleBarnDoorWipeStrategy::paintStep( QPainter &p, int currPos, const KPrPageEffect::Data &data ) { p.drawPixmap( QPoint( 0, 0 ), data.m_oldPage, data.m_widget->rect() ); p.setClipPath( clipPath( currPos, data.m_widget->rect() ) ); p.drawPixmap( QPoint( 0, 0 ), data.m_newPage, data.m_widget->rect() ); } void DoubleBarnDoorWipeStrategy::next( const KPrPageEffect::Data &data ) { data.m_widget->update(); } QPainterPath DoubleBarnDoorWipeStrategy::clipPath( int step, const QRect &area ) { int width_2 = area.width() >> 1; int height_2 = area.height() >> 1; qreal percent = static_cast( step ) / static_cast( StepCount ); int stepx = static_cast( width_2 * percent ); int stepy = static_cast( height_2 * percent ); QPainterPath path; path.lineTo( area.topLeft() ); path.lineTo( area.topLeft() + QPoint( stepx, 0 ) ); path.lineTo( area.center() - QPoint( 0, stepy ) ); path.lineTo( area.topRight() - QPoint( stepx, 0 ) ); path.lineTo( area.topRight() ); path.lineTo( area.topRight() + QPoint( 0, stepy ) ); path.lineTo( area.center() + QPoint( stepx, 0 ) ); path.lineTo( area.bottomRight() - QPoint( 0, stepy ) ); path.lineTo( area.bottomRight() ); path.lineTo( area.bottomRight() - QPoint( stepx, 0 ) ); path.lineTo( area.center() + QPoint( 0, stepy ) ); path.lineTo( area.bottomLeft() + QPoint( stepx, 0 ) ); path.lineTo( area.bottomLeft() ); path.lineTo( area.bottomLeft() - QPoint( 0, stepy ) ); path.lineTo( area.center() - QPoint( stepx, 0 ) ); path.lineTo( area.topLeft() + QPoint( 0, stepy ) ); path.closeSubpath(); return path; } diff --git a/stage/plugins/pageeffects/edgewipe/miscdiagonalwipe/DoubleDiamondWipeStrategy.cpp b/stage/plugins/pageeffects/edgewipe/miscdiagonalwipe/DoubleDiamondWipeStrategy.cpp index 882a7891d44..28cc8bdc43f 100644 --- a/stage/plugins/pageeffects/edgewipe/miscdiagonalwipe/DoubleDiamondWipeStrategy.cpp +++ b/stage/plugins/pageeffects/edgewipe/miscdiagonalwipe/DoubleDiamondWipeStrategy.cpp @@ -1,82 +1,83 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "DoubleDiamondWipeStrategy.h" #include "MiscDiagonalWipeEffectFactory.h" #include #include +#include const int StepCount = 250; DoubleDiamondWipeStrategy::DoubleDiamondWipeStrategy() : KPrPageEffectStrategy( MiscDiagonalWipeEffectFactory::DoubleDiamond, "miscDiagonalWipe", "doubleDiamond", false ) { } DoubleDiamondWipeStrategy::~DoubleDiamondWipeStrategy() { } void DoubleDiamondWipeStrategy::setup( const KPrPageEffect::Data &data, QTimeLine &timeLine ) { Q_UNUSED( data ); timeLine.setFrameRange( 0, StepCount ); } void DoubleDiamondWipeStrategy::paintStep( QPainter &p, int currPos, const KPrPageEffect::Data &data ) { p.drawPixmap( QPoint( 0, 0 ), data.m_oldPage, data.m_widget->rect() ); p.setClipPath( clipPath( currPos, data.m_widget->rect() ) ); p.drawPixmap( QPoint( 0, 0 ), data.m_newPage, data.m_widget->rect() ); } void DoubleDiamondWipeStrategy::next( const KPrPageEffect::Data &data ) { data.m_widget->update(); } QPainterPath DoubleDiamondWipeStrategy::clipPath( int step, const QRect &area ) { int width_2 = area.width() >> 1; int height_2 = area.height() >> 1; qreal percent = static_cast(StepCount - step) / static_cast(StepCount); int stepx = static_cast( width_2 * percent ); int stepy = static_cast( height_2 * percent ); QPainterPath outerPath; outerPath.moveTo( area.topLeft() + QPoint( stepx, 0 ) ); outerPath.lineTo( area.topRight() - QPoint( stepx, 0 ) ); outerPath.lineTo( area.topRight() + QPoint( 0, stepy ) ); outerPath.lineTo( area.bottomRight() - QPoint( 0, stepy ) ); outerPath.lineTo( area.bottomRight() - QPoint( stepx, 0 ) ); outerPath.lineTo( area.bottomLeft() + QPoint( stepx, 0 ) ); outerPath.lineTo( area.bottomLeft() - QPoint( 0, stepy ) ); outerPath.lineTo( area.topLeft() + QPoint( 0, stepy ) ); outerPath.closeSubpath(); QPainterPath innerPath; innerPath.moveTo( area.center() - QPoint( 0, stepy ) ); innerPath.lineTo( area.center() + QPoint( stepx, 0 ) ); innerPath.lineTo( area.center() + QPoint( 0, stepy ) ); innerPath.lineTo( area.center() - QPoint( stepx, 0 ) ); innerPath.closeSubpath(); return outerPath.subtracted( innerPath ); } diff --git a/stage/plugins/pageeffects/edgewipe/veewipe/VeeWipeStrategy.cpp b/stage/plugins/pageeffects/edgewipe/veewipe/VeeWipeStrategy.cpp index 203da474d4e..fa36ff45290 100644 --- a/stage/plugins/pageeffects/edgewipe/veewipe/VeeWipeStrategy.cpp +++ b/stage/plugins/pageeffects/edgewipe/veewipe/VeeWipeStrategy.cpp @@ -1,123 +1,124 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "VeeWipeStrategy.h" #include "VeeWipeEffectFactory.h" #include #include +#include const int StepCount = 250; VeeWipeStrategy::VeeWipeStrategy( int subtype, const char *smilSubType, bool reverse ) : KPrPageEffectStrategy( subtype, "veeWipe", smilSubType, reverse ) { } VeeWipeStrategy::~VeeWipeStrategy() { } void VeeWipeStrategy::setup( const KPrPageEffect::Data &data, QTimeLine &timeLine ) { Q_UNUSED( data ); timeLine.setFrameRange( 0, StepCount ); } void VeeWipeStrategy::paintStep( QPainter &p, int currPos, const KPrPageEffect::Data &data ) { p.drawPixmap( QPoint( 0, 0 ), data.m_oldPage, data.m_widget->rect() ); p.setClipPath( clipPath( currPos, data.m_widget->rect() ) ); p.drawPixmap( QPoint( 0, 0 ), data.m_newPage, data.m_widget->rect() ); } void VeeWipeStrategy::next( const KPrPageEffect::Data &data ) { data.m_widget->update(); } QPainterPath VeeWipeStrategy::clipPath( int step, const QRect &area ) { qreal percent = static_cast(step) / static_cast(StepCount); if( reverse() ) percent = static_cast(StepCount-step) / static_cast(StepCount); int stepx = static_cast( 2 * area.width() * percent ); int stepy = static_cast( 2 * area.height() * percent ); int width_2 = area.width() >> 1; int height_2 = area.height() >> 1; QPainterPath path; switch( subType() ) { case VeeWipeEffectFactory::FromTop: { QPoint move( 0, stepy - 2 * area.height() ); path.moveTo( move + area.topLeft() ); path.lineTo( move + area.bottomLeft() ); path.lineTo( move + QPoint( width_2, 2 * area.height() ) ); path.lineTo( move + area.bottomRight() ); path.lineTo( move + area.topRight() ); break; } case VeeWipeEffectFactory::FromRight: { QPoint move( 2 * area.width()-stepx, 0 ); path.moveTo( move + area.topRight() ); path.lineTo( move + area.topLeft() ); path.lineTo( move + QPoint( -area.width(), height_2 ) ); path.lineTo( move + area.bottomLeft() ); path.lineTo( move + area.bottomRight() ); break; } case VeeWipeEffectFactory::FromBottom: { QPoint move( 0, 2 * area.height() - stepy ); path.moveTo( move + area.bottomLeft() ); path.lineTo( move + area.topLeft() ); path.lineTo( move + QPoint( width_2, -area.height() ) ); path.lineTo( move + area.topRight() ); path.lineTo( move + area.bottomRight() ); break; } case VeeWipeEffectFactory::FromLeft: { QPoint move( stepx - 2 * area.width(), 0 ); path.moveTo( move + area.topLeft() ); path.lineTo( move + area.topRight() ); path.lineTo( move + QPoint( 2 * area.width(), height_2 ) ); path.lineTo( move + area.bottomRight() ); path.lineTo( move + area.bottomLeft() ); break; } default: return QPainterPath(); } path.closeSubpath(); if( reverse() ) { QPainterPath areaPath; areaPath.addRect( area ); path = areaPath.subtracted( path ); } return path; } diff --git a/stage/plugins/pageeffects/edgewipe/zigzagwipe/ZigZagWipeStrategy.cpp b/stage/plugins/pageeffects/edgewipe/zigzagwipe/ZigZagWipeStrategy.cpp index 7abf3fb3812..79e43fb66ed 100644 --- a/stage/plugins/pageeffects/edgewipe/zigzagwipe/ZigZagWipeStrategy.cpp +++ b/stage/plugins/pageeffects/edgewipe/zigzagwipe/ZigZagWipeStrategy.cpp @@ -1,117 +1,118 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ZigZagWipeStrategy.h" #include "ZigZagWipeEffectFactory.h" #include #include +#include const int StepCount = 250; ZigZagWipeStrategy::ZigZagWipeStrategy( int subtype, const char *smilSubType, bool reverse ) : KPrPageEffectStrategy( subtype, "ZigZagWipe", smilSubType, reverse ) { } ZigZagWipeStrategy::~ZigZagWipeStrategy() { } void ZigZagWipeStrategy::setup( const KPrPageEffect::Data &data, QTimeLine &timeLine ) { Q_UNUSED( data ); timeLine.setFrameRange( 0, StepCount ); } void ZigZagWipeStrategy::paintStep( QPainter &p, int currPos, const KPrPageEffect::Data &data ) { p.drawPixmap( QPoint( 0, 0 ), data.m_oldPage, data.m_widget->rect() ); p.setClipPath( clipPath( currPos, data.m_widget->rect() ) ); p.drawPixmap( QPoint( 0, 0 ), data.m_newPage, data.m_widget->rect() ); } void ZigZagWipeStrategy::next( const KPrPageEffect::Data &data ) { data.m_widget->update(); } QPainterPath ZigZagWipeStrategy::clipPath( int step, const QRect &area ) { const int zigZagCount = 10; const qreal zigZagHeight = area.height() / static_cast( zigZagCount ); const qreal zigZagWidth = area.width() / static_cast( zigZagCount ); qreal percent = static_cast(step) / static_cast(StepCount); if( reverse() ) percent = static_cast(StepCount-step) / static_cast(StepCount); int stepx = static_cast( (area.width() + 2*zigZagWidth) * percent ); int stepy = static_cast( (area.height() + 2*zigZagHeight) * percent ); qreal zigZagHeight_2 = 0.5 * zigZagHeight; qreal zigZagWidth_2 = 0.5 * zigZagWidth; QPainterPath path; switch( subType() ) { case ZigZagWipeEffectFactory::FromTop: case ZigZagWipeEffectFactory::FromBottom: { qreal zigZagBase = stepy - zigZagHeight; qreal zigZagTip = stepy; path.moveTo( area.topLeft() - QPointF( 0, zigZagHeight ) ); path.lineTo( QPointF( area.left(), zigZagBase ) ); for( int i = 0; i < zigZagCount; ++i ) { path.lineTo( area.topLeft() + QPointF( (2*i+1) * zigZagWidth_2, zigZagTip ) ); path.lineTo( area.topLeft() + QPointF( (i+1) * zigZagWidth, zigZagBase ) ); } path.lineTo( area.topRight() - QPointF( 0, zigZagHeight ) ); break; } case ZigZagWipeEffectFactory::FromLeft: case ZigZagWipeEffectFactory::FromRight: { qreal zigZagBase = stepx - zigZagWidth; qreal zigZagTip = stepx; path.moveTo( area.topLeft() - QPointF( zigZagWidth, 0 ) ); path.lineTo( QPointF( zigZagBase, area.top() ) ); for( int i = 0; i < zigZagCount; ++i ) { path.lineTo( area.topLeft() + QPointF( zigZagTip, (2*i+1) * zigZagHeight_2 ) ); path.lineTo( area.topLeft() + QPointF( zigZagBase, (i+1) * zigZagHeight ) ); } path.lineTo( area.bottomLeft() - QPointF( zigZagWidth, 0 ) ); break; } default: return QPainterPath(); } path.closeSubpath(); if( reverse() ) { QPainterPath areaPath; areaPath.addRect( area ); path = areaPath.subtracted( path ); } return path; } diff --git a/words/part/frames/KWFrameLayout.cpp b/words/part/frames/KWFrameLayout.cpp index 727705f118e..037e8acf5dc 100644 --- a/words/part/frames/KWFrameLayout.cpp +++ b/words/part/frames/KWFrameLayout.cpp @@ -1,906 +1,907 @@ /* This file is part of the KDE project * Copyright (C) 2000-2006 David Faure * Copyright (C) 2005-2011 Sebastian Sauer * Copyright (C) 2005-2006, 2009 Thomas Zander * Copyright (C) 2008 Pierre Ducroquet * Copyright (C) 2010 by Nokia, Matus Hanzes * Copyright 2012 Friedrich W. H. Kossebau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KWFrameLayout.h" #include "KWPageManager.h" #include "KWTextFrameSet.h" #include "KWPageStyle.h" #include "KWPage.h" #include "KWCopyShape.h" #include "KWDocument.h" #include "Words.h" #include #include #include #include #include +#include #include #include #include #include #include /** * This shape is a helper class for drawing the background of pages * and/or the separators between multiple columns. */ class KWPageBackground : public KoShape { public: KWPageBackground() { setSelectable(false); setTextRunAroundSide(KoShape::RunThrough, KoShape::Background); } ~KWPageBackground() override { } void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) override { applyConversion(painter, converter); // paint background if (background()) { QPainterPath p; p.addRect(QRectF(QPointF(), size())); background()->paint(painter, converter, paintContext, p); } // paint separators if (! m_separatorPositions.isEmpty()) { QPen separatorPen(QBrush(m_separatorColor), m_separatorWidth, Qt::PenStyle(m_separatorStyle), Qt::FlatCap); painter.setPen(separatorPen); foreach(qreal separatorPos, m_separatorPositions) { QLineF line(separatorPos, m_separatorY, separatorPos, m_separatorY+m_separatorHeight); painter.drawLine(line); } } } bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) override { return true; } void saveOdf(KoShapeSavingContext &) const override { } void setSeparators(KoColumns::SeparatorStyle separatorStyle, const QColor &color, const QList &separatorPositions, qreal separatorY, qreal separatorWidth, qreal separatorHeight) { m_separatorStyle = separatorStyle; m_separatorColor = color; m_separatorPositions = separatorPositions; m_separatorY = separatorY; m_separatorWidth = separatorWidth; m_separatorHeight = separatorHeight; } void clearSeparators() { m_separatorPositions.clear(); } private: KoColumns::SeparatorStyle m_separatorStyle; QColor m_separatorColor; QList m_separatorPositions; qreal m_separatorY; /** Width in pt */ qreal m_separatorWidth; qreal m_separatorHeight; }; KWFrameLayout::KWFrameLayout(const KWPageManager *pageManager, const QList &frameSets) : m_pageManager(pageManager), m_frameSets(frameSets), m_maintext(0), m_backgroundFrameSet(0), m_document(0), m_setup(false) { } void KWFrameLayout::createNewFramesForPage(int pageNumber) { debugWords << "pageNumber=" << pageNumber; m_setup = false; // force reindexing of types const KWPage page = m_pageManager->page(pageNumber); Q_ASSERT(page.isValid()); const KWPageStyle pageStyle = page.pageStyle(); // Header footer handling. // first make a list of all types. QList allHFTypes; allHFTypes.append(Words::OddPagesHeaderTextFrameSet); allHFTypes.append(Words::EvenPagesHeaderTextFrameSet); allHFTypes.append(Words::OddPagesFooterTextFrameSet); allHFTypes.append(Words::EvenPagesFooterTextFrameSet); // create headers & footers Words::TextFrameSetType origin; if (shouldHaveHeaderOrFooter(pageNumber, true, &origin)) { allHFTypes.removeAll(origin); KWTextFrameSet *fs = getOrCreate(origin, page); debugWords << "HeaderTextFrame" << fs << "sequencedShapeOn=" << sequencedShapeOn(fs, pageNumber) << "pageStyle=" << pageStyle.name(); if (!sequencedShapeOn(fs, pageNumber)) { createCopyFrame(fs, page); } } if (shouldHaveHeaderOrFooter(pageNumber, false, &origin)) { allHFTypes.removeAll(origin); KWTextFrameSet *fs = getOrCreate(origin, page); debugWords << "FooterTextFrame" << fs << "sequencedShapeOn=" << sequencedShapeOn(fs, pageNumber) << "pageStyle=" << pageStyle.name(); if (!sequencedShapeOn(fs, pageNumber)) { createCopyFrame(fs, page); } } //debugWords <<"createNewFramesForPage" << pageNumber << "TextFrameSetType=" << Words::frameSetTypeName(origin); // delete headers/footer frames that are not needed on this page foreach (KoShape *shape, sequencedShapesOnPage(page.rect())) { if (KWFrameSet::from(shape)->type() != Words::TextFrameSet) continue; KWTextFrameSet *tfs = dynamic_cast(KWFrameSet::from(shape)); if (tfs && (allHFTypes.contains(tfs->textFrameSetType()) || (tfs->pageStyle() != pageStyle && Words::isHeaderFooter(tfs)))) { Q_ASSERT(shape); KWPage p = m_pageManager->page(shape); Q_ASSERT(p.isValid()); debugWords<<"Delete disabled header/footer shape=" << shape << "pageRect=" << page.rect() << "pageNumber=" << p.pageNumber(); tfs->removeShape(shape); delete shape; } } // create main text shape. All columns of them. const int columnsCount = pageStyle.columns().count; Q_ASSERT(columnsCount >= 1); KWTextFrameSet *fs = getOrCreate(Words::MainTextFrameSet, page); QRectF rect(QPointF(0, page.offsetInDocument()), QSizeF(page.width(), page.height())); debugWords << "MainTextFrame" << fs << "pageRect=" << rect << "columnsCount=" << columnsCount; int neededColumnsCount = columnsCount; // Check existing column shapes foreach (KoShape *shape, sequencedShapesOnPage(rect)) { if (KWFrameSet::from(shape) == fs) { --neededColumnsCount; if (neededColumnsCount < 0) { debugWords << "Deleting KWFrame from MainTextFrame"; fs->removeShape(shape); delete shape; } } } const qreal colwidth = pageStyle.pageLayout().width / columnsCount; const qreal colheight = pageStyle.pageLayout().height; // Create missing column shapes for (int c = 0; c < neededColumnsCount; ++c) { debugWords << "Creating KWFrame for MainTextFrame"; KoShape * shape = createTextShape(page); shape->setPosition(QPoint(c * colwidth+10.0, page.offsetInDocument()+10.0)); shape->setSize(QSizeF(colwidth, colheight)); new KWFrame(shape, fs); } if (pageStyle.background() || (columnsCount > 1)) { // create page background if (!m_backgroundFrameSet) { m_backgroundFrameSet = new KWFrameSet(Words::BackgroundFrameSet); m_backgroundFrameSet->setName("backgroundFrames"); emit newFrameSet(m_backgroundFrameSet); Q_ASSERT(m_frameSets.contains(m_backgroundFrameSet)); // the emit should have made that so :) } KoShape *background = sequencedShapeOn(m_backgroundFrameSet, pageNumber); if (background == 0) { background = new KWPageBackground(); background->setPosition(QPointF(0, page.offsetInDocument())); new KWFrame(background, m_backgroundFrameSet); background->setTextRunAroundSide(KoShape::RunThrough); } background->setBackground(pageStyle.background()); } else { // check if there is a frame, if so, delete it, we don't need it. KoShape *background = sequencedShapeOn(m_backgroundFrameSet, pageNumber); if (background) delete background; } layoutFramesOnPage(pageNumber); } void KWFrameLayout::layoutFramesOnPage(int pageNumber) { /* assumes all frames are there and will do layouting of all the frames - headers/footers/main FS are positioned - normal frames are clipped to page */ KWPage page = m_pageManager->page(pageNumber); Q_ASSERT(page.isValid()); if (!page.isValid()) return; /* +-----------------+ | 0 | <- pageStyle->pageLayout()->topMargin + layout->topPadding | 1 [ header ] | | 2 | <- pageStyle->headerDistance() | 3 [ maintxt ] | | 4 | <- pageStyle->footerDistance() | 5 [ footer ] | | 6 | <- pageStyle->pageLayout()->bottomMargin + layout->bottomPadding +-----------------+ */ // Create some data structures used for the layouting of the frames later int minZIndex = INT_MAX; qreal requestedHeight[7], minimumHeight[7], resultingPositions[7]; for (int i = 0; i < 7; i++) { // zero fill. requestedHeight[i] = 0; minimumHeight[i] = 0; resultingPositions[i] = 0; } minimumHeight[0] = page.topMargin() + page.topPadding(); minimumHeight[6] = page.bottomMargin() + page.bottomPadding(); KoPageLayout layout = page.pageStyle().pageLayout(); layout.leftMargin = page.leftMargin(); layout.rightMargin = page.rightMargin(); layout.leftPadding = page.leftPadding(); layout.rightPadding = page.rightPadding(); const qreal left = 0, width = page.width(); const qreal textWidth = width - layout.leftMargin - layout.rightMargin - layout.leftPadding - layout.rightPadding; KWPageStyle pageStyle = page.pageStyle(); KoColumns columns = pageStyle.columns(); int columnIndex = 0; KoShape **main; KoShape *footer = 0, *header = 0; KoShape *pageBackground = 0; main = new KoShape*[columns.count]; if (columns.count > 0) main[0] = 0; QRectF pageRect(left, page.offsetInDocument(), width, page.height()); QList shapes = sequencedShapesOnPage(pageRect); debugWords << "pageNumber=" << pageNumber << "columns=" << columns.count << "shapeCount=" << shapes.count(); foreach (KoShape *shape, shapes) { KWTextFrameSet *textFrameSet = 0; switch (KWFrameSet::from(shape)->type()) { case Words::BackgroundFrameSet: pageBackground = shape; continue; case Words::TextFrameSet: textFrameSet = static_cast(KWFrameSet::from(shape)); if (textFrameSet->textFrameSetType() == Words::OtherTextFrameSet) { minZIndex = qMin(minZIndex, shape->zIndex()); continue; } break; case Words::OtherFrameSet: minZIndex = qMin(minZIndex, shape->zIndex()); continue; } Q_ASSERT(textFrameSet); /* KWPage page = m_pageManager->page(frame->shape()); Q_ASSERT(page.isValid()); debugWords << "textFrameSetType=" << Words::frameSetTypeName(textFrameSet->textFrameSetType()) << "page=" << page.pageNumber() << "offset=" << page.offsetInDocument() << "position=" << frame->shape()->position() << "size=" << frame->shape()->size() << "outlineRect=" << frame->shape()->outlineRect() << "boundingRect=" << frame->shape()->boundingRect(); */ switch (textFrameSet->textFrameSetType()) { case Words::OddPagesHeaderTextFrameSet: case Words::EvenPagesHeaderTextFrameSet: { header = shape; minimumHeight[2] = pageStyle.headerDistance(); minimumHeight[1] = qMax((qreal)10, pageStyle.headerMinimumHeight() - pageStyle.headerDistance()); requestedHeight[1] = qMax(minimumHeight[1], textFrameSet->shapes().first()->minimumHeight()); if (pageStyle.headerDynamicSpacing()) { minimumHeight[2] = qMax((qreal)0, minimumHeight[1] - requestedHeight[1]); } break; } case Words::OddPagesFooterTextFrameSet: case Words::EvenPagesFooterTextFrameSet: { footer = shape; minimumHeight[4] = pageStyle.footerDistance(); minimumHeight[5] = qMax((qreal)10, pageStyle.footerMinimumHeight() - pageStyle.footerDistance()); requestedHeight[5] = qMax(minimumHeight[5], textFrameSet->shapes().first()->minimumHeight()); if (pageStyle.footerDynamicSpacing()) { minimumHeight[4] = qMax((qreal)0, minimumHeight[5] - requestedHeight[5]); } break; } case Words::MainTextFrameSet: { if (columnIndex == columns.count) { warnWords << "Too many columns present on page, ignoring 1, columns.count=" << columns.count; break; } main[columnIndex] = shape; ++columnIndex; minimumHeight[3] = 10; // make at least one line fit lest we add endless pages. QTextLayout *layout = textFrameSet->document()->begin().layout(); if (layout && layout->lineCount() > 0) { minimumHeight[3] = qMax((qreal) 10, layout->lineAt(0).height()); } requestedHeight[3] = -1; // rest break; } default:; } } pageBackground = sequencedShapeOn(m_backgroundFrameSet, pageNumber); --minZIndex; for (int i = 0; i < columns.count; ++i) { Q_ASSERT_X(main[i], __FUNCTION__, QString("No TextShape for column=%1 columnsCount=%2").arg(i).arg(columns.count).toLocal8Bit()); if (main[i]) main[i]->setZIndex(minZIndex); } if (footer) { footer->setZIndex(minZIndex); // Make us compatible with ms word (seems saner too). Compatible with LO would be 0 footer->setRunThrough(-3); //so children will be <= -2 and thus below main text } if (header) { header->setZIndex(minZIndex); // Make us compatible with ms word (seems saner too). Compatible with LO would be 0 header->setRunThrough(-3); //so children will be <= -2 and thus below main text } if (pageBackground) { pageBackground->setRunThrough(-10); //so it will be below everything pageBackground->setZIndex(--minZIndex); } // spread space across items. qreal heightLeft = page.height(); for (int i = 0; i < 7; i++) { heightLeft -= qMax(minimumHeight[i], requestedHeight[i]); } if (heightLeft >= 0) { // easy; plenty of space minimumHeight[3] += heightLeft; // add space to main text frame qreal y = page.offsetInDocument(); for (int i = 0; i < 7; i++) { resultingPositions[i] = y; y += qMax(minimumHeight[i], requestedHeight[i]); } } else { // for situations where the header + footer are too big to fit together with a // minimum sized main text frame. heightLeft = page.height(); for (int i = 0; i < 7; i++) heightLeft -= minimumHeight[i]; qreal y = page.offsetInDocument(); for (int i = 0; i < 7; i++) { resultingPositions[i] = y; qreal row = minimumHeight[i]; if (requestedHeight[i] > row) { row += heightLeft / 3; } y += row; } } // actually move / size the frames. if (columns.count > 0 && main[0]) { const qreal fullColumnHeight = resultingPositions[4] - resultingPositions[3]; const qreal columnsXOffset = left + layout.leftMargin + layout.leftPadding; QRectF *columnRects = new QRectF[columns.count]; // uniform columns? if (columns.columnData.isEmpty()) { const qreal columnWidth = (textWidth - columns.gapWidth * (columns.count - 1)) / columns.count; const qreal columnStep = columnWidth + columns.gapWidth; for (int i = 0; i < columns.count; i++) { columnRects[i] = QRectF( columnsXOffset + columnStep * i, resultingPositions[3], columnWidth, fullColumnHeight); } } else { const qreal totalRelativeWidth = columns.totalRelativeWidth(); int relativeColumnXOffset = 0; for (int i = 0; i < columns.count; i++) { const KoColumns::ColumnDatum &columnDatum = columns.columnData.at(i); const qreal columnWidth = textWidth * columnDatum.relativeWidth / totalRelativeWidth; const qreal columnXOffset = textWidth * relativeColumnXOffset / totalRelativeWidth; columnRects[i] = QRectF( columnsXOffset + columnXOffset + columnDatum.leftMargin, resultingPositions[3] + columnDatum.topMargin, columnWidth - columnDatum.leftMargin - columnDatum.rightMargin, fullColumnHeight - columnDatum.topMargin - columnDatum.bottomMargin); relativeColumnXOffset += columnDatum.relativeWidth; } } //make sure the order of shapes geometrically follows the textflow order for (int i = 0; i < columns.count; i++) { for (int f = 0; f < columns.count; f++) { if (f == i) continue; if (qAbs(main[f]->position().x() - columnRects[i].x()) < 10.0) { qSwap(main[f], main[i]); break; } } } // finally set size and position of the shapes for (int i = columns.count - 1; i >= 0; i--) { main[i]->setPosition(columnRects[i].topLeft()); main[i]->setSize(columnRects[i].size()); } delete[] columnRects; // We need to store the content rect so layout can place it's anchored shapes // correctly page.setContentRect(QRectF(QPointF(left + layout.leftMargin + layout.leftPadding, resultingPositions[3]), QSizeF(textWidth ,resultingPositions[4] - resultingPositions[3]))); } if (header) { header->setPosition( QPointF(left + layout.leftMargin + layout.leftPadding, resultingPositions[1])); header->setSize(QSizeF(textWidth, resultingPositions[2] - resultingPositions[1])); } if (footer) { footer->setPosition( QPointF(left + layout.leftMargin + layout.leftPadding, resultingPositions[5])); footer->setSize(QSizeF(textWidth, resultingPositions[6] - resultingPositions[5])); } if (pageBackground) { pageBackground->setPosition(QPointF(left + layout.leftMargin, page.offsetInDocument() + layout.topMargin)); pageBackground->setSize(QSizeF(width - layout.leftMargin - layout.rightMargin, page.height() - layout.topMargin - layout.bottomMargin)); // set separator data KWPageBackground *bs = dynamic_cast(pageBackground); if (columns.count > 1) { const qreal fullColumnHeight = resultingPositions[4] - resultingPositions[3]; QList separatorXPositions; // uniform columns? if (columns.columnData.isEmpty()) { const qreal separatorXBaseOffset = layout.leftPadding - (columns.gapWidth * 0.5); const qreal columnWidth = (textWidth - columns.gapWidth * (columns.count - 1)) / columns.count; const qreal columnStep = columnWidth + columns.gapWidth; for (int i = 1; i < columns.count; ++i) { separatorXPositions << separatorXBaseOffset + columnStep * i; } } else { const qreal totalRelativeWidth = columns.totalRelativeWidth(); int relativeColumnXOffset = 0; for (int i = 0; i < columns.count-1; i++) { const KoColumns::ColumnDatum &columnDatum = columns.columnData.at(i); relativeColumnXOffset += columnDatum.relativeWidth; const qreal columnXOffset = textWidth * relativeColumnXOffset / totalRelativeWidth; separatorXPositions << layout.leftPadding + columnXOffset; } } const qreal separatorHeight = fullColumnHeight * columns.separatorHeight / 100.0; const qreal separatorY = layout.topPadding + ((columns.separatorVerticalAlignment == KoColumns::AlignBottom) ? fullColumnHeight * (100 - columns.separatorHeight) / 100.0 : (columns.separatorVerticalAlignment == KoColumns::AlignVCenter) ? fullColumnHeight * (100 - columns.separatorHeight) / 200.0 : /* default: KoColumns::AlignTop */ 0); bs->setSeparators(columns.separatorStyle, columns.separatorColor, separatorXPositions, separatorY, columns.separatorWidth, separatorHeight); } else { bs->clearSeparators(); } } delete [] main; } void KWFrameLayout::proposeShapeMove(const KoShape *shape, QPointF &delta, const KWPage &page) { KoShapeAnchor *anchor = shape->anchor(); if (!anchor) { return; // nothing we can do } QRectF refRect; const qreal textWidth = page.width() - page.leftMargin() - page.rightMargin() - page.leftPadding() - page.rightPadding(); switch (anchor->horizontalRel()) { case KoShapeAnchor::HParagraph: // LO mistakenly saves it like this sometimes - stupid LO anchor->setHorizontalRel(KoShapeAnchor::HPage); // let's fix it // fall through case KoShapeAnchor::HPage: refRect.setX(0); refRect.setWidth(page.width()); break; case KoShapeAnchor::HPageContent: refRect.setX(page.leftMargin() + page.leftPadding()); refRect.setWidth(textWidth); break; case KoShapeAnchor::HPageStartMargin: refRect.setX(0); refRect.setRight(page.leftMargin() + page.leftPadding()); break; case KoShapeAnchor::HPageEndMargin: refRect.setX(page.width() - page.rightMargin() - page.rightPadding()); refRect.setRight(page.width()); break; default: break; } switch (anchor->verticalRel()) { case KoShapeAnchor::VPage: refRect.setY(page.offsetInDocument()); refRect.setHeight(page.height()); break; case KoShapeAnchor::VPageContent: refRect.setY(page.contentRect().y()); refRect.setHeight(page.contentRect().height()); break; default: break; } QPointF newPos = shape->position() + delta; switch (anchor->horizontalPos()) { case KoShapeAnchor::HLeft: newPos.setX(refRect.x()); break; case KoShapeAnchor::HCenter: newPos.setX(refRect.x() + (refRect.width() - shape->size().width()) / 2); break; case KoShapeAnchor::HRight: newPos.setX(refRect.right() - shape->size().width()); break; default: break; } switch (anchor->verticalPos()) { case KoShapeAnchor::VTop: newPos.setY(refRect.y()); break; case KoShapeAnchor::VMiddle: newPos.setY(refRect.y() + (refRect.height() - shape->size().height()) / 2); break; case KoShapeAnchor::VBottom: newPos.setY(refRect.bottom() - shape->size().height()); break; default: break; } delta = newPos - shape->position(); } bool KWFrameLayout::shouldHaveHeaderOrFooter(int pageNumber, bool header, Words::TextFrameSetType *origin) { KWPage page = m_pageManager->page(pageNumber); Q_ASSERT(page.isValid()); KWPageStyle pagestyle = page.pageStyle(); Words::HeaderFooterType type = header ? pagestyle.headerPolicy() : pagestyle.footerPolicy(); switch (type) { case Words::HFTypeNone: return false; case Words::HFTypeEvenOdd: if (header) *origin = pageNumber % 2 == 0 ? Words::EvenPagesHeaderTextFrameSet : Words::OddPagesHeaderTextFrameSet; else *origin = pageNumber % 2 == 0 ? Words::EvenPagesFooterTextFrameSet : Words::OddPagesFooterTextFrameSet; break; case Words::HFTypeUniform: *origin = header ? Words::OddPagesHeaderTextFrameSet : Words::OddPagesFooterTextFrameSet; break; } return true; } QList KWFrameLayout::sequencedShapesOnPage(int pageNumber) const { KWPage page = m_pageManager->page(pageNumber); Q_ASSERT(page.isValid()); return sequencedShapesOnPage(page.rect()); } QList KWFrameLayout::sequencedShapesOnPage(const QRectF &page) const { // hopefully replaced with a tree QList answer; foreach (KWFrameSet *fs, m_frameSets) { foreach (KoShape *shape, fs->shapes()) { // use TopLeftCorner as main,header,footer are not rotated, also see bug 275288 if (page.contains(shape->absolutePosition(KoFlake::TopLeftCorner))) answer.append(shape); } } return answer; } KWTextFrameSet *KWFrameLayout::getOrCreate(Words::TextFrameSetType type, const KWPage &page) { Q_ASSERT(page.isValid()); setup(); FrameSets frameSets = m_pageStyles.value(page.pageStyle()); KWTextFrameSet **answer = 0; switch (type) { case Words::OddPagesHeaderTextFrameSet: answer = &frameSets.oddHeaders; break; case Words::EvenPagesHeaderTextFrameSet: answer = &frameSets.evenHeaders; break; case Words::OddPagesFooterTextFrameSet: answer = &frameSets.oddFooters; break; case Words::EvenPagesFooterTextFrameSet: answer = &frameSets.evenFooters; break; case Words::MainTextFrameSet: answer = &m_maintext; break; default: Q_ASSERT(false); // we should never get asked for 'other' } Q_ASSERT(answer); // The frameset wasn't created yet what can happen if for example a file is // loaded that does not exist or just does not create the required framesets. if (*answer == 0) { KWTextFrameSet *newFS = new KWTextFrameSet(m_document, type); *answer = newFS; if (type != Words::MainTextFrameSet) { newFS->setPageStyle(page.pageStyle()); m_pageStyles.insert(page.pageStyle(), frameSets); } emit newFrameSet(newFS); Q_ASSERT(m_frameSets.contains(newFS)); // the emit should have made that so :) } return *answer; } void KWFrameLayout::setup() { // When framesets have been removed things needs to be updated if (m_setup && ((m_maintext && !m_frameSets.contains(m_maintext)) || (m_backgroundFrameSet && !m_frameSets.contains(m_backgroundFrameSet)))) { m_setup = false; } if (m_setup) { return; } KWTextFrameSet *oldMainText = m_maintext; m_maintext = 0; m_backgroundFrameSet = 0; m_pageStyles.clear(); foreach (KWFrameSet *fs, m_frameSets) { if (fs->type() == Words::BackgroundFrameSet) { m_backgroundFrameSet = fs; } else if (fs->type() == Words::TextFrameSet) { KWTextFrameSet *tfs = static_cast(fs); FrameSets frameSets = m_pageStyles.value(tfs->pageStyle()); switch (tfs->textFrameSetType()) { case Words::OddPagesHeaderTextFrameSet: frameSets.oddHeaders = tfs; break; case Words::EvenPagesHeaderTextFrameSet: frameSets.evenHeaders = tfs; break; case Words::OddPagesFooterTextFrameSet: frameSets.oddFooters = tfs; break; case Words::EvenPagesFooterTextFrameSet: frameSets.evenFooters = tfs; break; case Words::MainTextFrameSet: Q_ASSERT(m_maintext == 0); // there can be only one! if (tfs != oldMainText) { oldMainText = 0; disconnect(tfs, SIGNAL(shapeRemoved(KoShape*)), this, SLOT(mainShapeRemoved(KoShape*))); connect(tfs, SIGNAL(shapeRemoved(KoShape*)), this, SLOT(mainShapeRemoved(KoShape*))); } m_maintext = tfs; default: ;// ignore } if (tfs->pageStyle().isValid()) m_pageStyles.insert(tfs->pageStyle(), frameSets); } } m_setup = true; } KoShape *KWFrameLayout::createTextShape(const KWPage &page) { debugWords << "pageNumber=" << page.pageNumber(); Q_ASSERT(page.isValid()); KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(TextShape_SHAPEID); if (!factory) return 0; KoDocumentResourceManager *rm = 0; if (m_document) rm = m_document->resourceManager(); KoShape *shape = factory->createDefaultShape(rm); //Q_ASSERT(shape); return shape; } KoShape *KWFrameLayout::sequencedShapeOn(KWFrameSet *fs, int pageNumber) const { KWPage page = m_pageManager->page(pageNumber); Q_ASSERT(page.isValid()); foreach (KoShape *shape, sequencedShapesOnPage(page.rect())) { if (KWFrameSet::from(shape) == fs) return shape; } return 0; } QList KWFrameLayout::sequencedShapesOn(KWFrameSet *fs, int pageNumber) const { KWPage page = m_pageManager->page(pageNumber); Q_ASSERT(page.isValid()); QList shapes; foreach (KoShape *shape, sequencedShapesOnPage(page.rect())) { if (KWFrameSet::from(shape) == fs) shapes.append(shape); } return shapes; } void KWFrameLayout::cleanFrameSet(KWTextFrameSet *fs) { debugWords << "frameSet=" << fs << "shapeCount=" << (fs ? fs->shapeCount() : 0); if (fs == 0) return; if (fs->shapeCount() == 0) return; foreach (KWFrame *frame, fs->frames()) { fs->removeShape(frame->shape()); delete frame->shape(); } } KWFrame *KWFrameLayout::createCopyFrame(KWFrameSet *fs, const KWPage &page) { Q_ASSERT(page.isValid()); debugWords << "frameSet=" << fs << "pageNumber=" << page.pageNumber() << "shapeCount=" << fs->shapeCount(); if (fs->shapeCount() == 0) { // special case for the headers. Just return a new textframe. KWTextFrameSet *tfs = dynamic_cast(fs); Q_ASSERT(tfs); // an empty, non-text frameset asking for a copy? Thats a bug. KoShape *shape = createTextShape(page); shape->setPosition(QPointF(10.0, page.offsetInDocument()+10.0)); shape->setSize(QSize(20, 10)); KWFrame *frame = new KWFrame(shape, tfs); return frame; } KoShape *orig = 0; //Lets find the last non-copy frame in the frameset for(int i = fs->shapeCount() - 1; i >= 0; --i) { KoShape *candidate = fs->shapes()[i]; if (!dynamic_cast(candidate)) { orig = candidate; break; } } Q_ASSERT(orig); // can't have a frameset with only copy frames. KWCopyShape *shape = new KWCopyShape(orig, m_pageManager); shape->setPosition(QPointF(0, page.offsetInDocument())); KWFrame *frame = new KWFrame(shape, fs); return frame; } KWTextFrameSet *KWFrameLayout::mainFrameSet() const { const_cast(this)->setup(); return m_maintext; } QList KWFrameLayout::getFrameSets(const KWPageStyle &pageStyle) const { FrameSets frameSets = m_pageStyles.value(pageStyle); QList result; result.append(m_maintext); result.append(frameSets.oddHeaders); result.append(frameSets.evenHeaders); result.append(frameSets.oddFooters); result.append(frameSets.evenFooters); result.append(frameSets.pageBackground); return result; } KWTextFrameSet* KWFrameLayout::getFrameSet(Words::TextFrameSetType type, const KWPageStyle &pageStyle) const { FrameSets frameSets = m_pageStyles.value(pageStyle); switch (type) { case Words::OddPagesHeaderTextFrameSet: return frameSets.oddHeaders; case Words::EvenPagesHeaderTextFrameSet: return frameSets.evenHeaders; case Words::OddPagesFooterTextFrameSet: return frameSets.oddFooters; case Words::EvenPagesFooterTextFrameSet: return frameSets.evenFooters; case Words::MainTextFrameSet: return m_maintext; default: break; } return 0; } void KWFrameLayout::mainShapeRemoved(KoShape *shape) { // if a main-shape is removed we should remove all other auto-generated shapes on that page to allow // the page to be removed totally. Besides; we don't want to have a header when there is no main-shape on a page :) KWPage page = m_pageManager->page(shape); if (!page.isValid()) return; debugWords << "shape=" << shape << "pageNumber=" << page.pageNumber(); QList shapesToDelete; foreach (KWFrameSet *fs, m_frameSets) { KWTextFrameSet *tfs = dynamic_cast (fs); if (!tfs || !Words::isAutoGenerated(tfs)) continue; const bool isMainFs = fs == m_maintext; foreach (KoShape *s, fs->shapes()) { if (s == shape) continue; if (page == m_pageManager->page(s)) { if (isMainFs) // there is another shape of the main text frameset on this page. return; shapesToDelete << s; } } } // delete them! foreach (KoShape *s, shapesToDelete) { // first remove if from the frameset to make sure the doc gets a signal and removes the page if needed //frame->frameSet()->removeFrame(frame); // then actually delete the frame itself. delete s; } }