diff --git a/krita/data/actions/InteractionTool.action b/krita/data/actions/InteractionTool.action index 575906569b..3852938cfd 100644 --- a/krita/data/actions/InteractionTool.action +++ b/krita/data/actions/InteractionTool.action @@ -1,262 +1,292 @@ Interaction Tool Raise Ctrl+] Raise object-order-raise-calligra false &Raise Align Right Align Right object-align-horizontal-right-calligra false Align Right Ungroup Ungroup object-ungroup-calligra false Ungroup Send to Back Ctrl+Shift+[ Send to Back object-order-back-calligra false Send to &Back Bring to Front Ctrl+Shift+] Bring to Front object-order-front-calligra false Bring to &Front Vertically Center Vertically Center object-align-vertical-center-calligra false Vertically Center Group Group object-group-calligra false Group Align Left Align Left object-align-horizontal-left-calligra false Align Left Align Top Align Top object-align-vertical-top-calligra false Align Top Horizontally Center Horizontally Center object-align-horizontal-center-calligra false Horizontally Center Lower Ctrl+[ Lower object-order-lower-calligra false &Lower Align Bottom Align Bottom object-align-vertical-bottom-calligra false Align Bottom Distribute Left Distribute left edges equidistantly distribute-horizontal-left false Distribute Centers Horizontally Distribute centers equidistantly horizontally distribute-horizontal-center false Distribute Right Distribute right edges equidistantly distribute-horizontal-right false Distribute Horizontal Gap Make horizontal gaps between objects equal distribute-horizontal false Distribute Top Distribute top edges equidistantly distribute-vertical-top false Distribute Centers Vertically Distribute centers equidistantly vertically distribute-vertical-center false Distribute Bottom Distribute bottom edges equidistantly distribute-vertical-bottom false Distribute Vertical Gap Make vertical gaps between objects equal distribute-vertical false Rotate 90° CW Rotate object 90° clockwise object-rotate-right false Rotate 90° CCW Rotate object 90° counterclockwise object-rotate-left false Rotate 180° CCW Rotate object 180° false Mirror Horizontally Mirror object horizontally symmetry-horizontal false Mirror Vertically Mirror object vertically symmetry-vertical false Reset Transformations Reset object transformations false + + Unite + Create boolean onion of multiple objects + + + + + false + + + + Intersect + Create boolean intersection of multiple objects + + + + + false + + + + Subtract + Subtract multiple objects from the first selected one + + + + + false + + diff --git a/libs/basicflakes/tools/KoCreatePathTool.cpp b/libs/basicflakes/tools/KoCreatePathTool.cpp index fa8027221f..652e3bf8eb 100644 --- a/libs/basicflakes/tools/KoCreatePathTool.cpp +++ b/libs/basicflakes/tools/KoCreatePathTool.cpp @@ -1,500 +1,500 @@ /* This file is part of the KDE project * * Copyright (C) 2006 Thorsten Zachmann * Copyright (C) 2008-2010 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCreatePathTool.h" #include "KoCreatePathTool_p.h" #include #include "KoPointerEvent.h" #include "KoPathShape.h" #include "KoSelection.h" #include "KoDocumentResourceManager.h" #include "KoShapePaintingContext.h" #include "KoShapeStroke.h" #include "KoCanvasBase.h" #include "kis_int_parse_spin_box.h" #include #include "kis_canvas_resource_provider.h" #include #include #include #include #include #include #include KoCreatePathTool::KoCreatePathTool(KoCanvasBase *canvas) : KoToolBase(*(new KoCreatePathToolPrivate(this, canvas))) { } KoCreatePathTool::~KoCreatePathTool() { } void KoCreatePathTool::paint(QPainter &painter, const KoViewConverter &converter) { Q_D(KoCreatePathTool); if (pathStarted()) { painter.save(); paintPath(*(d->shape), painter, converter); painter.restore(); KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, d->shape, converter, d->handleRadius); const bool firstPointActive = d->firstPoint == d->activePoint; if (d->pointIsDragged || firstPointActive) { const bool onlyPaintActivePoints = false; KoPathPoint::PointTypes paintFlags = KoPathPoint::ControlPoint2; if (d->activePoint->activeControlPoint1()) { paintFlags |= KoPathPoint::ControlPoint1; } helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandles()); d->activePoint->paint(helper, paintFlags, onlyPaintActivePoints); } if (!firstPointActive) { helper.setHandleStyle(d->mouseOverFirstPoint ? KisHandleStyle::highlightedPrimaryHandles() : KisHandleStyle::primarySelection()); d->firstPoint->paint(helper, KoPathPoint::Node); } } if (d->hoveredPoint) { KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, d->hoveredPoint->parent(), converter, d->handleRadius); helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandles()); d->hoveredPoint->paint(helper, KoPathPoint::Node); } painter.save(); KoShape::applyConversion(painter, converter); canvas()->snapGuide()->paint(painter, converter); painter.restore(); } void KoCreatePathTool::paintPath(KoPathShape& pathShape, QPainter &painter, const KoViewConverter &converter) { Q_D(KoCreatePathTool); painter.setTransform(pathShape.absoluteTransformation(&converter) * painter.transform()); painter.save(); KoShapePaintingContext paintContext; //FIXME pathShape.paint(painter, converter, paintContext); painter.restore(); if (pathShape.stroke()) { painter.save(); pathShape.stroke()->paint(d->shape, painter, converter); painter.restore(); } } void KoCreatePathTool::mousePressEvent(KoPointerEvent *event) { Q_D(KoCreatePathTool); //Right click removes last point if (event->button() == Qt::RightButton) { removeLastPoint(); return; } const bool isOverFirstPoint = d->shape && handleGrabRect(d->firstPoint->point()).contains(event->point); bool haveCloseModifier = (listeningToModifiers() && (event->modifiers() & Qt::ShiftModifier)); if ((event->button() == Qt::LeftButton) && haveCloseModifier && !isOverFirstPoint) { endPathWithoutLastPoint(); return; } d->finishAfterThisPoint = false; if (pathStarted()) { if (isOverFirstPoint) { d->activePoint->setPoint(d->firstPoint->point()); canvas()->updateCanvas(d->shape->boundingRect()); canvas()->updateCanvas(canvas()->snapGuide()->boundingRect()); if (haveCloseModifier) { d->shape->closeMerge(); // we are closing the path, so reset the existing start path point d->existingStartPoint = 0; // finish path endPath(); } else { // the path shape will get closed when the user releases // the mouse button d->finishAfterThisPoint = true; } } else { canvas()->updateCanvas(canvas()->snapGuide()->boundingRect()); QPointF point = canvas()->snapGuide()->snap(event->point, event->modifiers()); // check whether we hit an start/end node of an existing path d->existingEndPoint = d->endPointAtPosition(point); if (d->existingEndPoint.isValid() && d->existingEndPoint != d->existingStartPoint) { point = d->existingEndPoint.path->shapeToDocument(d->existingEndPoint.point->point()); d->activePoint->setPoint(point); // finish path endPath(); } else { d->activePoint->setPoint(point); canvas()->updateCanvas(d->shape->boundingRect()); canvas()->updateCanvas(canvas()->snapGuide()->boundingRect()); } } } else { KoPathShape *pathShape = new KoPathShape(); d->shape = pathShape; pathShape->setShapeId(KoPathShapeId); KoShapeStrokeSP stroke(new KoShapeStroke()); const qreal size = canvas()->resourceManager()->resource(KisCanvasResourceProvider::Size).toReal(); stroke->setLineWidth(canvas()->unit().fromUserValue(size)); stroke->setColor(canvas()->resourceManager()->foregroundColor().toQColor()); pathShape->setStroke(stroke); canvas()->updateCanvas(canvas()->snapGuide()->boundingRect()); QPointF point = canvas()->snapGuide()->snap(event->point, event->modifiers()); // check whether we hit an start/end node of an existing path d->existingStartPoint = d->endPointAtPosition(point); if (d->existingStartPoint.isValid()) { point = d->existingStartPoint.path->shapeToDocument(d->existingStartPoint.point->point()); } d->activePoint = pathShape->moveTo(point); d->firstPoint = d->activePoint; canvas()->updateCanvas(handlePaintRect(point)); canvas()->updateCanvas(canvas()->snapGuide()->boundingRect()); canvas()->snapGuide()->setAdditionalEditedShape(pathShape); d->angleSnapStrategy = new AngleSnapStrategy(d->angleSnappingDelta, d->angleSnapStatus); canvas()->snapGuide()->addCustomSnapStrategy(d->angleSnapStrategy); } if (d->angleSnapStrategy) d->angleSnapStrategy->setStartPoint(d->activePoint->point()); } bool KoCreatePathTool::listeningToModifiers() { Q_D(KoCreatePathTool); return d->listeningToModifiers; } bool KoCreatePathTool::pathStarted() { Q_D(KoCreatePathTool); return ((bool) d->shape); } void KoCreatePathTool::mouseDoubleClickEvent(KoPointerEvent *event) { //remove handle canvas()->updateCanvas(handlePaintRect(event->point)); endPathWithoutLastPoint(); } void KoCreatePathTool::mouseMoveEvent(KoPointerEvent *event) { Q_D(KoCreatePathTool); KoPathPoint *endPoint = d->endPointAtPosition(event->point); if (d->hoveredPoint != endPoint) { if (d->hoveredPoint) { QPointF nodePos = d->hoveredPoint->parent()->shapeToDocument(d->hoveredPoint->point()); canvas()->updateCanvas(handlePaintRect(nodePos)); } d->hoveredPoint = endPoint; if (d->hoveredPoint) { QPointF nodePos = d->hoveredPoint->parent()->shapeToDocument(d->hoveredPoint->point()); canvas()->updateCanvas(handlePaintRect(nodePos)); } } if (!pathStarted()) { canvas()->updateCanvas(canvas()->snapGuide()->boundingRect()); canvas()->snapGuide()->snap(event->point, event->modifiers()); canvas()->updateCanvas(canvas()->snapGuide()->boundingRect()); d->mouseOverFirstPoint = false; return; } d->mouseOverFirstPoint = handleGrabRect(d->firstPoint->point()).contains(event->point); canvas()->updateCanvas(d->shape->boundingRect()); canvas()->updateCanvas(canvas()->snapGuide()->boundingRect()); QPointF snappedPosition = canvas()->snapGuide()->snap(event->point, event->modifiers()); d->repaintActivePoint(); if (event->buttons() & Qt::LeftButton) { d->pointIsDragged = true; QPointF offset = snappedPosition - d->activePoint->point(); d->activePoint->setControlPoint2(d->activePoint->point() + offset); // pressing stops controls points moving symmetrically if ((event->modifiers() & Qt::AltModifier) == 0) { d->activePoint->setControlPoint1(d->activePoint->point() - offset); } d->repaintActivePoint(); } else { d->activePoint->setPoint(snappedPosition); } canvas()->updateCanvas(d->shape->boundingRect()); canvas()->updateCanvas(canvas()->snapGuide()->boundingRect()); } void KoCreatePathTool::mouseReleaseEvent(KoPointerEvent *event) { Q_D(KoCreatePathTool); if (! d->shape || (event->buttons() & Qt::RightButton)) return; d->listeningToModifiers = true; // After the first press-and-release d->repaintActivePoint(); d->pointIsDragged = false; KoPathPoint *lastActivePoint = d->activePoint; if (!d->finishAfterThisPoint) { d->activePoint = d->shape->lineTo(event->point); canvas()->snapGuide()->setIgnoredPathPoints((QList() << d->activePoint)); } // apply symmetric point property if applicable if (lastActivePoint->activeControlPoint1() && lastActivePoint->activeControlPoint2()) { QPointF diff1 = lastActivePoint->point() - lastActivePoint->controlPoint1(); QPointF diff2 = lastActivePoint->controlPoint2() - lastActivePoint->point(); if (qFuzzyCompare(diff1.x(), diff2.x()) && qFuzzyCompare(diff1.y(), diff2.y())) lastActivePoint->setProperty(KoPathPoint::IsSymmetric); } if (d->finishAfterThisPoint) { d->firstPoint->setControlPoint1(d->activePoint->controlPoint1()); delete d->shape->removePoint(d->shape->pathPointIndex(d->activePoint)); d->activePoint = d->firstPoint; d->shape->closeMerge(); // we are closing the path, so reset the existing start path point d->existingStartPoint = 0; // finish path endPath(); } if (d->angleSnapStrategy && lastActivePoint->activeControlPoint2()) { d->angleSnapStrategy->deactivate(); } } void KoCreatePathTool::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { emit done(); } else { event->ignore(); } } void KoCreatePathTool::endPath() { Q_D(KoCreatePathTool); d->addPathShape(); } void KoCreatePathTool::endPathWithoutLastPoint() { Q_D(KoCreatePathTool); if (d->shape) { QRectF dirtyRect = d->shape->boundingRect(); delete d->shape->removePoint(d->shape->pathPointIndex(d->activePoint)); canvas()->updateCanvas(dirtyRect); d->addPathShape(); } } void KoCreatePathTool::cancelPath() { Q_D(KoCreatePathTool); if (d->shape) { canvas()->updateCanvas(handlePaintRect(d->firstPoint->point())); canvas()->updateCanvas(d->shape->boundingRect()); d->firstPoint = 0; d->activePoint = 0; } d->cleanUp(); } void KoCreatePathTool::removeLastPoint() { Q_D(KoCreatePathTool); if ((d->shape)) { KoPathPointIndex lastPointIndex = d->shape->pathPointIndex(d->activePoint); if (lastPointIndex.second > 1) { lastPointIndex.second--; delete d->shape->removePoint(lastPointIndex); d->hoveredPoint = 0; d->repaintActivePoint(); canvas()->updateCanvas(d->shape->boundingRect()); } } } void KoCreatePathTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); Q_D(KoCreatePathTool); useCursor(Qt::ArrowCursor); // retrieve the actual global handle radius d->handleRadius = handleRadius(); // reset snap guide canvas()->updateCanvas(canvas()->snapGuide()->boundingRect()); canvas()->snapGuide()->reset(); } void KoCreatePathTool::deactivate() { cancelPath(); KoToolBase::deactivate(); } void KoCreatePathTool::documentResourceChanged(int key, const QVariant & res) { Q_D(KoCreatePathTool); switch (key) { case KoDocumentResourceManager::HandleRadius: { d->handleRadius = res.toUInt(); } break; default: return; } } void KoCreatePathTool::addPathShape(KoPathShape *pathShape) { Q_D(KoCreatePathTool); KoPathShape *startShape = 0; KoPathShape *endShape = 0; pathShape->normalize(); // check if existing start/end points are still valid d->existingStartPoint.validate(canvas()); d->existingEndPoint.validate(canvas()); if (d->connectPaths(pathShape, d->existingStartPoint, d->existingEndPoint)) { if (d->existingStartPoint.isValid()) { startShape = d->existingStartPoint.path; } if (d->existingEndPoint.isValid() && d->existingEndPoint != d->existingStartPoint) { endShape = d->existingEndPoint.path; } } - KUndo2Command *cmd = canvas()->shapeController()->addShape(pathShape); + KUndo2Command *cmd = canvas()->shapeController()->addShape(pathShape, 0); if (cmd) { KoSelection *selection = canvas()->shapeManager()->selection(); selection->deselectAll(); selection->select(pathShape); if (startShape) { canvas()->shapeController()->removeShape(startShape, cmd); } if (endShape && startShape != endShape) { canvas()->shapeController()->removeShape(endShape, cmd); } canvas()->addCommand(cmd); } else { canvas()->updateCanvas(pathShape->boundingRect()); delete pathShape; } } QList > KoCreatePathTool::createOptionWidgets() { Q_D(KoCreatePathTool); QList > list; QWidget *angleWidget = new QWidget(); angleWidget->setObjectName("Angle Constraints"); QGridLayout *layout = new QGridLayout(angleWidget); layout->addWidget(new QLabel(i18n("Angle snapping delta:"), angleWidget), 0, 0); QSpinBox *angleEdit = new KisIntParseSpinBox(angleWidget); angleEdit->setValue(d->angleSnappingDelta); angleEdit->setRange(1, 360); angleEdit->setSingleStep(1); angleEdit->setSuffix(QChar(Qt::Key_degree)); layout->addWidget(angleEdit, 0, 1); layout->addWidget(new QLabel(i18n("Activate angle snap:"), angleWidget), 1, 0); QCheckBox *angleSnap = new QCheckBox(angleWidget); angleSnap->setChecked(false); angleSnap->setCheckable(true); layout->addWidget(angleSnap, 1, 1); QWidget *specialSpacer = new QWidget(); specialSpacer->setObjectName("SpecialSpacer"); layout->addWidget(specialSpacer, 2, 1); angleWidget->setWindowTitle(i18n("Angle Constraints")); list.append(angleWidget); connect(angleEdit, SIGNAL(valueChanged(int)), this, SLOT(angleDeltaChanged(int))); connect(angleSnap, SIGNAL(stateChanged(int)), this, SLOT(angleSnapChanged(int))); return list; } //have to include this because of Q_PRIVATE_SLOT #include diff --git a/libs/basicflakes/tools/KoPencilTool.cpp b/libs/basicflakes/tools/KoPencilTool.cpp index e837a38457..ac331d3839 100644 --- a/libs/basicflakes/tools/KoPencilTool.cpp +++ b/libs/basicflakes/tools/KoPencilTool.cpp @@ -1,581 +1,581 @@ /* 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 #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) , m_strokeWidget(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) { KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, m_hoveredPoint->parent(), converter, handleRadius()); helper.setHandleStyle(KisHandleStyle::primarySelection()); m_hoveredPoint->paint(helper, KoPathPoint::Node); } } void KoPencilTool::repaintDecorations() { } void KoPencilTool::mousePressEvent(KoPointerEvent *event) { KoShapeStrokeSP stroke = createStroke(); if (!m_shape && stroke && stroke->isVisible()) { 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 activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); m_points.clear(); m_close = false; slotUpdatePencilCursor(); if (m_strokeWidget) { m_strokeWidget->activate(); } } void KoPencilTool::deactivate() { m_points.clear(); delete m_shape; m_shape = 0; m_existingStartPoint = 0; m_existingEndPoint = 0; m_hoveredPoint = 0; if (m_strokeWidget) { m_strokeWidget->deactivate(); } KoToolBase::deactivate(); } void KoPencilTool::slotUpdatePencilCursor() { KoShapeStrokeSP stroke = createStroke(); useCursor((stroke && stroke->isVisible()) ? Qt::ArrowCursor : Qt::ForbiddenCursor); } 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(canvas(), 0); m_strokeWidget->setNoSelectionTrackingMode(true); m_strokeWidget->setWindowTitle(i18n("Line")); connect(m_strokeWidget, SIGNAL(sigStrokeChanged()), SLOT(slotUpdatePencilCursor())); if (isActivated()) { m_strokeWidget->activate(); } 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); + KUndo2Command * cmd = canvas()->shapeController()->addShape(path, 0); 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; } KoShapeStrokeSP KoPencilTool::createStroke() { KoShapeStrokeSP stroke; 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/CMakeLists.txt b/libs/flake/CMakeLists.txt index c0847bdaad..5bfb182d84 100644 --- a/libs/flake/CMakeLists.txt +++ b/libs/flake/CMakeLists.txt @@ -1,242 +1,243 @@ project(kritaflake) include_directories( ${CMAKE_SOURCE_DIR}/libs/flake/commands ${CMAKE_SOURCE_DIR}/libs/flake/tools ${CMAKE_SOURCE_DIR}/libs/flake/svg ${CMAKE_SOURCE_DIR}/libs/flake/text ${CMAKE_BINARY_DIR}/libs/flake ) add_subdirectory(styles) add_subdirectory(tests) set(kritaflake_SRCS KoGradientHelper.cpp KoFlake.cpp KoCanvasBase.cpp KoResourceManager_p.cpp KoDerivedResourceConverter.cpp KoResourceUpdateMediator.cpp KoCanvasResourceManager.cpp KoDocumentResourceManager.cpp KoCanvasObserverBase.cpp KoCanvasSupervisor.cpp KoDockFactoryBase.cpp KoDockRegistry.cpp KoDataCenterBase.cpp KoInsets.cpp KoPathShape.cpp KoPathPoint.cpp KoPathSegment.cpp KoSelection.cpp KoSelectedShapesProxy.cpp KoSelectedShapesProxySimple.cpp KoShape.cpp KoShapeAnchor.cpp KoShapeBasedDocumentBase.cpp KoShapeApplicationData.cpp KoShapeContainer.cpp KoShapeContainerModel.cpp KoShapeGroup.cpp KoShapeManager.cpp KoShapePaintingContext.cpp KoFrameShape.cpp KoUnavailShape.cpp KoMarker.cpp KoMarkerCollection.cpp KoToolBase.cpp KoCanvasController.cpp KoCanvasControllerWidget.cpp KoCanvasControllerWidgetViewport_p.cpp KoShapeRegistry.cpp KoDeferredShapeFactoryBase.cpp KoToolFactoryBase.cpp KoPathShapeFactory.cpp KoShapeFactoryBase.cpp KoShapeUserData.cpp KoParameterShape.cpp KoPointerEvent.cpp KoShapeController.cpp KoToolSelection.cpp KoShapeLayer.cpp KoPostscriptPaintDevice.cpp KoInputDevice.cpp KoToolManager_p.cpp KoToolManager.cpp KoToolRegistry.cpp KoToolProxy.cpp KoShapeSavingContext.cpp KoShapeLoadingContext.cpp KoLoadingShapeUpdater.cpp KoPathShapeLoader.cpp KoShapeStrokeModel.cpp KoShapeStroke.cpp KoShapeBackground.cpp KoColorBackground.cpp KoGradientBackground.cpp KoOdfGradientBackground.cpp KoHatchBackground.cpp KoPatternBackground.cpp KoVectorPatternBackground.cpp KoShapeConfigWidgetBase.cpp KoDrag.cpp KoSvgPaste.cpp KoDragOdfSaveHelper.cpp KoShapeOdfSaveHelper.cpp KoConnectionPoint.cpp KoConnectionShape.cpp KoConnectionShapeLoadingUpdater.cpp KoConnectionShapeFactory.cpp KoConnectionShapeConfigWidget.cpp KoSnapGuide.cpp KoSnapProxy.cpp KoSnapStrategy.cpp KoSnapData.cpp KoShapeShadow.cpp KoSharedLoadingData.cpp KoSharedSavingData.cpp KoViewConverter.cpp KoInputDeviceHandler.cpp KoInputDeviceHandlerEvent.cpp KoInputDeviceHandlerRegistry.cpp KoImageData.cpp KoImageData_p.cpp KoImageCollection.cpp KoOdfWorkaround.cpp KoFilterEffect.cpp KoFilterEffectStack.cpp KoFilterEffectFactoryBase.cpp KoFilterEffectRegistry.cpp KoFilterEffectConfigWidgetBase.cpp KoFilterEffectRenderContext.cpp KoFilterEffectLoadingContext.cpp KoTextShapeDataBase.cpp KoTosContainer.cpp KoTosContainerModel.cpp KoClipPath.cpp KoClipMask.cpp KoClipMaskPainter.cpp KoCurveFit.cpp commands/KoShapeGroupCommand.cpp commands/KoShapeAlignCommand.cpp commands/KoShapeBackgroundCommand.cpp commands/KoShapeCreateCommand.cpp commands/KoShapeDeleteCommand.cpp commands/KoShapeDistributeCommand.cpp commands/KoShapeLockCommand.cpp commands/KoShapeMoveCommand.cpp commands/KoShapeResizeCommand.cpp commands/KoShapeShearCommand.cpp commands/KoShapeSizeCommand.cpp commands/KoShapeStrokeCommand.cpp commands/KoShapeUngroupCommand.cpp commands/KoShapeReorderCommand.cpp commands/KoShapeKeepAspectRatioCommand.cpp commands/KoPathBaseCommand.cpp commands/KoPathPointMoveCommand.cpp commands/KoPathControlPointMoveCommand.cpp commands/KoPathPointTypeCommand.cpp commands/KoPathPointRemoveCommand.cpp commands/KoPathPointInsertCommand.cpp commands/KoPathSegmentBreakCommand.cpp commands/KoPathBreakAtPointCommand.cpp commands/KoPathSegmentTypeCommand.cpp commands/KoPathCombineCommand.cpp commands/KoSubpathRemoveCommand.cpp commands/KoSubpathJoinCommand.cpp commands/KoParameterHandleMoveCommand.cpp commands/KoParameterToPathCommand.cpp commands/KoShapeTransformCommand.cpp commands/KoPathFillRuleCommand.cpp commands/KoConnectionShapeTypeCommand.cpp commands/KoShapeShadowCommand.cpp commands/KoPathReverseCommand.cpp commands/KoShapeRenameCommand.cpp commands/KoShapeRunAroundCommand.cpp commands/KoPathPointMergeCommand.cpp commands/KoShapeTransparencyCommand.cpp commands/KoShapeClipCommand.cpp commands/KoShapeUnclipCommand.cpp commands/KoPathShapeMarkerCommand.cpp commands/KoShapeConnectionChangeCommand.cpp commands/KoMultiPathPointMergeCommand.cpp commands/KoMultiPathPointJoinCommand.cpp + commands/KoKeepShapesSelectedCommand.cpp tools/KoCreateShapeStrategy.cpp tools/KoPathToolFactory.cpp tools/KoPathTool.cpp tools/KoPathToolSelection.cpp tools/KoPathToolHandle.cpp tools/PathToolOptionWidget.cpp tools/KoPathPointRubberSelectStrategy.cpp tools/KoPathPointMoveStrategy.cpp tools/KoPathConnectionPointStrategy.cpp tools/KoPathControlPointMoveStrategy.cpp tools/KoParameterChangeStrategy.cpp tools/KoZoomTool.cpp tools/KoZoomToolFactory.cpp tools/KoZoomToolWidget.cpp tools/KoZoomStrategy.cpp tools/KoPanTool.cpp tools/KoPanToolFactory.cpp tools/KoInteractionTool.cpp tools/KoInteractionStrategy.cpp tools/KoInteractionStrategyFactory.cpp tools/KoCreateShapesTool.cpp tools/KoCreateShapesToolFactory.cpp tools/KoShapeRubberSelectStrategy.cpp tools/KoPathSegmentChangeStrategy.cpp svg/KoShapePainter.cpp svg/SvgUtil.cpp svg/SvgGraphicContext.cpp svg/SvgSavingContext.cpp svg/SvgWriter.cpp svg/SvgStyleWriter.cpp svg/SvgShape.cpp svg/SvgParser.cpp svg/SvgStyleParser.cpp svg/SvgGradientHelper.cpp svg/SvgFilterHelper.cpp svg/SvgCssHelper.cpp svg/SvgClipPathHelper.cpp svg/SvgLoadingContext.cpp svg/SvgShapeFactory.cpp svg/parsers/SvgTransformParser.cpp text/KoSvgText.cpp text/KoSvgTextProperties.cpp text/KoSvgTextChunkShape.cpp text/KoSvgTextShape.cpp text/KoSvgTextShapeMarkupConverter.cpp resources/KoSvgSymbolCollectionResource.cpp FlakeDebug.cpp tests/MockShapes.cpp ) ki18n_wrap_ui(kritaflake_SRCS tools/PathToolOptionWidgetBase.ui KoConnectionShapeConfigWidget.ui tools/KoZoomToolWidget.ui ) add_library(kritaflake SHARED ${kritaflake_SRCS}) generate_export_header(kritaflake BASE_NAME kritaflake) target_include_directories(kritaflake PUBLIC $ $ $ $ ) target_link_libraries(kritaflake kritapigment kritawidgetutils kritaodf kritacommand KF5::WidgetsAddons Qt5::Svg) set_target_properties(kritaflake PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaflake ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp index 5ab19095b7..aeb624a987 100644 --- a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp +++ b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp @@ -1,408 +1,408 @@ /* 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 "KoShapePaintingContext.h" #include "KoToolProxy.h" #include "KoCanvasControllerWidget.h" #include "KoViewConverter.h" #include "KoSvgPaste.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())) { event->ignore(); return; } delete m_draggedShape; m_draggedShape = 0; // 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())) { event->ignore(); return; } const QMimeData *data = event->mimeData(); if (data->hasFormat(SHAPETEMPLATE_MIMETYPE) || data->hasFormat(SHAPEID_MIMETYPE) || data->hasFormat("image/svg+xml")) { if (data->hasFormat("image/svg+xml")) { KoCanvasBase *canvas = m_parent->canvas(); QSizeF fragmentSize; QList shapes = KoSvgPaste::fetchShapesFromData(data->data("image/svg+xml"), canvas->shapeController()->documentRectInPixels(), canvas->shapeController()->pixelsPerInch(), &fragmentSize); if (!shapes.isEmpty()) { m_draggedShape = shapes[0]; } } else { QByteArray itemData; bool isTemplate = true; if (data->hasFormat(SHAPETEMPLATE_MIMETYPE)) { itemData = data->data(SHAPETEMPLATE_MIMETYPE); } else if (data->hasFormat(SHAPEID_MIMETYPE)) { 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; } 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()); } if (m_draggedShape->shapeId().isEmpty()) { m_draggedShape->setShapeId(factory->id()); } } event->setDropAction(Qt::CopyAction); event->accept(); Q_ASSERT(m_draggedShape); if (!m_draggedShape) return; m_draggedShape->setZIndex(KoShapePrivate::MaxZIndex); m_draggedShape->setAbsolutePosition(correctPosition(event->pos())); m_parent->canvas()->shapeManager()->addShape(m_draggedShape); } 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); + KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape, 0); 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(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/KoShapeController.cpp b/libs/flake/KoShapeController.cpp index 0c67c96b15..8fd031c123 100644 --- a/libs/flake/KoShapeController.cpp +++ b/libs/flake/KoShapeController.cpp @@ -1,212 +1,212 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2006-2008 Thorsten Zachmann * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeController.h" #include "KoShapeBasedDocumentBase.h" #include "KoShapeRegistry.h" #include "KoDocumentResourceManager.h" #include "KoShapeManager.h" #include "KoShapeLayer.h" #include "KoSelection.h" #include "commands/KoShapeCreateCommand.h" #include "commands/KoShapeDeleteCommand.h" #include "commands/KoShapeConnectionChangeCommand.h" #include "KoCanvasBase.h" #include "KoShapeConfigWidgetBase.h" #include "KoShapeFactoryBase.h" #include "KoShape.h" #include "KoConnectionShape.h" #include #include #include #include class KoShapeController::Private { public: Private() : canvas(0), shapeBasedDocument(0) { } KoCanvasBase *canvas; KoShapeBasedDocumentBase *shapeBasedDocument; - KUndo2Command* addShape(KoShape *shape, bool showDialog, KUndo2Command *parent) { + KUndo2Command* addShape(KoShape *shape, bool showDialog, KoShapeContainer *parentShape, KUndo2Command *parent) { if (canvas) { if (showDialog && !shape->shapeId().isEmpty()) { KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shape->shapeId()); Q_ASSERT(factory); int z = 0; Q_FOREACH (KoShape *sh, canvas->shapeManager()->shapes()) z = qMax(z, sh->zIndex()); shape->setZIndex(z + 1); // show config dialog. KPageDialog *dialog = new KPageDialog(canvas->canvasWidget()); dialog->setWindowTitle(i18n("%1 Options", factory->name())); int pageCount = 0; QList widgets; Q_FOREACH (KoShapeConfigWidgetBase* panel, factory->createShapeOptionPanels()) { if (! panel->showOnShapeCreate()) continue; panel->open(shape); panel->connect(panel, SIGNAL(accept()), dialog, SLOT(accept())); widgets.append(panel); panel->setResourceManager(canvas->resourceManager()); panel->setUnit(canvas->unit()); QString title = panel->windowTitle().isEmpty() ? panel->objectName() : panel->windowTitle(); dialog->addPage(panel, title); pageCount ++; } if (pageCount > 0) { if (pageCount > 1) dialog->setFaceType(KPageDialog::Tabbed); if (dialog->exec() != KPageDialog::Accepted) { delete dialog; return 0; } Q_FOREACH (KoShapeConfigWidgetBase *widget, widgets) widget->save(); } delete dialog; } } - return addShapesDirect({shape}, parent); + return addShapesDirect({shape}, parentShape, parent); } - KUndo2Command* addShapesDirect(const QList shapes, KUndo2Command *parent) + KUndo2Command* addShapesDirect(const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent) { - return new KoShapeCreateCommand(shapeBasedDocument, shapes, parent); + return new KoShapeCreateCommand(shapeBasedDocument, shapes, parentShape, parent); } void handleAttachedConnections(KoShape *shape, KUndo2Command *parentCmd) { foreach (KoShape *dependee, shape->dependees()) { KoConnectionShape *connection = dynamic_cast(dependee); if (connection) { if (shape == connection->firstShape()) { new KoShapeConnectionChangeCommand(connection, KoConnectionShape::StartHandle, shape, connection->firstConnectionId(), 0, -1, parentCmd); } else if (shape == connection->secondShape()) { new KoShapeConnectionChangeCommand(connection, KoConnectionShape::EndHandle, shape, connection->secondConnectionId(), 0, -1, parentCmd); } } } } }; KoShapeController::KoShapeController(KoCanvasBase *canvas, KoShapeBasedDocumentBase *shapeBasedDocument) : d(new Private()) { d->canvas = canvas; d->shapeBasedDocument = shapeBasedDocument; if (shapeBasedDocument) { shapeBasedDocument->resourceManager()->setShapeController(this); } } KoShapeController::~KoShapeController() { delete d; } void KoShapeController::reset() { d->canvas = 0; d->shapeBasedDocument = 0; } -KUndo2Command* KoShapeController::addShape(KoShape *shape, KUndo2Command *parent) +KUndo2Command* KoShapeController::addShape(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent) { - return d->addShape(shape, true, parent); + return d->addShape(shape, true, parentShape, parent); } -KUndo2Command* KoShapeController::addShapeDirect(KoShape *shape, KUndo2Command *parent) +KUndo2Command* KoShapeController::addShapeDirect(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent) { - return d->addShapesDirect({shape}, parent); + return d->addShapesDirect({shape}, parentShape, parent); } -KUndo2Command *KoShapeController::addShapesDirect(const QList shapes, KUndo2Command *parent) +KUndo2Command *KoShapeController::addShapesDirect(const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent) { - return d->addShapesDirect(shapes, parent); + return d->addShapesDirect(shapes, parentShape, parent); } KUndo2Command* KoShapeController::removeShape(KoShape *shape, KUndo2Command *parent) { KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeBasedDocument, shape, parent); QList shapes; shapes.append(shape); d->shapeBasedDocument->shapesRemoved(shapes, cmd); // detach shape from any attached connection shapes d->handleAttachedConnections(shape, cmd); return cmd; } KUndo2Command* KoShapeController::removeShapes(const QList &shapes, KUndo2Command *parent) { KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeBasedDocument, shapes, parent); d->shapeBasedDocument->shapesRemoved(shapes, cmd); foreach (KoShape *shape, shapes) { d->handleAttachedConnections(shape, cmd); } return cmd; } void KoShapeController::setShapeControllerBase(KoShapeBasedDocumentBase *shapeBasedDocument) { d->shapeBasedDocument = shapeBasedDocument; } QRectF KoShapeController::documentRectInPixels() const { return d->shapeBasedDocument ? d->shapeBasedDocument->documentRectInPixels() : QRectF(0,0,1920,1080); } qreal KoShapeController::pixelsPerInch() const { return d->shapeBasedDocument ? d->shapeBasedDocument->pixelsPerInch() : 72.0; } QRectF KoShapeController::documentRect() const { return d->shapeBasedDocument ? d->shapeBasedDocument->documentRect() : documentRectInPixels(); } KoDocumentResourceManager *KoShapeController::resourceManager() const { if (!d->shapeBasedDocument) return 0; return d->shapeBasedDocument->resourceManager(); } KoShapeBasedDocumentBase *KoShapeController::documentBase() const { return d->shapeBasedDocument; } diff --git a/libs/flake/KoShapeController.h b/libs/flake/KoShapeController.h index 26171e50af..b1d2912db2 100644 --- a/libs/flake/KoShapeController.h +++ b/libs/flake/KoShapeController.h @@ -1,169 +1,170 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2006-2008 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPECONTROLLER_H #define KOSHAPECONTROLLER_H #include "kritaflake_export.h" #include #include #include class KoCanvasBase; class KoShape; +class KoShapeContainer; class KoShapeBasedDocumentBase; class KUndo2Command; class KoDocumentResourceManager; /** * Class used by tools to maintain the list of shapes. * All applications have some sort of list of all shapes that belong to the document. * The applications implement the KoShapeBasedDocumentBase interface (all pure virtuals) * to add and remove shapes from the document. To ensure that an application can expect * a certain protocol to be adhered to when adding/removing shapes, all tools use the API * from this class for maintaining the list of shapes in the document. So no tool gets * to access the application directly. */ class KRITAFLAKE_EXPORT KoShapeController : public QObject { Q_OBJECT public: /** * Create a new Controller; typically not called by applications, only * by the KonCanvasBase constructor. * @param canvas the canvas this controller works for. The canvas can be 0 * @param shapeBasedDocument the application provided shapeBasedDocument that we can call. */ KoShapeController(KoCanvasBase *canvas, KoShapeBasedDocumentBase *shapeBasedDocument); /// destructor ~KoShapeController() override; /** * @brief reset sets the canvas and shapebased document to 0. */ void reset(); /** * @brief Add a shape to the document. * If the shape has no parent, the active layer will become its parent. * * @param shape to add to the document * @param parent the parent command if the resulting command is a compound undo command. * * @return command which will insert the shape into the document or 0 if the * insertion was cancelled. The command is not yet executed. */ - KUndo2Command* addShape(KoShape *shape, KUndo2Command *parent = 0); + KUndo2Command* addShape(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent = 0); /** * @brief Add a shape to the document, skipping any dialogs or other user interaction. * * @param shape to add to the document * @param parent the parent command if the resulting command is a compound undo command. * * @return command which will insert the shape into the document. The command is not yet executed. */ - KUndo2Command* addShapeDirect(KoShape *shape, KUndo2Command *parent = 0); + KUndo2Command* addShapeDirect(KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent = 0); /** * @brief Add shapes to the document, skipping any dialogs or other user interaction. * * @param shapes to add to the document * @param parent the parent command if the resulting command is a compound undo command. * * @return command which will insert the shapes into the document. The command is not yet executed. */ - KUndo2Command* addShapesDirect(const QList shape, KUndo2Command *parent = 0); + KUndo2Command* addShapesDirect(const QList shape, KoShapeContainer *parentShape, KUndo2Command *parent = 0); /** * @brief Remove a shape from the document. * * @param shape to remove from the document * @param parent the parent command if the resulting command is a compound undo command. * * @return command which will remove the shape from the document. * The command is not yet executed. */ KUndo2Command* removeShape(KoShape *shape, KUndo2Command *parent = 0); /** * Remove a shape from the document. * * @param shapes the set of shapes to remove from the document * @param parent the parent command if the resulting command is a compound undo command. * * @return command which will remove the shape from the document. * The command is not yet executed. */ KUndo2Command* removeShapes(const QList &shapes, KUndo2Command *parent = 0); /** * @brief Set the KoShapeBasedDocumentBase used to add/remove shapes. * * NOTE: only Sheets uses this method. Do not use it in your application. Sheets * has to also call: * KoToolManager::instance()->updateShapeControllerBase(shapeBasedDocument, canvas->canvasController()); * * @param shapeBasedDocument the new shapeBasedDocument. */ void setShapeControllerBase(KoShapeBasedDocumentBase *shapeBasedDocument); /** * The size of the document measured in rasterized pixels. This information is needed for loading * SVG documents that use 'px' as the default unit. */ QRectF documentRectInPixels() const; /** * Resolution of the rasterized representaiton of the document. Used to load SVG documents correctly. */ qreal pixelsPerInch() const; /** * Document rect measured in 'pt' */ QRectF documentRect() const; /** * Return a pointer to the resource manager associated with the * shape-set (typically a document). The resource manager contains * document wide resources * such as variable managers, the image * collection and others. */ KoDocumentResourceManager *resourceManager() const; /** * @brief Returns the KoShapeBasedDocumentBase used to add/remove shapes. * * @return the KoShapeBasedDocumentBase */ KoShapeBasedDocumentBase *documentBase() const; private: class Private; Private * const d; }; Q_DECLARE_METATYPE(KoShapeController *) #endif diff --git a/libs/flake/KoToolProxy.cpp b/libs/flake/KoToolProxy.cpp index fcdb2f3b82..13678d100c 100644 --- a/libs/flake/KoToolProxy.cpp +++ b/libs/flake/KoToolProxy.cpp @@ -1,589 +1,589 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (c) 2006-2011 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoToolProxy.h" #include "KoToolProxy_p.h" #include #include #include #include #include #include #include #include #include #include #include "KoToolBase.h" #include "KoPointerEvent.h" #include "KoInputDevice.h" #include "KoToolManager_p.h" #include "KoToolSelection.h" #include "KoCanvasBase.h" #include "KoCanvasController.h" #include "KoShapeManager.h" #include "KoSelection.h" #include "KoShapeLayer.h" #include "KoShapeRegistry.h" #include "KoShapeController.h" #include "KoOdf.h" #include "KoViewConverter.h" #include "KoShapeFactoryBase.h" KoToolProxyPrivate::KoToolProxyPrivate(KoToolProxy *p) : activeTool(0), tabletPressed(false), hasSelection(false), controller(0), parent(p) { scrollTimer.setInterval(100); mouseLeaveWorkaround = false; multiClickCount = 0; } void KoToolProxyPrivate::timeout() // Auto scroll the canvas { Q_ASSERT(controller); QPoint offset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY()); QPoint origin = controller->canvas()->documentOrigin(); QPoint viewPoint = widgetScrollPoint - origin - offset; QRectF mouseArea(viewPoint, QSizeF(10, 10)); mouseArea.setTopLeft(mouseArea.center()); controller->ensureVisible(mouseArea, true); QPoint newOffset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY()); QPoint moved = offset - newOffset; if (moved.isNull()) return; widgetScrollPoint += moved; QPointF documentPoint = parent->widgetToDocument(widgetScrollPoint); QMouseEvent event(QEvent::MouseMove, widgetScrollPoint, Qt::LeftButton, Qt::LeftButton, 0); KoPointerEvent ev(&event, documentPoint); activeTool->mouseMoveEvent(&ev); } void KoToolProxyPrivate::checkAutoScroll(const KoPointerEvent &event) { if (controller == 0) return; if (!activeTool) return; if (!activeTool->wantsAutoScroll()) return; if (!event.isAccepted()) return; if (event.buttons() != Qt::LeftButton) return; widgetScrollPoint = event.pos(); if (! scrollTimer.isActive()) scrollTimer.start(); } void KoToolProxyPrivate::selectionChanged(bool newSelection) { if (hasSelection == newSelection) return; hasSelection = newSelection; emit parent->selectionChanged(hasSelection); } bool KoToolProxyPrivate::isActiveLayerEditable() { if (!activeTool) return false; KoShapeManager * shapeManager = activeTool->canvas()->shapeManager(); KoShapeLayer * activeLayer = shapeManager->selection()->activeLayer(); if (activeLayer && !activeLayer->isEditable()) return false; return true; } KoToolProxy::KoToolProxy(KoCanvasBase *canvas, QObject *parent) : QObject(parent), d(new KoToolProxyPrivate(this)) { KoToolManager::instance()->priv()->registerToolProxy(this, canvas); connect(&d->scrollTimer, SIGNAL(timeout()), this, SLOT(timeout())); } KoToolProxy::~KoToolProxy() { delete d; } void KoToolProxy::paint(QPainter &painter, const KoViewConverter &converter) { if (d->activeTool) d->activeTool->paint(painter, converter); } void KoToolProxy::repaintDecorations() { if (d->activeTool) d->activeTool->repaintDecorations(); } QPointF KoToolProxy::widgetToDocument(const QPointF &widgetPoint) const { QPoint offset = QPoint(d->controller->canvasOffsetX(), d->controller->canvasOffsetY()); QPoint origin = d->controller->canvas()->documentOrigin(); QPointF viewPoint = widgetPoint.toPoint() - QPointF(origin - offset); return d->controller->canvas()->viewConverter()->viewToDocument(viewPoint); } KoCanvasBase* KoToolProxy::canvas() const { return d->controller->canvas(); } void KoToolProxy::touchEvent(QTouchEvent *event) { QPointF point; QList touchPoints; bool isPrimary = true; Q_FOREACH (QTouchEvent::TouchPoint p, event->touchPoints()) { QPointF docPoint = widgetToDocument(p.screenPos()); if (isPrimary) { point = docPoint; isPrimary = false; } KoTouchPoint touchPoint; touchPoint.touchPoint = p; touchPoint.point = point; touchPoint.lastPoint = widgetToDocument(p.lastNormalizedPos()); touchPoints << touchPoint; } KoPointerEvent ev(event, point, touchPoints); KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); switch (event->type()) { case QEvent::TouchBegin: ev.setTabletButton(Qt::LeftButton); if (d->activeTool) { if( d->activeTool->wantsTouch() ) d->activeTool->touchEvent(event); else d->activeTool->mousePressEvent(&ev); } break; case QEvent::TouchUpdate: ev.setTabletButton(Qt::LeftButton); if (d->activeTool) { if( d->activeTool->wantsTouch() ) d->activeTool->touchEvent(event); else d->activeTool->mouseMoveEvent(&ev); } break; case QEvent::TouchEnd: ev.setTabletButton(Qt::LeftButton); if (d->activeTool) { if( d->activeTool->wantsTouch() ) d->activeTool->touchEvent(event); else d->activeTool->mouseReleaseEvent(&ev); } break; default: ; // ingore the rest } d->mouseLeaveWorkaround = true; } void KoToolProxy::tabletEvent(QTabletEvent *event, const QPointF &point) { // We get these events exclusively from KisToolProxy - accept them event->accept(); KoInputDevice id(event->device(), event->pointerType(), event->uniqueId()); KoToolManager::instance()->priv()->switchInputDevice(id); KoPointerEvent ev(event, point); switch (event->type()) { case QEvent::TabletPress: ev.setTabletButton(Qt::LeftButton); if (!d->tabletPressed && d->activeTool) d->activeTool->mousePressEvent(&ev); d->tabletPressed = true; break; case QEvent::TabletRelease: ev.setTabletButton(Qt::LeftButton); d->tabletPressed = false; d->scrollTimer.stop(); if (d->activeTool) d->activeTool->mouseReleaseEvent(&ev); break; case QEvent::TabletMove: if (d->tabletPressed) ev.setTabletButton(Qt::LeftButton); if (d->activeTool) d->activeTool->mouseMoveEvent(&ev); d->checkAutoScroll(ev); default: ; // ignore the rest. } d->mouseLeaveWorkaround = true; } void KoToolProxy::mousePressEvent(KoPointerEvent *ev) { d->mouseLeaveWorkaround = false; KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); d->mouseDownPoint = ev->pos(); if (d->tabletPressed) // refuse to send a press unless there was a release first. return; QPointF globalPoint = ev->globalPos(); if (d->multiClickGlobalPoint != globalPoint) { if (qAbs(globalPoint.x() - d->multiClickGlobalPoint.x()) > 5|| qAbs(globalPoint.y() - d->multiClickGlobalPoint.y()) > 5) { d->multiClickCount = 0; } d->multiClickGlobalPoint = globalPoint; } if (d->multiClickCount && d->multiClickTimeStamp.elapsed() < QApplication::doubleClickInterval()) { // One more multiclick; d->multiClickCount++; } else { d->multiClickTimeStamp.start(); d->multiClickCount = 1; } if (d->activeTool) { switch (d->multiClickCount) { case 0: case 1: d->activeTool->mousePressEvent(ev); break; case 2: d->activeTool->mouseDoubleClickEvent(ev); break; case 3: default: d->activeTool->mouseTripleClickEvent(ev); break; } } else { d->multiClickCount = 0; ev->ignore(); } } void KoToolProxy::mousePressEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mousePressEvent(&ev); } void KoToolProxy::mouseDoubleClickEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mouseDoubleClickEvent(&ev); } void KoToolProxy::mouseDoubleClickEvent(KoPointerEvent *event) { // let us handle it as any other mousepress (where we then detect multi clicks mousePressEvent(event); } void KoToolProxy::mouseMoveEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mouseMoveEvent(&ev); } void KoToolProxy::mouseMoveEvent(KoPointerEvent *event) { if (d->mouseLeaveWorkaround) { d->mouseLeaveWorkaround = false; return; } KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); if (d->activeTool == 0) { event->ignore(); return; } d->activeTool->mouseMoveEvent(event); d->checkAutoScroll(*event); } void KoToolProxy::mouseReleaseEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mouseReleaseEvent(&ev); } void KoToolProxy::mouseReleaseEvent(KoPointerEvent* event) { d->mouseLeaveWorkaround = false; KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); d->scrollTimer.stop(); if (d->activeTool) { d->activeTool->mouseReleaseEvent(event); } else { event->ignore(); } } void KoToolProxy::keyPressEvent(QKeyEvent *event) { if (d->activeTool) d->activeTool->keyPressEvent(event); else event->ignore(); } void KoToolProxy::keyReleaseEvent(QKeyEvent *event) { if (d->activeTool) d->activeTool->keyReleaseEvent(event); else event->ignore(); } void KoToolProxy::wheelEvent(QWheelEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); if (d->activeTool) d->activeTool->wheelEvent(&ev); else event->ignore(); } void KoToolProxy::wheelEvent(KoPointerEvent *event) { if (d->activeTool) d->activeTool->wheelEvent(event); else event->ignore(); } void KoToolProxy::explicitUserStrokeEndRequest() { if (d->activeTool) { d->activeTool->explicitUserStrokeEndRequest(); } } QVariant KoToolProxy::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const { if (d->activeTool) return d->activeTool->inputMethodQuery(query, converter); return QVariant(); } void KoToolProxy::inputMethodEvent(QInputMethodEvent *event) { if (d->activeTool) d->activeTool->inputMethodEvent(event); } QMenu *KoToolProxy::popupActionsMenu() { return d->activeTool ? d->activeTool->popupActionsMenu() : 0; } void KoToolProxy::setActiveTool(KoToolBase *tool) { if (d->activeTool) disconnect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool))); d->activeTool = tool; if (tool) { connect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool))); d->selectionChanged(hasSelection()); emit toolChanged(tool->toolId()); } } void KoToolProxyPrivate::setCanvasController(KoCanvasController *c) { controller = c; } QHash KoToolProxy::actions() const { return d->activeTool ? d->activeTool->actions() : QHash(); } bool KoToolProxy::hasSelection() const { return d->activeTool ? d->activeTool->hasSelection() : false; } void KoToolProxy::cut() { if (d->activeTool && d->isActiveLayerEditable()) d->activeTool->cut(); } void KoToolProxy::copy() const { if (d->activeTool) d->activeTool->copy(); } bool KoToolProxy::paste() { bool success = false; KoCanvasBase *canvas = d->controller->canvas(); if (d->activeTool && d->isActiveLayerEditable()) { success = d->activeTool->paste(); } if (!success) { const QMimeData *data = QApplication::clipboard()->mimeData(); QList imageList; QImage image = QApplication::clipboard()->image(); if (!image.isNull()) { imageList << image; } // QT5TODO: figure out how to download data synchronously, which is deprecated in frameworks. else if (data->hasUrls()) { QList urls = QApplication::clipboard()->mimeData()->urls(); foreach (const QUrl &url, urls) { QImage image; image.load(url.toLocalFile()); if (!image.isNull()) { imageList << image; } } } KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("PictureShape"); QWidget *canvasWidget = canvas->canvasWidget(); const KoViewConverter *converter = canvas->viewConverter(); if (imageList.length() > 0 && factory && canvasWidget) { KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Paste Image")); QList pastedShapes; Q_FOREACH (const QImage &image, imageList) { if (!image.isNull()) { QPointF p = converter->viewToDocument(canvasWidget->mapFromGlobal(QCursor::pos()) + canvas->canvasController()->documentOffset()- canvasWidget->pos()); KoProperties params; params.setProperty("qimage", image); KoShape *shape = factory->createShape(¶ms, canvas->shapeController()->resourceManager()); shape->setPosition(p); pastedShapes << shape; success = true; } } if (!pastedShapes.isEmpty()) { // add shape to the document - canvas->shapeController()->addShapesDirect(pastedShapes, cmd); + canvas->shapeController()->addShapesDirect(pastedShapes, 0, cmd); canvas->addCommand(cmd); } } } return success; } void KoToolProxy::dragMoveEvent(QDragMoveEvent *event, const QPointF &point) { if (d->activeTool) d->activeTool->dragMoveEvent(event, point); } void KoToolProxy::dragLeaveEvent(QDragLeaveEvent *event) { if (d->activeTool) d->activeTool->dragLeaveEvent(event); } void KoToolProxy::dropEvent(QDropEvent *event, const QPointF &point) { if (d->activeTool) d->activeTool->dropEvent(event, point); } void KoToolProxy::deleteSelection() { if (d->activeTool) d->activeTool->deleteSelection(); } void KoToolProxy::processEvent(QEvent *e) const { if(e->type()==QEvent::ShortcutOverride && d->activeTool && d->activeTool->isInTextMode() && (static_cast(e)->modifiers()==Qt::NoModifier || static_cast(e)->modifiers()==Qt::ShiftModifier)) { e->accept(); } } void KoToolProxy::requestUndoDuringStroke() { if (d->activeTool) { d->activeTool->requestUndoDuringStroke(); } } void KoToolProxy::requestStrokeCancellation() { if (d->activeTool) { d->activeTool->requestStrokeCancellation(); } } void KoToolProxy::requestStrokeEnd() { if (d->activeTool) { d->activeTool->requestStrokeEnd(); } } KoToolProxyPrivate *KoToolProxy::priv() { return d; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoToolProxy.cpp" diff --git a/libs/flake/commands/KoKeepShapesSelectedCommand.cpp b/libs/flake/commands/KoKeepShapesSelectedCommand.cpp new file mode 100644 index 0000000000..f1fcc2b82d --- /dev/null +++ b/libs/flake/commands/KoKeepShapesSelectedCommand.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2017 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "KoKeepShapesSelectedCommand.h" + +#include +#include + + +KoKeepShapesSelectedCommand::KoKeepShapesSelectedCommand(const QList &selectedBefore, + const QList &selectedAfter, + KoSelection *selection, + bool isFinalizing, + KUndo2Command *parent) + : KisCommandUtils::FlipFlopCommand(isFinalizing, parent), + m_selectedBefore(selectedBefore), + m_selectedAfter(selectedAfter), + m_selection(selection) +{ + +} + +void KoKeepShapesSelectedCommand::end() +{ + m_selection->deselectAll(); + + const QList newSelectedShapes = + isFinalizing() ? m_selectedAfter : m_selectedBefore; + + Q_FOREACH (KoShape *shape, newSelectedShapes) { + m_selection->select(shape); + } +} + diff --git a/libs/flake/commands/KoKeepShapesSelectedCommand.h b/libs/flake/commands/KoKeepShapesSelectedCommand.h new file mode 100644 index 0000000000..195ad91a6f --- /dev/null +++ b/libs/flake/commands/KoKeepShapesSelectedCommand.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KOKEEPSHAPESSELECTEDCOMMAND_H +#define KOKEEPSHAPESSELECTEDCOMMAND_H + +#include "kis_command_utils.h" +#include + +class KoSelection; +class KoShape; + +class KRITAFLAKE_EXPORT KoKeepShapesSelectedCommand : public KisCommandUtils::FlipFlopCommand +{ +public: + KoKeepShapesSelectedCommand(const QList &selectedBefore, + const QList &selectedAfter, + KoSelection *selection, + bool isFinalizing, + KUndo2Command *parent); + +protected: + void end(); + +private: + QList m_selectedBefore; + QList m_selectedAfter; + KoSelection *m_selection; +}; + +#endif // KOKEEPSHAPESSELECTEDCOMMAND_H diff --git a/libs/flake/commands/KoShapeCreateCommand.cpp b/libs/flake/commands/KoShapeCreateCommand.cpp index a2c443c217..f3312b3eb2 100644 --- a/libs/flake/commands/KoShapeCreateCommand.cpp +++ b/libs/flake/commands/KoShapeCreateCommand.cpp @@ -1,127 +1,126 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2006 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeCreateCommand.h" #include "KoShape.h" #include "KoShapeContainer.h" #include "KoShapeBasedDocumentBase.h" #include #include "kis_assert.h" #include #include #include #include class Q_DECL_HIDDEN KoShapeCreateCommand::Private { public: - Private(KoShapeBasedDocumentBase *_document, const QList &_shapes) + Private(KoShapeBasedDocumentBase *_document, const QList &_shapes, KoShapeContainer *_parentShape) : shapesDocument(_document), shapes(_shapes), + explicitParentShape(_parentShape), deleteShapes(true) { - Q_FOREACH(KoShape *shape, shapes) { - originalShapeParents << shape->parent(); - } } ~Private() { if (deleteShapes) { qDeleteAll(shapes); } } KoShapeBasedDocumentBase *shapesDocument; QList shapes; - QList originalShapeParents; + KoShapeContainer *explicitParentShape; bool deleteShapes; std::vector> reorderingCommands; QScopedPointer reorderingCommand; }; -KoShapeCreateCommand::KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, KoShape *shape, KUndo2Command *parent) - : KoShapeCreateCommand(controller, QList() << shape, parent) +KoShapeCreateCommand::KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, KoShape *shape, KoShapeContainer *parentShape, KUndo2Command *parent) + : KoShapeCreateCommand(controller, QList() << shape, parentShape, parent) { } -KoShapeCreateCommand::KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, const QList shapes, KUndo2Command *parent) - : KUndo2Command(kundo2_i18np("Create shape", "Create shapes", shapes.size()), parent), - d(new Private(controller, shapes)) +KoShapeCreateCommand::KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, const QList shapes, KoShapeContainer *parentShape, KUndo2Command *parent) + : KUndo2Command(kundo2_i18np("Create shape", "Create shapes", shapes.size()), parent), + d(new Private(controller, shapes, parentShape)) { } KoShapeCreateCommand::~KoShapeCreateCommand() { delete d; } void KoShapeCreateCommand::redo() { KUndo2Command::redo(); KIS_ASSERT(d->shapesDocument); d->deleteShapes = false; d->reorderingCommands.clear(); Q_FOREACH(KoShape *shape, d->shapes) { + if (d->explicitParentShape) { + shape->setParent(d->explicitParentShape); + } + d->shapesDocument->addShape(shape); KoShapeContainer *shapeParent = shape->parent(); KIS_SAFE_ASSERT_RECOVER_NOOP(shape->parent() || dynamic_cast(shape)); if (shapeParent) { KUndo2Command *cmd = KoShapeReorderCommand::mergeInShape(shapeParent->shapes(), shape); if (d->reorderingCommand) { cmd->redo(); d->reorderingCommands.push_back( std::unique_ptr(cmd)); } } } } void KoShapeCreateCommand::undo() { KUndo2Command::undo(); KIS_ASSERT(d->shapesDocument); while (!d->reorderingCommands.empty()) { std::unique_ptr cmd = std::move(d->reorderingCommands.back()); cmd->undo(); d->reorderingCommands.pop_back(); } - KIS_SAFE_ASSERT_RECOVER_RETURN(d->shapes.size() == d->originalShapeParents.size()); - - for (int i = 0; i < d->shapes.size(); i++) { - d->shapesDocument->removeShape(d->shapes[i]); - d->shapes[i]->setParent(d->originalShapeParents[i]); + Q_FOREACH(KoShape *shape, d->shapes) { + d->shapesDocument->removeShape(shape); } d->deleteShapes = true; } diff --git a/libs/flake/commands/KoShapeCreateCommand.h b/libs/flake/commands/KoShapeCreateCommand.h index 82a7e6f8d8..140f33a463 100644 --- a/libs/flake/commands/KoShapeCreateCommand.h +++ b/libs/flake/commands/KoShapeCreateCommand.h @@ -1,62 +1,65 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPECREATECOMMAND_H #define KOSHAPECREATECOMMAND_H #include "kritaflake_export.h" #include class KoShape; +class KoShapeContainer; class KoShapeBasedDocumentBase; /// The undo / redo command for creating shapes class KRITAFLAKE_EXPORT KoShapeCreateCommand : public KUndo2Command { public: /** * Command used on creation of new shapes * @param controller the controller used to add/remove the shape from * @param shape the shape thats just been created. * @param parent the parent command used for macro commands */ KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, KoShape *shape, + KoShapeContainer *parentShape = 0, KUndo2Command *parent = 0); /** * Command used on creation of new shapes * @param controller the controller used to add/remove the shape from * @param shapes the shapes that have just been created. * @param parent the parent command used for macro commands */ KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, const QList shape, + KoShapeContainer *parentShape = 0, KUndo2Command *parent = 0); ~KoShapeCreateCommand() override; /// redo the command void redo() override; /// revert the actions done in redo void undo() override; private: class Private; Private * const d; }; #endif diff --git a/libs/flake/tests/TestShapePainting.cpp b/libs/flake/tests/TestShapePainting.cpp index b74c4e0a67..3820c2a6ef 100644 --- a/libs/flake/tests/TestShapePainting.cpp +++ b/libs/flake/tests/TestShapePainting.cpp @@ -1,325 +1,325 @@ /* * This file is part of Calligra tests * * Copyright (C) 2006-2010 Thomas Zander * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "TestShapePainting.h" #include #include "KoShapeContainer.h" #include "KoShapeManager.h" #include "KoShapePaintingContext.h" #include "KoViewConverter.h" #include #include void TestShapePainting::testPaintShape() { MockShape *shape1 = new MockShape(); MockShape *shape2 = new MockShape(); MockContainer *container = new MockContainer(); container->addShape(shape1); container->addShape(shape2); QCOMPARE(shape1->parent(), container); QCOMPARE(shape2->parent(), container); container->setClipped(shape1, false); container->setClipped(shape2, false); QCOMPARE(container->isClipped(shape1), false); QCOMPARE(container->isClipped(shape2), false); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(container); QCOMPARE(manager.shapes().count(), 3); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); KoViewConverter vc; manager.paint(painter, vc, false); // with the shape not being clipped, the shapeManager will paint it for us. QCOMPARE(shape1->paintedCount, 1); QCOMPARE(shape2->paintedCount, 1); QCOMPARE(container->paintedCount, 1); // the container should thus not paint the shape shape1->paintedCount = 0; shape2->paintedCount = 0; container->paintedCount = 0; KoShapePaintingContext paintContext; container->paint(painter, vc, paintContext); QCOMPARE(shape1->paintedCount, 0); QCOMPARE(shape2->paintedCount, 0); QCOMPARE(container->paintedCount, 1); container->setClipped(shape1, false); container->setClipped(shape2, true); QCOMPARE(container->isClipped(shape1), false); QCOMPARE(container->isClipped(shape2), true); shape1->paintedCount = 0; shape2->paintedCount = 0; container->paintedCount = 0; manager.paint(painter, vc, false); // with this shape not being clipped, the shapeManager will paint the container and this shape QCOMPARE(shape1->paintedCount, 1); // with this shape being clipped, the container will paint it for us. QCOMPARE(shape2->paintedCount, 1); QCOMPARE(container->paintedCount, 1); delete container; } void TestShapePainting::testPaintHiddenShape() { MockShape *shape = new MockShape(); MockContainer *fourth = new MockContainer(); MockContainer *thirth = new MockContainer(); MockContainer *second = new MockContainer(); MockContainer *top = new MockContainer(); top->addShape(second); second->addShape(thirth); thirth->addShape(fourth); fourth->addShape(shape); second->setVisible(false); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(top); QCOMPARE(manager.shapes().count(), 5); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); KoViewConverter vc; manager.paint(painter, vc, false); QCOMPARE(top->paintedCount, 1); QCOMPARE(second->paintedCount, 0); QCOMPARE(thirth->paintedCount, 0); QCOMPARE(fourth->paintedCount, 0); QCOMPARE(shape->paintedCount, 0); delete top; } void TestShapePainting::testPaintOrder() { // the stacking order determines the painting order so things on top // get their paint called last. // Each shape has a zIndex and within the children a container has // it determines the stacking order. Its important to realize that // the zIndex is thus local to a container, if you have layer1 and layer2 // with both various child shapes the stacking order of the layer shapes // is most important, then within this the child shape index is used. class OrderedMockShape : public MockShape { public: OrderedMockShape(QList &list) : order(list) {} void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override { order.append(this); MockShape::paint(painter, converter, paintcontext); } QList ℴ }; QList order; MockContainer *top = new MockContainer(); top->setZIndex(2); OrderedMockShape *shape1 = new OrderedMockShape(order); shape1->setZIndex(5); OrderedMockShape *shape2 = new OrderedMockShape(order); shape2->setZIndex(0); top->addShape(shape1); top->addShape(shape2); MockContainer *bottom = new MockContainer(); bottom->setZIndex(1); OrderedMockShape *shape3 = new OrderedMockShape(order); shape3->setZIndex(-1); OrderedMockShape *shape4 = new OrderedMockShape(order); shape4->setZIndex(9); bottom->addShape(shape3); bottom->addShape(shape4); MockCanvas canvas; KoShapeManager manager(&canvas); manager.addShape(top); manager.addShape(bottom); QCOMPARE(manager.shapes().count(), 6); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); KoViewConverter vc; manager.paint(painter, vc, false); QCOMPARE(top->paintedCount, 1); QCOMPARE(bottom->paintedCount, 1); QCOMPARE(shape1->paintedCount, 1); QCOMPARE(shape2->paintedCount, 1); QCOMPARE(shape3->paintedCount, 1); QCOMPARE(shape4->paintedCount, 1); QCOMPARE(order.count(), 4); QVERIFY(order[0] == shape3); // lowest first QVERIFY(order[1] == shape4); QVERIFY(order[2] == shape2); QVERIFY(order[3] == shape1); // again, with clipping. order.clear(); painter.setClipRect(0, 0, 100, 100); manager.paint(painter, vc, false); QCOMPARE(top->paintedCount, 2); QCOMPARE(bottom->paintedCount, 2); QCOMPARE(shape1->paintedCount, 2); QCOMPARE(shape2->paintedCount, 2); QCOMPARE(shape3->paintedCount, 2); QCOMPARE(shape4->paintedCount, 2); QCOMPARE(order.count(), 4); QVERIFY(order[0] == shape3); // lowest first QVERIFY(order[1] == shape4); QVERIFY(order[2] == shape2); QVERIFY(order[3] == shape1); order.clear(); MockContainer *root = new MockContainer(); root->setZIndex(0); MockContainer *branch1 = new MockContainer(); branch1->setZIndex(1); OrderedMockShape *child1_1 = new OrderedMockShape(order); child1_1->setZIndex(1); OrderedMockShape *child1_2 = new OrderedMockShape(order); child1_2->setZIndex(2); branch1->addShape(child1_1); branch1->addShape(child1_2); MockContainer *branch2 = new MockContainer(); branch2->setZIndex(2); OrderedMockShape *child2_1 = new OrderedMockShape(order); child2_1->setZIndex(1); OrderedMockShape *child2_2 = new OrderedMockShape(order); child2_2->setZIndex(2); branch2->addShape(child2_1); branch2->addShape(child2_2); root->addShape(branch1); root->addShape(branch2); QList sortedShapes; sortedShapes.append(root); sortedShapes.append(branch1); sortedShapes.append(branch2); sortedShapes.append(branch1->shapes()); sortedShapes.append(branch2->shapes()); qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); QCOMPARE(sortedShapes.count(), 7); QVERIFY(sortedShapes[0] == root); QVERIFY(sortedShapes[1] == branch1); QVERIFY(sortedShapes[2] == child1_1); QVERIFY(sortedShapes[3] == child1_2); QVERIFY(sortedShapes[4] == branch2); QVERIFY(sortedShapes[5] == child2_1); QVERIFY(sortedShapes[6] == child2_2); delete top; delete bottom; delete root; } #include #include #include #include #include "kis_debug.h" void TestShapePainting::testGroupUngroup() { MockShape *shape1 = new MockShape(); MockShape *shape2 = new MockShape(); shape1->setName("shape1"); shape2->setName("shape2"); QList groupedShapes = {shape1, shape2}; MockShapeController controller; MockCanvas canvas(&controller); KoShapeManager *manager = canvas.shapeManager(); controller.addShape(shape1); controller.addShape(shape2); QImage image(100, 100, QImage::Format_Mono); QPainter painter(&image); painter.setClipRect(image.rect()); KoViewConverter vc; KoShapeGroup *group = 0; for (int i = 0; i < 3; i++) { { group = new KoShapeGroup(); group->setName("group"); KUndo2Command groupingCommand; - canvas.shapeController()->addShapeDirect(group, &groupingCommand); + canvas.shapeController()->addShapeDirect(group, 0, &groupingCommand); new KoShapeGroupCommand(group, groupedShapes, false, true, true, &groupingCommand); groupingCommand.redo(); manager->paint(painter, vc, false); QCOMPARE(shape1->paintedCount, 2 * i + 1); QCOMPARE(shape2->paintedCount, 2 * i + 1); QCOMPARE(manager->shapes().size(), 3); } { KUndo2Command ungroupingCommand; new KoShapeUngroupCommand(group, group->shapes(), QList(), &ungroupingCommand); canvas.shapeController()->removeShape(group, &ungroupingCommand); ungroupingCommand.redo(); manager->paint(painter, vc, false); QCOMPARE(shape1->paintedCount, 2 * i + 2); QCOMPARE(shape2->paintedCount, 2 * i + 2); QCOMPARE(manager->shapes().size(), 2); group = 0; } } } QTEST_MAIN(TestShapePainting) diff --git a/libs/flake/tools/KoCreateShapeStrategy.cpp b/libs/flake/tools/KoCreateShapeStrategy.cpp index 054a56b7c4..83fbc4424e 100644 --- a/libs/flake/tools/KoCreateShapeStrategy.cpp +++ b/libs/flake/tools/KoCreateShapeStrategy.cpp @@ -1,134 +1,134 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2006 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCreateShapeStrategy.h" #include "KoShapeRubberSelectStrategy_p.h" #include "KoCreateShapesTool.h" #include "KoShape.h" #include "KoShapeRegistry.h" #include "KoShapeManager.h" #include "KoCanvasBase.h" #include "KoSelection.h" #include "KoShapeFactoryBase.h" #include "KoShapeController.h" #include "KoViewConverter.h" #include #include KoCreateShapeStrategy::KoCreateShapeStrategy(KoCreateShapesTool *tool, const QPointF &clicked) : KoShapeRubberSelectStrategy(tool, clicked, tool->canvas()->snapToGrid()) { KoCreateShapesTool *parent = static_cast(d_ptr->tool); KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(parent->shapeId()); if (factory) { const KoProperties *props = parent->shapeProperties(); KoShape *shape; if (props) { shape = factory->createShape(props); } else { shape = factory->createDefaultShape(); } m_outline = shape->outline(); m_outlineBoundingRect = m_outline.boundingRect(); delete shape; } } KUndo2Command* KoCreateShapeStrategy::createCommand() { Q_D(KoShapeRubberSelectStrategy); KoCreateShapesTool *parent = static_cast(d_ptr->tool); KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(parent->shapeId()); if (! factory) { warnFlake << "Application requested a shape that is not registered" << parent->shapeId(); return 0; } const KoProperties *props = parent->shapeProperties(); KoShape *shape; if (props) shape = factory->createShape(props, parent->canvas()->shapeController()->resourceManager()); else shape = factory->createDefaultShape(parent->canvas()->shapeController()->resourceManager()); if (shape->shapeId().isEmpty()) shape->setShapeId(factory->id()); QRectF rect = d->selectedRect(); shape->setPosition(rect.topLeft()); QSizeF newSize = rect.size(); // if the user has dragged when creating the shape, // resize the shape to the dragged size if (newSize.width() > 1.0 && newSize.height() > 1.0) shape->setSize(newSize); - KUndo2Command * cmd = parent->canvas()->shapeController()->addShape(shape); + KUndo2Command * cmd = parent->canvas()->shapeController()->addShape(shape, 0); if (cmd) { KoSelection *selection = parent->canvas()->shapeManager()->selection(); selection->deselectAll(); selection->select(shape); } return cmd; } void KoCreateShapeStrategy::finishInteraction(Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); Q_D(KoShapeRubberSelectStrategy); d->tool->canvas()->updateCanvas(d->selectedRect()); } void KoCreateShapeStrategy::paint(QPainter &painter, const KoViewConverter &converter) { Q_D(KoShapeRubberSelectStrategy); if (m_outline.isEmpty()) KoShapeRubberSelectStrategy::paint(painter, converter); else { painter.save(); painter.setRenderHint(QPainter::Antialiasing, false); QColor selectColor(Qt::blue); // TODO make configurable selectColor.setAlphaF(0.5); QBrush sb(selectColor, Qt::SolidPattern); painter.setPen(QPen(sb, 0)); painter.setBrush(sb); QRectF paintRect = converter.documentToView(d->selectedRect()); qreal xscale = paintRect.width() / m_outlineBoundingRect.width(); qreal yscale = paintRect.height() / m_outlineBoundingRect.height(); QTransform matrix; matrix.translate(-m_outlineBoundingRect.left(), -m_outlineBoundingRect.top()); matrix.scale(xscale, yscale); painter.translate(paintRect.left(), paintRect.top()); painter.setTransform(matrix, true); painter.drawPath(m_outline); painter.restore(); } } void KoCreateShapeStrategy::handleMouseMove(const QPointF &point, Qt::KeyboardModifiers modifiers) { Q_D(KoShapeRubberSelectStrategy); KoShapeRubberSelectStrategy::handleMouseMove(point, modifiers); if (! m_outline.isEmpty()) d->tool->canvas()->updateCanvas(d->selectedRect()); } diff --git a/libs/ui/actions/KisPasteActionFactory.cpp b/libs/ui/actions/KisPasteActionFactory.cpp index 5a23f23456..3f5c66c8de 100644 --- a/libs/ui/actions/KisPasteActionFactory.cpp +++ b/libs/ui/actions/KisPasteActionFactory.cpp @@ -1,199 +1,199 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisPasteActionFactory.h" #include "kis_image.h" #include "KisViewManager.h" #include "kis_tool_proxy.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_shape_layer.h" #include "kis_import_catcher.h" #include "kis_clipboard.h" #include "commands/kis_image_layer_add_command.h" #include "kis_processing_applicator.h" #include #include #include #include #include #include "kis_algebra_2d.h" #include namespace { QPointF getFittingOffset(QList shapes, const QPointF &shapesOffset, const QRectF &documentRect, const qreal fitRatio) { QPointF accumulatedFitOffset; Q_FOREACH (KoShape *shape, shapes) { const QRectF bounds = shape->boundingRect(); const QPointF center = bounds.center() + shapesOffset; const qreal wMargin = (0.5 - fitRatio) * bounds.width(); const qreal hMargin = (0.5 - fitRatio) * bounds.height(); const QRectF allowedRect = documentRect.adjusted(-wMargin, -hMargin, wMargin, hMargin); const QPointF fittedCenter = KisAlgebra2D::clampPoint(center, allowedRect); accumulatedFitOffset += fittedCenter - center; } return accumulatedFitOffset; } bool tryPasteShapes(bool pasteAtCursorPosition, KisViewManager *view) { bool result = false; KoSvgPaste paste; if (paste.hasShapes()) { KoCanvasBase *canvas = view->canvasBase(); QSizeF fragmentSize; QList shapes = paste.fetchShapes(canvas->shapeController()->documentRectInPixels(), canvas->shapeController()->pixelsPerInch(), &fragmentSize); if (!shapes.isEmpty()) { KoShapeManager *shapeManager = canvas->shapeManager(); shapeManager->selection()->deselectAll(); KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18n("Paste shapes")); - canvas->shapeController()->addShapesDirect(shapes, parentCommand); + canvas->shapeController()->addShapesDirect(shapes, 0, parentCommand); QPointF finalShapesOffset; if (pasteAtCursorPosition) { QRectF boundingRect = KoShape::boundingRect(shapes); const QPointF cursorPos = canvas->canvasController()->currentCursorPosition(); finalShapesOffset = cursorPos - boundingRect.center(); } else { bool foundOverlapping = false; QRectF boundingRect = KoShape::boundingRect(shapes); const QPointF offsetStep = 0.1 * QPointF(boundingRect.width(), boundingRect.height()); QPointF offset; Q_FOREACH (KoShape *shape, shapes) { QRectF br1 = shape->boundingRect(); bool hasOverlappingShape = false; do { hasOverlappingShape = false; // we cannot use shapesAt() here, because the groups are not // handled in the shape manager's tree QList conflicts = shapeManager->shapes(); Q_FOREACH (KoShape *intersectedShape, conflicts) { if (intersectedShape == shape) continue; QRectF br2 = intersectedShape->boundingRect(); const qreal tolerance = 2.0; /* pt */ if (KisAlgebra2D::fuzzyCompareRects(br1, br2, tolerance)) { br1.translate(offsetStep.x(), offsetStep.y()); offset += offsetStep; hasOverlappingShape = true; foundOverlapping = true; break; } } } while (hasOverlappingShape); if (foundOverlapping) break; } if (foundOverlapping) { finalShapesOffset = offset; } } const QRectF documentRect = canvas->shapeController()->documentRect(); finalShapesOffset += getFittingOffset(shapes, finalShapesOffset, documentRect, 0.1); if (!finalShapesOffset.isNull()) { new KoShapeMoveCommand(shapes, finalShapesOffset, parentCommand); } canvas->addCommand(parentCommand); Q_FOREACH (KoShape *shape, shapes) { canvas->selectedShapesProxy()->selection()->select(shape); } result = true; } } return result; } } void KisPasteActionFactory::run(bool pasteAtCursorPosition, KisViewManager *view) { KisImageSP image = view->image(); if (!image) return; if (tryPasteShapes(pasteAtCursorPosition, view)) { return; } const QRect fittingBounds = pasteAtCursorPosition ? QRect() : image->bounds(); KisPaintDeviceSP clip = KisClipboard::instance()->clip(fittingBounds, true); if (clip) { if (pasteAtCursorPosition) { const QPointF docPos = view->canvasBase()->canvasController()->currentCursorPosition(); const QPointF imagePos = view->canvasBase()->coordinatesConverter()->documentToImage(docPos); const QPointF offset = (imagePos - QRectF(clip->exactBounds()).center()).toPoint(); clip->setX(clip->x() + offset.x()); clip->setY(clip->y() + offset.y()); } KisImportCatcher::adaptClipToImageColorSpace(clip, image); KisPaintLayer *newLayer = new KisPaintLayer(image.data(), image->nextLayerName() + i18n("(pasted)"), OPACITY_OPAQUE_U8, clip); KisNodeSP aboveNode = view->activeLayer(); KisNodeSP parentNode = aboveNode ? aboveNode->parent() : image->root(); KUndo2Command *cmd = new KisImageLayerAddCommand(image, newLayer, parentNode, aboveNode); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); endAction(ap, KisOperationConfiguration(id()).toXML()); } else { // XXX: "Add saving of XML data for Paste of shapes" view->canvasBase()->toolProxy()->paste(); } } diff --git a/libs/ui/actions/kis_selection_action_factories.cpp b/libs/ui/actions/kis_selection_action_factories.cpp index e11d6124cb..b0660b5deb 100644 --- a/libs/ui/actions/kis_selection_action_factories.cpp +++ b/libs/ui/actions/kis_selection_action_factories.cpp @@ -1,579 +1,579 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_selection_action_factories.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_pixel_selection.h" #include "kis_paint_layer.h" #include "kis_image.h" #include "kis_image_barrier_locker.h" #include "kis_fill_painter.h" #include "kis_transaction.h" #include "kis_iterator_ng.h" #include "kis_processing_applicator.h" #include "kis_group_layer.h" #include "commands/kis_selection_commands.h" #include "commands/kis_image_layer_add_command.h" #include "kis_tool_proxy.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_selection_manager.h" #include "kis_transaction_based_command.h" #include "kis_selection_filters.h" #include "kis_shape_selection.h" #include "KisPart.h" #include "kis_shape_layer.h" #include #include #include #include "kis_canvas_resource_provider.h" #include "kis_figure_painting_tool_helper.h" namespace ActionHelper { void copyFromDevice(KisViewManager *view, KisPaintDeviceSP device, bool makeSharpClip = false) { KisImageWSP image = view->image(); if (!image) return; KisSelectionSP selection = view->selection(); QRect rc = (selection) ? selection->selectedExactRect() : image->bounds(); KisPaintDeviceSP clip = new KisPaintDevice(device->colorSpace()); Q_CHECK_PTR(clip); const KoColorSpace *cs = clip->colorSpace(); // TODO if the source is linked... copy from all linked layers?!? // Copy image data KisPainter::copyAreaOptimized(QPoint(), device, clip, rc); if (selection) { // Apply selection mask. KisPaintDeviceSP selectionProjection = selection->projection(); KisHLineIteratorSP layerIt = clip->createHLineIteratorNG(0, 0, rc.width()); KisHLineConstIteratorSP selectionIt = selectionProjection->createHLineIteratorNG(rc.x(), rc.y(), rc.width()); const KoColorSpace *selCs = selection->projection()->colorSpace(); for (qint32 y = 0; y < rc.height(); y++) { for (qint32 x = 0; x < rc.width(); x++) { /** * Sharp method is an exact reverse of COMPOSITE_OVER * so if you cover the cut/copied piece over its source * you get an exactly the same image without any seams */ if (makeSharpClip) { qreal dstAlpha = cs->opacityF(layerIt->rawData()); qreal sel = selCs->opacityF(selectionIt->oldRawData()); qreal newAlpha = sel * dstAlpha / (1.0 - dstAlpha + sel * dstAlpha); float mask = newAlpha / dstAlpha; cs->applyAlphaNormedFloatMask(layerIt->rawData(), &mask, 1); } else { cs->applyAlphaU8Mask(layerIt->rawData(), selectionIt->oldRawData(), 1); } layerIt->nextPixel(); selectionIt->nextPixel(); } layerIt->nextRow(); selectionIt->nextRow(); } } KisClipboard::instance()->setClip(clip, rc.topLeft()); } } void KisSelectAllActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Select All")); if (!image->globalSelection()) { ap->applyCommand(new KisSetEmptyGlobalSelectionCommand(image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } struct SelectAll : public KisTransactionBasedCommand { SelectAll(KisImageSP image) : m_image(image) {} KisImageSP m_image; KUndo2Command* paint() override { KisSelectionSP selection = m_image->globalSelection(); KisSelectionTransaction transaction(selection->pixelSelection()); selection->pixelSelection()->select(m_image->bounds()); return transaction.endAndTake(); } }; ap->applyCommand(new SelectAll(image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisDeselectActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KUndo2Command *cmd = new KisDeselectGlobalSelectionCommand(image); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisReselectActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KUndo2Command *cmd = new KisReselectGlobalSelectionCommand(image); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisFillActionFactory::run(const QString &fillSource, KisViewManager *view) { KisNodeSP node = view->activeNode(); if (!node || !node->hasEditablePaintDevice()) return; KisSelectionSP selection = view->selection(); QRect selectedRect = selection ? selection->selectedRect() : view->image()->bounds(); Q_UNUSED(selectedRect); KisPaintDeviceSP filled = node->paintDevice()->createCompositionSourceDevice(); Q_UNUSED(filled); bool usePattern = false; bool useBgColor = false; if (fillSource.contains("pattern")) { usePattern = true; } else if (fillSource.contains("bg")) { useBgColor = true; } KisProcessingApplicator applicator(view->image(), node, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Flood Fill Layer")); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(view->image(), node, view->resourceProvider()->resourceManager()); if (!fillSource.contains("opacity")) { resources->setOpacity(1.0); } KisProcessingVisitorSP visitor = new FillProcessingVisitor(QPoint(0, 0), // start position selection, resources, false, // fast mode usePattern, true, // fill only selection, 0, // feathering radius 0, // sizemod 80, // threshold, false, // unmerged useBgColor); applicator.applyVisitor(visitor, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); } void KisClearActionFactory::run(KisViewManager *view) { // XXX: "Add saving of XML data for Clear action" view->canvasBase()->toolProxy()->deleteSelection(); } void KisImageResizeToSelectionActionFactory::run(KisViewManager *view) { // XXX: "Add saving of XML data for Image Resize To Selection action" KisSelectionSP selection = view->selection(); if (!selection) return; view->image()->cropImage(selection->selectedExactRect()); } void KisCutCopyActionFactory::run(bool willCut, bool makeSharpClip, KisViewManager *view) { KisImageSP image = view->image(); if (!image) return; bool haveShapesSelected = view->selectionManager()->haveShapesSelected(); if (haveShapesSelected) { // XXX: "Add saving of XML data for Cut/Copy of shapes" KisImageBarrierLocker locker(image); if (willCut) { view->canvasBase()->toolProxy()->cut(); } else { view->canvasBase()->toolProxy()->copy(); } } else { KisNodeSP node = view->activeNode(); if (!node) return; KisSelectionSP selection = view->selection(); if (selection.isNull()) return; { KisImageBarrierLocker locker(image); KisPaintDeviceSP dev = node->paintDevice(); if (!dev) { dev = node->projection(); } if (!dev) { view->showFloatingMessage( i18nc("floating message when cannot copy from a node", "Cannot copy pixels from this type of layer "), QIcon(), 3000, KisFloatingMessage::Medium); return; } if (dev->exactBounds().isEmpty()) { view->showFloatingMessage( i18nc("floating message when copying empty selection", "Selection is empty: no pixels were copied "), QIcon(), 3000, KisFloatingMessage::Medium); return; } ActionHelper::copyFromDevice(view, dev, makeSharpClip); } if (willCut) { KUndo2Command *command = 0; if (node->hasEditablePaintDevice()) { struct ClearSelection : public KisTransactionBasedCommand { ClearSelection(KisNodeSP node, KisSelectionSP sel) : m_node(node), m_sel(sel) {} KisNodeSP m_node; KisSelectionSP m_sel; KUndo2Command* paint() override { KisSelectionSP cutSelection = m_sel; // Shrinking the cutting area was previously used // for getting seamless cut-paste. Now we use makeSharpClip // instead. // QRect originalRect = cutSelection->selectedExactRect(); // static const int preciseSelectionThreshold = 16; // // if (originalRect.width() > preciseSelectionThreshold || // originalRect.height() > preciseSelectionThreshold) { // cutSelection = new KisSelection(*m_sel); // delete cutSelection->flatten(); // // KisSelectionFilter* filter = new KisShrinkSelectionFilter(1, 1, false); // // QRect processingRect = filter->changeRect(originalRect); // filter->process(cutSelection->pixelSelection(), processingRect); // } KisTransaction transaction(m_node->paintDevice()); m_node->paintDevice()->clearSelection(cutSelection); m_node->setDirty(cutSelection->selectedRect()); return transaction.endAndTake(); } }; command = new ClearSelection(node, selection); } KUndo2MagicString actionName = willCut ? kundo2_i18n("Cut") : kundo2_i18n("Copy"); KisProcessingApplicator *ap = beginAction(view, actionName); if (command) { ap->applyCommand(command, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } KisOperationConfiguration config(id()); config.setProperty("will-cut", willCut); endAction(ap, config.toXML()); } } } void KisCopyMergedActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; if (!view->blockUntilOperationsFinished(image)) return; image->barrierLock(); KisPaintDeviceSP dev = image->root()->projection(); ActionHelper::copyFromDevice(view, dev); image->unlock(); KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Copy Merged")); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisPasteNewActionFactory::run(KisViewManager *viewManager) { Q_UNUSED(viewManager); KisPaintDeviceSP clip = KisClipboard::instance()->clip(QRect(), true); if (!clip) return; QRect rect = clip->exactBounds(); if (rect.isEmpty()) return; KisDocument *doc = KisPart::instance()->createDocument(); KisImageSP image = new KisImage(doc->createUndoStore(), rect.width(), rect.height(), clip->colorSpace(), i18n("Pasted")); KisPaintLayerSP layer = new KisPaintLayer(image.data(), image->nextLayerName() + i18n("(pasted)"), OPACITY_OPAQUE_U8, clip->colorSpace()); KisPainter::copyAreaOptimized(QPoint(), clip, layer->paintDevice(), rect); image->addNode(layer.data(), image->rootLayer()); doc->setCurrentImage(image); KisPart::instance()->addDocument(doc); KisMainWindow *win = viewManager->mainWindow(); win->addViewAndNotifyLoadingCompleted(doc); } void KisInvertSelectionOperation::runFromXML(KisViewManager* view, const KisOperationConfiguration& config) { KisSelectionFilter* filter = new KisInvertSelectionFilter(); runFilter(filter, view, config); } void KisSelectionToVectorActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (selection->hasShapeSelection() || !selection->outlineCacheValid()) { return; } QPainterPath selectionOutline = selection->outlineCache(); QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline)); shape->setShapeId(KoPathShapeId); /** * Mark a shape that it belongs to a shape selection */ if(!shape->userData()) { shape->setUserData(new KisShapeSelectionMarker); } KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection")); - ap->applyCommand(view->canvasBase()->shapeController()->addShape(shape), + ap->applyCommand(view->canvasBase()->shapeController()->addShape(shape, 0), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisShapesToVectorSelectionActionFactory::run(KisViewManager* view) { const QList originalShapes = view->canvasBase()->shapeManager()->selection()->selectedShapes(); QList clonedShapes; Q_FOREACH (KoShape *shape, originalShapes) { clonedShapes << shape->cloneShape(); } KisSelectionToolHelper helper(view->canvasBase(), kundo2_i18n("Convert shapes to vector selection")); helper.addSelectionShapes(clonedShapes); } void KisSelectionToShapeActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (!selection->outlineCacheValid()) { return; } QPainterPath selectionOutline = selection->outlineCache(); QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline)); shape->setShapeId(KoPathShapeId); KoColor fgColor = view->canvasBase()->resourceManager()->resource(KoCanvasResourceManager::ForegroundColor).value(); KoShapeStrokeSP border(new KoShapeStroke(1.0, fgColor.toQColor())); shape->setStroke(border); view->document()->shapeController()->addShape(shape); } void KisStrokeSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params) { KisImageWSP image = view->image(); if (!image) { return; } KisSelectionSP selection = view->selection(); if (!selection) { return; } int size = params.lineSize; KisPixelSelectionSP pixelSelection = selection->projection(); if (!pixelSelection->outlineCacheValid()) { pixelSelection->recalculateOutlineCache(); } QPainterPath outline = pixelSelection->outlineCache(); QColor color = params.color.toQColor(); KisNodeSP currentNode = view->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); if (!currentNode->inherits("KisShapeLayer") && currentNode->childCount() == 0) { KoCanvasResourceManager * rManager = view->resourceProvider()->resourceManager(); KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush; KisPainter::FillStyle fillStyle = params.fillStyle(); KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image, currentNode, rManager , strokeStyle, fillStyle); helper.setFGColorOverride(params.color); helper.setSelectionOverride(0); QPen pen(Qt::red, size); pen.setJoinStyle(Qt::RoundJoin); if (fillStyle != KisPainter::FillStyleNone) { helper.paintPainterPathQPenFill(outline, pen, params.fillColor); } else { helper.paintPainterPathQPen(outline, pen, params.fillColor); } } else { QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(outline)); shape->setShapeId(KoPathShapeId); KoShapeStrokeSP border(new KoShapeStroke(size, color)); shape->setStroke(border); view->document()->shapeController()->addShape(shape); } image->setModified(); } void KisStrokeBrushSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params) { KisImageWSP image = view->image(); if (!image) { return; } KisSelectionSP selection = view->selection(); if (!selection) { return; } KisPixelSelectionSP pixelSelection = selection->projection(); if (!pixelSelection->outlineCacheValid()) { pixelSelection->recalculateOutlineCache(); } KisNodeSP currentNode = view->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); if (!currentNode->inherits("KisShapeLayer") && currentNode->childCount() == 0) { KoCanvasResourceManager * rManager = view->resourceProvider()->resourceManager(); QPainterPath outline = pixelSelection->outlineCache(); KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush; KisPainter::FillStyle fillStyle = KisPainter::FillStyleNone; KoColor color = params.color; KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image, currentNode, rManager , strokeStyle, fillStyle); helper.setFGColorOverride(color); helper.setSelectionOverride(0); helper.paintPainterPath(outline); image->setModified(); } } diff --git a/libs/ui/tool/kis_selection_tool_helper.cpp b/libs/ui/tool/kis_selection_tool_helper.cpp index db14dc4223..d39f241b45 100644 --- a/libs/ui/tool/kis_selection_tool_helper.cpp +++ b/libs/ui/tool/kis_selection_tool_helper.cpp @@ -1,263 +1,263 @@ /* * Copyright (c) 2007 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_selection_tool_helper.h" #include #include #include #include "kis_pixel_selection.h" #include "kis_shape_selection.h" #include "kis_image.h" #include "canvas/kis_canvas2.h" #include "KisViewManager.h" #include "kis_selection_manager.h" #include "kis_transaction.h" #include "commands/kis_selection_commands.h" #include "kis_shape_controller.h" #include #include "kis_processing_applicator.h" #include "kis_transaction_based_command.h" #include "kis_gui_context_command.h" #include "kis_command_utils.h" #include "commands/kis_deselect_global_selection_command.h" #include "kis_algebra_2d.h" #include "kis_config.h" KisSelectionToolHelper::KisSelectionToolHelper(KisCanvas2* canvas, const KUndo2MagicString& name) : m_canvas(canvas) , m_name(name) { m_image = m_canvas->viewManager()->image(); } KisSelectionToolHelper::~KisSelectionToolHelper() { } struct LazyInitGlobalSelection : public KisTransactionBasedCommand { LazyInitGlobalSelection(KisViewManager *view) : m_view(view) {} KisViewManager *m_view; KUndo2Command* paint() override { return !m_view->selection() ? new KisSetEmptyGlobalSelectionCommand(m_view->image()) : 0; } }; void KisSelectionToolHelper::selectPixelSelection(KisPixelSelectionSP selection, SelectionAction action) { KisViewManager* view = m_canvas->viewManager(); if (selection->selectedExactRect().isEmpty()) { m_canvas->viewManager()->selectionManager()->deselect(); return; } KisProcessingApplicator applicator(view->image(), 0 /* we need no automatic updates */, KisProcessingApplicator::SUPPORTS_WRAPAROUND_MODE, KisImageSignalVector() << ModifiedSignal, m_name); applicator.applyCommand(new LazyInitGlobalSelection(view)); struct ApplyToPixelSelection : public KisTransactionBasedCommand { ApplyToPixelSelection(KisViewManager *view, KisPixelSelectionSP selection, SelectionAction action) : m_view(view), m_selection(selection), m_action(action) {} KisViewManager *m_view; KisPixelSelectionSP m_selection; SelectionAction m_action; KUndo2Command* paint() override { KisPixelSelectionSP pixelSelection = m_view->selection()->pixelSelection(); KIS_ASSERT_RECOVER(pixelSelection) { return 0; } bool hasSelection = !pixelSelection->isEmpty(); KisSelectionTransaction transaction(pixelSelection); if (!hasSelection && m_action == SELECTION_SUBTRACT) { pixelSelection->invert(); } pixelSelection->applySelection(m_selection, m_action); const QRect imageBounds = m_view->image()->bounds(); QRect selectionExactRect = m_view->selection()->selectedExactRect(); if (!imageBounds.contains(selectionExactRect)) { pixelSelection->crop(imageBounds); if (pixelSelection->outlineCacheValid()) { QPainterPath cache = pixelSelection->outlineCache(); QPainterPath imagePath; imagePath.addRect(imageBounds); cache &= imagePath; pixelSelection->setOutlineCache(cache); } selectionExactRect &= imageBounds; } QRect dirtyRect = imageBounds; if (hasSelection && m_action != SELECTION_REPLACE && m_action != SELECTION_INTERSECT) { dirtyRect = m_selection->selectedRect(); } m_view->selection()->updateProjection(dirtyRect); KUndo2Command *savedCommand = transaction.endAndTake(); pixelSelection->setDirty(dirtyRect); if (selectionExactRect.isEmpty()) { KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand(); cmd->addCommand(savedCommand); cmd->addCommand(new KisDeselectGlobalSelectionCommand(m_view->image())); savedCommand = cmd; } return savedCommand; } }; applicator.applyCommand(new ApplyToPixelSelection(view, selection, action)); applicator.end(); } void KisSelectionToolHelper::addSelectionShape(KoShape* shape) { QList shapes; shapes.append(shape); addSelectionShapes(shapes); } void KisSelectionToolHelper::addSelectionShapes(QList< KoShape* > shapes) { KisViewManager* view = m_canvas->viewManager(); if (view->image()->wrapAroundModePermitted()) { view->showFloatingMessage( i18n("Shape selection does not fully " "support wraparound mode. Please " "use pixel selection instead"), KisIconUtils::loadIcon("selection-info")); } KisProcessingApplicator applicator(view->image(), 0 /* we need no automatic updates */, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, m_name); applicator.applyCommand(new LazyInitGlobalSelection(view)); struct ClearPixelSelection : public KisTransactionBasedCommand { ClearPixelSelection(KisViewManager *view) : m_view(view) {} KisViewManager *m_view; KUndo2Command* paint() override { KisPixelSelectionSP pixelSelection = m_view->selection()->pixelSelection(); KIS_ASSERT_RECOVER(pixelSelection) { return 0; } KisSelectionTransaction transaction(pixelSelection); pixelSelection->clear(); return transaction.endAndTake(); } }; applicator.applyCommand(new ClearPixelSelection(view)); struct AddSelectionShape : public KisTransactionBasedCommand { AddSelectionShape(KisViewManager *view, KoShape* shape) : m_view(view), m_shape(shape) {} KisViewManager *m_view; KoShape* m_shape; KUndo2Command* paint() override { /** * Mark a shape that it belongs to a shape selection */ if(!m_shape->userData()) { m_shape->setUserData(new KisShapeSelectionMarker); } - return m_view->canvasBase()->shapeController()->addShape(m_shape); + return m_view->canvasBase()->shapeController()->addShape(m_shape, 0); } }; Q_FOREACH (KoShape* shape, shapes) { applicator.applyCommand( new KisGuiContextCommand(new AddSelectionShape(view, shape), view)); } applicator.end(); } void KisSelectionToolHelper::cropRectIfNeeded(QRect *rect, SelectionAction action) { KisImageWSP image = m_canvas->viewManager()->image(); if (!image->wrapAroundModePermitted() && action != SELECTION_SUBTRACT) { *rect &= image->bounds(); } } bool KisSelectionToolHelper::canShortcutToDeselect(const QRect &rect, SelectionAction action) { return rect.isEmpty() && (action == SELECTION_INTERSECT || action == SELECTION_REPLACE); } bool KisSelectionToolHelper::canShortcutToNoop(const QRect &rect, SelectionAction action) { return rect.isEmpty() && action == SELECTION_ADD; } void KisSelectionToolHelper::cropPathIfNeeded(QPainterPath *path) { KisImageWSP image = m_canvas->viewManager()->image(); if (!image->wrapAroundModePermitted()) { QPainterPath cropPath; cropPath.addRect(image->bounds()); *path &= cropPath; } } bool KisSelectionToolHelper::tryDeselectCurrentSelection(const QRectF selectionViewRect, SelectionAction action) { bool result = false; if (KisAlgebra2D::maxDimension(selectionViewRect) < KisConfig().selectionViewSizeMinimum() && (action == SELECTION_INTERSECT || action == SELECTION_REPLACE)) { // Queueing this action to ensure we avoid a race condition when unlocking the node system QTimer::singleShot(0, m_canvas->viewManager()->selectionManager(), SLOT(deselect())); result = true; } return result; } diff --git a/libs/ui/tool/kis_tool_shape.cc b/libs/ui/tool/kis_tool_shape.cc index 306a84a914..34c6238354 100644 --- a/libs/ui/tool/kis_tool_shape.cc +++ b/libs/ui/tool/kis_tool_shape.cc @@ -1,255 +1,255 @@ /* * Copyright (c) 2005 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_shape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_figure_painting_tool_helper.h" #include #include KisToolShape::KisToolShape(KoCanvasBase * canvas, const QCursor & cursor) : KisToolPaint(canvas, cursor) { m_shapeOptionsWidget = 0; } KisToolShape::~KisToolShape() { // in case the widget hasn't been shown if (m_shapeOptionsWidget && !m_shapeOptionsWidget->parent()) { delete m_shapeOptionsWidget; } } void KisToolShape::activate(ToolActivation toolActivation, const QSet &shapes) { KisToolPaint::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } int KisToolShape::flags() const { return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET |KisTool::FLAG_USES_CUSTOM_SIZE; } QWidget * KisToolShape::createOptionWidget() { m_shapeOptionsWidget = new WdgGeometryOptions(0); m_shapeOptionsWidget->cmbOutline->setCurrentIndex(KisPainter::StrokeStyleBrush); //connect two combo box event. Inherited classes can call the slots to make appropriate changes connect(m_shapeOptionsWidget->cmbOutline, SIGNAL(currentIndexChanged(int)), this, SLOT(outlineSettingChanged(int))); connect(m_shapeOptionsWidget->cmbFill, SIGNAL(currentIndexChanged(int)), this, SLOT(fillSettingChanged(int))); m_shapeOptionsWidget->cmbOutline->setCurrentIndex(m_configGroup.readEntry("outlineType", 0)); m_shapeOptionsWidget->cmbFill->setCurrentIndex(m_configGroup.readEntry("fillType", 0)); //if both settings are empty, force the outline to brush so the tool will work when first activated if ( m_shapeOptionsWidget->cmbFill->currentIndex() == 0 && m_shapeOptionsWidget->cmbOutline->currentIndex() == 0) { m_shapeOptionsWidget->cmbOutline->setCurrentIndex(1); // brush } return m_shapeOptionsWidget; } void KisToolShape::outlineSettingChanged(int value) { m_configGroup.writeEntry("outlineType", value); } void KisToolShape::fillSettingChanged(int value) { m_configGroup.writeEntry("fillType", value); } KisPainter::FillStyle KisToolShape::fillStyle(void) { if (m_shapeOptionsWidget) { return static_cast(m_shapeOptionsWidget->cmbFill->currentIndex()); } else { return KisPainter::FillStyleNone; } } KisPainter::StrokeStyle KisToolShape::strokeStyle(void) { if (m_shapeOptionsWidget) { return static_cast(m_shapeOptionsWidget->cmbOutline->currentIndex()); } else { return KisPainter::StrokeStyleNone; } } qreal KisToolShape::currentStrokeWidth() const { const qreal sizeInPx = canvas()->resourceManager()->resource(KisCanvasResourceProvider::Size).toReal(); return canvas()->unit().fromUserValue(sizeInPx); } void KisToolShape::setupPaintAction(KisRecordedPaintAction* action) { KisToolPaint::setupPaintAction(action); action->setFillStyle(fillStyle()); action->setStrokeStyle(strokeStyle()); action->setGenerator(currentGenerator()); action->setPattern(currentPattern()); action->setGradient(currentGradient()); } void KisToolShape::addShape(KoShape* shape) { KoImageCollection* imageCollection = canvas()->shapeController()->resourceManager()->imageCollection(); switch(fillStyle()) { case KisPainter::FillStyleForegroundColor: shape->setBackground(QSharedPointer(new KoColorBackground(currentFgColor().toQColor()))); break; case KisPainter::FillStyleBackgroundColor: shape->setBackground(QSharedPointer(new KoColorBackground(currentBgColor().toQColor()))); break; case KisPainter::FillStylePattern: if (imageCollection) { QSharedPointer fill(new KoPatternBackground(imageCollection)); if (currentPattern()) { fill->setPattern(currentPattern()->pattern()); shape->setBackground(fill); } } else { shape->setBackground(QSharedPointer(0)); } break; case KisPainter::FillStyleGradient: { QLinearGradient *gradient = new QLinearGradient(QPointF(0, 0), QPointF(1, 1)); gradient->setCoordinateMode(QGradient::ObjectBoundingMode); gradient->setStops(currentGradient()->toQGradient()->stops()); QSharedPointer gradientFill(new KoGradientBackground(gradient)); shape->setBackground(gradientFill); } break; case KisPainter::FillStyleNone: default: shape->setBackground(QSharedPointer(0)); break; } - KUndo2Command * cmd = canvas()->shapeController()->addShape(shape); + KUndo2Command * cmd = canvas()->shapeController()->addShape(shape, 0); canvas()->addCommand(cmd); } void KisToolShape::addPathShape(KoPathShape* pathShape, const KUndo2MagicString& name) { KisNodeSP node = currentNode(); if (!node || !blockUntilOperationsFinished()) { return; } // Get painting options KisPaintOpPresetSP preset = currentPaintOpPreset(); // Compute the outline KisImageSP image = this->image(); QTransform matrix; matrix.scale(image->xRes(), image->yRes()); matrix.translate(pathShape->position().x(), pathShape->position().y()); QPainterPath mapedOutline = matrix.map(pathShape->outline()); // Recorde the paint action KisRecordedPathPaintAction bezierCurvePaintAction( KisNodeQueryPath::absolutePath(node), preset ); bezierCurvePaintAction.setPaintColor(currentFgColor()); QPointF lastPoint, nextPoint; int elementCount = mapedOutline.elementCount(); for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = mapedOutline.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: if (i != 0) { qFatal("Unhandled"); // XXX: I am not sure the tool can produce such element, deal with it when it can } lastPoint = QPointF(element.x, element.y); break; case QPainterPath::LineToElement: nextPoint = QPointF(element.x, element.y); bezierCurvePaintAction.addLine(KisPaintInformation(lastPoint), KisPaintInformation(nextPoint)); lastPoint = nextPoint; break; case QPainterPath::CurveToElement: nextPoint = QPointF(mapedOutline.elementAt(i + 2).x, mapedOutline.elementAt(i + 2).y); bezierCurvePaintAction.addCurve(KisPaintInformation(lastPoint), QPointF(mapedOutline.elementAt(i).x, mapedOutline.elementAt(i).y), QPointF(mapedOutline.elementAt(i + 1).x, mapedOutline.elementAt(i + 1).y), KisPaintInformation(nextPoint)); lastPoint = nextPoint; break; default: continue; } } image->actionRecorder()->addAction(bezierCurvePaintAction); if (node->hasEditablePaintDevice()) { KisFigurePaintingToolHelper helper(name, image, node, canvas()->resourceManager(), strokeStyle(), fillStyle()); helper.paintPainterPath(mapedOutline); } else if (node->inherits("KisShapeLayer")) { pathShape->normalize(); addShape(pathShape); } notifyModified(); } diff --git a/plugins/flake/artistictextshape/ArtisticTextTool.cpp b/plugins/flake/artistictextshape/ArtisticTextTool.cpp index a502f8695b..4e207d75e7 100644 --- a/plugins/flake/artistictextshape/ArtisticTextTool.cpp +++ b/plugins/flake/artistictextshape/ArtisticTextTool.cpp @@ -1,1009 +1,1008 @@ /* This file is part of the KDE project * Copyright (C) 2007,2011 Jan Hambrecht * Copyright (C) 2008 Rob Buis * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ArtisticTextTool.h" #include "ArtisticTextToolSelection.h" #include "AttachTextToPathCommand.h" #include "DetachTextFromPathCommand.h" #include "AddTextRangeCommand.h" #include "RemoveTextRangeCommand.h" #include "ArtisticTextShapeConfigWidget.h" #include "ArtisticTextShapeOnPathWidget.h" #include "MoveStartOffsetStrategy.h" #include "SelectTextStrategy.h" #include "ChangeTextOffsetCommand.h" #include "ChangeTextFontCommand.h" #include "ChangeTextAnchorCommand.h" #include "ReplaceTextRangeCommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_action_registry.h" #include #include #include #include #include #include #include #include #include #include #include #include #include const int BlinkInterval = 500; static bool hit(const QKeySequence &input, KStandardShortcut::StandardShortcut shortcut) { foreach (const QKeySequence &ks, KStandardShortcut::shortcut(shortcut)) { if (input == ks) { return true; } } return false; } ArtisticTextTool::ArtisticTextTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_selection(canvas, this) , m_currentShape(0) , m_hoverText(0) , m_hoverPath(0) , m_hoverHandle(false) , m_textCursor(-1) , m_showCursor(true) , m_currentStrategy(0) { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); m_detachPath = actionRegistry->makeQAction("artistictext_detach_from_path", this); m_detachPath->setEnabled(false); connect(m_detachPath, SIGNAL(triggered()), this, SLOT(detachPath())); addAction("artistictext_detach_from_path", m_detachPath); m_convertText = actionRegistry->makeQAction("artistictext_convert_to_path", this); m_convertText->setEnabled(false); connect(m_convertText, SIGNAL(triggered()), this, SLOT(convertText())); addAction("artistictext_convert_to_path", m_convertText); m_fontBold = actionRegistry->makeQAction("artistictext_font_bold", this); connect(m_fontBold, SIGNAL(toggled(bool)), this, SLOT(toggleFontBold(bool))); addAction("artistictext_font_bold", m_fontBold); m_fontItalic = actionRegistry->makeQAction("artistictext_font_italic", this); connect(m_fontItalic, SIGNAL(toggled(bool)), this, SLOT(toggleFontItalic(bool))); addAction("artistictext_font_italic", m_fontItalic); m_superScript = actionRegistry->makeQAction("artistictext_superscript", this); connect(m_superScript, SIGNAL(triggered()), this, SLOT(setSuperScript())); addAction("artistictext_superscript", m_superScript); m_subScript = actionRegistry->makeQAction("artistictext_subscript", this); connect(m_subScript, SIGNAL(triggered()), this, SLOT(setSubScript())); addAction("artistictext_subscript", m_subScript); QAction *anchorStart = actionRegistry->makeQAction("artistictext_anchor_start", this); anchorStart->setData(ArtisticTextShape::AnchorStart); addAction("artistictext_anchor_start", anchorStart); QAction *anchorMiddle = actionRegistry->makeQAction("artistictext_anchor_middle", this); anchorMiddle->setData(ArtisticTextShape::AnchorMiddle); addAction("artistictext_anchor_middle", anchorMiddle); QAction *anchorEnd = actionRegistry->makeQAction("artistictext_anchor_end", this); anchorEnd->setData(ArtisticTextShape::AnchorEnd); addAction("artistictext_anchor_end", anchorEnd); m_anchorGroup = new QActionGroup(this); m_anchorGroup->setExclusive(true); m_anchorGroup->addAction(anchorStart); m_anchorGroup->addAction(anchorMiddle); m_anchorGroup->addAction(anchorEnd); connect(m_anchorGroup, SIGNAL(triggered(QAction*)), this, SLOT(anchorChanged(QAction*))); connect(canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), this, SLOT(textChanged())); addAction("edit_select_all", KStandardAction::selectAll(this, SLOT(selectAll()), this)); addAction("edit_deselect_all", KStandardAction::deselect(this, SLOT(deselectAll()), this)); setTextMode(true); } ArtisticTextTool::~ArtisticTextTool() { delete m_currentStrategy; } QTransform ArtisticTextTool::cursorTransform() const { if (!m_currentShape) { return QTransform(); } QTransform transform; const int textLength = m_currentShape->plainText().length(); if (m_textCursor <= textLength) { const QPointF pos = m_currentShape->charPositionAt(m_textCursor); const qreal angle = m_currentShape->charAngleAt(m_textCursor); QFontMetrics metrics(m_currentShape->fontAt(m_textCursor)); transform.translate(pos.x() - 1, pos.y()); transform.rotate(360. - angle); transform.translate(0, metrics.descent()); } else if (m_textCursor <= textLength + m_linefeedPositions.size()) { const QPointF pos = m_linefeedPositions.value(m_textCursor - textLength - 1); QFontMetrics metrics(m_currentShape->fontAt(textLength - 1)); transform.translate(pos.x(), pos.y()); transform.translate(0, metrics.descent()); } return transform * m_currentShape->absoluteTransformation(0); } void ArtisticTextTool::paint(QPainter &painter, const KoViewConverter &converter) { if (! m_currentShape) { return; } if (m_showCursor && m_blinkingCursor.isActive() && !m_currentStrategy) { painter.save(); m_currentShape->applyConversion(painter, converter); painter.setBrush(Qt::black); painter.setWorldTransform(cursorTransform(), true); painter.setClipping(false); painter.drawPath(m_textCursorShape); painter.restore(); } m_showCursor = !m_showCursor; if (m_currentShape->isOnPath()) { painter.save(); m_currentShape->applyConversion(painter, converter); if (!m_currentShape->baselineShape()) { painter.setPen(Qt::DotLine); painter.setBrush(Qt::NoBrush); painter.drawPath(m_currentShape->baseline()); } painter.setPen(Qt::blue); painter.setBrush(m_hoverHandle ? Qt::red : Qt::white); painter.drawPath(offsetHandleShape()); painter.restore(); } if (m_selection.hasSelection()) { painter.save(); m_selection.paint(painter, converter); painter.restore(); } } void ArtisticTextTool::repaintDecorations() { canvas()->updateCanvas(offsetHandleShape().boundingRect()); if (m_currentShape && m_currentShape->isOnPath()) { if (!m_currentShape->baselineShape()) { canvas()->updateCanvas(m_currentShape->baseline().boundingRect()); } } m_selection.repaintDecoration(); } int ArtisticTextTool::cursorFromMousePosition(const QPointF &mousePosition) { if (!m_currentShape) { return -1; } const QPointF pos = m_currentShape->documentToShape(mousePosition); const int len = m_currentShape->plainText().length(); int hit = -1; qreal mindist = DBL_MAX; for (int i = 0; i <= len; ++i) { QPointF center = pos - m_currentShape->charPositionAt(i); if ((fabs(center.x()) + fabs(center.y())) < mindist) { hit = i; mindist = fabs(center.x()) + fabs(center.y()); } } return hit; } void ArtisticTextTool::mousePressEvent(KoPointerEvent *event) { if (m_hoverHandle) { m_currentStrategy = new MoveStartOffsetStrategy(this, m_currentShape); } if (m_hoverText) { KoSelection *selection = canvas()->selectedShapesProxy()->selection(); if (m_hoverText != m_currentShape) { // if we hit another text shape, select that shape selection->deselectAll(); setCurrentShape(m_hoverText); selection->select(m_currentShape); } // change the text cursor position int hitCursorPos = cursorFromMousePosition(event->point); if (hitCursorPos >= 0) { setTextCursorInternal(hitCursorPos); m_selection.clear(); } m_currentStrategy = new SelectTextStrategy(this, m_textCursor); } event->ignore(); } void ArtisticTextTool::mouseMoveEvent(KoPointerEvent *event) { m_hoverPath = 0; m_hoverText = 0; if (m_currentStrategy) { m_currentStrategy->handleMouseMove(event->point, event->modifiers()); return; } const bool textOnPath = m_currentShape && m_currentShape->isOnPath(); if (textOnPath) { QPainterPath handle = offsetHandleShape(); QPointF handleCenter = handle.boundingRect().center(); if (handleGrabRect(event->point).contains(handleCenter)) { // mouse is on offset handle if (!m_hoverHandle) { canvas()->updateCanvas(handle.boundingRect()); } m_hoverHandle = true; } else { if (m_hoverHandle) { canvas()->updateCanvas(handle.boundingRect()); } m_hoverHandle = false; } } if (!m_hoverHandle) { // find text or path shape at cursor position QList shapes = canvas()->shapeManager()->shapesAt(handleGrabRect(event->point)); if (shapes.contains(m_currentShape)) { m_hoverText = m_currentShape; } else { Q_FOREACH (KoShape *shape, shapes) { ArtisticTextShape *text = dynamic_cast(shape); if (text && !m_hoverText) { m_hoverText = text; } KoPathShape *path = dynamic_cast(shape); if (path && !m_hoverPath) { m_hoverPath = path; } if (m_hoverPath && m_hoverText) { break; } } } } const bool hoverOnBaseline = textOnPath && m_currentShape->baselineShape() == m_hoverPath; // update cursor and status text if (m_hoverText) { useCursor(QCursor(Qt::IBeamCursor)); if (m_hoverText == m_currentShape) { emit statusTextChanged(i18n("Click to change cursor position.")); } else { emit statusTextChanged(i18n("Click to select text shape.")); } } else if (m_hoverPath && m_currentShape && !hoverOnBaseline) { useCursor(QCursor(Qt::PointingHandCursor)); emit statusTextChanged(i18n("Double click to put text on path.")); } else if (m_hoverHandle) { useCursor(QCursor(Qt::ArrowCursor)); emit statusTextChanged(i18n("Drag handle to change start offset.")); } else { useCursor(QCursor(Qt::ArrowCursor)); if (m_currentShape) { emit statusTextChanged(i18n("Press escape to finish editing.")); } else { emit statusTextChanged(QString()); } } } void ArtisticTextTool::mouseReleaseEvent(KoPointerEvent *event) { if (m_currentStrategy) { m_currentStrategy->finishInteraction(event->modifiers()); KUndo2Command *cmd = m_currentStrategy->createCommand(); if (cmd) { canvas()->addCommand(cmd); } delete m_currentStrategy; m_currentStrategy = 0; } updateActions(); } void ArtisticTextTool::shortcutOverrideEvent(QKeyEvent *event) { QKeySequence item(event->key() | ((Qt::ControlModifier | Qt::AltModifier) & event->modifiers())); if (hit(item, KStandardShortcut::Begin) || hit(item, KStandardShortcut::End)) { event->accept(); } } void ArtisticTextTool::mouseDoubleClickEvent(KoPointerEvent */*event*/) { if (m_hoverPath && m_currentShape) { if (!m_currentShape->isOnPath() || m_currentShape->baselineShape() != m_hoverPath) { m_blinkingCursor.stop(); m_showCursor = false; updateTextCursorArea(); canvas()->addCommand(new AttachTextToPathCommand(m_currentShape, m_hoverPath)); m_blinkingCursor.start(BlinkInterval); updateActions(); m_hoverPath = 0; m_linefeedPositions.clear(); return; } } } void ArtisticTextTool::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { event->ignore(); return; } event->accept(); if (m_currentShape && textCursor() > -1) { switch (event->key()) { case Qt::Key_Delete: if (m_selection.hasSelection()) { removeFromTextCursor(m_selection.selectionStart(), m_selection.selectionCount()); } else if (textCursor() >= 0 && textCursor() < m_currentShape->plainText().length()) { removeFromTextCursor(textCursor(), 1); } break; case Qt::Key_Backspace: if (m_selection.hasSelection()) { removeFromTextCursor(m_selection.selectionStart(), m_selection.selectionCount()); } else { removeFromTextCursor(textCursor() - 1, 1); } break; case Qt::Key_Right: if (event->modifiers() & Qt::ShiftModifier) { int selectionStart, selectionEnd; if (m_selection.hasSelection()) { selectionStart = m_selection.selectionStart(); selectionEnd = selectionStart + m_selection.selectionCount(); if (textCursor() == selectionStart) { selectionStart = textCursor() + 1; } else if (textCursor() == selectionEnd) { selectionEnd = textCursor() + 1; } } else { selectionStart = textCursor(); selectionEnd = textCursor() + 1; } m_selection.selectText(selectionStart, selectionEnd); } else { m_selection.clear(); } setTextCursor(m_currentShape, textCursor() + 1); break; case Qt::Key_Left: if (event->modifiers() & Qt::ShiftModifier) { int selectionStart, selectionEnd; if (m_selection.hasSelection()) { selectionStart = m_selection.selectionStart(); selectionEnd = selectionStart + m_selection.selectionCount(); if (textCursor() == selectionStart) { selectionStart = textCursor() - 1; } else if (textCursor() == selectionEnd) { selectionEnd = textCursor() - 1; } } else { selectionEnd = textCursor(); selectionStart = textCursor() - 1; } m_selection.selectText(selectionStart, selectionEnd); } else { m_selection.clear(); } setTextCursor(m_currentShape, textCursor() - 1); break; case Qt::Key_Home: if (event->modifiers() & Qt::ShiftModifier) { const int selectionStart = 0; const int selectionEnd = m_selection.hasSelection() ? m_selection.selectionStart() + m_selection.selectionCount() : m_textCursor; m_selection.selectText(selectionStart, selectionEnd); } else { m_selection.clear(); } setTextCursor(m_currentShape, 0); break; case Qt::Key_End: if (event->modifiers() & Qt::ShiftModifier) { const int selectionStart = m_selection.hasSelection() ? m_selection.selectionStart() : m_textCursor; const int selectionEnd = m_currentShape->plainText().length(); m_selection.selectText(selectionStart, selectionEnd); } else { m_selection.clear(); } setTextCursor(m_currentShape, m_currentShape->plainText().length()); break; case Qt::Key_Return: case Qt::Key_Enter: { const int textLength = m_currentShape->plainText().length(); if (m_textCursor >= textLength) { // get font metrics for last character QFontMetrics metrics(m_currentShape->fontAt(textLength - 1)); const qreal offset = m_currentShape->size().height() + (m_linefeedPositions.size() + 1) * metrics.height(); m_linefeedPositions.append(QPointF(0, offset)); setTextCursor(m_currentShape, textCursor() + 1); } break; } default: if (event->text().isEmpty()) { event->ignore(); return; } if (m_selection.hasSelection()) { removeFromTextCursor(m_selection.selectionStart(), m_selection.selectionCount()); } addToTextCursor(event->text()); } } else { event->ignore(); } } void ArtisticTextTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); foreach (KoShape *shape, shapes) { ArtisticTextShape *text = dynamic_cast(shape); if (text) { setCurrentShape(text); break; } } if (!m_currentShape) { // none found emit done(); return; } m_hoverText = 0; m_hoverPath = 0; updateActions(); emit statusTextChanged(i18n("Press return to finish editing.")); repaintDecorations(); connect(canvas()->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged())); } void ArtisticTextTool::blinkCursor() { updateTextCursorArea(); } void ArtisticTextTool::deactivate() { if (m_currentShape) { if (m_currentShape->plainText().isEmpty()) { canvas()->addCommand(canvas()->shapeController()->removeShape(m_currentShape)); } setCurrentShape(0); } m_hoverPath = 0; m_hoverText = 0; disconnect(canvas()->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged())); KoToolBase::deactivate(); } void ArtisticTextTool::updateActions() { if (m_currentShape) { const QFont font = m_currentShape->fontAt(textCursor()); const CharIndex index = m_currentShape->indexOfChar(textCursor()); ArtisticTextRange::BaselineShift baselineShift = ArtisticTextRange::None; if (index.first >= 0) { baselineShift = m_currentShape->text().at(index.first).baselineShift(); } m_fontBold->blockSignals(true); m_fontBold->setChecked(font.bold()); m_fontBold->blockSignals(false); m_fontBold->setEnabled(true); m_fontItalic->blockSignals(true); m_fontItalic->setChecked(font.italic()); m_fontItalic->blockSignals(false); m_fontItalic->setEnabled(true); m_detachPath->setEnabled(m_currentShape->isOnPath()); m_convertText->setEnabled(true); m_anchorGroup->blockSignals(true); Q_FOREACH (QAction *action, m_anchorGroup->actions()) { if (action->data().toInt() == m_currentShape->textAnchor()) { action->setChecked(true); } } m_anchorGroup->blockSignals(false); m_anchorGroup->setEnabled(true); m_superScript->blockSignals(true); m_superScript->setChecked(baselineShift == ArtisticTextRange::Super); m_superScript->blockSignals(false); m_subScript->blockSignals(true); m_subScript->setChecked(baselineShift == ArtisticTextRange::Sub); m_subScript->blockSignals(false); m_superScript->setEnabled(true); m_subScript->setEnabled(true); } else { m_detachPath->setEnabled(false); m_convertText->setEnabled(false); m_fontBold->setEnabled(false); m_fontItalic->setEnabled(false); m_anchorGroup->setEnabled(false); m_superScript->setEnabled(false); m_subScript->setEnabled(false); } } void ArtisticTextTool::detachPath() { if (m_currentShape && m_currentShape->isOnPath()) { canvas()->addCommand(new DetachTextFromPathCommand(m_currentShape)); updateActions(); } } void ArtisticTextTool::convertText() { if (! m_currentShape) { return; } KoPathShape *path = KoPathShape::createShapeFromPainterPath(m_currentShape->outline()); - path->setParent(m_currentShape->parent()); path->setZIndex(m_currentShape->zIndex()); path->setStroke(m_currentShape->stroke()); path->setBackground(m_currentShape->background()); path->setTransformation(m_currentShape->transformation()); path->setShapeId(KoPathShapeId); - KUndo2Command *cmd = canvas()->shapeController()->addShapeDirect(path); + KUndo2Command *cmd = canvas()->shapeController()->addShapeDirect(path, 0); cmd->setText(kundo2_i18n("Convert to Path")); canvas()->shapeController()->removeShape(m_currentShape, cmd); canvas()->addCommand(cmd); emit done(); } QList > ArtisticTextTool::createOptionWidgets() { QList > widgets; ArtisticTextShapeConfigWidget *configWidget = new ArtisticTextShapeConfigWidget(this); configWidget->setObjectName("ArtisticTextConfigWidget"); configWidget->setWindowTitle(i18n("Text Properties")); connect(configWidget, SIGNAL(fontFamilyChanged(QFont)), this, SLOT(setFontFamiliy(QFont))); connect(configWidget, SIGNAL(fontSizeChanged(int)), this, SLOT(setFontSize(int))); connect(this, SIGNAL(shapeSelected()), configWidget, SLOT(updateWidget())); connect(canvas()->selectedShapesProxy(), SIGNAL(selectionContentChanged()), configWidget, SLOT(updateWidget())); widgets.append(configWidget); ArtisticTextShapeOnPathWidget *pathWidget = new ArtisticTextShapeOnPathWidget(this); pathWidget->setObjectName("ArtisticTextPathWidget"); pathWidget->setWindowTitle(i18n("Text On Path")); connect(pathWidget, SIGNAL(offsetChanged(int)), this, SLOT(setStartOffset(int))); connect(this, SIGNAL(shapeSelected()), pathWidget, SLOT(updateWidget())); connect(canvas()->selectedShapesProxy(), SIGNAL(selectionContentChanged()), pathWidget, SLOT(updateWidget())); widgets.append(pathWidget); if (m_currentShape) { pathWidget->updateWidget(); configWidget->updateWidget(); } return widgets; } KoToolSelection *ArtisticTextTool::selection() { return &m_selection; } void ArtisticTextTool::enableTextCursor(bool enable) { if (enable) { if (m_currentShape) { setTextCursorInternal(m_currentShape->plainText().length()); } connect(&m_blinkingCursor, SIGNAL(timeout()), this, SLOT(blinkCursor())); m_blinkingCursor.start(BlinkInterval); } else { m_blinkingCursor.stop(); disconnect(&m_blinkingCursor, SIGNAL(timeout()), this, SLOT(blinkCursor())); setTextCursorInternal(-1); m_showCursor = false; } } void ArtisticTextTool::setTextCursor(ArtisticTextShape *textShape, int textCursor) { if (!m_currentShape || textShape != m_currentShape) { return; } if (m_textCursor == textCursor || textCursor < 0) { return; } const int textLength = m_currentShape->plainText().length() + m_linefeedPositions.size(); if (textCursor > textLength) { return; } setTextCursorInternal(textCursor); } int ArtisticTextTool::textCursor() const { return m_textCursor; } void ArtisticTextTool::updateTextCursorArea() const { if (! m_currentShape || m_textCursor < 0) { return; } QRectF bbox = cursorTransform().mapRect(m_textCursorShape.boundingRect()); canvas()->updateCanvas(bbox); } void ArtisticTextTool::setCurrentShape(ArtisticTextShape *currentShape) { if (m_currentShape == currentShape) { return; } enableTextCursor(false); m_currentShape = currentShape; m_selection.setSelectedShape(m_currentShape); if (m_currentShape) { enableTextCursor(true); } emit shapeSelected(); } void ArtisticTextTool::setTextCursorInternal(int textCursor) { updateTextCursorArea(); m_textCursor = textCursor; createTextCursorShape(); updateTextCursorArea(); updateActions(); emit shapeSelected(); } void ArtisticTextTool::createTextCursorShape() { if (m_textCursor < 0 || ! m_currentShape) { return; } const QRectF extents = m_currentShape->charExtentsAt(m_textCursor); m_textCursorShape = QPainterPath(); m_textCursorShape.addRect(0, 0, 1, -extents.height()); m_textCursorShape.closeSubpath(); } void ArtisticTextTool::removeFromTextCursor(int from, unsigned int count) { if (from >= 0) { if (m_selection.hasSelection()) { // clear selection before text is removed, or else selection will be invalid m_selection.clear(); } KUndo2Command *cmd = new RemoveTextRangeCommand(this, m_currentShape, from, count); canvas()->addCommand(cmd); } } void ArtisticTextTool::addToTextCursor(const QString &str) { if (!str.isEmpty() && m_textCursor > -1) { QString printable; for (int i = 0; i < str.length(); i++) { if (str[i].isPrint()) { printable.append(str[i]); } } unsigned int len = printable.length(); if (len) { const int textLength = m_currentShape->plainText().length(); if (m_textCursor <= textLength) { KUndo2Command *cmd = new AddTextRangeCommand(this, m_currentShape, printable, m_textCursor); canvas()->addCommand(cmd); } else if (m_textCursor > textLength && m_textCursor <= textLength + m_linefeedPositions.size()) { const QPointF pos = m_linefeedPositions.value(m_textCursor - textLength - 1); ArtisticTextRange newLine(printable, m_currentShape->fontAt(textLength - 1)); newLine.setXOffsets(QList() << pos.x(), ArtisticTextRange::AbsoluteOffset); newLine.setYOffsets(QList() << pos.y() - m_currentShape->baselineOffset(), ArtisticTextRange::AbsoluteOffset); KUndo2Command *cmd = new AddTextRangeCommand(this, m_currentShape, newLine, m_textCursor); canvas()->addCommand(cmd); m_linefeedPositions.clear(); } } } } void ArtisticTextTool::textChanged() { if (!m_currentShape) { return; } const QString currentText = m_currentShape->plainText(); if (m_textCursor > currentText.length()) { setTextCursorInternal(currentText.length()); } } void ArtisticTextTool::shapeSelectionChanged() { KoSelection *selection = canvas()->selectedShapesProxy()->selection(); if (selection->isSelected(m_currentShape)) { return; } foreach (KoShape *shape, selection->selectedShapes()) { ArtisticTextShape *text = dynamic_cast(shape); if (text) { setCurrentShape(text); break; } } } QPainterPath ArtisticTextTool::offsetHandleShape() { QPainterPath handle; if (!m_currentShape || !m_currentShape->isOnPath()) { return handle; } const QPainterPath baseline = m_currentShape->baseline(); const qreal offset = m_currentShape->startOffset(); QPointF offsetPoint = baseline.pointAtPercent(offset); QSizeF paintSize = handlePaintRect(QPointF()).size(); handle.moveTo(0, 0); handle.lineTo(0.5 * paintSize.width(), paintSize.height()); handle.lineTo(-0.5 * paintSize.width(), paintSize.height()); handle.closeSubpath(); QTransform transform; transform.translate(offsetPoint.x(), offsetPoint.y()); transform.rotate(360. - baseline.angleAtPercent(offset)); return transform.map(handle); } void ArtisticTextTool::setStartOffset(int offset) { if (!m_currentShape || !m_currentShape->isOnPath()) { return; } const qreal newOffset = static_cast(offset) / 100.0; if (newOffset != m_currentShape->startOffset()) { canvas()->addCommand(new ChangeTextOffsetCommand(m_currentShape, m_currentShape->startOffset(), newOffset)); } } void ArtisticTextTool::changeFontProperty(FontProperty property, const QVariant &value) { if (!m_currentShape || !m_selection.hasSelection()) { return; } // build font ranges const int selectedCharCount = m_selection.selectionCount(); const int selectedCharStart = m_selection.selectionStart(); QList ranges = m_currentShape->text(); CharIndex index = m_currentShape->indexOfChar(selectedCharStart); if (index.first < 0) { return; } KUndo2Command *cmd = new KUndo2Command; int collectedCharCount = 0; while (collectedCharCount < selectedCharCount) { ArtisticTextRange &range = ranges[index.first]; QFont font = range.font(); switch (property) { case BoldProperty: font.setBold(value.toBool()); break; case ItalicProperty: font.setItalic(value.toBool()); break; case FamiliyProperty: font.setFamily(value.toString()); break; case SizeProperty: font.setPointSize(value.toInt()); break; } const int changeCount = qMin(selectedCharCount - collectedCharCount, range.text().count() - index.second); const int changeStart = selectedCharStart + collectedCharCount; new ChangeTextFontCommand(m_currentShape, changeStart, changeCount, font, cmd); index.first++; index.second = 0; collectedCharCount += changeCount; } canvas()->addCommand(cmd); } void ArtisticTextTool::toggleFontBold(bool enabled) { changeFontProperty(BoldProperty, QVariant(enabled)); } void ArtisticTextTool::toggleFontItalic(bool enabled) { changeFontProperty(ItalicProperty, QVariant(enabled)); } void ArtisticTextTool::anchorChanged(QAction *action) { if (!m_currentShape) { return; } ArtisticTextShape::TextAnchor newAnchor = static_cast(action->data().toInt()); if (newAnchor != m_currentShape->textAnchor()) { canvas()->addCommand(new ChangeTextAnchorCommand(m_currentShape, newAnchor)); } } void ArtisticTextTool::setFontFamiliy(const QFont &font) { changeFontProperty(FamiliyProperty, QVariant(font.family())); } void ArtisticTextTool::setFontSize(int size) { changeFontProperty(SizeProperty, QVariant(size)); } void ArtisticTextTool::setSuperScript() { toggleSubSuperScript(ArtisticTextRange::Super); } void ArtisticTextTool::setSubScript() { toggleSubSuperScript(ArtisticTextRange::Sub); } void ArtisticTextTool::toggleSubSuperScript(ArtisticTextRange::BaselineShift mode) { if (!m_currentShape || !m_selection.hasSelection()) { return; } const int from = m_selection.selectionStart(); const int count = m_selection.selectionCount(); QList ranges = m_currentShape->copyText(from, count); const int rangeCount = ranges.count(); if (!rangeCount) { return; } // determine if we want to disable the specified mode const bool disableMode = ranges.first().baselineShift() == mode; const qreal fontSize = m_currentShape->defaultFont().pointSizeF(); for (int i = 0; i < rangeCount; ++i) { ArtisticTextRange ¤tRange = ranges[i]; QFont font = currentRange.font(); if (disableMode) { currentRange.setBaselineShift(ArtisticTextRange::None); font.setPointSizeF(fontSize); } else { currentRange.setBaselineShift(mode); font.setPointSizeF(fontSize * ArtisticTextRange::subAndSuperScriptSizeFactor()); } currentRange.setFont(font); } canvas()->addCommand(new ReplaceTextRangeCommand(m_currentShape, ranges, from, count, this)); } void ArtisticTextTool::selectAll() { if (m_currentShape) { m_selection.selectText(0, m_currentShape->plainText().count()); } } void ArtisticTextTool::deselectAll() { if (m_currentShape) { m_selection.clear(); } } QVariant ArtisticTextTool::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const { if (!m_currentShape) { return QVariant(); } switch (query) { case Qt::ImMicroFocus: { // The rectangle covering the area of the input cursor in widget coordinates. QRectF rect = m_textCursorShape.boundingRect(); rect.moveTop(rect.bottom()); QTransform shapeMatrix = m_currentShape->absoluteTransformation(&converter); qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); shapeMatrix.scale(zoomX, zoomY); rect = shapeMatrix.mapRect(rect); return rect.toRect(); } case Qt::ImFont: // The currently used font for text input. return m_currentShape->fontAt(m_textCursor); case Qt::ImCursorPosition: // The logical position of the cursor within the text surrounding the input area (see ImSurroundingText). return m_currentShape->charPositionAt(m_textCursor); case Qt::ImSurroundingText: // The plain text around the input area, for example the current paragraph. return m_currentShape->plainText(); case Qt::ImCurrentSelection: // The currently selected text. return QVariant(); default: ; // Qt 4.6 adds ImMaximumTextLength and ImAnchorPosition } return QVariant(); } diff --git a/plugins/tools/basictools/kis_tool_line.cc b/plugins/tools/basictools/kis_tool_line.cc index 770498df96..358efd9451 100644 --- a/plugins/tools/basictools/kis_tool_line.cc +++ b/plugins/tools/basictools/kis_tool_line.cc @@ -1,369 +1,369 @@ /* * kis_tool_line.cc - part of Krayon * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2003 Boudewijn Rempt * Copyright (c) 2009 Lukáš Tvrdý * Copyright (c) 2007,2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_line.h" #include #include #include #include #include #include #include #include #include #include #include "kis_figure_painting_tool_helper.h" #include "kis_canvas2.h" #include #include #include #include "kis_painting_information_builder.h" #include "kis_tool_line_helper.h" #define ENABLE_RECORDING const KisCoordinatesConverter* getCoordinatesConverter(KoCanvasBase * canvas) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas); return kritaCanvas->coordinatesConverter(); } KisToolLine::KisToolLine(KoCanvasBase * canvas) : KisToolShape(canvas, KisCursor::load("tool_line_cursor.png", 6, 6)), m_showGuideline(true), m_strokeIsRunning(false), m_infoBuilder(new KisConverterPaintingInformationBuilder(getCoordinatesConverter(canvas))), m_helper(new KisToolLineHelper(m_infoBuilder.data(), kundo2_i18n("Draw Line"))), m_strokeUpdateCompressor(500, KisSignalCompressor::POSTPONE), m_longStrokeUpdateCompressor(1000, KisSignalCompressor::FIRST_INACTIVE) { setObjectName("tool_line"); setSupportOutline(true); connect(&m_strokeUpdateCompressor, SIGNAL(timeout()), SLOT(updateStroke())); connect(&m_longStrokeUpdateCompressor, SIGNAL(timeout()), SLOT(updateStroke())); } KisToolLine::~KisToolLine() { } void KisToolLine::resetCursorStyle() { KisToolPaint::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolLine::activate(ToolActivation activation, const QSet &shapes) { KisToolPaint::activate(activation, shapes); configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolLine::deactivate() { KisToolPaint::deactivate(); cancelStroke(); } QWidget* KisToolLine::createOptionWidget() { QWidget* widget = KisToolPaint::createOptionWidget(); m_chkUseSensors = new QCheckBox(i18n("Use sensors")); addOptionWidgetOption(m_chkUseSensors); m_chkShowPreview = new QCheckBox(i18n("Show Preview")); addOptionWidgetOption(m_chkShowPreview); m_chkShowGuideline = new QCheckBox(i18n("Show Guideline")); addOptionWidgetOption(m_chkShowGuideline); // hook up connections for value changing connect(m_chkUseSensors, SIGNAL(clicked(bool)), this, SLOT(setUseSensors(bool)) ); connect(m_chkShowPreview, SIGNAL(clicked(bool)), this, SLOT(setShowPreview(bool)) ); connect(m_chkShowGuideline, SIGNAL(clicked(bool)), this, SLOT(setShowGuideline(bool)) ); // read values in from configuration m_chkUseSensors->setChecked(configGroup.readEntry("useSensors", true)); m_chkShowPreview->setChecked(configGroup.readEntry("showPreview", true)); m_chkShowGuideline->setChecked(configGroup.readEntry("showGuideline", true)); return widget; } void KisToolLine::setUseSensors(bool value) { configGroup.writeEntry("useSensors", value); } void KisToolLine::setShowGuideline(bool value) { m_showGuideline = value; configGroup.writeEntry("showGuideline", value); } void KisToolLine::setShowPreview(bool value) { configGroup.writeEntry("showPreview", value); } void KisToolLine::requestStrokeCancellation() { cancelStroke(); } void KisToolLine::requestStrokeEnd() { // Terminate any in-progress strokes if (nodePaintAbility() == PAINT && m_helper->isRunning()) { endStroke(); } } void KisToolLine::updatePreviewTimer(bool showGuideline) { // If the user disables the guideline, we will want to try to draw some // preview lines even if they're slow, so set the timer to FIRST_ACTIVE. if (showGuideline) { m_strokeUpdateCompressor.setMode(KisSignalCompressor::POSTPONE); } else { m_strokeUpdateCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); } } void KisToolLine::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if(mode() == KisTool::PAINT_MODE) { paintLine(gc,QRect()); } KisToolPaint::paint(gc,converter); } void KisToolLine::beginPrimaryAction(KoPointerEvent *event) { NodePaintAbility nodeAbility = nodePaintAbility(); if (nodeAbility == NONE || !nodeEditable()) { event->ignore(); return; } setMode(KisTool::PAINT_MODE); // Always show guideline on vector layers m_showGuideline = m_chkShowGuideline->isChecked() || nodeAbility != PAINT; updatePreviewTimer(m_showGuideline); m_helper->setEnabled(nodeAbility == PAINT); m_helper->setUseSensors(m_chkUseSensors->isChecked()); m_helper->start(event, canvas()->resourceManager()); m_startPoint = convertToPixelCoordAndSnap(event); m_endPoint = m_startPoint; m_lastUpdatedPoint = m_startPoint; m_strokeIsRunning = true; } void KisToolLine::updateStroke() { if (!m_strokeIsRunning) return; m_helper->repaintLine(canvas()->resourceManager(), image(), currentNode(), image().data()); } void KisToolLine::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); if (!m_strokeIsRunning) return; // First ensure the old guideline is deleted updateGuideline(); QPointF pos = convertToPixelCoordAndSnap(event); if (event->modifiers() == Qt::AltModifier) { QPointF trans = pos - m_endPoint; m_helper->translatePoints(trans); m_startPoint += trans; m_endPoint += trans; } else if (event->modifiers() == Qt::ShiftModifier) { pos = straightLine(pos); m_helper->addPoint(event, pos); } else { m_helper->addPoint(event, pos); } m_endPoint = pos; // Draw preview if requested if (m_chkShowPreview->isChecked()) { // If the cursor has moved a significant amount, immediately clear the // current preview and redraw. Otherwise, do slow redraws periodically. auto updateDistance = (pixelToView(m_lastUpdatedPoint) - pixelToView(pos)).manhattanLength(); if (updateDistance > 10) { m_helper->clearPaint(); m_longStrokeUpdateCompressor.stop(); m_strokeUpdateCompressor.start(); m_lastUpdatedPoint = pos; } else if (updateDistance > 1) { m_longStrokeUpdateCompressor.start(); } } updateGuideline(); KisToolPaint::requestUpdateOutline(event->point, event); } void KisToolLine::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); updateGuideline(); endStroke(); } void KisToolLine::endStroke() { NodePaintAbility nodeAbility = nodePaintAbility(); if (!m_strokeIsRunning || m_startPoint == m_endPoint || nodeAbility == NONE) { return; } if (nodeAbility == PAINT) { updateStroke(); m_helper->end(); } else { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); QTransform resolutionMatrix; resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); path->moveTo(resolutionMatrix.map(m_startPoint)); path->lineTo(resolutionMatrix.map(m_endPoint)); path->normalize(); KoShapeStrokeSP border(new KoShapeStroke(currentStrokeWidth(), currentFgColor().toQColor())); path->setStroke(border); - KUndo2Command * cmd = canvas()->shapeController()->addShape(path); + KUndo2Command * cmd = canvas()->shapeController()->addShape(path, 0); canvas()->addCommand(cmd); } m_strokeIsRunning = false; m_endPoint = m_startPoint; } void KisToolLine::cancelStroke() { if (!m_strokeIsRunning) return; if (m_startPoint == m_endPoint) return; /** * The actual stroke is run by the timer so it is a legal * situation when m_strokeIsRunning is true, but the actual redraw * stroke is not running. */ if (m_helper->isRunning()) { m_helper->cancel(); } m_strokeIsRunning = false; m_endPoint = m_startPoint; } QPointF KisToolLine::straightLine(QPointF point) { const QPointF lineVector = point - m_startPoint; qreal lineAngle = std::atan2(lineVector.y(), lineVector.x()); if (lineAngle < 0) { lineAngle += 2 * M_PI; } const qreal ANGLE_BETWEEN_CONSTRAINED_LINES = (2 * M_PI) / 24; const quint32 constrainedLineIndex = static_cast((lineAngle / ANGLE_BETWEEN_CONSTRAINED_LINES) + 0.5); const qreal constrainedLineAngle = constrainedLineIndex * ANGLE_BETWEEN_CONSTRAINED_LINES; const qreal lineLength = std::sqrt((lineVector.x() * lineVector.x()) + (lineVector.y() * lineVector.y())); const QPointF constrainedLineVector(lineLength * std::cos(constrainedLineAngle), lineLength * std::sin(constrainedLineAngle)); const QPointF result = m_startPoint + constrainedLineVector; return result; } void KisToolLine::updateGuideline() { if (canvas()) { QRectF bound(m_startPoint, m_endPoint); canvas()->updateCanvas(convertToPt(bound.normalized().adjusted(-3, -3, 3, 3))); } } void KisToolLine::paintLine(QPainter& gc, const QRect&) { QPointF viewStartPos = pixelToView(m_startPoint); QPointF viewStartEnd = pixelToView(m_endPoint); if (m_showGuideline && canvas()) { QPainterPath path; path.moveTo(viewStartPos); path.lineTo(viewStartEnd); paintToolOutline(&gc, path); } } QString KisToolLine::quickHelp() const { return i18n("Alt+Drag will move the origin of the currently displayed line around, Shift+Drag will force you to draw straight lines"); } diff --git a/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp b/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp index fa347dc9a3..8ce68e366a 100644 --- a/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp +++ b/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp @@ -1,987 +1,987 @@ /* This file is part of the KDE project * * Copyright (C) 2009 Thorsten Zachmann * Copyright (C) 2009 Jean-Nicolas Artaud * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ConnectionTool.h" #include #include #include #include "AddConnectionPointCommand.h" #include "RemoveConnectionPointCommand.h" #include "ChangeConnectionPointCommand.h" #include "MoveConnectionPointStrategy.h" #include "ConnectionPointWidget.h" #define TextShape_SHAPEID "TextShapeID" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_document_aware_spin_box_unit_manager.h" #include #include "kis_action_registry.h" #include #include #include #include #include #include ConnectionTool::ConnectionTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_editMode(Idle) , m_connectionType(KoConnectionShape::Standard) , m_currentShape(0) , m_activeHandle(-1) , m_currentStrategy(0) , m_oldSnapStrategies(0) , m_resetPaint(true) { QPixmap connectPixmap; connectPixmap.load(":/cursor_connect.png"); m_connectCursor = QCursor(connectPixmap, 4, 1); KisActionRegistry *actionRegistry = KisActionRegistry::instance(); m_editConnectionPoint = actionRegistry->makeQAction("toggle-edit-mode", this); m_editConnectionPoint->setCheckable(true); addAction("toggle-edit-mode", m_editConnectionPoint); m_alignPercent = actionRegistry->makeQAction("align-relative", this); m_alignPercent->setCheckable(true); addAction("align-relative", m_alignPercent); m_alignLeft = actionRegistry->makeQAction("align-left", this); m_alignLeft->setCheckable(true); addAction("align-left", m_alignLeft); m_alignCenterH = actionRegistry->makeQAction("align-centerh", this); m_alignCenterH->setCheckable(true); addAction("align-centerh", m_alignCenterH); m_alignRight = actionRegistry->makeQAction("align-right", this); m_alignRight->setCheckable(true); addAction("align-right", m_alignRight); m_alignTop = actionRegistry->makeQAction("align-top", this); m_alignTop->setCheckable(true); addAction("align-top", m_alignTop); m_alignCenterV = actionRegistry->makeQAction("align-centerv", this); m_alignCenterV->setCheckable(true); addAction("align-centerv", m_alignCenterV); m_alignBottom = actionRegistry->makeQAction("align-bottom", this); m_alignBottom->setCheckable(true); addAction("align-bottom", m_alignBottom); m_escapeAll = actionRegistry->makeQAction("escape-all", this); m_escapeAll->setCheckable(true); addAction("escape-all", m_escapeAll); m_escapeHorizontal = actionRegistry->makeQAction("escape-horizontal", this); m_escapeHorizontal->setCheckable(true); addAction("escape-horizontal", m_escapeHorizontal); m_escapeVertical = actionRegistry->makeQAction("escape-vertical", this); m_escapeVertical->setCheckable(true); addAction("escape-vertical", m_escapeVertical); m_escapeLeft = actionRegistry->makeQAction("escape-left", this); m_escapeLeft->setCheckable(true); addAction("escape-left", m_escapeLeft); m_escapeRight = actionRegistry->makeQAction("escape-right", this); m_escapeRight->setCheckable(true); addAction("escape-right", m_escapeRight); m_escapeUp = actionRegistry->makeQAction("escape-up", this); m_escapeUp->setCheckable(true); addAction("escape-up", m_escapeUp); m_escapeDown = actionRegistry->makeQAction("escape-down", this); m_escapeDown->setCheckable(true); addAction("escape-down", m_escapeDown); m_alignHorizontal = new QActionGroup(this); m_alignHorizontal->setExclusive(true); m_alignHorizontal->addAction(m_alignLeft); m_alignHorizontal->addAction(m_alignCenterH); m_alignHorizontal->addAction(m_alignRight); connect(m_alignHorizontal, SIGNAL(triggered(QAction*)), this, SLOT(horizontalAlignChanged())); m_alignVertical = new QActionGroup(this); m_alignVertical->setExclusive(true); m_alignVertical->addAction(m_alignTop); m_alignVertical->addAction(m_alignCenterV); m_alignVertical->addAction(m_alignBottom); connect(m_alignVertical, SIGNAL(triggered(QAction*)), this, SLOT(verticalAlignChanged())); m_alignRelative = new QActionGroup(this); m_alignRelative->setExclusive(true); m_alignRelative->addAction(m_alignPercent); connect(m_alignRelative, SIGNAL(triggered(QAction*)), this, SLOT(relativeAlignChanged())); m_escapeDirections = new QActionGroup(this); m_escapeDirections->setExclusive(true); m_escapeDirections->addAction(m_escapeAll); m_escapeDirections->addAction(m_escapeHorizontal); m_escapeDirections->addAction(m_escapeVertical); m_escapeDirections->addAction(m_escapeLeft); m_escapeDirections->addAction(m_escapeRight); m_escapeDirections->addAction(m_escapeUp); m_escapeDirections->addAction(m_escapeDown); connect(m_escapeDirections, SIGNAL(triggered(QAction*)), this, SLOT(escapeDirectionChanged())); connect(this, SIGNAL(connectionPointEnabled(bool)), m_alignHorizontal, SLOT(setEnabled(bool))); connect(this, SIGNAL(connectionPointEnabled(bool)), m_alignVertical, SLOT(setEnabled(bool))); connect(this, SIGNAL(connectionPointEnabled(bool)), m_alignRelative, SLOT(setEnabled(bool))); connect(this, SIGNAL(connectionPointEnabled(bool)), m_escapeDirections, SLOT(setEnabled(bool))); resetEditMode(); } ConnectionTool::~ConnectionTool() { } void ConnectionTool::paint(QPainter &painter, const KoViewConverter &converter) { // get the correctly sized rect for painting handles QRectF handleRect = handlePaintRect(QPointF()); painter.setRenderHint(QPainter::Antialiasing, true); if (m_currentStrategy) { painter.save(); m_currentStrategy->paint(painter, converter); painter.restore(); } QList shapes = canvas()->shapeManager()->shapes(); for (QList::const_iterator end = shapes.constBegin(); end != shapes.constEnd(); ++end) { KoShape *shape = *end; if (!dynamic_cast(shape)) { // only paint connection points of textShapes not inside a tos container and other shapes if (shape->shapeId() == TextShape_SHAPEID && dynamic_cast(shape->parent())) { continue; } painter.save(); painter.setPen(Qt::black); QTransform transform = shape->absoluteTransformation(0); KoShape::applyConversion(painter, converter); // Draw all the connection points of the shape KoConnectionPoints connectionPoints = shape->connectionPoints(); KoConnectionPoints::const_iterator cp = connectionPoints.constBegin(); KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd(); for (; cp != lastCp; ++cp) { if (shape == findNonConnectionShapeAtPosition(transform.map(cp.value().position))) { handleRect.moveCenter(transform.map(cp.value().position)); painter.setBrush(cp.key() == m_activeHandle && shape == m_currentShape ? Qt::red : Qt::white); painter.drawRect(handleRect); } } painter.restore(); } } // paint connection points or connection handles depending // on the shape the mouse is currently if (m_currentShape && m_editMode == EditConnection) { KoConnectionShape *connectionShape = dynamic_cast(m_currentShape); if (connectionShape) { int radius = handleRadius() + 1; int handleCount = connectionShape->handleCount(); for (int i = 0; i < handleCount; ++i) { KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, connectionShape, converter, radius); helper.setHandleStyle(i == m_activeHandle ? KisHandleStyle::highlightedPrimaryHandles() : KisHandleStyle::primarySelection()); connectionShape->paintHandle(helper, i); } } } } void ConnectionTool::repaintDecorations() { const qreal radius = handleRadius(); QRectF repaintRect; if (m_currentShape) { repaintRect = m_currentShape->boundingRect(); canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); KoConnectionShape *connectionShape = dynamic_cast(m_currentShape); if (!m_resetPaint && m_currentShape->isVisible(true) && !connectionShape) { // only paint connection points of textShapes not inside a tos container and other shapes if (!(m_currentShape->shapeId() == TextShape_SHAPEID && dynamic_cast(m_currentShape->parent()))) { KoConnectionPoints connectionPoints = m_currentShape->connectionPoints(); KoConnectionPoints::const_iterator cp = connectionPoints.constBegin(); KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd(); for (; cp != lastCp; ++cp) { repaintRect = handleGrabRect(m_currentShape->shapeToDocument(cp.value().position)); canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); } } } if (m_editMode == EditConnection) { if (connectionShape) { QPointF handlePos = connectionShape->handlePosition(m_activeHandle); handlePos = connectionShape->shapeToDocument(handlePos); repaintRect = handlePaintRect(handlePos); canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); } } } if (m_resetPaint) { QList shapes = canvas()->shapeManager()->shapes(); for (QList::const_iterator end = shapes.constBegin(); end != shapes.constEnd(); ++end) { KoShape *shape = *end; if (!dynamic_cast(shape)) { // only paint connection points of textShapes not inside a tos container and other shapes if (shape->shapeId() == TextShape_SHAPEID && dynamic_cast(shape->parent())) { continue; } KoConnectionPoints connectionPoints = shape->connectionPoints(); KoConnectionPoints::const_iterator cp = connectionPoints.constBegin(); KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd(); for (; cp != lastCp; ++cp) { repaintRect = handleGrabRect(shape->shapeToDocument(cp.value().position)); canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); } } } } m_resetPaint = false; } void ConnectionTool::mousePressEvent(KoPointerEvent *event) { if (!m_currentShape) { return; } KoShape *hitShape = findShapeAtPosition(event->point); int hitHandle = handleAtPoint(m_currentShape, event->point); if (m_editMode == EditConnection && hitHandle >= 0) { // create connection handle change strategy m_currentStrategy = new KoPathConnectionPointStrategy(this, dynamic_cast(m_currentShape), hitHandle); } else if (m_editMode == EditConnectionPoint) { if (hitHandle >= KoConnectionPoint::FirstCustomConnectionPoint) { // start moving custom connection point m_currentStrategy = new MoveConnectionPointStrategy(m_currentShape, hitHandle, this); } } else if (m_editMode == CreateConnection) { // create new connection shape, connect it to the active connection point // and start editing the new connection // create the new connection shape KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("KoConnectionShape"); KoShape *shape = factory->createDefaultShape(canvas()->shapeController()->resourceManager()); KoConnectionShape *connectionShape = dynamic_cast(shape); if (!connectionShape) { delete shape; resetEditMode(); return; } //set connection type connectionShape->setType(m_connectionType); // get the position of the connection point we start our connection from QPointF cp = m_currentShape->shapeToDocument(m_currentShape->connectionPoint(m_activeHandle).position); // move both handles to that point connectionShape->moveHandle(0, cp); connectionShape->moveHandle(1, cp); // connect the first handle of the connection shape to our connection point if (!connectionShape->connectFirst(m_currentShape, m_activeHandle)) { delete shape; resetEditMode(); return; } //add connector label connectionShape->createTextShape(canvas()->shapeController()->resourceManager()); connectionShape->setPlainText(QString()); // create the connection edit strategy from the path tool m_currentStrategy = new KoPathConnectionPointStrategy(this, connectionShape, 1); if (!m_currentStrategy) { delete shape; resetEditMode(); return; } // update our handle data setEditMode(m_editMode, shape, 1); // add connection shape to the shape manager so it gets painted canvas()->shapeManager()->addShape(connectionShape); } else { // pressing on a shape in idle mode switches to corresponding edit mode if (hitShape) { if (dynamic_cast(hitShape)) { int hitHandle = handleAtPoint(hitShape, event->point); setEditMode(EditConnection, hitShape, hitHandle); if (hitHandle >= 0) { // start editing connection shape m_currentStrategy = new KoPathConnectionPointStrategy(this, dynamic_cast(m_currentShape), m_activeHandle); } } } else { resetEditMode(); } } } void ConnectionTool::mouseMoveEvent(KoPointerEvent *event) { if (m_currentStrategy) { repaintDecorations(); if (m_editMode != EditConnection && m_editMode != CreateConnection) { QPointF snappedPos = canvas()->snapGuide()->snap(event->point, event->modifiers()); m_currentStrategy->handleMouseMove(snappedPos, event->modifiers()); } else { m_currentStrategy->handleMouseMove(event->point, event->modifiers()); } repaintDecorations(); } else if (m_editMode == EditConnectionPoint) { KoShape *hoverShape = findNonConnectionShapeAtPosition(event->point);//TODO exclude connectors, need snap guide maybe? if (hoverShape) { m_currentShape = hoverShape; Q_ASSERT(m_currentShape); // check if we should highlight another connection point int handle = handleAtPoint(m_currentShape, event->point); if (handle >= 0) { setEditMode(m_editMode, m_currentShape, handle); useCursor(handle >= KoConnectionPoint::FirstCustomConnectionPoint ? Qt::SizeAllCursor : Qt::ArrowCursor); } else { updateStatusText(); useCursor(Qt::CrossCursor); } } else { m_currentShape = 0; useCursor(Qt::ArrowCursor); } } else if (m_editMode == EditConnection) { Q_ASSERT(m_currentShape); KoShape *hoverShape = findShapeAtPosition(event->point); // check if we should highlight another connection handle int handle = handleAtPoint(m_currentShape, event->point); setEditMode(m_editMode, m_currentShape, handle); if (m_activeHandle == KoConnectionShape::StartHandle || m_activeHandle == KoConnectionShape::EndHandle) { useCursor(Qt::SizeAllCursor); } else if (m_activeHandle >= KoConnectionShape::ControlHandle_1) { } else if (hoverShape && hoverShape != m_currentShape) { useCursor(Qt::PointingHandCursor); } else { useCursor(Qt::ArrowCursor); } } else {// Idle and no current strategy KoShape *hoverShape = findShapeAtPosition(event->point); int hoverHandle = -1; if (hoverShape) { KoConnectionShape *connectionShape = dynamic_cast(hoverShape); if (!connectionShape) { QPointF snappedPos = canvas()->snapGuide()->snap(event->point, event->modifiers()); hoverHandle = handleAtPoint(hoverShape, snappedPos); setEditMode(hoverHandle >= 0 ? CreateConnection : Idle, hoverShape, hoverHandle); } useCursor(hoverHandle >= 0 ? m_connectCursor : Qt::PointingHandCursor); } else { useCursor(Qt::ArrowCursor); } } } void ConnectionTool::mouseReleaseEvent(KoPointerEvent *event) { if (m_currentStrategy) { if (m_editMode == CreateConnection) { // check if connection handles have a minimal distance KoConnectionShape *connectionShape = dynamic_cast(m_currentShape); Q_ASSERT(connectionShape); // get both handle positions in document coordinates QPointF p1 = connectionShape->shapeToDocument(connectionShape->handlePosition(0)); QPointF p2 = connectionShape->shapeToDocument(connectionShape->handlePosition(1)); int grabDistance = grabSensitivity(); // use grabbing sensitivity as minimal distance threshold if (squareDistance(p1, p2) < grabDistance * grabDistance) { // minimal distance was not reached, so we have to undo the started work: // - cleanup and delete the strategy // - remove connection shape from shape manager and delete it // - reset edit mode to last state delete m_currentStrategy; m_currentStrategy = 0; repaintDecorations(); canvas()->shapeManager()->remove(m_currentShape); setEditMode(m_editMode, connectionShape->firstShape(), connectionShape->firstConnectionId()); repaintDecorations(); delete connectionShape; return; } else { // finalize adding the new connection shape with an undo command - KUndo2Command *cmd = canvas()->shapeController()->addShape(m_currentShape); + KUndo2Command *cmd = canvas()->shapeController()->addShape(m_currentShape, 0); canvas()->addCommand(cmd); setEditMode(EditConnection, m_currentShape, KoConnectionShape::StartHandle); } } m_currentStrategy->finishInteraction(event->modifiers()); // TODO: Add parent command to KoInteractionStrategy::createCommand // so that we can have a single command to undo for the user KUndo2Command *command = m_currentStrategy->createCommand(); if (command) { canvas()->addCommand(command); } delete m_currentStrategy; m_currentStrategy = 0; } updateStatusText(); } void ConnectionTool::mouseDoubleClickEvent(KoPointerEvent *event) { if (m_editMode == EditConnectionPoint) { repaintDecorations(); //quit EditConnectionPoint mode when double click blank region on canvas if (!m_currentShape) { resetEditMode(); return; } //add connection point when double click a shape //remove connection point when double click a existed connection point int handleId = handleAtPoint(m_currentShape, event->point); if (handleId < 0) { QPointF mousePos = canvas()->snapGuide()->snap(event->point, event->modifiers()); QPointF point = m_currentShape->documentToShape(mousePos); canvas()->addCommand(new AddConnectionPointCommand(m_currentShape, point)); } else { canvas()->addCommand(new RemoveConnectionPointCommand(m_currentShape, handleId)); } setEditMode(m_editMode, m_currentShape, -1); } else { //deactivate connection tool when double click blank region on canvas KoShape *hitShape = findShapeAtPosition(event->point); if (!hitShape) { deactivate(); emit done(); } else if (dynamic_cast(hitShape)) { repaintDecorations(); setEditMode(EditConnection, m_currentShape, -1); //TODO: temporarily activate text tool to edit connection path } } } void ConnectionTool::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { deactivate(); emit done(); } else if (event->key() == Qt::Key_Backspace) { deleteSelection(); event->accept(); } } void ConnectionTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); // save old enabled snap strategies, set bounding box snap strategy m_oldSnapStrategies = canvas()->snapGuide()->enabledSnapStrategies(); canvas()->snapGuide()->enableSnapStrategies(KoSnapGuide::BoundingBoxSnapping); canvas()->snapGuide()->reset(); m_resetPaint = true; repaintDecorations(); } void ConnectionTool::deactivate() { // Put everything to 0 to be able to begin a new shape properly delete m_currentStrategy; m_currentStrategy = 0; resetEditMode(); m_resetPaint = true; repaintDecorations(); // restore previously set snap strategies canvas()->snapGuide()->enableSnapStrategies(m_oldSnapStrategies); canvas()->snapGuide()->reset(); KoToolBase::deactivate(); } qreal ConnectionTool::squareDistance(const QPointF &p1, const QPointF &p2) const { // Square of the distance const qreal dx = p2.x() - p1.x(); const qreal dy = p2.y() - p1.y(); return dx * dx + dy * dy; } KoShape *ConnectionTool::findShapeAtPosition(const QPointF &position) const { QList shapes = canvas()->shapeManager()->shapesAt(handleGrabRect(position)); if (!shapes.isEmpty()) { qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); // we want to priorize connection shape handles, even if the connection shape // is not at the top of the shape stack at the mouse position KoConnectionShape *connectionShape = nearestConnectionShape(shapes, position); // use best connection shape or first shape from stack (last in the list) if not found if (connectionShape) { return connectionShape; } else { for (QList::const_iterator end = shapes.constEnd() - 1; end >= shapes.constBegin(); --end) { KoShape *shape = *end; if (!dynamic_cast(shape) && shape->shapeId() != TextShape_SHAPEID) { return shape; } } } } return 0; } KoShape *ConnectionTool::findNonConnectionShapeAtPosition(const QPointF &position) const { QList shapes = canvas()->shapeManager()->shapesAt(handleGrabRect(position)); if (!shapes.isEmpty()) { qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); for (QList::const_iterator end = shapes.constEnd() - 1; end >= shapes.constBegin(); --end) { KoShape *shape = *end; if (!dynamic_cast(shape) && shape->shapeId() != TextShape_SHAPEID) { return shape; } } } return 0; } int ConnectionTool::handleAtPoint(KoShape *shape, const QPointF &mousePoint) const { if (!shape) { return -1; } const QPointF shapePoint = shape->documentToShape(mousePoint); KoConnectionShape *connectionShape = dynamic_cast(shape); if (connectionShape) { // check connection shape handles return connectionShape->handleIdAt(handleGrabRect(shapePoint)); } else { // check connection points int grabDistance = grabSensitivity(); qreal minDistance = HUGE_VAL; int handleId = -1; KoConnectionPoints connectionPoints = shape->connectionPoints(); KoConnectionPoints::const_iterator cp = connectionPoints.constBegin(); KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd(); for (; cp != lastCp; ++cp) { qreal d = squareDistance(shapePoint, cp.value().position); if (d <= grabDistance && d < minDistance) { handleId = cp.key(); minDistance = d; } } return handleId; } } KoConnectionShape *ConnectionTool::nearestConnectionShape(const QList &shapes, const QPointF &mousePos) const { int grabDistance = grabSensitivity(); KoConnectionShape *nearestConnectionShape = 0; qreal minSquaredDistance = HUGE_VAL; const qreal maxSquaredDistance = grabDistance * grabDistance; Q_FOREACH (KoShape *shape, shapes) { KoConnectionShape *connectionShape = dynamic_cast(shape); if (!connectionShape || !connectionShape->isParametricShape()) { continue; } // convert document point to shape coordinates QPointF p = connectionShape->documentToShape(mousePos); // our region of interest, i.e. a region around our mouse position QRectF roi = handleGrabRect(p); // check all segments of this shape which intersect the region of interest QList segments = connectionShape->segmentsAt(roi); foreach (const KoPathSegment &s, segments) { qreal nearestPointParam = s.nearestPoint(p); QPointF nearestPoint = s.pointAt(nearestPointParam); QPointF diff = p - nearestPoint; qreal squaredDistance = diff.x() * diff.x() + diff.y() * diff.y(); // are we within the allowed distance ? if (squaredDistance > maxSquaredDistance) { continue; } // are we closer to the last closest point ? if (squaredDistance < minSquaredDistance) { nearestConnectionShape = connectionShape; minSquaredDistance = squaredDistance; } } } return nearestConnectionShape; } void ConnectionTool::setEditMode(EditMode mode, KoShape *currentShape, int handle) { repaintDecorations(); m_editMode = mode; if (m_currentShape != currentShape) { KoConnectionShape *connectionShape = dynamic_cast(currentShape); foreach (KoShapeConfigWidgetBase *cw, m_connectionShapeWidgets) { if (connectionShape) { cw->open(currentShape); } } } if (mode == Idle) { emit sendConnectionType(m_connectionType); } m_currentShape = currentShape; m_activeHandle = handle; repaintDecorations(); updateActions(); updateStatusText(); } void ConnectionTool::resetEditMode() { m_connectionType = KoConnectionShape::Standard; setEditMode(Idle, 0, -1); emit sendConnectionPointEditState(false); } void ConnectionTool::updateActions() { const bool connectionPointSelected = m_editMode == EditConnectionPoint && m_activeHandle >= 0; if (connectionPointSelected) { KoConnectionPoint cp = m_currentShape->connectionPoint(m_activeHandle); m_alignPercent->setChecked(false); Q_FOREACH (QAction *action, m_alignHorizontal->actions()) { action->setChecked(false); } Q_FOREACH (QAction *action, m_alignVertical->actions()) { action->setChecked(false); } switch (cp.alignment) { case KoConnectionPoint::AlignNone: m_alignPercent->setChecked(true); break; case KoConnectionPoint::AlignTopLeft: m_alignLeft->setChecked(true); m_alignTop->setChecked(true); break; case KoConnectionPoint::AlignTop: m_alignCenterH->setChecked(true); m_alignTop->setChecked(true); break; case KoConnectionPoint::AlignTopRight: m_alignRight->setChecked(true); m_alignTop->setChecked(true); break; case KoConnectionPoint::AlignLeft: m_alignLeft->setChecked(true); m_alignCenterV->setChecked(true); break; case KoConnectionPoint::AlignCenter: m_alignCenterH->setChecked(true); m_alignCenterV->setChecked(true); break; case KoConnectionPoint::AlignRight: m_alignRight->setChecked(true); m_alignCenterV->setChecked(true); break; case KoConnectionPoint::AlignBottomLeft: m_alignLeft->setChecked(true); m_alignBottom->setChecked(true); break; case KoConnectionPoint::AlignBottom: m_alignCenterH->setChecked(true); m_alignBottom->setChecked(true); break; case KoConnectionPoint::AlignBottomRight: m_alignRight->setChecked(true); m_alignBottom->setChecked(true); break; } Q_FOREACH (QAction *action, m_escapeDirections->actions()) { action->setChecked(false); } switch (cp.escapeDirection) { case KoConnectionPoint::AllDirections: m_escapeAll->setChecked(true); break; case KoConnectionPoint::HorizontalDirections: m_escapeHorizontal->setChecked(true); break; case KoConnectionPoint::VerticalDirections: m_escapeVertical->setChecked(true); break; case KoConnectionPoint::LeftDirection: m_escapeLeft->setChecked(true); break; case KoConnectionPoint::RightDirection: m_escapeRight->setChecked(true); break; case KoConnectionPoint::UpDirection: m_escapeUp->setChecked(true); break; case KoConnectionPoint::DownDirection: m_escapeDown->setChecked(true); break; } } emit connectionPointEnabled(connectionPointSelected); } void ConnectionTool::updateStatusText() { switch (m_editMode) { case Idle: if (m_currentShape) { if (dynamic_cast(m_currentShape)) { if (m_activeHandle >= 0) { emit statusTextChanged(i18n("Drag to edit connection.")); } else { emit statusTextChanged(i18n("Double click connection or press delete to remove it.")); } } else if (m_activeHandle < 0) { emit statusTextChanged(i18n("Click to edit connection points.")); } } else { emit statusTextChanged(QString()); } break; case EditConnection: if (m_activeHandle >= 0) { emit statusTextChanged(i18n("Drag to edit connection.")); } else { emit statusTextChanged(i18n("Double click connection or press delete to remove it.")); } break; case EditConnectionPoint: if (m_activeHandle >= KoConnectionPoint::FirstCustomConnectionPoint) { emit statusTextChanged(i18n("Drag to move connection point. Double click connection or press delete to remove it.")); } else if (m_activeHandle >= 0) { emit statusTextChanged(i18n("Double click connection point or press delete to remove it.")); } else { emit statusTextChanged(i18n("Double click to add connection point.")); } break; case CreateConnection: emit statusTextChanged(i18n("Drag to create new connection.")); break; default: emit statusTextChanged(QString()); } } QList > ConnectionTool::createOptionWidgets() { QList > list; m_connectionShapeWidgets.clear(); KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(KOCONNECTIONSHAPEID); if (factory) { QList widgets = factory->createShapeOptionPanels(); Q_FOREACH (KoShapeConfigWidgetBase *cw, widgets) { if (cw->showOnShapeCreate() || !cw->showOnShapeSelect()) { delete cw; continue; } connect(cw, SIGNAL(propertyChanged()), this, SLOT(connectionChanged())); KoConnectionShapeConfigWidget *cw2 = (KoConnectionShapeConfigWidget *)cw; if (cw2) { connect(cw2, SIGNAL(connectionTypeChanged(int)), this, SLOT(getConnectionType(int))); connect(this, SIGNAL(sendConnectionType(int)), cw2, SLOT(setConnectionType(int))); } m_connectionShapeWidgets.append(cw); cw->setWindowTitle(i18n("Connection")); list.append(cw); } } KoStrokeConfigWidget *strokeWidget = new KoStrokeConfigWidget(canvas(), 0); KisDocumentAwareSpinBoxUnitManager* managerLineWidth = new KisDocumentAwareSpinBoxUnitManager(strokeWidget); KisDocumentAwareSpinBoxUnitManager* managerMitterLimit = new KisDocumentAwareSpinBoxUnitManager(strokeWidget); managerLineWidth->setApparentUnitFromSymbol("px"); managerMitterLimit->setApparentUnitFromSymbol("px"); strokeWidget->setUnitManagers(managerLineWidth, managerMitterLimit); strokeWidget->setWindowTitle(i18n("Line")); list.append(strokeWidget); ConnectionPointWidget *connectPoint = new ConnectionPointWidget(this); connectPoint->setWindowTitle(i18n("Connection Point")); list.append(connectPoint); return list; } void ConnectionTool::horizontalAlignChanged() { if (m_alignPercent->isChecked()) { m_alignPercent->setChecked(false); m_alignTop->setChecked(true); } updateConnectionPoint(); } void ConnectionTool::verticalAlignChanged() { if (m_alignPercent->isChecked()) { m_alignPercent->setChecked(false); m_alignLeft->setChecked(true); } updateConnectionPoint(); } void ConnectionTool::relativeAlignChanged() { Q_FOREACH (QAction *action, m_alignHorizontal->actions()) { action->setChecked(false); } Q_FOREACH (QAction *action, m_alignVertical->actions()) { action->setChecked(false); } m_alignPercent->setChecked(true); updateConnectionPoint(); } void ConnectionTool::updateConnectionPoint() { if (m_editMode == EditConnectionPoint && m_currentShape && m_activeHandle >= 0) { KoConnectionPoint oldPoint = m_currentShape->connectionPoint(m_activeHandle); KoConnectionPoint newPoint = oldPoint; if (m_alignPercent->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignNone; } else if (m_alignLeft->isChecked() && m_alignTop->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignTopLeft; } else if (m_alignCenterH->isChecked() && m_alignTop->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignTop; } else if (m_alignRight->isChecked() && m_alignTop->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignTopRight; } else if (m_alignLeft->isChecked() && m_alignCenterV->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignLeft; } else if (m_alignCenterH->isChecked() && m_alignCenterV->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignCenter; } else if (m_alignRight->isChecked() && m_alignCenterV->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignRight; } else if (m_alignLeft->isChecked() && m_alignBottom->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignBottomLeft; } else if (m_alignCenterH->isChecked() && m_alignBottom->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignBottom; } else if (m_alignRight->isChecked() && m_alignBottom->isChecked()) { newPoint.alignment = KoConnectionPoint::AlignBottomRight; } canvas()->addCommand(new ChangeConnectionPointCommand(m_currentShape, m_activeHandle, oldPoint, newPoint)); } } void ConnectionTool::escapeDirectionChanged() { if (m_editMode == EditConnectionPoint && m_currentShape && m_activeHandle >= 0) { KoConnectionPoint oldPoint = m_currentShape->connectionPoint(m_activeHandle); KoConnectionPoint newPoint = oldPoint; QAction *checkedAction = m_escapeDirections->checkedAction(); if (checkedAction == m_escapeAll) { newPoint.escapeDirection = KoConnectionPoint::AllDirections; } else if (checkedAction == m_escapeHorizontal) { newPoint.escapeDirection = KoConnectionPoint::HorizontalDirections; } else if (checkedAction == m_escapeVertical) { newPoint.escapeDirection = KoConnectionPoint::VerticalDirections; } else if (checkedAction == m_escapeLeft) { newPoint.escapeDirection = KoConnectionPoint::LeftDirection; } else if (checkedAction == m_escapeRight) { newPoint.escapeDirection = KoConnectionPoint::RightDirection; } else if (checkedAction == m_escapeUp) { newPoint.escapeDirection = KoConnectionPoint::UpDirection; } else if (checkedAction == m_escapeDown) { newPoint.escapeDirection = KoConnectionPoint::DownDirection; } canvas()->addCommand(new ChangeConnectionPointCommand(m_currentShape, m_activeHandle, oldPoint, newPoint)); } } void ConnectionTool::connectionChanged() { if (m_editMode != EditConnection) { return; } KoConnectionShape *connectionShape = dynamic_cast(m_currentShape); if (!connectionShape) { return; } Q_FOREACH (KoShapeConfigWidgetBase *cw, m_connectionShapeWidgets) { canvas()->addCommand(cw->createCommand()); } } void ConnectionTool::deleteSelection() { if (m_editMode == EditConnectionPoint && m_currentShape && m_activeHandle >= 0) { repaintDecorations(); canvas()->addCommand(new RemoveConnectionPointCommand(m_currentShape, m_activeHandle)); setEditMode(m_editMode, m_currentShape, -1); } else if (m_editMode == EditConnection && m_currentShape) { repaintDecorations(); canvas()->addCommand(canvas()->shapeController()->removeShape(m_currentShape)); resetEditMode(); } } void ConnectionTool::getConnectionType(int type) { if (m_editMode == Idle) { m_connectionType = (KoConnectionShape::Type)type; } } void ConnectionTool::toggleConnectionPointEditMode(int state) { if (state == Qt::Checked) { setEditMode(EditConnectionPoint, 0, -1); } else if (state == Qt::Unchecked) { setEditMode(Idle, 0, -1); } else { return; } } diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp index 7ff8460272..20807c7184 100644 --- a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp +++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp @@ -1,1465 +1,1582 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2008-2009 Jan Hambrecht Copyright (C) 2008 C. Boemann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "DefaultTool.h" #include "DefaultToolGeometryWidget.h" #include "DefaultToolTabbedWidget.h" #include "SelectionDecorator.h" #include "ShapeMoveStrategy.h" #include "ShapeRotateStrategy.h" #include "ShapeShearStrategy.h" #include "ShapeResizeStrategy.h" #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include "kis_action_registry.h" #include #include "kis_document_aware_spin_box_unit_manager.h" #include #include #include #include #include #include #include #include #include #include #include "kis_assert.h" #include "kis_global.h" #include "kis_debug.h" #include #define HANDLE_DISTANCE 10 #define HANDLE_DISTANCE_SQ (HANDLE_DISTANCE * HANDLE_DISTANCE) #define INNER_HANDLE_DISTANCE_SQ 16 namespace { static const QString EditFillGradientFactoryId = "edit_fill_gradient"; static const QString EditStrokeGradientFactoryId = "edit_stroke_gradient"; enum TransformActionType { TransformRotate90CW, TransformRotate90CCW, TransformRotate180, TransformMirrorX, TransformMirrorY, TransformReset }; +enum BooleanOp { + BooleanUnion, + BooleanIntersection, + BooleanSubtraction +}; + } QPolygonF selectionPolygon(KoSelection *selection) { QPolygonF result; QList selectedShapes = selection->selectedShapes(); if (!selectedShapes.size()) { return result; } if (selectedShapes.size() > 1) { QTransform matrix = selection->absoluteTransformation(0); result = matrix.map(QPolygonF(QRectF(QPointF(0, 0), selection->size()))); } else { KoShape *selectedShape = selectedShapes.first(); QTransform matrix = selectedShape->absoluteTransformation(0); result = matrix.map(QPolygonF(QRectF(QPointF(0, 0), selectedShape->size()))); } return result; } class NopInteractionStrategy : public KoInteractionStrategy { public: explicit NopInteractionStrategy(KoToolBase *parent) : KoInteractionStrategy(parent) { } KUndo2Command *createCommand() override { return 0; } void handleMouseMove(const QPointF & /*mouseLocation*/, Qt::KeyboardModifiers /*modifiers*/) override {} void finishInteraction(Qt::KeyboardModifiers /*modifiers*/) override {} void paint(QPainter &painter, const KoViewConverter &converter) override { Q_UNUSED(painter); Q_UNUSED(converter); } }; class SelectionInteractionStrategy : public KoShapeRubberSelectStrategy { public: explicit SelectionInteractionStrategy(KoToolBase *parent, const QPointF &clicked, bool useSnapToGrid) : KoShapeRubberSelectStrategy(parent, clicked, useSnapToGrid) { } void paint(QPainter &painter, const KoViewConverter &converter) override { KoShapeRubberSelectStrategy::paint(painter, converter); } }; #include #include "KoShapeGradientHandles.h" #include "ShapeGradientEditStrategy.h" class DefaultTool::MoveGradientHandleInteractionFactory : public KoInteractionStrategyFactory { public: MoveGradientHandleInteractionFactory(KoFlake::FillVariant fillVariant, int priority, const QString &id, DefaultTool *_q) : KoInteractionStrategyFactory(priority, id), q(_q), m_fillVariant(fillVariant) { } KoInteractionStrategy* createStrategy(KoPointerEvent *ev) override { m_currentHandle = handleAt(ev->point); if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) { KoShape *shape = onlyEditableShape(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, 0); return new ShapeGradientEditStrategy(q, m_fillVariant, shape, m_currentHandle.type, ev->point); } return 0; } bool hoverEvent(KoPointerEvent *ev) override { m_currentHandle = handleAt(ev->point); return false; } bool paintOnHover(QPainter &painter, const KoViewConverter &converter) override { Q_UNUSED(painter); Q_UNUSED(converter); return false; } bool tryUseCustomCursor() override { if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) { q->useCursor(Qt::OpenHandCursor); } return m_currentHandle.type != KoShapeGradientHandles::Handle::None; } private: KoShape* onlyEditableShape() const { KoSelection *selection = q->koSelection(); QList shapes = selection->selectedEditableShapes(); KoShape *shape = 0; if (shapes.size() == 1) { shape = shapes.first(); } return shape; } KoShapeGradientHandles::Handle handleAt(const QPointF &pos) { KoShapeGradientHandles::Handle result; KoShape *shape = onlyEditableShape(); if (shape) { KoFlake::SelectionHandle globalHandle = q->handleAt(pos); const qreal distanceThresholdSq = globalHandle == KoFlake::NoHandle ? HANDLE_DISTANCE_SQ : 0.25 * HANDLE_DISTANCE_SQ; const KoViewConverter *converter = q->canvas()->viewConverter(); const QPointF viewPoint = converter->documentToView(pos); qreal minDistanceSq = std::numeric_limits::max(); KoShapeGradientHandles sh(m_fillVariant, shape); Q_FOREACH (const KoShapeGradientHandles::Handle &handle, sh.handles()) { const QPointF handlePoint = converter->documentToView(handle.pos); const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint); if (distanceSq < distanceThresholdSq && distanceSq < minDistanceSq) { result = handle; minDistanceSq = distanceSq; } } } return result; } private: DefaultTool *q; KoFlake::FillVariant m_fillVariant; KoShapeGradientHandles::Handle m_currentHandle; }; class SelectionHandler : public KoToolSelection { public: SelectionHandler(DefaultTool *parent) : KoToolSelection(parent) , m_selection(parent->koSelection()) { } bool hasSelection() override { if (m_selection) { return m_selection->count(); } return false; } private: QPointer m_selection; }; DefaultTool::DefaultTool(KoCanvasBase *canvas) : KoInteractionTool(canvas) , m_lastHandle(KoFlake::NoHandle) , m_hotPosition(KoFlake::TopLeft) , m_mouseWasInsideHandles(false) , m_selectionHandler(new SelectionHandler(this)) , m_customEventStrategy(0) , m_tabbedOptionWidget(0) { setupActions(); QPixmap rotatePixmap, shearPixmap; rotatePixmap.load(":/cursor_rotate.png"); Q_ASSERT(!rotatePixmap.isNull()); shearPixmap.load(":/cursor_shear.png"); Q_ASSERT(!shearPixmap.isNull()); m_rotateCursors[0] = QCursor(rotatePixmap.transformed(QTransform().rotate(45))); m_rotateCursors[1] = QCursor(rotatePixmap.transformed(QTransform().rotate(90))); m_rotateCursors[2] = QCursor(rotatePixmap.transformed(QTransform().rotate(135))); m_rotateCursors[3] = QCursor(rotatePixmap.transformed(QTransform().rotate(180))); m_rotateCursors[4] = QCursor(rotatePixmap.transformed(QTransform().rotate(225))); m_rotateCursors[5] = QCursor(rotatePixmap.transformed(QTransform().rotate(270))); m_rotateCursors[6] = QCursor(rotatePixmap.transformed(QTransform().rotate(315))); m_rotateCursors[7] = QCursor(rotatePixmap); /* m_rotateCursors[0] = QCursor(Qt::RotateNCursor); m_rotateCursors[1] = QCursor(Qt::RotateNECursor); m_rotateCursors[2] = QCursor(Qt::RotateECursor); m_rotateCursors[3] = QCursor(Qt::RotateSECursor); m_rotateCursors[4] = QCursor(Qt::RotateSCursor); m_rotateCursors[5] = QCursor(Qt::RotateSWCursor); m_rotateCursors[6] = QCursor(Qt::RotateWCursor); m_rotateCursors[7] = QCursor(Qt::RotateNWCursor); */ m_shearCursors[0] = QCursor(shearPixmap); m_shearCursors[1] = QCursor(shearPixmap.transformed(QTransform().rotate(45))); m_shearCursors[2] = QCursor(shearPixmap.transformed(QTransform().rotate(90))); m_shearCursors[3] = QCursor(shearPixmap.transformed(QTransform().rotate(135))); m_shearCursors[4] = QCursor(shearPixmap.transformed(QTransform().rotate(180))); m_shearCursors[5] = QCursor(shearPixmap.transformed(QTransform().rotate(225))); m_shearCursors[6] = QCursor(shearPixmap.transformed(QTransform().rotate(270))); m_shearCursors[7] = QCursor(shearPixmap.transformed(QTransform().rotate(315))); m_sizeCursors[0] = Qt::SizeVerCursor; m_sizeCursors[1] = Qt::SizeBDiagCursor; m_sizeCursors[2] = Qt::SizeHorCursor; m_sizeCursors[3] = Qt::SizeFDiagCursor; m_sizeCursors[4] = Qt::SizeVerCursor; m_sizeCursors[5] = Qt::SizeBDiagCursor; m_sizeCursors[6] = Qt::SizeHorCursor; m_sizeCursors[7] = Qt::SizeFDiagCursor; connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(updateActions())); } DefaultTool::~DefaultTool() { } void DefaultTool::slotActivateEditFillGradient(bool value) { if (value) { addInteractionFactory( new MoveGradientHandleInteractionFactory(KoFlake::Fill, 1, EditFillGradientFactoryId, this)); } else { removeInteractionFactory(EditFillGradientFactoryId); } repaintDecorations(); } void DefaultTool::slotActivateEditStrokeGradient(bool value) { if (value) { addInteractionFactory( new MoveGradientHandleInteractionFactory(KoFlake::StrokeFill, 0, EditStrokeGradientFactoryId, this)); } else { removeInteractionFactory(EditStrokeGradientFactoryId); } repaintDecorations(); } bool DefaultTool::wantsAutoScroll() const { return true; } void DefaultTool::addMappedAction(QSignalMapper *mapper, const QString &actionId, int commandType) { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); QAction *action = actionRegistry->makeQAction(actionId, this); addAction(actionId, action); connect(action, SIGNAL(triggered()), mapper, SLOT(map())); mapper->setMapping(action, commandType); } void DefaultTool::setupActions() { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); QAction *actionBringToFront = actionRegistry->makeQAction("object_order_front", this); addAction("object_order_front", actionBringToFront); connect(actionBringToFront, SIGNAL(triggered()), this, SLOT(selectionBringToFront())); QAction *actionRaise = actionRegistry->makeQAction("object_order_raise", this); addAction("object_order_raise", actionRaise); connect(actionRaise, SIGNAL(triggered()), this, SLOT(selectionMoveUp())); QAction *actionLower = actionRegistry->makeQAction("object_order_lower", this); addAction("object_order_lower", actionLower); connect(actionLower, SIGNAL(triggered()), this, SLOT(selectionMoveDown())); QAction *actionSendToBack = actionRegistry->makeQAction("object_order_back", this); addAction("object_order_back", actionSendToBack); connect(actionSendToBack, SIGNAL(triggered()), this, SLOT(selectionSendToBack())); QSignalMapper *alignSignalsMapper = new QSignalMapper(this); connect(alignSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionAlign(int))); addMappedAction(alignSignalsMapper, "object_align_horizontal_left", KoShapeAlignCommand::HorizontalLeftAlignment); addMappedAction(alignSignalsMapper, "object_align_horizontal_center", KoShapeAlignCommand::HorizontalCenterAlignment); addMappedAction(alignSignalsMapper, "object_align_horizontal_right", KoShapeAlignCommand::HorizontalRightAlignment); addMappedAction(alignSignalsMapper, "object_align_vertical_top", KoShapeAlignCommand::VerticalTopAlignment); addMappedAction(alignSignalsMapper, "object_align_vertical_center", KoShapeAlignCommand::VerticalCenterAlignment); addMappedAction(alignSignalsMapper, "object_align_vertical_bottom", KoShapeAlignCommand::VerticalBottomAlignment); QSignalMapper *distributeSignalsMapper = new QSignalMapper(this); connect(distributeSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionDistribute(int))); addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_left", KoShapeDistributeCommand::HorizontalLeftDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_center", KoShapeDistributeCommand::HorizontalCenterDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_right", KoShapeDistributeCommand::HorizontalRightDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_gaps", KoShapeDistributeCommand::HorizontalGapsDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_vertical_top", KoShapeDistributeCommand::VerticalTopDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_vertical_center", KoShapeDistributeCommand::VerticalCenterDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_vertical_bottom", KoShapeDistributeCommand::VerticalBottomDistribution); addMappedAction(distributeSignalsMapper, "object_distribute_vertical_gaps", KoShapeDistributeCommand::VerticalGapsDistribution); QAction *actionGroupBottom = actionRegistry->makeQAction("object_group", this); addAction("object_group", actionGroupBottom); connect(actionGroupBottom, SIGNAL(triggered()), this, SLOT(selectionGroup())); QAction *actionUngroupBottom = actionRegistry->makeQAction("object_ungroup", this); addAction("object_ungroup", actionUngroupBottom); connect(actionUngroupBottom, SIGNAL(triggered()), this, SLOT(selectionUngroup())); QSignalMapper *transformSignalsMapper = new QSignalMapper(this); connect(transformSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionTransform(int))); addMappedAction(transformSignalsMapper, "object_transform_rotate_90_cw", TransformRotate90CW); addMappedAction(transformSignalsMapper, "object_transform_rotate_90_ccw", TransformRotate90CCW); addMappedAction(transformSignalsMapper, "object_transform_rotate_180", TransformRotate180); addMappedAction(transformSignalsMapper, "object_transform_mirror_horizontally", TransformMirrorX); addMappedAction(transformSignalsMapper, "object_transform_mirror_vertically", TransformMirrorY); addMappedAction(transformSignalsMapper, "object_transform_reset", TransformReset); + QSignalMapper *booleanSignalsMapper = new QSignalMapper(this); + connect(booleanSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionBooleanOp(int))); + + addMappedAction(booleanSignalsMapper, "object_unite", BooleanUnion); + addMappedAction(booleanSignalsMapper, "object_intersect", BooleanIntersection); + addMappedAction(booleanSignalsMapper, "object_subtract", BooleanSubtraction); + m_contextMenu.reset(new QMenu()); } qreal DefaultTool::rotationOfHandle(KoFlake::SelectionHandle handle, bool useEdgeRotation) { QPointF selectionCenter = koSelection()->absolutePosition(); QPointF direction; switch (handle) { case KoFlake::TopMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::TopLeft); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::TopRight) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::TopRightHandle: direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized()).toPointF(); break; case KoFlake::RightMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::TopRight); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopRight); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::BottomRightHandle: direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized()).toPointF(); break; case KoFlake::BottomMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::BottomRight); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::BottomLeft); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::BottomLeftHandle: direction = koSelection()->absolutePosition(KoFlake::BottomLeft) - selectionCenter; direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized()).toPointF(); break; case KoFlake::LeftMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::BottomLeft); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomLeft) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::TopLeftHandle: direction = koSelection()->absolutePosition(KoFlake::TopLeft) - selectionCenter; direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized()).toPointF(); break; case KoFlake::NoHandle: return 0.0; break; } qreal rotation = atan2(direction.y(), direction.x()) * 180.0 / M_PI; switch (handle) { case KoFlake::TopMiddleHandle: if (useEdgeRotation) { rotation -= 0.0; } else { rotation -= 270.0; } break; case KoFlake::TopRightHandle: rotation -= 315.0; break; case KoFlake::RightMiddleHandle: if (useEdgeRotation) { rotation -= 90.0; } else { rotation -= 0.0; } break; case KoFlake::BottomRightHandle: rotation -= 45.0; break; case KoFlake::BottomMiddleHandle: if (useEdgeRotation) { rotation -= 180.0; } else { rotation -= 90.0; } break; case KoFlake::BottomLeftHandle: rotation -= 135.0; break; case KoFlake::LeftMiddleHandle: if (useEdgeRotation) { rotation -= 270.0; } else { rotation -= 180.0; } break; case KoFlake::TopLeftHandle: rotation -= 225.0; break; case KoFlake::NoHandle: break; } if (rotation < 0.0) { rotation += 360.0; } return rotation; } void DefaultTool::updateCursor() { if (tryUseCustomCursor()) return; QCursor cursor = Qt::ArrowCursor; QString statusText; if (koSelection()->count() > 0) { // has a selection bool editable = !koSelection()->selectedEditableShapes().isEmpty(); if (!m_mouseWasInsideHandles) { m_angle = rotationOfHandle(m_lastHandle, true); int rotOctant = 8 + int(8.5 + m_angle / 45); bool rotateHandle = false; bool shearHandle = false; switch (m_lastHandle) { case KoFlake::TopMiddleHandle: cursor = m_shearCursors[(0 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::TopRightHandle: cursor = m_rotateCursors[(1 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::RightMiddleHandle: cursor = m_shearCursors[(2 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::BottomRightHandle: cursor = m_rotateCursors[(3 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::BottomMiddleHandle: cursor = m_shearCursors[(4 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::BottomLeftHandle: cursor = m_rotateCursors[(5 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::LeftMiddleHandle: cursor = m_shearCursors[(6 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::TopLeftHandle: cursor = m_rotateCursors[(7 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::NoHandle: cursor = Qt::ArrowCursor; break; } if (rotateHandle) { statusText = i18n("Left click rotates around center, right click around highlighted position."); } if (shearHandle) { statusText = i18n("Click and drag to shear selection."); } } else { statusText = i18n("Click and drag to resize selection."); m_angle = rotationOfHandle(m_lastHandle, false); int rotOctant = 8 + int(8.5 + m_angle / 45); bool cornerHandle = false; switch (m_lastHandle) { case KoFlake::TopMiddleHandle: cursor = m_sizeCursors[(0 + rotOctant) % 8]; break; case KoFlake::TopRightHandle: cursor = m_sizeCursors[(1 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::RightMiddleHandle: cursor = m_sizeCursors[(2 + rotOctant) % 8]; break; case KoFlake::BottomRightHandle: cursor = m_sizeCursors[(3 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::BottomMiddleHandle: cursor = m_sizeCursors[(4 + rotOctant) % 8]; break; case KoFlake::BottomLeftHandle: cursor = m_sizeCursors[(5 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::LeftMiddleHandle: cursor = m_sizeCursors[(6 + rotOctant) % 8]; break; case KoFlake::TopLeftHandle: cursor = m_sizeCursors[(7 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::NoHandle: cursor = Qt::SizeAllCursor; statusText = i18n("Click and drag to move selection."); break; } if (cornerHandle) { statusText = i18n("Click and drag to resize selection. Middle click to set highlighted position."); } } if (!editable) { cursor = Qt::ArrowCursor; } } else { // there used to be guides... :'''( } useCursor(cursor); if (currentStrategy() == 0) { emit statusTextChanged(statusText); } } void DefaultTool::paint(QPainter &painter, const KoViewConverter &converter) { SelectionDecorator decorator(canvas()->resourceManager()); decorator.setSelection(koSelection()); decorator.setHandleRadius(handleRadius()); decorator.setShowFillGradientHandles(hasInteractioFactory(EditFillGradientFactoryId)); decorator.setShowStrokeFillGradientHandles(hasInteractioFactory(EditStrokeGradientFactoryId)); decorator.paint(painter, converter); KoInteractionTool::paint(painter, converter); painter.save(); KoShape::applyConversion(painter, converter); canvas()->snapGuide()->paint(painter, converter); painter.restore(); } void DefaultTool::mousePressEvent(KoPointerEvent *event) { KoInteractionTool::mousePressEvent(event); updateCursor(); } void DefaultTool::mouseMoveEvent(KoPointerEvent *event) { KoInteractionTool::mouseMoveEvent(event); if (currentStrategy() == 0 && koSelection() && koSelection()->count() > 0) { QRectF bound = handlesSize(); if (bound.contains(event->point)) { bool inside; KoFlake::SelectionHandle newDirection = handleAt(event->point, &inside); if (inside != m_mouseWasInsideHandles || m_lastHandle != newDirection) { m_lastHandle = newDirection; m_mouseWasInsideHandles = inside; //repaintDecorations(); } } else { /*if (m_lastHandle != KoFlake::NoHandle) repaintDecorations(); */ m_lastHandle = KoFlake::NoHandle; m_mouseWasInsideHandles = false; // there used to be guides... :'''( } } else { // there used to be guides... :'''( } updateCursor(); } QRectF DefaultTool::handlesSize() { KoSelection *selection = koSelection(); if (!selection->count()) return QRectF(); recalcSelectionBox(selection); QRectF bound = m_selectionOutline.boundingRect(); // expansion Border if (!canvas() || !canvas()->viewConverter()) { return bound; } QPointF border = canvas()->viewConverter()->viewToDocument(QPointF(HANDLE_DISTANCE, HANDLE_DISTANCE)); bound.adjust(-border.x(), -border.y(), border.x(), border.y()); return bound; } void DefaultTool::mouseReleaseEvent(KoPointerEvent *event) { KoInteractionTool::mouseReleaseEvent(event); updateCursor(); } void DefaultTool::mouseDoubleClickEvent(KoPointerEvent *event) { KoSelection *selection = canvas()->selectedShapesProxy()->selection(); KoShape *shape = canvas()->shapeManager()->shapeAt(event->point, KoFlake::ShapeOnTop); if (shape && !selection->isSelected(shape)) { if (!(event->modifiers() & Qt::ShiftModifier)) { selection->deselectAll(); } selection->select(shape); } explicitUserStrokeEndRequest(); } bool DefaultTool::moveSelection(int direction, Qt::KeyboardModifiers modifiers) { bool result = false; qreal x = 0.0, y = 0.0; if (direction == Qt::Key_Left) { x = -5; } else if (direction == Qt::Key_Right) { x = 5; } else if (direction == Qt::Key_Up) { y = -5; } else if (direction == Qt::Key_Down) { y = 5; } if (x != 0.0 || y != 0.0) { // actually move if ((modifiers & Qt::ShiftModifier) != 0) { x *= 10; y *= 10; } else if ((modifiers & Qt::AltModifier) != 0) { // more precise x /= 5; y /= 5; } QList shapes = koSelection()->selectedEditableShapes(); if (!shapes.isEmpty()) { canvas()->addCommand(new KoShapeMoveCommand(shapes, QPointF(x, y))); result = true; } } return result; } void DefaultTool::keyPressEvent(QKeyEvent *event) { KoInteractionTool::keyPressEvent(event); if (currentStrategy() == 0) { switch (event->key()) { case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: if (moveSelection(event->key(), event->modifiers())) { event->accept(); } break; case Qt::Key_1: case Qt::Key_2: case Qt::Key_3: case Qt::Key_4: case Qt::Key_5: canvas()->resourceManager()->setResource(HotPosition, event->key() - Qt::Key_1); event->accept(); break; default: return; } } } void DefaultTool::repaintDecorations() { if (koSelection() && koSelection()->count() > 0) { canvas()->updateCanvas(handlesSize()); } } void DefaultTool::copy() const { // all the selected shapes, not only editable! QList shapes = canvas()->selectedShapesProxy()->selection()->selectedShapes(); if (!shapes.isEmpty()) { KoDrag drag; drag.setSvg(shapes); drag.addToClipboard(); } } void DefaultTool::deleteSelection() { QList shapes; foreach (KoShape *s, canvas()->selectedShapesProxy()->selection()->selectedShapes()) { if (s->isGeometryProtected()) { continue; } shapes << s; } if (!shapes.empty()) { canvas()->addCommand(canvas()->shapeController()->removeShapes(shapes)); } } bool DefaultTool::paste() { // we no longer have to do anything as tool Proxy will do it for us return false; } KoSelection *DefaultTool::koSelection() { Q_ASSERT(canvas()); Q_ASSERT(canvas()->selectedShapesProxy()); return canvas()->selectedShapesProxy()->selection(); } KoFlake::SelectionHandle DefaultTool::handleAt(const QPointF &point, bool *innerHandleMeaning) { // check for handles in this order; meaning that when handles overlap the one on top is chosen static const KoFlake::SelectionHandle handleOrder[] = { KoFlake::BottomRightHandle, KoFlake::TopLeftHandle, KoFlake::BottomLeftHandle, KoFlake::TopRightHandle, KoFlake::BottomMiddleHandle, KoFlake::RightMiddleHandle, KoFlake::LeftMiddleHandle, KoFlake::TopMiddleHandle, KoFlake::NoHandle }; const KoViewConverter *converter = canvas()->viewConverter(); KoSelection *selection = koSelection(); if (!selection->count() || !converter) { return KoFlake::NoHandle; } recalcSelectionBox(selection); if (innerHandleMeaning) { QPainterPath path; path.addPolygon(m_selectionOutline); *innerHandleMeaning = path.contains(point) || path.intersects(handlePaintRect(point)); } const QPointF viewPoint = converter->documentToView(point); for (int i = 0; i < KoFlake::NoHandle; ++i) { KoFlake::SelectionHandle handle = handleOrder[i]; const QPointF handlePoint = converter->documentToView(m_selectionBox[handle]); const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint); // if just inside the outline if (distanceSq < HANDLE_DISTANCE_SQ) { if (innerHandleMeaning) { if (distanceSq < INNER_HANDLE_DISTANCE_SQ) { *innerHandleMeaning = true; } } return handle; } } return KoFlake::NoHandle; } void DefaultTool::recalcSelectionBox(KoSelection *selection) { KIS_ASSERT_RECOVER_RETURN(selection->count()); QTransform matrix = selection->absoluteTransformation(0); m_selectionOutline = matrix.map(QPolygonF(selection->outlineRect())); m_angle = 0.0; QPolygonF outline = m_selectionOutline; //shorter name in the following :) m_selectionBox[KoFlake::TopMiddleHandle] = (outline.value(0) + outline.value(1)) / 2; m_selectionBox[KoFlake::TopRightHandle] = outline.value(1); m_selectionBox[KoFlake::RightMiddleHandle] = (outline.value(1) + outline.value(2)) / 2; m_selectionBox[KoFlake::BottomRightHandle] = outline.value(2); m_selectionBox[KoFlake::BottomMiddleHandle] = (outline.value(2) + outline.value(3)) / 2; m_selectionBox[KoFlake::BottomLeftHandle] = outline.value(3); m_selectionBox[KoFlake::LeftMiddleHandle] = (outline.value(3) + outline.value(0)) / 2; m_selectionBox[KoFlake::TopLeftHandle] = outline.value(0); if (selection->count() == 1) { #if 0 // TODO detect mirroring KoShape *s = koSelection()->firstSelectedShape(); if (s->scaleX() < 0) { // vertically mirrored: swap left / right qSwap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::TopRightHandle]); qSwap(m_selectionBox[KoFlake::LeftMiddleHandle], m_selectionBox[KoFlake::RightMiddleHandle]); qSwap(m_selectionBox[KoFlake::BottomLeftHandle], m_selectionBox[KoFlake::BottomRightHandle]); } if (s->scaleY() < 0) { // vertically mirrored: swap top / bottom qSwap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::BottomLeftHandle]); qSwap(m_selectionBox[KoFlake::TopMiddleHandle], m_selectionBox[KoFlake::BottomMiddleHandle]); qSwap(m_selectionBox[KoFlake::TopRightHandle], m_selectionBox[KoFlake::BottomRightHandle]); } #endif } } void DefaultTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); m_mouseWasInsideHandles = false; m_lastHandle = KoFlake::NoHandle; useCursor(Qt::ArrowCursor); repaintDecorations(); updateActions(); if (m_tabbedOptionWidget) { m_tabbedOptionWidget->activate(); } } void DefaultTool::deactivate() { KoToolBase::deactivate(); if (m_tabbedOptionWidget) { m_tabbedOptionWidget->deactivate(); } } void DefaultTool::selectionGroup() { KoSelection *selection = koSelection(); if (!selection) return; QList selectedShapes = selection->selectedEditableShapes(); qSort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); KoShapeGroup *group = new KoShapeGroup(); // TODO what if only one shape is left? KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes")); - canvas()->shapeController()->addShapeDirect(group, cmd); + canvas()->shapeController()->addShapeDirect(group, 0, cmd); new KoShapeGroupCommand(group, selectedShapes, false, true, true, cmd); canvas()->addCommand(cmd); // update selection so we can ungroup immediately again selection->deselectAll(); selection->select(group); } void DefaultTool::selectionUngroup() { KoSelection *selection = koSelection(); if (!selection) return; QList selectedShapes = selection->selectedEditableShapes(); qSort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex); KUndo2Command *cmd = 0; // add a ungroup command for each found shape container to the macro command Q_FOREACH (KoShape *shape, selectedShapes) { KoShapeGroup *group = dynamic_cast(shape); if (group) { cmd = cmd ? cmd : new KUndo2Command(kundo2_i18n("Ungroup shapes")); new KoShapeUngroupCommand(group, group->shapes(), group->parent() ? QList() : canvas()->shapeManager()->topLevelShapes(), cmd); canvas()->shapeController()->removeShape(group, cmd); } } if (cmd) { canvas()->addCommand(cmd); } } void DefaultTool::selectionTransform(int transformAction) { KoSelection *selection = koSelection(); if (!selection) return; QList editableShapes = selection->selectedEditableShapes(); if (editableShapes.isEmpty()) { return; } QTransform applyTransform; bool shouldReset = false; KUndo2MagicString actionName = kundo2_noi18n("BUG: No transform action"); switch (TransformActionType(transformAction)) { case TransformRotate90CW: applyTransform.rotate(90.0); actionName = kundo2_i18n("Rotate Object 90° CW"); break; case TransformRotate90CCW: applyTransform.rotate(-90.0); actionName = kundo2_i18n("Rotate Object 90° CCW"); break; case TransformRotate180: applyTransform.rotate(180.0); actionName = kundo2_i18n("Rotate Object 180°"); break; case TransformMirrorX: applyTransform.scale(-1.0, 1.0); actionName = kundo2_i18n("Mirror Object Horizontally"); break; case TransformMirrorY: applyTransform.scale(1.0, -1.0); actionName = kundo2_i18n("Mirror Object Vertically"); break; case TransformReset: shouldReset = true; actionName = kundo2_i18n("Reset Object Transformations"); break; } if (!shouldReset && applyTransform.isIdentity()) return; QList oldTransforms; QList newTransforms; const QRectF outlineRect = KoShape::absoluteOutlineRect(editableShapes); const QPointF centerPoint = outlineRect.center(); const QTransform centerTrans = QTransform::fromTranslate(centerPoint.x(), centerPoint.y()); const QTransform centerTransInv = QTransform::fromTranslate(-centerPoint.x(), -centerPoint.y()); // we also add selection to the list of trasformed shapes, so that its outline is updated correctly QList transformedShapes = editableShapes; transformedShapes << selection; Q_FOREACH (KoShape *shape, transformedShapes) { oldTransforms.append(shape->transformation()); QTransform t; if (!shouldReset) { const QTransform world = shape->absoluteTransformation(0); t = world * centerTransInv * applyTransform * centerTrans * world.inverted() * shape->transformation(); } else { const QPointF center = shape->outlineRect().center(); const QPointF offset = shape->transformation().map(center) - center; t = QTransform::fromTranslate(offset.x(), offset.y()); } newTransforms.append(t); } KoShapeTransformCommand *cmd = new KoShapeTransformCommand(transformedShapes, oldTransforms, newTransforms); cmd->setText(actionName); canvas()->addCommand(cmd); } +void DefaultTool::selectionBooleanOp(int booleanOp) +{ + KoSelection *selection = koSelection(); + if (!selection) return; + + QList editableShapes = selection->selectedEditableShapes(); + if (editableShapes.isEmpty()) { + return; + } + + QVector srcOutlines; + QPainterPath dstOutline; + KUndo2MagicString actionName = kundo2_noi18n("BUG: boolean action name"); + + // TODO: implement a reference shape selection dialog! + const int referenceShapeIndex = 0; + KoShape *referenceShape = editableShapes[referenceShapeIndex]; + + Q_FOREACH (KoShape *shape, editableShapes) { + srcOutlines << shape->absoluteTransformation(0).map(shape->outline()); + } + + if (booleanOp == BooleanUnion) { + Q_FOREACH (const QPainterPath &path, srcOutlines) { + dstOutline |= path; + } + actionName = kundo2_i18n("Unite Shapes"); + } else if (booleanOp == BooleanIntersection) { + for (int i = 0; i < srcOutlines.size(); i++) { + if (i == 0) { + dstOutline = srcOutlines[i]; + } else { + dstOutline &= srcOutlines[i]; + } + } + + // there is a bug in Qt, sometimes it leaves the resulting + // outline open, so just close it explicitly. + dstOutline.closeSubpath(); + + actionName = kundo2_i18n("Intersect Shapes"); + + } else if (booleanOp == BooleanSubtraction) { + for (int i = 0; i < srcOutlines.size(); i++) { + dstOutline = srcOutlines[referenceShapeIndex]; + if (i != referenceShapeIndex) { + dstOutline -= srcOutlines[i]; + } + } + + actionName = kundo2_i18n("Subtract Shapes"); + } + + KoShape *newShape = 0; + + if (!dstOutline.isEmpty()) { + newShape = KoPathShape::createShapeFromPainterPath(dstOutline); + } + + KUndo2Command *cmd = new KUndo2Command(actionName); + + new KoKeepShapesSelectedCommand(editableShapes, {}, selection, false, cmd); + + QList newSelectedShapes; + + if (newShape) { + newShape->setBackground(referenceShape->background()); + newShape->setStroke(referenceShape->stroke()); + newShape->setZIndex(referenceShape->zIndex()); + + KoShapeContainer *parent = referenceShape->parent(); + canvas()->shapeController()->addShapeDirect(newShape, parent, cmd); + + newSelectedShapes << newShape; + } + + canvas()->shapeController()->removeShapes(editableShapes, cmd); + + new KoKeepShapesSelectedCommand({}, newSelectedShapes, selection, true, cmd); + + canvas()->addCommand(cmd); +} + void DefaultTool::selectionAlign(int _align) { KoShapeAlignCommand::Align align = static_cast(_align); KoSelection *selection = koSelection(); if (!selection) return; QList editableShapes = selection->selectedEditableShapes(); if (editableShapes.isEmpty()) { return; } // TODO add an option to the widget so that one can align to the page // with multiple selected shapes too QRectF bb; // single selected shape is automatically aligned to document rect if (editableShapes.count() == 1) { if (!canvas()->resourceManager()->hasResource(KoCanvasResourceManager::PageSize)) { return; } bb = QRectF(QPointF(0, 0), canvas()->resourceManager()->sizeResource(KoCanvasResourceManager::PageSize)); } else { bb = KoShape::absoluteOutlineRect(editableShapes); } KoShapeAlignCommand *cmd = new KoShapeAlignCommand(editableShapes, align, bb); canvas()->addCommand(cmd); } void DefaultTool::selectionDistribute(int _distribute) { KoShapeDistributeCommand::Distribute distribute = static_cast(_distribute); KoSelection *selection = koSelection(); if (!selection) return; QList editableShapes = selection->selectedEditableShapes(); if (editableShapes.size() < 3) { return; } QRectF bb = KoShape::absoluteOutlineRect(editableShapes); KoShapeDistributeCommand *cmd = new KoShapeDistributeCommand(editableShapes, distribute, bb); canvas()->addCommand(cmd); } void DefaultTool::selectionBringToFront() { selectionReorder(KoShapeReorderCommand::BringToFront); } void DefaultTool::selectionMoveUp() { selectionReorder(KoShapeReorderCommand::RaiseShape); } void DefaultTool::selectionMoveDown() { selectionReorder(KoShapeReorderCommand::LowerShape); } void DefaultTool::selectionSendToBack() { selectionReorder(KoShapeReorderCommand::SendToBack); } void DefaultTool::selectionReorder(KoShapeReorderCommand::MoveShapeType order) { KoSelection *selection = canvas()->selectedShapesProxy()->selection(); if (!selection) { return; } QList selectedShapes = selection->selectedEditableShapes(); if (selectedShapes.isEmpty()) { return; } KUndo2Command *cmd = KoShapeReorderCommand::createCommand(selectedShapes, canvas()->shapeManager(), order); if (cmd) { canvas()->addCommand(cmd); } } QList > DefaultTool::createOptionWidgets() { QList > widgets; m_tabbedOptionWidget = new DefaultToolTabbedWidget(this); if (isActivated()) { m_tabbedOptionWidget->activate(); } widgets.append(m_tabbedOptionWidget); connect(m_tabbedOptionWidget, SIGNAL(sigSwitchModeEditFillGradient(bool)), SLOT(slotActivateEditFillGradient(bool))); connect(m_tabbedOptionWidget, SIGNAL(sigSwitchModeEditStrokeGradient(bool)), SLOT(slotActivateEditStrokeGradient(bool))); return widgets; } void DefaultTool::canvasResourceChanged(int key, const QVariant &res) { if (key == HotPosition) { m_hotPosition = KoFlake::AnchorPosition(res.toInt()); repaintDecorations(); } } KoInteractionStrategy *DefaultTool::createStrategy(KoPointerEvent *event) { KoShapeManager *shapeManager = canvas()->shapeManager(); KoSelection *selection = koSelection(); bool insideSelection = false; KoFlake::SelectionHandle handle = handleAt(event->point, &insideSelection); bool editableShape = !selection->selectedEditableShapes().isEmpty(); const bool selectMultiple = event->modifiers() & Qt::ShiftModifier; const bool selectNextInStack = event->modifiers() & Qt::ControlModifier; const bool avoidSelection = event->modifiers() & Qt::AltModifier; if (selectNextInStack) { // change the hot selection position when middle clicking on a handle KoFlake::AnchorPosition newHotPosition = m_hotPosition; switch (handle) { case KoFlake::TopMiddleHandle: newHotPosition = KoFlake::Top; break; case KoFlake::TopRightHandle: newHotPosition = KoFlake::TopRight; break; case KoFlake::RightMiddleHandle: newHotPosition = KoFlake::Right; break; case KoFlake::BottomRightHandle: newHotPosition = KoFlake::BottomRight; break; case KoFlake::BottomMiddleHandle: newHotPosition = KoFlake::Bottom; break; case KoFlake::BottomLeftHandle: newHotPosition = KoFlake::BottomLeft; break; case KoFlake::LeftMiddleHandle: newHotPosition = KoFlake::Left; break; case KoFlake::TopLeftHandle: newHotPosition = KoFlake::TopLeft; break; case KoFlake::NoHandle: default: // check if we had hit the center point const KoViewConverter *converter = canvas()->viewConverter(); QPointF pt = converter->documentToView(event->point); // TODO: use calculated values instead! QPointF centerPt = converter->documentToView(selection->absolutePosition()); if (kisSquareDistance(pt, centerPt) < HANDLE_DISTANCE_SQ) { newHotPosition = KoFlake::Center; } break; } if (m_hotPosition != newHotPosition) { canvas()->resourceManager()->setResource(HotPosition, newHotPosition); return new NopInteractionStrategy(this); } } if (!avoidSelection && editableShape) { // manipulation of selected shapes goes first if (handle != KoFlake::NoHandle) { // resizing or shearing only with left mouse button if (insideSelection) { return new ShapeResizeStrategy(this, event->point, handle); } if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle || handle == KoFlake::BottomMiddleHandle || handle == KoFlake::LeftMiddleHandle) { return new ShapeShearStrategy(this, event->point, handle); } // rotating is allowed for rigth mouse button too if (handle == KoFlake::TopLeftHandle || handle == KoFlake::TopRightHandle || handle == KoFlake::BottomLeftHandle || handle == KoFlake::BottomRightHandle) { return new ShapeRotateStrategy(this, event->point, event->buttons()); } } if (!selectMultiple && !selectNextInStack) { if (insideSelection) { return new ShapeMoveStrategy(this, event->point); } } } KoShape *shape = shapeManager->shapeAt(event->point, selectNextInStack ? KoFlake::NextUnselected : KoFlake::ShapeOnTop); if (avoidSelection || (!shape && handle == KoFlake::NoHandle)) { if (!selectMultiple) { repaintDecorations(); selection->deselectAll(); } return new SelectionInteractionStrategy(this, event->point, false); } if (selection->isSelected(shape)) { if (selectMultiple) { repaintDecorations(); selection->deselect(shape); } } else if (handle == KoFlake::NoHandle) { // clicked on shape which is not selected repaintDecorations(); if (!selectMultiple) { shapeManager->selection()->deselectAll(); } selection->select(shape); repaintDecorations(); // tablet selection isn't precise and may lead to a move, preventing that if (event->isTabletEvent()) { return new NopInteractionStrategy(this); } return new ShapeMoveStrategy(this, event->point); } return 0; } void DefaultTool::updateActions() { QList editableShapes; if (koSelection()) { editableShapes = koSelection()->selectedEditableShapes(); } const bool hasEditableShapes = !editableShapes.isEmpty(); action("object_order_front")->setEnabled(hasEditableShapes); action("object_order_raise")->setEnabled(hasEditableShapes); action("object_order_lower")->setEnabled(hasEditableShapes); action("object_order_back")->setEnabled(hasEditableShapes); action("object_transform_rotate_90_cw")->setEnabled(hasEditableShapes); action("object_transform_rotate_90_ccw")->setEnabled(hasEditableShapes); action("object_transform_rotate_180")->setEnabled(hasEditableShapes); action("object_transform_mirror_horizontally")->setEnabled(hasEditableShapes); action("object_transform_mirror_vertically")->setEnabled(hasEditableShapes); action("object_transform_reset")->setEnabled(hasEditableShapes); + const bool multipleSelected = editableShapes.size() > 1; + const bool alignmentEnabled = - editableShapes.size() > 1 || + multipleSelected || (!editableShapes.isEmpty() && canvas()->resourceManager()->hasResource(KoCanvasResourceManager::PageSize)); action("object_align_horizontal_left")->setEnabled(alignmentEnabled); action("object_align_horizontal_center")->setEnabled(alignmentEnabled); action("object_align_horizontal_right")->setEnabled(alignmentEnabled); action("object_align_vertical_top")->setEnabled(alignmentEnabled); action("object_align_vertical_center")->setEnabled(alignmentEnabled); action("object_align_vertical_bottom")->setEnabled(alignmentEnabled); - action("object_group")->setEnabled(editableShapes.size() > 1); + + action("object_group")->setEnabled(multipleSelected); + + action("object_unite")->setEnabled(multipleSelected); + action("object_intersect")->setEnabled(multipleSelected); + action("object_subtract")->setEnabled(multipleSelected); const bool distributionEnabled = editableShapes.size() > 2; action("object_distribute_horizontal_left")->setEnabled(distributionEnabled); action("object_distribute_horizontal_center")->setEnabled(distributionEnabled); action("object_distribute_horizontal_right")->setEnabled(distributionEnabled); action("object_distribute_horizontal_gaps")->setEnabled(distributionEnabled); action("object_distribute_vertical_top")->setEnabled(distributionEnabled); action("object_distribute_vertical_center")->setEnabled(distributionEnabled); action("object_distribute_vertical_bottom")->setEnabled(distributionEnabled); action("object_distribute_vertical_gaps")->setEnabled(distributionEnabled); bool hasGroupShape = false; foreach (KoShape *shape, editableShapes) { if (dynamic_cast(shape)) { hasGroupShape = true; break; } } action("object_ungroup")->setEnabled(hasGroupShape); emit selectionChanged(editableShapes.size()); } KoToolSelection *DefaultTool::selection() { return m_selectionHandler; } QMenu* DefaultTool::popupActionsMenu() { if (m_contextMenu) { m_contextMenu->clear(); KActionCollection *collection = this->canvas()->canvasController()->actionCollection(); m_contextMenu->addAction(collection->action("edit_cut")); m_contextMenu->addAction(collection->action("edit_copy")); m_contextMenu->addAction(collection->action("edit_paste")); m_contextMenu->addSeparator(); m_contextMenu->addAction(action("object_order_front")); m_contextMenu->addAction(action("object_order_raise")); m_contextMenu->addAction(action("object_order_lower")); m_contextMenu->addAction(action("object_order_back")); if (action("object_group")->isEnabled() || action("object_ungroup")->isEnabled()) { m_contextMenu->addSeparator(); m_contextMenu->addAction(action("object_group")); m_contextMenu->addAction(action("object_ungroup")); } + m_contextMenu->addSeparator(); + QMenu *transform = m_contextMenu->addMenu(i18n("Transform")); transform->addAction(action("object_transform_rotate_90_cw")); transform->addAction(action("object_transform_rotate_90_ccw")); transform->addAction(action("object_transform_rotate_180")); transform->addSeparator(); transform->addAction(action("object_transform_mirror_horizontally")); transform->addAction(action("object_transform_mirror_vertically")); transform->addSeparator(); transform->addAction(action("object_transform_reset")); + + if (action("object_unite")->isEnabled() || + action("object_intersect")->isEnabled() || + action("object_subtract")->isEnabled()) { + + QMenu *transform = m_contextMenu->addMenu(i18n("Logical Operations")); + transform->addAction(action("object_unite")); + transform->addAction(action("object_intersect")); + transform->addAction(action("object_subtract")); + } } return m_contextMenu.data(); } void DefaultTool::explicitUserStrokeEndRequest() { QList shapes = koSelection()->selectedEditableShapesAndDelegates(); emit activateTemporary(KoToolManager::instance()->preferredToolForSelection(shapes)); } diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.h b/plugins/tools/defaulttool/defaulttool/DefaultTool.h index 4f41fb9cea..bd1b33d036 100644 --- a/plugins/tools/defaulttool/defaulttool/DefaultTool.h +++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.h @@ -1,175 +1,176 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2008 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef DEFAULTTOOL_H #define DEFAULTTOOL_H #include #include #include #include #include #include class QSignalMapper; class KoInteractionStrategy; class KoShapeMoveCommand; class KoSelection; class DefaultToolTabbedWidget; /** * The default tool (associated with the arrow icon) implements the default * interactions you have with flake objects.
* The tool provides scaling, moving, selecting, rotation and soon skewing of * any number of shapes. *

Note that the implementation of those different strategies are delegated * to the InteractionStrategy class and its subclasses. */ class DefaultTool : public KoInteractionTool { Q_OBJECT public: /** * Constructor for basic interaction tool where user actions are translated * and handled by interaction strategies of type KoInteractionStrategy. * @param canvas the canvas this tool will be working for. */ explicit DefaultTool(KoCanvasBase *canvas); ~DefaultTool() override; enum CanvasResource { HotPosition = 1410100299 }; public: bool wantsAutoScroll() const override; void paint(QPainter &painter, const KoViewConverter &converter) override; void repaintDecorations() override; ///reimplemented void copy() const override; ///reimplemented void deleteSelection() override; ///reimplemented bool paste() override; ///reimplemented KoToolSelection *selection() override; QMenu* popupActionsMenu() override; /** * Returns which selection handle is at params point (or NoHandle if none). * @return which selection handle is at params point (or NoHandle if none). * @param point the location (in pt) where we should look for a handle * @param innerHandleMeaning this boolean is altered to true if the point * is inside the selection rectangle and false if it is just outside. * The value of innerHandleMeaning is undefined if the handle location is NoHandle */ KoFlake::SelectionHandle handleAt(const QPointF &point, bool *innerHandleMeaning = 0); public Q_SLOTS: void activate(ToolActivation activation, const QSet &shapes) override; void deactivate() override; private Q_SLOTS: void selectionAlign(int _align); void selectionDistribute(int _distribute); void selectionBringToFront(); void selectionSendToBack(); void selectionMoveUp(); void selectionMoveDown(); void selectionGroup(); void selectionUngroup(); void selectionTransform(int transformAction); + void selectionBooleanOp(int booleanOp); void slotActivateEditFillGradient(bool value); void slotActivateEditStrokeGradient(bool value); /// Update actions on selection change void updateActions(); public: // Events void mousePressEvent(KoPointerEvent *event) override; void mouseMoveEvent(KoPointerEvent *event) override; void mouseReleaseEvent(KoPointerEvent *event) override; void mouseDoubleClickEvent(KoPointerEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void explicitUserStrokeEndRequest() override; protected: QList > createOptionWidgets() override; KoInteractionStrategy *createStrategy(KoPointerEvent *event) override; private: class MoveGradientHandleInteractionFactory; private: void setupActions(); void recalcSelectionBox(KoSelection *selection); void updateCursor(); /// Returns rotation angle of given handle of the current selection qreal rotationOfHandle(KoFlake::SelectionHandle handle, bool useEdgeRotation); void addMappedAction(QSignalMapper *mapper, const QString &actionId, int type); void selectionReorder(KoShapeReorderCommand::MoveShapeType order); bool moveSelection(int direction, Qt::KeyboardModifiers modifiers); /// Returns selection rectangle adjusted by handle proximity threshold QRectF handlesSize(); // convenience method; KoSelection *koSelection(); void canvasResourceChanged(int key, const QVariant &res) override; KoFlake::SelectionHandle m_lastHandle; KoFlake::AnchorPosition m_hotPosition; bool m_mouseWasInsideHandles; QPointF m_selectionBox[8]; QPolygonF m_selectionOutline; QPointF m_lastPoint; // TODO alter these 3 arrays to be static const instead QCursor m_sizeCursors[8]; QCursor m_rotateCursors[8]; QCursor m_shearCursors[8]; qreal m_angle; KoToolSelection *m_selectionHandler; friend class SelectionHandler; KoInteractionStrategy *m_customEventStrategy; QScopedPointer m_contextMenu; DefaultToolTabbedWidget *m_tabbedOptionWidget; }; #endif diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp index 1f88b06b35..7e031832fc 100644 --- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp +++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp @@ -1,516 +1,516 @@ /* This file is part of the KDE project * Copyright (C) 2008 Fela Winkelmolen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KarbonCalligraphyTool.h" #include "KarbonCalligraphicShape.h" #include "KarbonCalligraphyOptionWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef M_PI const qreal M_PI = 3.1415927; using std::pow; using std::sqrt; KarbonCalligraphyTool::KarbonCalligraphyTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_shape(0) , m_angle(0) , m_selectedPath(0) , m_isDrawing(false) , m_speed(0, 0) , m_lastShape(0) { connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), SLOT(updateSelectedPath())); updateSelectedPath(); } KarbonCalligraphyTool::~KarbonCalligraphyTool() { } void KarbonCalligraphyTool::paint(QPainter &painter, const KoViewConverter &converter) { if (m_selectedPath) { painter.save(); painter.setRenderHints(QPainter::Antialiasing, false); painter.setPen(Qt::red); // TODO make configurable QRectF rect = m_selectedPath->boundingRect(); QPointF p1 = converter.documentToView(rect.topLeft()); QPointF p2 = converter.documentToView(rect.bottomRight()); painter.drawRect(QRectF(p1, p2)); painter.restore(); } if (!m_shape) { return; } painter.save(); painter.setTransform(m_shape->absoluteTransformation(&converter) * painter.transform()); KoShapePaintingContext paintContext; //FIXME m_shape->paint(painter, converter, paintContext); painter.restore(); } void KarbonCalligraphyTool::mousePressEvent(KoPointerEvent *event) { if (m_isDrawing) { return; } m_lastPoint = event->point; m_speed = QPointF(0, 0); m_isDrawing = true; m_pointCount = 0; m_shape = new KarbonCalligraphicShape(m_caps); m_shape->setBackground(QSharedPointer(new KoColorBackground(canvas()->resourceManager()->foregroundColor().toQColor()))); //addPoint( event ); } void KarbonCalligraphyTool::mouseMoveEvent(KoPointerEvent *event) { if (!m_isDrawing) { return; } addPoint(event); } void KarbonCalligraphyTool::mouseReleaseEvent(KoPointerEvent *event) { if (!m_isDrawing) { return; } if (m_pointCount == 0) { // handle click: select shape (if any) if (event->point == m_lastPoint) { KoShapeManager *shapeManager = canvas()->shapeManager(); KoShape *selectedShape = shapeManager->shapeAt(event->point); if (selectedShape != 0) { shapeManager->selection()->deselectAll(); shapeManager->selection()->select(selectedShape); } } delete m_shape; m_shape = 0; m_isDrawing = false; return; } else { m_endOfPath = false; // allow last point being added addPoint(event); // add last point m_isDrawing = false; } m_shape->simplifyGuidePath(); - KUndo2Command *cmd = canvas()->shapeController()->addShape(m_shape); + KUndo2Command *cmd = canvas()->shapeController()->addShape(m_shape, 0); if (cmd) { m_lastShape = m_shape; canvas()->addCommand(cmd); canvas()->updateCanvas(m_shape->boundingRect()); } else { // don't leak shape when command could not be created delete m_shape; } m_shape = 0; } void KarbonCalligraphyTool::addPoint(KoPointerEvent *event) { if (m_pointCount == 0) { if (m_usePath && m_selectedPath) { m_selectedPathOutline = m_selectedPath->outline(); } m_pointCount = 1; m_endOfPath = false; m_followPathPosition = 0; m_lastMousePos = event->point; m_lastPoint = calculateNewPoint(event->point, &m_speed); m_deviceSupportsTilt = (event->xTilt() != 0 || event->yTilt() != 0); return; } if (m_endOfPath) { return; } ++m_pointCount; setAngle(event); QPointF newSpeed; QPointF newPoint = calculateNewPoint(event->point, &newSpeed); qreal width = calculateWidth(event->pressure()); qreal angle = calculateAngle(m_speed, newSpeed); // add the previous point m_shape->appendPoint(m_lastPoint, angle, width); m_speed = newSpeed; m_lastPoint = newPoint; canvas()->updateCanvas(m_shape->lastPieceBoundingRect()); if (m_usePath && m_selectedPath) { m_speed = QPointF(0, 0); // following path } } void KarbonCalligraphyTool::setAngle(KoPointerEvent *event) { if (!m_useAngle) { m_angle = (360 - m_customAngle + 90) / 180.0 * M_PI; return; } // setting m_angle to the angle of the device if (event->xTilt() != 0 || event->yTilt() != 0) { m_deviceSupportsTilt = false; } if (m_deviceSupportsTilt) { if (event->xTilt() == 0 && event->yTilt() == 0) { return; // leave as is } qDebug() << "using tilt" << m_angle; if (event->x() == 0) { m_angle = M_PI / 2; return; } // y is inverted in qt painting m_angle = std::atan(static_cast(-event->yTilt() / event->xTilt())) + M_PI / 2; } else { m_angle = event->rotation() + M_PI / 2; qDebug() << "using rotation" << m_angle; } } QPointF KarbonCalligraphyTool::calculateNewPoint(const QPointF &mousePos, QPointF *speed) { if (!m_usePath || !m_selectedPath) { // don't follow path QPointF force = mousePos - m_lastPoint; QPointF dSpeed = force / m_mass; *speed = m_speed * (1.0 - m_drag) + dSpeed; return m_lastPoint + *speed; } QPointF sp = mousePos - m_lastMousePos; m_lastMousePos = mousePos; // follow selected path qreal step = QLineF(QPointF(0, 0), sp).length(); m_followPathPosition += step; qreal t; if (m_followPathPosition >= m_selectedPathOutline.length()) { t = 1.0; m_endOfPath = true; } else { t = m_selectedPathOutline.percentAtLength(m_followPathPosition); } QPointF res = m_selectedPathOutline.pointAtPercent(t) + m_selectedPath->position(); *speed = res - m_lastPoint; return res; } qreal KarbonCalligraphyTool::calculateWidth(qreal pressure) { // calculate the modulo of the speed qreal speed = std::sqrt(pow(m_speed.x(), 2) + pow(m_speed.y(), 2)); qreal thinning = m_thinning * (speed + 1) / 10.0; // can be negative if (thinning > 1) { thinning = 1; } if (!m_usePressure) { pressure = 1.0; } qreal strokeWidth = m_strokeWidth * pressure * (1 - thinning); const qreal MINIMUM_STROKE_WIDTH = 1.0; if (strokeWidth < MINIMUM_STROKE_WIDTH) { strokeWidth = MINIMUM_STROKE_WIDTH; } return strokeWidth; } qreal KarbonCalligraphyTool::calculateAngle(const QPointF &oldSpeed, const QPointF &newSpeed) { // calculate the avarage of the speed (sum of the normalized values) qreal oldLength = QLineF(QPointF(0, 0), oldSpeed).length(); qreal newLength = QLineF(QPointF(0, 0), newSpeed).length(); QPointF oldSpeedNorm = !qFuzzyCompare(oldLength + 1, 1) ? oldSpeed / oldLength : QPointF(0, 0); QPointF newSpeedNorm = !qFuzzyCompare(newLength + 1, 1) ? newSpeed / newLength : QPointF(0, 0); QPointF speed = oldSpeedNorm + newSpeedNorm; // angle solely based on the speed qreal speedAngle = 0; if (speed.x() != 0) { // avoid division by zero speedAngle = std::atan(speed.y() / speed.x()); } else if (speed.y() > 0) { // x == 0 && y != 0 speedAngle = M_PI / 2; } else if (speed.y() < 0) { // x == 0 && y != 0 speedAngle = -M_PI / 2; } if (speed.x() < 0) { speedAngle += M_PI; } // move 90 degrees speedAngle += M_PI / 2; qreal fixedAngle = m_angle; // check if the fixed angle needs to be flipped qreal diff = fixedAngle - speedAngle; while (diff >= M_PI) { // normalize diff between -180 and 180 diff -= 2 * M_PI; } while (diff < -M_PI) { diff += 2 * M_PI; } if (std::abs(diff) > M_PI / 2) { // if absolute value < 90 fixedAngle += M_PI; // += 180 } qreal dAngle = speedAngle - fixedAngle; // normalize dAngle between -90 and +90 while (dAngle >= M_PI / 2) { dAngle -= M_PI; } while (dAngle < -M_PI / 2) { dAngle += M_PI; } qreal angle = fixedAngle + dAngle * (1.0 - m_fixation); return angle; } void KarbonCalligraphyTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); useCursor(Qt::CrossCursor); m_lastShape = 0; } void KarbonCalligraphyTool::deactivate() { if (m_lastShape && canvas()->shapeManager()->shapes().contains(m_lastShape)) { KoSelection *selection = canvas()->shapeManager()->selection(); selection->deselectAll(); selection->select(m_lastShape); } KoToolBase::deactivate(); } QList > KarbonCalligraphyTool::createOptionWidgets() { // if the widget don't exists yet create it QList > widgets; //KoFillConfigWidget *fillWidget = new KoFillConfigWidget(0); //fillWidget->setWindowTitle(i18n("Fill")); //widgets.append(fillWidget); KarbonCalligraphyOptionWidget *widget = new KarbonCalligraphyOptionWidget; connect(widget, SIGNAL(usePathChanged(bool)), this, SLOT(setUsePath(bool))); connect(widget, SIGNAL(usePressureChanged(bool)), this, SLOT(setUsePressure(bool))); connect(widget, SIGNAL(useAngleChanged(bool)), this, SLOT(setUseAngle(bool))); connect(widget, SIGNAL(widthChanged(double)), this, SLOT(setStrokeWidth(double))); connect(widget, SIGNAL(thinningChanged(double)), this, SLOT(setThinning(double))); connect(widget, SIGNAL(angleChanged(int)), this, SLOT(setAngle(int))); connect(widget, SIGNAL(fixationChanged(double)), this, SLOT(setFixation(double))); connect(widget, SIGNAL(capsChanged(double)), this, SLOT(setCaps(double))); connect(widget, SIGNAL(massChanged(double)), this, SLOT(setMass(double))); connect(widget, SIGNAL(dragChanged(double)), this, SLOT(setDrag(double))); connect(this, SIGNAL(pathSelectedChanged(bool)), widget, SLOT(setUsePathEnabled(bool))); // add shortcuts QAction *action = new QAction(i18n("Calligraphy: increase width"), this); action->setShortcut(Qt::Key_Right); connect(action, SIGNAL(triggered()), widget, SLOT(increaseWidth())); addAction("calligraphy_increase_width", action); action = new QAction(i18n("Calligraphy: decrease width"), this); action->setShortcut(Qt::Key_Left); connect(action, SIGNAL(triggered()), widget, SLOT(decreaseWidth())); addAction("calligraphy_decrease_width", action); action = new QAction(i18n("Calligraphy: increase angle"), this); action->setShortcut(Qt::Key_Up); connect(action, SIGNAL(triggered()), widget, SLOT(increaseAngle())); addAction("calligraphy_increase_angle", action); action = new QAction(i18n("Calligraphy: decrease angle"), this); action->setShortcut(Qt::Key_Down); connect(action, SIGNAL(triggered()), widget, SLOT(decreaseAngle())); addAction("calligraphy_decrease_angle", action); // sync all parameters with the loaded profile widget->emitAll(); widget->setObjectName(i18n("Calligraphy")); widget->setWindowTitle(i18n("Calligraphy")); widgets.append(widget); return widgets; } void KarbonCalligraphyTool::setStrokeWidth(double width) { m_strokeWidth = width; } void KarbonCalligraphyTool::setThinning(double thinning) { m_thinning = thinning; } void KarbonCalligraphyTool::setAngle(int angle) { m_customAngle = angle; } void KarbonCalligraphyTool::setFixation(double fixation) { m_fixation = fixation; } void KarbonCalligraphyTool::setMass(double mass) { m_mass = mass * mass + 1; } void KarbonCalligraphyTool::setDrag(double drag) { m_drag = drag; } void KarbonCalligraphyTool::setUsePath(bool usePath) { m_usePath = usePath; } void KarbonCalligraphyTool::setUsePressure(bool usePressure) { m_usePressure = usePressure; } void KarbonCalligraphyTool::setUseAngle(bool useAngle) { m_useAngle = useAngle; } void KarbonCalligraphyTool::setCaps(double caps) { m_caps = caps; } void KarbonCalligraphyTool::updateSelectedPath() { KoPathShape *oldSelectedPath = m_selectedPath; // save old value KoSelection *selection = canvas()->shapeManager()->selection(); if (selection) { // null pointer if it the selection isn't a KoPathShape // or if the selection is empty m_selectedPath = dynamic_cast(selection->firstSelectedShape()); // or if it's a KoPathShape but with no or more than one subpaths if (m_selectedPath && m_selectedPath->subpathCount() != 1) { m_selectedPath = 0; } // or if there ora none or more than 1 shapes selected if (selection->count() != 1) { m_selectedPath = 0; } // emit signal it there wasn't a selected path and now there is // or the other way around if ((m_selectedPath != 0) != (oldSelectedPath != 0)) { emit pathSelectedChanged(m_selectedPath != 0); } } }