diff --git a/libs/basicflakes/tools/KoPencilTool.cpp b/libs/basicflakes/tools/KoPencilTool.cpp index 4636e9c56b..e928c5da3c 100644 --- a/libs/basicflakes/tools/KoPencilTool.cpp +++ b/libs/basicflakes/tools/KoPencilTool.cpp @@ -1,559 +1,559 @@ /* This file is part of the KDE project * Copyright (C) 2007,2009,2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPencilTool.h" #include "KoCurveFit.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 "KoCreatePathTool_p.h" #include "kis_double_parse_spin_box.h" KoPencilTool::KoPencilTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_mode(ModeCurve) , m_optimizeRaw(false) , m_optimizeCurve(false) , m_combineAngle(15.0) , m_fittingError(5.0) , m_close(false) , m_shape(0) , m_existingStartPoint(0) , m_existingEndPoint(0) , m_hoveredPoint(0) { } KoPencilTool::~KoPencilTool() { } void KoPencilTool::paint(QPainter &painter, const KoViewConverter &converter) { if (m_shape) { painter.save(); painter.setTransform(m_shape->absoluteTransformation(&converter) * painter.transform()); painter.save(); KoShapePaintingContext paintContext; //FIXME m_shape->paint(painter, converter, paintContext); painter.restore(); if (m_shape->stroke()) { painter.save(); m_shape->stroke()->paint(m_shape, painter, converter); painter.restore(); } painter.restore(); } if (m_hoveredPoint) { painter.save(); painter.setTransform(m_hoveredPoint->parent()->absoluteTransformation(&converter), true); KoShape::applyConversion(painter, converter); - painter.setPen(Qt::blue); //TODO make configurable + painter.setPen(QPen(Qt::blue, 0)); //TODO make configurable painter.setBrush(Qt::white); //TODO make configurable m_hoveredPoint->paint(painter, handleRadius(), KoPathPoint::Node); painter.restore(); } } void KoPencilTool::repaintDecorations() { } void KoPencilTool::mousePressEvent(KoPointerEvent *event) { if (! m_shape) { m_shape = new KoPathShape(); m_shape->setShapeId(KoPathShapeId); m_shape->setStroke(createStroke()); m_points.clear(); QPointF point = event->point; m_existingStartPoint = endPointAtPosition(point); if (m_existingStartPoint) point = m_existingStartPoint->parent()->shapeToDocument(m_existingStartPoint->point()); addPoint(point); } } void KoPencilTool::mouseMoveEvent(KoPointerEvent *event) { if (event->buttons() & Qt::LeftButton) addPoint(event->point); KoPathPoint * endPoint = endPointAtPosition(event->point); if (m_hoveredPoint != endPoint) { if (m_hoveredPoint) { QPointF nodePos = m_hoveredPoint->parent()->shapeToDocument(m_hoveredPoint->point()); canvas()->updateCanvas(handlePaintRect(nodePos)); } m_hoveredPoint = endPoint; if (m_hoveredPoint) { QPointF nodePos = m_hoveredPoint->parent()->shapeToDocument(m_hoveredPoint->point()); canvas()->updateCanvas(handlePaintRect(nodePos)); } } } void KoPencilTool::mouseReleaseEvent(KoPointerEvent *event) { if (! m_shape) return; QPointF point = event->point; m_existingEndPoint = endPointAtPosition(point); if (m_existingEndPoint) point = m_existingEndPoint->parent()->shapeToDocument(m_existingEndPoint->point()); addPoint(point); finish(event->modifiers() & Qt::ShiftModifier); m_existingStartPoint = 0; m_existingEndPoint = 0; m_hoveredPoint = 0; // the original path may be different from the one added canvas()->updateCanvas(m_shape->boundingRect()); delete m_shape; m_shape = 0; m_points.clear(); } void KoPencilTool::keyPressEvent(QKeyEvent *event) { if (m_shape) { event->accept(); } else { event->ignore(); } } void KoPencilTool::activate(ToolActivation, const QSet &) { m_points.clear(); m_close = false; useCursor(Qt::ArrowCursor); } void KoPencilTool::deactivate() { m_points.clear(); delete m_shape; m_shape = 0; m_existingStartPoint = 0; m_existingEndPoint = 0; m_hoveredPoint = 0; } void KoPencilTool::addPoint(const QPointF & point) { if (! m_shape) return; // do a moveTo for the first point added if (m_points.empty()) m_shape->moveTo(point); // do not allow coincident points else if (point != m_points.last()) m_shape->lineTo(point); else return; m_points.append(point); canvas()->updateCanvas(m_shape->boundingRect()); } qreal KoPencilTool::lineAngle(const QPointF &p1, const QPointF &p2) { qreal angle = atan2(p2.y() - p1.y(), p2.x() - p1.x()); if (angle < 0.0) angle += 2 * M_PI; return angle * 180.0 / M_PI; } void KoPencilTool::finish(bool closePath) { if (m_points.count() < 2) return; KoPathShape * path = 0; QList complete; QList *points = &m_points; if (m_mode == ModeStraight || m_optimizeRaw || m_optimizeCurve) { float combineAngle; if (m_mode == ModeStraight) combineAngle = m_combineAngle; else combineAngle = 0.50f; //Add the first two points complete.append(m_points[0]); complete.append(m_points[1]); //Now we need to get the angle of the first line float lastAngle = lineAngle(complete[0], complete[1]); uint pointCount = m_points.count(); for (uint i = 2; i < pointCount; ++i) { float angle = lineAngle(complete.last(), m_points[i]); if (qAbs(angle - lastAngle) < combineAngle) complete.removeLast(); complete.append(m_points[i]); lastAngle = angle; } m_points.clear(); points = &complete; } switch (m_mode) { case ModeCurve: { path = bezierFit(*points, m_fittingError); } break; case ModeStraight: case ModeRaw: { path = new KoPathShape(); uint pointCount = points->count(); path->moveTo(points->at(0)); for (uint i = 1; i < pointCount; ++i) path->lineTo(points->at(i)); } break; } if (! path) return; path->setShapeId(KoPathShapeId); path->setStroke(createStroke()); addPathShape(path, closePath); } QList > KoPencilTool::createOptionWidgets() { QList > widgets; QWidget *optionWidget = new QWidget(); QVBoxLayout * layout = new QVBoxLayout(optionWidget); QHBoxLayout *modeLayout = new QHBoxLayout; modeLayout->setSpacing(3); QLabel *modeLabel = new QLabel(i18n("Precision:"), optionWidget); QComboBox * modeBox = new QComboBox(optionWidget); modeBox->addItem(i18nc("The raw line data", "Raw")); modeBox->addItem(i18n("Curve")); modeBox->addItem(i18n("Straight")); modeLayout->addWidget(modeLabel); modeLayout->addWidget(modeBox, 1); layout->addLayout(modeLayout); QStackedWidget * stackedWidget = new QStackedWidget(optionWidget); QWidget * rawBox = new QWidget(stackedWidget); QVBoxLayout * rawLayout = new QVBoxLayout(rawBox); QCheckBox * optimizeRaw = new QCheckBox(i18n("Optimize"), rawBox); rawLayout->addWidget(optimizeRaw); rawLayout->setContentsMargins(0, 0, 0, 0); QWidget * curveBox = new QWidget(stackedWidget); QHBoxLayout * curveLayout = new QHBoxLayout(curveBox); QCheckBox * optimizeCurve = new QCheckBox(i18n("Optimize"), curveBox); QDoubleSpinBox * fittingError = new KisDoubleParseSpinBox(curveBox); fittingError->setValue(0.50); fittingError->setMaximum(400.0); fittingError->setMinimum(0.0); fittingError->setSingleStep(m_fittingError); fittingError->setToolTip(i18n("Exactness:")); curveLayout->addWidget(optimizeCurve); curveLayout->addWidget(fittingError); curveLayout->setContentsMargins(0, 0, 0, 0); QWidget *straightBox = new QWidget(stackedWidget); QVBoxLayout *straightLayout = new QVBoxLayout(straightBox); QDoubleSpinBox *combineAngle = new KisDoubleParseSpinBox(straightBox); combineAngle->setValue(0.50); combineAngle->setMaximum(360.0); combineAngle->setMinimum(0.0); combineAngle->setSingleStep(m_combineAngle); combineAngle->setSuffix(" deg"); // QT5TODO //combineAngle->setLabel(i18n("Combine angle:"), Qt::AlignLeft | Qt::AlignVCenter); straightLayout->addWidget(combineAngle); straightLayout->setContentsMargins(0, 0, 0, 0); stackedWidget->addWidget(rawBox); stackedWidget->addWidget(curveBox); stackedWidget->addWidget(straightBox); layout->addWidget(stackedWidget); layout->addStretch(1); connect(modeBox, SIGNAL(activated(int)), stackedWidget, SLOT(setCurrentIndex(int))); connect(modeBox, SIGNAL(activated(int)), this, SLOT(selectMode(int))); connect(optimizeRaw, SIGNAL(stateChanged(int)), this, SLOT(setOptimize(int))); connect(optimizeCurve, SIGNAL(stateChanged(int)), this, SLOT(setOptimize(int))); connect(fittingError, SIGNAL(valueChanged(double)), this, SLOT(setDelta(double))); connect(combineAngle, SIGNAL(valueChanged(double)), this, SLOT(setDelta(double))); modeBox->setCurrentIndex(m_mode); stackedWidget->setCurrentIndex(m_mode); optionWidget->setObjectName(i18n("Pencil")); optionWidget->setWindowTitle(i18n("Pencil")); widgets.append(optionWidget); m_strokeWidget = new KoStrokeConfigWidget(0); m_strokeWidget->setWindowTitle(i18n("Line")); m_strokeWidget->setCanvas(canvas()); widgets.append(m_strokeWidget); return widgets; } void KoPencilTool::addPathShape(KoPathShape* path, bool closePath) { KoShape * startShape = 0; KoShape * endShape = 0; if (closePath) { path->close(); path->normalize(); } else { path->normalize(); if (connectPaths(path, m_existingStartPoint, m_existingEndPoint)) { if (m_existingStartPoint) startShape = m_existingStartPoint->parent(); if (m_existingEndPoint && m_existingEndPoint != m_existingStartPoint) endShape = m_existingEndPoint->parent(); } } KUndo2Command * cmd = canvas()->shapeController()->addShape(path); if (cmd) { KoSelection *selection = canvas()->shapeManager()->selection(); selection->deselectAll(); selection->select(path); if (startShape) canvas()->shapeController()->removeShape(startShape, cmd); if (endShape && startShape != endShape) canvas()->shapeController()->removeShape(endShape, cmd); canvas()->addCommand(cmd); } else { canvas()->updateCanvas(path->boundingRect()); delete path; } } void KoPencilTool::selectMode(int mode) { m_mode = static_cast(mode); } void KoPencilTool::setOptimize(int state) { if (m_mode == ModeRaw) m_optimizeRaw = state == Qt::Checked ? true : false; else m_optimizeCurve = state == Qt::Checked ? true : false; } void KoPencilTool::setDelta(double delta) { if (m_mode == ModeCurve) m_fittingError = delta; else if (m_mode == ModeStraight) m_combineAngle = delta; } KoShapeStroke* KoPencilTool::createStroke() { KoShapeStroke *stroke = 0; if (m_strokeWidget) { stroke = m_strokeWidget->createShapeStroke(); } return stroke; } KoPathPoint* KoPencilTool::endPointAtPosition(const QPointF &position) { QRectF roi = handleGrabRect(position); QList shapes = canvas()->shapeManager()->shapesAt(roi); KoPathPoint * nearestPoint = 0; qreal minDistance = HUGE_VAL; qreal maxDistance = canvas()->viewConverter()->viewToDocumentX(grabSensitivity()); Q_FOREACH(KoShape * shape, shapes) { KoPathShape * path = dynamic_cast(shape); if (!path) continue; KoParameterShape *paramShape = dynamic_cast(shape); 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; } bool KoPencilTool::connectPaths(KoPathShape *pathShape, KoPathPoint *pointAtStart, KoPathPoint *pointAtEnd) { // at least one point must be valid if (!pointAtStart && !pointAtEnd) return false; // do not allow connecting to the same point twice if (pointAtStart == pointAtEnd) pointAtEnd = 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); KoPathShape * startShape = pointAtStart ? pointAtStart->parent() : 0; KoPathShape * endShape = pointAtEnd ? pointAtEnd->parent() : 0; // combine with the path we hit on start KoPathPointIndex startIndex(-1, -1); if (pointAtStart) { startIndex = startShape->pathPointIndex(pointAtStart); pathShape->combine(startShape); pathShape->moveSubpath(0, pathShape->subpathCount() - 1); } // combine with the path we hit on finish KoPathPointIndex endIndex(-1, -1); if (pointAtEnd) { endIndex = endShape->pathPointIndex(pointAtEnd); 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; } qreal KoPencilTool::getFittingError() { return this->m_fittingError; } void KoPencilTool::setFittingError(qreal fittingError) { this->m_fittingError = fittingError; } diff --git a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp index 15096e27f2..edb24195b7 100644 --- a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp +++ b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp @@ -1,384 +1,384 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007, 2009 Thomas Zander * Copyright (C) 2006 Thorsten Zachmann * Copyright (C) 2007-2010 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCanvasControllerWidgetViewport_p.h" #include #include #include #include #include #include #include #include "KoShape.h" #include "KoShape_p.h" #include "KoShapeFactoryBase.h" // for the SHAPE mimetypes #include "KoShapeRegistry.h" #include "KoShapeController.h" #include "KoShapeManager.h" #include "KoSelection.h" #include "KoCanvasBase.h" #include "KoShapeLayer.h" #include "KoShapePaste.h" #include "KoShapePaintingContext.h" #include "KoToolProxy.h" #include "KoCanvasControllerWidget.h" #include "KoViewConverter.h" // ********** Viewport ********** Viewport::Viewport(KoCanvasControllerWidget *parent) : QWidget(parent) , m_draggedShape(0) , m_drawShadow(false) , m_canvas(0) , m_documentOffset(QPoint(0, 0)) , m_margin(0) { setAutoFillBackground(true); setAcceptDrops(true); setMouseTracking(true); m_parent = parent; } void Viewport::setCanvas(QWidget *canvas) { if (m_canvas) { m_canvas->hide(); delete m_canvas; } m_canvas = canvas; if (!canvas) return; m_canvas->setParent(this); m_canvas->show(); if (!m_canvas->minimumSize().isNull()) { m_documentSize = m_canvas->minimumSize(); } resetLayout(); } void Viewport::setDocumentSize(const QSize &size) { m_documentSize = size; resetLayout(); } void Viewport::documentOffsetMoved(const QPoint &pt) { m_documentOffset = pt; resetLayout(); } void Viewport::setDrawShadow(bool drawShadow) { m_drawShadow = drawShadow; } void Viewport::handleDragEnterEvent(QDragEnterEvent *event) { // if not a canvas set then ignore this, makes it possible to assume // we have a canvas in all the support methods. if (!(m_parent->canvas() && m_parent->canvas()->canvasWidget())) return; // only allow dropping when active layer is editable KoSelection *selection = m_parent->canvas()->shapeManager()->selection(); KoShapeLayer *activeLayer = selection->activeLayer(); if (activeLayer && (!activeLayer->isEditable() || activeLayer->isGeometryProtected())) return; const QMimeData *data = event->mimeData(); if (data->hasFormat(SHAPETEMPLATE_MIMETYPE) || data->hasFormat(SHAPEID_MIMETYPE)) { QByteArray itemData; bool isTemplate = true; if (data->hasFormat(SHAPETEMPLATE_MIMETYPE)) itemData = data->data(SHAPETEMPLATE_MIMETYPE); else { isTemplate = false; itemData = data->data(SHAPEID_MIMETYPE); } QDataStream dataStream(&itemData, QIODevice::ReadOnly); QString id; dataStream >> id; QString properties; if (isTemplate) dataStream >> properties; // and finally, there is a point. QPointF offset; dataStream >> offset; // The rest of this method is mostly a copy paste from the KoCreateShapeStrategy // So, lets remove this again when Zagge adds his new class that does this kind of thing. (KoLoadSave) KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(id); if (! factory) { warnFlake << "Application requested a shape that is not registered '" << id << "', Ignoring"; event->ignore(); return; } event->setDropAction(Qt::CopyAction); event->accept(); if (isTemplate) { KoProperties props; props.load(properties); m_draggedShape = factory->createShape(&props, m_parent->canvas()->shapeController()->resourceManager()); } else m_draggedShape = factory->createDefaultShape(m_parent->canvas()->shapeController()->resourceManager()); Q_ASSERT(m_draggedShape); if (!m_draggedShape) return; if (m_draggedShape->shapeId().isEmpty()) m_draggedShape->setShapeId(factory->id()); m_draggedShape->setZIndex(KoShapePrivate::MaxZIndex); m_draggedShape->setAbsolutePosition(correctPosition(event->pos())); m_parent->canvas()->shapeManager()->addShape(m_draggedShape); } else if (data->hasFormat(KoOdf::mimeType(KoOdf::Text))) { KoShapeManager *sm = m_parent->canvas()->shapeManager(); KoShapePaste paste(m_parent->canvas(), sm->selection()->activeLayer()); if (paste.paste(KoOdf::Text, data)) { QList shapes = paste.pastedShapes(); if (shapes.count() == 1) { m_draggedShape = shapes.first(); m_draggedShape->setZIndex(KoShapePrivate::MaxZIndex); event->setDropAction(Qt::CopyAction); } event->accept(); } } else { event->ignore(); } } void Viewport::handleDropEvent(QDropEvent *event) { if (!m_draggedShape) { m_parent->canvas()->toolProxy()->dropEvent(event, correctPosition(event->pos())); return; } repaint(m_draggedShape); m_parent->canvas()->shapeManager()->remove(m_draggedShape); // remove it to not interfere with z-index calc. m_draggedShape->setPosition(QPointF(0, 0)); // always save position. QPointF newPos = correctPosition(event->pos()); m_parent->canvas()->clipToDocument(m_draggedShape, newPos); // ensure the shape is dropped inside the document. m_draggedShape->setAbsolutePosition(newPos); KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape); if (cmd) { m_parent->canvas()->addCommand(cmd); KoSelection *selection = m_parent->canvas()->shapeManager()->selection(); // repaint selection before selecting newly create shape Q_FOREACH (KoShape * shape, selection->selectedShapes()) shape->update(); selection->deselectAll(); selection->select(m_draggedShape); } else delete m_draggedShape; m_draggedShape = 0; } QPointF Viewport::correctPosition(const QPoint &point) const { QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. QPoint correctedPos(point.x() - canvasWidget->x(), point.y() - canvasWidget->y()); correctedPos += m_documentOffset; return m_parent->canvas()->viewToDocument(correctedPos); } void Viewport::handleDragMoveEvent(QDragMoveEvent *event) { if (!m_draggedShape) { m_parent->canvas()->toolProxy()->dragMoveEvent(event, correctPosition(event->pos())); return; } m_draggedShape->update(); repaint(m_draggedShape); m_draggedShape->setAbsolutePosition(correctPosition(event->pos())); m_draggedShape->update(); repaint(m_draggedShape); } void Viewport::repaint(KoShape *shape) { QRect rect = m_parent->canvas()->viewConverter()->documentToView(shape->boundingRect()).toRect(); QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. rect.moveLeft(rect.left() + canvasWidget->x() - m_documentOffset.x()); rect.moveTop(rect.top() + canvasWidget->y() - m_documentOffset.y()); rect.adjust(-2, -2, 2, 2); // adjust for antialias update(rect); } void Viewport::handleDragLeaveEvent(QDragLeaveEvent *event) { if (m_draggedShape) { repaint(m_draggedShape); m_parent->canvas()->shapeManager()->remove(m_draggedShape); delete m_draggedShape; m_draggedShape = 0; } else { m_parent->canvas()->toolProxy()->dragLeaveEvent(event); } } void Viewport::handlePaintEvent(QPainter &painter, QPaintEvent *event) { Q_UNUSED(event); // Draw the shadow around the canvas. if (m_parent->canvas() && m_parent->canvas()->canvasWidget() && m_drawShadow) { QWidget *canvas = m_parent->canvas()->canvasWidget(); - painter.setPen(Qt::black); + painter.setPen(QPen(Qt::black, 0)); QRect rect(canvas->x(), canvas->y(), canvas->width(), canvas->height()); rect.adjust(-1, -1, 0, 0); painter.drawRect(rect); painter.drawLine(rect.right() + 2, rect.top() + 2, rect.right() + 2, rect.bottom() + 2); painter.drawLine(rect.left() + 2, rect.bottom() + 2, rect.right() + 2, rect.bottom() + 2); } if (m_draggedShape) { const KoViewConverter *vc = m_parent->canvas()->viewConverter(); painter.save(); QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. painter.translate(canvasWidget->x() - m_documentOffset.x(), canvasWidget->y() - m_documentOffset.y()); QPointF offset = vc->documentToView(m_draggedShape->position()); painter.setOpacity(0.6); painter.translate(offset.x(), offset.y()); painter.setRenderHint(QPainter::Antialiasing); KoShapePaintingContext paintContext; //FIXME m_draggedShape->paint(painter, *vc, paintContext); painter.restore(); } } void Viewport::resetLayout() { // Determine the area we have to show QRect viewRect(m_documentOffset, size()); const int viewH = viewRect.height(); const int viewW = viewRect.width(); const int docH = m_documentSize.height(); const int docW = m_documentSize.width(); int moveX = 0; int moveY = 0; int resizeW = viewW; int resizeH = viewH; // debugFlake <<"viewH:" << viewH << endl // << "docH: " << docH << endl // << "viewW: " << viewW << endl // << "docW: " << docW << endl; if (viewH == docH && viewW == docW) { // Do nothing resizeW = docW; resizeH = docH; } else if (viewH > docH && viewW > docW) { // Show entire canvas centered moveX = (viewW - docW) / 2; moveY = (viewH - docH) / 2; resizeW = docW; resizeH = docH; } else if (viewW > docW) { // Center canvas horizontally moveX = (viewW - docW) / 2; resizeW = docW; int marginTop = m_margin - m_documentOffset.y(); int marginBottom = viewH - (m_documentSize.height() - m_documentOffset.y()); if (marginTop > 0) moveY = marginTop; if (marginTop > 0) resizeH = viewH - marginTop; if (marginBottom > 0) resizeH = viewH - marginBottom; } else if (viewH > docH) { // Center canvas vertically moveY = (viewH - docH) / 2; resizeH = docH; int marginLeft = m_margin - m_documentOffset.x(); int marginRight = viewW - (m_documentSize.width() - m_documentOffset.x()); if (marginLeft > 0) moveX = marginLeft; if (marginLeft > 0) resizeW = viewW - marginLeft; if (marginRight > 0) resizeW = viewW - marginRight; } else { // Take care of the margin around the canvas int marginTop = m_margin - m_documentOffset.y(); int marginLeft = m_margin - m_documentOffset.x(); int marginRight = viewW - (m_documentSize.width() - m_documentOffset.x()); int marginBottom = viewH - (m_documentSize.height() - m_documentOffset.y()); if (marginTop > 0) moveY = marginTop; if (marginLeft > 0) moveX = marginLeft; if (marginTop > 0) resizeH = viewH - marginTop; if (marginLeft > 0) resizeW = viewW - marginLeft; if (marginRight > 0) resizeW = viewW - marginRight; if (marginBottom > 0) resizeH = viewH - marginBottom; } if (m_parent->canvasMode() == KoCanvasController::AlignTop) { // have up to m_margin pixels at top. moveY = qMin(m_margin, moveY); } if (m_canvas) { QRect geom; if (m_parent->canvasMode() == KoCanvasController::Infinite) geom = QRect(0, 0, viewW, viewH); else geom = QRect(moveX, moveY, resizeW, resizeH); if (m_canvas->geometry() != geom) { m_canvas->setGeometry(geom); m_canvas->update(); } } if (m_drawShadow) { update(); } emit sizeChanged(); #if 0 debugFlake <<"View port geom:" << geometry(); if (m_canvas) debugFlake <<"Canvas widget geom:" << m_canvas->geometry(); #endif } diff --git a/libs/flake/KoImageData.cpp b/libs/flake/KoImageData.cpp index d380ae3806..5882360d8c 100644 --- a/libs/flake/KoImageData.cpp +++ b/libs/flake/KoImageData.cpp @@ -1,374 +1,374 @@ /* This file is part of the KDE project * Copyright (C) 2007, 2009 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 2008 C. Boemann * Copyright (C) 2008 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoImageData.h" #include "KoImageData_p.h" #include "KoImageCollection.h" #include #include #include #include #include #include #include #include /// the maximum amount of bytes the image can be while we store it in memory instead of /// spooling it to disk in a temp-file. #define MAX_MEMORY_IMAGESIZE 90000 KoImageData::KoImageData() : d(0) { } KoImageData::KoImageData(const KoImageData &imageData) : KoShapeUserData(), d(imageData.d) { if (d) d->refCount.ref(); } KoImageData::KoImageData(KoImageDataPrivate *priv) : d(priv) { d->refCount.ref(); } KoImageData::~KoImageData() { if (d && !d->refCount.deref()) delete d; } QPixmap KoImageData::pixmap(const QSize &size) { if (!d) return QPixmap(); QSize wantedSize = size; if (! wantedSize.isValid()) { if (d->pixmap.isNull()) // we have a problem, Houston.. wantedSize = QSize(100, 100); else wantedSize = d->pixmap.size(); } if (d->pixmap.isNull() || d->pixmap.size() != wantedSize) { switch (d->dataStoreState) { case KoImageDataPrivate::StateEmpty: { #if 0 // this is not possible as it gets called during the paint method // and will crash. Therefore create a tmp pixmap and return it. d->pixmap = QPixmap(1, 1); QPainter p(&d->pixmap); - p.setPen(QPen(Qt::gray)); + p.setPen(QPen(Qt::gray, 0)); p.drawPoint(0, 0); p.end(); break; #endif QPixmap tmp(1, 1); tmp.fill(Qt::gray); return tmp; } case KoImageDataPrivate::StateNotLoaded: image(); // forces load // fall through case KoImageDataPrivate::StateImageLoaded: case KoImageDataPrivate::StateImageOnly: if (!d->image.isNull()) { // create pixmap from image. // this is the highest quality and lowest memory usage way of doing the conversion. d->pixmap = QPixmap::fromImage(d->image.scaled(wantedSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } } if (d->dataStoreState == KoImageDataPrivate::StateImageLoaded) { if (d->cleanCacheTimer.isActive()) d->cleanCacheTimer.stop(); // schedule an auto-unload of the big QImage in a second. d->cleanCacheTimer.start(); } } return d->pixmap; } bool KoImageData::hasCachedPixmap() const { return d && !d->pixmap.isNull(); } QSizeF KoImageData::imageSize() { if (!d->imageSize.isValid()) { // The imagesize have not yet been calculated if (image().isNull()) // auto loads the image return QSizeF(100, 100); if (d->image.dotsPerMeterX()) d->imageSize.setWidth(DM_TO_POINT(d->image.width() / (qreal) d->image.dotsPerMeterX() * 10.0)); else d->imageSize.setWidth(d->image.width() / 72.0); if (d->image.dotsPerMeterY()) d->imageSize.setHeight(DM_TO_POINT(d->image.height() / (qreal) d->image.dotsPerMeterY() * 10.0)); else d->imageSize.setHeight(d->image.height() / 72.0); } return d->imageSize; } QImage KoImageData::image() const { if (d->dataStoreState == KoImageDataPrivate::StateNotLoaded) { // load image if (d->temporaryFile) { bool r = d->temporaryFile->open(); if (!r) { d->errorCode = OpenFailed; } else if (d->errorCode == Success && !d->image.load(d->temporaryFile->fileName(), d->suffix.toLatin1())) { d->errorCode = OpenFailed; } d->temporaryFile->close(); } else { if (d->errorCode == Success && !d->image.load(d->imageLocation.toLocalFile())) { d->errorCode = OpenFailed; } } if (d->errorCode == Success) { d->dataStoreState = KoImageDataPrivate::StateImageLoaded; } } return d->image; } bool KoImageData::hasCachedImage() const { return d && !d->image.isNull(); } void KoImageData::setImage(const QImage &image, KoImageCollection *collection) { qint64 oldKey = 0; if (d) { oldKey = d->key; } Q_ASSERT(!image.isNull()); if (collection) { // let the collection first check if it already has one. If it doesn't it'll call this method // again and well go to the other clause KoImageData *other = collection->createImageData(image); this->operator=(*other); delete other; } else { if (d == 0) { d = new KoImageDataPrivate(this); d->refCount.ref(); } delete d->temporaryFile; d->temporaryFile = 0; d->clear(); d->suffix = "png"; // good default for non-lossy storage. if (image.byteCount() > MAX_MEMORY_IMAGESIZE) { // store image QBuffer buffer; buffer.open(QIODevice::WriteOnly); if (!image.save(&buffer, d->suffix.toLatin1())) { warnFlake << "Write temporary file failed"; d->errorCode = StorageFailed; delete d->temporaryFile; d->temporaryFile = 0; return; } buffer.close(); buffer.open(QIODevice::ReadOnly); d->copyToTemporary(buffer); } else { d->image = image; d->dataStoreState = KoImageDataPrivate::StateImageOnly; QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); // use .png for images we get as QImage QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(ba); d->key = KoImageDataPrivate::generateKey(md5.result()); } if (oldKey != 0 && d->collection) { d->collection->update(oldKey, d->key); } } } void KoImageData::setImage(const QString &url, KoStore *store, KoImageCollection *collection) { if (collection) { // Let the collection first check if it already has one. If it // doesn't it'll call this method again and we'll go to the // other clause. KoImageData *other = collection->createImageData(url, store); this->operator=(*other); delete other; } else { if (d == 0) { d = new KoImageDataPrivate(this); d->refCount.ref(); } else { d->clear(); } d->setSuffix(url); if (store->open(url)) { struct Finalizer { ~Finalizer() { store->close(); } KoStore *store; }; Finalizer closer; closer.store = store; KoStoreDevice device(store); const bool lossy = url.endsWith(".jpg", Qt::CaseInsensitive) || url.endsWith(".gif", Qt::CaseInsensitive); if (!lossy && device.size() < MAX_MEMORY_IMAGESIZE) { QByteArray data = device.readAll(); if (d->image.loadFromData(data)) { QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(data); qint64 oldKey = d->key; d->key = KoImageDataPrivate::generateKey(md5.result()); if (oldKey != 0 && d->collection) { d->collection->update(oldKey, d->key); } d->dataStoreState = KoImageDataPrivate::StateImageOnly; return; } } if (!device.open(QIODevice::ReadOnly)) { warnFlake << "open file from store " << url << "failed"; d->errorCode = OpenFailed; return; } d->copyToTemporary(device); } else { warnFlake << "Find file in store " << url << "failed"; d->errorCode = OpenFailed; return; } } } void KoImageData::setImage(const QByteArray &imageData, KoImageCollection *collection) { if (collection) { // let the collection first check if it already has one. If it doesn't it'll call this method // again and we'll go to the other clause KoImageData *other = collection->createImageData(imageData); this->operator=(*other); delete other; } else { if (d == 0) { d = new KoImageDataPrivate(this); d->refCount.ref(); } d->suffix = "png"; // good default for non-lossy storage. if (imageData.size() <= MAX_MEMORY_IMAGESIZE) { QImage image; if (!image.loadFromData(imageData)) { // mark the image as invalid, but keep the data in memory // even if Calligra cannot handle the format, the data should // be retained d->errorCode = OpenFailed; } d->image = image; d->dataStoreState = KoImageDataPrivate::StateImageOnly; } if (imageData.size() > MAX_MEMORY_IMAGESIZE || d->errorCode == OpenFailed) { d->image = QImage(); // store image data QBuffer buffer; buffer.setData(imageData); buffer.open(QIODevice::ReadOnly); d->copyToTemporary(buffer); } QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(imageData); qint64 oldKey = d->key; d->key = KoImageDataPrivate::generateKey(md5.result()); if (oldKey != 0 && d->collection) { d->collection->update(oldKey, d->key); } } } bool KoImageData::isValid() const { return d && d->dataStoreState != KoImageDataPrivate::StateEmpty && d->errorCode == Success; } bool KoImageData::operator==(const KoImageData &other) const { return other.d == d; } KoImageData &KoImageData::operator=(const KoImageData &other) { if (other.d) other.d->refCount.ref(); if (d && !d->refCount.deref()) delete d; d = other.d; return *this; } qint64 KoImageData::key() const { return d->key; } QString KoImageData::suffix() const { return d->suffix; } KoImageData::ErrorCode KoImageData::errorCode() const { return d->errorCode; } bool KoImageData::saveData(QIODevice &device) { return d->saveData(device); } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoImageData.cpp" diff --git a/libs/flake/KoPathShape.cpp b/libs/flake/KoPathShape.cpp index 68dd426bca..7f7e5e567a 100644 --- a/libs/flake/KoPathShape.cpp +++ b/libs/flake/KoPathShape.cpp @@ -1,1740 +1,1740 @@ /* 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 // 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() { Q_FOREACH (KoSubpath *subpath, m_subpaths) { Q_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); + 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); + 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; Q_FOREACH (KoSubpath * subpath, m_subpaths) { KoPathPoint * lastPoint = subpath->first(); bool activeCP = false; Q_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 aproximation 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 Q_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(); Q_FOREACH (KoSubpath* subpath, path->m_subpaths) { KoSubpath *newSubpath = new KoSubpath(); Q_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); Q_FOREACH (KoSubpath* subpath, m_subpaths) { KoPathShape *shape = new KoPathShape(); if (! shape) continue; shape->setStroke(stroke()); shape->setShapeId(shapeId()); KoSubpath *newSubpath = new KoSubpath(); Q_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 consits 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/KoRTree.h b/libs/flake/KoRTree.h index bdf8add1f3..bcc8fa4ba5 100644 --- a/libs/flake/KoRTree.h +++ b/libs/flake/KoRTree.h @@ -1,1090 +1,1090 @@ /* This file is part of the KDE project Copyright (c) 2006 Thorsten Zachmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Based on code from Wolfgang Baer - WBaer@gmx.de */ #ifndef KORTREE_H #define KORTREE_H #include #include #include #include #include #include #include #include // #define CALLIGRA_RTREE_DEBUG #ifdef CALLIGRA_RTREE_DEBUG #include #endif /** * @brief The KoRTree class is a template class that provides a R-tree. * * This class implements a R-tree as described in * "R-TREES. A DYNAMIC INDEX STRUCTURE FOR SPATIAL SEARCHING" by Antomn Guttman * * It only supports 2 dimensional bounding boxes which are repesented by a QRectF. * For node splitting the Quadratic-Cost Algorithm is used as descibed by Guttman. */ template class KoRTree { public: /** * @brief Constructor * * @param capacity the capacity a node can take * @param minimum the minimum filling of a node max 0.5 * capacity */ KoRTree(int capacity, int minimum); /** * @brief Destructor */ virtual ~KoRTree(); /** * @brief Insert data item into the tree * * This will insert a data item into the tree. If necessary the tree will * adjust itself. * * @param data * @param bb */ virtual void insert(const QRectF& bb, const T& data); /** * @brief Remove a data item from the tree * * This removed a data item from the tree. If necessary the tree will * adjust itself. * * @param data */ void remove(const T& data); /** * @brief Find all data items which intersects rect * The items are sorted by insertion time in ascending order. * * @param rect where the objects have to be in * * @return objects intersecting the rect */ virtual QList intersects(const QRectF& rect) const; /** * @brief Find all data item which contain the point * The items are sorted by insertion time in ascending order. * * @param point which should be contained in the objects * * @return objects which contain the point */ QList contains(const QPointF &point) const; /** * @brief Find all data rectangles * The order is NOT guaranteed to be the same as that used by values(). * * @return a list containing all the data rectangles used in the tree */ QList keys() const; /** * @brief Find all data items * The order is NOT guaranteed to be the same as that used by keys(). * * @return a list containing all the data used in the tree */ QList values() const; virtual void clear() { delete m_root; m_root = createLeafNode(m_capacity + 1, 0, 0); m_leafMap.clear(); } #ifdef CALLIGRA_RTREE_DEBUG /** * @brief Paint the tree * * @param p painter which should be used for painting */ void paint(QPainter & p) const; /** * @brief Print the tree using qdebug */ void debug() const; #endif protected: class NonLeafNode; class LeafNode; class Node { public: #ifdef CALLIGRA_RTREE_DEBUG static int nodeIdCnt; #endif Node(int capacity, int level, Node * parent); virtual ~Node() {} virtual void remove(int index); // move node between nodes of the same type from node virtual void move(Node * node, int index) = 0; virtual LeafNode * chooseLeaf(const QRectF& bb) = 0; virtual NonLeafNode * chooseNode(const QRectF& bb, int level) = 0; virtual void intersects(const QRectF& rect, QMap & result) const = 0; virtual void contains(const QPointF & point, QMap & result) const = 0; virtual void keys(QList & result) const = 0; virtual void values(QMap & result) const = 0; virtual Node * parent() const { return m_parent; } virtual void setParent(Node * parent) { m_parent = parent; } virtual int childCount() const { return m_counter; } virtual const QRectF& boundingBox() const { return m_boundingBox; } virtual void updateBoundingBox(); virtual const QRectF& childBoundingBox(int index) const { return m_childBoundingBox[index]; } virtual void setChildBoundingBox(int index, const QRectF& rect) { m_childBoundingBox[index] = rect; } virtual void clear(); virtual bool isRoot() const { return m_parent == 0; } virtual bool isLeaf() const { return false; } virtual int place() const { return m_place; } virtual void setPlace(int place) { m_place = place; } virtual int level() const { return m_level; } virtual void setLevel(int level) { m_level = level; } #ifdef CALLIGRA_RTREE_DEBUG virtual int nodeId() const { return m_nodeId; } virtual void paint(QPainter & p, int level) const = 0; virtual void debug(QString line) const = 0; protected: #define levelColorSize 5 static QColor levelColor[levelColorSize]; virtual void paintRect(QPainter & p, int level) const; #endif protected: Node * m_parent; QRectF m_boundingBox; QVector m_childBoundingBox; int m_counter; // the position in the parent int m_place; #ifdef CALLIGRA_RTREE_DEBUG int m_nodeId; #endif int m_level; }; class NonLeafNode : virtual public Node { public: NonLeafNode(int capacity, int level, Node * parent); virtual ~NonLeafNode(); virtual void insert(const QRectF& bb, Node * data); virtual void remove(int index); virtual void move(Node * node, int index); virtual LeafNode * chooseLeaf(const QRectF& bb); virtual NonLeafNode * chooseNode(const QRectF& bb, int level); virtual void intersects(const QRectF& rect, QMap & result) const; virtual void contains(const QPointF & point, QMap & result) const; virtual void keys(QList & result) const; virtual void values(QMap & result) const; virtual Node * getNode(int index) const; #ifdef CALLIGRA_RTREE_DEBUG virtual void paint(QPainter & p, int level) const; virtual void debug(QString line) const; #endif protected: virtual Node * getLeastEnlargement(const QRectF& bb) const; QVector m_childs; }; class LeafNode : virtual public Node { public: static int dataIdCounter; LeafNode(int capacity, int level, Node * parent); virtual ~LeafNode(); virtual void insert(const QRectF& bb, const T& data, int id); virtual void remove(int index); virtual void remove(const T& data); virtual void move(Node * node, int index); virtual LeafNode * chooseLeaf(const QRectF& bb); virtual NonLeafNode * chooseNode(const QRectF& bb, int level); virtual void intersects(const QRectF& rect, QMap & result) const; virtual void contains(const QPointF & point, QMap & result) const; virtual void keys(QList & result) const; virtual void values(QMap & result) const; virtual const T& getData(int index) const; virtual int getDataId(int index) const; virtual bool isLeaf() const { return true; } #ifdef CALLIGRA_RTREE_DEBUG virtual void debug(QString line) const; virtual void paint(QPainter & p, int level) const; #endif protected: QVector m_data; QVector m_dataIds; }; // factory methods virtual LeafNode* createLeafNode(int capacity, int level, Node * parent) { return new LeafNode(capacity, level, parent); } virtual NonLeafNode* createNonLeafNode(int capacity, int level, Node * parent) { return new NonLeafNode(capacity, level, parent); } // methods for insert QPair splitNode(Node * node); QPair pickSeeds(Node * node); QPair pickNext(Node * node, QVector & marker, Node * group1, Node * group2); virtual void adjustTree(Node * node1, Node * node2); void insertHelper(const QRectF& bb, const T& data, int id); // methods for delete void insert(Node * node); virtual void condenseTree(Node * node, QVector & reinsert); int m_capacity; int m_minimum; Node * m_root; QMap m_leafMap; }; template KoRTree::KoRTree(int capacity, int minimum) : m_capacity(capacity) , m_minimum(minimum) , m_root(createLeafNode(m_capacity + 1, 0, 0)) { if (minimum > capacity / 2) qFatal("KoRTree::KoRTree minimum can be maximal capacity/2"); //qDebug() << "root node " << m_root->nodeId(); } template KoRTree::~KoRTree() { delete m_root; } template void KoRTree::insert(const QRectF& bb, const T& data) { insertHelper(bb, data, LeafNode::dataIdCounter++); } template void KoRTree::insertHelper(const QRectF& bb, const T& data, int id) { QRectF nbb(bb.normalized()); // This has to be done as it is not possible to use QRectF::united() with a isNull() if (nbb.isNull()) { nbb.setWidth(0.0001); nbb.setHeight(0.0001); qWarning() << "KoRTree::insert boundingBox isNull setting size to" << nbb.size(); } else { // This has to be done as QRectF::intersects() return false if the rect does not have any area overlapping. // If there is no width or height there is no area and therefore no overlapping. if ( nbb.width() == 0 ) { nbb.setWidth(0.0001); } if ( nbb.height() == 0 ) { nbb.setHeight(0.0001); } } LeafNode * leaf = m_root->chooseLeaf(nbb); //qDebug() << " leaf" << leaf->nodeId() << nbb; if (leaf->childCount() < m_capacity) { leaf->insert(nbb, data, id); m_leafMap[data] = leaf; adjustTree(leaf, 0); } else { leaf->insert(nbb, data, id); m_leafMap[data] = leaf; QPair newNodes = splitNode(leaf); LeafNode * l = dynamic_cast(newNodes.first); if (l) for (int i = 0; i < l->childCount(); ++i) m_leafMap[l->getData(i)] = l; l = dynamic_cast(newNodes.second); if (l) for (int i = 0; i < l->childCount(); ++i) m_leafMap[l->getData(i)] = l; adjustTree(newNodes.first, newNodes.second); } } template void KoRTree::insert(Node * node) { if (node->level() == m_root->level()) { adjustTree(m_root, node); } else { QRectF bb(node->boundingBox()); NonLeafNode * newParent = m_root->chooseNode(bb, node->level() + 1); newParent->insert(bb, node); QPair newNodes(node, 0); if (newParent->childCount() > m_capacity) { newNodes = splitNode(newParent); } adjustTree(newNodes.first, newNodes.second); } } template void KoRTree::remove(const T&data) { //qDebug() << "KoRTree remove"; LeafNode * leaf = m_leafMap[data]; if (leaf == 0) { qWarning() << "KoRTree::remove( const T&data) data not found"; return; } m_leafMap.remove(data); leaf->remove(data); QVector reinsert; condenseTree(leaf, reinsert); for (int i = 0; i < reinsert.size(); ++i) { if (reinsert[i]->isLeaf()) { LeafNode * leaf = dynamic_cast(reinsert[i]); for (int j = 0; j < leaf->childCount(); ++j) { insertHelper(leaf->childBoundingBox(j), leaf->getData(j), leaf->getDataId(j)); } // clear is needed as the data items are not removed when insert into a new node leaf->clear(); delete leaf; } else { NonLeafNode * node = dynamic_cast(reinsert[i]); for (int j = 0; j < node->childCount(); ++j) { insert(node->getNode(j)); } // clear is needed as the data items are not removed when insert into a new node node->clear(); delete node; } } } template QList KoRTree::intersects(const QRectF& rect) const { QMap found; m_root->intersects(rect, found); return found.values(); } template QList KoRTree::contains(const QPointF &point) const { QMap found; m_root->contains(point, found); return found.values(); } template QList KoRTree::keys() const { QList found; m_root->keys(found); return found; } template QList KoRTree::values() const { QMap found; m_root->values(found); return found.values(); } #ifdef CALLIGRA_RTREE_DEBUG template void KoRTree::paint(QPainter & p) const { if (m_root) { m_root->paint(p, 0); } } template void KoRTree::debug() const { QString prefix(""); m_root->debug(prefix); } #endif template QPair< typename KoRTree::Node*, typename KoRTree::Node* > KoRTree::splitNode(typename KoRTree::Node* node) { //qDebug() << "KoRTree::splitNode" << node; Node * n1; Node * n2; if (node->isLeaf()) { n1 = createLeafNode(m_capacity + 1, node->level(), node->parent()); n2 = createLeafNode(m_capacity + 1, node->level(), node->parent()); } else { n1 = createNonLeafNode(m_capacity + 1, node->level(), node->parent()); n2 = createNonLeafNode(m_capacity + 1, node->level(), node->parent()); } //qDebug() << " n1" << n1 << n1->nodeId(); //qDebug() << " n2" << n2 << n2->nodeId(); QVector marker(m_capacity + 1); QPair seeds(pickSeeds(node)); n1->move(node, seeds.first); n2->move(node, seeds.second); marker[seeds.first] = true; marker[seeds.second] = true; // There is one more in a node to split than the capacity and as we // already put the seeds in the new nodes subtract them. int remaining = m_capacity + 1 - 2; while (remaining > 0) { if (m_minimum - n1->childCount() == remaining) { for (int i = 0; i < m_capacity + 1; ++i) { if (!marker[i]) { n1->move(node, i); --remaining; } } } else if (m_minimum - n2->childCount() == remaining) { for (int i = 0; i < m_capacity + 1; ++i) { if (!marker[i]) { n2->move(node, i); --remaining; } } } else { QPair next(pickNext(node, marker, n1, n2)); if (next.first == 0) { n1->move(node, next.second); } else { n2->move(node, next.second); } --remaining; } } Q_ASSERT(n1->childCount() + n2->childCount() == node->childCount()); // move the data back to the old node // this has to be done as the current node is already in the tree. node->clear(); for (int i = 0; i < n1->childCount(); ++i) { node->move(n1, i); } //qDebug() << " delete n1" << n1 << n1->nodeId(); // clear is needed as the data items are not removed n1->clear(); delete n1; return qMakePair(node, n2); } template QPair KoRTree::pickSeeds(Node *node) { int s1 = 0; int s2 = 1; qreal max = 0; for (int i = 0; i < m_capacity + 1; ++i) { for (int j = i+1; j < m_capacity + 1; ++j) { if (i != j) { QRectF bb1(node->childBoundingBox(i)); QRectF bb2(node->childBoundingBox(j)); QRectF comp(node->childBoundingBox(i).united(node->childBoundingBox(j))); qreal area = comp.width() * comp.height() - bb1.width() * bb1.height() - bb2.width() * bb2.height(); //qDebug() << " ps" << i << j << area; if (area > max) { max = area; s1 = i; s2 = j; } } } } return qMakePair(s1, s2); } template QPair KoRTree::pickNext(Node * node, QVector & marker, Node * group1, Node * group2) { //qDebug() << "KoRTree::pickNext" << marker; qreal max = -1.0; int select = 0; int group = 0; for (int i = 0; i < m_capacity + 1; ++i) { if (marker[i] == false) { QRectF bb1 = group1->boundingBox().united(node->childBoundingBox(i)); QRectF bb2 = group2->boundingBox().united(node->childBoundingBox(i)); qreal d1 = bb1.width() * bb1.height() - group1->boundingBox().width() * group1->boundingBox().height(); qreal d2 = bb2.width() * bb2.height() - group2->boundingBox().width() * group2->boundingBox().height(); qreal diff = qAbs(d1 - d2); //qDebug() << " diff" << diff << i << d1 << d2; if (diff > max) { max = diff; select = i; //qDebug() << " i =" << i; if (qAbs(d1) > qAbs(d2)) { group = 1; } else { group = 0; } //qDebug() << " group =" << group; } } } marker[select] = true; return qMakePair(group, select); } template void KoRTree::adjustTree(Node *node1, Node *node2) { //qDebug() << "KoRTree::adjustTree"; if (node1->isRoot()) { //qDebug() << " root"; if (node2) { NonLeafNode * newRoot = createNonLeafNode(m_capacity + 1, node1->level() + 1, 0); newRoot->insert(node1->boundingBox(), node1); newRoot->insert(node2->boundingBox(), node2); m_root = newRoot; //qDebug() << "new root" << m_root->nodeId(); } } else { NonLeafNode * parent = dynamic_cast(node1->parent()); if (!parent) { qFatal("KoRTree::adjustTree: no parent node found!"); return; } //QRectF pbbold( parent->boundingBox() ); parent->setChildBoundingBox(node1->place(), node1->boundingBox()); parent->updateBoundingBox(); //qDebug() << " bb1 =" << node1->boundingBox() << node1->place() << pbbold << "->" << parent->boundingBox() << parent->nodeId(); if (!node2) { //qDebug() << " update"; adjustTree(parent, 0); } else { if (parent->childCount() < m_capacity) { //qDebug() << " no split needed"; parent->insert(node2->boundingBox(), node2); adjustTree(parent, 0); } else { //qDebug() << " split again"; parent->insert(node2->boundingBox(), node2); QPair newNodes = splitNode(parent); adjustTree(newNodes.first, newNodes.second); } } } } template void KoRTree::condenseTree(Node *node, QVector & reinsert) { //qDebug() << "KoRTree::condenseTree begin reinsert.size()" << reinsert.size(); if (!node->isRoot()) { Node * parent = node->parent(); //qDebug() << " !node->isRoot us" << node->childCount(); if (node->childCount() < m_minimum) { //qDebug() << " remove node"; parent->remove(node->place()); reinsert.push_back(node); } else { //qDebug() << " update BB parent is root" << parent->isRoot(); parent->setChildBoundingBox(node->place(), node->boundingBox()); parent->updateBoundingBox(); } condenseTree(parent, reinsert); } else { //qDebug() << " node->isRoot us" << node->childCount(); if (node->childCount() == 1 && !node->isLeaf()) { //qDebug() << " usedSpace = 1"; NonLeafNode * n = dynamic_cast(node); if (n) { Node * kid = n->getNode(0); // clear is needed as the data items are not removed m_root->clear(); delete m_root; m_root = kid; m_root->setParent(0); //qDebug() << " new root" << m_root; } else { qFatal("KoRTree::condenseTree cast to NonLeafNode failed"); } } } //qDebug() << "KoRTree::condenseTree end reinsert.size()" << reinsert.size(); } #ifdef CALLIGRA_RTREE_DEBUG template QColor KoRTree::Node::levelColor[] = { QColor(Qt::green), QColor(Qt::red), QColor(Qt::cyan), QColor(Qt::magenta), QColor(Qt::yellow), }; template int KoRTree::Node::nodeIdCnt = 0; #endif template KoRTree::Node::Node(int capacity, int level, Node * parent) : m_parent(parent) , m_childBoundingBox(capacity) , m_counter(0) #ifdef CALLIGRA_RTREE_DEBUG , m_nodeId(nodeIdCnt++) #endif , m_level(level) { } template void KoRTree::Node::remove(int index) { for (int i = index + 1; i < m_counter; ++i) { m_childBoundingBox[i-1] = m_childBoundingBox[i]; } --m_counter; updateBoundingBox(); } template void KoRTree::Node::updateBoundingBox() { m_boundingBox = QRectF(); for (int i = 0; i < m_counter; ++i) { m_boundingBox = m_boundingBox.united(m_childBoundingBox[i]); } } template void KoRTree::Node::clear() { m_counter = 0; m_boundingBox = QRectF(); } #ifdef CALLIGRA_RTREE_DEBUG template void KoRTree::Node::paintRect(QPainter & p, int level) const { QColor c(Qt::black); if (level < levelColorSize) { c = levelColor[level]; } - QPen pen(c); + QPen pen(c, 0); p.setPen(pen); QRectF bbdraw(this->m_boundingBox); bbdraw.adjust(level * 2, level * 2, -level * 2, -level * 2); p.drawRect(bbdraw); } #endif template KoRTree::NonLeafNode::NonLeafNode(int capacity, int level, Node * parent) : Node(capacity, level, parent) , m_childs(capacity) { //qDebug() << "NonLeafNode::NonLeafNode()" << this; } template KoRTree::NonLeafNode::~NonLeafNode() { //qDebug() << "NonLeafNode::~NonLeafNode()" << this; for (int i = 0; i < this->m_counter; ++i) { delete m_childs[i]; } } template void KoRTree::NonLeafNode::insert(const QRectF& bb, Node * data) { m_childs[this->m_counter] = data; data->setPlace(this->m_counter); data->setParent(this); this->m_childBoundingBox[this->m_counter] = bb; this->m_boundingBox = this->m_boundingBox.united(bb); //qDebug() << "NonLeafNode::insert" << this->nodeId() << data->nodeId(); ++this->m_counter; } template void KoRTree::NonLeafNode::remove(int index) { for (int i = index + 1; i < this->m_counter; ++i) { m_childs[i-1] = m_childs[i]; m_childs[i-1]->setPlace(i - 1); } Node::remove(index); } template void KoRTree::NonLeafNode::move(Node * node, int index) { //qDebug() << "NonLeafNode::move" << this << node << index << node->nodeId() << "->" << this->nodeId(); NonLeafNode * n = dynamic_cast(node); if (n) { QRectF bb = n->childBoundingBox(index); insert(bb, n->getNode(index)); } } template typename KoRTree::LeafNode * KoRTree::NonLeafNode::chooseLeaf(const QRectF& bb) { return getLeastEnlargement(bb)->chooseLeaf(bb); } template typename KoRTree::NonLeafNode * KoRTree::NonLeafNode::chooseNode(const QRectF& bb, int level) { if (this->m_level > level) { return getLeastEnlargement(bb)->chooseNode(bb, level); } else { return this; } } template void KoRTree::NonLeafNode::intersects(const QRectF& rect, QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { if (this->m_childBoundingBox[i].intersects(rect)) { m_childs[i]->intersects(rect, result); } } } template void KoRTree::NonLeafNode::contains(const QPointF & point, QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { if (this->m_childBoundingBox[i].contains(point)) { m_childs[i]->contains(point, result); } } } template void KoRTree::NonLeafNode::keys(QList & result) const { for (int i = 0; i < this->m_counter; ++i) { m_childs[i]->keys(result); } } template void KoRTree::NonLeafNode::values(QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { m_childs[i]->values(result); } } template typename KoRTree::Node * KoRTree::NonLeafNode::getNode(int index) const { return m_childs[index]; } template typename KoRTree::Node * KoRTree::NonLeafNode::getLeastEnlargement(const QRectF& bb) const { //qDebug() << "NonLeafNode::getLeastEnlargement"; QVarLengthArray area(this->m_counter); for (int i = 0; i < this->m_counter; ++i) { QSizeF big(this->m_childBoundingBox[i].united(bb).size()); area[i] = big.width() * big.height() - this->m_childBoundingBox[i].width() * this->m_childBoundingBox[i].height(); } int minIndex = 0; qreal minArea = area[minIndex]; //qDebug() << " min" << minIndex << minArea; for (int i = 1; i < this->m_counter; ++i) { if (area[i] < minArea) { minIndex = i; minArea = area[i]; //qDebug() << " min" << minIndex << minArea; } } return m_childs[minIndex]; } #ifdef CALLIGRA_RTREE_DEBUG template void KoRTree::NonLeafNode::debug(QString line) const { for (int i = 0; i < this->m_counter; ++i) { qDebug("%s %d %d", qPrintable(line), this->nodeId(), i); m_childs[i]->debug(line + " "); } } template void KoRTree::NonLeafNode::paint(QPainter & p, int level) const { this->paintRect(p, level); for (int i = 0; i < this->m_counter; ++i) { m_childs[i]->paint(p, level + 1); } } #endif template int KoRTree::LeafNode::dataIdCounter = 0; template KoRTree::LeafNode::LeafNode(int capacity, int level, Node * parent) : Node(capacity, level, parent) , m_data(capacity) , m_dataIds(capacity) { //qDebug() << "LeafNode::LeafNode" << this; } template KoRTree::LeafNode::~LeafNode() { //qDebug() << "LeafNode::~LeafNode" << this; } template void KoRTree::LeafNode::insert(const QRectF& bb, const T& data, int id) { m_data[this->m_counter] = data; m_dataIds[this->m_counter] = id; this->m_childBoundingBox[this->m_counter] = bb; this->m_boundingBox = this->m_boundingBox.united(bb); ++this->m_counter; } template void KoRTree::LeafNode::remove(int index) { for (int i = index + 1; i < this->m_counter; ++i) { m_data[i-1] = m_data[i]; m_dataIds[i-1] = m_dataIds[i]; } Node::remove(index); } template void KoRTree::LeafNode::remove(const T& data) { int old_counter = this->m_counter; for (int i = 0; i < this->m_counter; ++i) { if (m_data[i] == data) { //qDebug() << "LeafNode::remove id" << i; remove(i); break; } } if (old_counter == this->m_counter) { qWarning() << "LeafNode::remove( const T&data) data not found"; } } template void KoRTree::LeafNode::move(Node * node, int index) { LeafNode * n = dynamic_cast(node); if (n) { //qDebug() << "LeafNode::move" << this << node << index // << node->nodeId() << "->" << this->nodeId() << n->childBoundingBox( index ); QRectF bb = n->childBoundingBox(index); insert(bb, n->getData(index), n->getDataId(index)); } } template typename KoRTree::LeafNode * KoRTree::LeafNode::chooseLeaf(const QRectF& bb) { Q_UNUSED(bb); return this; } template typename KoRTree::NonLeafNode * KoRTree::LeafNode::chooseNode(const QRectF& bb, int level) { Q_UNUSED(bb); Q_UNUSED(level); qFatal("LeafNode::chooseNode called. This should not happen!"); return 0; } template void KoRTree::LeafNode::intersects(const QRectF& rect, QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { if (this->m_childBoundingBox[i].intersects(rect)) { result.insert(m_dataIds[i], m_data[i]); } } } template void KoRTree::LeafNode::contains(const QPointF & point, QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { if (this->m_childBoundingBox[i].contains(point)) { result.insert(m_dataIds[i], m_data[i]); } } } template void KoRTree::LeafNode::keys(QList & result) const { for (int i = 0; i < this->m_counter; ++i) { result.push_back(this->m_childBoundingBox[i]); } } template void KoRTree::LeafNode::values(QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { result.insert(m_dataIds[i], m_data[i]); } } template const T& KoRTree::LeafNode::getData(int index) const { return m_data[ index ]; } template int KoRTree::LeafNode::getDataId(int index) const { return m_dataIds[ index ]; } #ifdef CALLIGRA_RTREE_DEBUG template void KoRTree::LeafNode::debug(QString line) const { for (int i = 0; i < this->m_counter; ++i) { qDebug("%s %d %d %p", qPrintable(line), this->nodeId(), i, &(m_data[i])); qDebug() << this->m_childBoundingBox[i].toRect(); } } template void KoRTree::LeafNode::paint(QPainter & p, int level) const { if (this->m_counter) { this->paintRect(p, level); } } #endif #endif /* KORTREE_H */ diff --git a/libs/flake/KoSnapGuide.cpp b/libs/flake/KoSnapGuide.cpp index 50992dc565..353f3e464e 100644 --- a/libs/flake/KoSnapGuide.cpp +++ b/libs/flake/KoSnapGuide.cpp @@ -1,281 +1,281 @@ /* 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 template inline QSharedPointer toQShared(T* ptr) { return QSharedPointer(ptr); } class Q_DECL_HIDDEN KoSnapGuide::Private { public: Private(KoCanvasBase *parentCanvas) : canvas(parentCanvas), editedShape(0), currentStrategy(0), active(true), snapDistance(10) { } ~Private() { strategies.clear(); } KoCanvasBase *canvas; KoShape *editedShape; typedef QSharedPointer KoSnapStrategySP; typedef QList StrategiesList; StrategiesList strategies; KoSnapStrategySP currentStrategy; KoSnapGuide::Strategies usedStrategies; bool active; int snapDistance; QList ignoredPoints; QList ignoredShapes; }; KoSnapGuide::KoSnapGuide(KoCanvasBase *canvas) : d(new Private(canvas)) { d->strategies.append(toQShared(new GridSnapStrategy())); d->strategies.append(toQShared(new NodeSnapStrategy())); d->strategies.append(toQShared(new OrthogonalSnapStrategy())); d->strategies.append(toQShared(new ExtensionSnapStrategy())); d->strategies.append(toQShared(new IntersectionSnapStrategy())); d->strategies.append(toQShared(new BoundingBoxSnapStrategy())); } KoSnapGuide::~KoSnapGuide() { } void KoSnapGuide::setEditedShape(KoShape *shape) { d->editedShape = shape; } KoShape *KoSnapGuide::editedShape() const { return d->editedShape; } void KoSnapGuide::enableSnapStrategy(Strategy type, bool value) { if (value) { d->usedStrategies |= type; } else { d->usedStrategies &= ~type; } } bool KoSnapGuide::isStrategyEnabled(Strategy type) const { return d->usedStrategies & type; } 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(toQShared(customStrategy)); return true; } void KoSnapGuide::overrideSnapStrategy(Strategy type, KoSnapStrategy *strategy) { for (auto it = d->strategies.begin(); it != d->strategies.end(); /*noop*/) { if ((*it)->type() == type) { if (strategy) { *it = toQShared(strategy); } else { it = d->strategies.erase(it); } return; } else { ++it; } } if (strategy) { d->strategies.append(toQShared(strategy)); } } 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, const QPointF &dragOffset, Qt::KeyboardModifiers modifiers) { QPointF pos = mousePosition + dragOffset; pos = snap(pos, modifiers); return pos - dragOffset; } QPointF KoSnapGuide::snap(const QPointF &mousePosition, Qt::KeyboardModifiers modifiers) { d->currentStrategy.clear(); 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 (Private::KoSnapStrategySP 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() { 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); + QPen whitePen(Qt::white, 0); whitePen.setStyle(Qt::SolidLine); painter.setPen(whitePen); painter.drawPath(decoration); - QPen redPen(Qt::red); + 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.clear(); 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) { d->strategies.removeAt(i); } } } diff --git a/libs/flake/KoUnavailShape.cpp b/libs/flake/KoUnavailShape.cpp index 0e0494f36b..d6881b8d70 100644 --- a/libs/flake/KoUnavailShape.cpp +++ b/libs/flake/KoUnavailShape.cpp @@ -1,656 +1,656 @@ /* This file is part of the KDE project * * Copyright (C) 2010-2011 Inge Wallin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // Own #include "KoUnavailShape.h" // Qt #include #include #include #include #include #include #include #include #include // Calligra #include #include #include #include #include #include #include #include #include "KoShapeLoadingContext.h" #include "KoShapeSavingContext.h" #include "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(":/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(":/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))); + 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/vectorimage/libemf/EmfOutputPainterStrategy.cpp b/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp index b044a31071..60fddd99dc 100644 --- a/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp +++ b/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp @@ -1,1472 +1,1472 @@ /* 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 "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))); + 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(Qt::black); + 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/widgets/KoColorPatch.cpp b/libs/widgets/KoColorPatch.cpp index 0d2b53402c..9adc823e1a 100644 --- a/libs/widgets/KoColorPatch.cpp +++ b/libs/widgets/KoColorPatch.cpp @@ -1,93 +1,93 @@ /** * Copyright (c) 2006 C. Boemann (cbo@boemann.dk) * * 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 "KoColorPatch.h" #include KoColorPatch::KoColorPatch( QWidget *parent ) : QFrame( parent ) { m_displayRenderer = KoDumbColorDisplayRenderer::instance(); connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()), SLOT(update()), Qt::UniqueConnection); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); } KoColorPatch::~KoColorPatch() { } QSize KoColorPatch::sizeHint() const { return QSize(12,12); } void KoColorPatch::setColor(const KoColor& c) { m_color = c; update(); } void KoColorPatch::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { if (m_displayRenderer) { m_displayRenderer->disconnect(this); } m_displayRenderer = displayRenderer; } else { m_displayRenderer = KoDumbColorDisplayRenderer::instance(); } connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()), SLOT(update()), Qt::UniqueConnection); } QColor KoColorPatch::getColorFromDisplayRenderer(KoColor c) { QColor col; if (m_displayRenderer) { c.convertTo(m_displayRenderer->getPaintingColorSpace()); col = m_displayRenderer->toQColor(c); } else { col = c.toQColor(); } return col; } KoColor KoColorPatch::color() const { return m_color; } void KoColorPatch::mousePressEvent (QMouseEvent *e ) { Q_UNUSED( e ); emit triggered(this); } void KoColorPatch::paintEvent(QPaintEvent *pe) { QColor qc = getColorFromDisplayRenderer(m_color); QFrame::paintEvent(pe); QPainter painter( this ); - painter.setPen(qc); + painter.setPen(QPen(qc, 0)); painter.setBrush(QBrush(qc)); painter.drawRect(contentsRect()); } diff --git a/libs/widgets/KoColorSlider.cpp b/libs/widgets/KoColorSlider.cpp index 25a1931437..3a125f37d2 100644 --- a/libs/widgets/KoColorSlider.cpp +++ b/libs/widgets/KoColorSlider.cpp @@ -1,199 +1,199 @@ /* This file is part of the KDE project Copyright (C) 2006 Sven Langkamp Copyright (c) 2009 Cyrille Berger 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 "KoColorSlider.h" #include "KoColorSpace.h" #include #include #include #include #include #include #define ARROWSIZE 8 struct Q_DECL_HIDDEN KoColorSlider::Private { Private() : upToDate(false), displayRenderer(0) {} KoColor minColor; KoColor maxColor; QPixmap pixmap; bool upToDate; QPointer displayRenderer; }; KoColorSlider::KoColorSlider(QWidget* parent, KoColorDisplayRendererInterface *displayRenderer) : KSelector(parent) , d(new Private) { setMaximum(255); d->displayRenderer = displayRenderer; connect(d->displayRenderer, SIGNAL(displayConfigurationChanged()), SLOT(update())); } KoColorSlider::KoColorSlider(Qt::Orientation o, QWidget *parent, KoColorDisplayRendererInterface *displayRenderer) : KSelector(o, parent), d(new Private) { setMaximum(255); d->displayRenderer = displayRenderer; connect(d->displayRenderer, SIGNAL(displayConfigurationChanged()), SLOT(update())); } KoColorSlider::~KoColorSlider() { delete d; } void KoColorSlider::setColors(const KoColor& mincolor, const KoColor& maxcolor) { d->minColor = mincolor; d->maxColor = maxcolor; d->upToDate = false; QTimer::singleShot(1, this, SLOT(update())); } void KoColorSlider::drawContents( QPainter *painter ) { QPixmap checker(8, 8); QPainter p(&checker); p.fillRect(0, 0, 4, 4, Qt::lightGray); p.fillRect(4, 0, 4, 4, Qt::darkGray); p.fillRect(0, 4, 4, 4, Qt::darkGray); p.fillRect(4, 4, 4, 4, Qt::lightGray); p.end(); QRect contentsRect_(contentsRect()); painter->fillRect(contentsRect_, QBrush(checker)); if( !d->upToDate || d->pixmap.isNull() || d->pixmap.width() != contentsRect_.width() || d->pixmap.height() != contentsRect_.height() ) { KoColor c = d->minColor; // smart way to fetch colorspace QColor color; const quint8 *colors[2]; colors[0] = d->minColor.data(); colors[1] = d->maxColor.data(); KoMixColorsOp * mixOp = c.colorSpace()->mixColorsOp(); Q_ASSERT(mixOp); QImage image(contentsRect_.width(), contentsRect_.height(), QImage::Format_ARGB32 ); if( orientation() == Qt::Horizontal ) { for (int x = 0; x < contentsRect_.width(); x++) { qreal t = static_cast(x) / (contentsRect_.width() - 1); qint16 colorWeights[2]; colorWeights[0] = static_cast((1.0 - t) * 255 + 0.5); colorWeights[1] = 255 - colorWeights[0]; mixOp->mixColors(colors, colorWeights, 2, c.data()); if (d->displayRenderer) { color = d->displayRenderer->toQColor(c); } else { color = c.toQColor(); } for (int y = 0; y < contentsRect_.height(); y++) image.setPixel(x, y, color.rgba()); } } else { for (int y = 0; y < contentsRect_.height(); y++) { qreal t = static_cast(y) / (contentsRect_.height() - 1); qint16 colorWeights[2]; colorWeights[0] = static_cast((t) * 255 + 0.5); colorWeights[1] = 255 - colorWeights[0]; mixOp->mixColors(colors, colorWeights, 2, c.data()); if (d->displayRenderer) { color = d->displayRenderer->toQColor(c); } else { color = c.toQColor(); } for (int x = 0; x < contentsRect_.width(); x++) image.setPixel(x, y, color.rgba()); } } d->pixmap = QPixmap::fromImage(image); d->upToDate = true; } painter->drawPixmap( contentsRect_, d->pixmap, QRect( 0, 0, d->pixmap.width(), d->pixmap.height()) ); } KoColor KoColorSlider::currentColor() const { const quint8 *colors[2]; colors[0] = d->minColor.data(); colors[1] = d->maxColor.data(); KoMixColorsOp * mixOp = d->minColor.colorSpace()->mixColorsOp(); KoColor c(d->minColor.colorSpace()); qint16 weights[2]; weights[1] = (value() - minimum()) / qreal(maximum() - minimum()) * 255; weights[0] = 255 - weights[1]; mixOp->mixColors(colors, weights, 2, c.data()); return c; } void KoColorSlider::drawArrow(QPainter *painter, const QPoint &pos) { - painter->setPen(palette().text().color()); + painter->setPen(QPen(palette().text().color(), 0)); painter->setBrush(palette().text()); QStyleOption o; o.initFrom(this); o.state &= ~QStyle::State_MouseOver; if ( orientation() == Qt::Vertical ) { o.rect = QRect( pos.x(), pos.y() - ARROWSIZE / 2, ARROWSIZE, ARROWSIZE ); } else { o.rect = QRect( pos.x() - ARROWSIZE / 2, pos.y(), ARROWSIZE, ARROWSIZE ); } QStyle::PrimitiveElement arrowPE; switch (arrowDirection()) { case Qt::UpArrow: arrowPE = QStyle::PE_IndicatorArrowUp; break; case Qt::DownArrow: arrowPE = QStyle::PE_IndicatorArrowDown; break; case Qt::RightArrow: arrowPE = QStyle::PE_IndicatorArrowRight; break; case Qt::LeftArrow: default: arrowPE = QStyle::PE_IndicatorArrowLeft; break; } style()->drawPrimitive(arrowPE, &o, painter, this); } diff --git a/libs/widgets/KoPagePreviewWidget.cpp b/libs/widgets/KoPagePreviewWidget.cpp index ef8d4704fd..517243d99e 100644 --- a/libs/widgets/KoPagePreviewWidget.cpp +++ b/libs/widgets/KoPagePreviewWidget.cpp @@ -1,164 +1,164 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thomas Zander * Copyright (C) 2006 Gary Cramblitt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPagePreviewWidget.h" #include #include #include #include #include #include class Q_DECL_HIDDEN KoPagePreviewWidget::Private { public: KoPageLayout pageLayout; KoColumns columns; }; KoPagePreviewWidget::KoPagePreviewWidget(QWidget *parent) : QWidget(parent) , d(new Private) { setMinimumSize( 100, 100 ); } KoPagePreviewWidget::~KoPagePreviewWidget() { delete d; } void KoPagePreviewWidget::paintEvent(QPaintEvent *event) { Q_UNUSED(event); // resolution[XY] is in pixel per pt qreal resolutionX = POINT_TO_INCH( static_cast(KoDpi::dpiX()) ); qreal resolutionY = POINT_TO_INCH( static_cast(KoDpi::dpiY()) ); qreal pageWidth = d->pageLayout.width * resolutionX; qreal pageHeight = d->pageLayout.height * resolutionY; const bool pageSpread = (d->pageLayout.bindingSide >= 0 && d->pageLayout.pageEdge >= 0); qreal sheetWidth = pageWidth / (pageSpread?2:1); qreal zoomH = (height() * 90 / 100) / pageHeight; qreal zoomW = (width() * 90 / 100) / pageWidth; qreal zoom = qMin( zoomW, zoomH ); pageWidth *= zoom; sheetWidth *= zoom; pageHeight *= zoom; QPainter painter( this ); QRect page = QRectF((width() - pageWidth) / 2.0, (height() - pageHeight) / 2.0, sheetWidth, pageHeight).toRect(); painter.save(); drawPage(painter, zoom, page, true); painter.restore(); if(pageSpread) { page.moveLeft(page.left() + (int) (sheetWidth)); painter.save(); drawPage(painter, zoom, page, false); painter.restore(); } painter.end(); // paint scale } void KoPagePreviewWidget::drawPage(QPainter &painter, qreal zoom, const QRect &dimensions, bool left) { painter.fillRect(dimensions, QBrush(palette().base())); - painter.setPen(QPen(palette().color(QPalette::Dark))); + painter.setPen(QPen(palette().color(QPalette::Dark), 0)); painter.drawRect(dimensions); // draw text areas QRect textArea = dimensions; if ((d->pageLayout.topMargin == 0 && d->pageLayout.bottomMargin == 0 && d->pageLayout.leftMargin == 0 && d->pageLayout.rightMargin == 0) || ( d->pageLayout.pageEdge == 0 && d->pageLayout.bindingSide == 0)) { // no margin return; } else { textArea.setTop(textArea.top() + qRound(zoom * d->pageLayout.topMargin)); textArea.setBottom(textArea.bottom() - qRound(zoom * d->pageLayout.bottomMargin)); qreal leftMargin, rightMargin; if(d->pageLayout.bindingSide < 0) { // normal margins. leftMargin = d->pageLayout.leftMargin; rightMargin = d->pageLayout.rightMargin; } else { // margins mirrored for left/right pages leftMargin = d->pageLayout.bindingSide; rightMargin = d->pageLayout.pageEdge; if(left) qSwap(leftMargin, rightMargin); } textArea.setLeft(textArea.left() + qRound(zoom * leftMargin)); textArea.setRight(textArea.right() - qRound(zoom * rightMargin)); } painter.setBrush( QBrush( palette().color(QPalette::ButtonText), Qt::HorPattern ) ); - painter.setPen( palette().color(QPalette::Dark) ); + painter.setPen(QPen(palette().color(QPalette::Dark), 0)); // uniform columns? if (d->columns.columnData.isEmpty()) { qreal columnWidth = (textArea.width() + (d->columns.gapWidth * zoom)) / d->columns.count; int width = qRound(columnWidth - d->columns.gapWidth * zoom); for ( int i = 0; i < d->columns.count; ++i ) painter.drawRect( qRound(textArea.x() + i * columnWidth), textArea.y(), width, textArea.height()); } else { qreal totalRelativeWidth = 0.0; Q_FOREACH (const KoColumns::ColumnDatum &cd, d->columns.columnData) { totalRelativeWidth += cd.relativeWidth; } int relativeColumnXOffset = 0; for (int i = 0; i < d->columns.count; i++) { const KoColumns::ColumnDatum &columnDatum = d->columns.columnData.at(i); const qreal columnWidth = textArea.width() * columnDatum.relativeWidth / totalRelativeWidth; const qreal columnXOffset = textArea.width() * relativeColumnXOffset / totalRelativeWidth; painter.drawRect( qRound(textArea.x() + columnXOffset + columnDatum.leftMargin * zoom), qRound(textArea.y() + columnDatum.topMargin * zoom), qRound(columnWidth - (columnDatum.leftMargin + columnDatum.rightMargin) * zoom), qRound(textArea.height() - (columnDatum.topMargin + columnDatum.bottomMargin) * zoom)); relativeColumnXOffset += columnDatum.relativeWidth; } } } void KoPagePreviewWidget::setPageLayout(const KoPageLayout &layout) { d->pageLayout = layout; update(); } void KoPagePreviewWidget::setColumns(const KoColumns &columns) { d->columns = columns; update(); } diff --git a/libs/widgets/KoRuler.cpp b/libs/widgets/KoRuler.cpp index 30823a0530..5d42b37519 100644 --- a/libs/widgets/KoRuler.cpp +++ b/libs/widgets/KoRuler.cpp @@ -1,1367 +1,1367 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Reginald Stadlbauer Copyright (C) 2006 Peter Simonsson Copyright (C) 2007 C. Boemann Copyright (C) 2007-2008 Jan Hambrecht Copyright (C) 2007 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoRuler.h" #include "KoRuler_p.h" #include #include #include #include #include #include #include #include #include #include // the distance in pixels of a mouse position considered outside the rule static const int OutsideRulerThreshold = 20; // static const int fullStepMarkerLength = 6; static const int halfStepMarkerLength = 6; static const int quarterStepMarkerLength = 3; static const int measurementTextAboveBelowMargin = 1; void RulerTabChooser::mousePressEvent(QMouseEvent *) { if (! m_showTabs) { return; } switch(m_type) { case QTextOption::LeftTab: m_type = QTextOption::RightTab; break; case QTextOption::RightTab: m_type = QTextOption::CenterTab; break; case QTextOption::CenterTab: m_type = QTextOption::DelimiterTab; break; case QTextOption::DelimiterTab: m_type = QTextOption::LeftTab; break; } update(); } void RulerTabChooser::paintEvent(QPaintEvent *) { if (! m_showTabs) { return; } QPainter painter(this); QPolygonF polygon; - painter.setPen(palette().color(QPalette::Text)); + painter.setPen(QPen(palette().color(QPalette::Text), 0)); painter.setBrush(palette().color(QPalette::Text)); painter.setRenderHint( QPainter::Antialiasing ); qreal x= width()/2; painter.translate(0,-height()/2+5); switch (m_type) { case QTextOption::LeftTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x+6.5, height() - 2.5) << QPointF(x+0.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::RightTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x-5.5, height() - 2.5) << QPointF(x+0.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::CenterTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x-5.5, height() - 2.5) << QPointF(x+6.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::DelimiterTab: polygon << QPointF(x-5.5, height() - 2.5) << QPointF(x+6.5, height() - 2.5); painter.drawPolyline(polygon); polygon << QPointF(x+0.5, height() - 2.5) << QPointF(x+0.5, height() - 8.5); painter.drawPolyline(polygon); break; default: break; } } static int compareTabs(KoRuler::Tab &tab1, KoRuler::Tab &tab2) { return tab1.position < tab2.position; } QRectF HorizontalPaintingStrategy::drawBackground(const KoRulerPrivate *d, QPainter &painter) { lengthInPixel = d->viewConverter->documentToViewX(d->rulerLength); QRectF rectangle; rectangle.setX(qMax(0, d->offset)); rectangle.setY(0); rectangle.setWidth(qMin(qreal(d->ruler->width() - 1.0 - rectangle.x()), (d->offset >= 0) ? lengthInPixel : lengthInPixel + d->offset)); rectangle.setHeight(d->ruler->height() - 1); QRectF activeRangeRectangle; activeRangeRectangle.setX(qMax(rectangle.x() + 1, d->viewConverter->documentToViewX(d->effectiveActiveRangeStart()) + d->offset)); activeRangeRectangle.setY(rectangle.y() + 1); activeRangeRectangle.setRight(qMin(rectangle.right() - 1, d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd()) + d->offset)); activeRangeRectangle.setHeight(rectangle.height() - 2); - painter.setPen(d->ruler->palette().color(QPalette::Mid)); + painter.setPen(QPen(d->ruler->palette().color(QPalette::Mid), 0)); painter.fillRect(rectangle,d->ruler->palette().color(QPalette::AlternateBase)); // make background slightly different so it is easier to see painter.drawRect(rectangle); if(d->effectiveActiveRangeStart() != d->effectiveActiveRangeEnd()) painter.fillRect(activeRangeRectangle, d->ruler->palette().brush(QPalette::Base)); if(d->showSelectionBorders) { // Draw first selection border if(d->firstSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewX(d->firstSelectionBorder) + d->offset; painter.drawLine(QPointF(border, rectangle.y() + 1), QPointF(border, rectangle.bottom() - 1)); } // Draw second selection border if(d->secondSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewX(d->secondSelectionBorder) + d->offset; painter.drawLine(QPointF(border, rectangle.y() + 1), QPointF(border, rectangle.bottom() - 1)); } } return rectangle; } void HorizontalPaintingStrategy::drawTabs(const KoRulerPrivate *d, QPainter &painter) { if (! d->showTabs) return; QPolygonF polygon; const QColor tabColor = d->ruler->palette().color(QPalette::Text); - painter.setPen(tabColor); + painter.setPen(QPen(tabColor, 0)); painter.setBrush(tabColor); painter.setRenderHint( QPainter::Antialiasing ); qreal position = -10000; foreach (const KoRuler::Tab & t, d->tabs) { qreal x; if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - t.position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + t.position) + d->offset; } position = qMax(position, t.position); polygon.clear(); switch (t.type) { case QTextOption::LeftTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x+6.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::RightTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::CenterTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+6.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::DelimiterTab: polygon << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+6.5, d->ruler->height() - 0.5); painter.drawPolyline(polygon); polygon << QPointF(x+0.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 6.5); painter.drawPolyline(polygon); break; default: break; } } // and also draw the regular interval tab that are non editable if (d->tabDistance > 0.0) { // first possible position position = qMax(position, d->relativeTabs ? 0 : d->paragraphIndent); if (position < 0) { position = int(position / d->tabDistance) * d->tabDistance; } else { position = (int(position / d->tabDistance) + 1) * d->tabDistance; } while (position < d->effectiveActiveRangeEnd() - d->effectiveActiveRangeStart() - d->endIndent) { qreal x; if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + position) + d->offset; } polygon.clear(); polygon << QPointF(x+0.5, d->ruler->height() - 3.5) << QPointF(x+4.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); position += d->tabDistance; } } } void HorizontalPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF &rectangle) { qreal numberStep = d->numberStepForUnit(); // number step in unit // QRectF activeRangeRectangle; int numberStepPixel = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue(numberStep))); // const bool adjustMillimeters = (d->unit.type() == KoUnit::Millimeter); const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); painter.setFont(font); if (numberStepPixel == 0 || numberStep == 0) return; // Calc the longest text length int textLength = 0; for(int i = 0; i < lengthInPixel; i += numberStepPixel) { int number = qRound((i / numberStepPixel) * numberStep); textLength = qMax(textLength, fontMetrics.width(QString::number(number))); } textLength += 4; // Add some padding // Change number step so all digits fits while(textLength > numberStepPixel) { numberStepPixel += numberStepPixel; numberStep += numberStep; } int start=0; // Calc the first number step if(d->offset < 0) start = qAbs(d->offset); // make a little hack so rulers shows correctly inversed number aligned const qreal lengthInUnit = d->unit.toUserValue(d->rulerLength); const qreal hackyLength = lengthInUnit - fmod(lengthInUnit, numberStep); if(d->rightToLeft) { start -= int(d->viewConverter->documentToViewX(fmod(d->rulerLength, d->unit.fromUserValue(numberStep)))); } int stepCount = (start / numberStepPixel) + 1; int halfStepCount = (start / qRound(numberStepPixel * 0.5)) + 1; int quarterStepCount = (start / qRound(numberStepPixel * 0.25)) + 1; int pos = 0; - const QPen numberPen(d->ruler->palette().color(QPalette::Text)); - const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text)); + const QPen numberPen(d->ruler->palette().color(QPalette::Text), 0); + const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text), 0); painter.setPen(markerPen); if(d->offset > 0) painter.translate(d->offset, 0); const int len = qRound(rectangle.width()) + start; int nextStep = qRound(d->viewConverter->documentToViewX( d->unit.fromUserValue(numberStep * stepCount))); int nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); int nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); for(int i = start; i < len; ++i) { pos = i - start; if(i == nextStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - fullStepMarkerLength)); int number = qRound(stepCount * numberStep); QString numberText = QString::number(number); int x = pos; if (d->rightToLeft) { // this is done in a hacky way with the fine tuning done above numberText = QString::number(hackyLength - stepCount * numberStep); } painter.setPen(numberPen); painter.drawText(QPointF(x-fontMetrics.width(numberText)/2.0, rectangle.bottom() -fullStepMarkerLength -measurementTextAboveBelowMargin), numberText); painter.setPen(markerPen); ++stepCount; nextStep = qRound(d->viewConverter->documentToViewX( d->unit.fromUserValue(numberStep * stepCount))); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextHalfStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - halfStepMarkerLength)); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextQuarterStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - quarterStepMarkerLength)); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } } // Draw the mouse indicator const int mouseCoord = d->mouseCoordinate - start; if (d->selected == KoRulerPrivate::None || d->selected == KoRulerPrivate::HotSpot) { const qreal top = rectangle.y() + 1; const qreal bottom = rectangle.bottom() -1; if (d->selected == KoRulerPrivate::None && d->showMousePosition && mouseCoord > 0 && mouseCoord < rectangle.width() ) painter.drawLine(QPointF(mouseCoord, top), QPointF(mouseCoord, bottom)); foreach (const KoRulerPrivate::HotSpotData & hp, d->hotspots) { const qreal x = d->viewConverter->documentToViewX(hp.position) + d->offset; painter.drawLine(QPointF(x, top), QPointF(x, bottom)); } } } void HorizontalPaintingStrategy::drawIndents(const KoRulerPrivate *d, QPainter &painter) { QPolygonF polygon; painter.setBrush(d->ruler->palette().brush(QPalette::Base)); painter.setRenderHint( QPainter::Antialiasing ); qreal x; // Draw first line start indent if (d->rightToLeft) x = d->effectiveActiveRangeEnd() - d->firstLineIndent - d->paragraphIndent; else x = d->effectiveActiveRangeStart() + d->firstLineIndent + d->paragraphIndent; // convert and use the +0.5 to go to nearest integer so that the 0.5 added below ensures sharp lines x = int(d->viewConverter->documentToViewX(x) + d->offset + 0.5); polygon << QPointF(x+6.5, 0.5) << QPointF(x+0.5, 8.5) << QPointF(x-5.5, 0.5) << QPointF(x+5.5, 0.5); painter.drawPolygon(polygon); // draw the hanging indent. if (d->rightToLeft) x = d->effectiveActiveRangeStart() + d->endIndent; else x = d->effectiveActiveRangeStart() + d->paragraphIndent; // convert and use the +0.5 to go to nearest integer so that the 0.5 added below ensures sharp lines x = int(d->viewConverter->documentToViewX(x) + d->offset + 0.5); const int bottom = d->ruler->height(); polygon.clear(); polygon << QPointF(x+6.5, bottom - 0.5) << QPointF(x+0.5, bottom - 8.5) << QPointF(x-5.5, bottom - 0.5) << QPointF(x+5.5, bottom - 0.5); painter.drawPolygon(polygon); // Draw end-indent or paragraph indent if mode is rightToLeft qreal diff; if (d->rightToLeft) diff = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - d->paragraphIndent) + d->offset - x; else diff = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - d->endIndent) + d->offset - x; polygon.translate(diff, 0); painter.drawPolygon(polygon); } QSize HorizontalPaintingStrategy::sizeHint() { // assumes that digits for the number only use glyphs which do not go below the baseline const QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); const int digitsHeight = fm.ascent() + 1; // +1 for baseline const int minimum = digitsHeight + fullStepMarkerLength + 2*measurementTextAboveBelowMargin; return QSize(0, minimum); } QRectF VerticalPaintingStrategy::drawBackground(const KoRulerPrivate *d, QPainter &painter) { lengthInPixel = d->viewConverter->documentToViewY(d->rulerLength); QRectF rectangle; rectangle.setX(0); rectangle.setY(qMax(0, d->offset)); rectangle.setWidth(d->ruler->width() - 1.0); rectangle.setHeight(qMin(qreal(d->ruler->height() - 1.0 - rectangle.y()), (d->offset >= 0) ? lengthInPixel : lengthInPixel + d->offset)); QRectF activeRangeRectangle; activeRangeRectangle.setX(rectangle.x() + 1); activeRangeRectangle.setY(qMax(rectangle.y() + 1, d->viewConverter->documentToViewY(d->effectiveActiveRangeStart()) + d->offset)); activeRangeRectangle.setWidth(rectangle.width() - 2); activeRangeRectangle.setBottom(qMin(rectangle.bottom() - 1, d->viewConverter->documentToViewY(d->effectiveActiveRangeEnd()) + d->offset)); - painter.setPen(d->ruler->palette().color(QPalette::Mid)); + painter.setPen(QPen(d->ruler->palette().color(QPalette::Mid), 0)); painter.fillRect(rectangle,d->ruler->palette().color(QPalette::AlternateBase)); painter.drawRect(rectangle); if(d->effectiveActiveRangeStart() != d->effectiveActiveRangeEnd()) painter.fillRect(activeRangeRectangle, d->ruler->palette().brush(QPalette::Base)); if(d->showSelectionBorders) { // Draw first selection border if(d->firstSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewY(d->firstSelectionBorder) + d->offset; painter.drawLine(QPointF(rectangle.x() + 1, border), QPointF(rectangle.right() - 1, border)); } // Draw second selection border if(d->secondSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewY(d->secondSelectionBorder) + d->offset; painter.drawLine(QPointF(rectangle.x() + 1, border), QPointF(rectangle.right() - 1, border)); } } return rectangle; } void VerticalPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF &rectangle) { qreal numberStep = d->numberStepForUnit(); // number step in unit int numberStepPixel = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep))); if (numberStepPixel <= 0) return; const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); painter.setFont(font); // Calc the longest text length int textLength = 0; for(int i = 0; i < lengthInPixel; i += numberStepPixel) { int number = qRound((i / numberStepPixel) * numberStep); textLength = qMax(textLength, fontMetrics.width(QString::number(number))); } textLength += 4; // Add some padding if (numberStepPixel == 0 || numberStep == 0) return; // Change number step so all digits will fit while(textLength > numberStepPixel) { numberStepPixel += numberStepPixel; numberStep += numberStep; } // Calc the first number step const int start = d->offset < 0 ? qAbs(d->offset) : 0; // make a little hack so rulers shows correctly inversed number aligned int stepCount = (start / numberStepPixel) + 1; int halfStepCount = (start / qRound(numberStepPixel * 0.5)) + 1; int quarterStepCount = (start / qRound(numberStepPixel * 0.25)) + 1; - const QPen numberPen(d->ruler->palette().color(QPalette::Text)); - const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text)); + const QPen numberPen(d->ruler->palette().color(QPalette::Text), 0); + const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text), 0); painter.setPen(markerPen); if(d->offset > 0) painter.translate(0, d->offset); const int len = qRound(rectangle.height()) + start; int nextStep = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep * stepCount))); int nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); int nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); int pos = 0; for(int i = start; i < len; ++i) { pos = i - start; if(i == nextStep) { painter.save(); painter.translate(rectangle.right()-fullStepMarkerLength, pos); if(pos != 0) painter.drawLine(QPointF(0, 0), QPointF(fullStepMarkerLength-1, 0)); painter.rotate(-90); int number = qRound(stepCount * numberStep); QString numberText = QString::number(number); painter.setPen(numberPen); painter.drawText(QPointF(-fontMetrics.width(numberText) / 2.0, -measurementTextAboveBelowMargin), numberText); painter.restore(); ++stepCount; nextStep = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep * stepCount))); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextHalfStep) { if(pos != 0) painter.drawLine(QPointF(rectangle.right() - halfStepMarkerLength, pos), QPointF(rectangle.right() - 1, pos)); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextQuarterStep) { if(pos != 0) painter.drawLine(QPointF(rectangle.right() - quarterStepMarkerLength, pos), QPointF(rectangle.right() - 1, pos)); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } } // Draw the mouse indicator const int mouseCoord = d->mouseCoordinate - start; if (d->selected == KoRulerPrivate::None || d->selected == KoRulerPrivate::HotSpot) { const qreal left = rectangle.left() + 1; const qreal right = rectangle.right() -1; if (d->selected == KoRulerPrivate::None && d->showMousePosition && mouseCoord > 0 && mouseCoord < rectangle.height() ) painter.drawLine(QPointF(left, mouseCoord), QPointF(right, mouseCoord)); foreach (const KoRulerPrivate::HotSpotData & hp, d->hotspots) { const qreal y = d->viewConverter->documentToViewY(hp.position) + d->offset; painter.drawLine(QPointF(left, y), QPointF(right, y)); } } } QSize VerticalPaintingStrategy::sizeHint() { // assumes that digits for the number only use glyphs which do not go below the baseline const QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); const int digitsHeight = fm.ascent() + 1; // +1 for baseline const int minimum = digitsHeight + fullStepMarkerLength + 2*measurementTextAboveBelowMargin; return QSize(minimum, 0); } void HorizontalDistancesPaintingStrategy::drawDistanceLine(const KoRulerPrivate *d, QPainter &painter, const qreal start, const qreal end) { // Don't draw too short lines if (qMax(start, end) - qMin(start, end) < 1) return; painter.save(); painter.translate(d->offset, d->ruler->height() / 2); - painter.setPen(d->ruler->palette().color(QPalette::Text)); + painter.setPen(QPen(d->ruler->palette().color(QPalette::Text), 0)); painter.setBrush(d->ruler->palette().color(QPalette::Text)); QLineF line(QPointF(d->viewConverter->documentToViewX(start), 0), QPointF(d->viewConverter->documentToViewX(end), 0)); QPointF midPoint = line.pointAt(0.5); // Draw the label text const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); QString label = d->unit.toUserStringValue( d->viewConverter->viewToDocumentX(line.length())) + ' ' + d->unit.symbol(); QPointF labelPosition = QPointF(midPoint.x() - fontMetrics.width(label)/2, midPoint.y() + fontMetrics.ascent()/2); painter.setFont(font); painter.drawText(labelPosition, label); // Draw the arrow lines qreal arrowLength = (line.length() - fontMetrics.width(label)) / 2 - 2; arrowLength = qMax(qreal(0.0), arrowLength); QLineF startArrow(line.p1(), line.pointAt(arrowLength / line.length())); QLineF endArrow(line.p2(), line.pointAt(1.0 - arrowLength / line.length())); painter.drawLine(startArrow); painter.drawLine(endArrow); // Draw the arrow heads QPolygonF arrowHead; arrowHead << line.p1() << QPointF(line.x1()+3, line.y1()-3) << QPointF(line.x1()+3, line.y1()+3); painter.drawPolygon(arrowHead); arrowHead.clear(); arrowHead << line.p2() << QPointF(line.x2()-3, line.y2()-3) << QPointF(line.x2()-3, line.y2()+3); painter.drawPolygon(arrowHead); painter.restore(); } void HorizontalDistancesPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF&) { QList points; points << 0.0; points << d->effectiveActiveRangeStart() + d->paragraphIndent + d->firstLineIndent; points << d->effectiveActiveRangeStart() + d->paragraphIndent; points << d->effectiveActiveRangeEnd() - d->endIndent; points << d->effectiveActiveRangeStart(); points << d->effectiveActiveRangeEnd(); points << d->rulerLength; qSort(points.begin(), points.end()); QListIterator i(points); i.next(); while (i.hasNext() && i.hasPrevious()) { drawDistanceLine(d, painter, i.peekPrevious(), i.peekNext()); i.next(); } } KoRulerPrivate::KoRulerPrivate(KoRuler *parent, const KoViewConverter *vc, Qt::Orientation o) : unit(KoUnit(KoUnit::Point)), orientation(o), viewConverter(vc), offset(0), rulerLength(0), activeRangeStart(0), activeRangeEnd(0), activeOverrideRangeStart(0), activeOverrideRangeEnd(0), mouseCoordinate(-1), showMousePosition(0), showSelectionBorders(false), firstSelectionBorder(0), secondSelectionBorder(0), showIndents(false), firstLineIndent(0), paragraphIndent(0), endIndent(0), showTabs(false), relativeTabs(false), tabMoved(false), originalIndex(-1), currentIndex(0), rightToLeft(false), selected(None), selectOffset(0), tabChooser(0), normalPaintingStrategy(o == Qt::Horizontal ? (PaintingStrategy*)new HorizontalPaintingStrategy() : (PaintingStrategy*)new VerticalPaintingStrategy()), distancesPaintingStrategy((PaintingStrategy*)new HorizontalDistancesPaintingStrategy()), paintingStrategy(normalPaintingStrategy), ruler(parent), guideCreationStarted(false) { } KoRulerPrivate::~KoRulerPrivate() { delete normalPaintingStrategy; delete distancesPaintingStrategy; } qreal KoRulerPrivate::numberStepForUnit() const { switch(unit.type()) { case KoUnit::Inch: case KoUnit::Centimeter: case KoUnit::Decimeter: case KoUnit::Millimeter: return 1.0; case KoUnit::Pica: case KoUnit::Cicero: return 10.0; case KoUnit::Point: default: return 100.0; } } qreal KoRulerPrivate::doSnapping(const qreal value) const { qreal numberStep = unit.fromUserValue(numberStepForUnit()/4.0); return numberStep * qRound(value / numberStep); } KoRulerPrivate::Selection KoRulerPrivate::selectionAtPosition(const QPoint & pos, int *selectOffset ) { const int height = ruler->height(); if (rightToLeft) { int x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - firstLineIndent - paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x +8 && pos.y() < height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::FirstLineIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x +8 && pos.y() > height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::ParagraphIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + endIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::EndIndent; } } else { int x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + firstLineIndent + paragraphIndent) + offset); if (pos.x() >= x -8 && pos.x() <= x + 8 && pos.y() < height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::FirstLineIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8 && pos.y() > height/2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::ParagraphIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - endIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::EndIndent; } } return KoRulerPrivate::None; } int KoRulerPrivate::hotSpotIndex(const QPoint & pos) { for(int counter = 0; counter < hotspots.count(); counter++) { bool hit; if (orientation == Qt::Horizontal) hit = qAbs(viewConverter->documentToViewX(hotspots[counter].position) - pos.x() + offset) < 3; else hit = qAbs(viewConverter->documentToViewY(hotspots[counter].position) - pos.y() + offset) < 3; if (hit) return counter; } return -1; } qreal KoRulerPrivate::effectiveActiveRangeStart() const { if (activeOverrideRangeStart != activeOverrideRangeEnd) { return activeOverrideRangeStart; } else { return activeRangeStart; } } qreal KoRulerPrivate::effectiveActiveRangeEnd() const { if (activeOverrideRangeStart != activeOverrideRangeEnd) { return activeOverrideRangeEnd; } else { return activeRangeEnd; } } void KoRulerPrivate::emitTabChanged() { KoRuler::Tab tab; if (currentIndex >= 0) tab = tabs[currentIndex]; emit ruler->tabChanged(originalIndex, currentIndex >= 0 ? &tab : 0); } KoRuler::KoRuler(QWidget* parent, Qt::Orientation orientation, const KoViewConverter* viewConverter) : QWidget(parent) , d( new KoRulerPrivate( this, viewConverter, orientation) ) { setMouseTracking( true ); } KoRuler::~KoRuler() { delete d; } KoUnit KoRuler::unit() const { return d->unit; } void KoRuler::setUnit(const KoUnit &unit) { d->unit = unit; update(); } qreal KoRuler::rulerLength() const { return d->rulerLength; } Qt::Orientation KoRuler::orientation() const { return d->orientation; } void KoRuler::setOffset(int offset) { d->offset = offset; update(); } void KoRuler::setRulerLength(qreal length) { d->rulerLength = length; update(); } void KoRuler::paintEvent(QPaintEvent* event) { QPainter painter(this); painter.setClipRegion(event->region()); painter.save(); QRectF rectangle = d->paintingStrategy->drawBackground(d, painter); painter.restore(); painter.save(); d->paintingStrategy->drawMeasurements(d, painter, rectangle); painter.restore(); if (d->showIndents) { painter.save(); d->paintingStrategy->drawIndents(d, painter); painter.restore(); } d->paintingStrategy->drawTabs(d, painter); } QSize KoRuler::minimumSizeHint() const { return d->paintingStrategy->sizeHint(); } QSize KoRuler::sizeHint() const { return d->paintingStrategy->sizeHint(); } void KoRuler::setActiveRange(qreal start, qreal end) { d->activeRangeStart = start; d->activeRangeEnd = end; update(); } void KoRuler::setOverrideActiveRange(qreal start, qreal end) { d->activeOverrideRangeStart = start; d->activeOverrideRangeEnd = end; update(); } void KoRuler::updateMouseCoordinate(int coordinate) { if(d->mouseCoordinate == coordinate) return; d->mouseCoordinate = coordinate; update(); } void KoRuler::setShowMousePosition(bool show) { d->showMousePosition = show; update(); } bool KoRuler::showMousePosition() const { return d->showMousePosition; } void KoRuler::setRightToLeft(bool isRightToLeft) { d->rightToLeft = isRightToLeft; update(); } void KoRuler::setShowIndents(bool show) { d->showIndents = show; update(); } void KoRuler::setFirstLineIndent(qreal indent) { d->firstLineIndent = indent; if (d->showIndents) { update(); } } void KoRuler::setParagraphIndent(qreal indent) { d->paragraphIndent = indent; if (d->showIndents) { update(); } } void KoRuler::setEndIndent(qreal indent) { d->endIndent = indent; if (d->showIndents) { update(); } } qreal KoRuler::firstLineIndent() const { return d->firstLineIndent; } qreal KoRuler::paragraphIndent() const { return d->paragraphIndent; } qreal KoRuler::endIndent() const { return d->endIndent; } QWidget *KoRuler::tabChooser() { if ((d->tabChooser == 0) && (d->orientation == Qt::Horizontal)) { d->tabChooser = new RulerTabChooser(parentWidget()); d->tabChooser->setShowTabs(d->showTabs); } return d->tabChooser; } void KoRuler::setShowSelectionBorders(bool show) { d->showSelectionBorders = show; update(); } void KoRuler::updateSelectionBorders(qreal first, qreal second) { d->firstSelectionBorder = first; d->secondSelectionBorder = second; if(d->showSelectionBorders) update(); } void KoRuler::setShowTabs(bool show) { if (d->showTabs == show) { return; } d->showTabs = show; if (d->tabChooser) { d->tabChooser->setShowTabs(show); } update(); } void KoRuler::setRelativeTabs(bool relative) { d->relativeTabs = relative; if (d->showTabs) { update(); } } void KoRuler::updateTabs(const QList &tabs, qreal tabDistance) { d->tabs = tabs; d->tabDistance = tabDistance; if (d->showTabs) { update(); } } QList KoRuler::tabs() const { QList answer = d->tabs; qSort(answer.begin(), answer.end(), compareTabs); return answer; } void KoRuler::setPopupActionList(const QList &popupActionList) { d->popupActions = popupActionList; } QList KoRuler::popupActionList() const { return d->popupActions; } void KoRuler::mousePressEvent ( QMouseEvent* ev ) { d->tabMoved = false; d->selected = KoRulerPrivate::None; if (ev->button() == Qt::RightButton && !d->popupActions.isEmpty()) QMenu::exec(d->popupActions, ev->globalPos()); if (ev->button() != Qt::LeftButton) { ev->ignore(); return; } /** * HACK ALERT: We don't need all that indentation stuff in Krita. * Just ensure the rulers are created correctly. */ if (d->selected == KoRulerPrivate::None) { d->guideCreationStarted = true; return; } QPoint pos = ev->pos(); if (d->showTabs) { int i = 0; int x; foreach (const Tab & t, d->tabs) { if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - t.position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + t.position) + d->offset; } if (pos.x() >= x-6 && pos.x() <= x+6) { d->selected = KoRulerPrivate::Tab; d->selectOffset = x - pos.x(); d->currentIndex = i; break; } i++; } d->originalIndex = d->currentIndex; } if (d->selected == KoRulerPrivate::None) d->selected = d->selectionAtPosition(ev->pos(), &d->selectOffset); if (d->selected == KoRulerPrivate::None) { int hotSpotIndex = d->hotSpotIndex(ev->pos()); if (hotSpotIndex >= 0) { d->selected = KoRulerPrivate::HotSpot; update(); } } if (d->showTabs && d->selected == KoRulerPrivate::None) { // still haven't found something so let assume the user wants to add a tab qreal tabpos; if (d->rightToLeft) { tabpos = d->viewConverter->viewToDocumentX(pos.x() - d->offset) + d->effectiveActiveRangeEnd() + (d->relativeTabs ? d->paragraphIndent : 0); } else { tabpos = d->viewConverter->viewToDocumentX(pos.x() - d->offset) - d->effectiveActiveRangeStart() - (d->relativeTabs ? d->paragraphIndent : 0); } Tab t = {tabpos, d->tabChooser ? d->tabChooser->type() : d->rightToLeft ? QTextOption::RightTab : QTextOption::LeftTab}; d->tabs.append(t); d->selectOffset = 0; d->selected = KoRulerPrivate::Tab; d->currentIndex = d->tabs.count() - 1; d->originalIndex = -1; // new! update(); } if (d->orientation == Qt::Horizontal && (ev->modifiers() & Qt::ShiftModifier) && (d->selected == KoRulerPrivate::FirstLineIndent || d->selected == KoRulerPrivate::ParagraphIndent || d->selected == KoRulerPrivate::Tab || d->selected == KoRulerPrivate::EndIndent)) d->paintingStrategy = d->distancesPaintingStrategy; if (d->selected != KoRulerPrivate::None) emit aboutToChange(); } void KoRuler::mouseReleaseEvent ( QMouseEvent* ev ) { ev->accept(); if (d->selected == KoRulerPrivate::None && d->guideCreationStarted) { d->guideCreationStarted = false; emit guideCreationFinished(d->orientation, ev->globalPos()); } else if (d->selected == KoRulerPrivate::Tab) { if (d->originalIndex >= 0 && !d->tabMoved) { int type = d->tabs[d->currentIndex].type; type++; if (type > 3) type = 0; d->tabs[d->currentIndex].type = static_cast (type); update(); } d->emitTabChanged(); } else if( d->selected != KoRulerPrivate::None) emit indentsChanged(true); else ev->ignore(); d->paintingStrategy = d->normalPaintingStrategy; d->selected = KoRulerPrivate::None; } void KoRuler::mouseMoveEvent ( QMouseEvent* ev ) { QPoint pos = ev->pos(); if (d->selected == KoRulerPrivate::None && d->guideCreationStarted) { emit guideCreationInProgress(d->orientation, ev->globalPos()); ev->accept(); update(); return; } qreal activeLength = d->effectiveActiveRangeEnd() - d->effectiveActiveRangeStart(); switch (d->selected) { case KoRulerPrivate::FirstLineIndent: if (d->rightToLeft) d->firstLineIndent = d->effectiveActiveRangeEnd() - d->paragraphIndent - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->firstLineIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart() - d->paragraphIndent; if( ! (ev->modifiers() & Qt::ShiftModifier)) { d->firstLineIndent = d->doSnapping(d->firstLineIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } emit indentsChanged(false); break; case KoRulerPrivate::ParagraphIndent: if (d->rightToLeft) d->paragraphIndent = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->paragraphIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); if( ! (ev->modifiers() & Qt::ShiftModifier)) { d->paragraphIndent = d->doSnapping(d->paragraphIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } if (d->paragraphIndent + d->endIndent > activeLength) d->paragraphIndent = activeLength - d->endIndent; emit indentsChanged(false); break; case KoRulerPrivate::EndIndent: if (d->rightToLeft) d->endIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); else d->endIndent = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); if (!(ev->modifiers() & Qt::ShiftModifier)) { d->endIndent = d->doSnapping(d->endIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } if (d->paragraphIndent + d->endIndent > activeLength) d->endIndent = activeLength - d->paragraphIndent; emit indentsChanged(false); break; case KoRulerPrivate::Tab: d->tabMoved = true; if (d->currentIndex < 0) { // tab is deleted. if (ev->pos().y() < height()) { // reinstante it. d->currentIndex = d->tabs.count(); d->tabs.append(d->deletedTab); } else { break; } } if (d->rightToLeft) d->tabs[d->currentIndex].position = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->tabs[d->currentIndex].position = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); if (!(ev->modifiers() & Qt::ShiftModifier)) d->tabs[d->currentIndex].position = d->doSnapping(d->tabs[d->currentIndex].position); if (d->tabs[d->currentIndex].position < 0) d->tabs[d->currentIndex].position = 0; if (d->tabs[d->currentIndex].position > activeLength) d->tabs[d->currentIndex].position = activeLength; if (ev->pos().y() > height() + OutsideRulerThreshold ) { // moved out of the ruler, delete it. d->deletedTab = d->tabs.takeAt(d->currentIndex); d->currentIndex = -1; // was that a temporary added tab? if ( d->originalIndex == -1 ) emit guideLineCreated(d->orientation, d->orientation == Qt::Horizontal ? d->viewConverter->viewToDocumentY(ev->pos().y()) : d->viewConverter->viewToDocumentX(ev->pos().x())); } d->emitTabChanged(); break; case KoRulerPrivate::HotSpot: qreal newPos; if (d->orientation == Qt::Horizontal) newPos= d->viewConverter->viewToDocumentX(pos.x() - d->offset); else newPos= d->viewConverter->viewToDocumentY(pos.y() - d->offset); d->hotspots[d->currentIndex].position = newPos; emit hotSpotChanged(d->hotspots[d->currentIndex].id, newPos); break; case KoRulerPrivate::None: d->mouseCoordinate = (d->orientation == Qt::Horizontal ? pos.x() : pos.y()) - d->offset; int hotSpotIndex = d->hotSpotIndex(pos); if (hotSpotIndex >= 0) { setCursor(QCursor( d->orientation == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor )); break; } unsetCursor(); KoRulerPrivate::Selection selection = d->selectionAtPosition(pos); QString text; switch(selection) { case KoRulerPrivate::FirstLineIndent: text = i18n("First line indent"); break; case KoRulerPrivate::ParagraphIndent: text = i18n("Left indent"); break; case KoRulerPrivate::EndIndent: text = i18n("Right indent"); break; case KoRulerPrivate::None: if (ev->buttons() & Qt::LeftButton) { if (d->orientation == Qt::Horizontal && ev->pos().y() > height() + OutsideRulerThreshold) emit guideLineCreated(d->orientation, d->viewConverter->viewToDocumentY(ev->pos().y())); else if (d->orientation == Qt::Vertical && ev->pos().x() > width() + OutsideRulerThreshold) emit guideLineCreated(d->orientation, d->viewConverter->viewToDocumentX(ev->pos().x())); } break; default: break; } setToolTip(text); } update(); } void KoRuler::clearHotSpots() { if (d->hotspots.isEmpty()) return; d->hotspots.clear(); update(); } void KoRuler::setHotSpot(qreal position, int id) { uint hotspotCount = d->hotspots.count(); for( uint i = 0; i < hotspotCount; ++i ) { KoRulerPrivate::HotSpotData & hs = d->hotspots[i]; if (hs.id == id) { hs.position = position; update(); return; } } // not there yet, then insert it. KoRulerPrivate::HotSpotData hs; hs.position = position; hs.id = id; d->hotspots.append(hs); } bool KoRuler::removeHotSpot(int id) { QList::Iterator iter = d->hotspots.begin(); while(iter != d->hotspots.end()) { if (iter->id == id) { d->hotspots.erase(iter); update(); return true; } } return false; } void KoRuler::createGuideToolConnection(KoCanvasBase *canvas) { Q_ASSERT(canvas); KoToolBase *tool = KoToolManager::instance()->toolById(canvas, QLatin1String("GuidesTool")); if (!tool) return; // It's perfectly fine to have no guides tool, we don't have to warn the user about it connect(this, SIGNAL(guideLineCreated(Qt::Orientation,qreal)), tool, SLOT(createGuideLine(Qt::Orientation,qreal))); } diff --git a/libs/widgetutils/KoGroupButton.cpp b/libs/widgetutils/KoGroupButton.cpp index a97b65b7d2..77a51996a7 100644 --- a/libs/widgetutils/KoGroupButton.cpp +++ b/libs/widgetutils/KoGroupButton.cpp @@ -1,144 +1,144 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Aurélien Gâteau Copyright (C) 2012 Jean-Nicolas Artaud Copyright (C) 2012 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoGroupButton.h" // Qt #include #include #include #include // KF5 #include class Q_DECL_HIDDEN KoGroupButton::Private { public: Private(KoGroupButton *qq, const GroupPosition position) : groupPosition(position) { // Make the policy closer to QPushButton's default but horizontal shouldn't be Fixed, // otherwise spacing gets broken qq->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } GroupPosition groupPosition; }; KoGroupButton::KoGroupButton(GroupPosition position, QWidget* parent) : QToolButton(parent), d(new Private(this, position)) { } KoGroupButton::KoGroupButton(QWidget* parent) : QToolButton(parent), d(new Private(this, NoGroup)) { } KoGroupButton::~KoGroupButton() { delete d; } void KoGroupButton::setGroupPosition(KoGroupButton::GroupPosition groupPosition) { d->groupPosition = groupPosition; } KoGroupButton::GroupPosition KoGroupButton::groupPosition() const { return d->groupPosition; } void KoGroupButton::paintEvent(QPaintEvent* event) { if (groupPosition() == NoGroup) { QToolButton::paintEvent(event); return; } QStylePainter painter(this); QStyleOptionToolButton opt; initStyleOption(&opt); QStyleOptionToolButton panelOpt = opt; // Panel QRect& panelRect = panelOpt.rect; switch (groupPosition()) { case GroupLeft: panelRect.setWidth(panelRect.width() * 2); break; case GroupCenter: panelRect.setLeft(panelRect.left() - panelRect.width()); panelRect.setWidth(panelRect.width() * 3); break; case GroupRight: panelRect.setLeft(panelRect.left() - panelRect.width()); break; case NoGroup: Q_ASSERT(0); } if (autoRaise()) { if (!isChecked() && !isDown() && !(panelOpt.state & QStyle::State_MouseOver)) { // Use 'pushed' appearance for all buttons, but those that are not really pushed // are drawn with less contrast and are toned down. panelOpt.state |= (QStyle::State_On | QStyle::State_Sunken); QPalette panelPal(panelOpt.palette); QColor c; c = panelPal.color(QPalette::Button); c.setAlpha(50); panelPal.setColor(QPalette::Button, c); c = panelPal.color(QPalette::Window); c.setAlpha(50); panelPal.setColor(QPalette::Window, c); panelOpt.palette = panelPal; painter.setOpacity(0.5); } } painter.drawPrimitive(QStyle::PE_PanelButtonTool, panelOpt); painter.setOpacity(1.0); // Separator //! @todo make specific fixes for styles such as Plastique, Cleanlooks if there's practical no alernative const int y1 = opt.rect.top() + 1; const int y2 = opt.rect.bottom() - 1; painter.setOpacity(0.4); if (d->groupPosition != GroupRight) { const int x = opt.rect.right(); - painter.setPen(opt.palette.color(QPalette::Dark)); + painter.setPen(QPen(opt.palette.color(QPalette::Dark), 0)); painter.drawLine(x, y1, x, y2); } painter.setOpacity(1.0); // Text painter.drawControl(QStyle::CE_ToolButtonLabel, opt); // Filtering message on tooltip text for CJK to remove accelerators. // Quoting ktoolbar.cpp: // """ // CJK languages use more verbose accelerator marker: they add a Latin // letter in parenthesis, and put accelerator on that. Hence, the default // removal of ampersand only may not be enough there, instead the whole // parenthesis construct should be removed. Provide these filtering i18n // messages so that translators can use Transcript for custom removal. // """ if (!actions().isEmpty()) { QAction* action = actions().first(); setToolTip(i18nc("@info:tooltip of custom triple button", "%1", action->toolTip())); } }