diff --git a/karbon/ui/widgets/KarbonCanvas.cpp b/karbon/ui/widgets/KarbonCanvas.cpp index a8eb7a83df8..d8ce5fefdac 100644 --- a/karbon/ui/widgets/KarbonCanvas.cpp +++ b/karbon/ui/widgets/KarbonCanvas.cpp @@ -1,469 +1,469 @@ /* This file is part of the KDE project Copyright (C) 2001-2002 Lennart Kudling Copyright (C) 2001-2002 Rob Buis Copyright (C) 2002-2004, 2006 Laurent Montel Copyright (C) 2002 Benoit Vautrin Copyright (C) 2004 Waldo Bastian Copyright (C) 2004-2005 David Faure Copyright (C) 2005-2006 Tim Beaulen Copyright (C) 2007 Thomas Zander Copyright (C) 2005-2007 Jan Hambrecht Copyright (C) 2006 Peter Simonsson Copyright (C) 2006 C. Boemann Copyright (C) 2006 Thorsten Zachmann Copyright (C) 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 "KarbonCanvas.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KarbonCanvas::KarbonCanvasPrivate { public: KarbonCanvasPrivate() : zoomHandler() , part(0) , showMargins(false) , documentOffset(0, 0) , viewMargin(100) { pixelGrid.setGrid(1.0, 1.0); pixelGrid.setShowGrid(true); } ~KarbonCanvasPrivate() { delete toolProxy; toolProxy = 0; delete shapeManager; } KoShapeManager* shapeManager; KoZoomHandler zoomHandler; KoToolProxy *toolProxy; KarbonDocument *part; QPoint origin; ///< the origin of the document page rect bool showMargins; ///< should page margins be shown QPoint documentOffset; ///< the offset of the virtual canvas from the viewport int viewMargin; ///< the view margin around the document in pixels QRectF documentViewRect; ///< the last calculated document view rect KoGridData pixelGrid; ///< pixel grid data }; KarbonCanvas::KarbonCanvas(KarbonDocument *p) : QWidget() , KoCanvasBase(p), d(new KarbonCanvasPrivate()) { d->part = p; d->toolProxy = new KoToolProxy(this); d->shapeManager = new KoShapeManager(this, d->part->shapes()); connect(d->shapeManager, SIGNAL(selectionChanged()), this, SLOT(updateSizeAndOffset())); setBackgroundRole(QPalette::Base); setAutoFillBackground(true); setBackgroundColor(Qt::white); setMouseTracking(true); setFocusPolicy(Qt::StrongFocus); // allow to receive keyboard input updateSizeAndOffset(); setAttribute(Qt::WA_InputMethodEnabled, true); setAttribute(Qt::WA_AcceptTouchEvents, true); } KarbonCanvas::~KarbonCanvas() { delete d; } KoShapeManager * KarbonCanvas::shapeManager() const { return d->shapeManager; } KoViewConverter * KarbonCanvas::viewConverter() const { return &d->zoomHandler; } KoToolProxy * KarbonCanvas::toolProxy() const { return d->toolProxy; } QWidget * KarbonCanvas::canvasWidget() { return this; } const QWidget * KarbonCanvas::canvasWidget() const { return this; } bool KarbonCanvas::event(QEvent *e) { if(toolProxy()) { if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd) { toolProxy()->touchEvent(dynamic_cast(e)); } toolProxy()->processEvent(e); } return QWidget::event(e); } void KarbonCanvas::paintEvent(QPaintEvent * ev) { QPainter painter(this); painter.translate(-d->documentOffset); QRect clipRect = ev->rect().translated(d->documentOffset); painter.setClipRect(clipRect); painter.translate(d->origin.x(), d->origin.y()); - painter.setPen(Qt::black); + painter.setPen(QPen(Qt::black, 0)); // paint the page rect painter.drawRect(d->zoomHandler.documentToView(QRectF(QPointF(0.0, 0.0), d->part->pageSize()))); // paint the page margins paintMargins(painter, d->zoomHandler); // get the cliprect in document coordinates QRectF updateRect = d->zoomHandler.viewToDocument(widgetToView(clipRect)); // paint the shapes painter.setRenderHint(QPainter::Antialiasing); d->shapeManager->paint(painter, d->zoomHandler, false); // paint the grid and guides painter.setRenderHint(QPainter::Antialiasing, false); // check how big a single point is and paint a pixel grid if big enough const qreal pointSize = d->zoomHandler.zoomItX(1.0); if (pointSize > 10.0 && d->part->gridData().showGrid()) { // set a slightly lighter color than the current grid color d->pixelGrid.setGridColor(d->part->gridData().gridColor().lighter(110)); d->pixelGrid.paintGrid(painter, d->zoomHandler, updateRect); } d->part->gridData().paintGrid(painter, d->zoomHandler, updateRect); d->part->guidesData().paintGuides(painter, d->zoomHandler, updateRect); // paint the tool decorations painter.setRenderHint(QPainter::Antialiasing); d->toolProxy->paint(painter, d->zoomHandler); painter.end(); } void KarbonCanvas::paintMargins(QPainter &painter, const KoViewConverter &converter) { if (! d->showMargins) return; KoPageLayout pl = d->part->pageLayout(); QSizeF pageSize = d->part->pageSize(); QRectF marginRect(pl.leftMargin, pl.topMargin, pageSize.width() - pl.leftMargin - pl.rightMargin, pageSize.height() - pl.topMargin - pl.bottomMargin); - QPen pen(Qt::blue); + QPen pen(Qt::blue, 0); QVector pattern; pattern << 5 << 5; pen.setDashPattern(pattern); painter.setPen(pen); painter.drawRect(converter.documentToView(marginRect)); } void KarbonCanvas::mouseMoveEvent(QMouseEvent *e) { d->toolProxy->mouseMoveEvent(e, d->zoomHandler.viewToDocument(widgetToView(e->pos() + d->documentOffset))); } void KarbonCanvas::mousePressEvent(QMouseEvent *e) { d->toolProxy->mousePressEvent(e, d->zoomHandler.viewToDocument(widgetToView(e->pos() + d->documentOffset))); if (!e->isAccepted() && e->button() == Qt::RightButton) { QList actions = d->toolProxy->popupActionList(); if (!actions.isEmpty()) { QMenu menu(this); foreach(QAction *action, d->toolProxy->popupActionList()) { menu.addAction(action); } menu.exec(e->globalPos()); } } e->setAccepted(true); } void KarbonCanvas::mouseDoubleClickEvent(QMouseEvent *e) { d->toolProxy->mouseDoubleClickEvent(e, d->zoomHandler.viewToDocument(widgetToView(e->pos() + d->documentOffset))); } void KarbonCanvas::mouseReleaseEvent(QMouseEvent *e) { d->toolProxy->mouseReleaseEvent(e, d->zoomHandler.viewToDocument(widgetToView(e->pos() + d->documentOffset))); } void KarbonCanvas::keyReleaseEvent(QKeyEvent *e) { d->toolProxy->keyReleaseEvent(e); } void KarbonCanvas::keyPressEvent(QKeyEvent *e) { d->toolProxy->keyPressEvent(e); if (! e->isAccepted()) { if (e->key() == Qt::Key_Backtab || (e->key() == Qt::Key_Tab && (e->modifiers() & Qt::ShiftModifier))) focusNextPrevChild(false); else if (e->key() == Qt::Key_Tab) focusNextPrevChild(true); } } void KarbonCanvas::tabletEvent(QTabletEvent *e) { d->toolProxy->tabletEvent(e, d->zoomHandler.viewToDocument(widgetToView(e->pos() + d->documentOffset))); } void KarbonCanvas::wheelEvent(QWheelEvent *e) { d->toolProxy->wheelEvent(e, d->zoomHandler.viewToDocument(widgetToView(e->pos() + d->documentOffset))); } QVariant KarbonCanvas::inputMethodQuery(Qt::InputMethodQuery query) const { if (query == Qt::ImMicroFocus) { QRectF rect = (d->toolProxy->inputMethodQuery(query, *(viewConverter())).toRectF()).toRect(); QPointF scroll(canvasController()->scrollBarValue()); rect.translate(documentOrigin() - scroll); return rect.toRect(); } return d->toolProxy->inputMethodQuery(query, *(viewConverter())); } void KarbonCanvas::inputMethodEvent(QInputMethodEvent *event) { d->toolProxy->inputMethodEvent(event); } void KarbonCanvas::resizeEvent(QResizeEvent *) { updateSizeAndOffset(); } void KarbonCanvas::gridSize(qreal *horizontal, qreal *vertical) const { if (horizontal) *horizontal = d->part->gridData().gridX(); if (vertical) *vertical = d->part->gridData().gridY(); } bool KarbonCanvas::snapToGrid() const { return d->part->gridData().snapToGrid(); } void KarbonCanvas::addCommand(KUndo2Command *command) { d->part->addCommand(command); updateSizeAndOffset(); } void KarbonCanvas::updateCanvas(const QRectF& rc) { QRect clipRect(viewToWidget(d->zoomHandler.documentToView(rc).toRect())); clipRect.adjust(-2, -2, 2, 2); // grow for anti-aliasing clipRect.moveTopLeft(clipRect.topLeft() - d->documentOffset); update(clipRect); } void KarbonCanvas::updateSizeAndOffset() { // save the old view rect for comparing QRectF oldDocumentViewRect = d->documentViewRect; d->documentViewRect = documentViewRect(); // check if the view rect has changed and emit signal if it has if (oldDocumentViewRect != d->documentViewRect) { QRectF viewRect = d->zoomHandler.documentToView(d->documentViewRect); KoCanvasController * controller = canvasController(); if (controller) { // tell canvas controller the new document size in pixel controller->updateDocumentSize(viewRect.size().toSize(), true); // make sure the actual selection is visible KoSelection * selection = d->shapeManager->selection(); if (selection->count()) controller->ensureVisible(d->zoomHandler.documentToView(selection->boundingRect())); } } adjustOrigin(); update(); } void KarbonCanvas::adjustOrigin() { // calculate the zoomed document bounding rect QRect documentRect = d->zoomHandler.documentToView(documentViewRect()).toRect(); // save the old origin to see if it has changed QPoint oldOrigin = d->origin; // set the origin to the zoom document rect origin d->origin = -documentRect.topLeft(); // the document bounding rect is always centered on the virtual canvas // if there are margins left around the zoomed document rect then // distribute them evenly on both sides int widthDiff = size().width() - documentRect.width(); if (widthDiff > 0) d->origin.rx() += qRound(0.5 * widthDiff); int heightDiff = size().height() - documentRect.height(); if (heightDiff > 0) d->origin.ry() += qRound(0.5 * heightDiff); // check if the origin has changed and emit signal if it has if (d->origin != oldOrigin) emit documentOriginChanged(d->origin); } void KarbonCanvas::setDocumentOffset(const QPoint &offset) { d->documentOffset = offset; } const QPoint &KarbonCanvas::documentOffset() const { return d->documentOffset; } KarbonDocument *KarbonCanvas::document() const { return d->part; } void KarbonCanvas::enableOutlineMode(bool on) { if (on) new KarbonOutlinePaintingStrategy(d->shapeManager); else { d->shapeManager->setPaintingStrategy(new KoShapeManagerPaintingStrategy(d->shapeManager)); } } QPoint KarbonCanvas::widgetToView(const QPoint& p) const { return p - d->origin; } QRect KarbonCanvas::widgetToView(const QRect& r) const { return r.translated(- d->origin); } QPoint KarbonCanvas::viewToWidget(const QPoint& p) const { return p + d->origin; } QRect KarbonCanvas::viewToWidget(const QRect& r) const { return r.translated(d->origin); } KoUnit KarbonCanvas::unit() const { return d->part->unit(); } QPoint KarbonCanvas::documentOrigin() const { return d->origin; } void KarbonCanvas::setShowPageMargins(bool on) { d->showMargins = on; } void KarbonCanvas::setDocumentViewMargin(int margin) { d->viewMargin = margin; } int KarbonCanvas::documentViewMargin() const { return d->viewMargin; } QRectF KarbonCanvas::documentViewRect() { QRectF bbox = d->part->boundingRect(); d->documentViewRect = bbox.adjusted(-d->viewMargin, -d->viewMargin, d->viewMargin, d->viewMargin); return d->documentViewRect; } void KarbonCanvas::updateInputMethodInfo() { updateMicroFocus(); } KoGuidesData * KarbonCanvas::guidesData() { return &d->part->guidesData(); } void KarbonCanvas::setBackgroundColor(const QColor &color) { QPalette pal = palette(); pal.setColor(QPalette::Normal, backgroundRole(), color); pal.setColor(QPalette::Inactive, backgroundRole(), color); setPalette(pal); } void KarbonCanvas::setCursor(const QCursor &cursor) { QWidget::setCursor(cursor); } diff --git a/libs/basicflakes/tools/KoCreatePathTool.cpp b/libs/basicflakes/tools/KoCreatePathTool.cpp index e8b13da156d..563f71c9d99 100644 --- a/libs/basicflakes/tools/KoCreatePathTool.cpp +++ b/libs/basicflakes/tools/KoCreatePathTool.cpp @@ -1,504 +1,504 @@ /* 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 "KoPointerEvent.h" #include "KoPathShape.h" #include "KoSelection.h" #include "KoDocumentResourceManager.h" #include "KoShapePaintingContext.h" #include "KoShapeStroke.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()) { KoShapeStroke *stroke(createStroke()); if (stroke) { d->shape->setStroke(stroke); } painter.save(); paintPath(*(d->shape), painter, converter); painter.restore(); painter.save(); painter.setTransform(d->shape->absoluteTransformation(&converter) * painter.transform()); KoShape::applyConversion(painter, converter); - painter.setPen(Qt::blue); + painter.setPen(QPen(Qt::blue, 0)); painter.setBrush(Qt::white); //TODO make configurable const bool firstPoint = (d->firstPoint == d->activePoint); if (d->pointIsDragged || firstPoint) { const bool onlyPaintActivePoints = false; KoPathPoint::PointTypes paintFlags = KoPathPoint::ControlPoint2; if (d->activePoint->activeControlPoint1()) paintFlags |= KoPathPoint::ControlPoint1; d->activePoint->paint(painter, d->handleRadius, paintFlags, onlyPaintActivePoints); } // check if we have to color the first point if (d->mouseOverFirstPoint) painter.setBrush(Qt::red); //TODO make configurable else painter.setBrush(Qt::white); //TODO make configurable d->firstPoint->paint(painter, d->handleRadius, KoPathPoint::Node); painter.restore(); } if (d->hoveredPoint) { painter.save(); painter.setTransform(d->hoveredPoint->parent()->absoluteTransformation(&converter), true); KoShape::applyConversion(painter, converter); - painter.setPen(Qt::blue); + painter.setPen(QPen(Qt::blue, 0)); painter.setBrush(Qt::white); //TODO make configurable d->hoveredPoint->paint(painter, d->handleRadius, KoPathPoint::Node); painter.restore(); } 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); KoShapeStroke *stroke = new KoShapeStroke(canvas()->resourceManager()->activeStroke()); 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()->setEditedShape(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()<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, const QSet &) { 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(); } 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()); pathShape->setStroke(createStroke()); 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); 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 QSpinBox(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); d->strokeWidget = new KoStrokeConfigWidget(0); d->strokeWidget->setWindowTitle(i18n("Line")); d->strokeWidget->setCanvas(canvas()); d->strokeWidget->setActive(false); list.append(d->strokeWidget); connect(angleEdit, SIGNAL(valueChanged(int)), this, SLOT(angleDeltaChanged(int))); connect(angleSnap, SIGNAL(stateChanged(int)), this, SLOT(angleSnapChanged(int))); return list; } KoShapeStroke *KoCreatePathTool::createStroke() { Q_D(KoCreatePathTool); KoShapeStroke *stroke = 0; if (d->strokeWidget) { stroke = d->strokeWidget->createShapeStroke(); } return stroke; } //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 68f9be38012..2e96bafeb2b 100644 --- a/libs/basicflakes/tools/KoPencilTool.cpp +++ b/libs/basicflakes/tools/KoPencilTool.cpp @@ -1,549 +1,549 @@ /* This file is part of the KDE project * Copyright (C) 2007,2009,2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPencilTool.h" #include "KoCurveFit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoCreatePathTool_p.h" KoPencilTool::KoPencilTool(KoCanvasBase *canvas) : KoToolBase(canvas), m_mode(ModeCurve), m_optimizeRaw(false) , m_optimizeCurve(false), m_combineAngle(15.0), m_fittingError(5.0) , m_close(false), m_shape(0) , m_existingStartPoint(0), m_existingEndPoint(0), m_hoveredPoint(0) { } KoPencilTool::~KoPencilTool() { } void KoPencilTool::paint(QPainter &painter, const KoViewConverter &converter) { if (m_shape) { painter.save(); painter.setTransform(m_shape->absoluteTransformation(&converter) * painter.transform()); painter.save(); KoShapePaintingContext paintContext; //FIXME m_shape->paint(painter, converter, paintContext); painter.restore(); if (m_shape->stroke()) { painter.save(); m_shape->stroke()->paint(m_shape, painter, converter); painter.restore(); } painter.restore(); } if (m_hoveredPoint) { painter.save(); painter.setTransform(m_hoveredPoint->parent()->absoluteTransformation(&converter), true); KoShape::applyConversion(painter, converter); - painter.setPen(Qt::blue); //TODO make configurable + painter.setPen(QPen(Qt::blue, 0)); //TODO make configurable painter.setBrush(Qt::white); //TODO make configurable m_hoveredPoint->paint(painter, handleRadius(), KoPathPoint::Node); painter.restore(); } } void KoPencilTool::repaintDecorations() { } void KoPencilTool::mousePressEvent(KoPointerEvent *event) { if (! m_shape) { m_shape = new KoPathShape(); m_shape->setShapeId(KoPathShapeId); m_shape->setStroke(createStroke()); m_points.clear(); QPointF point = event->point; m_existingStartPoint = endPointAtPosition(point); if (m_existingStartPoint) point = m_existingStartPoint->parent()->shapeToDocument(m_existingStartPoint->point()); addPoint(point); } } void KoPencilTool::mouseMoveEvent(KoPointerEvent *event) { if (event->buttons() & Qt::LeftButton) addPoint(event->point); KoPathPoint * endPoint = endPointAtPosition(event->point); if (m_hoveredPoint != endPoint) { if (m_hoveredPoint) { QPointF nodePos = m_hoveredPoint->parent()->shapeToDocument(m_hoveredPoint->point()); canvas()->updateCanvas(handlePaintRect(nodePos)); } m_hoveredPoint = endPoint; if (m_hoveredPoint) { QPointF nodePos = m_hoveredPoint->parent()->shapeToDocument(m_hoveredPoint->point()); canvas()->updateCanvas(handlePaintRect(nodePos)); } } } void KoPencilTool::mouseReleaseEvent(KoPointerEvent *event) { if (! m_shape) return; QPointF point = event->point; m_existingEndPoint = endPointAtPosition(point); if (m_existingEndPoint) point = m_existingEndPoint->parent()->shapeToDocument(m_existingEndPoint->point()); addPoint(point); finish(event->modifiers() & Qt::ShiftModifier); m_existingStartPoint = 0; m_existingEndPoint = 0; m_hoveredPoint = 0; // the original path may be different from the one added canvas()->updateCanvas(m_shape->boundingRect()); delete m_shape; m_shape = 0; m_points.clear(); } void KoPencilTool::keyPressEvent(QKeyEvent *event) { if (m_shape) { event->accept(); } else { event->ignore(); } } void KoPencilTool::activate(ToolActivation, const QSet &) { m_points.clear(); m_close = false; useCursor(Qt::ArrowCursor); } void KoPencilTool::deactivate() { m_points.clear(); delete m_shape; m_shape = 0; m_existingStartPoint = 0; m_existingEndPoint = 0; m_hoveredPoint = 0; } void KoPencilTool::addPoint(const QPointF & point) { if (! m_shape) return; // do a moveTo for the first point added if (m_points.empty()) m_shape->moveTo(point); // do not allow coincident points else if (point != m_points.last()) m_shape->lineTo(point); else return; m_points.append(point); canvas()->updateCanvas(m_shape->boundingRect()); } qreal KoPencilTool::lineAngle(const QPointF &p1, const QPointF &p2) { qreal angle = atan2(p2.y() - p1.y(), p2.x() - p1.x()); if (angle < 0.0) angle += 2 * M_PI; return angle * 180.0 / M_PI; } void KoPencilTool::finish(bool closePath) { if (m_points.count() < 2) return; KoPathShape * path = 0; QVector complete; QVector *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 QDoubleSpinBox(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 QDoubleSpinBox(straightBox); combineAngle->setValue(0.50); combineAngle->setMaximum(360.0); combineAngle->setMinimum(0.0); combineAngle->setSingleStep(m_combineAngle); combineAngle->setSuffix(" deg"); // QT5TODO //combineAngle->setLabel(i18n("Combine angle:"), Qt::AlignLeft | Qt::AlignVCenter); straightLayout->addWidget(combineAngle); straightLayout->setContentsMargins(0, 0, 0, 0); stackedWidget->addWidget(rawBox); stackedWidget->addWidget(curveBox); stackedWidget->addWidget(straightBox); layout->addWidget(stackedWidget); layout->addStretch(1); connect(modeBox, SIGNAL(activated(int)), stackedWidget, SLOT(setCurrentIndex(int))); connect(modeBox, SIGNAL(activated(int)), this, SLOT(selectMode(int))); connect(optimizeRaw, SIGNAL(stateChanged(int)), this, SLOT(setOptimize(int))); connect(optimizeCurve, SIGNAL(stateChanged(int)), this, SLOT(setOptimize(int))); connect(fittingError, SIGNAL(valueChanged(double)), this, SLOT(setDelta(double))); connect(combineAngle, SIGNAL(valueChanged(double)), this, SLOT(setDelta(double))); modeBox->setCurrentIndex(m_mode); stackedWidget->setCurrentIndex(m_mode); optionWidget->setObjectName(i18n("Pencil")); optionWidget->setWindowTitle(i18n("Pencil")); widgets.append(optionWidget); m_strokeWidget = new KoStrokeConfigWidget(0); m_strokeWidget->setWindowTitle(i18n("Line")); m_strokeWidget->setCanvas(canvas()); widgets.append(m_strokeWidget); return widgets; } void KoPencilTool::addPathShape(KoPathShape* path, bool closePath) { KoShape * startShape = 0; KoShape * endShape = 0; if (closePath) { path->close(); path->normalize(); } else { path->normalize(); if (connectPaths(path, m_existingStartPoint, m_existingEndPoint)) { if (m_existingStartPoint) startShape = m_existingStartPoint->parent(); if (m_existingEndPoint && m_existingEndPoint != m_existingStartPoint) endShape = m_existingEndPoint->parent(); } } KUndo2Command * cmd = canvas()->shapeController()->addShape(path); if (cmd) { KoSelection *selection = canvas()->shapeManager()->selection(); selection->deselectAll(); selection->select(path); if (startShape) canvas()->shapeController()->removeShape(startShape, cmd); if (endShape && startShape != endShape) canvas()->shapeController()->removeShape(endShape, cmd); canvas()->addCommand(cmd); } else { canvas()->updateCanvas(path->boundingRect()); delete path; } } void KoPencilTool::selectMode(int mode) { m_mode = static_cast(mode); } void KoPencilTool::setOptimize(int state) { if (m_mode == ModeRaw) m_optimizeRaw = state == Qt::Checked ? true : false; else m_optimizeCurve = state == Qt::Checked ? true : false; } void KoPencilTool::setDelta(double delta) { if (m_mode == ModeCurve) m_fittingError = delta; else if (m_mode == ModeStraight) m_combineAngle = delta; } KoShapeStroke* KoPencilTool::createStroke() { KoShapeStroke *stroke = 0; if (m_strokeWidget) { stroke = m_strokeWidget->createShapeStroke(); } return stroke; } KoPathPoint* KoPencilTool::endPointAtPosition(const QPointF &position) { QRectF roi = handleGrabRect(position); QList shapes = canvas()->shapeManager()->shapesAt(roi); KoPathPoint * nearestPoint = 0; qreal minDistance = HUGE_VAL; qreal maxDistance = canvas()->viewConverter()->viewToDocumentX(grabSensitivity()); foreach(KoShape *shape, shapes) { KoPathShape * path = dynamic_cast(shape); if (!path) continue; KoParameterShape *paramShape = dynamic_cast(shape); if (paramShape && paramShape->isParametricShape()) continue; KoPathPoint * p = 0; uint subpathCount = path->subpathCount(); for (uint i = 0; i < subpathCount; ++i) { if (path->isClosedSubpath(i)) continue; p = path->pointByIndex(KoPathPointIndex(i, 0)); // check start of subpath qreal d = squareDistance(position, path->shapeToDocument(p->point())); if (d < minDistance && d < maxDistance) { nearestPoint = p; minDistance = d; } // check end of subpath p = path->pointByIndex(KoPathPointIndex(i, path->subpathPointCount(i) - 1)); d = squareDistance(position, path->shapeToDocument(p->point())); if (d < minDistance && d < maxDistance) { nearestPoint = p; minDistance = d; } } } return nearestPoint; } bool KoPencilTool::connectPaths(KoPathShape *pathShape, KoPathPoint *pointAtStart, KoPathPoint *pointAtEnd) { // at least one point must be valid if (!pointAtStart && !pointAtEnd) return false; // do not allow connecting to the same point twice if (pointAtStart == pointAtEnd) pointAtEnd = 0; // we have hit an existing path point on start/finish // what we now do is: // 1. combine the new created path with the ones we hit on start/finish // 2. merge the endpoints of the corresponding subpaths uint newPointCount = pathShape->subpathPointCount(0); KoPathPointIndex newStartPointIndex(0, 0); KoPathPointIndex newEndPointIndex(0, newPointCount - 1); KoPathPoint * newStartPoint = pathShape->pointByIndex(newStartPointIndex); KoPathPoint * newEndPoint = pathShape->pointByIndex(newEndPointIndex); KoPathShape * startShape = pointAtStart ? pointAtStart->parent() : 0; KoPathShape * endShape = pointAtEnd ? pointAtEnd->parent() : 0; // combine with the path we hit on start KoPathPointIndex startIndex(-1, -1); if (pointAtStart) { startIndex = startShape->pathPointIndex(pointAtStart); pathShape->combine(startShape); pathShape->moveSubpath(0, pathShape->subpathCount() - 1); } // combine with the path we hit on finish KoPathPointIndex endIndex(-1, -1); if (pointAtEnd) { endIndex = endShape->pathPointIndex(pointAtEnd); if (endShape != startShape) { endIndex.first += pathShape->subpathCount(); pathShape->combine(endShape); } } // do we connect twice to a single subpath ? bool connectToSingleSubpath = (startShape == endShape && startIndex.first == endIndex.first); if (startIndex.second == 0 && !connectToSingleSubpath) { pathShape->reverseSubpath(startIndex.first); startIndex.second = pathShape->subpathPointCount(startIndex.first) - 1; } if (endIndex.second > 0 && !connectToSingleSubpath) { pathShape->reverseSubpath(endIndex.first); endIndex.second = 0; } // after combining we have a path where with the subpaths in the following // order: // 1. the subpaths of the pathshape we started the new path at // 2. the subpath we just created // 3. the subpaths of the pathshape we finished the new path at // get the path points we want to merge, as these are not going to // change while merging KoPathPoint * existingStartPoint = pathShape->pointByIndex(startIndex); KoPathPoint * existingEndPoint = pathShape->pointByIndex(endIndex); // merge first two points if (existingStartPoint) { KoPathPointData pd1(pathShape, pathShape->pathPointIndex(existingStartPoint)); KoPathPointData pd2(pathShape, pathShape->pathPointIndex(newStartPoint)); KoPathPointMergeCommand cmd1(pd1, pd2); cmd1.redo(); } // merge last two points if (existingEndPoint) { KoPathPointData pd3(pathShape, pathShape->pathPointIndex(newEndPoint)); KoPathPointData pd4(pathShape, pathShape->pathPointIndex(existingEndPoint)); KoPathPointMergeCommand cmd2(pd3, pd4); cmd2.redo(); } return true; } qreal KoPencilTool::getFittingError(){ return this->m_fittingError; } void KoPencilTool::setFittingError(qreal fittingError){ this->m_fittingError = fittingError; } diff --git a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp index b2dc6f8ff93..fa65b7832a9 100644 --- a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp +++ b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp @@ -1,379 +1,379 @@ /* 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 "KoShape.h" #include "KoShape_p.h" #include "KoShapeFactoryBase.h" // for the SHAPE mimetypes #include "KoShapeRegistry.h" #include "KoShapeController.h" #include "KoShapeManager.h" #include "KoSelection.h" #include "KoCanvasBase.h" #include "KoShapeLayer.h" #include "KoShapePaste.h" #include "KoShapePaintingContext.h" #include "KoToolProxy.h" #include #include #include #include #include #include // ********** Viewport ********** Viewport::Viewport(KoCanvasControllerWidget *parent) : QWidget(parent) , m_draggedShape(0) , m_drawShadow(false) , m_canvas(0) , m_documentOffset(QPoint(0, 0)) , m_margin(0) { setAutoFillBackground(true); setAcceptDrops(true); setMouseTracking(true); m_parent = parent; } void Viewport::setCanvas(QWidget *canvas) { if (m_canvas) { m_canvas->hide(); delete m_canvas; } m_canvas = canvas; if (!canvas) return; m_canvas->setParent(this); m_canvas->show(); if (!m_canvas->minimumSize().isNull()) { m_documentSize = m_canvas->minimumSize(); } resetLayout(); } void Viewport::setDocumentSize(const QSize &size) { m_documentSize = size; resetLayout(); } void Viewport::documentOffsetMoved(const QPoint &pt) { m_documentOffset = pt; resetLayout(); } void Viewport::setDrawShadow(bool drawShadow) { m_drawShadow = drawShadow; } void Viewport::handleDragEnterEvent(QDragEnterEvent *event) { // if not a canvas set then ignore this, makes it possible to assume // we have a canvas in all the support methods. if (!(m_parent->canvas() && m_parent->canvas()->canvasWidget())) return; // only allow dropping when active layer is editable KoSelection *selection = m_parent->canvas()->shapeManager()->selection(); KoShapeLayer *activeLayer = selection->activeLayer(); if (activeLayer && (!activeLayer->isEditable() || activeLayer->isGeometryProtected())) return; const QMimeData *data = event->mimeData(); if (data->hasFormat(SHAPETEMPLATE_MIMETYPE) || data->hasFormat(SHAPEID_MIMETYPE)) { QByteArray itemData; bool isTemplate = true; if (data->hasFormat(SHAPETEMPLATE_MIMETYPE)) itemData = data->data(SHAPETEMPLATE_MIMETYPE); else { isTemplate = false; itemData = data->data(SHAPEID_MIMETYPE); } QDataStream dataStream(&itemData, QIODevice::ReadOnly); QString id; dataStream >> id; QString properties; if (isTemplate) dataStream >> properties; // and finally, there is a point. QPointF offset; dataStream >> offset; // The rest of this method is mostly a copy paste from the KoCreateShapeStrategy // So, lets remove this again when Zagge adds his new class that does this kind of thing. (KoLoadSave) KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(id); if (! factory) { warnFlake << "Application requested a shape that is not registered '" << id << "', Ignoring"; event->ignore(); return; } event->setDropAction(Qt::CopyAction); event->accept(); if (isTemplate) { KoProperties props; props.load(properties); m_draggedShape = factory->createShape(&props, m_parent->canvas()->shapeController()->resourceManager()); } else m_draggedShape = factory->createDefaultShape(m_parent->canvas()->shapeController()->resourceManager()); Q_ASSERT(m_draggedShape); if (!m_draggedShape) return; if (m_draggedShape->shapeId().isEmpty()) m_draggedShape->setShapeId(factory->id()); m_draggedShape->setZIndex(KoShapePrivate::MaxZIndex); m_draggedShape->setAbsolutePosition(correctPosition(event->pos())); m_parent->canvas()->shapeManager()->addShape(m_draggedShape); } else if (data->hasFormat(KoOdf::mimeType(KoOdf::Text))) { KoShapeManager *sm = m_parent->canvas()->shapeManager(); KoShapePaste paste(m_parent->canvas(), sm->selection()->activeLayer()); if (paste.paste(KoOdf::Text, data)) { QList shapes = paste.pastedShapes(); if (shapes.count() == 1) { m_draggedShape = shapes.first(); m_draggedShape->setZIndex(KoShapePrivate::MaxZIndex); event->setDropAction(Qt::CopyAction); } event->accept(); } } else { event->ignore(); } } void Viewport::handleDropEvent(QDropEvent *event) { if (!m_draggedShape) { m_parent->canvas()->toolProxy()->dropEvent(event, correctPosition(event->pos())); return; } repaint(m_draggedShape); m_parent->canvas()->shapeManager()->remove(m_draggedShape); // remove it to not interfere with z-index calc. m_draggedShape->setPosition(QPointF(0, 0)); // always save position. QPointF newPos = correctPosition(event->pos()); m_parent->canvas()->clipToDocument(m_draggedShape, newPos); // ensure the shape is dropped inside the document. m_draggedShape->setAbsolutePosition(newPos); KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape); if (cmd) { m_parent->canvas()->addCommand(cmd); KoSelection *selection = m_parent->canvas()->shapeManager()->selection(); // repaint selection before selecting newly create shape foreach(KoShape * shape, selection->selectedShapes()) shape->update(); selection->deselectAll(); selection->select(m_draggedShape); } else delete m_draggedShape; m_draggedShape = 0; } QPointF Viewport::correctPosition(const QPoint &point) const { QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. QPoint correctedPos(point.x() - canvasWidget->x(), point.y() - canvasWidget->y()); correctedPos += m_documentOffset; return m_parent->canvas()->viewToDocument(correctedPos); } void Viewport::handleDragMoveEvent(QDragMoveEvent *event) { if (!m_draggedShape) { m_parent->canvas()->toolProxy()->dragMoveEvent(event, correctPosition(event->pos())); return; } m_draggedShape->update(); repaint(m_draggedShape); m_draggedShape->setAbsolutePosition(correctPosition(event->pos())); m_draggedShape->update(); repaint(m_draggedShape); } void Viewport::repaint(KoShape *shape) { QRect rect = m_parent->canvas()->viewConverter()->documentToView(shape->boundingRect()).toRect(); QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. rect.moveLeft(rect.left() + canvasWidget->x() - m_documentOffset.x()); rect.moveTop(rect.top() + canvasWidget->y() - m_documentOffset.y()); rect.adjust(-2, -2, 2, 2); // adjust for antialias update(rect); } void Viewport::handleDragLeaveEvent(QDragLeaveEvent *event) { if (m_draggedShape) { repaint(m_draggedShape); m_parent->canvas()->shapeManager()->remove(m_draggedShape); delete m_draggedShape; m_draggedShape = 0; } else { m_parent->canvas()->toolProxy()->dragLeaveEvent(event); } } void Viewport::handlePaintEvent(QPainter &painter, QPaintEvent *event) { Q_UNUSED(event); // Draw the shadow around the canvas. if (m_parent->canvas() && m_parent->canvas()->canvasWidget() && m_drawShadow) { QWidget *canvas = m_parent->canvas()->canvasWidget(); - painter.setPen(Qt::black); + painter.setPen(QPen(Qt::black, 0)); QRect rect(canvas->x(), canvas->y(), canvas->width(), canvas->height()); rect.adjust(-1, -1, 0, 0); painter.drawRect(rect); painter.drawLine(rect.right() + 2, rect.top() + 2, rect.right() + 2, rect.bottom() + 2); painter.drawLine(rect.left() + 2, rect.bottom() + 2, rect.right() + 2, rect.bottom() + 2); } if (m_draggedShape) { const KoViewConverter *vc = m_parent->canvas()->viewConverter(); painter.save(); QWidget *canvasWidget = m_parent->canvas()->canvasWidget(); Q_ASSERT(canvasWidget); // since we should not allow drag if there is not. painter.translate(canvasWidget->x() - m_documentOffset.x(), canvasWidget->y() - m_documentOffset.y()); QPointF offset = vc->documentToView(m_draggedShape->position()); painter.setOpacity(0.6); painter.translate(offset.x(), offset.y()); painter.setRenderHint(QPainter::Antialiasing); KoShapePaintingContext paintContext; //FIXME m_draggedShape->paint(painter, *vc, paintContext); painter.restore(); } } void Viewport::resetLayout() { // Determine the area we have to show QRect viewRect(m_documentOffset, size()); const int viewH = viewRect.height(); const int viewW = viewRect.width(); const int docH = m_documentSize.height(); const int docW = m_documentSize.width(); int moveX = 0; int moveY = 0; int resizeW = viewW; int resizeH = viewH; // debugFlake <<"viewH:" << viewH << endl // << "docH: " << docH << endl // << "viewW: " << viewW << endl // << "docW: " << docW << endl; if (viewH == docH && viewW == docW) { // Do nothing resizeW = docW; resizeH = docH; } else if (viewH > docH && viewW > docW) { // Show entire canvas centered moveX = (viewW - docW) / 2; moveY = (viewH - docH) / 2; resizeW = docW; resizeH = docH; } else if (viewW > docW) { // Center canvas horizontally moveX = (viewW - docW) / 2; resizeW = docW; int marginTop = m_margin - m_documentOffset.y(); int marginBottom = viewH - (m_documentSize.height() - m_documentOffset.y()); if (marginTop > 0) moveY = marginTop; if (marginTop > 0) resizeH = viewH - marginTop; if (marginBottom > 0) resizeH = viewH - marginBottom; } else if (viewH > docH) { // Center canvas vertically moveY = (viewH - docH) / 2; resizeH = docH; int marginLeft = m_margin - m_documentOffset.x(); int marginRight = viewW - (m_documentSize.width() - m_documentOffset.x()); if (marginLeft > 0) moveX = marginLeft; if (marginLeft > 0) resizeW = viewW - marginLeft; if (marginRight > 0) resizeW = viewW - marginRight; } else { // Take care of the margin around the canvas int marginTop = m_margin - m_documentOffset.y(); int marginLeft = m_margin - m_documentOffset.x(); int marginRight = viewW - (m_documentSize.width() - m_documentOffset.x()); int marginBottom = viewH - (m_documentSize.height() - m_documentOffset.y()); if (marginTop > 0) moveY = marginTop; if (marginLeft > 0) moveX = marginLeft; if (marginTop > 0) resizeH = viewH - marginTop; if (marginLeft > 0) resizeW = viewW - marginLeft; if (marginRight > 0) resizeW = viewW - marginRight; if (marginBottom > 0) resizeH = viewH - marginBottom; } if (m_parent->canvasMode() == KoCanvasController::AlignTop) { // have up to m_margin pixels at top. moveY = qMin(m_margin, moveY); } if (m_canvas) { QRect geom; if (m_parent->canvasMode() == KoCanvasController::Infinite) geom = QRect(0, 0, viewW, viewH); else geom = QRect(moveX, moveY, resizeW, resizeH); if (m_canvas->geometry() != geom) { m_canvas->setGeometry(geom); m_canvas->update(); } } if (m_drawShadow) { update(); } emit sizeChanged(); #if 0 debugFlake <<"View port geom:" << geometry(); if (m_canvas) debugFlake <<"Canvas widget geom:" << m_canvas->geometry(); #endif } diff --git a/libs/flake/KoGridData.cpp b/libs/flake/KoGridData.cpp index e9ec520399f..c7d17d576f7 100644 --- a/libs/flake/KoGridData.cpp +++ b/libs/flake/KoGridData.cpp @@ -1,230 +1,230 @@ /* This file is part of the KDE project Copyright (C) 2006 Laurent Montel Copyright (C) 2007, 2009 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoGridData.h" #include "KoViewConverter.h" #include #include #include #include #include #include #include #include #include #define DEFAULT_GRID_SIZE_MM 5.0 class Q_DECL_HIDDEN KoGridData::Private { public: Private() : snapToGrid(false), showGrid(false), paintGridInBackground(false), gridX(MM_TO_POINT(DEFAULT_GRID_SIZE_MM)), gridY(MM_TO_POINT(DEFAULT_GRID_SIZE_MM)), gridColor(Qt::lightGray), toggleGridAction(0) { } ~Private() { delete toggleGridAction; } bool snapToGrid; bool showGrid; bool paintGridInBackground; qreal gridX, gridY; QColor gridColor; KToggleAction *toggleGridAction; }; KoGridData::KoGridData() : d(new Private()) { } KoGridData::~KoGridData() { delete d; } qreal KoGridData::gridX() const { return d->gridX; } qreal KoGridData::gridY() const { return d->gridY; } void KoGridData::setGrid(qreal x, qreal y) { d->gridX = x; d->gridY = y; } bool KoGridData::snapToGrid() const { return d->snapToGrid; } void KoGridData::setSnapToGrid(bool on) { d->snapToGrid = on; } QColor KoGridData::gridColor() const { return d->gridColor; } void KoGridData::setGridColor(const QColor & color) { d->gridColor = color; } bool KoGridData::showGrid() const { if (d->toggleGridAction) return d->toggleGridAction->isChecked(); return d->showGrid; } void KoGridData::setShowGrid(bool showGrid) { if (d->toggleGridAction) d->toggleGridAction->setChecked(showGrid); d->showGrid = showGrid; } bool KoGridData::paintGridInBackground() const { return d->paintGridInBackground; } void KoGridData::setPaintGridInBackground(bool inBackground) { d->paintGridInBackground = inBackground; } void KoGridData::paintGrid(QPainter &painter, const KoViewConverter &converter, const QRectF &area) const { if (! showGrid()) return; - painter.setPen(gridColor()); + painter.setPen(QPen(gridColor(), 0)); qreal x = 0.0; do { painter.drawLine(converter.documentToView(QPointF(x, area.top())), converter.documentToView(QPointF(x, area.bottom()))); x += gridX(); } while (x <= area.right()); x = - gridX(); while (x >= area.left()) { painter.drawLine(converter.documentToView(QPointF(x, area.top())), converter.documentToView(QPointF(x, area.bottom()))); x -= gridX(); }; qreal y = 0.0; do { painter.drawLine(converter.documentToView(QPointF(area.left(), y)), converter.documentToView(QPointF(area.right(), y))); y += gridY(); } while (y <= area.bottom()); y = - gridY(); while (y >= area.top()) { painter.drawLine(converter.documentToView(QPointF(area.left(), y)), converter.documentToView(QPointF(area.right(), y))); y -= gridY(); }; } bool KoGridData::loadOdfSettings(const KoXmlDocument & settingsDoc) { KoOasisSettings settings(settingsDoc); KoOasisSettings::Items viewSettings = settings.itemSet("ooo:view-settings"); if (viewSettings.isNull()) return false; KoOasisSettings::IndexedMap viewMap = viewSettings.indexedMap("Views"); if (viewMap.isNull()) return false; KoOasisSettings::Items firstView = viewMap.entry(0); if (firstView.isNull()) return false; qreal gridX = firstView.parseConfigItemInt("GridFineWidth", DEFAULT_GRID_SIZE_MM); qreal gridY = firstView.parseConfigItemInt("GridFineHeight", DEFAULT_GRID_SIZE_MM); d->gridX = MM_TO_POINT(gridX / 100.0); d->gridY = MM_TO_POINT(gridY / 100.0); d->snapToGrid = firstView.parseConfigItemBool("IsSnapToGrid"); return true; } void KoGridData::saveOdfSettings(KoXmlWriter &settingsWriter) { settingsWriter.startElement("config:config-item"); settingsWriter.addAttribute("config:name", "IsSnapToGrid"); settingsWriter.addAttribute("config:type", "boolean"); settingsWriter.addTextNode(snapToGrid() ? "true" : "false"); settingsWriter.endElement(); if (d->gridX >= 0.0) { settingsWriter.startElement("config:config-item"); settingsWriter.addAttribute("config:name", "GridFineWidth"); settingsWriter.addAttribute("config:type", "int"); settingsWriter.addTextNode(QString::number(static_cast(POINT_TO_MM(d->gridX * 100.0)))); settingsWriter.endElement(); } if (d->gridY >= 0.0) { settingsWriter.startElement("config:config-item"); settingsWriter.addAttribute("config:name", "GridFineHeight"); settingsWriter.addAttribute("config:type", "int"); settingsWriter.addTextNode(QString::number(static_cast(POINT_TO_MM(d->gridY * 100.0)))); settingsWriter.endElement(); } } KToggleAction *KoGridData::gridToggleAction(QWidget* canvas) { if (! d->toggleGridAction) { d->toggleGridAction = new KToggleAction(koIcon("view-grid"), i18n("Show Grid"), 0); d->toggleGridAction->setToolTip(i18n("Shows or hides grid")); d->toggleGridAction->setChecked(d->showGrid); } if (canvas) QObject::connect(d->toggleGridAction, SIGNAL(toggled(bool)), canvas, SLOT(update())); return d->toggleGridAction; } diff --git a/libs/flake/KoGuidesData.cpp b/libs/flake/KoGuidesData.cpp index 218bd80ce99..d7c855ceaee 100644 --- a/libs/flake/KoGuidesData.cpp +++ b/libs/flake/KoGuidesData.cpp @@ -1,200 +1,200 @@ /* This file is part of the KDE project Copyright (C) 2006 Laurent Montel Copyright (C) 2008 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoGuidesData.h" #include "KoViewConverter.h" #include #include #include #include class Q_DECL_HIDDEN KoGuidesData::Private { public: Private() : showGuideLines(true), guidesColor(Qt::lightGray) {} void parseHelpLine(const QString &text) { //V7939H1139 QString str; int newPos = text.length() - 1; //start to element = 1 for (int pos = text.length() - 1; pos >= 0;--pos) { if (text[pos] == 'P') { //point element str = text.mid(pos + 1, (newPos - pos)); /* QStringList listVal = QStringList::split(",", str); int posX = (listVal[0].toInt()/100); int posY = (listVal[1].toInt()/100); point.setAttribute("posX", MM_TO_POINT(posX)); point.setAttribute("posY", MM_TO_POINT(posY)); */ newPos = pos - 1; } else if (text[pos] == 'V') { //vertical element str = text.mid(pos + 1, (newPos - pos)); //debugFlake<<" vertical :"<< str; qreal posX = str.toDouble() / 100.0; vertGuideLines.append(MM_TO_POINT(posX)); newPos = (pos - 1); } else if (text[pos] == 'H') { //horizontal element str = text.mid(pos + 1, (newPos - pos)); qreal posY = str.toDouble() / 100.0; horzGuideLines.append(MM_TO_POINT(posY)); newPos = pos - 1; } } } /// list of positions of horizontal guide lines QList horzGuideLines; /// list of positions of vertical guide lines QList vertGuideLines; bool showGuideLines; QColor guidesColor; }; KoGuidesData::KoGuidesData() : d(new Private()) { } KoGuidesData::~KoGuidesData() { delete d; } void KoGuidesData::setHorizontalGuideLines(const QList &lines) { d->horzGuideLines = lines; } void KoGuidesData::setVerticalGuideLines(const QList &lines) { d->vertGuideLines = lines; } void KoGuidesData::addGuideLine(Qt::Orientation o, qreal pos) { if (o == Qt::Horizontal) { d->horzGuideLines.append(pos); } else { d->vertGuideLines.append(pos); } } bool KoGuidesData::showGuideLines() const { return d->showGuideLines; } void KoGuidesData::setShowGuideLines(bool show) { d->showGuideLines = show; } QList KoGuidesData::horizontalGuideLines() const { return d->horzGuideLines; } QList KoGuidesData::verticalGuideLines() const { return d->vertGuideLines; } void KoGuidesData::paintGuides(QPainter &painter, const KoViewConverter &converter, const QRectF &area) const { if (! showGuideLines()) return; - painter.setPen(d->guidesColor); + painter.setPen(QPen(d->guidesColor, 0)); foreach(qreal guide, d->horzGuideLines) { if (guide < area.top() || guide > area.bottom()) continue; painter.drawLine(converter.documentToView(QPointF(area.left(), guide)), converter.documentToView(QPointF(area.right(), guide))); } foreach(qreal guide, d->vertGuideLines) { if (guide < area.left() || guide > area.right()) continue; painter.drawLine(converter.documentToView(QPointF(guide, area.top())), converter.documentToView(QPointF(guide, area.bottom()))); } } void KoGuidesData::setGuidesColor(const QColor &color) { d->guidesColor = color; } QColor KoGuidesData::guidesColor() const { return d->guidesColor; } bool KoGuidesData::loadOdfSettings(const KoXmlDocument & settingsDoc) { d->vertGuideLines.clear(); d->horzGuideLines.clear(); KoOasisSettings settings(settingsDoc); KoOasisSettings::Items viewSettings = settings.itemSet("ooo:view-settings"); if (viewSettings.isNull()) return false; KoOasisSettings::IndexedMap viewMap = viewSettings.indexedMap("Views"); if (viewMap.isNull()) return false; KoOasisSettings::Items firstView = viewMap.entry(0); if (firstView.isNull()) return false; QString str = firstView.parseConfigItemString("SnapLinesDrawing"); if (!str.isEmpty()) d->parseHelpLine(str); return true; } void KoGuidesData::saveOdfSettings(KoXmlWriter &settingsWriter) { settingsWriter.startElement("config:config-item"); settingsWriter.addAttribute("config:name", "SnapLinesDrawing"); settingsWriter.addAttribute("config:type", "string"); QString lineStr; foreach(qreal h, d->horzGuideLines) { int tmpY = static_cast(POINT_TO_MM(h * 100.0)); lineStr += 'H' + QString::number(tmpY); } foreach(qreal v, d->vertGuideLines) { int tmpX = static_cast(POINT_TO_MM(v * 100.0)); lineStr += 'V' + QString::number(tmpX); } settingsWriter.addTextNode(lineStr); settingsWriter.endElement(); // config:config-item } diff --git a/libs/flake/KoImageData.cpp b/libs/flake/KoImageData.cpp index d380ae38067..5882360d8cd 100644 --- a/libs/flake/KoImageData.cpp +++ b/libs/flake/KoImageData.cpp @@ -1,374 +1,374 @@ /* This file is part of the KDE project * Copyright (C) 2007, 2009 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 2008 C. Boemann * Copyright (C) 2008 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoImageData.h" #include "KoImageData_p.h" #include "KoImageCollection.h" #include #include #include #include #include #include #include #include /// the maximum amount of bytes the image can be while we store it in memory instead of /// spooling it to disk in a temp-file. #define MAX_MEMORY_IMAGESIZE 90000 KoImageData::KoImageData() : d(0) { } KoImageData::KoImageData(const KoImageData &imageData) : KoShapeUserData(), d(imageData.d) { if (d) d->refCount.ref(); } KoImageData::KoImageData(KoImageDataPrivate *priv) : d(priv) { d->refCount.ref(); } KoImageData::~KoImageData() { if (d && !d->refCount.deref()) delete d; } QPixmap KoImageData::pixmap(const QSize &size) { if (!d) return QPixmap(); QSize wantedSize = size; if (! wantedSize.isValid()) { if (d->pixmap.isNull()) // we have a problem, Houston.. wantedSize = QSize(100, 100); else wantedSize = d->pixmap.size(); } if (d->pixmap.isNull() || d->pixmap.size() != wantedSize) { switch (d->dataStoreState) { case KoImageDataPrivate::StateEmpty: { #if 0 // this is not possible as it gets called during the paint method // and will crash. Therefore create a tmp pixmap and return it. d->pixmap = QPixmap(1, 1); QPainter p(&d->pixmap); - p.setPen(QPen(Qt::gray)); + p.setPen(QPen(Qt::gray, 0)); p.drawPoint(0, 0); p.end(); break; #endif QPixmap tmp(1, 1); tmp.fill(Qt::gray); return tmp; } case KoImageDataPrivate::StateNotLoaded: image(); // forces load // fall through case KoImageDataPrivate::StateImageLoaded: case KoImageDataPrivate::StateImageOnly: if (!d->image.isNull()) { // create pixmap from image. // this is the highest quality and lowest memory usage way of doing the conversion. d->pixmap = QPixmap::fromImage(d->image.scaled(wantedSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } } if (d->dataStoreState == KoImageDataPrivate::StateImageLoaded) { if (d->cleanCacheTimer.isActive()) d->cleanCacheTimer.stop(); // schedule an auto-unload of the big QImage in a second. d->cleanCacheTimer.start(); } } return d->pixmap; } bool KoImageData::hasCachedPixmap() const { return d && !d->pixmap.isNull(); } QSizeF KoImageData::imageSize() { if (!d->imageSize.isValid()) { // The imagesize have not yet been calculated if (image().isNull()) // auto loads the image return QSizeF(100, 100); if (d->image.dotsPerMeterX()) d->imageSize.setWidth(DM_TO_POINT(d->image.width() / (qreal) d->image.dotsPerMeterX() * 10.0)); else d->imageSize.setWidth(d->image.width() / 72.0); if (d->image.dotsPerMeterY()) d->imageSize.setHeight(DM_TO_POINT(d->image.height() / (qreal) d->image.dotsPerMeterY() * 10.0)); else d->imageSize.setHeight(d->image.height() / 72.0); } return d->imageSize; } QImage KoImageData::image() const { if (d->dataStoreState == KoImageDataPrivate::StateNotLoaded) { // load image if (d->temporaryFile) { bool r = d->temporaryFile->open(); if (!r) { d->errorCode = OpenFailed; } else if (d->errorCode == Success && !d->image.load(d->temporaryFile->fileName(), d->suffix.toLatin1())) { d->errorCode = OpenFailed; } d->temporaryFile->close(); } else { if (d->errorCode == Success && !d->image.load(d->imageLocation.toLocalFile())) { d->errorCode = OpenFailed; } } if (d->errorCode == Success) { d->dataStoreState = KoImageDataPrivate::StateImageLoaded; } } return d->image; } bool KoImageData::hasCachedImage() const { return d && !d->image.isNull(); } void KoImageData::setImage(const QImage &image, KoImageCollection *collection) { qint64 oldKey = 0; if (d) { oldKey = d->key; } Q_ASSERT(!image.isNull()); if (collection) { // let the collection first check if it already has one. If it doesn't it'll call this method // again and well go to the other clause KoImageData *other = collection->createImageData(image); this->operator=(*other); delete other; } else { if (d == 0) { d = new KoImageDataPrivate(this); d->refCount.ref(); } delete d->temporaryFile; d->temporaryFile = 0; d->clear(); d->suffix = "png"; // good default for non-lossy storage. if (image.byteCount() > MAX_MEMORY_IMAGESIZE) { // store image QBuffer buffer; buffer.open(QIODevice::WriteOnly); if (!image.save(&buffer, d->suffix.toLatin1())) { warnFlake << "Write temporary file failed"; d->errorCode = StorageFailed; delete d->temporaryFile; d->temporaryFile = 0; return; } buffer.close(); buffer.open(QIODevice::ReadOnly); d->copyToTemporary(buffer); } else { d->image = image; d->dataStoreState = KoImageDataPrivate::StateImageOnly; QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); // use .png for images we get as QImage QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(ba); d->key = KoImageDataPrivate::generateKey(md5.result()); } if (oldKey != 0 && d->collection) { d->collection->update(oldKey, d->key); } } } void KoImageData::setImage(const QString &url, KoStore *store, KoImageCollection *collection) { if (collection) { // Let the collection first check if it already has one. If it // doesn't it'll call this method again and we'll go to the // other clause. KoImageData *other = collection->createImageData(url, store); this->operator=(*other); delete other; } else { if (d == 0) { d = new KoImageDataPrivate(this); d->refCount.ref(); } else { d->clear(); } d->setSuffix(url); if (store->open(url)) { struct Finalizer { ~Finalizer() { store->close(); } KoStore *store; }; Finalizer closer; closer.store = store; KoStoreDevice device(store); const bool lossy = url.endsWith(".jpg", Qt::CaseInsensitive) || url.endsWith(".gif", Qt::CaseInsensitive); if (!lossy && device.size() < MAX_MEMORY_IMAGESIZE) { QByteArray data = device.readAll(); if (d->image.loadFromData(data)) { QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(data); qint64 oldKey = d->key; d->key = KoImageDataPrivate::generateKey(md5.result()); if (oldKey != 0 && d->collection) { d->collection->update(oldKey, d->key); } d->dataStoreState = KoImageDataPrivate::StateImageOnly; return; } } if (!device.open(QIODevice::ReadOnly)) { warnFlake << "open file from store " << url << "failed"; d->errorCode = OpenFailed; return; } d->copyToTemporary(device); } else { warnFlake << "Find file in store " << url << "failed"; d->errorCode = OpenFailed; return; } } } void KoImageData::setImage(const QByteArray &imageData, KoImageCollection *collection) { if (collection) { // let the collection first check if it already has one. If it doesn't it'll call this method // again and we'll go to the other clause KoImageData *other = collection->createImageData(imageData); this->operator=(*other); delete other; } else { if (d == 0) { d = new KoImageDataPrivate(this); d->refCount.ref(); } d->suffix = "png"; // good default for non-lossy storage. if (imageData.size() <= MAX_MEMORY_IMAGESIZE) { QImage image; if (!image.loadFromData(imageData)) { // mark the image as invalid, but keep the data in memory // even if Calligra cannot handle the format, the data should // be retained d->errorCode = OpenFailed; } d->image = image; d->dataStoreState = KoImageDataPrivate::StateImageOnly; } if (imageData.size() > MAX_MEMORY_IMAGESIZE || d->errorCode == OpenFailed) { d->image = QImage(); // store image data QBuffer buffer; buffer.setData(imageData); buffer.open(QIODevice::ReadOnly); d->copyToTemporary(buffer); } QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(imageData); qint64 oldKey = d->key; d->key = KoImageDataPrivate::generateKey(md5.result()); if (oldKey != 0 && d->collection) { d->collection->update(oldKey, d->key); } } } bool KoImageData::isValid() const { return d && d->dataStoreState != KoImageDataPrivate::StateEmpty && d->errorCode == Success; } bool KoImageData::operator==(const KoImageData &other) const { return other.d == d; } KoImageData &KoImageData::operator=(const KoImageData &other) { if (other.d) other.d->refCount.ref(); if (d && !d->refCount.deref()) delete d; d = other.d; return *this; } qint64 KoImageData::key() const { return d->key; } QString KoImageData::suffix() const { return d->suffix; } KoImageData::ErrorCode KoImageData::errorCode() const { return d->errorCode; } bool KoImageData::saveData(QIODevice &device) { return d->saveData(device); } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoImageData.cpp" diff --git a/libs/flake/KoPathShape.cpp b/libs/flake/KoPathShape.cpp index cc1e9e067b5..a6fdc008b2d 100644 --- a/libs/flake/KoPathShape.cpp +++ b/libs/flake/KoPathShape.cpp @@ -1,1740 +1,1740 @@ /* This file is part of the KDE project Copyright (C) 2006-2008, 2010-2011 Thorsten Zachmann Copyright (C) 2006-2011 Jan Hambrecht Copyright (C) 2007-2009 Thomas Zander Copyright (C) 2011 Jean-Nicolas Artaud This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPathShape.h" #include "KoPathShape_p.h" #include "KoPathSegment.h" #include "KoOdfWorkaround.h" #include "KoPathPoint.h" #include "KoShapeStrokeModel.h" #include "KoViewConverter.h" #include "KoPathShapeLoader.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoShapeShadow.h" #include "KoShapeBackground.h" #include "KoShapeContainer.h" #include "KoFilterEffectStack.h" #include "KoMarker.h" #include "KoMarkerSharedLoadingData.h" #include "KoShapeStroke.h" #include "KoInsets.h" #include #include #include #include #include #include #include #include #include #include // for qIsNaN static bool qIsNaNPoint(const QPointF &p) { return qIsNaN(p.x()) || qIsNaN(p.y()); } static const qreal DefaultMarkerWidth = 3.0; KoPathShapePrivate::KoPathShapePrivate(KoPathShape *q) : KoTosContainerPrivate(q), fillRule(Qt::OddEvenFill), startMarker(KoMarkerData::MarkerStart), endMarker(KoMarkerData::MarkerEnd) { } QRectF KoPathShapePrivate::handleRect(const QPointF &p, qreal radius) const { return QRectF(p.x() - radius, p.y() - radius, 2*radius, 2*radius); } void KoPathShapePrivate::applyViewboxTransformation(const KoXmlElement &element) { // apply viewbox transformation const QRect viewBox = KoPathShape::loadOdfViewbox(element); if (! viewBox.isEmpty()) { // load the desired size QSizeF size; size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); // load the desired position QPointF pos; pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); // create matrix to transform original path data into desired size and position QTransform viewMatrix; viewMatrix.translate(-viewBox.left(), -viewBox.top()); viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height()); viewMatrix.translate(pos.x(), pos.y()); // transform the path data map(viewMatrix); } } KoPathShape::KoPathShape() :KoTosContainer(*(new KoPathShapePrivate(this))) { } KoPathShape::KoPathShape(KoPathShapePrivate &dd) : KoTosContainer(dd) { } KoPathShape::~KoPathShape() { clear(); } void KoPathShape::saveContourOdf(KoShapeSavingContext &context, const QSizeF &scaleFactor) const { Q_D(const KoPathShape); if (m_subpaths.length() <= 1) { QTransform matrix; matrix.scale(scaleFactor.width(), scaleFactor.height()); QString points; KoSubpath *subPath = m_subpaths.first(); KoSubpath::const_iterator pointIt(subPath->constBegin()); KoPathPoint *currPoint= 0; // iterate over all points for (; pointIt != subPath->constEnd(); ++pointIt) { currPoint = *pointIt; if (currPoint->activeControlPoint1() || currPoint->activeControlPoint2()) { break; } const QPointF p = matrix.map(currPoint->point()); points += QString("%1,%2 ").arg(qRound(1000*p.x())).arg(qRound(1000*p.y())); } if (currPoint && !(currPoint->activeControlPoint1() || currPoint->activeControlPoint2())) { context.xmlWriter().startElement("draw:contour-polygon"); context.xmlWriter().addAttributePt("svg:width", size().width()); context.xmlWriter().addAttributePt("svg:height", size().height()); const QSizeF s(size()); QString viewBox = QString("0 0 %1 %2").arg(qRound(1000*s.width())).arg(qRound(1000*s.height())); context.xmlWriter().addAttribute("svg:viewBox", viewBox); context.xmlWriter().addAttribute("draw:points", points); context.xmlWriter().addAttribute("draw:recreate-on-edit", "true"); context.xmlWriter().endElement(); return; } } // if we get here we couldn't save as polygon - let-s try contour-path context.xmlWriter().startElement("draw:contour-path"); saveOdfAttributes(context, OdfViewbox); context.xmlWriter().addAttribute("svg:d", toString()); context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes()); context.xmlWriter().addAttribute("draw:recreate-on-edit", "true"); context.xmlWriter().endElement(); } void KoPathShape::saveOdf(KoShapeSavingContext & context) const { Q_D(const KoPathShape); context.xmlWriter().startElement("draw:path"); saveOdfAttributes(context, OdfAllAttributes | OdfViewbox); context.xmlWriter().addAttribute("svg:d", toString()); context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes()); saveOdfCommonChildElements(context); saveText(context); context.xmlWriter().endElement(); } bool KoPathShape::loadContourOdf(const KoXmlElement &element, KoShapeLoadingContext &, const QSizeF &scaleFactor) { Q_D(KoPathShape); // first clear the path data from the default path clear(); if (element.localName() == "contour-polygon") { QString points = element.attributeNS(KoXmlNS::draw, "points").simplified(); points.replace(',', ' '); points.remove('\r'); points.remove('\n'); bool firstPoint = true; const QStringList coordinateList = points.split(' '); for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) { QPointF point; point.setX((*it).toDouble()); ++it; point.setY((*it).toDouble()); if (firstPoint) { moveTo(point); firstPoint = false; } else lineTo(point); } close(); } else if (element.localName() == "contour-path") { KoPathShapeLoader loader(this); loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); d->loadNodeTypes(element); } // apply viewbox transformation const QRect viewBox = KoPathShape::loadOdfViewbox(element); if (! viewBox.isEmpty()) { QSizeF size; size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); // create matrix to transform original path data into desired size and position QTransform viewMatrix; viewMatrix.translate(-viewBox.left(), -viewBox.top()); viewMatrix.scale(scaleFactor.width(), scaleFactor.height()); viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height()); // transform the path data d->map(viewMatrix); } setTransformation(QTransform()); return true; } bool KoPathShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { Q_D(KoPathShape); loadOdfAttributes(element, context, OdfMandatories | OdfAdditionalAttributes | OdfCommonChildElements); // first clear the path data from the default path clear(); if (element.localName() == "line") { QPointF start; start.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", ""))); start.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", ""))); QPointF end; end.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", ""))); end.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", ""))); moveTo(start); lineTo(end); } else if (element.localName() == "polyline" || element.localName() == "polygon") { QString points = element.attributeNS(KoXmlNS::draw, "points").simplified(); points.replace(',', ' '); points.remove('\r'); points.remove('\n'); bool firstPoint = true; const QStringList coordinateList = points.split(' '); for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) { QPointF point; point.setX((*it).toDouble()); ++it; point.setY((*it).toDouble()); if (firstPoint) { moveTo(point); firstPoint = false; } else lineTo(point); } if (element.localName() == "polygon") close(); } else { // path loading KoPathShapeLoader loader(this); loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); d->loadNodeTypes(element); } d->applyViewboxTransformation(element); QPointF pos = normalize(); setTransformation(QTransform()); if (element.hasAttributeNS(KoXmlNS::svg, "x") || element.hasAttributeNS(KoXmlNS::svg, "y")) { pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); } setPosition(pos); loadOdfAttributes(element, context, OdfTransformation); // now that the correct transformation is set up // apply that matrix to the path geometry so that // we don't transform the stroke d->map(transformation()); setTransformation(QTransform()); normalize(); loadText(element, context); return true; } QString KoPathShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const { Q_D(const KoPathShape); style.addProperty("svg:fill-rule", d->fillRule == Qt::OddEvenFill ? "evenodd" : "nonzero"); KoShapeStroke *lineBorder = dynamic_cast(stroke()); qreal lineWidth = 0; if (lineBorder) { lineWidth = lineBorder->lineWidth(); } d->startMarker.saveStyle(style, lineWidth, context); d->endMarker.saveStyle(style, lineWidth, context); return KoTosContainer::saveStyle(style, context); } void KoPathShape::loadStyle(const KoXmlElement & element, KoShapeLoadingContext &context) { Q_D(KoPathShape); KoTosContainer::loadStyle(element, context); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); if (styleStack.hasProperty(KoXmlNS::svg, "fill-rule")) { QString rule = styleStack.property(KoXmlNS::svg, "fill-rule"); d->fillRule = (rule == "nonzero") ? Qt::WindingFill : Qt::OddEvenFill; } else { d->fillRule = Qt::WindingFill; #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixMissingFillRule(d->fillRule, context); #endif } KoShapeStroke *lineBorder = dynamic_cast(stroke()); qreal lineWidth = 0; if (lineBorder) { lineWidth = lineBorder->lineWidth(); } d->startMarker.loadOdf(lineWidth, context); d->endMarker.loadOdf(lineWidth, context); } QRect KoPathShape::loadOdfViewbox(const KoXmlElement & element) { QRect viewbox; QString data = element.attributeNS(KoXmlNS::svg, QLatin1String("viewBox")); if (! data.isEmpty()) { data.replace(QLatin1Char(','), QLatin1Char(' ')); const QStringList coordinates = data.simplified().split(QLatin1Char(' '), QString::SkipEmptyParts); if (coordinates.count() == 4) { viewbox.setRect(coordinates.at(0).toInt(), coordinates.at(1).toInt(), coordinates.at(2).toInt(), coordinates.at(3).toInt()); } } return viewbox; } void KoPathShape::clear() { foreach(KoSubpath *subpath, m_subpaths) { foreach(KoPathPoint *point, *subpath) delete point; delete subpath; } m_subpaths.clear(); } void KoPathShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { Q_D(KoPathShape); applyConversion(painter, converter); QPainterPath path(outline()); path.setFillRule(d->fillRule); if (background()) { background()->paint(painter, converter, paintContext, path); } //d->paintDebug(painter); } #ifndef NDEBUG void KoPathShapePrivate::paintDebug(QPainter &painter) { Q_Q(KoPathShape); KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin()); int i = 0; - QPen pen(Qt::black); + QPen pen(Qt::black, 0); painter.save(); painter.setPen(pen); for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { ++i; KoPathPoint *point = (*it); QRectF r(point->point(), QSizeF(5, 5)); r.translate(-2.5, -2.5); - QPen pen(Qt::black); + QPen pen(Qt::black, 0); painter.setPen(pen); if (point->activeControlPoint1() && point->activeControlPoint2()) { QBrush b(Qt::red); painter.setBrush(b); } else if (point->activeControlPoint1()) { QBrush b(Qt::yellow); painter.setBrush(b); } else if (point->activeControlPoint2()) { QBrush b(Qt::darkYellow); painter.setBrush(b); } painter.drawEllipse(r); } } painter.restore(); debugFlake << "nop =" << i; } void KoPathShapePrivate::debugPath() const { Q_Q(const KoPathShape); KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin()); for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { debugFlake << "p:" << (*pathIt) << "," << *it << "," << (*it)->point() << "," << (*it)->properties(); } } } #endif void KoPathShape::paintPoints(QPainter &painter, const KoViewConverter &converter, int handleRadius) { applyConversion(painter, converter); KoSubpathList::const_iterator pathIt(m_subpaths.constBegin()); for (; pathIt != m_subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) (*it)->paint(painter, handleRadius, KoPathPoint::Node); } } QPainterPath KoPathShape::outline() const { QPainterPath path; foreach(KoSubpath * subpath, m_subpaths) { KoPathPoint * lastPoint = subpath->first(); bool activeCP = false; foreach(KoPathPoint * currPoint, *subpath) { KoPathPoint::PointProperties currProperties = currPoint->properties(); if (currPoint == subpath->first()) { if (currProperties & KoPathPoint::StartSubpath) { Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.moveTo(currPoint->point()); } } else if (activeCP && currPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.cubicTo( lastPoint->controlPoint2(), currPoint->controlPoint1(), currPoint->point()); } else if (activeCP || currPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); path.quadTo( activeCP ? lastPoint->controlPoint2() : currPoint->controlPoint1(), currPoint->point()); } else { Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.lineTo(currPoint->point()); } if (currProperties & KoPathPoint::CloseSubpath && currProperties & KoPathPoint::StopSubpath) { // add curve when there is a curve on the way to the first point KoPathPoint * firstPoint = subpath->first(); Q_ASSERT(!qIsNaNPoint(firstPoint->point())); if (currPoint->activeControlPoint2() && firstPoint->activeControlPoint1()) { path.cubicTo( currPoint->controlPoint2(), firstPoint->controlPoint1(), firstPoint->point()); } else if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(currPoint->point())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); path.quadTo( currPoint->activeControlPoint2() ? currPoint->controlPoint2() : firstPoint->controlPoint1(), firstPoint->point()); } path.closeSubpath(); } if (currPoint->activeControlPoint2()) { activeCP = true; } else { activeCP = false; } lastPoint = currPoint; } } return path; } QRectF KoPathShape::boundingRect() const { QTransform transform = absoluteTransformation(0); // calculate the bounding rect of the transformed outline QRectF bb; KoShapeStroke *lineBorder = dynamic_cast(stroke()); QPen pen; if (lineBorder) { pen.setWidthF(lineBorder->lineWidth()); } bb = transform.map(pathStroke(pen)).boundingRect(); if (stroke()) { KoInsets inset; stroke()->strokeInsets(this, inset); // calculate transformed border insets QPointF center = transform.map(QPointF()); QPointF tl = transform.map(QPointF(-inset.left,-inset.top)) - center; QPointF br = transform.map(QPointF(inset.right,inset.bottom)) -center; qreal left = qMin(tl.x(),br.x()); qreal right = qMax(tl.x(),br.x()); qreal top = qMin(tl.y(),br.y()); qreal bottom = qMax(tl.y(),br.y()); bb.adjust(left, top, right, bottom); } if (shadow()) { KoInsets insets; shadow()->insets(insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (filterEffectStack()) { QRectF clipRect = filterEffectStack()->clipRectForBoundingRect(QRectF(QPointF(), size())); bb |= transform.mapRect(clipRect); } return bb; } QSizeF KoPathShape::size() const { // don't call boundingRect here as it uses absoluteTransformation // which itself uses size() -> leads to infinite reccursion return outline().boundingRect().size(); } void KoPathShape::setSize(const QSizeF &newSize) { Q_D(KoPathShape); QTransform matrix(resizeMatrix(newSize)); KoShape::setSize(newSize); d->map(matrix); } QTransform KoPathShape::resizeMatrix(const QSizeF & newSize) const { QSizeF oldSize = size(); if (oldSize.width() == 0.0) { oldSize.setWidth(0.000001); } if (oldSize.height() == 0.0) { oldSize.setHeight(0.000001); } QSizeF sizeNew(newSize); if (sizeNew.width() == 0.0) { sizeNew.setWidth(0.000001); } if (sizeNew.height() == 0.0) { sizeNew.setHeight(0.000001); } return QTransform(sizeNew.width() / oldSize.width(), 0, 0, sizeNew.height() / oldSize.height(), 0, 0); } KoPathPoint * KoPathShape::moveTo(const QPointF &p) { KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StartSubpath | KoPathPoint::StopSubpath); KoSubpath * path = new KoSubpath; path->push_back(point); m_subpaths.push_back(path); return point; } KoPathPoint * KoPathShape::lineTo(const QPointF &p) { Q_D(KoPathShape); if (m_subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); KoPathPoint * lastPoint = m_subpaths.last()->last(); d->updateLast(&lastPoint); m_subpaths.last()->push_back(point); return point; } KoPathPoint * KoPathShape::curveTo(const QPointF &c1, const QPointF &c2, const QPointF &p) { Q_D(KoPathShape); if (m_subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * lastPoint = m_subpaths.last()->last(); d->updateLast(&lastPoint); lastPoint->setControlPoint2(c1); KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); point->setControlPoint1(c2); m_subpaths.last()->push_back(point); return point; } KoPathPoint * KoPathShape::curveTo(const QPointF &c, const QPointF &p) { Q_D(KoPathShape); if (m_subpaths.empty()) moveTo(QPointF(0, 0)); KoPathPoint * lastPoint = m_subpaths.last()->last(); d->updateLast(&lastPoint); lastPoint->setControlPoint2(c); KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); m_subpaths.last()->push_back(point); return point; } KoPathPoint * KoPathShape::arcTo(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle) { if (m_subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * lastPoint = m_subpaths.last()->last(); if (lastPoint->properties() & KoPathPoint::CloseSubpath) { lastPoint = m_subpaths.last()->first(); } QPointF startpoint(lastPoint->point()); KoPathPoint * newEndPoint = lastPoint; QPointF curvePoints[12]; int pointCnt = arcToCurve(rx, ry, startAngle, sweepAngle, startpoint, curvePoints); for (int i = 0; i < pointCnt; i += 3) { newEndPoint = curveTo(curvePoints[i], curvePoints[i+1], curvePoints[i+2]); } return newEndPoint; } int KoPathShape::arcToCurve(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle, const QPointF & offset, QPointF * curvePoints) const { int pointCnt = 0; // check Parameters if (sweepAngle == 0) return pointCnt; if (sweepAngle > 360) sweepAngle = 360; else if (sweepAngle < -360) sweepAngle = - 360; if (rx == 0 || ry == 0) { //TODO } // split angles bigger than 90° so that it gives a good aproximation to the circle qreal parts = ceil(qAbs(sweepAngle / 90.0)); qreal sa_rad = startAngle * M_PI / 180.0; qreal partangle = sweepAngle / parts; qreal endangle = startAngle + partangle; qreal se_rad = endangle * M_PI / 180.0; qreal sinsa = sin(sa_rad); qreal cossa = cos(sa_rad); qreal kappa = 4.0 / 3.0 * tan((se_rad - sa_rad) / 4); // startpoint is at the last point is the path but when it is closed // it is at the first point QPointF startpoint(offset); //center berechnen QPointF center(startpoint - QPointF(cossa * rx, -sinsa * ry)); //debugFlake <<"kappa" << kappa <<"parts" << parts; for (int part = 0; part < parts; ++part) { // start tangent curvePoints[pointCnt++] = QPointF(startpoint - QPointF(sinsa * rx * kappa, cossa * ry * kappa)); qreal sinse = sin(se_rad); qreal cosse = cos(se_rad); // end point QPointF endpoint(center + QPointF(cosse * rx, -sinse * ry)); // end tangent curvePoints[pointCnt++] = QPointF(endpoint - QPointF(-sinse * rx * kappa, -cosse * ry * kappa)); curvePoints[pointCnt++] = endpoint; // set the endpoint as next start point startpoint = endpoint; sinsa = sinse; cossa = cosse; endangle += partangle; se_rad = endangle * M_PI / 180.0; } return pointCnt; } void KoPathShape::close() { Q_D(KoPathShape); if (m_subpaths.empty()) { return; } d->closeSubpath(m_subpaths.last()); } void KoPathShape::closeMerge() { Q_D(KoPathShape); if (m_subpaths.empty()) { return; } d->closeMergeSubpath(m_subpaths.last()); } QPointF KoPathShape::normalize() { Q_D(KoPathShape); QPointF tl(outline().boundingRect().topLeft()); QTransform matrix; matrix.translate(-tl.x(), -tl.y()); d->map(matrix); // keep the top left point of the object applyTransformation(matrix.inverted()); d->shapeChanged(ContentChanged); return tl; } void KoPathShapePrivate::map(const QTransform &matrix) { Q_Q(KoPathShape); KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin()); for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { (*it)->map(matrix); } } } void KoPathShapePrivate::updateLast(KoPathPoint **lastPoint) { Q_Q(KoPathShape); // check if we are about to add a new point to a closed subpath if ((*lastPoint)->properties() & KoPathPoint::StopSubpath && (*lastPoint)->properties() & KoPathPoint::CloseSubpath) { // get the first point of the subpath KoPathPoint *subpathStart = q->m_subpaths.last()->first(); // clone the first point of the subpath... KoPathPoint * newLastPoint = new KoPathPoint(*subpathStart); // ... and make it a normal point newLastPoint->setProperties(KoPathPoint::Normal); // now start a new subpath with the cloned start point KoSubpath *path = new KoSubpath; path->push_back(newLastPoint); q->m_subpaths.push_back(path); *lastPoint = newLastPoint; } else { // the subpath was not closed so the formerly last point // of the subpath is no end point anymore (*lastPoint)->unsetProperty(KoPathPoint::StopSubpath); } (*lastPoint)->unsetProperty(KoPathPoint::CloseSubpath); } QList KoPathShape::pointsAt(const QRectF &r) const { QList result; KoSubpathList::const_iterator pathIt(m_subpaths.constBegin()); for (; pathIt != m_subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { if (r.contains((*it)->point())) result.append(*it); else if ((*it)->activeControlPoint1() && r.contains((*it)->controlPoint1())) result.append(*it); else if ((*it)->activeControlPoint2() && r.contains((*it)->controlPoint2())) result.append(*it); } } return result; } QList KoPathShape::segmentsAt(const QRectF &r) const { QList segments; int subpathCount = m_subpaths.count(); for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) { KoSubpath * subpath = m_subpaths[subpathIndex]; int pointCount = subpath->count(); bool subpathClosed = isClosedSubpath(subpathIndex); for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex) { if (pointIndex == (pointCount - 1) && ! subpathClosed) break; KoPathSegment s(subpath->at(pointIndex), subpath->at((pointIndex + 1) % pointCount)); QRectF controlRect = s.controlPointRect(); if (! r.intersects(controlRect) && ! controlRect.contains(r)) continue; QRectF bound = s.boundingRect(); if (! r.intersects(bound) && ! bound.contains(r)) continue; segments.append(s); } } return segments; } KoPathPointIndex KoPathShape::pathPointIndex(const KoPathPoint *point) const { for (int subpathIndex = 0; subpathIndex < m_subpaths.size(); ++subpathIndex) { KoSubpath * subpath = m_subpaths.at(subpathIndex); for (int pointPos = 0; pointPos < subpath->size(); ++pointPos) { if (subpath->at(pointPos) == point) { return KoPathPointIndex(subpathIndex, pointPos); } } } return KoPathPointIndex(-1, -1); } KoPathPoint * KoPathShape::pointByIndex(const KoPathPointIndex &pointIndex) const { Q_D(const KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) return 0; return subpath->at(pointIndex.second); } KoPathSegment KoPathShape::segmentByIndex(const KoPathPointIndex &pointIndex) const { Q_D(const KoPathShape); KoPathSegment segment(0, 0); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath != 0 && pointIndex.second >= 0 && pointIndex.second < subpath->size()) { KoPathPoint * point = subpath->at(pointIndex.second); int index = pointIndex.second; // check if we have a (closing) segment starting from the last point if ((index == subpath->size() - 1) && point->properties() & KoPathPoint::CloseSubpath) index = 0; else ++index; if (index < subpath->size()) { segment = KoPathSegment(point, subpath->at(index)); } } return segment; } int KoPathShape::pointCount() const { int i = 0; KoSubpathList::const_iterator pathIt(m_subpaths.constBegin()); for (; pathIt != m_subpaths.constEnd(); ++pathIt) { i += (*pathIt)->size(); } return i; } int KoPathShape::subpathCount() const { return m_subpaths.count(); } int KoPathShape::subpathPointCount(int subpathIndex) const { Q_D(const KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return -1; return subpath->size(); } bool KoPathShape::isClosedSubpath(int subpathIndex) const { Q_D(const KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return false; const bool firstClosed = subpath->first()->properties() & KoPathPoint::CloseSubpath; const bool lastClosed = subpath->last()->properties() & KoPathPoint::CloseSubpath; return firstClosed && lastClosed; } bool KoPathShape::insertPoint(KoPathPoint* point, const KoPathPointIndex &pointIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second > subpath->size()) return false; KoPathPoint::PointProperties properties = point->properties(); properties &= ~KoPathPoint::StartSubpath; properties &= ~KoPathPoint::StopSubpath; properties &= ~KoPathPoint::CloseSubpath; // check if new point starts subpath if (pointIndex.second == 0) { properties |= KoPathPoint::StartSubpath; // subpath was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep the path closed properties |= KoPathPoint::CloseSubpath; } // old first point does not start the subpath anymore subpath->first()->unsetProperty(KoPathPoint::StartSubpath); } // check if new point stops subpath else if (pointIndex.second == subpath->size()) { properties |= KoPathPoint::StopSubpath; // subpath was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep the path closed properties = properties | KoPathPoint::CloseSubpath; } // old last point does not end subpath anymore subpath->last()->unsetProperty(KoPathPoint::StopSubpath); } point->setProperties(properties); point->setParent(this); subpath->insert(pointIndex.second , point); return true; } KoPathPoint * KoPathShape::removePoint(const KoPathPointIndex &pointIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) return 0; KoPathPoint * point = subpath->takeAt(pointIndex.second); //don't do anything (not even crash), if there was only one point if (pointCount()==0) { return point; } // check if we removed the first point else if (pointIndex.second == 0) { // first point removed, set new StartSubpath subpath->first()->setProperty(KoPathPoint::StartSubpath); // check if path was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep path closed subpath->first()->setProperty(KoPathPoint::CloseSubpath); } } // check if we removed the last point else if (pointIndex.second == subpath->size()) { // use size as point is already removed // last point removed, set new StopSubpath subpath->last()->setProperty(KoPathPoint::StopSubpath); // check if path was closed if (point->properties() & KoPathPoint::CloseSubpath) { // keep path closed subpath->last()->setProperty(KoPathPoint::CloseSubpath); } } return point; } bool KoPathShape::breakAfter(const KoPathPointIndex &pointIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second > subpath->size() - 2 || isClosedSubpath(pointIndex.first)) return false; KoSubpath * newSubpath = new KoSubpath; int size = subpath->size(); for (int i = pointIndex.second + 1; i < size; ++i) { newSubpath->append(subpath->takeAt(pointIndex.second + 1)); } // now make the first point of the new subpath a starting node newSubpath->first()->setProperty(KoPathPoint::StartSubpath); // the last point of the old subpath is now an ending node subpath->last()->setProperty(KoPathPoint::StopSubpath); // insert the new subpath after the broken one m_subpaths.insert(pointIndex.first + 1, newSubpath); return true; } bool KoPathShape::join(int subpathIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); KoSubpath *nextSubpath = d->subPath(subpathIndex + 1); if (!subpath || !nextSubpath || isClosedSubpath(subpathIndex) || isClosedSubpath(subpathIndex+1)) return false; // the last point of the subpath does not end the subpath anymore subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // the first point of the next subpath does not start a subpath anymore nextSubpath->first()->unsetProperty(KoPathPoint::StartSubpath); // append the second subpath to the first foreach(KoPathPoint * p, *nextSubpath) subpath->append(p); // remove the nextSubpath from path m_subpaths.removeAt(subpathIndex + 1); // delete it as it is no longer possible to use it delete nextSubpath; return true; } bool KoPathShape::moveSubpath(int oldSubpathIndex, int newSubpathIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(oldSubpathIndex); if (subpath == 0 || newSubpathIndex >= m_subpaths.size()) return false; if (oldSubpathIndex == newSubpathIndex) return true; m_subpaths.removeAt(oldSubpathIndex); m_subpaths.insert(newSubpathIndex, subpath); return true; } KoPathPointIndex KoPathShape::openSubpath(const KoPathPointIndex &pointIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() || !isClosedSubpath(pointIndex.first)) return KoPathPointIndex(-1, -1); KoPathPoint * oldStartPoint = subpath->first(); // the old starting node no longer starts the subpath oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); // the old end node no longer closes the subpath subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // reorder the subpath for (int i = 0; i < pointIndex.second; ++i) { subpath->append(subpath->takeFirst()); } // make the first point a start node subpath->first()->setProperty(KoPathPoint::StartSubpath); // make the last point an end node subpath->last()->setProperty(KoPathPoint::StopSubpath); return pathPointIndex(oldStartPoint); } KoPathPointIndex KoPathShape::closeSubpath(const KoPathPointIndex &pointIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() || isClosedSubpath(pointIndex.first)) return KoPathPointIndex(-1, -1); KoPathPoint * oldStartPoint = subpath->first(); // the old starting node no longer starts the subpath oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); // the old end node no longer ends the subpath subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // reorder the subpath for (int i = 0; i < pointIndex.second; ++i) { subpath->append(subpath->takeFirst()); } subpath->first()->setProperty(KoPathPoint::StartSubpath); subpath->last()->setProperty(KoPathPoint::StopSubpath); d->closeSubpath(subpath); return pathPointIndex(oldStartPoint); } bool KoPathShape::reverseSubpath(int subpathIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return false; int size = subpath->size(); for (int i = 0; i < size; ++i) { KoPathPoint *p = subpath->takeAt(i); p->reverse(); subpath->prepend(p); } // adjust the position dependent properties KoPathPoint *first = subpath->first(); KoPathPoint *last = subpath->last(); KoPathPoint::PointProperties firstProps = first->properties(); KoPathPoint::PointProperties lastProps = last->properties(); firstProps |= KoPathPoint::StartSubpath; firstProps &= ~KoPathPoint::StopSubpath; lastProps |= KoPathPoint::StopSubpath; lastProps &= ~KoPathPoint::StartSubpath; if (firstProps & KoPathPoint::CloseSubpath) { firstProps |= KoPathPoint::CloseSubpath; lastProps |= KoPathPoint::CloseSubpath; } first->setProperties(firstProps); last->setProperties(lastProps); return true; } KoSubpath * KoPathShape::removeSubpath(int subpathIndex) { Q_D(KoPathShape); KoSubpath *subpath = d->subPath(subpathIndex); if (subpath != 0) m_subpaths.removeAt(subpathIndex); return subpath; } bool KoPathShape::addSubpath(KoSubpath * subpath, int subpathIndex) { if (subpathIndex < 0 || subpathIndex > m_subpaths.size()) return false; m_subpaths.insert(subpathIndex, subpath); return true; } bool KoPathShape::combine(KoPathShape *path) { if (! path) return false; QTransform pathMatrix = path->absoluteTransformation(0); QTransform myMatrix = absoluteTransformation(0).inverted(); foreach(KoSubpath* subpath, path->m_subpaths) { KoSubpath *newSubpath = new KoSubpath(); foreach(KoPathPoint* point, *subpath) { KoPathPoint *newPoint = new KoPathPoint(*point); newPoint->map(pathMatrix); newPoint->map(myMatrix); newPoint->setParent(this); newSubpath->append(newPoint); } m_subpaths.append(newSubpath); } normalize(); return true; } bool KoPathShape::separate(QList & separatedPaths) { if (! m_subpaths.size()) return false; QTransform myMatrix = absoluteTransformation(0); foreach(KoSubpath* subpath, m_subpaths) { KoPathShape *shape = new KoPathShape(); if (! shape) continue; shape->setStroke(stroke()); shape->setShapeId(shapeId()); KoSubpath *newSubpath = new KoSubpath(); foreach(KoPathPoint* point, *subpath) { KoPathPoint *newPoint = new KoPathPoint(*point); newPoint->map(myMatrix); newSubpath->append(newPoint); } shape->m_subpaths.append(newSubpath); shape->normalize(); separatedPaths.append(shape); } return true; } void KoPathShapePrivate::closeSubpath(KoSubpath *subpath) { if (! subpath) return; subpath->last()->setProperty(KoPathPoint::CloseSubpath); subpath->first()->setProperty(KoPathPoint::CloseSubpath); } void KoPathShapePrivate::closeMergeSubpath(KoSubpath *subpath) { if (! subpath || subpath->size() < 2) return; KoPathPoint * lastPoint = subpath->last(); KoPathPoint * firstPoint = subpath->first(); // check if first and last points are coincident if (lastPoint->point() == firstPoint->point()) { // we are removing the current last point and // reuse its first control point if active firstPoint->setProperty(KoPathPoint::StartSubpath); firstPoint->setProperty(KoPathPoint::CloseSubpath); if (lastPoint->activeControlPoint1()) firstPoint->setControlPoint1(lastPoint->controlPoint1()); // remove last point delete subpath->takeLast(); // the new last point closes the subpath now lastPoint = subpath->last(); lastPoint->setProperty(KoPathPoint::StopSubpath); lastPoint->setProperty(KoPathPoint::CloseSubpath); } else { closeSubpath(subpath); } } KoSubpath *KoPathShapePrivate::subPath(int subpathIndex) const { Q_Q(const KoPathShape); if (subpathIndex < 0 || subpathIndex >= q->m_subpaths.size()) return 0; return q->m_subpaths.at(subpathIndex); } QString KoPathShape::pathShapeId() const { return KoPathShapeId; } QString KoPathShape::toString(const QTransform &matrix) const { QString d; // iterate over all subpaths KoSubpathList::const_iterator pathIt(m_subpaths.constBegin()); for (; pathIt != m_subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator pointIt((*pathIt)->constBegin()); // keep a pointer to the first point of the subpath KoPathPoint *firstPoint(*pointIt); // keep a pointer to the previous point of the subpath KoPathPoint *lastPoint = firstPoint; // keep track if the previous point has an active control point 2 bool activeControlPoint2 = false; // iterate over all points of the current subpath for (; pointIt != (*pathIt)->constEnd(); ++pointIt) { KoPathPoint *currPoint(*pointIt); // first point of subpath ? if (currPoint == firstPoint) { // are we starting a subpath ? if (currPoint->properties() & KoPathPoint::StartSubpath) { const QPointF p = matrix.map(currPoint->point()); d += QString("M%1 %2").arg(p.x()).arg(p.y()); } } // end point of curve segment ? else if (activeControlPoint2 || currPoint->activeControlPoint1()) { // check if we have a cubic or quadratic curve const bool isCubic = activeControlPoint2 && currPoint->activeControlPoint1(); KoPathSegment cubicSeg = isCubic ? KoPathSegment(lastPoint, currPoint) : KoPathSegment(lastPoint, currPoint).toCubic(); const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); const QPointF p = matrix.map(cubicSeg.second()->point()); d += QString("C%1 %2 %3 %4 %5 %6") .arg(cp1.x()).arg(cp1.y()) .arg(cp2.x()).arg(cp2.y()) .arg(p.x()).arg(p.y()); } // end point of line segment! else { const QPointF p = matrix.map(currPoint->point()); d += QString("L%1 %2").arg(p.x()).arg(p.y()); } // last point closes subpath ? if (currPoint->properties() & KoPathPoint::StopSubpath && currPoint->properties() & KoPathPoint::CloseSubpath) { // add curve when there is a curve on the way to the first point if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { // check if we have a cubic or quadratic curve const bool isCubic = currPoint->activeControlPoint2() && firstPoint->activeControlPoint1(); KoPathSegment cubicSeg = isCubic ? KoPathSegment(currPoint, firstPoint) : KoPathSegment(currPoint, firstPoint).toCubic(); const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); const QPointF p = matrix.map(cubicSeg.second()->point()); d += QString("C%1 %2 %3 %4 %5 %6") .arg(cp1.x()).arg(cp1.y()) .arg(cp2.x()).arg(cp2.y()) .arg(p.x()).arg(p.y()); } d += QString("Z"); } activeControlPoint2 = currPoint->activeControlPoint2(); lastPoint = currPoint; } } return d; } char nodeType(const KoPathPoint * point) { if (point->properties() & KoPathPoint::IsSmooth) { return 's'; } else if (point->properties() & KoPathPoint::IsSymmetric) { return 'z'; } else { return 'c'; } } QString KoPathShapePrivate::nodeTypes() const { Q_Q(const KoPathShape); QString types; KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin()); for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { if (it == (*pathIt)->constBegin()) { types.append('c'); } else { types.append(nodeType(*it)); } if ((*it)->properties() & KoPathPoint::StopSubpath && (*it)->properties() & KoPathPoint::CloseSubpath) { KoPathPoint * firstPoint = (*pathIt)->first(); types.append(nodeType(firstPoint)); } } } return types; } void updateNodeType(KoPathPoint * point, const QChar & nodeType) { if (nodeType == 's') { point->setProperty(KoPathPoint::IsSmooth); } else if (nodeType == 'z') { point->setProperty(KoPathPoint::IsSymmetric); } } void KoPathShapePrivate::loadNodeTypes(const KoXmlElement &element) { Q_Q(KoPathShape); if (element.hasAttributeNS(KoXmlNS::calligra, "nodeTypes")) { QString nodeTypes = element.attributeNS(KoXmlNS::calligra, "nodeTypes"); QString::const_iterator nIt(nodeTypes.constBegin()); KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin()); for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it, nIt++) { // be sure not to crash if there are not enough nodes in nodeTypes if (nIt == nodeTypes.constEnd()) { warnFlake << "not enough nodes in calligra:nodeTypes"; return; } // the first node is always of type 'c' if (it != (*pathIt)->constBegin()) { updateNodeType(*it, *nIt); } if ((*it)->properties() & KoPathPoint::StopSubpath && (*it)->properties() & KoPathPoint::CloseSubpath) { ++nIt; updateNodeType((*pathIt)->first(), *nIt); } } } } } Qt::FillRule KoPathShape::fillRule() const { Q_D(const KoPathShape); return d->fillRule; } void KoPathShape::setFillRule(Qt::FillRule fillRule) { Q_D(KoPathShape); d->fillRule = fillRule; } KoPathShape * KoPathShape::createShapeFromPainterPath(const QPainterPath &path) { KoPathShape * shape = new KoPathShape(); int elementCount = path.elementCount(); for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = path.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: shape->moveTo(QPointF(element.x, element.y)); break; case QPainterPath::LineToElement: shape->lineTo(QPointF(element.x, element.y)); break; case QPainterPath::CurveToElement: shape->curveTo(QPointF(element.x, element.y), QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y), QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y)); break; default: continue; } } shape->normalize(); return shape; } bool KoPathShape::hitTest(const QPointF &position) const { if (parent() && parent()->isClipped(this) && ! parent()->hitTest(position)) return false; QPointF point = absoluteTransformation(0).inverted().map(position); const QPainterPath outlinePath = outline(); if (stroke()) { KoInsets insets; stroke()->strokeInsets(this, insets); QRectF roi(QPointF(-insets.left, -insets.top), QPointF(insets.right, insets.bottom)); roi.moveCenter(point); if (outlinePath.intersects(roi) || outlinePath.contains(roi)) return true; } else { if (outlinePath.contains(point)) return true; } // if there is no shadow we can as well just leave if (! shadow()) return false; // the shadow has an offset to the shape, so we simply // check if the position minus the shadow offset hits the shape point = absoluteTransformation(0).inverted().map(position - shadow()->offset()); return outlinePath.contains(point); } void KoPathShape::setMarker(const KoMarkerData &markerData) { Q_D(KoPathShape); if (markerData.position() == KoMarkerData::MarkerStart) { d->startMarker = markerData; } else { d->endMarker = markerData; } } void KoPathShape::setMarker(KoMarker *marker, KoMarkerData::MarkerPosition position) { Q_D(KoPathShape); if (position == KoMarkerData::MarkerStart) { if (!d->startMarker.marker()) { d->startMarker.setWidth(MM_TO_POINT(DefaultMarkerWidth), qreal(0.0)); } d->startMarker.setMarker(marker); } else { if (!d->endMarker.marker()) { d->endMarker.setWidth(MM_TO_POINT(DefaultMarkerWidth), qreal(0.0)); } d->endMarker.setMarker(marker); } } KoMarker *KoPathShape::marker(KoMarkerData::MarkerPosition position) const { Q_D(const KoPathShape); if (position == KoMarkerData::MarkerStart) { return d->startMarker.marker(); } else { return d->endMarker.marker(); } } KoMarkerData KoPathShape::markerData(KoMarkerData::MarkerPosition position) const { Q_D(const KoPathShape); if (position == KoMarkerData::MarkerStart) { return d->startMarker; } else { return d->endMarker; } } QPainterPath KoPathShape::pathStroke(const QPen &pen) const { if (m_subpaths.isEmpty()) { return QPainterPath(); } QPainterPath pathOutline; QPainterPathStroker stroker; stroker.setWidth(0); stroker.setJoinStyle(Qt::MiterJoin); QPair firstSegments; QPair lastSegments; KoPathPoint *firstPoint = 0; KoPathPoint *lastPoint = 0; KoPathPoint *secondPoint = 0; KoPathPoint *preLastPoint = 0; KoSubpath *firstSubpath = m_subpaths.first(); bool twoPointPath = subpathPointCount(0) == 2; bool closedPath = isClosedSubpath(0); /* * The geometry is horizontally centered. It is vertically positioned relative to an offset value which * is specified by a draw:marker-start-center attribute for markers referenced by a * draw:marker-start attribute, and by the draw:marker-end-center attribute for markers * referenced by a draw:marker-end attribute. The attribute value true defines an offset of 0.5 * and the attribute value false defines an offset of 0.3, which is also the default value. The offset * specifies the marker's vertical position in a range from 0.0 to 1.0, where the value 0.0 means the * geometry's bottom bound is aligned to the X axis of the local coordinate system of the marker * geometry, and where the value 1.0 means the top bound to be aligned to the X axis of the local * coordinate system of the marker geometry. * * The shorten factor to use results of the 0.3 which means we need to start at 0.7 * height of the marker */ static const qreal shortenFactor = 0.7; KoMarkerData mdStart = markerData(KoMarkerData::MarkerStart); KoMarkerData mdEnd = markerData(KoMarkerData::MarkerEnd); if (mdStart.marker() && !closedPath) { QPainterPath markerPath = mdStart.marker()->path(mdStart.width(pen.widthF())); KoPathSegment firstSegment = segmentByIndex(KoPathPointIndex(0, 0)); if (firstSegment.isValid()) { QRectF pathBoundingRect = markerPath.boundingRect(); qreal shortenLength = pathBoundingRect.height() * shortenFactor; debugFlake << "length" << firstSegment.length() << shortenLength; qreal t = firstSegment.paramAtLength(shortenLength); firstSegments = firstSegment.splitAt(t); // transform the marker so that it goes from the first point of the first segment to the second point of the first segment QPointF startPoint = firstSegments.first.first()->point(); QPointF newStartPoint = firstSegments.first.second()->point(); QLineF vector(newStartPoint, startPoint); qreal angle = -vector.angle() + 90; QTransform transform; transform.translate(startPoint.x(), startPoint.y()) .rotate(angle) .translate(-pathBoundingRect.width() / 2.0, 0); markerPath = transform.map(markerPath); QPainterPath startOutline = stroker.createStroke(markerPath); startOutline = startOutline.united(markerPath); pathOutline.addPath(startOutline); firstPoint = firstSubpath->first(); if (firstPoint->properties() & KoPathPoint::StartSubpath) { firstSegments.second.first()->setProperty(KoPathPoint::StartSubpath); } debugFlake << "start marker" << angle << startPoint << newStartPoint << firstPoint->point(); if (!twoPointPath) { if (firstSegment.second()->activeControlPoint2()) { firstSegments.second.second()->setControlPoint2(firstSegment.second()->controlPoint2()); } secondPoint = (*firstSubpath)[1]; } else if (!mdEnd.marker()) { // in case it is two point path with no end marker we need to modify the last point via the secondPoint secondPoint = (*firstSubpath)[1]; } } } if (mdEnd.marker() && !closedPath) { QPainterPath markerPath = mdEnd.marker()->path(mdEnd.width(pen.widthF())); KoPathSegment lastSegment; /* * if the path consits only of 2 point and it it has an marker on both ends * use the firstSegments.second as that is the path that needs to be shortened */ if (twoPointPath && firstPoint) { lastSegment = firstSegments.second; } else { lastSegment = segmentByIndex(KoPathPointIndex(0, firstSubpath->count() - 2)); } if (lastSegment.isValid()) { QRectF pathBoundingRect = markerPath.boundingRect(); qreal shortenLength = lastSegment.length() - pathBoundingRect.height() * shortenFactor; qreal t = lastSegment.paramAtLength(shortenLength); lastSegments = lastSegment.splitAt(t); // transform the marker so that it goes from the last point of the first segment to the previous point of the last segment QPointF startPoint = lastSegments.second.second()->point(); QPointF newStartPoint = lastSegments.second.first()->point(); QLineF vector(newStartPoint, startPoint); qreal angle = -vector.angle() + 90; QTransform transform; transform.translate(startPoint.x(), startPoint.y()).rotate(angle).translate(-pathBoundingRect.width() / 2.0, 0); markerPath = transform.map(markerPath); QPainterPath endOutline = stroker.createStroke(markerPath); endOutline = endOutline.united(markerPath); pathOutline.addPath(endOutline); lastPoint = firstSubpath->last(); debugFlake << "end marker" << angle << startPoint << newStartPoint << lastPoint->point(); if (twoPointPath) { if (firstSegments.second.isValid()) { if (lastSegments.first.first()->activeControlPoint2()) { firstSegments.second.first()->setControlPoint2(lastSegments.first.first()->controlPoint2()); } } else { // if there is no start marker we need the first point needs to be changed via the preLastPoint // the flag needs to be set so the moveTo is done lastSegments.first.first()->setProperty(KoPathPoint::StartSubpath); preLastPoint = (*firstSubpath)[firstSubpath->count()-2]; } } else { if (lastSegment.first()->activeControlPoint1()) { lastSegments.first.first()->setControlPoint1(lastSegment.first()->controlPoint1()); } preLastPoint = (*firstSubpath)[firstSubpath->count()-2]; } } } stroker.setWidth(pen.widthF()); stroker.setJoinStyle(pen.joinStyle()); stroker.setMiterLimit(pen.miterLimit()); stroker.setCapStyle(pen.capStyle()); stroker.setDashOffset(pen.dashOffset()); stroker.setDashPattern(pen.dashPattern()); // shortent the path to make it look nice // replace the point temporarily in case there is an arrow // BE AWARE: this changes the content of the path so that outline give the correct values. if (firstPoint) { firstSubpath->first() = firstSegments.second.first(); if (secondPoint) { (*firstSubpath)[1] = firstSegments.second.second(); } } if (lastPoint) { if (preLastPoint) { (*firstSubpath)[firstSubpath->count() - 2] = lastSegments.first.first(); } firstSubpath->last() = lastSegments.first.second(); } QPainterPath path = stroker.createStroke(outline()); if (firstPoint) { firstSubpath->first() = firstPoint; if (secondPoint) { (*firstSubpath)[1] = secondPoint; } } if (lastPoint) { if (preLastPoint) { (*firstSubpath)[firstSubpath->count() - 2] = preLastPoint; } firstSubpath->last() = lastPoint; } pathOutline.addPath(path); pathOutline.setFillRule(Qt::WindingFill); return pathOutline; } diff --git a/libs/flake/KoRTree.h b/libs/flake/KoRTree.h index bdf8add1f30..bcc8fa4ba5b 100644 --- a/libs/flake/KoRTree.h +++ b/libs/flake/KoRTree.h @@ -1,1090 +1,1090 @@ /* This file is part of the KDE project Copyright (c) 2006 Thorsten Zachmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Based on code from Wolfgang Baer - WBaer@gmx.de */ #ifndef KORTREE_H #define KORTREE_H #include #include #include #include #include #include #include #include // #define CALLIGRA_RTREE_DEBUG #ifdef CALLIGRA_RTREE_DEBUG #include #endif /** * @brief The KoRTree class is a template class that provides a R-tree. * * This class implements a R-tree as described in * "R-TREES. A DYNAMIC INDEX STRUCTURE FOR SPATIAL SEARCHING" by Antomn Guttman * * It only supports 2 dimensional bounding boxes which are repesented by a QRectF. * For node splitting the Quadratic-Cost Algorithm is used as descibed by Guttman. */ template class KoRTree { public: /** * @brief Constructor * * @param capacity the capacity a node can take * @param minimum the minimum filling of a node max 0.5 * capacity */ KoRTree(int capacity, int minimum); /** * @brief Destructor */ virtual ~KoRTree(); /** * @brief Insert data item into the tree * * This will insert a data item into the tree. If necessary the tree will * adjust itself. * * @param data * @param bb */ virtual void insert(const QRectF& bb, const T& data); /** * @brief Remove a data item from the tree * * This removed a data item from the tree. If necessary the tree will * adjust itself. * * @param data */ void remove(const T& data); /** * @brief Find all data items which intersects rect * The items are sorted by insertion time in ascending order. * * @param rect where the objects have to be in * * @return objects intersecting the rect */ virtual QList intersects(const QRectF& rect) const; /** * @brief Find all data item which contain the point * The items are sorted by insertion time in ascending order. * * @param point which should be contained in the objects * * @return objects which contain the point */ QList contains(const QPointF &point) const; /** * @brief Find all data rectangles * The order is NOT guaranteed to be the same as that used by values(). * * @return a list containing all the data rectangles used in the tree */ QList keys() const; /** * @brief Find all data items * The order is NOT guaranteed to be the same as that used by keys(). * * @return a list containing all the data used in the tree */ QList values() const; virtual void clear() { delete m_root; m_root = createLeafNode(m_capacity + 1, 0, 0); m_leafMap.clear(); } #ifdef CALLIGRA_RTREE_DEBUG /** * @brief Paint the tree * * @param p painter which should be used for painting */ void paint(QPainter & p) const; /** * @brief Print the tree using qdebug */ void debug() const; #endif protected: class NonLeafNode; class LeafNode; class Node { public: #ifdef CALLIGRA_RTREE_DEBUG static int nodeIdCnt; #endif Node(int capacity, int level, Node * parent); virtual ~Node() {} virtual void remove(int index); // move node between nodes of the same type from node virtual void move(Node * node, int index) = 0; virtual LeafNode * chooseLeaf(const QRectF& bb) = 0; virtual NonLeafNode * chooseNode(const QRectF& bb, int level) = 0; virtual void intersects(const QRectF& rect, QMap & result) const = 0; virtual void contains(const QPointF & point, QMap & result) const = 0; virtual void keys(QList & result) const = 0; virtual void values(QMap & result) const = 0; virtual Node * parent() const { return m_parent; } virtual void setParent(Node * parent) { m_parent = parent; } virtual int childCount() const { return m_counter; } virtual const QRectF& boundingBox() const { return m_boundingBox; } virtual void updateBoundingBox(); virtual const QRectF& childBoundingBox(int index) const { return m_childBoundingBox[index]; } virtual void setChildBoundingBox(int index, const QRectF& rect) { m_childBoundingBox[index] = rect; } virtual void clear(); virtual bool isRoot() const { return m_parent == 0; } virtual bool isLeaf() const { return false; } virtual int place() const { return m_place; } virtual void setPlace(int place) { m_place = place; } virtual int level() const { return m_level; } virtual void setLevel(int level) { m_level = level; } #ifdef CALLIGRA_RTREE_DEBUG virtual int nodeId() const { return m_nodeId; } virtual void paint(QPainter & p, int level) const = 0; virtual void debug(QString line) const = 0; protected: #define levelColorSize 5 static QColor levelColor[levelColorSize]; virtual void paintRect(QPainter & p, int level) const; #endif protected: Node * m_parent; QRectF m_boundingBox; QVector m_childBoundingBox; int m_counter; // the position in the parent int m_place; #ifdef CALLIGRA_RTREE_DEBUG int m_nodeId; #endif int m_level; }; class NonLeafNode : virtual public Node { public: NonLeafNode(int capacity, int level, Node * parent); virtual ~NonLeafNode(); virtual void insert(const QRectF& bb, Node * data); virtual void remove(int index); virtual void move(Node * node, int index); virtual LeafNode * chooseLeaf(const QRectF& bb); virtual NonLeafNode * chooseNode(const QRectF& bb, int level); virtual void intersects(const QRectF& rect, QMap & result) const; virtual void contains(const QPointF & point, QMap & result) const; virtual void keys(QList & result) const; virtual void values(QMap & result) const; virtual Node * getNode(int index) const; #ifdef CALLIGRA_RTREE_DEBUG virtual void paint(QPainter & p, int level) const; virtual void debug(QString line) const; #endif protected: virtual Node * getLeastEnlargement(const QRectF& bb) const; QVector m_childs; }; class LeafNode : virtual public Node { public: static int dataIdCounter; LeafNode(int capacity, int level, Node * parent); virtual ~LeafNode(); virtual void insert(const QRectF& bb, const T& data, int id); virtual void remove(int index); virtual void remove(const T& data); virtual void move(Node * node, int index); virtual LeafNode * chooseLeaf(const QRectF& bb); virtual NonLeafNode * chooseNode(const QRectF& bb, int level); virtual void intersects(const QRectF& rect, QMap & result) const; virtual void contains(const QPointF & point, QMap & result) const; virtual void keys(QList & result) const; virtual void values(QMap & result) const; virtual const T& getData(int index) const; virtual int getDataId(int index) const; virtual bool isLeaf() const { return true; } #ifdef CALLIGRA_RTREE_DEBUG virtual void debug(QString line) const; virtual void paint(QPainter & p, int level) const; #endif protected: QVector m_data; QVector m_dataIds; }; // factory methods virtual LeafNode* createLeafNode(int capacity, int level, Node * parent) { return new LeafNode(capacity, level, parent); } virtual NonLeafNode* createNonLeafNode(int capacity, int level, Node * parent) { return new NonLeafNode(capacity, level, parent); } // methods for insert QPair splitNode(Node * node); QPair pickSeeds(Node * node); QPair pickNext(Node * node, QVector & marker, Node * group1, Node * group2); virtual void adjustTree(Node * node1, Node * node2); void insertHelper(const QRectF& bb, const T& data, int id); // methods for delete void insert(Node * node); virtual void condenseTree(Node * node, QVector & reinsert); int m_capacity; int m_minimum; Node * m_root; QMap m_leafMap; }; template KoRTree::KoRTree(int capacity, int minimum) : m_capacity(capacity) , m_minimum(minimum) , m_root(createLeafNode(m_capacity + 1, 0, 0)) { if (minimum > capacity / 2) qFatal("KoRTree::KoRTree minimum can be maximal capacity/2"); //qDebug() << "root node " << m_root->nodeId(); } template KoRTree::~KoRTree() { delete m_root; } template void KoRTree::insert(const QRectF& bb, const T& data) { insertHelper(bb, data, LeafNode::dataIdCounter++); } template void KoRTree::insertHelper(const QRectF& bb, const T& data, int id) { QRectF nbb(bb.normalized()); // This has to be done as it is not possible to use QRectF::united() with a isNull() if (nbb.isNull()) { nbb.setWidth(0.0001); nbb.setHeight(0.0001); qWarning() << "KoRTree::insert boundingBox isNull setting size to" << nbb.size(); } else { // This has to be done as QRectF::intersects() return false if the rect does not have any area overlapping. // If there is no width or height there is no area and therefore no overlapping. if ( nbb.width() == 0 ) { nbb.setWidth(0.0001); } if ( nbb.height() == 0 ) { nbb.setHeight(0.0001); } } LeafNode * leaf = m_root->chooseLeaf(nbb); //qDebug() << " leaf" << leaf->nodeId() << nbb; if (leaf->childCount() < m_capacity) { leaf->insert(nbb, data, id); m_leafMap[data] = leaf; adjustTree(leaf, 0); } else { leaf->insert(nbb, data, id); m_leafMap[data] = leaf; QPair newNodes = splitNode(leaf); LeafNode * l = dynamic_cast(newNodes.first); if (l) for (int i = 0; i < l->childCount(); ++i) m_leafMap[l->getData(i)] = l; l = dynamic_cast(newNodes.second); if (l) for (int i = 0; i < l->childCount(); ++i) m_leafMap[l->getData(i)] = l; adjustTree(newNodes.first, newNodes.second); } } template void KoRTree::insert(Node * node) { if (node->level() == m_root->level()) { adjustTree(m_root, node); } else { QRectF bb(node->boundingBox()); NonLeafNode * newParent = m_root->chooseNode(bb, node->level() + 1); newParent->insert(bb, node); QPair newNodes(node, 0); if (newParent->childCount() > m_capacity) { newNodes = splitNode(newParent); } adjustTree(newNodes.first, newNodes.second); } } template void KoRTree::remove(const T&data) { //qDebug() << "KoRTree remove"; LeafNode * leaf = m_leafMap[data]; if (leaf == 0) { qWarning() << "KoRTree::remove( const T&data) data not found"; return; } m_leafMap.remove(data); leaf->remove(data); QVector reinsert; condenseTree(leaf, reinsert); for (int i = 0; i < reinsert.size(); ++i) { if (reinsert[i]->isLeaf()) { LeafNode * leaf = dynamic_cast(reinsert[i]); for (int j = 0; j < leaf->childCount(); ++j) { insertHelper(leaf->childBoundingBox(j), leaf->getData(j), leaf->getDataId(j)); } // clear is needed as the data items are not removed when insert into a new node leaf->clear(); delete leaf; } else { NonLeafNode * node = dynamic_cast(reinsert[i]); for (int j = 0; j < node->childCount(); ++j) { insert(node->getNode(j)); } // clear is needed as the data items are not removed when insert into a new node node->clear(); delete node; } } } template QList KoRTree::intersects(const QRectF& rect) const { QMap found; m_root->intersects(rect, found); return found.values(); } template QList KoRTree::contains(const QPointF &point) const { QMap found; m_root->contains(point, found); return found.values(); } template QList KoRTree::keys() const { QList found; m_root->keys(found); return found; } template QList KoRTree::values() const { QMap found; m_root->values(found); return found.values(); } #ifdef CALLIGRA_RTREE_DEBUG template void KoRTree::paint(QPainter & p) const { if (m_root) { m_root->paint(p, 0); } } template void KoRTree::debug() const { QString prefix(""); m_root->debug(prefix); } #endif template QPair< typename KoRTree::Node*, typename KoRTree::Node* > KoRTree::splitNode(typename KoRTree::Node* node) { //qDebug() << "KoRTree::splitNode" << node; Node * n1; Node * n2; if (node->isLeaf()) { n1 = createLeafNode(m_capacity + 1, node->level(), node->parent()); n2 = createLeafNode(m_capacity + 1, node->level(), node->parent()); } else { n1 = createNonLeafNode(m_capacity + 1, node->level(), node->parent()); n2 = createNonLeafNode(m_capacity + 1, node->level(), node->parent()); } //qDebug() << " n1" << n1 << n1->nodeId(); //qDebug() << " n2" << n2 << n2->nodeId(); QVector marker(m_capacity + 1); QPair seeds(pickSeeds(node)); n1->move(node, seeds.first); n2->move(node, seeds.second); marker[seeds.first] = true; marker[seeds.second] = true; // There is one more in a node to split than the capacity and as we // already put the seeds in the new nodes subtract them. int remaining = m_capacity + 1 - 2; while (remaining > 0) { if (m_minimum - n1->childCount() == remaining) { for (int i = 0; i < m_capacity + 1; ++i) { if (!marker[i]) { n1->move(node, i); --remaining; } } } else if (m_minimum - n2->childCount() == remaining) { for (int i = 0; i < m_capacity + 1; ++i) { if (!marker[i]) { n2->move(node, i); --remaining; } } } else { QPair next(pickNext(node, marker, n1, n2)); if (next.first == 0) { n1->move(node, next.second); } else { n2->move(node, next.second); } --remaining; } } Q_ASSERT(n1->childCount() + n2->childCount() == node->childCount()); // move the data back to the old node // this has to be done as the current node is already in the tree. node->clear(); for (int i = 0; i < n1->childCount(); ++i) { node->move(n1, i); } //qDebug() << " delete n1" << n1 << n1->nodeId(); // clear is needed as the data items are not removed n1->clear(); delete n1; return qMakePair(node, n2); } template QPair KoRTree::pickSeeds(Node *node) { int s1 = 0; int s2 = 1; qreal max = 0; for (int i = 0; i < m_capacity + 1; ++i) { for (int j = i+1; j < m_capacity + 1; ++j) { if (i != j) { QRectF bb1(node->childBoundingBox(i)); QRectF bb2(node->childBoundingBox(j)); QRectF comp(node->childBoundingBox(i).united(node->childBoundingBox(j))); qreal area = comp.width() * comp.height() - bb1.width() * bb1.height() - bb2.width() * bb2.height(); //qDebug() << " ps" << i << j << area; if (area > max) { max = area; s1 = i; s2 = j; } } } } return qMakePair(s1, s2); } template QPair KoRTree::pickNext(Node * node, QVector & marker, Node * group1, Node * group2) { //qDebug() << "KoRTree::pickNext" << marker; qreal max = -1.0; int select = 0; int group = 0; for (int i = 0; i < m_capacity + 1; ++i) { if (marker[i] == false) { QRectF bb1 = group1->boundingBox().united(node->childBoundingBox(i)); QRectF bb2 = group2->boundingBox().united(node->childBoundingBox(i)); qreal d1 = bb1.width() * bb1.height() - group1->boundingBox().width() * group1->boundingBox().height(); qreal d2 = bb2.width() * bb2.height() - group2->boundingBox().width() * group2->boundingBox().height(); qreal diff = qAbs(d1 - d2); //qDebug() << " diff" << diff << i << d1 << d2; if (diff > max) { max = diff; select = i; //qDebug() << " i =" << i; if (qAbs(d1) > qAbs(d2)) { group = 1; } else { group = 0; } //qDebug() << " group =" << group; } } } marker[select] = true; return qMakePair(group, select); } template void KoRTree::adjustTree(Node *node1, Node *node2) { //qDebug() << "KoRTree::adjustTree"; if (node1->isRoot()) { //qDebug() << " root"; if (node2) { NonLeafNode * newRoot = createNonLeafNode(m_capacity + 1, node1->level() + 1, 0); newRoot->insert(node1->boundingBox(), node1); newRoot->insert(node2->boundingBox(), node2); m_root = newRoot; //qDebug() << "new root" << m_root->nodeId(); } } else { NonLeafNode * parent = dynamic_cast(node1->parent()); if (!parent) { qFatal("KoRTree::adjustTree: no parent node found!"); return; } //QRectF pbbold( parent->boundingBox() ); parent->setChildBoundingBox(node1->place(), node1->boundingBox()); parent->updateBoundingBox(); //qDebug() << " bb1 =" << node1->boundingBox() << node1->place() << pbbold << "->" << parent->boundingBox() << parent->nodeId(); if (!node2) { //qDebug() << " update"; adjustTree(parent, 0); } else { if (parent->childCount() < m_capacity) { //qDebug() << " no split needed"; parent->insert(node2->boundingBox(), node2); adjustTree(parent, 0); } else { //qDebug() << " split again"; parent->insert(node2->boundingBox(), node2); QPair newNodes = splitNode(parent); adjustTree(newNodes.first, newNodes.second); } } } } template void KoRTree::condenseTree(Node *node, QVector & reinsert) { //qDebug() << "KoRTree::condenseTree begin reinsert.size()" << reinsert.size(); if (!node->isRoot()) { Node * parent = node->parent(); //qDebug() << " !node->isRoot us" << node->childCount(); if (node->childCount() < m_minimum) { //qDebug() << " remove node"; parent->remove(node->place()); reinsert.push_back(node); } else { //qDebug() << " update BB parent is root" << parent->isRoot(); parent->setChildBoundingBox(node->place(), node->boundingBox()); parent->updateBoundingBox(); } condenseTree(parent, reinsert); } else { //qDebug() << " node->isRoot us" << node->childCount(); if (node->childCount() == 1 && !node->isLeaf()) { //qDebug() << " usedSpace = 1"; NonLeafNode * n = dynamic_cast(node); if (n) { Node * kid = n->getNode(0); // clear is needed as the data items are not removed m_root->clear(); delete m_root; m_root = kid; m_root->setParent(0); //qDebug() << " new root" << m_root; } else { qFatal("KoRTree::condenseTree cast to NonLeafNode failed"); } } } //qDebug() << "KoRTree::condenseTree end reinsert.size()" << reinsert.size(); } #ifdef CALLIGRA_RTREE_DEBUG template QColor KoRTree::Node::levelColor[] = { QColor(Qt::green), QColor(Qt::red), QColor(Qt::cyan), QColor(Qt::magenta), QColor(Qt::yellow), }; template int KoRTree::Node::nodeIdCnt = 0; #endif template KoRTree::Node::Node(int capacity, int level, Node * parent) : m_parent(parent) , m_childBoundingBox(capacity) , m_counter(0) #ifdef CALLIGRA_RTREE_DEBUG , m_nodeId(nodeIdCnt++) #endif , m_level(level) { } template void KoRTree::Node::remove(int index) { for (int i = index + 1; i < m_counter; ++i) { m_childBoundingBox[i-1] = m_childBoundingBox[i]; } --m_counter; updateBoundingBox(); } template void KoRTree::Node::updateBoundingBox() { m_boundingBox = QRectF(); for (int i = 0; i < m_counter; ++i) { m_boundingBox = m_boundingBox.united(m_childBoundingBox[i]); } } template void KoRTree::Node::clear() { m_counter = 0; m_boundingBox = QRectF(); } #ifdef CALLIGRA_RTREE_DEBUG template void KoRTree::Node::paintRect(QPainter & p, int level) const { QColor c(Qt::black); if (level < levelColorSize) { c = levelColor[level]; } - QPen pen(c); + QPen pen(c, 0); p.setPen(pen); QRectF bbdraw(this->m_boundingBox); bbdraw.adjust(level * 2, level * 2, -level * 2, -level * 2); p.drawRect(bbdraw); } #endif template KoRTree::NonLeafNode::NonLeafNode(int capacity, int level, Node * parent) : Node(capacity, level, parent) , m_childs(capacity) { //qDebug() << "NonLeafNode::NonLeafNode()" << this; } template KoRTree::NonLeafNode::~NonLeafNode() { //qDebug() << "NonLeafNode::~NonLeafNode()" << this; for (int i = 0; i < this->m_counter; ++i) { delete m_childs[i]; } } template void KoRTree::NonLeafNode::insert(const QRectF& bb, Node * data) { m_childs[this->m_counter] = data; data->setPlace(this->m_counter); data->setParent(this); this->m_childBoundingBox[this->m_counter] = bb; this->m_boundingBox = this->m_boundingBox.united(bb); //qDebug() << "NonLeafNode::insert" << this->nodeId() << data->nodeId(); ++this->m_counter; } template void KoRTree::NonLeafNode::remove(int index) { for (int i = index + 1; i < this->m_counter; ++i) { m_childs[i-1] = m_childs[i]; m_childs[i-1]->setPlace(i - 1); } Node::remove(index); } template void KoRTree::NonLeafNode::move(Node * node, int index) { //qDebug() << "NonLeafNode::move" << this << node << index << node->nodeId() << "->" << this->nodeId(); NonLeafNode * n = dynamic_cast(node); if (n) { QRectF bb = n->childBoundingBox(index); insert(bb, n->getNode(index)); } } template typename KoRTree::LeafNode * KoRTree::NonLeafNode::chooseLeaf(const QRectF& bb) { return getLeastEnlargement(bb)->chooseLeaf(bb); } template typename KoRTree::NonLeafNode * KoRTree::NonLeafNode::chooseNode(const QRectF& bb, int level) { if (this->m_level > level) { return getLeastEnlargement(bb)->chooseNode(bb, level); } else { return this; } } template void KoRTree::NonLeafNode::intersects(const QRectF& rect, QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { if (this->m_childBoundingBox[i].intersects(rect)) { m_childs[i]->intersects(rect, result); } } } template void KoRTree::NonLeafNode::contains(const QPointF & point, QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { if (this->m_childBoundingBox[i].contains(point)) { m_childs[i]->contains(point, result); } } } template void KoRTree::NonLeafNode::keys(QList & result) const { for (int i = 0; i < this->m_counter; ++i) { m_childs[i]->keys(result); } } template void KoRTree::NonLeafNode::values(QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { m_childs[i]->values(result); } } template typename KoRTree::Node * KoRTree::NonLeafNode::getNode(int index) const { return m_childs[index]; } template typename KoRTree::Node * KoRTree::NonLeafNode::getLeastEnlargement(const QRectF& bb) const { //qDebug() << "NonLeafNode::getLeastEnlargement"; QVarLengthArray area(this->m_counter); for (int i = 0; i < this->m_counter; ++i) { QSizeF big(this->m_childBoundingBox[i].united(bb).size()); area[i] = big.width() * big.height() - this->m_childBoundingBox[i].width() * this->m_childBoundingBox[i].height(); } int minIndex = 0; qreal minArea = area[minIndex]; //qDebug() << " min" << minIndex << minArea; for (int i = 1; i < this->m_counter; ++i) { if (area[i] < minArea) { minIndex = i; minArea = area[i]; //qDebug() << " min" << minIndex << minArea; } } return m_childs[minIndex]; } #ifdef CALLIGRA_RTREE_DEBUG template void KoRTree::NonLeafNode::debug(QString line) const { for (int i = 0; i < this->m_counter; ++i) { qDebug("%s %d %d", qPrintable(line), this->nodeId(), i); m_childs[i]->debug(line + " "); } } template void KoRTree::NonLeafNode::paint(QPainter & p, int level) const { this->paintRect(p, level); for (int i = 0; i < this->m_counter; ++i) { m_childs[i]->paint(p, level + 1); } } #endif template int KoRTree::LeafNode::dataIdCounter = 0; template KoRTree::LeafNode::LeafNode(int capacity, int level, Node * parent) : Node(capacity, level, parent) , m_data(capacity) , m_dataIds(capacity) { //qDebug() << "LeafNode::LeafNode" << this; } template KoRTree::LeafNode::~LeafNode() { //qDebug() << "LeafNode::~LeafNode" << this; } template void KoRTree::LeafNode::insert(const QRectF& bb, const T& data, int id) { m_data[this->m_counter] = data; m_dataIds[this->m_counter] = id; this->m_childBoundingBox[this->m_counter] = bb; this->m_boundingBox = this->m_boundingBox.united(bb); ++this->m_counter; } template void KoRTree::LeafNode::remove(int index) { for (int i = index + 1; i < this->m_counter; ++i) { m_data[i-1] = m_data[i]; m_dataIds[i-1] = m_dataIds[i]; } Node::remove(index); } template void KoRTree::LeafNode::remove(const T& data) { int old_counter = this->m_counter; for (int i = 0; i < this->m_counter; ++i) { if (m_data[i] == data) { //qDebug() << "LeafNode::remove id" << i; remove(i); break; } } if (old_counter == this->m_counter) { qWarning() << "LeafNode::remove( const T&data) data not found"; } } template void KoRTree::LeafNode::move(Node * node, int index) { LeafNode * n = dynamic_cast(node); if (n) { //qDebug() << "LeafNode::move" << this << node << index // << node->nodeId() << "->" << this->nodeId() << n->childBoundingBox( index ); QRectF bb = n->childBoundingBox(index); insert(bb, n->getData(index), n->getDataId(index)); } } template typename KoRTree::LeafNode * KoRTree::LeafNode::chooseLeaf(const QRectF& bb) { Q_UNUSED(bb); return this; } template typename KoRTree::NonLeafNode * KoRTree::LeafNode::chooseNode(const QRectF& bb, int level) { Q_UNUSED(bb); Q_UNUSED(level); qFatal("LeafNode::chooseNode called. This should not happen!"); return 0; } template void KoRTree::LeafNode::intersects(const QRectF& rect, QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { if (this->m_childBoundingBox[i].intersects(rect)) { result.insert(m_dataIds[i], m_data[i]); } } } template void KoRTree::LeafNode::contains(const QPointF & point, QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { if (this->m_childBoundingBox[i].contains(point)) { result.insert(m_dataIds[i], m_data[i]); } } } template void KoRTree::LeafNode::keys(QList & result) const { for (int i = 0; i < this->m_counter; ++i) { result.push_back(this->m_childBoundingBox[i]); } } template void KoRTree::LeafNode::values(QMap & result) const { for (int i = 0; i < this->m_counter; ++i) { result.insert(m_dataIds[i], m_data[i]); } } template const T& KoRTree::LeafNode::getData(int index) const { return m_data[ index ]; } template int KoRTree::LeafNode::getDataId(int index) const { return m_dataIds[ index ]; } #ifdef CALLIGRA_RTREE_DEBUG template void KoRTree::LeafNode::debug(QString line) const { for (int i = 0; i < this->m_counter; ++i) { qDebug("%s %d %d %p", qPrintable(line), this->nodeId(), i, &(m_data[i])); qDebug() << this->m_childBoundingBox[i].toRect(); } } template void KoRTree::LeafNode::paint(QPainter & p, int level) const { if (this->m_counter) { this->paintRect(p, level); } } #endif #endif /* KORTREE_H */ diff --git a/libs/flake/KoSnapGuide.cpp b/libs/flake/KoSnapGuide.cpp index faaaba255eb..4c80b3a2afc 100644 --- a/libs/flake/KoSnapGuide.cpp +++ b/libs/flake/KoSnapGuide.cpp @@ -1,236 +1,236 @@ /* This file is part of the KDE project * Copyright (C) 2008-2009 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoSnapGuide.h" #include "KoSnapProxy.h" #include "KoSnapStrategy.h" #include #include #include #include #include #include class Q_DECL_HIDDEN KoSnapGuide::Private { public: Private(KoCanvasBase *parentCanvas) : canvas(parentCanvas), editedShape(0), currentStrategy(0), active(true), snapDistance(10) { } ~Private() { qDeleteAll(strategies); strategies.clear(); } KoCanvasBase *canvas; KoShape *editedShape; QList strategies; KoSnapStrategy *currentStrategy; KoSnapGuide::Strategies usedStrategies; bool active; int snapDistance; QList ignoredPoints; QList ignoredShapes; }; KoSnapGuide::KoSnapGuide(KoCanvasBase *canvas) : d(new Private(canvas)) { d->strategies.append(new GridSnapStrategy()); d->strategies.append(new NodeSnapStrategy()); d->strategies.append(new OrthogonalSnapStrategy()); d->strategies.append(new ExtensionSnapStrategy()); d->strategies.append(new IntersectionSnapStrategy()); d->strategies.append(new BoundingBoxSnapStrategy()); d->strategies.append(new LineGuideSnapStrategy()); } KoSnapGuide::~KoSnapGuide() { delete d; } void KoSnapGuide::setEditedShape(KoShape *shape) { d->editedShape = shape; } KoShape *KoSnapGuide::editedShape() const { return d->editedShape; } void KoSnapGuide::enableSnapStrategies(Strategies strategies) { d->usedStrategies = strategies; } KoSnapGuide::Strategies KoSnapGuide::enabledSnapStrategies() const { return d->usedStrategies; } bool KoSnapGuide::addCustomSnapStrategy(KoSnapStrategy *customStrategy) { if (!customStrategy || customStrategy->type() != CustomSnapping) return false; d->strategies.append(customStrategy); return true; } void KoSnapGuide::enableSnapping(bool on) { d->active = on; } bool KoSnapGuide::isSnapping() const { return d->active; } void KoSnapGuide::setSnapDistance(int distance) { d->snapDistance = qAbs(distance); } int KoSnapGuide::snapDistance() const { return d->snapDistance; } QPointF KoSnapGuide::snap(const QPointF &mousePosition, Qt::KeyboardModifiers modifiers) { d->currentStrategy = 0; if (! d->active || (modifiers & Qt::ShiftModifier)) return mousePosition; KoSnapProxy proxy(this); qreal minDistance = HUGE_VAL; qreal maxSnapDistance = d->canvas->viewConverter()->viewToDocument(QSizeF(d->snapDistance, d->snapDistance)).width(); foreach (KoSnapStrategy *strategy, d->strategies) { if (d->usedStrategies & strategy->type() || strategy->type() == GridSnapping || strategy->type() == CustomSnapping) { if (! strategy->snap(mousePosition, &proxy, maxSnapDistance)) continue; QPointF snapCandidate = strategy->snappedPosition(); qreal distance = KoSnapStrategy::squareDistance(snapCandidate, mousePosition); if (distance < minDistance) { d->currentStrategy = strategy; minDistance = distance; } } } if (! d->currentStrategy) return mousePosition; return d->currentStrategy->snappedPosition(); } QRectF KoSnapGuide::boundingRect() const { QRectF rect; if (d->currentStrategy) { rect = d->currentStrategy->decoration(*d->canvas->viewConverter()).boundingRect(); return rect.adjusted(-2, -2, 2, 2); } else { return rect; } } void KoSnapGuide::paint(QPainter &painter, const KoViewConverter &converter) { if (! d->currentStrategy || ! d->active) return; QPainterPath decoration = d->currentStrategy->decoration(converter); painter.setBrush(Qt::NoBrush); - QPen whitePen(Qt::white); + QPen whitePen(Qt::white, 0); whitePen.setStyle(Qt::SolidLine); painter.setPen(whitePen); painter.drawPath(decoration); - QPen redPen(Qt::red); + QPen redPen(Qt::red, 0); redPen.setStyle(Qt::DotLine); painter.setPen(redPen); painter.drawPath(decoration); } KoCanvasBase *KoSnapGuide::canvas() const { return d->canvas; } void KoSnapGuide::setIgnoredPathPoints(const QList &ignoredPoints) { d->ignoredPoints = ignoredPoints; } QList KoSnapGuide::ignoredPathPoints() const { return d->ignoredPoints; } void KoSnapGuide::setIgnoredShapes(const QList &ignoredShapes) { d->ignoredShapes = ignoredShapes; } QList KoSnapGuide::ignoredShapes() const { return d->ignoredShapes; } void KoSnapGuide::reset() { d->currentStrategy = 0; d->editedShape = 0; d->ignoredPoints.clear(); d->ignoredShapes.clear(); // remove all custom strategies int strategyCount = d->strategies.count(); for (int i = strategyCount-1; i >= 0; --i) { if (d->strategies[i]->type() == CustomSnapping) { delete d->strategies[i]; d->strategies.removeAt(i); } } } diff --git a/libs/flake/KoUnavailShape.cpp b/libs/flake/KoUnavailShape.cpp index 45a1b4de7ca..6b39d333ae0 100644 --- a/libs/flake/KoUnavailShape.cpp +++ b/libs/flake/KoUnavailShape.cpp @@ -1,656 +1,656 @@ /* This file is part of the KDE project * * Copyright (C) 2010-2011 Inge Wallin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // Own #include "KoUnavailShape.h" // Qt #include #include #include #include #include #include #include #include #include // Calligra #include #include #include #include #include #include #include #include #include "KoShapeLoadingContext.h" #include "KoShapeSavingContext.h" #include "KoShapeContainerDefaultModel.h" #include "KoShapeBackground.h" #include // The XML of a frame looks something like this: // // 1. // 2. // 3. // 4. // // or // // 1. // 2. ...inline xml here... // 3. // 4. // // We define each Xml statement on lines 2 and 3 above as an "object". // (Strictly only the first child element is an object in the ODF sense, // but we have to have some terminology here.) // // In an ODF frame, only the first line, i.e. the first object // contains the real contents. All the rest of the objects are used / // shown if we cannot handle the first one. The most common cases are // that there is only one object inside the frame OR that there are 2 // and the 2nd is a picture. // // Sometimes, e.g. in the case of an embedded document, the reference // points not to a file but to a directory structure inside the ODF // store. // // When we load and save in the UnavailShape, we have to be general // enough to cover all possible cases of references and inline XML, // embedded files and embedded directory structures. // // We also have to be careful because we cannot reuse the object names // that are in the original files when saving. Instead we need to // create new object names because the ones that were used in the // original file may already be used by other embedded files/objects // that are saved by other shapes. // // FIXME: There should only be ONE place where new object / file names // are generated, not 2(?) like there are now: // KoEmbeddedDocumentSaver and the KoImageCollection. // // An ObjectEntry is used to store information about objects in the // frame, as defined above. struct ObjectEntry { QByteArray objectXmlContents; // the XML tree in the object QString objectName; // object name in the frame without "./" // This is extracted from objectXmlContents. bool isDir; KoOdfManifestEntry *manifestEntry; // manifest entry for the above. }; // A FileEntry is used to store information about embedded files // inside (i.e. referred to by) an object. struct FileEntry { QString path; // Normalized filename, i.e. without "./". QString mimeType; bool isDir; QByteArray contents; }; class KoUnavailShape::Private { public: Private(KoUnavailShape* qq); ~Private(); void draw(QPainter &painter) const; void drawNull(QPainter &painter) const; void storeObjects(const KoXmlElement &element); void storeXmlRecursive(const KoXmlElement &el, KoXmlWriter &writer, ObjectEntry *object, QHash &unknownNamespaces); void storeFile(const QString &filename, KoShapeLoadingContext &context); QByteArray loadFile(const QString &filename, KoShapeLoadingContext &context); // Objects inside the frame. For each file, we store: // - The XML code for the object // - Any embedded files (names, contents) that are referenced by xlink:href // - Whether they are directories, i.e. if they contain a file tree and not just one file. // - The manifest entries QList objectEntries; // Embedded files QList embeddedFiles; // List of embedded files. // Some cached values. QPixmap questionMark; QPixmap pixmapPreview; QSvgRenderer *scalablePreview; KoUnavailShape* q; }; KoUnavailShape::Private::Private(KoUnavailShape* qq) : scalablePreview(new QSvgRenderer()) , q(qq) { // Get the question mark "icon". questionMark.load(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "calligra/pics/questionmark.png")); } KoUnavailShape::Private::~Private() { qDeleteAll(objectEntries); qDeleteAll(embeddedFiles); // It's a QObject, but we haven't parented it. delete(scalablePreview); } // ---------------------------------------------------------------- // The main class KoUnavailShape::KoUnavailShape() : KoFrameShape( "", "" ) , KoShapeContainer(new KoShapeContainerDefaultModel()) , d(new Private(this)) { setShapeId(KoUnavailShape_SHAPEID); // Default size of the shape. KoShape::setSize( QSizeF( CM_TO_POINT( 5 ), CM_TO_POINT( 3 ) ) ); } KoUnavailShape::~KoUnavailShape() { delete d; } void KoUnavailShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { applyConversion(painter, converter); // If the frame is empty, just draw a background. debugFlake << "Number of objects:" << d->objectEntries.size(); if (d->objectEntries.isEmpty()) { // But... only try to draw the background if there's one such if (background()) { QPainterPath p; p.addRect(QRectF(QPointF(), size())); background()->paint(painter, converter, paintContext, p); } } else { if(shapes().isEmpty()) { d->draw(painter); } } } void KoUnavailShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); } void KoUnavailShape::Private::draw(QPainter &painter) const { painter.save(); painter.setRenderHint(QPainter::Antialiasing); // Run through the previews in order of preference. Draw a placeholder // questionmark if there is no preview available for rendering. if (scalablePreview->isValid()) { QRect bounds(0, 0, q->boundingRect().width(), q->boundingRect().height()); scalablePreview->render(&painter, bounds); } else if (!pixmapPreview.isNull()) { QRect bounds(0, 0, q->boundingRect().width(), q->boundingRect().height()); painter.setRenderHint(QPainter::SmoothPixmapTransform); painter.drawPixmap(bounds, pixmapPreview); } else if (q->shapes().isEmpty()) { // Draw a nice question mark with a frame around it if there // is no other preview image. If there is a contained image // shape, we don't need to draw anything. // Get the question mark "icon". // FIXME: We should be able to use d->questionMark here. QPixmap questionMark; questionMark.load(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "calligra/pics/questionmark.png")); // The size of the image is: // - the size of the shape if shapesize < 2cm // - 2 cm if 2cm <= shapesize <= 8cm // - shapesize / 4 if shapesize > 8cm qreal width = q->size().width(); qreal height = q->size().height(); qreal picSize = CM_TO_POINT(2); // Default size is 2 cm. if (width < CM_TO_POINT(2) || height < CM_TO_POINT(2)) picSize = qMin(width, height); else if (width > CM_TO_POINT(8) && height > CM_TO_POINT(8)) picSize = qMin(width, height) / qreal(4.0); painter.drawPixmap((width - picSize) / qreal(2.0), (height - picSize) / qreal(2.0), picSize, picSize, questionMark); // Draw a gray rectangle around the shape. - painter.setPen(QPen(QColor(172, 196, 206))); + painter.setPen(QPen(QColor(172, 196, 206), 0)); painter.drawRect(QRectF(QPointF(0,0), q->size())); } painter.restore(); } void KoUnavailShape::Private::drawNull(QPainter &painter) const { QRectF rect(QPointF(0,0), q->size()); painter.save(); // Draw a simple cross in a rectangle just to indicate that there is something here. painter.drawLine(rect.topLeft(), rect.bottomRight()); painter.drawLine(rect.bottomLeft(), rect.topRight()); painter.restore(); } // ---------------------------------------------------------------- // Loading and Saving void KoUnavailShape::saveOdf(KoShapeSavingContext & context) const { debugFlake << "START SAVING ##################################################"; KoEmbeddedDocumentSaver &fileSaver = context.embeddedSaver(); KoXmlWriter &writer = context.xmlWriter(); writer.startElement("draw:frame"); // See also loadOdf() in loadOdfAttributes. saveOdfAttributes( context, OdfAllAttributes ); // Write the stored XML to the file, but don't reuse object names. int lap = 0; QString newName; foreach (const ObjectEntry *object, d->objectEntries) { QByteArray xmlArray(object->objectXmlContents); QString objectName(object->objectName); // Possibly empty. KoOdfManifestEntry *manifestEntry(object->manifestEntry); // Create a name for this object. If this is not the first // object, i.e. a replacement object (most likely a picture), // then reuse the name but put it in ReplacementObjects. if (++lap == 1) { // The first lap in the loop is the actual object. All // other laps are replacement objects. newName = fileSaver.getFilename("Object "); } else if (lap == 2) { newName = "ObjectReplacements/" + newName; } else // FIXME: what should replacement 2 and onwards be called? newName = newName + "_"; // If there was a previous object name, replace it with the new one. if (!objectName.isEmpty() && manifestEntry) { // FIXME: We must make a copy of the byte array here because // otherwise we won't be able to save > 1 time. xmlArray.replace(objectName.toLatin1(), newName.toLatin1()); } writer.addCompleteElement(xmlArray.data()); // If the objectName is empty, this may be inline XML. // If so, we are done now. if (objectName.isEmpty() || !manifestEntry) { continue; } // Save embedded files for this object. foreach (FileEntry *entry, d->embeddedFiles) { QString fileName(entry->path); // If we found a file for this object, we need to write it // but with the new object name instead of the old one. if (!fileName.startsWith(objectName)) continue; debugFlake << "Object name: " << objectName << "newName: " << newName << "filename: " << fileName << "isDir: " << entry->isDir; fileName.replace(objectName, newName); fileName.prepend("./"); debugFlake << "New filename: " << fileName; // FIXME: Check if we need special treatment of directories. fileSaver.saveFile(fileName, entry->mimeType.toLatin1(), entry->contents); } // Write the manifest entry for the object itself. If it's a // file, the manifest is already written by saveFile, so skip // it here. if (object->isDir) { fileSaver.saveManifestEntry(newName + '/', manifestEntry->mediaType(), manifestEntry->version()); } } writer.endElement(); // draw:frame } bool KoUnavailShape::loadOdf(const KoXmlElement &frameElement, KoShapeLoadingContext &context) { debugFlake << "START LOADING ##################################################"; //debugFlake << "Loading ODF frame in the KoUnavailShape. Element = " // << frameElement.tagName(); loadOdfAttributes(frameElement, context, OdfAllAttributes); // NOTE: We cannot use loadOdfFrame() because we want to save all // the things inside the frame, not just one of them, like // loadOdfFrame() provides. // Get the manifest. QList manifest = context.odfLoadingContext().manifestEntries(); #if 0 // Enable to show all manifest entries. debugFlake << "MANIFEST: "; foreach (KoOdfManifestEntry *entry, manifest) { debugFlake << entry->mediaType() << entry->fullPath() << entry->version(); } #endif // 1. Get the XML contents of the objects from the draw:frame. As // a side effect, this extracts the object names from all // xlink:href and stores them into d->objectNames. The saved // xml contents itself is saved into d->objectXmlContents // (QByteArray) so we can save it back from saveOdf(). d->storeObjects(frameElement); #if 1 // Debug only debugFlake << "----------------------------------------------------------------"; debugFlake << "After storeObjects():"; foreach (ObjectEntry *object, d->objectEntries) { debugFlake << "objectXmlContents: " << object->objectXmlContents << "objectName: " << object->objectName; // Note: at this point, isDir and manifestEntry are not set. #endif } // 2. Loop through the objects that were found in the frame and // save all the files associated with them. Some of the // objects are files, and some are directories. The // directories are searched and the files within are saved as // well. // // In this loop, isDir and manifestEntry of each ObjectEntry are set. bool foundPreview = false; foreach (ObjectEntry *object, d->objectEntries) { QString objectName = object->objectName; if (objectName.isEmpty()) continue; debugFlake << "Storing files for object named:" << objectName; // Try to find out if the entry is a directory. // If the object is a directory, then save all the files // inside it, otherwise save the file as it is. QString dirName = objectName + '/'; bool isDir = !context.odfLoadingContext().mimeTypeForPath(dirName).isEmpty(); if (isDir) { // A directory: the files can be found in the manifest. foreach (KoOdfManifestEntry *entry, manifest) { if (entry->fullPath() == dirName) continue; if (entry->fullPath().startsWith(dirName)) { d->storeFile(entry->fullPath(), context); } } } else { // A file: save it. d->storeFile(objectName, context); } // Get the manifest entry for this object. KoOdfManifestEntry *entry = 0; QString entryName = isDir ? dirName : objectName; for (int j = 0; j < manifest.size(); ++j) { KoOdfManifestEntry *temp = manifest.value(j); if (temp->fullPath() == entryName) { entry = new KoOdfManifestEntry(*temp); break; } } object->isDir = isDir; object->manifestEntry = entry; // If we have not already found a preview in previous times // through the loop, then see if this one may be a preview. if (!foundPreview) { debugFlake << "Attempting to load preview from " << objectName; QByteArray previewData = d->loadFile(objectName, context); // Check to see if we know the mimetype for this entry. Specifically: // 1. Check to see if the item is a loadable SVG file // FIXME: Perhaps check in the manifest first? But this // seems to work well. d->scalablePreview->load(previewData); if (d->scalablePreview->isValid()) { debugFlake << "Found scalable preview image!"; d->scalablePreview->setViewBox(d->scalablePreview->boundsOnElement("svg")); foundPreview = true; continue; } // 2. Otherwise check to see if it's a loadable pixmap file d->pixmapPreview.loadFromData(previewData); if (!d->pixmapPreview.isNull()) { debugFlake << "Found pixel based preview image!"; foundPreview = true; } } } #if 0 // Enable to get more detailed debug messages debugFlake << "Object manifest entries:"; for (int i = 0; i < d->manifestEntries.size(); ++i) { KoOdfManifestEntry *entry = d->manifestEntries.value(i); debugFlake << i << ":" << entry; if (entry) debugFlake << entry->fullPath() << entry->mediaType() << entry->version(); else debugFlake << "--"; } debugFlake << "END LOADING ####################################################"; #endif return true; } // Load the actual contents inside the frame. bool KoUnavailShape::loadOdfFrameElement(const KoXmlElement & /*element*/, KoShapeLoadingContext &/*context*/) { return true; } // ---------------------------------------------------------------- // Private functions void KoUnavailShape::Private::storeObjects(const KoXmlElement &element) { // Loop through all the child elements of the draw:frame and save them. KoXmlNode n = element.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { debugFlake << "In draw:frame, node =" << n.nodeName(); // This disregards #text, but that's not in the spec anyway so // it doesn't need to be saved. if (!n.isElement()) continue; KoXmlElement el = n.toElement(); ObjectEntry *object = new ObjectEntry; QByteArray contentsTmp; QBuffer buffer(&contentsTmp); // the member KoXmlWriter writer(&buffer); // 1. Find out the objectName // Save the normalized filename, i.e. without a starting "./". // An empty string is saved if no name is found. QString name = el.attributeNS(KoXmlNS::xlink, "href", QString()); if (name.startsWith(QLatin1String("./"))) name.remove(0, 2); object->objectName = name; // 2. Copy the XML code. QHash unknownNamespaces; storeXmlRecursive(el, writer, object, unknownNamespaces); object->objectXmlContents = contentsTmp; // 3, 4: the isDir and manifestEntry members are not set here, // but initialize them anyway. . object->isDir = false; // Has to be initialized to something. object->manifestEntry = 0; objectEntries.append(object); } } void KoUnavailShape::Private::storeXmlRecursive(const KoXmlElement &el, KoXmlWriter &writer, ObjectEntry *object, QHash &unknownNamespaces) { // Start the element; // keep the name in a QByteArray so that it stays valid until end element is called. const QByteArray name(el.nodeName().toLatin1()); writer.startElement(name.constData()); // Copy all the attributes, including namespaces. QList< QPair > attributeNames = el.attributeFullNames(); for (int i = 0; i < attributeNames.size(); ++i) { QPair attrPair(attributeNames.value(i)); if (attrPair.first.isEmpty()) { writer.addAttribute(attrPair.second.toLatin1(), el.attribute(attrPair.second)); } else { // This somewhat convoluted code is because we need the // namespace, not the namespace URI. QString nsShort = KoXmlNS::nsURI2NS(attrPair.first.toLatin1()); // in case we don't find the namespace in our list create a own one and use that // so the document created on saving is valid. if (nsShort.isEmpty()) { nsShort = unknownNamespaces.value(attrPair.first); if (nsShort.isEmpty()) { nsShort = QString("ns%1").arg(unknownNamespaces.size() + 1); unknownNamespaces.insert(attrPair.first, nsShort); } QString name = QString("xmlns:") + nsShort; writer.addAttribute(name.toLatin1(), attrPair.first.toLatin1()); } QString attr(nsShort + ':' + attrPair.second); writer.addAttribute(attr.toLatin1(), el.attributeNS(attrPair.first, attrPair.second)); } } // Child elements // Loop through all the child elements of the draw:frame. KoXmlNode n = el.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { if (n.isElement()) { storeXmlRecursive(n.toElement(), writer, object, unknownNamespaces); } else if (n.isText()) { writer.addTextNode(n.toText().data()/*.toUtf8()*/); } } // End the element writer.endElement(); } /** * This function stores the embedded file in an internal store - it does not save files to disk, * and thus it is named in this manner, to avoid the function being confused with functions which * save files to disk. */ void KoUnavailShape::Private::storeFile(const QString &fileName, KoShapeLoadingContext &context) { debugFlake << "Saving file: " << fileName; // Directories need to be saved too, but they don't have any file contents. if (fileName.endsWith('/')) { FileEntry *entry = new FileEntry; entry->path = fileName; entry->mimeType = context.odfLoadingContext().mimeTypeForPath(entry->path); entry->isDir = true; embeddedFiles.append(entry); } QByteArray fileContent = loadFile(fileName, context); if (fileContent.isNull()) return; // Actually store the file in the list. FileEntry *entry = new FileEntry; entry->path = fileName; if (entry->path.startsWith(QLatin1String("./"))) entry->path.remove(0, 2); entry->mimeType = context.odfLoadingContext().mimeTypeForPath(entry->path); entry->isDir = false; entry->contents = fileContent; embeddedFiles.append(entry); debugFlake << "File length: " << fileContent.size(); } QByteArray KoUnavailShape::Private::loadFile(const QString &fileName, KoShapeLoadingContext &context) { // Can't load a file which is a directory, return an invalid QByteArray if (fileName.endsWith('/')) return QByteArray(); KoStore *store = context.odfLoadingContext().store(); QByteArray fileContent; if (!store->open(fileName)) { store->close(); return QByteArray(); } int fileSize = store->size(); fileContent = store->read(fileSize); store->close(); //debugFlake << "File content: " << fileContent; return fileContent; } diff --git a/libs/flake/tools/KoPathTool.cpp b/libs/flake/tools/KoPathTool.cpp index 0da8c7729f4..77f01cda7ae 100644 --- a/libs/flake/tools/KoPathTool.cpp +++ b/libs/flake/tools/KoPathTool.cpp @@ -1,968 +1,968 @@ /* This file is part of the KDE project * Copyright (C) 2006-2012 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * Copyright (C) 2007, 2010 Thomas Zander * Copyright (C) 2007 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPathTool.h" #include "KoToolBase_p.h" #include "KoPathShape_p.h" #include "KoPathToolHandle.h" #include "KoCanvasBase.h" #include "KoShapeManager.h" #include "KoDocumentResourceManager.h" #include "KoViewConverter.h" #include "KoSelection.h" #include "KoPointerEvent.h" #include "commands/KoPathPointTypeCommand.h" #include "commands/KoPathPointInsertCommand.h" #include "commands/KoPathPointRemoveCommand.h" #include "commands/KoPathSegmentTypeCommand.h" #include "commands/KoPathBreakAtPointCommand.h" #include "commands/KoPathSegmentBreakCommand.h" #include "commands/KoParameterToPathCommand.h" #include "commands/KoSubpathJoinCommand.h" #include "commands/KoPathPointMergeCommand.h" #include "KoParameterShape.h" #include "KoPathPoint.h" #include "KoPathPointRubberSelectStrategy.h" #include "KoPathSegmentChangeStrategy.h" #include "KoPathConnectionPointStrategy.h" #include "KoParameterChangeStrategy.h" #include "PathToolOptionWidget.h" #include "KoConnectionShape.h" #include "KoSnapGuide.h" #include "KoShapeController.h" #include #include #include #include #include #include #include #include static const unsigned char needle_bits[] = { 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x60, 0x00, 0xc0, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x80, 0x07, 0x00, 0x0f, 0x00, 0x1f, 0x00, 0x3e, 0x00, 0x7e, 0x00, 0x7c, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x00 }; static const unsigned char needle_move_bits[] = { 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x60, 0x00, 0xc0, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x80, 0x07, 0x10, 0x0f, 0x38, 0x1f, 0x54, 0x3e, 0xfe, 0x7e, 0x54, 0x7c, 0x38, 0x1c, 0x10, 0x18, 0x00, 0x00 }; // helper function to calculate the squared distance between two points qreal squaredDistance(const QPointF& p1, const QPointF &p2) { qreal dx = p1.x()-p2.x(); qreal dy = p1.y()-p2.y(); return dx*dx + dy*dy; } struct KoPathTool::PathSegment { PathSegment() : path(0), segmentStart(0), positionOnSegment(0) { } bool isValid() { return path && segmentStart; } KoPathShape *path; KoPathPoint *segmentStart; qreal positionOnSegment; }; KoPathTool::KoPathTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_pointSelection(this) , m_activeHandle(0) , m_handleRadius(3) , m_activeSegment(0) , m_currentStrategy(0) { QActionGroup *points = new QActionGroup(this); // m_pointTypeGroup->setExclusive(true); m_actionPathPointCorner = new QAction(koIcon("format-node-corner"), i18n("Corner point"), this); addAction("pathpoint-corner", m_actionPathPointCorner); m_actionPathPointCorner->setData(KoPathPointTypeCommand::Corner); points->addAction(m_actionPathPointCorner); m_actionPathPointSmooth = new QAction(koIcon("format-node-smooth"), i18n("Smooth point"), this); addAction("pathpoint-smooth", m_actionPathPointSmooth); m_actionPathPointSmooth->setData(KoPathPointTypeCommand::Smooth); points->addAction(m_actionPathPointSmooth); m_actionPathPointSymmetric = new QAction(koIcon("format-node-symmetric"), i18n("Symmetric Point"), this); addAction("pathpoint-symmetric", m_actionPathPointSymmetric); m_actionPathPointSymmetric->setData(KoPathPointTypeCommand::Symmetric); points->addAction(m_actionPathPointSymmetric); m_actionCurvePoint = new QAction(koIcon("format-node-curve"), i18n("Make curve point"), this); addAction("pathpoint-curve", m_actionCurvePoint); connect(m_actionCurvePoint, SIGNAL(triggered()), this, SLOT(pointToCurve())); m_actionLinePoint = new QAction(koIcon("format-node-line"), i18n("Make line point"), this); addAction("pathpoint-line", m_actionLinePoint); connect(m_actionLinePoint, SIGNAL(triggered()), this, SLOT(pointToLine())); m_actionLineSegment = new QAction(koIcon("format-segment-line"), i18n("Segment to Line"), this); m_actionLineSegment->setShortcut(Qt::Key_F); addAction("pathsegment-line", m_actionLineSegment); connect(m_actionLineSegment, SIGNAL(triggered()), this, SLOT(segmentToLine())); m_actionCurveSegment = new QAction(koIcon("format-segment-curve"), i18n("Segment to Curve"), this); m_actionCurveSegment->setShortcut(Qt::Key_C); addAction("pathsegment-curve", m_actionCurveSegment); connect(m_actionCurveSegment, SIGNAL(triggered()), this, SLOT(segmentToCurve())); m_actionAddPoint = new QAction(koIcon("format-insert-node"), i18n("Insert point"), this); addAction("pathpoint-insert", m_actionAddPoint); m_actionAddPoint->setShortcut(Qt::Key_Insert); connect(m_actionAddPoint, SIGNAL(triggered()), this, SLOT(insertPoints())); m_actionRemovePoint = new QAction(koIcon("format-remove-node"), i18n("Remove point"), this); m_actionRemovePoint->setShortcut(Qt::Key_Backspace); addAction("pathpoint-remove", m_actionRemovePoint); connect(m_actionRemovePoint, SIGNAL(triggered()), this, SLOT(removePoints())); m_actionBreakPoint = new QAction(koIcon("format-break-node"), i18n("Break at point"), this); addAction("path-break-point", m_actionBreakPoint); connect(m_actionBreakPoint, SIGNAL(triggered()), this, SLOT(breakAtPoint())); m_actionBreakSegment = new QAction(koIcon("format-disconnect-node"), i18n("Break at segment"), this); addAction("path-break-segment", m_actionBreakSegment); connect(m_actionBreakSegment, SIGNAL(triggered()), this, SLOT(breakAtSegment())); m_actionJoinSegment = new QAction(koIcon("format-connect-node"), i18n("Join with segment"), this); m_actionJoinSegment->setShortcut(Qt::Key_J); addAction("pathpoint-join", m_actionJoinSegment); connect(m_actionJoinSegment, SIGNAL(triggered()), this, SLOT(joinPoints())); m_actionMergePoints = new QAction(koIcon("format-join-node"), i18n("Merge points"), this); addAction("pathpoint-merge", m_actionMergePoints); connect(m_actionMergePoints, SIGNAL(triggered()), this, SLOT(mergePoints())); m_actionConvertToPath = new QAction(koIcon("format-convert-to-path"), i18n("To Path"), this); m_actionConvertToPath->setShortcut(Qt::Key_P); addAction("convert-to-path", m_actionConvertToPath); connect(m_actionConvertToPath, SIGNAL(triggered()), this, SLOT(convertToPath())); connect(points, SIGNAL(triggered(QAction*)), this, SLOT(pointTypeChanged(QAction*))); connect(&m_pointSelection, SIGNAL(selectionChanged()), this, SLOT(pointSelectionChanged())); QBitmap b = QBitmap::fromData(QSize(16, 16), needle_bits); QBitmap m = b.createHeuristicMask(false); m_selectCursor = QCursor(b, m, 2, 0); b = QBitmap::fromData(QSize(16, 16), needle_move_bits); m = b.createHeuristicMask(false); m_moveCursor = QCursor(b, m, 2, 0); } KoPathTool::~KoPathTool() { delete m_activeHandle; delete m_activeSegment; delete m_currentStrategy; } QList > KoPathTool::createOptionWidgets() { QList > list; PathToolOptionWidget * toolOptions = new PathToolOptionWidget(this); connect(this, SIGNAL(typeChanged(int)), toolOptions, SLOT(setSelectionType(int))); updateOptionsWidget(); toolOptions->setWindowTitle(i18n("Line/Curve")); list.append(toolOptions); return list; } void KoPathTool::pointTypeChanged(QAction *type) { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { QList selectedPoints = m_pointSelection.selectedPointsData(); QList pointToChange; QList::const_iterator it(selectedPoints.constBegin()); for (; it != selectedPoints.constEnd(); ++it) { KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex); if (point) { if (point->activeControlPoint1() && point->activeControlPoint2()) { pointToChange.append(*it); } } } if (!pointToChange.isEmpty()) { KoPathPointTypeCommand *cmd = new KoPathPointTypeCommand(pointToChange, static_cast(type->data().toInt())); d->canvas->addCommand(cmd); updateActions(); } } } void KoPathTool::insertPoints() { Q_D(KoToolBase); if (m_pointSelection.size() > 1) { QList segments(m_pointSelection.selectedSegmentsData()); if (!segments.isEmpty()) { KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, 0.5); d->canvas->addCommand(cmd); foreach (KoPathPoint * p, cmd->insertedPoints()) { m_pointSelection.add(p, false); } updateActions(); } } } void KoPathTool::removePoints() { Q_D(KoToolBase); // TODO finish current action or should this not possible during actions??? if (m_pointSelection.size() > 0) { KUndo2Command *cmd = KoPathPointRemoveCommand::createCommand(m_pointSelection.selectedPointsData(), d->canvas->shapeController()); PointHandle *pointHandle = dynamic_cast(m_activeHandle); if (pointHandle && m_pointSelection.contains(pointHandle->activePoint())) { delete m_activeHandle; m_activeHandle = 0; } m_pointSelection.clear(); d->canvas->addCommand(cmd); } } void KoPathTool::pointToLine() { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { QList selectedPoints = m_pointSelection.selectedPointsData(); QList pointToChange; QList::const_iterator it(selectedPoints.constBegin()); for (; it != selectedPoints.constEnd(); ++it) { KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex); if (point && (point->activeControlPoint1() || point->activeControlPoint2())) pointToChange.append(*it); } if (! pointToChange.isEmpty()) { d->canvas->addCommand(new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Line)); updateActions(); } } } void KoPathTool::pointToCurve() { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { QList selectedPoints = m_pointSelection.selectedPointsData(); QList pointToChange; QList::const_iterator it(selectedPoints.constBegin()); for (; it != selectedPoints.constEnd(); ++it) { KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex); if (point && (! point->activeControlPoint1() || ! point->activeControlPoint2())) pointToChange.append(*it); } if (! pointToChange.isEmpty()) { d->canvas->addCommand(new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Curve)); updateActions(); } } } void KoPathTool::segmentToLine() { Q_D(KoToolBase); if (m_pointSelection.size() > 1) { QList segments(m_pointSelection.selectedSegmentsData()); if (segments.size() > 0) { d->canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Line)); updateActions(); } } } void KoPathTool::segmentToCurve() { Q_D(KoToolBase); if (m_pointSelection.size() > 1) { QList segments(m_pointSelection.selectedSegmentsData()); if (segments.size() > 0) { d->canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Curve)); updateActions(); } } } void KoPathTool::convertToPath() { Q_D(KoToolBase); QList shapesToConvert; foreach(KoShape *shape, m_pointSelection.selectedShapes()) { KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) shapesToConvert.append(parameterShape); } if (shapesToConvert.count()) d->canvas->addCommand(new KoParameterToPathCommand(shapesToConvert)); updateOptionsWidget(); } void KoPathTool::joinPoints() { Q_D(KoToolBase); if (m_pointSelection.objectCount() == 1 && m_pointSelection.size() == 2) { QList pd(m_pointSelection.selectedPointsData()); const KoPathPointData & pd1 = pd.at(0); const KoPathPointData & pd2 = pd.at(1); KoPathShape * pathShape = pd1.pathShape; if (!pathShape->isClosedSubpath(pd1.pointIndex.first) && (pd1.pointIndex.second == 0 || pd1.pointIndex.second == pathShape->subpathPointCount(pd1.pointIndex.first) - 1) && !pathShape->isClosedSubpath(pd2.pointIndex.first) && (pd2.pointIndex.second == 0 || pd2.pointIndex.second == pathShape->subpathPointCount(pd2.pointIndex.first) - 1)) { KoSubpathJoinCommand *cmd = new KoSubpathJoinCommand(pd1, pd2); d->canvas->addCommand(cmd); } updateActions(); } } void KoPathTool::mergePoints() { Q_D(KoToolBase); if (m_pointSelection.objectCount() != 1 || m_pointSelection.size() != 2) return; QList pointData = m_pointSelection.selectedPointsData(); const KoPathPointData & pd1 = pointData.at(0); const KoPathPointData & pd2 = pointData.at(1); const KoPathPointIndex & index1 = pd1.pointIndex; const KoPathPointIndex & index2 = pd2.pointIndex; KoPathShape * path = pd1.pathShape; // check if subpaths are already closed if (path->isClosedSubpath(index1.first) || path->isClosedSubpath(index2.first)) return; // check if first point is an endpoint if (index1.second != 0 && index1.second != path->subpathPointCount(index1.first)-1) return; // check if second point is an endpoint if (index2.second != 0 && index2.second != path->subpathPointCount(index2.first)-1) return; // now we can start merging the endpoints KoPathPointMergeCommand *cmd = new KoPathPointMergeCommand(pd1, pd2); d->canvas->addCommand(cmd); updateActions(); } void KoPathTool::breakAtPoint() { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { d->canvas->addCommand(new KoPathBreakAtPointCommand(m_pointSelection.selectedPointsData())); updateActions(); } } void KoPathTool::breakAtSegment() { Q_D(KoToolBase); // only try to break a segment when 2 points of the same object are selected if (m_pointSelection.objectCount() == 1 && m_pointSelection.size() == 2) { QList segments(m_pointSelection.selectedSegmentsData()); if (segments.size() == 1) { d->canvas->addCommand(new KoPathSegmentBreakCommand(segments.at(0))); updateActions(); } } } void KoPathTool::paint(QPainter &painter, const KoViewConverter &converter) { Q_D(KoToolBase); painter.setRenderHint(QPainter::Antialiasing, true); // use different colors so that it is also visible on a background of the same color painter.setBrush(Qt::white); //TODO make configurable - painter.setPen(Qt::blue); + painter.setPen(QPen(Qt::blue, 0)); foreach(KoPathShape *shape, m_pointSelection.selectedShapes()) { painter.save(); painter.setTransform(shape->absoluteTransformation(&converter) * painter.transform()); KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) { parameterShape->paintHandles(painter, converter, m_handleRadius); } else { shape->paintPoints(painter, converter, m_handleRadius); } painter.restore(); } if (m_currentStrategy) { painter.save(); m_currentStrategy->paint(painter, converter); painter.restore(); } painter.setBrush(Qt::green); // TODO make color configurable - painter.setPen(Qt::blue); + painter.setPen(QPen(Qt::blue, 0)); m_pointSelection.paint(painter, converter); painter.setBrush(Qt::red); // TODO make color configurable - painter.setPen(Qt::blue); + painter.setPen(QPen(Qt::blue, 0)); if (m_activeHandle) { if (m_activeHandle->check(m_pointSelection.selectedShapes())) { m_activeHandle->paint(painter, converter); } else { delete m_activeHandle; m_activeHandle = 0; } } if (m_currentStrategy) { painter.save(); KoShape::applyConversion(painter, converter); d->canvas->snapGuide()->paint(painter, converter); painter.restore(); } } void KoPathTool::repaintDecorations() { foreach(KoShape *shape, m_pointSelection.selectedShapes()) { repaint(shape->boundingRect()); } m_pointSelection.repaint(); updateOptionsWidget(); } void KoPathTool::mousePressEvent(KoPointerEvent *event) { // we are moving if we hit a point and use the left mouse button event->ignore(); if (m_activeHandle) { m_currentStrategy = m_activeHandle->handleMousePress(event); event->accept(); } else { if (event->button() & Qt::LeftButton) { // check if we hit a path segment if (m_activeSegment && m_activeSegment->isValid()) { KoPathPointIndex index = m_activeSegment->path->pathPointIndex(m_activeSegment->segmentStart); KoPathPointData data(m_activeSegment->path, index); m_currentStrategy = new KoPathSegmentChangeStrategy(this, event->point, data, m_activeSegment->positionOnSegment); event->accept(); delete m_activeSegment; m_activeSegment = 0; } else { if ((event->modifiers() & Qt::ControlModifier) == 0) { m_pointSelection.clear(); } // start rubberband selection Q_ASSERT(m_currentStrategy == 0); m_currentStrategy = new KoPathPointRubberSelectStrategy(this, event->point); event->accept(); } } } } void KoPathTool::mouseMoveEvent(KoPointerEvent *event) { if (event->button() & Qt::RightButton) return; if (m_currentStrategy) { m_lastPoint = event->point; m_currentStrategy->handleMouseMove(event->point, event->modifiers()); // repaint new handle positions m_pointSelection.repaint(); if (m_activeHandle) m_activeHandle->repaint(); return; } delete m_activeSegment; m_activeSegment = 0; foreach(KoPathShape *shape, m_pointSelection.selectedShapes()) { QRectF roi = handleGrabRect(shape->documentToShape(event->point)); KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) { int handleId = parameterShape->handleIdAt(roi); if (handleId != -1) { useCursor(m_moveCursor); emit statusTextChanged(i18n("Drag to move handle.")); if (m_activeHandle) m_activeHandle->repaint(); delete m_activeHandle; if (KoConnectionShape * connectionShape = dynamic_cast(parameterShape)) { //qDebug() << "handleId" << handleId; m_activeHandle = new ConnectionHandle(this, connectionShape, handleId); m_activeHandle->repaint(); return; } else { //qDebug() << "handleId" << handleId; m_activeHandle = new ParameterHandle(this, parameterShape, handleId); m_activeHandle->repaint(); return; } } } else { QList points = shape->pointsAt(roi); if (! points.empty()) { // find the nearest control point from all points within the roi KoPathPoint * bestPoint = 0; KoPathPoint::PointType bestPointType = KoPathPoint::Node; qreal minDistance = HUGE_VAL; foreach(KoPathPoint *p, points) { // the node point must be hit if the point is not selected yet if (! m_pointSelection.contains(p) && ! roi.contains(p->point())) continue; // check for the control points first as otherwise it is no longer // possible to change the control points when they are the same as the point if (p->activeControlPoint1() && roi.contains(p->controlPoint1())) { qreal dist = squaredDistance(roi.center(), p->controlPoint1()); if (dist < minDistance) { bestPoint = p; bestPointType = KoPathPoint::ControlPoint1; minDistance = dist; } } if (p->activeControlPoint2() && roi.contains(p->controlPoint2())) { qreal dist = squaredDistance(roi.center(), p->controlPoint2()); if (dist < minDistance) { bestPoint = p; bestPointType = KoPathPoint::ControlPoint2; minDistance = dist; } } // check the node point at last qreal dist = squaredDistance(roi.center(), p->point()); if (dist < minDistance) { bestPoint = p; bestPointType = KoPathPoint::Node; minDistance = dist; } } if (! bestPoint) return; useCursor(m_moveCursor); if (bestPointType == KoPathPoint::Node) emit statusTextChanged(i18n("Drag to move point. Shift click to change point type.")); else emit statusTextChanged(i18n("Drag to move control point.")); PointHandle *prev = dynamic_cast(m_activeHandle); if (prev && prev->activePoint() == bestPoint && prev->activePointType() == bestPointType) return; // no change; if (m_activeHandle) m_activeHandle->repaint(); delete m_activeHandle; m_activeHandle = new PointHandle(this, bestPoint, bestPointType); m_activeHandle->repaint(); return; } } } useCursor(m_selectCursor); if (m_activeHandle) m_activeHandle->repaint(); delete m_activeHandle; m_activeHandle = 0; PathSegment *hoveredSegment = segmentAtPoint(event->point); if(hoveredSegment) { useCursor(Qt::PointingHandCursor); emit statusTextChanged(i18n("Drag to change curve directly. Double click to insert new path point.")); m_activeSegment = hoveredSegment; } else { uint selectedPointCount = m_pointSelection.size(); if (selectedPointCount == 0) emit statusTextChanged(""); else if (selectedPointCount == 1) emit statusTextChanged(i18n("Press B to break path at selected point.")); else emit statusTextChanged(i18n("Press B to break path at selected segments.")); } } void KoPathTool::mouseReleaseEvent(KoPointerEvent *event) { Q_D(KoToolBase); if (m_currentStrategy) { const bool hadNoSelection = !m_pointSelection.hasSelection(); m_currentStrategy->finishInteraction(event->modifiers()); KUndo2Command *command = m_currentStrategy->createCommand(); if (command) d->canvas->addCommand(command); if (hadNoSelection && dynamic_cast(m_currentStrategy) && !m_pointSelection.hasSelection()) { // the click didn't do anything at all. Allow it to be used by others. event->ignore(); } delete m_currentStrategy; m_currentStrategy = 0; if (m_pointSelection.selectedShapes().count() == 1) emit pathChanged(m_pointSelection.selectedShapes().first()); else emit pathChanged(0); } } void KoPathTool::keyPressEvent(QKeyEvent *event) { Q_D(KoToolBase); if (m_currentStrategy) { switch (event->key()) { case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_Shift: case Qt::Key_Meta: if (! event->isAutoRepeat()) { m_currentStrategy->handleMouseMove(m_lastPoint, event->modifiers()); } break; case Qt::Key_Escape: m_currentStrategy->cancelInteraction(); delete m_currentStrategy; m_currentStrategy = 0; break; default: event->ignore(); return; } } else { switch (event->key()) { // TODO move these to the actions in the constructor. case Qt::Key_I: { KoDocumentResourceManager *rm = d->canvas->shapeController()->resourceManager(); int handleRadius = rm->handleRadius(); if (event->modifiers() & Qt::ControlModifier) handleRadius--; else handleRadius++; rm->setHandleRadius(handleRadius); break; } #ifndef NDEBUG case Qt::Key_D: if (m_pointSelection.objectCount() == 1) { QList selectedPoints = m_pointSelection.selectedPointsData(); KoPathShapePrivate *p = static_cast(selectedPoints[0].pathShape->priv()); p->debugPath(); } break; #endif case Qt::Key_B: if (m_pointSelection.size() == 1) breakAtPoint(); else if (m_pointSelection.size() >= 2) breakAtSegment(); break; default: event->ignore(); return; } } event->accept(); } void KoPathTool::keyReleaseEvent(QKeyEvent *event) { if (m_currentStrategy) { switch (event->key()) { case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_Shift: case Qt::Key_Meta: if (! event->isAutoRepeat()) { m_currentStrategy->handleMouseMove(m_lastPoint, Qt::NoModifier); } break; default: break; } } event->accept(); } void KoPathTool::mouseDoubleClickEvent(KoPointerEvent *event) { Q_D(KoToolBase); event->ignore(); // check if we are doing something else at the moment if (m_currentStrategy) return; PathSegment *s = segmentAtPoint(event->point); if (!s) return; if (s->isValid()) { QList segments; segments.append(KoPathPointData(s->path, s->path->pathPointIndex(s->segmentStart))); KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, s->positionOnSegment); d->canvas->addCommand(cmd); foreach (KoPathPoint * p, cmd->insertedPoints()) { m_pointSelection.add(p, false); } updateActions(); event->accept(); } delete s; } KoPathTool::PathSegment* KoPathTool::segmentAtPoint(const QPointF &point) { Q_D(KoToolBase); // TODO: use global click proximity once added to the canvas resource provider const int clickProximity = 5; // convert click proximity to point using the current zoom level QPointF clickOffset = d->canvas->viewConverter()->viewToDocument(QPointF(clickProximity, clickProximity)); // the max allowed distance from a segment const qreal maxSquaredDistance = clickOffset.x()*clickOffset.x(); PathSegment *segment = new PathSegment; foreach(KoPathShape *shape, m_pointSelection.selectedShapes()) { KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) continue; // convert document point to shape coordinates QPointF p = shape->documentToShape(point); // our region of interest, i.e. a region around our mouse position QRectF roi(p - clickOffset, p + clickOffset); qreal minSqaredDistance = HUGE_VAL; // check all segments of this shape which intersect the region of interest QList segments = shape->segmentsAt(roi); foreach (const KoPathSegment &s, segments) { qreal nearestPointParam = s.nearestPoint(p); QPointF nearestPoint = s.pointAt(nearestPointParam); QPointF diff = p - nearestPoint; qreal squaredDistance = diff.x()*diff.x() + diff.y()*diff.y(); // are we within the allowed distance ? if (squaredDistance > maxSquaredDistance) continue; // are we closer to the last closest point ? if (squaredDistance < minSqaredDistance) { segment->path = shape; segment->segmentStart = s.first(); segment->positionOnSegment = nearestPointParam; } } } if (!segment->isValid()) { delete segment; segment = 0; } return segment; } void KoPathTool::activate(ToolActivation toolActivation, const QSet &shapes) { Q_D(KoToolBase); Q_UNUSED(toolActivation); // retrieve the actual global handle radius m_handleRadius = handleRadius(); d->canvas->snapGuide()->reset(); repaintDecorations(); QList selectedShapes; foreach(KoShape *shape, shapes) { KoPathShape *pathShape = dynamic_cast(shape); if (shape->isEditable() && pathShape) { // as the tool is just in activation repaintDecorations does not yet get called // so we need to use repaint of the tool and it is only needed to repaint the // current canvas repaint(pathShape->boundingRect()); selectedShapes.append(pathShape); } } if (selectedShapes.isEmpty()) { emit done(); return; } m_pointSelection.setSelectedShapes(selectedShapes); useCursor(m_selectCursor); connect(d->canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SLOT(activate())); updateOptionsWidget(); updateActions(); } void KoPathTool::activate() { Q_D(KoToolBase); QSet shapes; foreach(KoShape *shape, d->canvas->shapeManager()->selection()->selectedShapes()) { QSet delegates = shape->toolDelegates(); if (delegates.isEmpty()) { shapes << shape; } else { shapes += delegates; } } activate(DefaultActivation, shapes); } void KoPathTool::updateOptionsWidget() { PathToolOptionWidget::Types type; QList selectedShapes = m_pointSelection.selectedShapes(); foreach(KoPathShape *shape, selectedShapes) { KoParameterShape * parameterShape = dynamic_cast(shape); type |= parameterShape && parameterShape->isParametricShape() ? PathToolOptionWidget::ParametricShape : PathToolOptionWidget::PlainPath; } if (selectedShapes.count() == 1) emit pathChanged(selectedShapes.first()); else emit pathChanged(0); emit typeChanged(type); } void KoPathTool::updateActions() { const bool hasPointsSelected = m_pointSelection.hasSelection(); m_actionPathPointCorner->setEnabled(hasPointsSelected); m_actionPathPointSmooth->setEnabled(hasPointsSelected); m_actionPathPointSymmetric->setEnabled(hasPointsSelected); m_actionRemovePoint->setEnabled(hasPointsSelected); m_actionBreakPoint->setEnabled(hasPointsSelected); m_actionCurvePoint->setEnabled(hasPointsSelected); m_actionLinePoint->setEnabled(hasPointsSelected); bool hasSegmentsSelected = false; if (hasPointsSelected && m_pointSelection.size() > 1) hasSegmentsSelected = !m_pointSelection.selectedSegmentsData().isEmpty(); m_actionAddPoint->setEnabled(hasSegmentsSelected); m_actionLineSegment->setEnabled(hasSegmentsSelected); m_actionCurveSegment->setEnabled(hasSegmentsSelected); const uint objectCount = m_pointSelection.objectCount(); const uint pointCount = m_pointSelection.size(); m_actionBreakSegment->setEnabled(objectCount == 1 && pointCount == 2); m_actionJoinSegment->setEnabled(objectCount == 1 && pointCount == 2); m_actionMergePoints->setEnabled(objectCount == 1 && pointCount == 2); } void KoPathTool::deactivate() { Q_D(KoToolBase); disconnect(d->canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SLOT(activate())); m_pointSelection.clear(); m_pointSelection.setSelectedShapes(QList()); delete m_activeHandle; m_activeHandle = 0; delete m_activeSegment; m_activeSegment = 0; delete m_currentStrategy; m_currentStrategy = 0; d->canvas->snapGuide()->reset(); } void KoPathTool::documentResourceChanged(int key, const QVariant & res) { if (key == KoDocumentResourceManager::HandleRadius) { int oldHandleRadius = m_handleRadius; m_handleRadius = res.toUInt(); // repaint with the bigger of old and new handle radius int maxRadius = qMax(m_handleRadius, oldHandleRadius); foreach(KoPathShape *shape, m_pointSelection.selectedShapes()) { QRectF controlPointRect = shape->absoluteTransformation(0).map(shape->outline()).controlPointRect(); repaint(controlPointRect.adjusted(-maxRadius, -maxRadius, maxRadius, maxRadius)); } } } void KoPathTool::pointSelectionChanged() { Q_D(KoToolBase); updateActions(); d->canvas->snapGuide()->setIgnoredPathPoints(m_pointSelection.selectedPoints().toList()); emit selectionChanged(m_pointSelection.hasSelection()); } void KoPathTool::repaint(const QRectF &repaintRect) { Q_D(KoToolBase); //debugFlake <<"KoPathTool::repaint(" << repaintRect <<")" << m_handleRadius; // widen border to take antialiasing into account qreal radius = m_handleRadius + 1; d->canvas->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); } void KoPathTool::deleteSelection() { removePoints(); } KoToolSelection * KoPathTool::selection() { return &m_pointSelection; } diff --git a/libs/pageapp/KoPAPageBase.cpp b/libs/pageapp/KoPAPageBase.cpp index f5312ceaed7..ebb76efac1b 100644 --- a/libs/pageapp/KoPAPageBase.cpp +++ b/libs/pageapp/KoPAPageBase.cpp @@ -1,351 +1,351 @@ /* This file is part of the KDE project Copyright (C) 2006-2010 Thorsten Zachmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPAPageBase.h" #include "KoPASavingContext.h" #include "KoPALoadingContext.h" #include "KoPAPixmapCache.h" #include "KoPAPageContainerModel.h" #include "KoPAUtil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include KoPAPageBase::KoPAPageBase() : KoShapeContainer( new KoPAPageContainerModel() ) { // Add a default layer KoShapeLayer* layer = new KoShapeLayer; addShape(layer); } KoPAPageBase::~KoPAPageBase() { } void KoPAPageBase::paintComponent(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); } void KoPAPageBase::paintBackground( QPainter & painter, const KoViewConverter & converter, KoShapePaintingContext &paintContext ) { painter.save(); applyConversion( painter, converter ); KoPageLayout layout = pageLayout(); - painter.setPen( Qt::black ); + painter.setPen(QPen(Qt::black, 0)); if (background()) { QPainterPath p; p.addRect( QRectF( 0.0, 0.0, layout.width, layout.height ) ); background()->paint( painter, converter, paintContext, p ); } else { painter.setBrush(Qt::white); painter.drawRect(QRectF(0.0, 0.0, layout.width, layout.height)); } painter.restore(); } void KoPAPageBase::saveOdfPageContent( KoPASavingContext & paContext ) const { saveOdfLayers(paContext); saveOdfShapes( paContext ); saveOdfAnimations( paContext ); saveOdfPresentationNotes( paContext ); } void KoPAPageBase::saveOdfLayers(KoPASavingContext &paContext) const { QList shapes(this->shapes()); qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); foreach(KoShape* shape, shapes) { KoShapeLayer *layer = dynamic_cast(shape); if (layer) { paContext.addLayerForSaving(layer); } else { Q_ASSERT(layer); warnPageApp << "Page contains non layer where a layer is expected"; } } paContext.saveLayerSet(paContext.xmlWriter()); paContext.clearLayers(); } void KoPAPageBase::saveOdfShapes( KoShapeSavingContext &context ) const { QList shapes(this->shapes()); QList tlshapes( shapes ); qSort( tlshapes.begin(), tlshapes.end(), KoShape::compareShapeZIndex ); foreach( KoShape *shape, tlshapes ) { shape->saveOdf( context ); } } QString KoPAPageBase::saveOdfPageStyle( KoPASavingContext &paContext ) const { KoGenStyle style( KoGenStyle::DrawingPageAutoStyle, "drawing-page" ); if ( paContext.isSet( KoShapeSavingContext::AutoStyleInStyleXml ) ) { style.setAutoStyleInStylesDotXml( true ); } saveOdfPageStyleData( style, paContext ); return paContext.mainStyles().insert( style, "dp" ); } void KoPAPageBase::saveOdfPageStyleData( KoGenStyle &style, KoPASavingContext &paContext ) const { QSharedPointer bg = background(); if( bg ) bg->fillStyle( style, paContext ); } bool KoPAPageBase::saveOdfAnimations( KoPASavingContext & paContext ) const { Q_UNUSED( paContext ); return true; } bool KoPAPageBase::saveOdfPresentationNotes(KoPASavingContext &paContext) const { Q_UNUSED( paContext ); return true; } bool KoPAPageBase::loadOdf( const KoXmlElement &element, KoShapeLoadingContext & loadingContext ) { KoPALoadingContext &paContext = static_cast( loadingContext ); KoStyleStack& styleStack = loadingContext.odfLoadingContext().styleStack(); styleStack.save(); loadingContext.odfLoadingContext().fillStyleStack( element, KoXmlNS::draw, "style-name", "drawing-page" ); styleStack.setTypeProperties( "drawing-page" ); loadOdfPageTag(element, paContext); styleStack.restore(); // load layers and shapes const KoXmlElement & pageLayerSet = KoXml::namedItemNS( element, KoXmlNS::draw, "layer-set" ); const KoXmlElement & usedPageLayerSet = pageLayerSet.isNull() ? loadingContext.odfLoadingContext().stylesReader().layerSet(): pageLayerSet; int layerZIndex = 0; bool first = true; KoXmlElement layerElement; forEachElement( layerElement, usedPageLayerSet ) { KoShapeLayer * layer = 0; if ( first ) { first = false; layer = dynamic_cast( shapes().first() ); Q_ASSERT( layer ); } else { layer = new KoShapeLayer(); addShape( layer ); } if ( layer ) { layer->setZIndex( layerZIndex++ ); layer->loadOdf( layerElement, loadingContext ); } } KoShapeLayer * layer = dynamic_cast( shapes().first() ); if ( layer ) { KoXmlElement child; forEachElement( child, element ) { debugPageApp <<"loading shape" << child.localName(); KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf( child, loadingContext ); if ( shape ) { if( ! shape->parent() ) { layer->addShape( shape ); } } } } loadOdfPageExtra(element, paContext); return true; } void KoPAPageBase::loadOdfPageTag( const KoXmlElement &element, KoPALoadingContext &loadingContext ) { Q_UNUSED(element); KoStyleStack& styleStack = loadingContext.odfLoadingContext().styleStack(); if ( styleStack.hasProperty( KoXmlNS::draw, "fill" ) ) { setBackground(loadOdfFill(loadingContext)); } } void KoPAPageBase::loadOdfPageExtra( const KoXmlElement &element, KoPALoadingContext & loadingContext ) { Q_UNUSED( element ); Q_UNUSED( loadingContext ); } QSizeF KoPAPageBase::size() const { const KoPageLayout layout = pageLayout(); return QSize( layout.width, layout.height ); } QRectF KoPAPageBase::boundingRect() const { //return KoShapeContainer::boundingRect(); return contentRect().united(QRectF(QPointF(0, 0), size() )); } QRectF KoPAPageBase::contentRect() const { QRectF bb; foreach (KoShape* layer, shapes()) { if (bb.isNull()) { bb = layer->boundingRect(); } else { bb = bb.united(layer->boundingRect()); } } return bb; } void KoPAPageBase::shapeAdded( KoShape * shape ) { Q_UNUSED( shape ); } void KoPAPageBase::shapeRemoved( KoShape * shape ) { Q_UNUSED( shape ); } KoPageApp::PageType KoPAPageBase::pageType() const { return KoPageApp::Page; } QPixmap KoPAPageBase::thumbnail( const QSize& size ) { #ifdef CACHE_PAGE_THUMBNAILS QString key = thumbnailKey(); QPixmap pm; if ( !KoPAPixmapCache::instance()->find( key, size, pm ) ) { pm = generateThumbnail( size ); KoPAPixmapCache::instance()->insert( key, pm, size ); debugPageApp << "create thumbnail" << this << key << size; } else { //debugPageApp << "thumbnail in cache " << this; } return pm; #else return generateThumbnail( size ); #endif } QPixmap KoPAPageBase::generateThumbnail(const QSize &size) { // don't paint null pixmap if ( size.isEmpty() ) // either width or height is <= 0 return QPixmap(); KoZoomHandler zoomHandler; QSize thumbnailSize(size); KoPAUtil::setSizeAndZoom(pageLayout(), thumbnailSize, zoomHandler); QPixmap pixmap(thumbnailSize); // paint white as default page background pixmap.fill(Qt::white); QPainter painter(&pixmap); painter.setClipRect(QRect(QPoint(0, 0), thumbnailSize)); painter.setRenderHint(QPainter::Antialiasing); paintPage(painter, zoomHandler); return pixmap; } QImage KoPAPageBase::thumbImage(const QSize &size) { if (size.isEmpty()) { return QImage(); } KoZoomHandler zoomHandler; QSize thumbnailSize(size); KoPAUtil::setSizeAndZoom(pageLayout(), thumbnailSize, zoomHandler); QImage image(thumbnailSize, QImage::Format_RGB32); // paint white as default page background image.fill(QColor(Qt::white).rgb()); QPainter painter(&image); painter.setClipRect(QRect(QPoint(0, 0), thumbnailSize)); painter.setRenderHint(QPainter::Antialiasing); paintPage(painter, zoomHandler); return image; } void KoPAPageBase::pageUpdated() { KoPAPixmapCache::instance()->remove( thumbnailKey() ); } QString KoPAPageBase::thumbnailKey() const { QString key; key.sprintf( "%p", static_cast( this ) ); return key; } KoShapeManagerPaintingStrategy * KoPAPageBase::getPaintingStrategy() const { return 0; } diff --git a/libs/pageapp/KoPAViewModeNormal.cpp b/libs/pageapp/KoPAViewModeNormal.cpp index 8fcd450899a..5973956e598 100644 --- a/libs/pageapp/KoPAViewModeNormal.cpp +++ b/libs/pageapp/KoPAViewModeNormal.cpp @@ -1,247 +1,247 @@ /* This file is part of the KDE project * Copyright (C) 2007-2008 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPAViewModeNormal.h" #include #include #include #include #include #include #include #include "KoPACanvasBase.h" #include "KoPADocument.h" #include "KoPAPage.h" #include "KoPAMasterPage.h" #include "KoPAViewBase.h" #include "commands/KoPAChangePageLayoutCommand.h" KoPAViewModeNormal::KoPAViewModeNormal( KoPAViewBase * view, KoPACanvasBase * canvas ) : KoPAViewMode( view, canvas ) , m_masterMode( false ) , m_savedPage( 0 ) { } KoPAViewModeNormal::~KoPAViewModeNormal() { } void KoPAViewModeNormal::paint(KoPACanvasBase* canvas, QPainter& painter, const QRectF &paintRect) { #ifdef NDEBUG Q_UNUSED(canvas) #else Q_ASSERT( m_canvas == canvas ); #endif painter.translate( -m_canvas->documentOffset() ); painter.setRenderHint( QPainter::Antialiasing ); QRect clipRect = paintRect.translated( m_canvas->documentOffset() ).toRect(); painter.setClipRect( clipRect ); painter.translate( m_canvas->documentOrigin().x(), m_canvas->documentOrigin().y() ); KoViewConverter * converter = m_view->viewConverter( m_canvas ); QRectF updateRect = converter->viewToDocument( m_canvas->widgetToView( clipRect ) ); KoShapePaintingContext context; m_view->activePage()->paintBackground( painter, *converter, context ); const KoPageLayout &layout = activePageLayout(); QSizeF pageSize(layout.width, layout.height); QRectF gridRect = QRectF(QPointF(), pageSize).intersected(updateRect); if (m_canvas->document()->gridData().paintGridInBackground()) { painter.setRenderHint(QPainter::Antialiasing, false); m_canvas->document()->gridData().paintGrid(painter, *converter, gridRect ); } // paint the page margins paintMargins( painter, *converter ); painter.setRenderHint( QPainter::Antialiasing ); // paint the shapes if ( m_view->activePage()->displayMasterShapes() ) { m_canvas->masterShapeManager()->paint( painter, *converter, false ); } m_canvas->shapeManager()->paint( painter, *converter, false ); painter.setRenderHint( QPainter::Antialiasing, false ); if (!m_canvas->document()->gridData().paintGridInBackground()) { m_canvas->document()->gridData().paintGrid(painter, *converter, gridRect); } m_canvas->document()->guidesData().paintGuides(painter, *converter, updateRect); painter.setRenderHint(QPainter::Antialiasing); m_toolProxy->paint(painter, *converter); } void KoPAViewModeNormal::tabletEvent( QTabletEvent *event, const QPointF &point ) { m_toolProxy->tabletEvent( event, point ); } void KoPAViewModeNormal::mousePressEvent( QMouseEvent *event, const QPointF &point ) { m_toolProxy->mousePressEvent( event, point ); } void KoPAViewModeNormal::mouseDoubleClickEvent( QMouseEvent *event, const QPointF &point ) { m_toolProxy->mouseDoubleClickEvent( event, point ); } void KoPAViewModeNormal::mouseMoveEvent( QMouseEvent *event, const QPointF &point ) { m_toolProxy->mouseMoveEvent( event, point ); } void KoPAViewModeNormal::mouseReleaseEvent( QMouseEvent *event, const QPointF &point ) { m_toolProxy->mouseReleaseEvent( event, point ); } void KoPAViewModeNormal::shortcutOverrideEvent( QKeyEvent *event ) { m_toolProxy->shortcutOverrideEvent( event ); } void KoPAViewModeNormal::keyPressEvent( QKeyEvent *event ) { m_toolProxy->keyPressEvent( event ); if ( ! event->isAccepted() ) { event->accept(); switch ( event->key() ) { case Qt::Key_Home: m_view->navigatePage( KoPageApp::PageFirst ); break; case Qt::Key_PageUp: m_view->navigatePage( KoPageApp::PagePrevious ); break; case Qt::Key_PageDown: m_view->navigatePage( KoPageApp::PageNext ); break; case Qt::Key_End: m_view->navigatePage( KoPageApp::PageLast ); break; default: event->ignore(); break; } } } void KoPAViewModeNormal::keyReleaseEvent( QKeyEvent *event ) { m_toolProxy->keyReleaseEvent( event ); } void KoPAViewModeNormal::wheelEvent( QWheelEvent * event, const QPointF &point ) { m_toolProxy->wheelEvent( event, point ); } void KoPAViewModeNormal::setMasterMode( bool master ) { m_masterMode = master; KoPAPage * page = dynamic_cast( m_view->activePage() ); if ( m_masterMode ) { if ( page ) { m_view->doUpdateActivePage( page->masterPage() ); m_savedPage = page; } } else if ( m_savedPage ) { m_view->doUpdateActivePage( m_savedPage ); m_savedPage = 0; } } bool KoPAViewModeNormal::masterMode() { return m_masterMode; } void KoPAViewModeNormal::addShape( KoShape *shape ) { // the KoShapeController sets the active layer as parent KoPAPageBase * page( m_view->kopaDocument()->pageByShape( shape ) ); bool isMaster = dynamic_cast( page ) != 0; KoPAPage * p; if ( page == m_view->activePage() ) { m_view->kopaCanvas()->shapeManager()->addShape( shape ); } else if ( isMaster && ( p = dynamic_cast( m_view->activePage() ) ) != 0 ) { if ( p->masterPage() == page ) { m_view->kopaCanvas()->masterShapeManager()->addShape( shape ); } } } void KoPAViewModeNormal::removeShape( KoShape *shape ) { KoPAPageBase * page( m_view->kopaDocument()->pageByShape( shape ) ); bool isMaster = dynamic_cast( page ) != 0; KoPAPage * p; if ( page == m_view->activePage() ) { m_view->kopaCanvas()->shapeManager()->remove( shape ); } else if ( isMaster && ( p = dynamic_cast( m_view->activePage() ) ) != 0 ) { if ( p->masterPage() == page ) { m_view->kopaCanvas()->masterShapeManager()->remove( shape ); } } } void KoPAViewModeNormal::changePageLayout( const KoPageLayout &pageLayout, bool applyToDocument, KUndo2Command *parent ) { KoPAPageBase *page = m_view->activePage(); KoPAMasterPage *masterPage = dynamic_cast( page ); if ( !masterPage ) { masterPage = static_cast( page )->masterPage(); } new KoPAChangePageLayoutCommand( m_canvas->document(), masterPage, pageLayout, applyToDocument, parent ); } void KoPAViewModeNormal::paintMargins( QPainter &painter, const KoViewConverter &converter ) { KoPAPageBase *page = m_view->activePage(); KoPageLayout pl = page->pageLayout(); QSizeF pageSize = QSizeF( pl.width, pl.height ); QRectF marginRect( pl.leftMargin, pl.topMargin, pageSize.width() - pl.leftMargin - pl.rightMargin, pageSize.height() - pl.topMargin - pl.bottomMargin ); - QPen pen( Qt::gray ); + QPen pen(Qt::gray, 0); painter.setPen( pen ); painter.drawRect( converter.documentToView( marginRect ) ); } diff --git a/libs/textlayout/KoTextLayoutTableArea.cpp b/libs/textlayout/KoTextLayoutTableArea.cpp index 4376c6b7822..d4ccef2cc29 100644 --- a/libs/textlayout/KoTextLayoutTableArea.cpp +++ b/libs/textlayout/KoTextLayoutTableArea.cpp @@ -1,1175 +1,1175 @@ /* This file is part of the KDE project * Copyright (C) 2009 Elvis Stansvik * Copyright (C) 2011 C. Boemann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoTextLayoutTableArea.h" #include "KoTextLayoutCellHelper.h" #include "TableIterator.h" #include "KoPointedAt.h" #include "KoCharAreaInfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "FrameIterator.h" class Q_DECL_HIDDEN KoTextLayoutTableArea::Private { public: Private() : startOfArea(0) { } QVector > cellAreas; TableIterator *startOfArea; TableIterator *endOfArea; bool lastRowHasSomething; QTextTable *table; int headerRows; qreal headerOffsetX; qreal headerOffsetY; KoTableColumnAndRowStyleManager carsManager; qreal tableWidth; QVector headerRowPositions; // we will only fill those that this area covers QVector rowPositions; // we will only fill those that this area covers QVector columnWidths; QVector columnPositions; bool collapsing; bool totalMisFit; KoTextDocumentLayout *documentLayout; KoTableCellStyle effectiveCellStyle(const QTextTableCell &tableCell); }; KoTableCellStyle KoTextLayoutTableArea::Private::effectiveCellStyle(const QTextTableCell &tableCell) { QTextTableFormat tableFormat = table->format(); KoTableCellStyle cellStyle(tableCell.format().toTableCellFormat()); if (documentLayout->styleManager() && table->format().hasProperty(KoTableStyle::TableTemplate)) { if (KoTextTableTemplate *tableTemplate = documentLayout->styleManager()->tableTemplate(table->format().intProperty(KoTableStyle::TableTemplate))) { //priorities according to ODF 1.2, 16.18 - table:table-template if (tableCell.column() == 0 && tableTemplate->firstColumn() && tableFormat.boolProperty(KoTableStyle::UseFirstColumnStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->firstColumn())); return cellStyle; } if (tableCell.column() == (table->columns() - 1) && tableTemplate->lastColumn() && tableFormat.boolProperty(KoTableStyle::UseLastColumnStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->lastColumn())); return cellStyle; } if (tableCell.row() == 0 && tableTemplate->firstRow() && tableFormat.boolProperty(KoTableStyle::UseFirstRowStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->firstRow())); return cellStyle; } if (tableCell.row() == (table->rows() - 1) && tableTemplate->lastRow() && tableFormat.boolProperty(KoTableStyle::UseLastRowStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->lastRow())); return cellStyle; } if (((tableCell.row() + 1) % 2) == 0 && tableTemplate->evenRows() && tableFormat.boolProperty(KoTableStyle::UseBandingRowStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->evenRows())); return cellStyle; } if (((tableCell.row() + 1) % 2) != 0 && tableTemplate->oddRows() && tableFormat.boolProperty(KoTableStyle::UseBandingRowStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->oddRows())); return cellStyle; } if (((tableCell.column() + 1) % 2) == 0 && tableTemplate->evenColumns() && tableFormat.boolProperty(KoTableStyle::UseBandingColumnStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->evenColumns())); return cellStyle; } if (((tableCell.column() + 1) % 2) != 0 && tableTemplate->oddColumns() && tableFormat.boolProperty(KoTableStyle::UseBandingColumnStyles)) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->oddColumns())); return cellStyle; } if (tableTemplate->body()) { cellStyle = *(documentLayout->styleManager()->tableCellStyle(tableTemplate->body())); } } } return cellStyle; } KoTextLayoutTableArea::KoTextLayoutTableArea(QTextTable *table, KoTextLayoutArea *parent, KoTextDocumentLayout *documentLayout) : KoTextLayoutArea(parent, documentLayout) , d(new Private) { Q_ASSERT(table); Q_ASSERT(parent); d->table = table; d->documentLayout = documentLayout; d->carsManager = KoTableColumnAndRowStyleManager::getManager(table); // Resize geometry vectors for the table. d->rowPositions.resize(table->rows() + 1); d->headerRowPositions.resize(table->rows() + 1); d->cellAreas.resize(table->rows()); for (int row = 0; row < table->rows(); ++row) { d->cellAreas[row].resize(table->columns()); } d->collapsing = d->table->format().boolProperty(KoTableStyle::CollapsingBorders); } KoTextLayoutTableArea::~KoTextLayoutTableArea() { for (int row = d->startOfArea->row; row < d->cellAreas.size(); ++row) { for (int col = 0; col < d->cellAreas[row].size(); ++col) { delete d->cellAreas[row][col]; } } delete d->startOfArea; delete d->endOfArea; delete d; } KoPointedAt KoTextLayoutTableArea::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const { int firstRow = qMax(d->startOfArea->row, d->headerRows); int lastRow = d->endOfArea->row; if (d->lastRowHasSomething == false) { --lastRow; } if (lastRow < d->startOfArea->row) { return KoPointedAt(); // empty } // Test normal cells. if (point.y() > d->rowPositions[firstRow] - 3.0 && point.y() < d->rowPositions[lastRow + 1] + 3.0) { QVector::const_iterator start = d->rowPositions.constBegin() + firstRow; QVector::const_iterator end = d->rowPositions.constBegin() + lastRow + 1; int row = qLowerBound(start, end, point.y()) - d->rowPositions.constBegin() - 1; int column = qLowerBound(d->columnPositions, point.x()) - d->columnPositions.constBegin() - 1; if (point.y() < d->rowPositions[firstRow]) { ++row; } column = qBound(0, column, d->table->columns() - 1); KoPointedAt pointedAt; if (qAbs(d->columnPositions[column] - point.x()) < 3.0) { pointedAt.tableHit = KoPointedAt::ColumnDivider; } else if (qAbs(d->columnPositions[column+1] - point.x()) < 3.0) { pointedAt.tableHit = KoPointedAt::ColumnDivider; ++column; } else if (d->columnPositions[0] < point.x() && point.x() < d->columnPositions[d->table->columns()] && qAbs(d->rowPositions[row] - point.y()) < 3.0) { pointedAt.tableHit = KoPointedAt::RowDivider; } else if (d->columnPositions[0] < point.x() && point.x() < d->columnPositions[d->table->columns()] && qAbs(d->rowPositions[row+1] - point.y()) < 3.0) { pointedAt.tableHit = KoPointedAt::RowDivider; ++row; } else { QTextTableCell cell = d->table->cellAt(row, column); pointedAt = d->cellAreas[cell.row()][cell.column()]->hitTest(point, accuracy); } if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { if (column > 0) { pointedAt.tableLeadSize = d->columnPositions[column] - d->columnPositions[column-1]; } if (column < d->table->columns()) { pointedAt.tableTrailSize = d->columnPositions[column+1] - d->columnPositions[column]; } } else if (pointedAt.tableHit == KoPointedAt::RowDivider) { if (row > 0) { pointedAt.tableLeadSize = d->rowPositions[row] - d->rowPositions[row-1]; } if (row < d->table->rows()) { pointedAt.tableTrailSize = d->rowPositions[row+1] - d->rowPositions[row]; } } pointedAt.table = d->table; pointedAt.tableRowDivider = row; pointedAt.tableColumnDivider = column; pointedAt.tableDividerPos = QPointF(d->columnPositions[column],d->rowPositions[row]); return pointedAt; } // Test header row cells. QPointF headerPoint = point - QPointF(d->headerOffsetX, d->headerOffsetY); if (headerPoint.y() > d->headerRowPositions.first() && headerPoint.y() < d->headerRowPositions[d->headerRows]) { QVector::const_iterator start = d->headerRowPositions.constBegin(); QVector::const_iterator end = d->headerRowPositions.constBegin() + d->headerRows; int row = qLowerBound(start, end, headerPoint.y()) - d->headerRowPositions.constBegin() - 1; int column = qLowerBound(d->columnPositions, headerPoint.x()) - d->columnPositions.constBegin() - 1; column = qBound(0, column, d->table->columns() - 1); KoPointedAt pointedAt; if (qAbs(d->columnPositions[column] - headerPoint.x()) < 3.0) { pointedAt.tableHit = KoPointedAt::ColumnDivider; } else if (qAbs(d->columnPositions[column+1] - headerPoint.x()) < 3.0) { pointedAt.tableHit = KoPointedAt::ColumnDivider; ++column; } else { QTextTableCell cell = d->table->cellAt(row, column); pointedAt = d->cellAreas[cell.row()][cell.column()]->hitTest(headerPoint, accuracy); } if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { if (column > 0) { pointedAt.tableLeadSize = d->columnPositions[column] - d->columnPositions[column-1]; } if (column < d->table->columns()) { pointedAt.tableTrailSize = d->columnPositions[column+1] - d->columnPositions[column]; } } pointedAt.table = d->table; pointedAt.tableRowDivider = row; pointedAt.tableColumnDivider = column; pointedAt.tableDividerPos = QPointF(d->columnPositions[column],d->rowPositions[row]); return pointedAt; } return KoPointedAt(); } QVector KoTextLayoutTableArea::generateCharAreaInfos() const { QVector result; if (d->startOfArea == 0) { // We have not been layouted yet return result; } int lastRow = d->endOfArea->row; if (d->lastRowHasSomething == false) { --lastRow; } if (lastRow < d->startOfArea->row) { return result; // empty } // TODO: check why paint() does use visitedCells only for non-header rows QSet > visitedCells; const int firstRow = qMax(d->startOfArea->row, d->headerRows); for (int row = 0; row < d->headerRows; ++row) { // TODO: use lambda to shware inner loop with below for (int column = 0; column < d->table->columns(); ++column) { QTextTableCell tableCell = d->table->cellAt(row, column); const int testRow = (row == firstRow ? tableCell.row() : row); if (d->cellAreas[testRow][column] && !visitedCells.contains(QPair(testRow, column))) { const int column = tableCell.column(); result.append(d->cellAreas[testRow][column]->generateCharAreaInfos()); visitedCells.insert(QPair(testRow, column)); } } } for (int row = firstRow; row <= lastRow; ++row) { for (int column = 0; column < d->table->columns(); ++column) { QTextTableCell tableCell = d->table->cellAt(row, column); const int testRow = (row == firstRow ? tableCell.row() : row); if (d->cellAreas[testRow][column] && !visitedCells.contains(QPair(testRow, column))) { const int column = tableCell.column(); result.append(d->cellAreas[testRow][column]->generateCharAreaInfos()); visitedCells.insert(QPair(testRow, column)); } } } return result; } QRectF KoTextLayoutTableArea::selectionBoundingBox(QTextCursor &cursor) const { int lastRow = d->endOfArea->row; if (d->lastRowHasSomething == false) { --lastRow; } if (lastRow < d->startOfArea->row) { return QRectF(); // empty } int firstRow = qMax(d->startOfArea->row, d->headerRows); QTextTableCell startTableCell = d->table->cellAt(cursor.selectionStart()); QTextTableCell endTableCell = d->table->cellAt(cursor.selectionEnd()); if (startTableCell == endTableCell) { if (startTableCell.row() < d->startOfArea->row || startTableCell.row() > lastRow) { return QRectF(); // cell is not in this area } KoTextLayoutArea *area = d->cellAreas[startTableCell.row()][startTableCell.column()]; Q_ASSERT(area); return area->selectionBoundingBox(cursor); } else { int selectionRow; int selectionColumn; int selectionRowSpan; int selectionColumnSpan; cursor.selectedTableCells(&selectionRow, &selectionRowSpan, &selectionColumn, &selectionColumnSpan); qreal top, bottom; if (selectionRow < d->headerRows) { top = d->headerRowPositions[selectionRow] + d->headerOffsetY; } else { top = d->rowPositions[qMin(qMax(firstRow, selectionRow), lastRow)]; } if (selectionRow + selectionRowSpan < d->headerRows) { bottom = d->headerRowPositions[selectionRow + selectionRowSpan] + d->headerOffsetY; } else { bottom = d->rowPositions[d->headerRows] + d->headerOffsetY; if (selectionRow + selectionRowSpan >= firstRow) { bottom = d->rowPositions[qMin(selectionRow + selectionRowSpan, lastRow + 1)]; } } return QRectF(d->columnPositions[selectionColumn], top, d->columnPositions[selectionColumn + selectionColumnSpan] - d->columnPositions[selectionColumn], bottom - top); } } bool KoTextLayoutTableArea::layoutTable(TableIterator *cursor) { d->startOfArea = new TableIterator(cursor); d->headerRows = cursor->headerRows; d->totalMisFit = false; // If table is done we create an empty area and return true if (cursor->row == d->table->rows()) { setBottom(top()); d->endOfArea = new TableIterator(cursor); return true; } layoutColumns(); bool first = cursor->row == 0 && (d->cellAreas[0][0] == 0); if (first) { // are we at the beginning of the table cursor->row = 0; d->rowPositions[0] = top() + d->table->format().topMargin(); d->headerOffsetX = 0; d->headerOffsetY = 0; } else { for (int row = 0; row < d->headerRows; ++row) { // Copy header rows d->headerRowPositions[row] = cursor->headerRowPositions[row]; for (int col = 0; col < d->table->columns(); ++col) { d->cellAreas[row][col] = cursor->headerCellAreas[row][col]; } } if (d->headerRows) { // Also set the position of the border below headers d->headerRowPositions[d->headerRows] = cursor->headerRowPositions[d->headerRows]; } // If headerRows == 0 then the following reduces to: d->rowPositions[cursor->row] = top() d->headerOffsetY = top() - d->headerRowPositions[0]; d->rowPositions[cursor->row] = d->headerRowPositions[d->headerRows] + d->headerOffsetY; // headerOffsetX should also be set d->headerOffsetX = d->columnPositions[0] - cursor->headerPositionX; } bool complete = first; qreal topBorderWidth = 0; qreal bottomBorderWidth = 0; qreal dummyWidth = 0; collectBorderThicknesss(cursor->row - 1, dummyWidth, topBorderWidth); collectBorderThicknesss(cursor->row, topBorderWidth, bottomBorderWidth); do { qreal nextBottomBorderWidth = 0; collectBorderThicknesss(cursor->row+1, bottomBorderWidth, nextBottomBorderWidth); d->lastRowHasSomething = false; complete = layoutRow(cursor, topBorderWidth, bottomBorderWidth); setBottom(d->rowPositions[cursor->row + 1] + bottomBorderWidth); topBorderWidth = bottomBorderWidth; bottomBorderWidth = nextBottomBorderWidth; if (complete) { setVirginPage(false); cursor->row++; } } while (complete && cursor->row < d->table->rows()); if (cursor->row == d->table->rows()) { d->lastRowHasSomething = false; } if (first) { // were we at the beginning of the table for (int row = 0; row < d->headerRows; ++row) { // Copy header rows cursor->headerRowPositions[row] = d->rowPositions[row]; d->headerRowPositions[row] = d->rowPositions[row]; for (int col = 0; col < d->table->columns(); ++col) { cursor->headerCellAreas[row][col] = d->cellAreas[row][col]; } } if (d->headerRows) { // Also set the position of the border below headers cursor->headerRowPositions[d->headerRows] = d->rowPositions[d->headerRows]; d->headerRowPositions[d->headerRows] = d->rowPositions[d->headerRows]; } cursor->headerPositionX = d->columnPositions[0]; if (!virginPage() && d->totalMisFit) { //if we couldn't fit the header rows plus some then don't even try cursor->row = 0; nukeRow(cursor); } } d->endOfArea = new TableIterator(cursor); return complete; } void KoTextLayoutTableArea::layoutColumns() { QTextTableFormat tableFormat = d->table->format(); d->columnPositions.resize(d->table->columns() + 1); d->columnWidths.resize(d->table->columns() + 1); // Table width. d->tableWidth = 0; qreal parentWidth = right() - left(); if (tableFormat.width().rawValue() == 0 || tableFormat.alignment() == Qt::AlignJustify) { // We got a zero width value or alignment is justify, so use 100% of parent. d->tableWidth = parentWidth - tableFormat.leftMargin() - tableFormat.rightMargin(); } else { if (tableFormat.width().type() == QTextLength::FixedLength) { // Fixed length value, so use the raw value directly. d->tableWidth = tableFormat.width().rawValue(); } else if (tableFormat.width().type() == QTextLength::PercentageLength) { // Percentage length value, so use a percentage of parent width. d->tableWidth = tableFormat.width().rawValue() * (parentWidth / 100) - tableFormat.leftMargin() - tableFormat.rightMargin(); } else { // Unknown length type, so use 100% of parent. d->tableWidth = parentWidth - tableFormat.leftMargin() - tableFormat.rightMargin(); } } // Column widths. qreal availableWidth = d->tableWidth; // Width available for columns. QList fixedWidthColumns; // List of fixed width columns. QList relativeWidthColumns; // List of relative width columns. qreal relativeWidthSum = 0; // Sum of relative column width values. int numNonStyleColumns = 0; for (int col = 0; col < d->table->columns(); ++col) { KoTableColumnStyle columnStyle = d->carsManager.columnStyle(col); if (columnStyle.hasProperty(KoTableColumnStyle::RelativeColumnWidth)) { // Relative width specified. Will be handled in the next loop. d->columnWidths[col] = 0.0; relativeWidthColumns.append(col); relativeWidthSum += columnStyle.relativeColumnWidth(); } else if (columnStyle.hasProperty(KoTableColumnStyle::ColumnWidth)) { // Only width specified, so use it. d->columnWidths[col] = columnStyle.columnWidth(); fixedWidthColumns.append(col); availableWidth -= columnStyle.columnWidth(); } else { // Neither width nor relative width specified. d->columnWidths[col] = 0.0; relativeWidthColumns.append(col); // handle it as a relative width column without asking for anything ++numNonStyleColumns; } } // Handle the case that the fixed size columns are larger then the defined table width if (availableWidth < 0.0) { if (tableFormat.width().rawValue() == 0 && fixedWidthColumns.count() > 0) { // If not table width was defined then we need to scale down the fixed size columns so they match // into the width of the table. qreal diff = (-availableWidth) / qreal(fixedWidthColumns.count()); foreach(int col, fixedWidthColumns) { d->columnWidths[col] = qMax(qreal(0.0), d->columnWidths[col] - diff); } } availableWidth = 0.0; } // Calculate width to those columns that don't actually request it qreal widthForNonWidthColumn = ((1.0 - qMin(relativeWidthSum, 1.0)) * availableWidth); availableWidth -= widthForNonWidthColumn; // might as well do this calc before dividing by numNonStyleColumns if (numNonStyleColumns > 0 && widthForNonWidthColumn > 0.0) { widthForNonWidthColumn /= numNonStyleColumns; } // Relative column widths have now been summed up and can be distributed. foreach (int col, relativeWidthColumns) { KoTableColumnStyle columnStyle = d->carsManager.columnStyle(col); if (columnStyle.hasProperty(KoTableColumnStyle::RelativeColumnWidth) || columnStyle.hasProperty(KoTableColumnStyle::ColumnWidth)) { d->columnWidths[col] = qMax(columnStyle.relativeColumnWidth() * availableWidth / relativeWidthSum, 0.0); } else { d->columnWidths[col] = widthForNonWidthColumn; } } // Column positions. qreal columnPosition = left(); qreal columnOffset = tableFormat.leftMargin(); if (tableFormat.alignment() == Qt::AlignRight) { // Table is right-aligned, so add all of the remaining space. columnOffset += parentWidth - d->tableWidth; } if (tableFormat.alignment() == Qt::AlignHCenter) { // Table is centered, so add half of the remaining space. columnOffset += (parentWidth - d->tableWidth) / 2; } for (int col = 0; col < d->columnPositions.size(); ++col) { d->columnPositions[col] = columnPosition + columnOffset; // Increment by this column's width. columnPosition += d->columnWidths[col]; } // Borders can be outside of the cell (outer-borders) in which case it's need // to take them into account to not cut content off. qreal leftBorder = 0.0; qreal rightBorder = 0.0; for (int row = 0; row < d->table->rows(); ++row) { QTextTableCell leftCell = d->table->cellAt(row, 0); KoTableCellStyle leftCellStyle = d->effectiveCellStyle(leftCell); leftBorder = qMax(leftBorder, leftCellStyle.leftOuterBorderWidth()); QTextTableCell rightCell = d->table->cellAt(row, d->table->columns() - 1); KoTableCellStyle rightCellStyle = d->effectiveCellStyle(rightCell); rightBorder = qMax(rightBorder, rightCellStyle.rightOuterBorderWidth()); } expandBoundingLeft(d->columnPositions[0] - leftBorder); expandBoundingRight(d->columnPositions[d->table->columns()] + rightBorder + leftBorder); } void KoTextLayoutTableArea::collectBorderThicknesss(int row, qreal &topBorderWidth, qreal &bottomBorderWidth) { int col = 0; if (d->collapsing && row >= 0 && row < d->table->rows()) { // let's collect the border info while (col < d->table->columns()) { QTextTableCell cell = d->table->cellAt(row, col); if (row == cell.row() + cell.rowSpan() - 1) { /* * This cell ends vertically in this row, and hence should * contribute to the bottom border. */ KoTableCellStyle cellStyle = d->effectiveCellStyle(cell); topBorderWidth = qMax(cellStyle.topBorderWidth(), topBorderWidth); bottomBorderWidth = qMax(cellStyle.bottomBorderWidth(), bottomBorderWidth); } col += cell.columnSpan(); // Skip across column spans. } } } void KoTextLayoutTableArea::nukeRow(TableIterator *cursor) { for (int column = 0; column < d->table->columns(); ++column) { delete d->cellAreas[cursor->row][column]; d->cellAreas[cursor->row][column] = 0; delete cursor->frameIterators[column]; cursor->frameIterators[column] = 0; } d->lastRowHasSomething = false; } bool KoTextLayoutTableArea::layoutRow(TableIterator *cursor, qreal topBorderWidth, qreal bottomBorderWidth) { int row = cursor->row; Q_ASSERT(row >= 0); Q_ASSERT(row < d->table->rows()); /* * Implementation Note: * * An undocumented behavior of QTextTable::cellAt is that requesting a * cell that is covered by a spanning cell will return the cell that * spans over the requested cell. Example: * * +------------+------------+ * | | | * | | | * | +------------+ * | | | * | | | * +------------+------------+ * * table.cellAt(1, 0).row() // Will return 0. * * In the code below, we rely on this behavior to determine wheather * a cell "vertically" ends in the current row, as those are the only * cells that should contribute to the row height. */ KoTableRowStyle rowStyle = d->carsManager.rowStyle(row); qreal rowHeight = rowStyle.rowHeight(); bool rowHasExactHeight = rowStyle.hasProperty(KoTableRowStyle::RowHeight); qreal rowBottom; if (rowHasExactHeight) { rowBottom = d->rowPositions[row] + rowHeight; } else { rowBottom = d->rowPositions[row] + rowStyle.minimumRowHeight(); } if (rowBottom > maximumAllowedBottom()) { d->rowPositions[row+1] = d->rowPositions[row]; if (cursor->row > d->startOfArea->row) { cursor->row--; layoutMergedCellsNotEnding(cursor, topBorderWidth, bottomBorderWidth, rowBottom); cursor->row++; } return false; // we can't honour minimum or fixed height so don't even try } bool allCellsFullyDone = true; bool anyCellTried = false; bool noCellsFitted = true; int col = 0; while (col < d->table->columns()) { // Get the cell format. QTextTableCell cell = d->table->cellAt(row, col); if (row == cell.row() + cell.rowSpan() - 1) { /* * This cell ends vertically in this row, and hence should * contribute to the row height. */ bool ignoreMisFittingCell = false; KoTableCellStyle cellStyle = d->effectiveCellStyle(cell); anyCellTried = true; qreal maxBottom = maximumAllowedBottom(); qreal requiredRowHeight = cellStyle.bottomPadding() + cellStyle.bottomPadding(); if (rowHasExactHeight) { maxBottom = qMin(d->rowPositions[row] + rowHeight, maxBottom); } maxBottom -= cellStyle.bottomPadding(); qreal areaTop = d->rowPositions[qMax(cell.row(), d->startOfArea->row)] + cellStyle.topPadding(); if (d->collapsing) { areaTop += topBorderWidth; maxBottom -= bottomBorderWidth; requiredRowHeight += bottomBorderWidth + topBorderWidth; } else { areaTop += cellStyle.topBorderWidth(); maxBottom -= cellStyle.bottomBorderWidth(); requiredRowHeight += cellStyle.bottomBorderWidth() + cellStyle.topBorderWidth(); } if (rowHasExactHeight && (rowHeight < requiredRowHeight)) { ignoreMisFittingCell = true; } if (maxBottom < areaTop && !ignoreMisFittingCell) { d->rowPositions[row+1] = d->rowPositions[row]; nukeRow(cursor); if (cursor->row > d->startOfArea->row) { cursor->row--; layoutMergedCellsNotEnding(cursor, topBorderWidth, bottomBorderWidth, rowBottom); cursor->row++; } return false; // we can't honour the borders so give up doing row } KoTextLayoutArea *cellArea = new KoTextLayoutArea(this, documentLayout()); d->cellAreas[cell.row()][cell.column()] = cellArea; qreal left = d->columnPositions[col] + cellStyle.leftPadding() + cellStyle.leftInnerBorderWidth(); qreal right = qMax(left, d->columnPositions[col+cell.columnSpan()] - cellStyle.rightPadding() - cellStyle.rightInnerBorderWidth()); cellArea->setReferenceRect( left, right, areaTop, maxBottom); cellArea->setVirginPage(virginPage()); cellArea->setLayoutEnvironmentResctictions(true, true); FrameIterator *cellCursor = cursor->frameIterator(col); bool cellFully = cellArea->layout(cellCursor); allCellsFullyDone = allCellsFullyDone && (cellFully || rowHasExactHeight); noCellsFitted = noCellsFitted && (cellArea->top() >= cellArea->bottom()) && !ignoreMisFittingCell; if (!rowHasExactHeight) { /* * Now we know how much height this cell contributes to the row, * and can determine wheather the row height will grow. */ if (d->collapsing) { rowBottom = qMax(cellArea->bottom() + cellStyle.bottomPadding(), rowBottom); } else { rowBottom = qMax(cellArea->bottom() + cellStyle.bottomPadding() + cellStyle.bottomBorderWidth(), rowBottom); } rowBottom = qMax(rowBottom, documentLayout()->maxYOfAnchoredObstructions(cell.firstCursorPosition().position(), cell.lastCursorPosition().position())); } d->lastRowHasSomething = true; // last row contains something (even if empty) } col += cell.columnSpan(); // Skip across column spans. } if (allCellsFullyDone) { for (col = 0; col < d->table->columns(); ++col) { QTextTableCell cell = d->table->cellAt(row, col); if (row == cell.row() + cell.rowSpan() - 1) { delete cursor->frameIterators[col]; cursor->frameIterators[col] = 0; } } } if (noCellsFitted && row <= d->headerRows) { d->totalMisFit = true; } if (anyCellTried && noCellsFitted && !rowHasExactHeight && !allCellsFullyDone) { d->rowPositions[row+1] = d->rowPositions[row]; nukeRow(cursor); if (cursor->row > d->startOfArea->row) { cursor->row--; layoutMergedCellsNotEnding(cursor, topBorderWidth, bottomBorderWidth, rowBottom); cursor->row++; } return false; // we can't honour the anything inside so give up doing row } if (!allCellsFullyDone) { layoutMergedCellsNotEnding(cursor, topBorderWidth, bottomBorderWidth, rowBottom); } else { // Cells all ended naturally, so we can now do vertical alignment // Stop! Other odf implementors also only do it if all cells are fully done col = 0; while (col < d->table->columns()) { QTextTableCell cell = d->table->cellAt(row, col); if (row == cell.row() + cell.rowSpan() - 1) { // cell ended in this row KoTextLayoutArea *cellArea = d->cellAreas[cell.row()][cell.column()]; KoTableCellStyle cellStyle = d->effectiveCellStyle(cell); if (cellStyle.alignment() & Qt::AlignBottom) { if (true /*FIXME test no page based shapes interfering*/) { cellArea->setVerticalAlignOffset(rowBottom - cellArea->bottom()); } } if (cellStyle.alignment() & Qt::AlignVCenter) { if (true /*FIXME test no page based shapes interfering*/) { cellArea->setVerticalAlignOffset((rowBottom - cellArea->bottom()) / 2); } } } col += cell.columnSpan(); // Skip across column spans. } } // Adjust Y position of NEXT row. // This is nice since the outside layout routine relies on the next row having a correct y position // the first row y position is set in layout() d->rowPositions[row+1] = rowBottom; return allCellsFullyDone; } bool KoTextLayoutTableArea::layoutMergedCellsNotEnding(TableIterator *cursor, qreal topBorderWidth, qreal bottomBorderWidth, qreal rowBottom) { Q_UNUSED(topBorderWidth) Q_UNUSED(bottomBorderWidth) // Let's make sure all merged cells in this row, that don't end in this row get's a layout int row = cursor->row; int col = 0; while (col < d->table->columns()) { QTextTableCell cell = d->table->cellAt(row, col); if (row != cell.row() + cell.rowSpan() - 1) { // TODO do all of the following like in layoutRow() KoTableCellStyle cellStyle = d->effectiveCellStyle(cell); KoTextLayoutArea *cellArea = new KoTextLayoutArea(this, documentLayout()); d->cellAreas[cell.row()][cell.column()] = cellArea; qreal left = d->columnPositions[col] + cellStyle.leftPadding() + cellStyle.leftInnerBorderWidth(); qreal right = qMax(left, d->columnPositions[col+cell.columnSpan()] - cellStyle.rightPadding() - cellStyle.rightInnerBorderWidth()); cellArea->setReferenceRect( left, right, d->rowPositions[qMax(cell.row(), d->startOfArea->row)] + cellStyle.topPadding() + cellStyle.topBorderWidth(), rowBottom - cellStyle.bottomPadding() - cellStyle.bottomBorderWidth()); cellArea->setVirginPage(virginPage()); cellArea->setLayoutEnvironmentResctictions(true, true); FrameIterator *cellCursor = cursor->frameIterator(col); cellArea->layout(cellCursor); if (cellArea->top() < cellArea->bottom() && row == d->headerRows) { d->totalMisFit = false; } } col += cell.columnSpan(); // Skip across column spans. } return true; } void KoTextLayoutTableArea::paint(QPainter *painter, const KoTextDocumentLayout::PaintContext &context) { if (d->startOfArea == 0) // We have not been layouted yet return; int lastRow = d->endOfArea->row; if (d->lastRowHasSomething == false) { --lastRow; } if (lastRow < d->startOfArea->row) { return; // empty } int firstRow = qMax(d->startOfArea->row, d->headerRows); // Draw table background qreal topY = d->headerRows ? d->rowPositions[0] : d->rowPositions[firstRow]; QRectF tableRect(d->columnPositions[0], topY, d->tableWidth, d->headerRowPositions[d->headerRows] - d->headerRowPositions[0] + d->rowPositions[lastRow+1] - d->rowPositions[firstRow]); painter->fillRect(tableRect, d->table->format().background()); KoTextDocumentLayout::PaintContext cellContext = context; QColor tableBackground = context.background; if (d->table->format().hasProperty(QTextFormat::BackgroundBrush)) { tableBackground = d->table->format().background().color(); } // Draw header row backgrounds for (int row = 0; row < d->headerRows; ++row) { QRectF rowRect(d->columnPositions[0], d->headerRowPositions[row], d->tableWidth, d->headerRowPositions[row+1] - d->headerRowPositions[row]); KoTableRowStyle rowStyle = d->carsManager.rowStyle(row); rowRect.translate(0, d->headerOffsetY); painter->fillRect(rowRect, rowStyle.background()); } // Draw plain row backgrounds for (int row = firstRow; row <= lastRow; ++row) { QRectF rowRect(d->columnPositions[0], d->rowPositions[row], d->tableWidth, d->rowPositions[row+1] - d->rowPositions[row]); KoTableRowStyle rowStyle = d->carsManager.rowStyle(row); painter->fillRect(rowRect, rowStyle.background()); } QSet > visitedCells; // Draw cell backgrounds and contents. for (int row = firstRow; row <= lastRow; ++row) { for (int column = 0; column < d->table->columns(); ++column) { QTextTableCell tableCell = d->table->cellAt(row, column); int testRow = (row == firstRow ? tableCell.row() : row); if (d->cellAreas[testRow][column] && !visitedCells.contains(QPair(testRow, column))) { cellContext.background = tableBackground; QBrush bgBrush = d->effectiveCellStyle(tableCell).background(); if (bgBrush != Qt::NoBrush) { cellContext.background = bgBrush.color(); } paintCell(painter, cellContext, tableCell, d->cellAreas[testRow][column]); visitedCells.insert(QPair(testRow, column)); } } } painter->translate(0, d->headerOffsetY); QVector accuBlankBorders; bool hasAntialiasing = painter->testRenderHint(QPainter::Antialiasing); // Draw header row cell backgrounds and contents. for (int row = 0; row < d->headerRows; ++row) { for (int column = 0; column < d->table->columns(); ++column) { QTextTableCell tableCell = d->table->cellAt(row, column); int testRow = row == firstRow ? tableCell.row() : row; if (d->cellAreas[testRow][column]) { cellContext.background = tableBackground; QBrush bgBrush = d->effectiveCellStyle(tableCell).background(); if (bgBrush != Qt::NoBrush) { cellContext.background = bgBrush.color(); } paintCell(painter, cellContext, tableCell, d->cellAreas[testRow][column]); } } } // Draw header row cell borders.(need to be second step so nabour cells don't overwrite) for (int row = 0; row < d->headerRows; ++row) { for (int column = 0; column < d->table->columns(); ++column) { QTextTableCell tableCell = d->table->cellAt(row, column); int testRow = row == firstRow ? tableCell.row() : row; if (d->cellAreas[testRow][column]) { painter->setRenderHint(QPainter::Antialiasing, true); paintCellBorders(painter, context, tableCell, false, lastRow, &accuBlankBorders); painter->setRenderHint(QPainter::Antialiasing, hasAntialiasing); } } } for (int i = 0; i < accuBlankBorders.size(); ++i) { accuBlankBorders[i].translate(0, d->headerOffsetY); } painter->translate(0, -d->headerOffsetY); // Draw cell borders. bool topRow = !d->headerRows && firstRow != 0; // are we top row in this area painter->setRenderHint(QPainter::Antialiasing, true); visitedCells.clear(); for (int row = firstRow; row <= lastRow; ++row) { for (int column = 0; column < d->table->columns(); ++column) { QTextTableCell tableCell = d->table->cellAt(row, column); int testRow = row == firstRow ? tableCell.row() : row; if (d->cellAreas[testRow][column] && !visitedCells.contains(QPair(testRow, column))) { paintCellBorders(painter, context, tableCell, topRow, lastRow, &accuBlankBorders); visitedCells.insert(QPair(testRow, column)); } } topRow = false; } painter->setRenderHint(QPainter::Antialiasing, hasAntialiasing); if (context.showTableBorders) { QPen pen(painter->pen()); - painter->setPen(QPen(QColor(0,0,0,96))); + painter->setPen(QPen(QColor(0,0,0,96), 0)); painter->drawLines(accuBlankBorders); painter->setPen(pen); } } void KoTextLayoutTableArea::paintCell(QPainter *painter, const KoTextDocumentLayout::PaintContext &context, const QTextTableCell &tableCell, KoTextLayoutArea *frameArea) { int row = tableCell.row(); int column = tableCell.column(); // This is an actual cell we want to draw, and not a covered one. QRectF bRect(cellBoundingRect(tableCell)); painter->save(); painter->setClipRect(bRect, Qt::IntersectClip); // Possibly paint the background of the cell QBrush background(d->effectiveCellStyle(tableCell).background()); if (background != Qt::NoBrush) { painter->fillRect(bRect, background); } // Possibly paint the selection of the entire cell if (context.showSelections) { foreach(const QAbstractTextDocumentLayout::Selection & selection, context.textContext.selections) { QTextTableCell startTableCell = d->table->cellAt(selection.cursor.selectionStart()); QTextTableCell endTableCell = d->table->cellAt(selection.cursor.selectionEnd()); if (startTableCell.isValid() && startTableCell != endTableCell) { int selectionRow; int selectionColumn; int selectionRowSpan; int selectionColumnSpan; selection.cursor.selectedTableCells(&selectionRow, &selectionRowSpan, &selectionColumn, &selectionColumnSpan); if (row >= selectionRow && column>=selectionColumn && row < selectionRow + selectionRowSpan && column < selectionColumn + selectionColumnSpan) { painter->fillRect(bRect, selection.format.background()); } } else if (selection.cursor.selectionStart() < d->table->firstPosition() && selection.cursor.selectionEnd() > d->table->lastPosition()) { painter->fillRect(bRect, selection.format.background()); } } } if (row < d->headerRows) { painter->translate(d->headerOffsetX, 0); } // Paint the content of the cellArea frameArea->paint(painter, context); painter->restore(); } void KoTextLayoutTableArea::paintCellBorders(QPainter *painter, const KoTextDocumentLayout::PaintContext &context, const QTextTableCell &tableCell, bool topRow, int lastRow, QVector *accuBlankBorders) { Q_UNUSED(context); int row = tableCell.row(); int column = tableCell.column(); // This is an actual cell we want to draw, and not a covered one. KoTableCellStyle cellStyle = d->effectiveCellStyle(tableCell); KoTextLayoutCellHelper cellStyleHelper(cellStyle); QRectF bRect = cellBoundingRect(tableCell); if (d->collapsing) { // First the horizontal borders if (row == 0) { cellStyleHelper.drawTopHorizontalBorder(*painter, bRect.x(), bRect.y(), bRect.width(), accuBlankBorders); } if (topRow && row != 0) { // in collapsing mode we need to also paint the top border of the area int c = column; while (c < column + tableCell.columnSpan()) { QTextTableCell tableCellAbove = d->table->cellAt(row - 1, c); QRectF aboveBRect = cellBoundingRect(tableCellAbove); qreal x = qMax(bRect.x(), aboveBRect.x()); qreal x2 = qMin(bRect.right(), aboveBRect.right()); KoTableCellStyle cellAboveStyle = d->effectiveCellStyle(tableCellAbove); KoTextLayoutCellHelper cellAboveStyleHelper(cellAboveStyle); cellAboveStyleHelper.drawSharedHorizontalBorder(*painter, cellStyle, x, bRect.y(), x2 - x, accuBlankBorders); c = tableCellAbove.column() + tableCellAbove.columnSpan(); } } if (row + tableCell.rowSpan() == d->table->rows()) { // we hit the bottom of the table so just draw the bottom border cellStyleHelper.drawBottomHorizontalBorder(*painter, bRect.x(), bRect.bottom(), bRect.width(), accuBlankBorders); } else { int c = column; while (c < column + tableCell.columnSpan()) { QTextTableCell tableCellBelow = d->table->cellAt(row == d->headerRows - 1 ? d->startOfArea->row : row + tableCell.rowSpan(), c); QRectF belowBRect = cellBoundingRect(tableCellBelow); qreal x = qMax(bRect.x(), belowBRect.x()); qreal x2 = qMin(bRect.right(), belowBRect.right()); KoTableCellStyle cellBelowStyle = d->effectiveCellStyle(tableCellBelow); cellStyleHelper.drawSharedHorizontalBorder(*painter, cellBelowStyle, x, bRect.bottom(), x2 - x, accuBlankBorders); c = tableCellBelow.column() + tableCellBelow.columnSpan(); } } // And then the same treatment for vertical borders if (column == 0) { cellStyleHelper.drawLeftmostVerticalBorder(*painter, bRect.x(), bRect.y(), bRect.height() + cellStyle.bottomOuterBorderWidth(), accuBlankBorders); } if (column + tableCell.columnSpan() == d->table->columns()) { // we hit the rightmost edge of the table so draw the rightmost border cellStyleHelper.drawRightmostVerticalBorder(*painter, bRect.right(), bRect.y(), bRect.height() + cellStyle.bottomOuterBorderWidth(), accuBlankBorders); } else { // we have cells to the right so draw sharedborders int r = row; while (r < row + tableCell.rowSpan() && r <= lastRow) { QTextTableCell tableCellRight = d->table->cellAt(r, column + tableCell.columnSpan()); KoTableCellStyle cellRightStyle = d->effectiveCellStyle(tableCellRight); QRectF rightBRect = cellBoundingRect(tableCellRight); qreal y = qMax(bRect.y(), rightBRect.y()); qreal y2 = qMin(bRect.bottom() + cellStyle.bottomOuterBorderWidth(), rightBRect.bottom() + cellRightStyle.bottomOuterBorderWidth()); cellStyleHelper.drawSharedVerticalBorder(*painter, cellRightStyle, bRect.right(), y, y2-y, accuBlankBorders); r = tableCellRight.row() + tableCellRight.rowSpan(); } } // Paint diagonal borders for current cell cellStyleHelper.paintDiagonalBorders(*painter, bRect); } else { // separating border model cellStyleHelper.paintBorders(*painter, bRect, accuBlankBorders); } } QRectF KoTextLayoutTableArea::cellBoundingRect(const QTextTableCell &cell) const { int row = cell.row(); int rowSpan = cell.rowSpan(); const int column = cell.column(); const int columnSpan = cell.columnSpan(); const qreal width = d->columnPositions[column + columnSpan] - d->columnPositions[column]; if (row >= d->headerRows) { int lastRow = d->endOfArea->row; if (d->lastRowHasSomething == false) { --lastRow; } if (lastRow < d->startOfArea->row) { return QRectF(); // empty } // Limit cell to within the area if (row < d->startOfArea->row) { rowSpan -= d->startOfArea->row - row; row += d->startOfArea->row - row; } if (row + rowSpan - 1 > lastRow) { rowSpan = lastRow - row + 1; } const qreal height = d->rowPositions[row + rowSpan] - d->rowPositions[row]; return QRectF(d->columnPositions[column], d->rowPositions[row], width, height); } else { return QRectF(d->columnPositions[column], d->headerRowPositions[row], width, d->headerRowPositions[row + rowSpan] - d->headerRowPositions[row]); } } diff --git a/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp b/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp index b044a310713..60fddd99dca 100644 --- a/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp +++ b/libs/vectorimage/libemf/EmfOutputPainterStrategy.cpp @@ -1,1472 +1,1472 @@ /* Copyright 2008 Brad Hards Copyright 2009 - 2010 Inge Wallin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "EmfOutputPainterStrategy.h" #include #include #include "EmfObjects.h" #define DEBUG_EMFPAINT 0 #define DEBUG_PAINTER_TRANSFORM 0 namespace Libemf { static QPainter::CompositionMode rasteropToQtComposition(long rop); // ================================================================ // Class OutputPainterStrategy OutputPainterStrategy::OutputPainterStrategy() : m_header( 0 ) , m_path( 0 ) , m_currentlyBuildingPath( false ) , m_fillRule(Qt::OddEvenFill) , m_mapMode(MM_TEXT) , m_textAlignMode(TA_NOUPDATECP) // == TA_TOP == TA_LEFT , m_currentCoords() { m_painter = 0; m_painterSaves = 0; m_outputSize = QSize(); m_keepAspectRatio = true; } OutputPainterStrategy::OutputPainterStrategy(QPainter &painter, QSize &size, bool keepAspectRatio) : m_header( 0 ) , m_path( 0 ) , m_currentlyBuildingPath( false ) , m_windowExtIsSet(false) , m_viewportExtIsSet(false) , m_windowViewportIsSet(false) , m_fillRule(Qt::OddEvenFill) , m_mapMode(MM_TEXT) , m_textAlignMode(TA_NOUPDATECP) // == TA_TOP == TA_LEFT , m_currentCoords() { m_painter = &painter; m_painterSaves = 0; m_outputSize = size; m_keepAspectRatio = keepAspectRatio; } OutputPainterStrategy::~OutputPainterStrategy() { delete m_header; delete m_path; } void OutputPainterStrategy::paintBounds(const Header *header) { // The rectangle is in device coordinates. QRectF rect(header->bounds()); m_painter->save(); // Draw a simple cross in a rectangle to show the bounds. - m_painter->setPen(QPen(QColor(172, 196, 206))); + m_painter->setPen(QPen(QColor(172, 196, 206), 0)); m_painter->drawRect(rect); m_painter->drawLine(rect.topLeft(), rect.bottomRight()); m_painter->drawLine(rect.bottomLeft(), rect.topRight()); m_painter->restore(); } void OutputPainterStrategy::init( const Header *header ) { // Save the header since we need the frame and bounds inside the drawing. m_header = new Header(*header); QSize headerBoundsSize = header->bounds().size(); #if DEBUG_EMFPAINT debugVectorImage << "----------------------------------------------------------------------"; debugVectorImage << "Shape size =" << m_outputSize.width() << m_outputSize.height() << " pt"; debugVectorImage << "----------------------------------------------------------------------"; debugVectorImage << "Boundary box (dev units) =" << header->bounds().x() << header->bounds().y() << header->bounds().width() << header->bounds().height(); debugVectorImage << "Frame (phys size) =" << header->frame().x() << header->frame().y() << header->frame().width() << header->frame().height() << " *0.01 mm"; debugVectorImage << "Device =" << header->device().width() << header->device().height(); debugVectorImage << "Millimeters =" << header->millimeters().width() << header->millimeters().height(); #endif #if DEBUG_PAINTER_TRANSFORM printPainterTransform("In init, before save:"); #endif // This is restored in cleanup(). m_painter->save(); // Calculate how much the painter should be resized to fill the // outputSize with output. qreal scaleX = qreal( m_outputSize.width() ) / headerBoundsSize.width(); qreal scaleY = qreal( m_outputSize.height() ) / headerBoundsSize.height(); if ( m_keepAspectRatio ) { // Use the smaller value so that we don't get an overflow in // any direction. if ( scaleX > scaleY ) scaleX = scaleY; else scaleY = scaleX; } #if DEBUG_EMFPAINT debugVectorImage << "scale = " << scaleX << ", " << scaleY; #endif // Transform the EMF object so that it fits in the shape. The // topleft of the EMF will be the top left of the shape. m_painter->scale( scaleX, scaleY ); m_painter->translate(-header->bounds().left(), -header->bounds().top()); #if DEBUG_PAINTER_TRANSFORM printPainterTransform("after fitting into shape"); #endif // Save the scale so that we can use it when setting lineWidth. m_outputScale = (scaleX + scaleY) / 2; // Calculate translation if we should center the EMF in the // area and keep the aspect ratio. #if 0 // Should apparently be upper left. See bug 265868 if ( m_keepAspectRatio ) { m_painter->translate((m_outputSize.width() / scaleX - headerBoundsSize.width()) / 2, (m_outputSize.height() / scaleY - headerBoundsSize.height()) / 2); #if DEBUG_PAINTER_TRANSFORM printPainterTransform("after translation for keeping center in the shape"); #endif } #endif m_outputTransform = m_painter->transform(); m_worldTransform = QTransform(); // For calculations of window / viewport during the painting m_windowOrg = QPoint(0, 0); m_viewportOrg = QPoint(0, 0); m_windowExtIsSet = false; m_viewportExtIsSet = false; m_windowViewportIsSet = false; #if DEBUG_EMFPAINT paintBounds(header); #endif } void OutputPainterStrategy::cleanup( const Header *header ) { Q_UNUSED( header ); #if DEBUG_EMFPAINT if (m_painterSaves > 0) debugVectorImage << "WARNING: UNRESTORED DC's:" << m_painterSaves; #endif // Restore all the save()s that were done during the processing. for (int i = 0; i < m_painterSaves; ++i) m_painter->restore(); m_painterSaves = 0; // Restore the painter to what it was before init() was called. m_painter->restore(); } void OutputPainterStrategy::eof() { } void OutputPainterStrategy::setPixelV( QPoint &point, quint8 red, quint8 green, quint8 blue, quint8 reserved ) { Q_UNUSED( reserved ); #if DEBUG_EMFPAINT debugVectorImage << point << red << green << blue; #endif m_painter->save(); QPen pen; pen.setColor( QColor( red, green, blue ) ); m_painter->setPen( pen ); m_painter->drawPoint( point ); m_painter->restore(); } void OutputPainterStrategy::beginPath() { #if DEBUG_EMFPAINT debugVectorImage; #endif delete( m_path ); m_path = new QPainterPath; m_currentlyBuildingPath = true; } void OutputPainterStrategy::closeFigure() { #if DEBUG_EMFPAINT debugVectorImage; #endif m_path->closeSubpath(); } void OutputPainterStrategy::endPath() { #if DEBUG_EMFPAINT debugVectorImage; #endif m_path->setFillRule( m_fillRule ); m_currentlyBuildingPath = false; } void OutputPainterStrategy::saveDC() { #if DEBUG_EMFPAINT debugVectorImage; #endif // A little trick here: Save the worldTransform in the painter. // If we didn't do this, we would have to create a separate stack // for these. // // FIXME: We should collect all the parts of the DC that are not // stored in the painter and save them separately. QTransform savedTransform = m_painter->worldTransform(); m_painter->setWorldTransform(m_worldTransform); m_painter->save(); ++m_painterSaves; m_painter->setWorldTransform(savedTransform); } void OutputPainterStrategy::restoreDC( const qint32 savedDC ) { #if DEBUG_EMFPAINT debugVectorImage << savedDC; #endif // Note that savedDC is always negative for (int i = 0; i < -savedDC; ++i) { if (m_painterSaves > 0) { m_painter->restore(); --m_painterSaves; } else { debugVectorImage << "restoreDC(): try to restore painter without save" << savedDC - i; break; } } // We used a trick in saveDC() and stored the worldTransform in // the painter. Now restore the full transformation. m_worldTransform = m_painter->worldTransform(); QTransform newMatrix = m_worldTransform * m_outputTransform; m_painter->setWorldTransform( newMatrix ); } void OutputPainterStrategy::setMetaRgn() { debugVectorImage << "EMR_SETMETARGN not yet implemented"; } // ---------------------------------------------------------------- // World Transform, Window and Viewport // General note about coordinate spaces and transforms: // // There are several coordinate spaces in use when drawing an EMF file: // 1. The object space, in which the objects' coordinates are expressed inside the EMF. // In general there are several of these. // 2. The page space, which is where they end up being painted in the EMF picture. // The union of these form the bounding box of the EMF. // 3. (possibly) the output space, where the EMF picture itself is placed // and/or scaled, rotated, etc // // The transform between spaces 1. and 2. is called the World Transform. // The world transform can be changed either through calls to change // the window or viewport or through calls to setWorldTransform() or // modifyWorldTransform(). // // The transform between spaces 2. and 3. is the transform that the QPainter // already contains when it is given to us. We need to save this and reapply // it after the world transform has changed. We call this transform the Output // Transform in lack of a better word. (Some sources call it the Device Transform.) // // An unanswered question: // // The file mp07_embedded_ppt.pptx in the test files contains the // following sequence of records: // - SetWorldTransform // - ModifyWorldTransform // - SetViewportOrg <-- doesn't change anything // - SetWindowOrg <-- doesn't change anything // - ExtTextOutw // // I was previously under the impression that whenever a // Set{Window,Viewport}{Org,Ext} record was encountered, the world // transform was supposed to be recalculated. But in this file, it // destroys the world transform. The question is which of the // following alternatives is true: // // 1. The world transform should only be recalculated if the // Set{Window,Viewport}{Org,Ext} record actually changes anything. // // 2. The transformations set by {Set,Modify}WorldTransform records // should always be reapplied after a change in window or viewport. // // 3. Something else // // I have for now implemented option 1. See the FIXME's in // SetWindowOrg et al. // // Set Window and Viewport void OutputPainterStrategy::recalculateWorldTransform() { m_worldTransform = QTransform(); // If neither the window nor viewport extension is set, then there // is no way to perform the calculation. Just give up. if (!m_windowExtIsSet && !m_viewportExtIsSet) return; // Negative window extensions mean flip the picture. Handle this here. bool flip = false; qreal midpointX = 0.0; qreal midpointY = 0.0; qreal scaleX = 1.0; qreal scaleY = 1.0; if (m_windowExt.width() < 0) { midpointX = m_windowOrg.x() + m_windowExt.width() / qreal(2.0); scaleX = -1.0; flip = true; } if (m_windowExt.height() < 0) { midpointY = m_windowOrg.y() + m_windowExt.height() / qreal(2.0); scaleY = -1.0; flip = true; } if (flip) { //debugVectorImage << "Flipping" << midpointX << midpointY << scaleX << scaleY; m_worldTransform.translate(midpointX, midpointY); m_worldTransform.scale(scaleX, scaleY); m_worldTransform.translate(-midpointX, -midpointY); //debugVectorImage << "After flipping for window" << mWorldTransform; } // Update the world transform if both window and viewport are set... // FIXME: Check windowExt == 0 in any direction if (m_windowExtIsSet && m_viewportExtIsSet) { // Both window and viewport are set. qreal windowViewportScaleX = qreal(m_viewportExt.width()) / qreal(m_windowExt.width()); qreal windowViewportScaleY = qreal(m_viewportExt.height()) / qreal(m_windowExt.height()); m_worldTransform.translate(-m_windowOrg.x(), -m_windowOrg.y()); m_worldTransform.scale(windowViewportScaleX, windowViewportScaleY); m_worldTransform.translate(m_viewportOrg.x(), m_viewportOrg.y()); } // ...and apply it to the painter m_painter->setWorldTransform(m_worldTransform); m_windowViewportIsSet = true; // Apply the output transform. QTransform newMatrix = m_worldTransform * m_outputTransform; m_painter->setWorldTransform( newMatrix ); } void OutputPainterStrategy::setWindowOrgEx( const QPoint &origin ) { #if DEBUG_EMFPAINT debugVectorImage << origin; #endif // FIXME: See unanswered question at the start of this section. if (m_windowOrg == origin) { //debugVectorImage << "same origin as before"; return; } m_windowOrg = origin; recalculateWorldTransform(); } void OutputPainterStrategy::setWindowExtEx( const QSize &size ) { #if DEBUG_EMFPAINT debugVectorImage << size; #endif // FIXME: See unanswered question at the start of this section. if (m_windowExt == size) { //debugVectorImage << "same extension as before"; return; } m_windowExt = size; m_windowExtIsSet = true; recalculateWorldTransform(); } void OutputPainterStrategy::setViewportOrgEx( const QPoint &origin ) { #if DEBUG_EMFPAINT debugVectorImage << origin; #endif // FIXME: See unanswered question at the start of this section. if (m_viewportOrg == origin) { //debugVectorImage << "same origin as before"; return; } m_viewportOrg = origin; recalculateWorldTransform(); } void OutputPainterStrategy::setViewportExtEx( const QSize &size ) { #if DEBUG_EMFPAINT debugVectorImage << size; #endif // FIXME: See unanswered question at the start of this section. if (m_viewportExt == size) { //debugVectorImage << "same extension as before"; return; } m_viewportExt = size; m_viewportExtIsSet = true; recalculateWorldTransform(); } void OutputPainterStrategy::modifyWorldTransform( quint32 mode, float M11, float M12, float M21, float M22, float Dx, float Dy ) { #if DEBUG_EMFPAINT if (mode == MWT_IDENTITY) debugVectorImage << "Identity matrix"; else debugVectorImage << mode << M11 << M12 << M21 << M22 << Dx << Dy; #endif QTransform matrix( M11, M12, M21, M22, Dx, Dy); if ( mode == MWT_IDENTITY ) { m_worldTransform = QTransform(); } else if ( mode == MWT_LEFTMULTIPLY ) { m_worldTransform = matrix * m_worldTransform; } else if ( mode == MWT_RIGHTMULTIPLY ) { m_worldTransform = m_worldTransform * matrix; } else if ( mode == MWT_SET ) { m_worldTransform = matrix; } else { warnVectorImage << "Unimplemented transform mode" << mode; } // Apply the output transform. QTransform newMatrix = m_worldTransform * m_outputTransform; m_painter->setWorldTransform( newMatrix ); } void OutputPainterStrategy::setWorldTransform( float M11, float M12, float M21, float M22, float Dx, float Dy ) { #if DEBUG_EMFPAINT debugVectorImage << M11 << M12 << M21 << M22 << Dx << Dy; #endif QTransform matrix( M11, M12, M21, M22, Dx, Dy); m_worldTransform = matrix; // Apply the output transform. QTransform newMatrix = m_worldTransform * m_outputTransform; m_painter->setWorldTransform( newMatrix ); } // ---------------------------------------------------------------- void OutputPainterStrategy::createPen( quint32 ihPen, quint32 penStyle, quint32 x, quint32 y, quint8 red, quint8 green, quint8 blue, quint8 reserved ) { Q_UNUSED( y ); Q_UNUSED( reserved ); #if DEBUG_EMFPAINT debugVectorImage << ihPen << hex << penStyle << dec << x << y << red << green << blue << reserved; #endif QPen pen; pen.setColor( QColor( red, green, blue ) ); if ( penStyle & PS_GEOMETRIC ) { pen.setCosmetic( false ); } else { pen.setCosmetic( true ); } switch ( penStyle & 0xF ) { case PS_SOLID: pen.setStyle( Qt::SolidLine ); break; case PS_DASH: pen.setStyle( Qt::DashLine ); break; case PS_DOT: pen.setStyle( Qt::DotLine ); break; case PS_DASHDOT: pen.setStyle( Qt::DashDotLine ); break; case PS_DASHDOTDOT: pen.setStyle( Qt::DashDotDotLine ); break; case PS_NULL: pen.setStyle( Qt::NoPen ); break; case PS_INSIDEFRAME: // FIXME: We don't properly support this pen.setStyle( Qt::SolidLine ); break; case PS_USERSTYLE: debugVectorImage << "UserStyle pen not yet supported, using SolidLine"; pen.setStyle( Qt::SolidLine ); break; case PS_ALTERNATE: debugVectorImage << "Alternate pen not yet supported, using DashLine"; pen.setStyle( Qt::DashLine ); break; default: debugVectorImage << "unexpected pen type, using SolidLine" << (penStyle & 0xF); pen.setStyle( Qt::SolidLine ); } switch ( penStyle & PS_ENDCAP_FLAT ) { case PS_ENDCAP_ROUND: pen.setCapStyle( Qt::RoundCap ); break; case PS_ENDCAP_SQUARE: pen.setCapStyle( Qt::SquareCap ); break; case PS_ENDCAP_FLAT: pen.setCapStyle( Qt::FlatCap ); break; default: debugVectorImage << "unexpected cap style, using SquareCap" << (penStyle & PS_ENDCAP_FLAT); pen.setCapStyle( Qt::SquareCap ); } pen.setWidthF(x * m_outputScale); m_objectTable.insert( ihPen, pen ); } void OutputPainterStrategy::createBrushIndirect( quint32 ihBrush, quint32 brushStyle, quint8 red, quint8 green, quint8 blue, quint8 reserved, quint32 brushHatch ) { Q_UNUSED( reserved ); Q_UNUSED( brushHatch ); #if DEBUG_EMFPAINT debugVectorImage << ihBrush << hex << brushStyle << dec << red << green << blue << reserved << brushHatch; #endif QBrush brush; switch ( brushStyle ) { case BS_SOLID: brush.setStyle( Qt::SolidPattern ); break; case BS_NULL: brush.setStyle( Qt::NoBrush ); break; case BS_HATCHED: brush.setStyle( Qt::CrossPattern ); break; case BS_PATTERN: Q_ASSERT( 0 ); break; case BS_INDEXED: Q_ASSERT( 0 ); break; case BS_DIBPATTERN: Q_ASSERT( 0 ); break; case BS_DIBPATTERNPT: Q_ASSERT( 0 ); break; case BS_PATTERN8X8: Q_ASSERT( 0 ); break; case BS_DIBPATTERN8X8: Q_ASSERT( 0 ); break; case BS_MONOPATTERN: Q_ASSERT( 0 ); break; default: Q_ASSERT( 0 ); } brush.setColor( QColor( red, green, blue ) ); // TODO: Handle the BrushHatch enum. m_objectTable.insert( ihBrush, brush ); } void OutputPainterStrategy::createMonoBrush( quint32 ihBrush, Bitmap *bitmap ) { QImage pattern(bitmap->image()); QBrush brush(pattern); m_objectTable.insert( ihBrush, brush ); } void OutputPainterStrategy::extCreateFontIndirectW( const ExtCreateFontIndirectWRecord &extCreateFontIndirectW ) { QFont font( extCreateFontIndirectW.fontFace() ); font.setWeight( convertFontWeight( extCreateFontIndirectW.weight() ) ); if ( extCreateFontIndirectW.height() < 0 ) { font.setPixelSize( -1 * extCreateFontIndirectW.height() ); } else if ( extCreateFontIndirectW.height() > 0 ) { font.setPixelSize( extCreateFontIndirectW.height() ); } // zero is "use a default size" which is effectively no-op here. // .snp files don't always provide 0x01 for italics if ( extCreateFontIndirectW.italic() != 0x00 ) { font.setItalic( true ); } if ( extCreateFontIndirectW.underline() != 0x00 ) { font.setUnderline( true ); } m_objectTable.insert( extCreateFontIndirectW.ihFonts(), font ); } void OutputPainterStrategy::selectStockObject( const quint32 ihObject ) { #if DEBUG_EMFPAINT debugVectorImage << ihObject; #endif switch ( ihObject ) { case WHITE_BRUSH: m_painter->setBrush( QBrush( Qt::white ) ); break; case LTGRAY_BRUSH: m_painter->setBrush( QBrush( Qt::lightGray ) ); break; case GRAY_BRUSH: m_painter->setBrush( QBrush( Qt::gray ) ); break; case DKGRAY_BRUSH: m_painter->setBrush( QBrush( Qt::darkGray ) ); break; case BLACK_BRUSH: m_painter->setBrush( QBrush( Qt::black ) ); break; case NULL_BRUSH: m_painter->setBrush( QBrush() ); break; case WHITE_PEN: m_painter->setPen( QPen( Qt::white ) ); break; case BLACK_PEN: m_painter->setPen( QPen( Qt::black ) ); break; case NULL_PEN: m_painter->setPen( QPen( Qt::NoPen ) ); break; case OEM_FIXED_FONT: case ANSI_FIXED_FONT: case SYSTEM_FIXED_FONT: { QFont font(QString("Fixed")); m_painter->setFont(font); break; } case ANSI_VAR_FONT: case DEFAULT_GUI_FONT: // Not sure if this is true, but it should work well { QFont font(QString("Helvetica")); // Could also be "System" m_painter->setFont(font); break; } break; case SYSTEM_FONT: // TODO: handle this break; case DEVICE_DEFAULT_FONT: // TODO: handle this break; case DEFAULT_PALETTE: break; case DC_BRUSH: // FIXME break; case DC_PEN: // FIXME break; default: warnVectorImage << "Unexpected stock object:" << ( ihObject & 0x8000000 ); } } void OutputPainterStrategy::selectObject( const quint32 ihObject ) { #if DEBUG_EMFPAINT debugVectorImage << hex << ihObject << dec; #endif if ( ihObject & 0x80000000 ) { selectStockObject( ihObject ); } else { QVariant obj = m_objectTable.value( ihObject ); switch ( obj.type() ) { case QVariant::Pen : m_painter->setPen( obj.value() ); break; case QVariant::Brush : m_painter->setBrush( obj.value() ); break; case QVariant::Font : m_painter->setFont( obj.value() ); break; default: debugVectorImage << "Unexpected type:" << obj.typeName(); } } } void OutputPainterStrategy::deleteObject( const quint32 ihObject ) { m_objectTable.take( ihObject ); } void OutputPainterStrategy::arc( const QRect &box, const QPoint &start, const QPoint &end ) { #if DEBUG_EMFPAINT debugVectorImage << box << start << end; #endif QPoint centrePoint = box.center(); qreal startAngle = angleFromArc( centrePoint, start ); qreal endAngle = angleFromArc( centrePoint, end ); qreal spanAngle = angularSpan( startAngle, endAngle ); m_painter->drawArc( box, startAngle*16, spanAngle*16 ); } void OutputPainterStrategy::chord( const QRect &box, const QPoint &start, const QPoint &end ) { #if DEBUG_EMFPAINT debugVectorImage << box << start << end; #endif QPoint centrePoint = box.center(); qreal startAngle = angleFromArc( centrePoint, start ); qreal endAngle = angleFromArc( centrePoint, end ); qreal spanAngle = angularSpan( startAngle, endAngle ); m_painter->drawChord( box, startAngle*16, spanAngle*16 ); } void OutputPainterStrategy::pie( const QRect &box, const QPoint &start, const QPoint &end ) { #if DEBUG_EMFPAINT debugVectorImage << box << start << end; #endif QPoint centrePoint = box.center(); qreal startAngle = angleFromArc( centrePoint, start ); qreal endAngle = angleFromArc( centrePoint, end ); qreal spanAngle = angularSpan( startAngle, endAngle ); m_painter->drawPie( box, startAngle*16, spanAngle*16 ); } void OutputPainterStrategy::ellipse( const QRect &box ) { #if DEBUG_EMFPAINT debugVectorImage << box; #endif m_painter->drawEllipse( box ); } void OutputPainterStrategy::rectangle( const QRect &box ) { #if DEBUG_EMFPAINT debugVectorImage << box; #endif m_painter->drawRect( box ); } void OutputPainterStrategy::setMapMode( const quint32 mapMode ) { #if DEBUG_EMFPAINT debugVectorImage << "Set map mode:" << mapMode; #endif m_mapMode = (MapMode)mapMode; } void OutputPainterStrategy::setBkMode( const quint32 backgroundMode ) { #if DEBUG_EMFPAINT debugVectorImage << backgroundMode; #endif if ( backgroundMode == TRANSPARENT ) { m_painter->setBackgroundMode( Qt::TransparentMode ); } else if ( backgroundMode == OPAQUE ) { m_painter->setBackgroundMode( Qt::OpaqueMode ); } else { debugVectorImage << "EMR_SETBKMODE: Unexpected value -" << backgroundMode; Q_ASSERT( 0 ); } } void OutputPainterStrategy::setPolyFillMode( const quint32 polyFillMode ) { #if DEBUG_EMFPAINT debugVectorImage << polyFillMode; #endif if ( polyFillMode == ALTERNATE ) { m_fillRule = Qt::OddEvenFill; } else if ( polyFillMode == WINDING ) { m_fillRule = Qt::WindingFill; } else { debugVectorImage << "EMR_SETPOLYFILLMODE: Unexpected value -" << polyFillMode; Q_ASSERT( 0 ); } } void OutputPainterStrategy::setLayout( const quint32 layoutMode ) { #if DEBUG_EMFPAINT debugVectorImage << layoutMode; #endif if ( layoutMode == LAYOUT_LTR ) { m_painter->setLayoutDirection( Qt::LeftToRight ); } else if ( layoutMode == LAYOUT_RTL ) { m_painter->setLayoutDirection( Qt::RightToLeft ); } else { debugVectorImage << "EMR_SETLAYOUT: Unexpected value -" << layoutMode; Q_ASSERT( 0 ); } } void OutputPainterStrategy::setTextAlign( const quint32 textAlignMode ) { #if DEBUG_EMFPAINT debugVectorImage << textAlignMode; #endif m_textAlignMode = textAlignMode; } void OutputPainterStrategy::setTextColor( const quint8 red, const quint8 green, const quint8 blue, const quint8 reserved ) { Q_UNUSED( reserved ); #if DEBUG_EMFPAINT debugVectorImage << red << green << blue << reserved; #endif m_textPen.setColor( QColor( red, green, blue ) ); } void OutputPainterStrategy::setBkColor( const quint8 red, const quint8 green, const quint8 blue, const quint8 reserved ) { Q_UNUSED( reserved ); #if DEBUG_EMFPAINT debugVectorImage << red << green << blue << reserved; #endif m_painter->setBackground( QBrush( QColor( red, green, blue ) ) ); } #define DEBUG_TEXTOUT 0 void OutputPainterStrategy::extTextOut( const QRect &bounds, const EmrTextObject &textObject ) { const QPoint &referencePoint = textObject.referencePoint(); const QString &text = textObject.textString(); #if DEBUG_EMFPAINT debugVectorImage << "Ref point: " << referencePoint << "options: " << hex << textObject.options() << dec << "rectangle: " << textObject.rectangle() << "text: " << textObject.textString(); #endif int x = referencePoint.x(); int y = referencePoint.y(); // The TA_UPDATECP flag tells us to use the current position if (m_textAlignMode & TA_UPDATECP) { // (left, top) position = current logical position #if DEBUG_EMFPAINT debugVectorImage << "TA_UPDATECP: use current logical position"; #endif x = m_currentCoords.x(); y = m_currentCoords.y(); } QFontMetrics fm = m_painter->fontMetrics(); int textWidth = fm.width(text) + fm.descent(); // fm.width(text) isn't right with Italic text int textHeight = fm.height(); // Make (x, y) be the coordinates of the upper left corner of the // rectangle surrounding the text. // // FIXME: Handle RTL text. // Horizontal align. Default is TA_LEFT. if ((m_textAlignMode & TA_HORZMASK) == TA_CENTER) x -= (textWidth / 2); else if ((m_textAlignMode & TA_HORZMASK) == TA_RIGHT) x -= textWidth; // Vertical align. Default is TA_TOP if ((m_textAlignMode & TA_VERTMASK) == TA_BASELINE) y -= (textHeight - fm.descent()); else if ((m_textAlignMode & TA_VERTMASK) == TA_BOTTOM) { y -= textHeight; } #if DEBUG_EMFPAINT debugVectorImage << "textWidth = " << textWidth << "height = " << textHeight; debugVectorImage << "font = " << m_painter->font() << "pointSize = " << m_painter->font().pointSize() << "ascent = " << fm.ascent() << "descent = " << fm.descent() << "height = " << fm.height() << "leading = " << fm.leading(); debugVectorImage << "actual point = " << x << y; #endif // Debug code that paints a rectangle around the output area. #if DEBUG_TEXTOUT m_painter->save(); m_painter->setWorldTransform(m_outputTransform); - m_painter->setPen(Qt::black); + m_painter->setPen(QPen(Qt::black, 0)); m_painter->drawRect(bounds); m_painter->restore(); #endif // Actual painting starts here. m_painter->save(); // Find out how much we have to scale the text to make it fit into // the output rectangle. Normally this wouldn't be necessary, but // when fonts are switched, the replacement fonts are sometimes // wider than the original fonts. QRect worldRect(m_worldTransform.mapRect(QRect(x, y, textWidth, textHeight))); //debugVectorImage << "rects:" << QRect(x, y, textWidth, textHeight) << worldRect; qreal scaleX = qreal(1.0); qreal scaleY = qreal(1.0); if (bounds.width() < worldRect.width()) scaleX = qreal(bounds.width()) / qreal(worldRect.width()); if (bounds.height() < worldRect.height()) scaleY = qreal(bounds.height()) / qreal(worldRect.height()); //debugVectorImage << "scale:" << scaleX << scaleY; if (scaleX < qreal(1.0) || scaleY < qreal(1.0)) { m_painter->translate(-x, -y); m_painter->scale(scaleX, scaleY); m_painter->translate(x / scaleX, y / scaleY); } // Use the special pen defined by mTextPen for text. QPen savePen = m_painter->pen(); m_painter->setPen(m_textPen); m_painter->drawText(int(x / scaleX), int(y / scaleY), textWidth, textHeight, Qt::AlignLeft|Qt::AlignTop, text); m_painter->setPen(savePen); m_painter->restore(); } void OutputPainterStrategy::moveToEx( const qint32 x, const qint32 y ) { #if DEBUG_EMFPAINT debugVectorImage << x << y; #endif if ( m_currentlyBuildingPath ) m_path->moveTo( QPoint( x, y ) ); else m_currentCoords = QPoint( x, y ); } void OutputPainterStrategy::lineTo( const QPoint &finishPoint ) { #if DEBUG_EMFPAINT debugVectorImage << finishPoint; #endif if ( m_currentlyBuildingPath ) m_path->lineTo( finishPoint ); else { m_painter->drawLine( m_currentCoords, finishPoint ); m_currentCoords = finishPoint; } } void OutputPainterStrategy::arcTo( const QRect &box, const QPoint &start, const QPoint &end ) { #if DEBUG_EMFPAINT debugVectorImage << box << start << end; #endif QPoint centrePoint = box.center(); qreal startAngle = angleFromArc( centrePoint, start ); qreal endAngle = angleFromArc( centrePoint, end ); qreal spanAngle = angularSpan( startAngle, endAngle ); m_path->arcTo( box, startAngle, spanAngle ); } void OutputPainterStrategy::polygon16( const QRect &bounds, const QList points ) { Q_UNUSED( bounds ); #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif QVector pointVector = points.toVector(); m_painter->drawPolygon( pointVector.constData(), pointVector.size(), m_fillRule ); } void OutputPainterStrategy::polyLine( const QRect &bounds, const QList points ) { Q_UNUSED( bounds ); #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif QVector pointVector = points.toVector(); m_painter->drawPolyline( pointVector.constData(), pointVector.size() ); } void OutputPainterStrategy::polyLine16( const QRect &bounds, const QList points ) { #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif polyLine( bounds, points ); } void OutputPainterStrategy::polyPolygon16( const QRect &bounds, const QList< QVector< QPoint > > &points ) { Q_UNUSED( bounds ); #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif for ( int i = 0; i < points.size(); ++i ) { m_painter->drawPolygon( points[i].constData(), points[i].size(), m_fillRule ); } } void OutputPainterStrategy::polyPolyLine16( const QRect &bounds, const QList< QVector< QPoint > > &points ) { Q_UNUSED( bounds ); #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif for ( int i = 0; i < points.size(); ++i ) { m_painter->drawPolyline( points[i].constData(), points[i].size() ); } } void OutputPainterStrategy::polyLineTo16( const QRect &bounds, const QList points ) { Q_UNUSED( bounds ); #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif for ( int i = 0; i < points.count(); ++i ) { m_path->lineTo( points[i] ); } } void OutputPainterStrategy::polyBezier16( const QRect &bounds, const QList points ) { #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif Q_UNUSED( bounds ); QPainterPath path; path.moveTo( points[0] ); for ( int i = 1; i < points.count(); i+=3 ) { path.cubicTo( points[i], points[i+1], points[i+2] ); } m_painter->drawPath( path ); } void OutputPainterStrategy::polyBezierTo16( const QRect &bounds, const QList points ) { #if DEBUG_EMFPAINT debugVectorImage << bounds << points; #endif Q_UNUSED( bounds ); for ( int i = 0; i < points.count(); i+=3 ) { m_path->cubicTo( points[i], points[i+1], points[i+2] ); } } void OutputPainterStrategy::fillPath( const QRect &bounds ) { #if DEBUG_EMFPAINT debugVectorImage << bounds; #endif Q_UNUSED( bounds ); m_painter->fillPath( *m_path, m_painter->brush() ); } void OutputPainterStrategy::strokeAndFillPath( const QRect &bounds ) { #if DEBUG_EMFPAINT debugVectorImage << bounds; #endif Q_UNUSED( bounds ); m_painter->drawPath( *m_path ); } void OutputPainterStrategy::strokePath( const QRect &bounds ) { #if DEBUG_EMFPAINT debugVectorImage << bounds; #endif Q_UNUSED( bounds ); m_painter->strokePath( *m_path, m_painter->pen() ); } void OutputPainterStrategy::setClipPath( const quint32 regionMode ) { #if DEBUG_EMFPAINT debugVectorImage << hex << regionMode << dec; #endif switch ( regionMode ) { case RGN_AND: m_painter->setClipPath( *m_path, Qt::IntersectClip ); break; // QT5TODO: T477 Qt::UniteClip got removed, see https://bugreports.qt.io/browse/QTBUG-20140 // case RGN_OR: // m_painter->setClipPath( *m_path, Qt::UniteClip ); // break; case RGN_COPY: m_painter->setClipPath( *m_path, Qt::ReplaceClip ); break; default: warnVectorImage << "Unexpected / unsupported clip region mode:" << regionMode; Q_ASSERT( 0 ); } } void OutputPainterStrategy::bitBlt( BitBltRecord &bitBltRecord ) { #if DEBUG_EMFPAINT debugVectorImage << bitBltRecord.xDest() << bitBltRecord.yDest() << bitBltRecord.cxDest() << bitBltRecord.cyDest() << hex << bitBltRecord.rasterOperation() << dec << bitBltRecord.bkColorSrc(); #endif QRect target( bitBltRecord.xDest(), bitBltRecord.yDest(), bitBltRecord.cxDest(), bitBltRecord.cyDest() ); // 0x00f00021 is the PatCopy raster operation which just fills a rectangle with a brush. // This seems to be the most common one. // // FIXME: Implement the rest of the raster operations. if (bitBltRecord.rasterOperation() == 0x00f00021) { // Would have been nice if we didn't have to pull out the // brush to use it with fillRect()... QBrush brush = m_painter->brush(); m_painter->fillRect(target, brush); } else if ( bitBltRecord.hasImage() ) { m_painter->drawImage( target, bitBltRecord.image() ); } } void OutputPainterStrategy::setStretchBltMode( const quint32 stretchMode ) { #if DEBUG_EMFPAINT debugVectorImage << hex << stretchMode << dec; #endif switch ( stretchMode ) { case 0x01: debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_ANDSCANS"; break; case 0x02: debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_ORSCANS"; break; case 0x03: debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_DELETESCANS"; break; case 0x04: debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_HALFTONE"; break; default: debugVectorImage << "EMR_STRETCHBLTMODE - unknown stretch mode:" << stretchMode; } } void OutputPainterStrategy::stretchDiBits( StretchDiBitsRecord &record ) { #if DEBUG_EMFPAINT debugVectorImage << "Bounds: " << record.bounds(); debugVectorImage << "Dest rect: " << record.xDest() << record.yDest() << record.cxDest() << record.cyDest(); debugVectorImage << "Src rect: " << record.xSrc() << record.ySrc() << record.cxSrc() << record.cySrc(); debugVectorImage << "Raster op: " << hex << record.rasterOperation() << dec; //<< record.bkColorSrc(); debugVectorImage << "usageSrc: " << record.usageSrc(); #endif QPoint targetPosition( record.xDest(), record.yDest() ); QSize targetSize( record.cxDest(), record.cyDest() ); QPoint sourcePosition( record.xSrc(), record.ySrc() ); QSize sourceSize( record.cxSrc(), record.cySrc() ); // special cases, from [MS-EMF] Section 2.3.1.7: // "This record specifies a mirror-image copy of the source bitmap to the // destination if the signs of the height or width fields differ. That is, // if cxSrc and cxDest have different signs, this record specifies a mirror // image of the source bitmap along the x-axis. If cySrc and cyDest have // different signs, this record specifies a mirror image of the source // bitmap along the y-axis." QRect target( targetPosition, targetSize ); QRect source( sourcePosition, sourceSize ); #if DEBUG_EMFPAINT //debugVectorImage << "image size" << record.image()->size(); debugVectorImage << "Before transformation:"; debugVectorImage << " target" << target; debugVectorImage << " source" << source; #endif if ( source.width() < 0 && target.width() > 0 ) { sourceSize.rwidth() *= -1; sourcePosition.rx() -= sourceSize.width(); source = QRect( sourcePosition, sourceSize ); } if ( source.width() > 0 && target.width() < 0 ) { targetSize.rwidth() *= -1; targetPosition.rx() -= targetSize.width(); target = QRect( targetPosition, targetSize ); } if ( source.height() < 0 && target.height() > 0 ) { sourceSize.rheight() *= -1; sourcePosition.ry() -= sourceSize.height(); source = QRect( sourcePosition, sourceSize ); } if ( source.height() > 0 && target.height() < 0 ) { targetSize.rheight() *= -1; targetPosition.ry() -= targetSize.height(); target = QRect( targetPosition, targetSize ); } #if DEBUG_EMFPAINT debugVectorImage << "After transformation:"; debugVectorImage << " target" << target; debugVectorImage << " source" << source; QImage image = record.image(); debugVectorImage << "Image" << image.size(); #endif QPainter::RenderHints oldRenderHints = m_painter->renderHints(); QPainter::CompositionMode oldCompMode = m_painter->compositionMode(); m_painter->setRenderHints(0); // Antialiasing makes composition modes invalid m_painter->setCompositionMode(rasteropToQtComposition(record.rasterOperation())); m_painter->drawImage(target, record.image(), source); m_painter->setCompositionMode(oldCompMode); m_painter->setRenderHints(oldRenderHints); } // ---------------------------------------------------------------- // Private functions void OutputPainterStrategy::printPainterTransform(const char *leadText) { QTransform transform; recalculateWorldTransform(); debugVectorImage << leadText << "world transform " << m_worldTransform << "incl output transform: " << m_painter->transform(); } qreal OutputPainterStrategy::angleFromArc( const QPoint ¢rePoint, const QPoint &radialPoint ) { double dX = radialPoint.x() - centrePoint.x(); double dY = centrePoint.y() - radialPoint.y(); // Qt angles are in degrees. atan2 returns radians return ( atan2( dY, dX ) * 180 / M_PI ); } qreal OutputPainterStrategy::angularSpan( const qreal startAngle, const qreal endAngle ) { qreal spanAngle = endAngle - startAngle; if ( spanAngle <= 0 ) { spanAngle += 360; } return spanAngle; } int OutputPainterStrategy::convertFontWeight( quint32 emfWeight ) { // FIXME: See how it's done in the wmf library and check if this is suitable here. if ( emfWeight == 0 ) { return QFont::Normal; } else if ( emfWeight <= 200 ) { return QFont::Light; } else if ( emfWeight <= 450 ) { return QFont::Normal; } else if ( emfWeight <= 650 ) { return QFont::DemiBold; } else if ( emfWeight <= 850 ) { return QFont::Bold; } else { return QFont::Black; } } static QPainter::CompositionMode rasteropToQtComposition(long rop) { // Code copied from filters/libkowmf/qwmf.cc // FIXME: Should be cleaned up /* TODO: Ternary raster operations 0x00C000CA dest = (source AND pattern) 0x00F00021 dest = pattern 0x00FB0A09 dest = DPSnoo 0x005A0049 dest = pattern XOR dest */ static const struct OpTab { long winRasterOp; QPainter::CompositionMode qtRasterOp; } opTab[] = { // ### untested (conversion from Qt::RasterOp) { 0x00CC0020, QPainter::CompositionMode_Source }, // CopyROP { 0x00EE0086, QPainter::RasterOp_SourceOrDestination }, // OrROP { 0x008800C6, QPainter::RasterOp_SourceAndDestination }, // AndROP { 0x00660046, QPainter::RasterOp_SourceXorDestination }, // XorROP // ---------------------------------------------------------------- // FIXME: Checked above this, below is still todo // ---------------------------------------------------------------- { 0x00440328, QPainter::CompositionMode_DestinationOut }, // AndNotROP { 0x00330008, QPainter::CompositionMode_DestinationOut }, // NotCopyROP { 0x001100A6, QPainter::CompositionMode_SourceOut }, // NandROP { 0x00C000CA, QPainter::CompositionMode_Source }, // CopyROP { 0x00BB0226, QPainter::CompositionMode_Destination }, // NotOrROP { 0x00F00021, QPainter::CompositionMode_Source }, // CopyROP { 0x00FB0A09, QPainter::CompositionMode_Source }, // CopyROP { 0x005A0049, QPainter::CompositionMode_Source }, // CopyROP { 0x00550009, QPainter::CompositionMode_DestinationOut }, // NotROP { 0x00000042, QPainter::CompositionMode_Clear }, // ClearROP { 0x00FF0062, QPainter::CompositionMode_Source } // SetROP }; int i; for (i = 0 ; i < 15 ; i++) if (opTab[i].winRasterOp == rop) break; if (i < 15) return opTab[i].qtRasterOp; else return QPainter::CompositionMode_Source; } } // xnamespace... diff --git a/libs/widgets/KoColorPatch.cpp b/libs/widgets/KoColorPatch.cpp index 6b000686a5b..f36267dafdc 100644 --- a/libs/widgets/KoColorPatch.cpp +++ b/libs/widgets/KoColorPatch.cpp @@ -1,66 +1,66 @@ /** * Copyright (c) 2006 C. Boemann (cbo@boemann.dk) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoColorPatch.h" #include KoColorPatch::KoColorPatch( QWidget *parent ) : QFrame( parent ) { setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); } KoColorPatch::~KoColorPatch() { } QSize KoColorPatch::sizeHint() const { return QSize(12,12); } void KoColorPatch::setColor(const KoColor& c) { m_color = c; update(); } KoColor KoColorPatch::color() const { return m_color; } void KoColorPatch::mousePressEvent (QMouseEvent *e ) { Q_UNUSED( e ); emit triggered(this); } void KoColorPatch::paintEvent(QPaintEvent *pe) { QColor qc; m_color.toQColor(&qc); QFrame::paintEvent(pe); QPainter painter( this ); - painter.setPen(qc); + painter.setPen(QPen(qc, 0)); painter.setBrush(QBrush(qc)); painter.drawRect(contentsRect()); } diff --git a/libs/widgets/KoColorSlider.cpp b/libs/widgets/KoColorSlider.cpp index 13ce8ac07ba..2ea93c50a71 100644 --- a/libs/widgets/KoColorSlider.cpp +++ b/libs/widgets/KoColorSlider.cpp @@ -1,199 +1,199 @@ /* This file is part of the KDE project Copyright (C) 2006 Sven Langkamp Copyright (c) 2009 Cyrille Berger This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoColorSlider.h" #include "KoColorSpace.h" #include #include #include #include #include #include #define ARROWSIZE 8 struct Q_DECL_HIDDEN KoColorSlider::Private { Private() : upToDate(false), displayRenderer(0) {} KoColor minColor; KoColor maxColor; QPixmap pixmap; bool upToDate; QPointer displayRenderer; }; KoColorSlider::KoColorSlider(QWidget* parent, KoColorDisplayRendererInterface *displayRenderer) : KSelector(parent) , d(new Private) { setMaximum(255); d->displayRenderer = displayRenderer; connect(d->displayRenderer, SIGNAL(displayConfigurationChanged()), SLOT(update())); } KoColorSlider::KoColorSlider(Qt::Orientation o, QWidget *parent, KoColorDisplayRendererInterface *displayRenderer) : KSelector(o, parent), d(new Private) { setMaximum(255); d->displayRenderer = displayRenderer; connect(d->displayRenderer, SIGNAL(displayConfigurationChanged()), SLOT(update())); } KoColorSlider::~KoColorSlider() { delete d; } void KoColorSlider::setColors(const KoColor& mincolor, const KoColor& maxcolor) { d->minColor = mincolor; d->maxColor = maxcolor; d->upToDate = false; QTimer::singleShot(1, this, SLOT(update())); } void KoColorSlider::drawContents( QPainter *painter ) { QPixmap checker(8, 8); QPainter p(&checker); p.fillRect(0, 0, 4, 4, Qt::lightGray); p.fillRect(4, 0, 4, 4, Qt::darkGray); p.fillRect(0, 4, 4, 4, Qt::darkGray); p.fillRect(4, 4, 4, 4, Qt::lightGray); p.end(); QRect contentsRect_(contentsRect()); painter->fillRect(contentsRect_, QBrush(checker)); if( !d->upToDate || d->pixmap.isNull() || d->pixmap.width() != contentsRect_.width() || d->pixmap.height() != contentsRect_.height() ) { KoColor c = d->minColor; // smart way to fetch colorspace QColor color; const quint8 *colors[2]; colors[0] = d->minColor.data(); colors[1] = d->maxColor.data(); KoMixColorsOp * mixOp = c.colorSpace()->mixColorsOp(); QImage image(contentsRect_.width(), contentsRect_.height(), QImage::Format_ARGB32 ); if( orientation() == Qt::Horizontal ) { for (int x = 0; x < contentsRect_.width(); x++) { qreal t = static_cast(x) / (contentsRect_.width() - 1); qint16 colorWeights[2]; colorWeights[0] = static_cast((1.0 - t) * 255 + 0.5); colorWeights[1] = 255 - colorWeights[0]; mixOp->mixColors(colors, colorWeights, 2, c.data()); if (d->displayRenderer) { color = d->displayRenderer->toQColor(c); } else { color = c.toQColor(); } for (int y = 0; y < contentsRect_.height(); y++) image.setPixel(x, y, color.rgba()); } } else { for (int y = 0; y < contentsRect_.height(); y++) { qreal t = static_cast(y) / (contentsRect_.height() - 1); qint16 colorWeights[2]; colorWeights[0] = static_cast((t) * 255 + 0.5); colorWeights[1] = 255 - colorWeights[0]; mixOp->mixColors(colors, colorWeights, 2, c.data()); if (d->displayRenderer) { color = d->displayRenderer->toQColor(c); } else { color = c.toQColor(); } for (int x = 0; x < contentsRect_.width(); x++) image.setPixel(x, y, color.rgba()); } } d->pixmap = QPixmap::fromImage(image); d->upToDate = true; } painter->drawPixmap( contentsRect_, d->pixmap, QRect( 0, 0, d->pixmap.width(), d->pixmap.height()) ); } KoColor KoColorSlider::currentColor() const { const quint8 *colors[2]; colors[0] = d->minColor.data(); colors[1] = d->maxColor.data(); KoMixColorsOp * mixOp = d->minColor.colorSpace()->mixColorsOp(); KoColor c(d->minColor.colorSpace()); qint16 weights[2]; weights[1] = (value() - minimum()) / qreal(maximum() - minimum()) * 255; weights[0] = 255 - weights[1]; mixOp->mixColors(colors, weights, 2, c.data()); return c; } void KoColorSlider::drawArrow(QPainter *painter, const QPoint &pos) { - painter->setPen(palette().text().color()); + painter->setPen(QPen(palette().text().color(), 0)); painter->setBrush(palette().text()); QStyleOption o; o.initFrom(this); o.state &= ~QStyle::State_MouseOver; if ( orientation() == Qt::Vertical ) { o.rect = QRect( pos.x(), pos.y() - ARROWSIZE / 2, ARROWSIZE, ARROWSIZE ); } else { o.rect = QRect( pos.x() - ARROWSIZE / 2, pos.y(), ARROWSIZE, ARROWSIZE ); } QStyle::PrimitiveElement arrowPE; switch (arrowDirection()) { case Qt::UpArrow: arrowPE = QStyle::PE_IndicatorArrowUp; break; case Qt::DownArrow: arrowPE = QStyle::PE_IndicatorArrowDown; break; case Qt::RightArrow: arrowPE = QStyle::PE_IndicatorArrowRight; break; case Qt::LeftArrow: default: arrowPE = QStyle::PE_IndicatorArrowLeft; break; } style()->drawPrimitive(arrowPE, &o, painter, this); } diff --git a/libs/widgets/KoContextBarButton.cpp b/libs/widgets/KoContextBarButton.cpp index 849833be95a..e87ac8c38fc 100644 --- a/libs/widgets/KoContextBarButton.cpp +++ b/libs/widgets/KoContextBarButton.cpp @@ -1,186 +1,186 @@ // vim: set tabstop=4 shiftwidth=4 noexpandtab: /* This file is part of the KDE project Copyright 2011 Aurélien Gâteau Copyright 2011 Paul Mendez 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 "KoContextBarButton.h" // Qt #include #include #include #include #include /** How lighter is the border of context bar buttons */ const int CONTEXTBAR_BORDER_LIGHTNESS = 140; /** How darker is the background of context bar buttons */ const int CONTEXTBAR_BACKGROUND_DARKNESS = 80; /** How lighter are context bar buttons when under mouse */ const int CONTEXTBAR_MOUSEOVER_LIGHTNESS = 120; /** Radius of ContextBarButtons */ const int CONTEXTBAR_RADIUS = 50; KoContextBarButton::KoContextBarButton(const QString &iconName, QWidget* parent) : QToolButton(parent) , m_isHovered(false) , m_fadingValue(0) , m_fadingTimeLine(0) { const int size = QApplication::style()->pixelMetric(QStyle::PM_ButtonIconSize); setIconSize(QSize(size, size)); setAutoRaise(true); setIcon(QIcon::fromTheme(iconName)); } KoContextBarButton::~KoContextBarButton() { } void KoContextBarButton::paintEvent(QPaintEvent*) { QStylePainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QStyleOptionToolButton opt; initStyleOption(&opt); const QColor bgColor = palette().color(QPalette::Highlight); QColor color = bgColor.dark(CONTEXTBAR_BACKGROUND_DARKNESS); QColor borderColor = bgColor.light(CONTEXTBAR_BORDER_LIGHTNESS); if (opt.state & QStyle::State_MouseOver && opt.state & QStyle::State_Enabled) { color = color.light(CONTEXTBAR_MOUSEOVER_LIGHTNESS); borderColor = borderColor.lighter(CONTEXTBAR_MOUSEOVER_LIGHTNESS); } const QRectF rectF = QRectF(opt.rect).adjusted(0.5, 0.5, -0.5, -0.5); QPainterPath path; path.addRoundRect(rectF, CONTEXTBAR_RADIUS, CONTEXTBAR_RADIUS); if (m_fadingValue < 255) { color.setAlpha(m_fadingValue); } // Background painter.fillPath(path, color); if (opt.state & QStyle::State_Raised && opt.state & QStyle::State_Enabled) { // Bottom shadow QLinearGradient gradient(rectF.bottomLeft(), rectF.bottomLeft() - QPoint(0, 5)); gradient.setColorAt(0, QColor::fromHsvF(0, 0, 0, .3)); gradient.setColorAt(1, Qt::transparent); painter.fillPath(path, gradient); // Left shadow gradient.setFinalStop(rectF.bottomLeft() + QPoint(3, 0)); painter.fillPath(path, gradient); } else { // Top shadow QLinearGradient gradient(rectF.topLeft(), rectF.topLeft() + QPoint(0, 5)); gradient.setColorAt(0, QColor::fromHsvF(0, 0, 0, .3)); gradient.setColorAt(1, Qt::transparent); painter.fillPath(path, gradient); // Left shadow gradient.setFinalStop(rectF.topLeft() + QPoint(5, 0)); painter.fillPath(path, gradient); } // Border - painter.setPen(borderColor); + painter.setPen(QPen(borderColor, 0)); painter.drawPath(path); // Content painter.drawControl(QStyle::CE_ToolButtonLabel, opt); } void KoContextBarButton::startFading() { Q_ASSERT(!m_fadingTimeLine); const int duration = 300; m_fadingTimeLine = new QTimeLine(duration, this); connect(m_fadingTimeLine, SIGNAL(frameChanged(int)), this, SLOT(setFadingValue(int))); m_fadingTimeLine->setFrameRange(0, 255); m_fadingTimeLine->start(); m_fadingValue = 0; } void KoContextBarButton::stopFading() { if (m_fadingTimeLine) { m_fadingTimeLine->stop(); delete m_fadingTimeLine; m_fadingTimeLine = 0; } m_fadingValue = 0; } void KoContextBarButton::enterEvent(QEvent *event) { QToolButton::enterEvent(event); // if the mouse cursor is above the selection toggle, display // it immediately without fading timer m_isHovered = true; if (m_fadingTimeLine) { m_fadingTimeLine->stop(); } m_fadingValue = 255; update(); } void KoContextBarButton::leaveEvent(QEvent *event) { QToolButton::leaveEvent(event); m_isHovered = false; update(); } void KoContextBarButton::setFadingValue(int value) { m_fadingValue = value; if (m_fadingValue >= 255) { Q_ASSERT(m_fadingTimeLine); m_fadingTimeLine->stop(); } update(); } void KoContextBarButton::showEvent(QShowEvent *event) { stopFading(); startFading(); QToolButton::showEvent(event); } void KoContextBarButton::hideEvent(QHideEvent *event) { stopFading(); QToolButton::hideEvent(event); } diff --git a/libs/widgets/KoDualColorButton.cpp b/libs/widgets/KoDualColorButton.cpp index 991341908cb..4c208bf0076 100644 --- a/libs/widgets/KoDualColorButton.cpp +++ b/libs/widgets/KoDualColorButton.cpp @@ -1,336 +1,336 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Daniel M. Duley This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoDualColorButton.h" #include "KoColor.h" #include "KoColorDisplayRendererInterface.h" #include #include "dcolorarrow.xbm" #include "dcolorreset.xpm" #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoDualColorButton::Private { public: Private(const KoColor &fgColor, const KoColor &bgColor, QWidget *_dialogParent, const KoColorDisplayRendererInterface *_displayRenderer) : dialogParent(_dialogParent) , dragFlag( false ) , miniCtlFlag( false ) , foregroundColor(fgColor) , backgroundColor(bgColor) , displayRenderer(_displayRenderer) { updateArrows(); resetPixmap = QPixmap( (const char **)dcolorreset_xpm ); popDialog = true; } void updateArrows() { arrowBitmap = QPixmap(12,12); arrowBitmap.fill(Qt::transparent); QPainter p(&arrowBitmap); - p.setPen(dialogParent->palette().foreground().color()); + p.setPen(QPen(dialogParent->palette().foreground().color(), 0)); // arrow pointing left p.drawLine(0, 3, 7, 3); p.drawLine(1, 2, 1, 4); p.drawLine(2, 1, 2, 5); p.drawLine(3, 0, 3, 6); // arrow pointing down p.drawLine(8, 4, 8, 11); p.drawLine(5, 8, 11, 8); p.drawLine(6, 9, 10, 9); p.drawLine(7, 10, 9, 10); } QWidget* dialogParent; QPixmap arrowBitmap; QPixmap resetPixmap; bool dragFlag, miniCtlFlag; KoColor foregroundColor; KoColor backgroundColor; QPoint dragPosition; Selection tmpSelection; bool popDialog; const KoColorDisplayRendererInterface *displayRenderer; void init(KoDualColorButton *q); }; void KoDualColorButton::Private::init(KoDualColorButton *q) { if ( q->sizeHint().isValid() ) q->setMinimumSize( q->sizeHint() ); q->setAcceptDrops( true ); } KoDualColorButton::KoDualColorButton(const KoColor &foregroundColor, const KoColor &backgroundColor, QWidget *parent, QWidget* dialogParent ) : QWidget( parent ), d( new Private(foregroundColor, backgroundColor, dialogParent, KoDumbColorDisplayRenderer::instance()) ) { d->init(this); } KoDualColorButton::KoDualColorButton(const KoColor &foregroundColor, const KoColor &backgroundColor, const KoColorDisplayRendererInterface *displayRenderer, QWidget *parent, QWidget* dialogParent) : QWidget( parent ), d( new Private(foregroundColor, backgroundColor, dialogParent, displayRenderer) ) { d->init(this); } KoDualColorButton::~KoDualColorButton() { delete d; } KoColor KoDualColorButton::foregroundColor() const { return d->foregroundColor; } KoColor KoDualColorButton::backgroundColor() const { return d->backgroundColor; } bool KoDualColorButton::popDialog() const { return d->popDialog; } QSize KoDualColorButton::sizeHint() const { return QSize( 34, 34 ); } void KoDualColorButton::setForegroundColor( const KoColor &color ) { d->foregroundColor = color; repaint(); } void KoDualColorButton::setBackgroundColor( const KoColor &color ) { d->backgroundColor = color; repaint(); } void KoDualColorButton::setPopDialog( bool popDialog ) { d->popDialog = popDialog; } void KoDualColorButton::metrics( QRect &foregroundRect, QRect &backgroundRect ) { foregroundRect = QRect( 0, 0, width() - 14, height() - 14 ); backgroundRect = QRect( 14, 14, width() - 14, height() - 14 ); } void KoDualColorButton::paintEvent(QPaintEvent *) { QRect foregroundRect; QRect backgroundRect; QPainter painter( this ); metrics( foregroundRect, backgroundRect ); QBrush defBrush = palette().brush( QPalette::Button ); QBrush foregroundBrush( d->displayRenderer->toQColor(d->foregroundColor), Qt::SolidPattern ); QBrush backgroundBrush( d->displayRenderer->toQColor(d->backgroundColor), Qt::SolidPattern ); qDrawShadeRect( &painter, backgroundRect, palette(), false, 1, 0, isEnabled() ? &backgroundBrush : &defBrush ); qDrawShadeRect( &painter, foregroundRect, palette(), false, 1, 0, isEnabled() ? &foregroundBrush : &defBrush ); painter.setPen( palette().color( QPalette::Shadow ) ); painter.drawPixmap( foregroundRect.right() + 2, 1, d->arrowBitmap ); painter.drawPixmap( 1, foregroundRect.bottom() + 2, d->resetPixmap ); } void KoDualColorButton::dragEnterEvent( QDragEnterEvent *event ) { event->setAccepted( isEnabled() && KColorMimeData::canDecode( event->mimeData() ) ); } void KoDualColorButton::dropEvent( QDropEvent *event ) { Q_UNUSED(event); /* QColor color = KColorMimeData::fromMimeData( event->mimeData() ); if ( color.isValid() ) { if ( d->selection == Foreground ) { d->foregroundColor = color; emit foregroundColorChanged( color ); } else { d->backgroundColor = color; emit backgroundColorChanged( color ); } repaint(); } */ } void KoDualColorButton::mousePressEvent( QMouseEvent *event ) { QRect foregroundRect; QRect backgroundRect; metrics( foregroundRect, backgroundRect ); d->dragPosition = event->pos(); d->dragFlag = false; if ( foregroundRect.contains( d->dragPosition ) ) { d->tmpSelection = Foreground; d->miniCtlFlag = false; } else if( backgroundRect.contains( d->dragPosition ) ) { d->tmpSelection = Background; d->miniCtlFlag = false; } else if ( event->pos().x() > foregroundRect.width() ) { // We handle the swap and reset controls as soon as the mouse is // is pressed and ignore further events on this click (mosfet). KoColor tmp = d->foregroundColor; d->foregroundColor = d->backgroundColor; d->backgroundColor = tmp; emit backgroundColorChanged( d->backgroundColor ); emit foregroundColorChanged( d->foregroundColor ); d->miniCtlFlag = true; } else if ( event->pos().x() < backgroundRect.x() ) { d->foregroundColor = d->displayRenderer->approximateFromRenderedQColor(Qt::black); d->backgroundColor = d->displayRenderer->approximateFromRenderedQColor(Qt::white); emit backgroundColorChanged( d->backgroundColor ); emit foregroundColorChanged( d->foregroundColor ); d->miniCtlFlag = true; } repaint(); } void KoDualColorButton::mouseMoveEvent( QMouseEvent *event ) { if ( !d->miniCtlFlag ) { int delay = QApplication::startDragDistance(); if ( event->x() >= d->dragPosition.x() + delay || event->x() <= d->dragPosition.x() - delay || event->y() >= d->dragPosition.y() + delay || event->y() <= d->dragPosition.y() - delay ) { KColorMimeData::createDrag( d->tmpSelection == Foreground ? d->displayRenderer->toQColor(d->foregroundColor) : d->displayRenderer->toQColor(d->backgroundColor), this )->start(); d->dragFlag = true; } } } void KoDualColorButton::mouseReleaseEvent( QMouseEvent *event ) { d->dragFlag = false; if ( d->miniCtlFlag ) return; d->miniCtlFlag = false; QRect foregroundRect; QRect backgroundRect; metrics( foregroundRect, backgroundRect ); if ( foregroundRect.contains( event->pos() )) { if(d->tmpSelection == Foreground ) { if( d->popDialog) { QColor c = d->displayRenderer->toQColor(d->foregroundColor); c = QColorDialog::getColor(c, this) ; if (c.isValid()) { d->foregroundColor = d->displayRenderer->approximateFromRenderedQColor(c); emit foregroundColorChanged(d->foregroundColor); } } else emit pleasePopDialog( d->foregroundColor); } else { d->foregroundColor = d->backgroundColor; emit foregroundColorChanged( d->foregroundColor ); } } else if ( backgroundRect.contains( event->pos() )) { if(d->tmpSelection == Background ) { if( d->popDialog) { QColor c = d->displayRenderer->toQColor(d->backgroundColor); c = QColorDialog::getColor(c, this); if (c.isValid()) { d->backgroundColor = d->displayRenderer->approximateFromRenderedQColor(c); emit backgroundColorChanged(d->backgroundColor); } } else emit pleasePopDialog( d->backgroundColor); } else { d->backgroundColor = d->foregroundColor; emit backgroundColorChanged( d->backgroundColor ); } } repaint(); } void KoDualColorButton::changeEvent(QEvent *event) { QWidget::changeEvent(event); switch (event->type()) { case QEvent::StyleChange: case QEvent::PaletteChange: d->updateArrows(); default: break; } } diff --git a/libs/widgets/KoPagePreviewWidget.cpp b/libs/widgets/KoPagePreviewWidget.cpp index 006b9331818..f9fab1886d9 100644 --- a/libs/widgets/KoPagePreviewWidget.cpp +++ b/libs/widgets/KoPagePreviewWidget.cpp @@ -1,164 +1,164 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thomas Zander * Copyright (C) 2006 Gary Cramblitt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoPagePreviewWidget.h" #include #include #include #include #include #include class Q_DECL_HIDDEN KoPagePreviewWidget::Private { public: KoPageLayout pageLayout; KoColumns columns; }; KoPagePreviewWidget::KoPagePreviewWidget(QWidget *parent) : QWidget(parent) , d(new Private) { setMinimumSize( 100, 100 ); } KoPagePreviewWidget::~KoPagePreviewWidget() { delete d; } void KoPagePreviewWidget::paintEvent(QPaintEvent *event) { Q_UNUSED(event); // resolution[XY] is in pixel per pt qreal resolutionX = POINT_TO_INCH( static_cast(KoDpi::dpiX()) ); qreal resolutionY = POINT_TO_INCH( static_cast(KoDpi::dpiY()) ); qreal pageWidth = d->pageLayout.width * resolutionX; qreal pageHeight = d->pageLayout.height * resolutionY; const bool pageSpread = (d->pageLayout.bindingSide >= 0 && d->pageLayout.pageEdge >= 0); qreal sheetWidth = pageWidth / (pageSpread?2:1); qreal zoomH = (height() * 90 / 100) / pageHeight; qreal zoomW = (width() * 90 / 100) / pageWidth; qreal zoom = qMin( zoomW, zoomH ); pageWidth *= zoom; sheetWidth *= zoom; pageHeight *= zoom; QPainter painter( this ); QRect page = QRectF((width() - pageWidth) / 2.0, (height() - pageHeight) / 2.0, sheetWidth, pageHeight).toRect(); painter.save(); drawPage(painter, zoom, page, true); painter.restore(); if(pageSpread) { page.moveLeft(page.left() + (int) (sheetWidth)); painter.save(); drawPage(painter, zoom, page, false); painter.restore(); } painter.end(); // paint scale } void KoPagePreviewWidget::drawPage(QPainter &painter, qreal zoom, const QRect &dimensions, bool left) { painter.fillRect(dimensions, QBrush(palette().base())); - painter.setPen(QPen(palette().color(QPalette::Dark))); + painter.setPen(QPen(palette().color(QPalette::Dark), 0)); painter.drawRect(dimensions); // draw text areas QRect textArea = dimensions; if ((d->pageLayout.topMargin == 0 && d->pageLayout.bottomMargin == 0 && d->pageLayout.leftMargin == 0 && d->pageLayout.rightMargin == 0) || ( d->pageLayout.pageEdge == 0 && d->pageLayout.bindingSide == 0)) { // no margin return; } else { textArea.setTop(textArea.top() + qRound(zoom * d->pageLayout.topMargin)); textArea.setBottom(textArea.bottom() - qRound(zoom * d->pageLayout.bottomMargin)); qreal leftMargin, rightMargin; if(d->pageLayout.bindingSide < 0) { // normal margins. leftMargin = d->pageLayout.leftMargin; rightMargin = d->pageLayout.rightMargin; } else { // margins mirrored for left/right pages leftMargin = d->pageLayout.bindingSide; rightMargin = d->pageLayout.pageEdge; if(left) qSwap(leftMargin, rightMargin); } textArea.setLeft(textArea.left() + qRound(zoom * leftMargin)); textArea.setRight(textArea.right() - qRound(zoom * rightMargin)); } painter.setBrush( QBrush( palette().color(QPalette::ButtonText), Qt::HorPattern ) ); - painter.setPen( palette().color(QPalette::Dark) ); + painter.setPen(QPen(palette().color(QPalette::Dark), 0)); // uniform columns? if (d->columns.columnData.isEmpty()) { qreal columnWidth = (textArea.width() + (d->columns.gapWidth * zoom)) / d->columns.count; int width = qRound(columnWidth - d->columns.gapWidth * zoom); for ( int i = 0; i < d->columns.count; ++i ) painter.drawRect( qRound(textArea.x() + i * columnWidth), textArea.y(), width, textArea.height()); } else { qreal totalRelativeWidth = 0.0; foreach(const KoColumns::ColumnDatum &cd, d->columns.columnData) { totalRelativeWidth += cd.relativeWidth; } int relativeColumnXOffset = 0; for (int i = 0; i < d->columns.count; i++) { const KoColumns::ColumnDatum &columnDatum = d->columns.columnData.at(i); const qreal columnWidth = textArea.width() * columnDatum.relativeWidth / totalRelativeWidth; const qreal columnXOffset = textArea.width() * relativeColumnXOffset / totalRelativeWidth; painter.drawRect( qRound(textArea.x() + columnXOffset + columnDatum.leftMargin * zoom), qRound(textArea.y() + columnDatum.topMargin * zoom), qRound(columnWidth - (columnDatum.leftMargin + columnDatum.rightMargin) * zoom), qRound(textArea.height() - (columnDatum.topMargin + columnDatum.bottomMargin) * zoom)); relativeColumnXOffset += columnDatum.relativeWidth; } } } void KoPagePreviewWidget::setPageLayout(const KoPageLayout &layout) { d->pageLayout = layout; update(); } void KoPagePreviewWidget::setColumns(const KoColumns &columns) { d->columns = columns; update(); } diff --git a/libs/widgets/KoRuler.cpp b/libs/widgets/KoRuler.cpp index 229b37886b0..89089cdebcb 100644 --- a/libs/widgets/KoRuler.cpp +++ b/libs/widgets/KoRuler.cpp @@ -1,1342 +1,1342 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Reginald Stadlbauer Copyright (C) 2006 Peter Simonsson Copyright (C) 2007 C. Boemann Copyright (C) 2007-2008 Jan Hambrecht Copyright (C) 2007 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoRuler.h" #include "KoRuler_p.h" #include #include #include #include #include #include #include #include #include #include // the distance in pixels of a mouse position considered outside the rule static const int OutsideRulerThreshold = 20; // static const int fullStepMarkerLength = 6; static const int halfStepMarkerLength = 6; static const int quarterStepMarkerLength = 3; static const int measurementTextAboveBelowMargin = 1; void RulerTabChooser::mousePressEvent(QMouseEvent *) { if (! m_showTabs) { return; } switch(m_type) { case QTextOption::LeftTab: m_type = QTextOption::RightTab; break; case QTextOption::RightTab: m_type = QTextOption::CenterTab; break; case QTextOption::CenterTab: m_type = QTextOption::DelimiterTab; break; case QTextOption::DelimiterTab: m_type = QTextOption::LeftTab; break; } update(); } void RulerTabChooser::paintEvent(QPaintEvent *) { if (! m_showTabs) { return; } QPainter painter(this); QPolygonF polygon; - painter.setPen(palette().color(QPalette::Text)); + painter.setPen(QPen(palette().color(QPalette::Text), 0)); painter.setBrush(palette().color(QPalette::Text)); painter.setRenderHint( QPainter::Antialiasing ); qreal x= width()/2; painter.translate(0,-height()/2+5); switch (m_type) { case QTextOption::LeftTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x+6.5, height() - 2.5) << QPointF(x+0.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::RightTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x-5.5, height() - 2.5) << QPointF(x+0.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::CenterTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x-5.5, height() - 2.5) << QPointF(x+6.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::DelimiterTab: polygon << QPointF(x-5.5, height() - 2.5) << QPointF(x+6.5, height() - 2.5); painter.drawPolyline(polygon); polygon << QPointF(x+0.5, height() - 2.5) << QPointF(x+0.5, height() - 8.5); painter.drawPolyline(polygon); break; default: break; } } static int compareTabs(KoRuler::Tab &tab1, KoRuler::Tab &tab2) { return tab1.position < tab2.position; } QRectF HorizontalPaintingStrategy::drawBackground(const KoRulerPrivate *d, QPainter &painter) { lengthInPixel = d->viewConverter->documentToViewX(d->rulerLength); QRectF rectangle; rectangle.setX(qMax(0, d->offset)); rectangle.setY(0); rectangle.setWidth(qMin(qreal(d->ruler->width() - 1.0 - rectangle.x()), (d->offset >= 0) ? lengthInPixel : lengthInPixel + d->offset)); rectangle.setHeight(d->ruler->height() - 1); QRectF activeRangeRectangle; activeRangeRectangle.setX(qMax(rectangle.x() + 1, d->viewConverter->documentToViewX(d->effectiveActiveRangeStart()) + d->offset)); activeRangeRectangle.setY(rectangle.y() + 1); activeRangeRectangle.setRight(qMin(rectangle.right() - 1, d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd()) + d->offset)); activeRangeRectangle.setHeight(rectangle.height() - 2); - painter.setPen(d->ruler->palette().color(QPalette::Mid)); + painter.setPen(QPen(d->ruler->palette().color(QPalette::Mid), 0)); painter.fillRect(rectangle,d->ruler->palette().color(QPalette::AlternateBase)); // make background slightly different so it is easier to see painter.drawRect(rectangle); if(d->effectiveActiveRangeStart() != d->effectiveActiveRangeEnd()) painter.fillRect(activeRangeRectangle, d->ruler->palette().brush(QPalette::Base)); if(d->showSelectionBorders) { // Draw first selection border if(d->firstSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewX(d->firstSelectionBorder) + d->offset; painter.drawLine(QPointF(border, rectangle.y() + 1), QPointF(border, rectangle.bottom() - 1)); } // Draw second selection border if(d->secondSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewX(d->secondSelectionBorder) + d->offset; painter.drawLine(QPointF(border, rectangle.y() + 1), QPointF(border, rectangle.bottom() - 1)); } } return rectangle; } void HorizontalPaintingStrategy::drawTabs(const KoRulerPrivate *d, QPainter &painter) { if (! d->showTabs) return; QPolygonF polygon; const QColor tabColor = d->ruler->palette().color(QPalette::Text); - painter.setPen(tabColor); + painter.setPen(QPen(tabColor, 0)); painter.setBrush(tabColor); painter.setRenderHint( QPainter::Antialiasing ); qreal position = -10000; foreach (const KoRuler::Tab & t, d->tabs) { qreal x; if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - t.position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + t.position) + d->offset; } position = qMax(position, t.position); polygon.clear(); switch (t.type) { case QTextOption::LeftTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x+6.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::RightTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::CenterTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+6.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::DelimiterTab: polygon << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+6.5, d->ruler->height() - 0.5); painter.drawPolyline(polygon); polygon << QPointF(x+0.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 6.5); painter.drawPolyline(polygon); break; default: break; } } // and also draw the regular interval tab that are non editable if (d->tabDistance > 0.0) { // first possible position position = qMax(position, d->relativeTabs ? 0 : d->paragraphIndent); if (position < 0) { position = int(position / d->tabDistance) * d->tabDistance; } else { position = (int(position / d->tabDistance) + 1) * d->tabDistance; } while (position < d->effectiveActiveRangeEnd() - d->effectiveActiveRangeStart() - d->endIndent) { qreal x; if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + position) + d->offset; } polygon.clear(); polygon << QPointF(x+0.5, d->ruler->height() - 3.5) << QPointF(x+4.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); position += d->tabDistance; } } } void HorizontalPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF &rectangle) { qreal numberStep = d->numberStepForUnit(); // number step in unit // QRectF activeRangeRectangle; int numberStepPixel = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue(numberStep))); // const bool adjustMillimeters = (d->unit.type() == KoUnit::Millimeter); const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); painter.setFont(font); if (numberStepPixel == 0 || numberStep == 0) return; // Calc the longest text length int textLength = 0; for(int i = 0; i < lengthInPixel; i += numberStepPixel) { int number = qRound((i / numberStepPixel) * numberStep); textLength = qMax(textLength, fontMetrics.width(QString::number(number))); } textLength += 4; // Add some padding // Change number step so all digits fits while(textLength > numberStepPixel) { numberStepPixel += numberStepPixel; numberStep += numberStep; } int start=0; // Calc the first number step if(d->offset < 0) start = qAbs(d->offset); // make a little hack so rulers shows correctly inversed number aligned const qreal lengthInUnit = d->unit.toUserValue(d->rulerLength); const qreal hackyLength = lengthInUnit - fmod(lengthInUnit, numberStep); if(d->rightToLeft) { start -= int(d->viewConverter->documentToViewX(fmod(d->rulerLength, d->unit.fromUserValue(numberStep)))); } int stepCount = (start / numberStepPixel) + 1; int halfStepCount = (start / qRound(numberStepPixel * 0.5)) + 1; int quarterStepCount = (start / qRound(numberStepPixel * 0.25)) + 1; int pos = 0; - const QPen numberPen(d->ruler->palette().color(QPalette::Text)); - const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text)); + const QPen numberPen(d->ruler->palette().color(QPalette::Text), 0); + const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text), 0); painter.setPen(markerPen); if(d->offset > 0) painter.translate(d->offset, 0); const int len = qRound(rectangle.width()) + start; int nextStep = qRound(d->viewConverter->documentToViewX( d->unit.fromUserValue(numberStep * stepCount))); int nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); int nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); for(int i = start; i < len; ++i) { pos = i - start; if(i == nextStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - fullStepMarkerLength)); int number = qRound(stepCount * numberStep); QString numberText = QString::number(number); int x = pos; if (d->rightToLeft) { // this is done in a hacky way with the fine tuning done above numberText = QString::number(hackyLength - stepCount * numberStep); } painter.setPen(numberPen); painter.drawText(QPointF(x-fontMetrics.width(numberText)/2.0, rectangle.bottom() -fullStepMarkerLength -measurementTextAboveBelowMargin), numberText); painter.setPen(markerPen); ++stepCount; nextStep = qRound(d->viewConverter->documentToViewX( d->unit.fromUserValue(numberStep * stepCount))); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextHalfStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - halfStepMarkerLength)); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextQuarterStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - quarterStepMarkerLength)); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } } // Draw the mouse indicator const int mouseCoord = d->mouseCoordinate - start; if (d->selected == KoRulerPrivate::None || d->selected == KoRulerPrivate::HotSpot) { const qreal top = rectangle.y() + 1; const qreal bottom = rectangle.bottom() -1; if (d->selected == KoRulerPrivate::None && d->showMousePosition && mouseCoord > 0 && mouseCoord < rectangle.width() ) painter.drawLine(QPointF(mouseCoord, top), QPointF(mouseCoord, bottom)); foreach (const KoRulerPrivate::HotSpotData & hp, d->hotspots) { const qreal x = d->viewConverter->documentToViewX(hp.position) + d->offset; painter.drawLine(QPointF(x, top), QPointF(x, bottom)); } } } void HorizontalPaintingStrategy::drawIndents(const KoRulerPrivate *d, QPainter &painter) { QPolygonF polygon; painter.setBrush(d->ruler->palette().brush(QPalette::Base)); painter.setRenderHint( QPainter::Antialiasing ); qreal x; // Draw first line start indent if (d->rightToLeft) x = d->effectiveActiveRangeEnd() - d->firstLineIndent - d->paragraphIndent; else x = d->effectiveActiveRangeStart() + d->firstLineIndent + d->paragraphIndent; // convert and use the +0.5 to go to nearest integer so that the 0.5 added below ensures sharp lines x = int(d->viewConverter->documentToViewX(x) + d->offset + 0.5); polygon << QPointF(x+6.5, 0.5) << QPointF(x+0.5, 8.5) << QPointF(x-5.5, 0.5) << QPointF(x+5.5, 0.5); painter.drawPolygon(polygon); // draw the hanging indent. if (d->rightToLeft) x = d->effectiveActiveRangeStart() + d->endIndent; else x = d->effectiveActiveRangeStart() + d->paragraphIndent; // convert and use the +0.5 to go to nearest integer so that the 0.5 added below ensures sharp lines x = int(d->viewConverter->documentToViewX(x) + d->offset + 0.5); const int bottom = d->ruler->height(); polygon.clear(); polygon << QPointF(x+6.5, bottom - 0.5) << QPointF(x+0.5, bottom - 8.5) << QPointF(x-5.5, bottom - 0.5) << QPointF(x+5.5, bottom - 0.5); painter.drawPolygon(polygon); // Draw end-indent or paragraph indent if mode is rightToLeft qreal diff; if (d->rightToLeft) diff = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - d->paragraphIndent) + d->offset - x; else diff = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - d->endIndent) + d->offset - x; polygon.translate(diff, 0); painter.drawPolygon(polygon); } QSize HorizontalPaintingStrategy::sizeHint() { // assumes that digits for the number only use glyphs which do not go below the baseline const QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); const int digitsHeight = fm.ascent() + 1; // +1 for baseline const int minimum = digitsHeight + fullStepMarkerLength + 2*measurementTextAboveBelowMargin; return QSize(0, minimum); } QRectF VerticalPaintingStrategy::drawBackground(const KoRulerPrivate *d, QPainter &painter) { lengthInPixel = d->viewConverter->documentToViewY(d->rulerLength); QRectF rectangle; rectangle.setX(0); rectangle.setY(qMax(0, d->offset)); rectangle.setWidth(d->ruler->width() - 1.0); rectangle.setHeight(qMin(qreal(d->ruler->height() - 1.0 - rectangle.y()), (d->offset >= 0) ? lengthInPixel : lengthInPixel + d->offset)); QRectF activeRangeRectangle; activeRangeRectangle.setX(rectangle.x() + 1); activeRangeRectangle.setY(qMax(rectangle.y() + 1, d->viewConverter->documentToViewY(d->effectiveActiveRangeStart()) + d->offset)); activeRangeRectangle.setWidth(rectangle.width() - 2); activeRangeRectangle.setBottom(qMin(rectangle.bottom() - 1, d->viewConverter->documentToViewY(d->effectiveActiveRangeEnd()) + d->offset)); - painter.setPen(d->ruler->palette().color(QPalette::Mid)); + painter.setPen(QPen(d->ruler->palette().color(QPalette::Mid), 0)); painter.fillRect(rectangle,d->ruler->palette().color(QPalette::AlternateBase)); painter.drawRect(rectangle); if(d->effectiveActiveRangeStart() != d->effectiveActiveRangeEnd()) painter.fillRect(activeRangeRectangle, d->ruler->palette().brush(QPalette::Base)); if(d->showSelectionBorders) { // Draw first selection border if(d->firstSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewY(d->firstSelectionBorder) + d->offset; painter.drawLine(QPointF(rectangle.x() + 1, border), QPointF(rectangle.right() - 1, border)); } // Draw second selection border if(d->secondSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewY(d->secondSelectionBorder) + d->offset; painter.drawLine(QPointF(rectangle.x() + 1, border), QPointF(rectangle.right() - 1, border)); } } return rectangle; } void VerticalPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF &rectangle) { qreal numberStep = d->numberStepForUnit(); // number step in unit int numberStepPixel = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep))); if (numberStepPixel <= 0) return; const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); painter.setFont(font); // Calc the longest text length int textLength = 0; for(int i = 0; i < lengthInPixel; i += numberStepPixel) { int number = qRound((i / numberStepPixel) * numberStep); textLength = qMax(textLength, fontMetrics.width(QString::number(number))); } textLength += 4; // Add some padding if (numberStepPixel == 0 || numberStep == 0) return; // Change number step so all digits will fit while(textLength > numberStepPixel) { numberStepPixel += numberStepPixel; numberStep += numberStep; } // Calc the first number step const int start = d->offset < 0 ? qAbs(d->offset) : 0; // make a little hack so rulers shows correctly inversed number aligned int stepCount = (start / numberStepPixel) + 1; int halfStepCount = (start / qRound(numberStepPixel * 0.5)) + 1; int quarterStepCount = (start / qRound(numberStepPixel * 0.25)) + 1; - const QPen numberPen(d->ruler->palette().color(QPalette::Text)); - const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text)); + const QPen numberPen(d->ruler->palette().color(QPalette::Text), 0); + const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text), 0); painter.setPen(markerPen); if(d->offset > 0) painter.translate(0, d->offset); const int len = qRound(rectangle.height()) + start; int nextStep = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep * stepCount))); int nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); int nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); int pos = 0; for(int i = start; i < len; ++i) { pos = i - start; if(i == nextStep) { painter.save(); painter.translate(rectangle.right()-fullStepMarkerLength, pos); if(pos != 0) painter.drawLine(QPointF(0, 0), QPointF(fullStepMarkerLength-1, 0)); painter.rotate(-90); int number = qRound(stepCount * numberStep); QString numberText = QString::number(number); painter.setPen(numberPen); painter.drawText(QPointF(-fontMetrics.width(numberText) / 2.0, -measurementTextAboveBelowMargin), numberText); painter.restore(); ++stepCount; nextStep = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep * stepCount))); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextHalfStep) { if(pos != 0) painter.drawLine(QPointF(rectangle.right() - halfStepMarkerLength, pos), QPointF(rectangle.right() - 1, pos)); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextQuarterStep) { if(pos != 0) painter.drawLine(QPointF(rectangle.right() - quarterStepMarkerLength, pos), QPointF(rectangle.right() - 1, pos)); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } } // Draw the mouse indicator const int mouseCoord = d->mouseCoordinate - start; if (d->selected == KoRulerPrivate::None || d->selected == KoRulerPrivate::HotSpot) { const qreal left = rectangle.left() + 1; const qreal right = rectangle.right() -1; if (d->selected == KoRulerPrivate::None && d->showMousePosition && mouseCoord > 0 && mouseCoord < rectangle.height() ) painter.drawLine(QPointF(left, mouseCoord), QPointF(right, mouseCoord)); foreach (const KoRulerPrivate::HotSpotData & hp, d->hotspots) { const qreal y = d->viewConverter->documentToViewY(hp.position) + d->offset; painter.drawLine(QPointF(left, y), QPointF(right, y)); } } } QSize VerticalPaintingStrategy::sizeHint() { // assumes that digits for the number only use glyphs which do not go below the baseline const QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); const int digitsHeight = fm.ascent() + 1; // +1 for baseline const int minimum = digitsHeight + fullStepMarkerLength + 2*measurementTextAboveBelowMargin; return QSize(minimum, 0); } void HorizontalDistancesPaintingStrategy::drawDistanceLine(const KoRulerPrivate *d, QPainter &painter, const qreal start, const qreal end) { // Don't draw too short lines if (qMax(start, end) - qMin(start, end) < 1) return; painter.save(); painter.translate(d->offset, d->ruler->height() / 2); - painter.setPen(d->ruler->palette().color(QPalette::Text)); + painter.setPen(QPen(d->ruler->palette().color(QPalette::Text), 0)); painter.setBrush(d->ruler->palette().color(QPalette::Text)); QLineF line(QPointF(d->viewConverter->documentToViewX(start), 0), QPointF(d->viewConverter->documentToViewX(end), 0)); QPointF midPoint = line.pointAt(0.5); // Draw the label text const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); QString label = d->unit.toUserStringValue( d->viewConverter->viewToDocumentX(line.length())) + ' ' + d->unit.symbol(); QPointF labelPosition = QPointF(midPoint.x() - fontMetrics.width(label)/2, midPoint.y() + fontMetrics.ascent()/2); painter.setFont(font); painter.drawText(labelPosition, label); // Draw the arrow lines qreal arrowLength = (line.length() - fontMetrics.width(label)) / 2 - 2; arrowLength = qMax(qreal(0.0), arrowLength); QLineF startArrow(line.p1(), line.pointAt(arrowLength / line.length())); QLineF endArrow(line.p2(), line.pointAt(1.0 - arrowLength / line.length())); painter.drawLine(startArrow); painter.drawLine(endArrow); // Draw the arrow heads QPolygonF arrowHead; arrowHead << line.p1() << QPointF(line.x1()+3, line.y1()-3) << QPointF(line.x1()+3, line.y1()+3); painter.drawPolygon(arrowHead); arrowHead.clear(); arrowHead << line.p2() << QPointF(line.x2()-3, line.y2()-3) << QPointF(line.x2()-3, line.y2()+3); painter.drawPolygon(arrowHead); painter.restore(); } void HorizontalDistancesPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF&) { QList points; points << 0.0; points << d->effectiveActiveRangeStart() + d->paragraphIndent + d->firstLineIndent; points << d->effectiveActiveRangeStart() + d->paragraphIndent; points << d->effectiveActiveRangeEnd() - d->endIndent; points << d->effectiveActiveRangeStart(); points << d->effectiveActiveRangeEnd(); points << d->rulerLength; qSort(points.begin(), points.end()); QListIterator i(points); i.next(); while (i.hasNext() && i.hasPrevious()) { drawDistanceLine(d, painter, i.peekPrevious(), i.peekNext()); i.next(); } } KoRulerPrivate::KoRulerPrivate(KoRuler *parent, const KoViewConverter *vc, Qt::Orientation o) : unit(KoUnit(KoUnit::Point)), orientation(o), viewConverter(vc), offset(0), rulerLength(0), activeRangeStart(0), activeRangeEnd(0), activeOverrideRangeStart(0), activeOverrideRangeEnd(0), mouseCoordinate(-1), showMousePosition(0), showSelectionBorders(false), firstSelectionBorder(0), secondSelectionBorder(0), showIndents(false), firstLineIndent(0), paragraphIndent(0), endIndent(0), showTabs(false), relativeTabs(false), tabMoved(false), originalIndex(-1), currentIndex(0), rightToLeft(false), selected(None), selectOffset(0), tabChooser(0), normalPaintingStrategy(o == Qt::Horizontal ? (PaintingStrategy*)new HorizontalPaintingStrategy() : (PaintingStrategy*)new VerticalPaintingStrategy()), distancesPaintingStrategy((PaintingStrategy*)new HorizontalDistancesPaintingStrategy()), paintingStrategy(normalPaintingStrategy), ruler(parent) { } KoRulerPrivate::~KoRulerPrivate() { delete normalPaintingStrategy; delete distancesPaintingStrategy; } qreal KoRulerPrivate::numberStepForUnit() const { switch(unit.type()) { case KoUnit::Inch: case KoUnit::Centimeter: case KoUnit::Decimeter: case KoUnit::Millimeter: return 1.0; case KoUnit::Pica: case KoUnit::Cicero: return 10.0; case KoUnit::Point: default: return 100.0; } } qreal KoRulerPrivate::doSnapping(const qreal value) const { qreal numberStep = unit.fromUserValue(numberStepForUnit()/4.0); return numberStep * qRound(value / numberStep); } KoRulerPrivate::Selection KoRulerPrivate::selectionAtPosition(const QPoint & pos, int *selectOffset ) { const int height = ruler->height(); if (rightToLeft) { int x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - firstLineIndent - paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x +8 && pos.y() < height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::FirstLineIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x +8 && pos.y() > height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::ParagraphIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + endIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::EndIndent; } } else { int x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + firstLineIndent + paragraphIndent) + offset); if (pos.x() >= x -8 && pos.x() <= x + 8 && pos.y() < height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::FirstLineIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8 && pos.y() > height/2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::ParagraphIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - endIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::EndIndent; } } return KoRulerPrivate::None; } int KoRulerPrivate::hotSpotIndex(const QPoint & pos) { for(int counter = 0; counter < hotspots.count(); counter++) { bool hit; if (orientation == Qt::Horizontal) hit = qAbs(viewConverter->documentToViewX(hotspots[counter].position) - pos.x() + offset) < 3; else hit = qAbs(viewConverter->documentToViewY(hotspots[counter].position) - pos.y() + offset) < 3; if (hit) return counter; } return -1; } qreal KoRulerPrivate::effectiveActiveRangeStart() const { if (activeOverrideRangeStart != activeOverrideRangeEnd) { return activeOverrideRangeStart; } else { return activeRangeStart; } } qreal KoRulerPrivate::effectiveActiveRangeEnd() const { if (activeOverrideRangeStart != activeOverrideRangeEnd) { return activeOverrideRangeEnd; } else { return activeRangeEnd; } } void KoRulerPrivate::emitTabChanged() { KoRuler::Tab tab; if (currentIndex >= 0) tab = tabs[currentIndex]; emit ruler->tabChanged(originalIndex, currentIndex >= 0 ? &tab : 0); } KoRuler::KoRuler(QWidget* parent, Qt::Orientation orientation, const KoViewConverter* viewConverter) : QWidget(parent) , d( new KoRulerPrivate( this, viewConverter, orientation) ) { setMouseTracking( true ); } KoRuler::~KoRuler() { delete d; } KoUnit KoRuler::unit() const { return d->unit; } void KoRuler::setUnit(const KoUnit &unit) { d->unit = unit; update(); } qreal KoRuler::rulerLength() const { return d->rulerLength; } Qt::Orientation KoRuler::orientation() const { return d->orientation; } void KoRuler::setOffset(int offset) { d->offset = offset; update(); } void KoRuler::setRulerLength(qreal length) { d->rulerLength = length; update(); } void KoRuler::paintEvent(QPaintEvent* event) { QPainter painter(this); painter.setClipRegion(event->region()); painter.save(); QRectF rectangle = d->paintingStrategy->drawBackground(d, painter); painter.restore(); painter.save(); d->paintingStrategy->drawMeasurements(d, painter, rectangle); painter.restore(); if (d->showIndents) { painter.save(); d->paintingStrategy->drawIndents(d, painter); painter.restore(); } d->paintingStrategy->drawTabs(d, painter); } QSize KoRuler::minimumSizeHint() const { return d->paintingStrategy->sizeHint(); } QSize KoRuler::sizeHint() const { return d->paintingStrategy->sizeHint(); } void KoRuler::setActiveRange(qreal start, qreal end) { d->activeRangeStart = start; d->activeRangeEnd = end; update(); } void KoRuler::setOverrideActiveRange(qreal start, qreal end) { d->activeOverrideRangeStart = start; d->activeOverrideRangeEnd = end; update(); } void KoRuler::updateMouseCoordinate(int coordinate) { if(d->mouseCoordinate == coordinate) return; d->mouseCoordinate = coordinate; update(); } void KoRuler::setShowMousePosition(bool show) { d->showMousePosition = show; update(); } void KoRuler::setRightToLeft(bool isRightToLeft) { d->rightToLeft = isRightToLeft; update(); } void KoRuler::setShowIndents(bool show) { d->showIndents = show; update(); } void KoRuler::setFirstLineIndent(qreal indent) { d->firstLineIndent = indent; if (d->showIndents) { update(); } } void KoRuler::setParagraphIndent(qreal indent) { d->paragraphIndent = indent; if (d->showIndents) { update(); } } void KoRuler::setEndIndent(qreal indent) { d->endIndent = indent; if (d->showIndents) { update(); } } qreal KoRuler::firstLineIndent() const { return d->firstLineIndent; } qreal KoRuler::paragraphIndent() const { return d->paragraphIndent; } qreal KoRuler::endIndent() const { return d->endIndent; } QWidget *KoRuler::tabChooser() { if ((d->tabChooser == 0) && (d->orientation == Qt::Horizontal)) { d->tabChooser = new RulerTabChooser(parentWidget()); d->tabChooser->setShowTabs(d->showTabs); } return d->tabChooser; } void KoRuler::setShowSelectionBorders(bool show) { d->showSelectionBorders = show; update(); } void KoRuler::updateSelectionBorders(qreal first, qreal second) { d->firstSelectionBorder = first; d->secondSelectionBorder = second; if(d->showSelectionBorders) update(); } void KoRuler::setShowTabs(bool show) { if (d->showTabs == show) { return; } d->showTabs = show; if (d->tabChooser) { d->tabChooser->setShowTabs(show); } update(); } void KoRuler::setRelativeTabs(bool relative) { d->relativeTabs = relative; if (d->showTabs) { update(); } } void KoRuler::updateTabs(const QList &tabs, qreal tabDistance) { d->tabs = tabs; d->tabDistance = tabDistance; if (d->showTabs) { update(); } } QList KoRuler::tabs() const { QList answer = d->tabs; qSort(answer.begin(), answer.end(), compareTabs); return answer; } void KoRuler::setPopupActionList(const QList &popupActionList) { d->popupActions = popupActionList; } QList KoRuler::popupActionList() const { return d->popupActions; } void KoRuler::mousePressEvent ( QMouseEvent* ev ) { d->tabMoved = false; d->selected = KoRulerPrivate::None; if (ev->button() == Qt::RightButton && !d->popupActions.isEmpty()) QMenu::exec(d->popupActions, ev->globalPos()); if (ev->button() != Qt::LeftButton) { ev->ignore(); return; } QPoint pos = ev->pos(); if (d->showTabs) { int i = 0; int x; foreach (const Tab & t, d->tabs) { if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - t.position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + t.position) + d->offset; } if (pos.x() >= x-6 && pos.x() <= x+6) { d->selected = KoRulerPrivate::Tab; d->selectOffset = x - pos.x(); d->currentIndex = i; break; } i++; } d->originalIndex = d->currentIndex; } if (d->selected == KoRulerPrivate::None) d->selected = d->selectionAtPosition(ev->pos(), &d->selectOffset); if (d->selected == KoRulerPrivate::None) { int hotSpotIndex = d->hotSpotIndex(ev->pos()); if (hotSpotIndex >= 0) { d->selected = KoRulerPrivate::HotSpot; update(); } } if (d->showTabs && d->selected == KoRulerPrivate::None) { // still haven't found something so let assume the user wants to add a tab qreal tabpos; if (d->rightToLeft) { tabpos = d->viewConverter->viewToDocumentX(pos.x() - d->offset) + d->effectiveActiveRangeEnd() + (d->relativeTabs ? d->paragraphIndent : 0); } else { tabpos = d->viewConverter->viewToDocumentX(pos.x() - d->offset) - d->effectiveActiveRangeStart() - (d->relativeTabs ? d->paragraphIndent : 0); } Tab t = {tabpos, d->tabChooser ? d->tabChooser->type() : d->rightToLeft ? QTextOption::RightTab : QTextOption::LeftTab}; d->tabs.append(t); d->selectOffset = 0; d->selected = KoRulerPrivate::Tab; d->currentIndex = d->tabs.count() - 1; d->originalIndex = -1; // new! update(); } if (d->orientation == Qt::Horizontal && (ev->modifiers() & Qt::ShiftModifier) && (d->selected == KoRulerPrivate::FirstLineIndent || d->selected == KoRulerPrivate::ParagraphIndent || d->selected == KoRulerPrivate::Tab || d->selected == KoRulerPrivate::EndIndent)) d->paintingStrategy = d->distancesPaintingStrategy; if (d->selected != KoRulerPrivate::None) emit aboutToChange(); } void KoRuler::mouseReleaseEvent ( QMouseEvent* ev ) { ev->accept(); if (d->selected == KoRulerPrivate::Tab) { if (d->originalIndex >= 0 && !d->tabMoved) { int type = d->tabs[d->currentIndex].type; type++; if (type > 3) type = 0; d->tabs[d->currentIndex].type = static_cast (type); update(); } d->emitTabChanged(); } else if( d->selected != KoRulerPrivate::None) emit indentsChanged(true); else ev->ignore(); d->paintingStrategy = d->normalPaintingStrategy; d->selected = KoRulerPrivate::None; } void KoRuler::mouseMoveEvent ( QMouseEvent* ev ) { QPoint pos = ev->pos(); qreal activeLength = d->effectiveActiveRangeEnd() - d->effectiveActiveRangeStart(); switch (d->selected) { case KoRulerPrivate::FirstLineIndent: if (d->rightToLeft) d->firstLineIndent = d->effectiveActiveRangeEnd() - d->paragraphIndent - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->firstLineIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart() - d->paragraphIndent; if( ! (ev->modifiers() & Qt::ShiftModifier)) { d->firstLineIndent = d->doSnapping(d->firstLineIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } emit indentsChanged(false); break; case KoRulerPrivate::ParagraphIndent: if (d->rightToLeft) d->paragraphIndent = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->paragraphIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); if( ! (ev->modifiers() & Qt::ShiftModifier)) { d->paragraphIndent = d->doSnapping(d->paragraphIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } if (d->paragraphIndent + d->endIndent > activeLength) d->paragraphIndent = activeLength - d->endIndent; emit indentsChanged(false); break; case KoRulerPrivate::EndIndent: if (d->rightToLeft) d->endIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); else d->endIndent = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); if (!(ev->modifiers() & Qt::ShiftModifier)) { d->endIndent = d->doSnapping(d->endIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } if (d->paragraphIndent + d->endIndent > activeLength) d->endIndent = activeLength - d->paragraphIndent; emit indentsChanged(false); break; case KoRulerPrivate::Tab: d->tabMoved = true; if (d->currentIndex < 0) { // tab is deleted. if (ev->pos().y() < height()) { // reinstante it. d->currentIndex = d->tabs.count(); d->tabs.append(d->deletedTab); } else { break; } } if (d->rightToLeft) d->tabs[d->currentIndex].position = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->tabs[d->currentIndex].position = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); if (!(ev->modifiers() & Qt::ShiftModifier)) d->tabs[d->currentIndex].position = d->doSnapping(d->tabs[d->currentIndex].position); if (d->tabs[d->currentIndex].position < 0) d->tabs[d->currentIndex].position = 0; if (d->tabs[d->currentIndex].position > activeLength) d->tabs[d->currentIndex].position = activeLength; if (ev->pos().y() > height() + OutsideRulerThreshold ) { // moved out of the ruler, delete it. d->deletedTab = d->tabs.takeAt(d->currentIndex); d->currentIndex = -1; // was that a temporary added tab? if ( d->originalIndex == -1 ) emit guideLineCreated(d->orientation, d->orientation == Qt::Horizontal ? d->viewConverter->viewToDocumentY(ev->pos().y()) : d->viewConverter->viewToDocumentX(ev->pos().x())); } d->emitTabChanged(); break; case KoRulerPrivate::HotSpot: qreal newPos; if (d->orientation == Qt::Horizontal) newPos= d->viewConverter->viewToDocumentX(pos.x() - d->offset); else newPos= d->viewConverter->viewToDocumentY(pos.y() - d->offset); d->hotspots[d->currentIndex].position = newPos; emit hotSpotChanged(d->hotspots[d->currentIndex].id, newPos); break; case KoRulerPrivate::None: d->mouseCoordinate = (d->orientation == Qt::Horizontal ? pos.x() : pos.y()) - d->offset; int hotSpotIndex = d->hotSpotIndex(pos); if (hotSpotIndex >= 0) { setCursor(QCursor( d->orientation == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor )); break; } unsetCursor(); KoRulerPrivate::Selection selection = d->selectionAtPosition(pos); QString text; switch(selection) { case KoRulerPrivate::FirstLineIndent: text = i18n("First line indent"); break; case KoRulerPrivate::ParagraphIndent: text = i18n("Left indent"); break; case KoRulerPrivate::EndIndent: text = i18n("Right indent"); break; case KoRulerPrivate::None: if (ev->buttons() & Qt::LeftButton) { if (d->orientation == Qt::Horizontal && ev->pos().y() > height() + OutsideRulerThreshold) emit guideLineCreated(d->orientation, d->viewConverter->viewToDocumentY(ev->pos().y())); else if (d->orientation == Qt::Vertical && ev->pos().x() > width() + OutsideRulerThreshold) emit guideLineCreated(d->orientation, d->viewConverter->viewToDocumentX(ev->pos().x())); } break; default: break; } setToolTip(text); } update(); } void KoRuler::clearHotSpots() { if (d->hotspots.isEmpty()) return; d->hotspots.clear(); update(); } void KoRuler::setHotSpot(qreal position, int id) { uint hotspotCount = d->hotspots.count(); for( uint i = 0; i < hotspotCount; ++i ) { KoRulerPrivate::HotSpotData & hs = d->hotspots[i]; if (hs.id == id) { hs.position = position; update(); return; } } // not there yet, then insert it. KoRulerPrivate::HotSpotData hs; hs.position = position; hs.id = id; d->hotspots.append(hs); } bool KoRuler::removeHotSpot(int id) { QList::Iterator iter = d->hotspots.begin(); while(iter != d->hotspots.end()) { if (iter->id == id) { d->hotspots.erase(iter); update(); return true; } } return false; } void KoRuler::createGuideToolConnection(KoCanvasBase *canvas) { Q_ASSERT(canvas); KoToolBase *tool = KoToolManager::instance()->toolById(canvas, QLatin1String("GuidesTool_ID")); if (!tool) return; // It's perfectly fine to have no guides tool, we don't have to warn the user about it connect(this, SIGNAL(guideLineCreated(Qt::Orientation,qreal)), tool, SLOT(createGuideLine(Qt::Orientation,qreal))); } diff --git a/libs/widgetutils/KoGroupButton.cpp b/libs/widgetutils/KoGroupButton.cpp index a97b65b7d2b..77a51996a73 100644 --- a/libs/widgetutils/KoGroupButton.cpp +++ b/libs/widgetutils/KoGroupButton.cpp @@ -1,144 +1,144 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Aurélien Gâteau Copyright (C) 2012 Jean-Nicolas Artaud Copyright (C) 2012 JarosÅ‚aw Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoGroupButton.h" // Qt #include #include #include #include // KF5 #include class Q_DECL_HIDDEN KoGroupButton::Private { public: Private(KoGroupButton *qq, const GroupPosition position) : groupPosition(position) { // Make the policy closer to QPushButton's default but horizontal shouldn't be Fixed, // otherwise spacing gets broken qq->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } GroupPosition groupPosition; }; KoGroupButton::KoGroupButton(GroupPosition position, QWidget* parent) : QToolButton(parent), d(new Private(this, position)) { } KoGroupButton::KoGroupButton(QWidget* parent) : QToolButton(parent), d(new Private(this, NoGroup)) { } KoGroupButton::~KoGroupButton() { delete d; } void KoGroupButton::setGroupPosition(KoGroupButton::GroupPosition groupPosition) { d->groupPosition = groupPosition; } KoGroupButton::GroupPosition KoGroupButton::groupPosition() const { return d->groupPosition; } void KoGroupButton::paintEvent(QPaintEvent* event) { if (groupPosition() == NoGroup) { QToolButton::paintEvent(event); return; } QStylePainter painter(this); QStyleOptionToolButton opt; initStyleOption(&opt); QStyleOptionToolButton panelOpt = opt; // Panel QRect& panelRect = panelOpt.rect; switch (groupPosition()) { case GroupLeft: panelRect.setWidth(panelRect.width() * 2); break; case GroupCenter: panelRect.setLeft(panelRect.left() - panelRect.width()); panelRect.setWidth(panelRect.width() * 3); break; case GroupRight: panelRect.setLeft(panelRect.left() - panelRect.width()); break; case NoGroup: Q_ASSERT(0); } if (autoRaise()) { if (!isChecked() && !isDown() && !(panelOpt.state & QStyle::State_MouseOver)) { // Use 'pushed' appearance for all buttons, but those that are not really pushed // are drawn with less contrast and are toned down. panelOpt.state |= (QStyle::State_On | QStyle::State_Sunken); QPalette panelPal(panelOpt.palette); QColor c; c = panelPal.color(QPalette::Button); c.setAlpha(50); panelPal.setColor(QPalette::Button, c); c = panelPal.color(QPalette::Window); c.setAlpha(50); panelPal.setColor(QPalette::Window, c); panelOpt.palette = panelPal; painter.setOpacity(0.5); } } painter.drawPrimitive(QStyle::PE_PanelButtonTool, panelOpt); painter.setOpacity(1.0); // Separator //! @todo make specific fixes for styles such as Plastique, Cleanlooks if there's practical no alernative const int y1 = opt.rect.top() + 1; const int y2 = opt.rect.bottom() - 1; painter.setOpacity(0.4); if (d->groupPosition != GroupRight) { const int x = opt.rect.right(); - painter.setPen(opt.palette.color(QPalette::Dark)); + painter.setPen(QPen(opt.palette.color(QPalette::Dark), 0)); painter.drawLine(x, y1, x, y2); } painter.setOpacity(1.0); // Text painter.drawControl(QStyle::CE_ToolButtonLabel, opt); // Filtering message on tooltip text for CJK to remove accelerators. // Quoting ktoolbar.cpp: // """ // CJK languages use more verbose accelerator marker: they add a Latin // letter in parenthesis, and put accelerator on that. Hence, the default // removal of ampersand only may not be enough there, instead the whole // parenthesis construct should be removed. Provide these filtering i18n // messages so that translators can use Transcript for custom removal. // """ if (!actions().isEmpty()) { QAction* action = actions().first(); setToolTip(i18nc("@info:tooltip of custom triple button", "%1", action->toolTip())); } } diff --git a/plugins/artistictextshape/ArtisticTextTool.cpp b/plugins/artistictextshape/ArtisticTextTool.cpp index 7ac7b458047..6900deb58b8 100644 --- a/plugins/artistictextshape/ArtisticTextTool.cpp +++ b/plugins/artistictextshape/ArtisticTextTool.cpp @@ -1,972 +1,972 @@ /* 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 #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) { m_detachPath = new QAction(koIcon("artistictext-detach-path"), i18n("Detach Path"), this); m_detachPath->setEnabled( false ); connect( m_detachPath, SIGNAL(triggered()), this, SLOT(detachPath()) ); addAction("artistictext_detach_from_path", m_detachPath); m_convertText = new QAction(koIcon("pathshape"), i18n("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 = new QAction(koIcon("format-text-bold"), i18n("Bold text"), this); m_fontBold->setCheckable(true); connect(m_fontBold, SIGNAL(toggled(bool)), this, SLOT(toggleFontBold(bool))); addAction("artistictext_font_bold", m_fontBold); m_fontItalic = new QAction(koIcon("format-text-italic"), i18n("Italic text"), this); m_fontItalic->setCheckable(true); connect(m_fontItalic, SIGNAL(toggled(bool)), this, SLOT(toggleFontItalic(bool))); addAction("artistictext_font_italic", m_fontItalic); m_superScript = new QAction(koIcon("format-text-superscript"), i18n("Superscript"), this); m_superScript->setCheckable(true); connect(m_superScript, SIGNAL(triggered()), this, SLOT(setSuperScript())); addAction("artistictext_superscript", m_superScript); m_subScript = new QAction(koIcon("format-text-subscript"), i18n("Subscript"), this); m_subScript->setCheckable(true); connect(m_subScript, SIGNAL(triggered()), this, SLOT(setSubScript())); addAction("artistictext_subscript", m_subScript); QAction *anchorStart = new QAction(koIcon("format-justify-left"), i18n("Anchor at Start"), this); anchorStart->setCheckable( true ); anchorStart->setData(ArtisticTextShape::AnchorStart); addAction("artistictext_anchor_start", anchorStart); QAction *anchorMiddle = new QAction(koIcon("format-justify-center"), i18n("Anchor at Middle"), this); anchorMiddle->setCheckable( true ); anchorMiddle->setData(ArtisticTextShape::AnchorMiddle); addAction("artistictext_anchor_middle", anchorMiddle); QAction *anchorEnd = new QAction(koIcon("format-justify-right"), i18n("Anchor at End"), this); anchorEnd->setCheckable( true ); 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*))); KoShapeManager *manager = canvas->shapeManager(); connect(manager, 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.setPen(QPen(Qt::blue, 0)); 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()->shapeManager()->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 { 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(""); } } 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 toolActivation, const QSet &shapes) { Q_UNUSED(toolActivation); 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(); KoShapeManager *manager = canvas()->shapeManager(); connect(manager, 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; KoShapeManager *manager = canvas()->shapeManager(); disconnect(manager, SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged())); } 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); 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 ); 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()->shapeManager(), 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()->shapeManager(), 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()->shapeManager()->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/chartshape/commands/DatasetCommand.cpp b/plugins/chartshape/commands/DatasetCommand.cpp index cb3bdbc78b3..003bdb09ae5 100644 --- a/plugins/chartshape/commands/DatasetCommand.cpp +++ b/plugins/chartshape/commands/DatasetCommand.cpp @@ -1,186 +1,186 @@ /* This file is part of the KDE project Copyright 2012 Brijesh Patel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "DatasetCommand.h" // KF5 #include // KoChart #include "DataSet.h" #include "ChartDebug.h" using namespace KoChart; using namespace KChart; DatasetCommand::DatasetCommand(DataSet* dataSet, ChartShape* chart) : m_dataSet(dataSet) , m_chart(chart) { m_newType = dataSet->chartType(); m_newSubtype = dataSet->chartSubType(); m_newShowCategory = dataSet->valueLabelType().category; m_newShowNumber = dataSet->valueLabelType().number; m_newShowPercent = dataSet->valueLabelType().percentage; m_newShowSymbol = dataSet->valueLabelType().symbol; m_newBrushColor = dataSet->brush().color(); m_newPenColor = dataSet->pen().color(); m_newMarkerStyle = dataSet->markerStyle(); m_newAxis = dataSet->attachedAxis(); } DatasetCommand::~DatasetCommand() { } void DatasetCommand::redo() { // save the old type /*m_oldType = m_dataSet->chartType(); m_oldSubtype = m_dataSet->chartSubType();*/ m_oldShowCategory = m_dataSet->valueLabelType().category; m_oldShowNumber = m_dataSet->valueLabelType().number; m_oldShowPercent = m_dataSet->valueLabelType().percentage; m_oldShowSymbol = m_dataSet->valueLabelType().symbol; m_oldBrushColor = m_dataSet->brush().color(); m_oldPenColor = m_dataSet->pen().color(); m_oldMarkerStyle = m_dataSet->markerStyle(); m_oldAxis = m_dataSet->attachedAxis(); if (m_oldShowCategory == m_newShowCategory && m_oldShowNumber == m_newShowNumber && m_oldShowPercent == m_newShowPercent && m_oldShowSymbol == m_newShowSymbol && m_oldBrushColor == m_newBrushColor && m_oldPenColor == m_newPenColor && m_oldMarkerStyle == m_newMarkerStyle) return; // Actually do the work DataSet::ValueLabelType valueLabelType = m_dataSet->valueLabelType(); valueLabelType.category = m_newShowCategory; valueLabelType.number = m_newShowNumber; valueLabelType.percentage = m_newShowPercent; valueLabelType.symbol = m_newShowSymbol; m_dataSet->setValueLabelType(valueLabelType); m_dataSet->setBrush(QBrush(m_newBrushColor)); - m_dataSet->setPen(QPen(m_newPenColor)); + m_dataSet->setPen(QPen(m_newPenColor, 0)); m_dataSet->setMarkerStyle(m_newMarkerStyle); m_dataSet->setAttachedAxis(m_newAxis); m_chart->update(); } void DatasetCommand::undo() { if (m_oldShowCategory == m_newShowCategory && m_oldShowNumber == m_newShowNumber && m_oldShowPercent == m_newShowPercent && m_oldShowSymbol == m_newShowSymbol && m_oldBrushColor == m_newBrushColor && m_oldPenColor == m_newPenColor && m_oldMarkerStyle == m_newMarkerStyle) return; DataSet::ValueLabelType valueLabelType = m_dataSet->valueLabelType(); valueLabelType.category = m_oldShowCategory; valueLabelType.number = m_oldShowNumber; valueLabelType.percentage = m_oldShowPercent; valueLabelType.symbol = m_oldShowSymbol; m_dataSet->setValueLabelType(valueLabelType); m_dataSet->setBrush(QBrush(m_oldBrushColor)); - m_dataSet->setPen(QPen(m_oldPenColor)); + m_dataSet->setPen(QPen(m_oldPenColor, 0)); m_dataSet->setMarkerStyle(m_oldMarkerStyle); m_dataSet->setAttachedAxis(m_oldAxis); m_chart->update(); } void DatasetCommand::setDataSetChartType(ChartType type, ChartSubtype subtype) { m_newType = type; m_newSubtype = subtype; setText(kundo2_i18n("Set Dataset Chart Type")); } void DatasetCommand::setDataSetShowCategory(bool show) { m_newShowCategory = show; if (show) { setText(kundo2_i18n("Show Dataset Category")); } else { setText(kundo2_i18n("Hide Dataset Category")); } } void DatasetCommand::setDataSetShowNumber(bool show) { m_newShowNumber = show; if (show) { setText(kundo2_i18n("Show Dataset Number")); } else { setText(kundo2_i18n("Hide Dataset Number")); } } void DatasetCommand::setDataSetShowPercent(bool show) { m_newShowPercent = show; if (show) { setText(kundo2_i18n("Show Dataset Percent")); } else { setText(kundo2_i18n("Hide Dataset Percent")); } } void DatasetCommand::setDataSetShowSymbol(bool show) { m_newShowSymbol = show; if (show) { setText(kundo2_i18n("Show Dataset Symbol")); } else { setText(kundo2_i18n("Hide Dataset Symbol")); } } void DatasetCommand::setDataSetBrush(const QColor &color) { m_newBrushColor = color; setText(kundo2_i18n("Set Dataset Brush Color")); } void DatasetCommand::setDataSetPen(const QColor &color) { m_newPenColor = color; setText(kundo2_i18n("Set Dataset Pen Color")); } void DatasetCommand::setDataSetMarker(OdfMarkerStyle style) { m_newMarkerStyle = style; setText(kundo2_i18n("Set Dataset Marker Style")); } void DatasetCommand::setDataSetAxis(Axis *axis) { m_newAxis = axis; setText(kundo2_i18n("Set Dataset Axis")); } diff --git a/plugins/commentshape/InitialsCommentShape.cpp b/plugins/commentshape/InitialsCommentShape.cpp index ab8e4bf7dd2..599480765bd 100644 --- a/plugins/commentshape/InitialsCommentShape.cpp +++ b/plugins/commentshape/InitialsCommentShape.cpp @@ -1,95 +1,95 @@ /* This file is part of the KDE project * Copyright (C) 2010 Carlos Licea * * 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 "InitialsCommentShape.h" #include "Globals.h" #include InitialsCommentShape::InitialsCommentShape() : KoShape() , m_active(true) { } InitialsCommentShape::~InitialsCommentShape() { } void InitialsCommentShape::saveOdf(KoShapeSavingContext& /*context*/) const { } bool InitialsCommentShape::loadOdf(const KoXmlElement& /*element*/, KoShapeLoadingContext& /*context*/) { return false; } void InitialsCommentShape::paint(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &) { applyConversion(painter, converter); QLinearGradient gradient(initialsBoxPoint, QPointF(0, initialsBoxSize.height())); qreal lighterPos = 0.0; qreal darkerPos = 0.0; if(!m_active){ darkerPos = 1.0; } else { lighterPos = 1.0; } gradient.setColorAt(lighterPos, QColor(Qt::yellow)); gradient.setColorAt(darkerPos, QColor(254, 201, 7)); const QBrush brush(gradient); painter.setBrush(brush); - painter.setPen(Qt::black); + painter.setPen(QPen(Qt::black, 0)); painter.drawRect(QRectF(initialsBoxPoint, initialsBoxSize)); painter.drawText(QRectF(initialsBoxPoint, initialsBoxSize), Qt::AlignCenter, m_initials); } void InitialsCommentShape::setInitials(const QString &initials) { m_initials = initials; } QString InitialsCommentShape::initials() { return m_initials; } bool InitialsCommentShape::isActive() const { return m_active; } void InitialsCommentShape::setActive(bool activate) { m_active = activate; } void InitialsCommentShape::toogleActive() { setActive(!m_active); update(); } diff --git a/plugins/defaultTools/connectionTool/ConnectionTool.cpp b/plugins/defaultTools/connectionTool/ConnectionTool.cpp index c06f733c8a3..9e3dec6eeed 100644 --- a/plugins/defaultTools/connectionTool/ConnectionTool.cpp +++ b/plugins/defaultTools/connectionTool/ConnectionTool.cpp @@ -1,957 +1,957 @@ /* 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 "AddConnectionPointCommand.h" #include "RemoveConnectionPointCommand.h" #include "ChangeConnectionPointCommand.h" #include "MoveConnectionPointStrategy.h" #include "ConnectionPointWidget.h" #include "../../textshape/TextShape.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 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(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "calligra/cursors/cursor_connect.png")); m_connectCursor = QCursor(connectPixmap, 4, 1); m_editConnectionPoint = new QAction(i18n("Edit connection points"), this); m_editConnectionPoint->setCheckable(true); addAction("toggle-edit-mode", m_editConnectionPoint); m_alignPercent = new QAction(QString("%"), this); m_alignPercent->setCheckable(true); addAction("align-relative", m_alignPercent); m_alignLeft = new QAction(koIcon("align-horizontal-left"), i18n("Align to left edge"), this); m_alignLeft->setCheckable(true); addAction("align-left", m_alignLeft); m_alignCenterH = new QAction(koIcon("align-horizontal-center"), i18n("Align to horizontal center"), this); m_alignCenterH->setCheckable(true); addAction("align-centerh", m_alignCenterH); m_alignRight = new QAction(koIcon("align-horizontal-right"), i18n("Align to right edge"), this); m_alignRight->setCheckable(true); addAction("align-right", m_alignRight); m_alignTop = new QAction(koIcon("align-vertical-top"), i18n("Align to top edge"), this); m_alignTop->setCheckable(true); addAction("align-top", m_alignTop); m_alignCenterV = new QAction(koIcon("align-vertical-center"), i18n("Align to vertical center"), this); m_alignCenterV->setCheckable(true); addAction("align-centerv", m_alignCenterV); m_alignBottom = new QAction(koIcon("align-vertical-bottom"), i18n("Align to bottom edge"), this); m_alignBottom->setCheckable(true); addAction("align-bottom", m_alignBottom); m_escapeAll = new QAction(koIcon("escape-direction-all"), i18n("Escape in all directions"), this); m_escapeAll->setCheckable(true); addAction("escape-all", m_escapeAll); m_escapeHorizontal = new QAction(koIcon("escape-direction-horizontal"), i18n("Escape in horizontal directions"), this); m_escapeHorizontal->setCheckable(true); addAction("escape-horizontal", m_escapeHorizontal); m_escapeVertical = new QAction(koIcon("escape-direction-vertical"), i18n("Escape in vertical directions"), this); m_escapeVertical->setCheckable(true); addAction("escape-vertical", m_escapeVertical); m_escapeLeft = new QAction(koIcon("escape-direction-left"), i18n("Escape in left direction"), this); m_escapeLeft->setCheckable(true); addAction("escape-left", m_escapeLeft); m_escapeRight = new QAction(koIcon("escape-direction-right"), i18n("Escape in right direction"), this); m_escapeRight->setCheckable(true); addAction("escape-right", m_escapeRight); m_escapeUp = new QAction(koIcon("escape-direction-up"), i18n("Escape in up direction"), this); m_escapeUp->setCheckable(true); addAction("escape-up", m_escapeUp); m_escapeDown = new QAction(koIcon("escape-direction-down"), i18n("Escape in down direction"), 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); + painter.setPen(QPen(Qt::black, 0)); 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) { painter.save(); - painter.setPen(Qt::blue); + painter.setPen(QPen(Qt::blue, 0)); painter.setBrush(i == m_activeHandle ? Qt::red : Qt::white); painter.setTransform(connectionShape->absoluteTransformation(&converter) * painter.transform()); connectionShape->paintHandle(painter, converter, i, radius); painter.restore(); } } } } 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(""); // create the connection edit strategy from the path tool m_currentStrategy = new KoPathConnectionPointStrategy(this, connectionShape, 1); if (!m_currentStrategy) { delete shape; resetEditMode(); return; } // update our handle data setEditMode(m_editMode, shape, 1); // add connection shape to the shape manager so it gets painted canvas()->shapeManager()->addShape(connectionShape); } else { // pressing on a shape in idle mode switches to corresponding edit mode if (hitShape) { if (dynamic_cast(hitShape)) { int hitHandle = handleAtPoint(hitShape, event->point); setEditMode(EditConnection, hitShape, hitHandle); if (hitHandle >= 0) { // start editing connection shape m_currentStrategy = new KoPathConnectionPointStrategy(this, dynamic_cast(m_currentShape), m_activeHandle); } } } else { resetEditMode(); } } } void ConnectionTool::mouseMoveEvent(KoPointerEvent *event) { if (m_currentStrategy) { repaintDecorations(); if (m_editMode != EditConnection && m_editMode != CreateConnection) { QPointF snappedPos = canvas()->snapGuide()->snap(event->point, event->modifiers()); m_currentStrategy->handleMouseMove(snappedPos, event->modifiers()); } else { m_currentStrategy->handleMouseMove(event->point, event->modifiers()); } repaintDecorations(); } else if (m_editMode == EditConnectionPoint) { KoShape *hoverShape = findNonConnectionShapeAtPosition(event->point);//TODO exclude connectors, need snap guide maybe? if (hoverShape) { m_currentShape = hoverShape; Q_ASSERT(m_currentShape); // check if we should highlight another connection point int handle = handleAtPoint(m_currentShape, event->point); if (handle >= 0) { setEditMode(m_editMode, m_currentShape, handle); useCursor(handle >= KoConnectionPoint::FirstCustomConnectionPoint ? Qt::SizeAllCursor : Qt::ArrowCursor); } else { updateStatusText(); useCursor(Qt::CrossCursor); } }else { m_currentShape = 0; useCursor(Qt::ArrowCursor); } } else if (m_editMode == EditConnection) { Q_ASSERT(m_currentShape); KoShape *hoverShape = findShapeAtPosition(event->point); // check if we should highlight another connection handle int handle = handleAtPoint(m_currentShape, event->point); setEditMode(m_editMode, m_currentShape, handle); if (m_activeHandle == KoConnectionShape::StartHandle || m_activeHandle == KoConnectionShape::EndHandle) { useCursor(Qt::SizeAllCursor); } else if (m_activeHandle >= KoConnectionShape::ControlHandle_1) { } else if (hoverShape && hoverShape != m_currentShape) { useCursor(Qt::PointingHandCursor); } else { useCursor(Qt::ArrowCursor); } } else {// Idle and no current strategy KoShape *hoverShape = findShapeAtPosition(event->point); int hoverHandle = -1; if (hoverShape) { KoConnectionShape * connectionShape = dynamic_cast(hoverShape); if (!connectionShape) { QPointF snappedPos = canvas()->snapGuide()->snap(event->point, event->modifiers()); hoverHandle = handleAtPoint(hoverShape, snappedPos); setEditMode(hoverHandle >= 0 ? CreateConnection : Idle, hoverShape, hoverHandle); } useCursor(hoverHandle >= 0 ? m_connectCursor : Qt::PointingHandCursor); } else { useCursor(Qt::ArrowCursor); } } } void ConnectionTool::mouseReleaseEvent(KoPointerEvent *event) { if (m_currentStrategy) { if (m_editMode == CreateConnection) { // check if connection handles have a minimal distance KoConnectionShape * connectionShape = dynamic_cast(m_currentShape); Q_ASSERT(connectionShape); // get both handle positions in document coordinates QPointF p1 = connectionShape->shapeToDocument(connectionShape->handlePosition(0)); QPointF p2 = connectionShape->shapeToDocument(connectionShape->handlePosition(1)); int grabDistance = grabSensitivity(); // use grabbing sensitivity as minimal distance threshold if (squareDistance(p1, p2) < grabDistance*grabDistance) { // minimal distance was not reached, so we have to undo the started work: // - cleanup and delete the strategy // - remove connection shape from shape manager and delete it // - reset edit mode to last state delete m_currentStrategy; m_currentStrategy = 0; repaintDecorations(); canvas()->shapeManager()->remove(m_currentShape); setEditMode(m_editMode, connectionShape->firstShape(), connectionShape->firstConnectionId()); repaintDecorations(); delete connectionShape; return; } else { // finalize adding the new connection shape with an undo command KUndo2Command * cmd = canvas()->shapeController()->addShape(m_currentShape); canvas()->addCommand(cmd); setEditMode(EditConnection, m_currentShape, KoConnectionShape::StartHandle); } } m_currentStrategy->finishInteraction(event->modifiers()); // TODO: Add parent command to KoInteractionStrategy::createCommand // so that we can have a single command to undo for the user KUndo2Command *command = m_currentStrategy->createCommand(); if (command) canvas()->addCommand(command); delete m_currentStrategy; m_currentStrategy = 0; } updateStatusText(); } void ConnectionTool::mouseDoubleClickEvent(KoPointerEvent *event) { if (m_editMode == EditConnectionPoint) { repaintDecorations(); //quit EditConnectionPoint mode when double click blank region on canvas if (!m_currentShape) { resetEditMode(); return; } //add connection point when double click a shape //remove connection point when double click a existed connection point int handleId = handleAtPoint(m_currentShape, event->point); if (handleId < 0) { QPointF mousePos = canvas()->snapGuide()->snap(event->point, event->modifiers()); QPointF point = m_currentShape->documentToShape(mousePos); canvas()->addCommand(new AddConnectionPointCommand(m_currentShape, point)); } else { canvas()->addCommand(new RemoveConnectionPointCommand(m_currentShape, handleId)); } setEditMode(m_editMode, m_currentShape, -1); } else { //deactivate connection tool when double click blank region on canvas KoShape *hitShape = findShapeAtPosition(event->point); if (!hitShape) { deactivate(); emit done(); } else if (dynamic_cast(hitShape)) { repaintDecorations(); setEditMode(EditConnection, m_currentShape, -1); //TODO: temporarily activate text tool to edit connection path } } } void ConnectionTool::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { deactivate(); emit done(); } else if (event->key() == Qt::Key_Backspace) { deleteSelection(); event->accept(); } } void ConnectionTool::activate(ToolActivation, const QSet &) { // 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(); } 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; 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); foreach(QAction *action, m_alignHorizontal->actions()) action->setChecked(false); 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; } 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(""); } 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(""); } } QList > ConnectionTool::createOptionWidgets() { QList > list; m_connectionShapeWidgets.clear(); KoShapeFactoryBase * factory = KoShapeRegistry::instance()->get(KOCONNECTIONSHAPEID); if (factory) { QList widgets = factory->createShapeOptionPanels(); 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(0); strokeWidget->setWindowTitle(i18n("Line")); strokeWidget->setCanvas(canvas()); 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() { foreach(QAction *action, m_alignHorizontal->actions()) action->setChecked(false); 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; 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/defaultTools/defaulttool/SelectionDecorator.cpp b/plugins/defaultTools/defaulttool/SelectionDecorator.cpp index 1d02efcf0d1..e2fe100e343 100644 --- a/plugins/defaultTools/defaulttool/SelectionDecorator.cpp +++ b/plugins/defaultTools/defaulttool/SelectionDecorator.cpp @@ -1,239 +1,239 @@ /* This file is part of the KDE project Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2006-2007 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SelectionDecorator.h" #include #include #include #define HANDLE_DISTANCE 10 KoFlake::Position SelectionDecorator::m_hotPosition = KoFlake::TopLeftCorner; SelectionDecorator::SelectionDecorator(KoFlake::SelectionHandle arrows, bool rotationHandles, bool shearHandles) : m_rotationHandles(rotationHandles) , m_shearHandles(shearHandles) , m_arrows(arrows) , m_handleRadius( 3 ) , m_lineWidth( 1 ) { #if 0 if(s_rotateCursor == 0) { s_rotateCursor->load(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "calligra/cursors/cursor_rotate.png")); } #endif } void SelectionDecorator::setSelection(KoSelection *selection) { m_selection = selection; } void SelectionDecorator::setHandleRadius( int radius ) { m_handleRadius = radius; m_lineWidth = qMax(1, (int)(radius / 2)); } void SelectionDecorator::setHotPosition( KoFlake::Position hotPosition ) { m_hotPosition = hotPosition; } KoFlake::Position SelectionDecorator::hotPosition() { return m_hotPosition; } void SelectionDecorator::paint(QPainter &painter, const KoViewConverter &converter) { QRectF handleArea; painter.save(); // save the original painter transformation QTransform painterMatrix = painter.worldTransform(); QPen pen; //Use the #00adf5 color with 50% opacity pen.setColor(QColor(0, 173, 245, 127)); pen.setWidth(m_lineWidth); pen.setJoinStyle(Qt::RoundJoin); painter.setPen( pen ); bool editable=false; foreach (KoShape *shape, m_selection->selectedShapes(KoFlake::StrippedSelection)) { // apply the shape transformation on top of the old painter transformation painter.setWorldTransform( shape->absoluteTransformation(&converter) * painterMatrix ); // apply the zoom factor KoShape::applyConversion( painter, converter ); // draw the shape bounding rect painter.drawRect( QRectF( QPointF(), shape->size() ) ); if (!shape->isGeometryProtected()) editable = true; } if (m_selection->count() > 1) { // more than one shape selected, so we need to draw the selection bounding rect - painter.setPen( Qt::blue ); + painter.setPen(QPen(Qt::blue, 0)); // apply the selection transformation on top of the old painter transformation painter.setWorldTransform(m_selection->absoluteTransformation(&converter) * painterMatrix); // apply the zoom factor KoShape::applyConversion(painter, converter); // draw the selection bounding rect painter.drawRect(QRectF(QPointF(), m_selection->size())); // save the selection bounding rect for later drawing the selection handles handleArea = QRectF(QPointF(), m_selection->size()); } else if (m_selection->firstSelectedShape()) { // only one shape selected, so we compose the correct painter matrix painter.setWorldTransform(m_selection->firstSelectedShape()->absoluteTransformation(&converter) * painterMatrix); KoShape::applyConversion(painter, converter); // save the only selected shapes bounding rect for later drawing the handles handleArea = QRectF(QPointF(), m_selection->firstSelectedShape()->size()); } painterMatrix = painter.worldTransform(); painter.restore(); // if we have no editable shape selected there is no need drawing the selection handles if (!editable) return; painter.save(); painter.setTransform(QTransform()); painter.setRenderHint( QPainter::Antialiasing, false ); painter.setPen(pen); painter.setBrush(pen.color()); QPolygonF outline = painterMatrix.map( handleArea ); // the 8 move rects QRectF rect( QPointF(0.5,0.5), QSizeF(2*m_handleRadius,2*m_handleRadius) ); rect.moveCenter(outline.value(0)); painter.drawRect(rect); rect.moveCenter(outline.value(1)); painter.drawRect(rect); rect.moveCenter(outline.value(2)); painter.drawRect(rect); rect.moveCenter(outline.value(3)); painter.drawRect(rect); rect.moveCenter((outline.value(0)+outline.value(1))/2); painter.drawRect(rect); rect.moveCenter((outline.value(1)+outline.value(2))/2); painter.drawRect(rect); rect.moveCenter((outline.value(2)+outline.value(3))/2); painter.drawRect(rect); rect.moveCenter((outline.value(3)+outline.value(0))/2); painter.drawRect(rect); // draw the hot position painter.setBrush(Qt::red); QPointF pos; switch( m_hotPosition ) { case KoFlake::TopLeftCorner: pos = handleArea.topLeft(); break; case KoFlake::TopRightCorner: pos = handleArea.topRight(); break; case KoFlake::BottomLeftCorner: pos = handleArea.bottomLeft(); break; case KoFlake::BottomRightCorner: pos = handleArea.bottomRight(); break; case KoFlake::CenteredPosition: pos = handleArea.center(); break; } rect.moveCenter( painterMatrix.map(pos )); painter.drawRect(rect); painter.restore(); #if 0 // draw the move arrow(s) if(m_arrows != KoFlake::NoHandle && bounds.width() > 45 && bounds.height() > 45) { qreal x1,x2,y1,y2; // 2 is where the arrow head is switch(m_arrows) { case KoFlake::TopMiddleHandle: x1=bounds.center().x(); x2=x1; y2=bounds.y()+8; y1=y2+20; break; case KoFlake::TopRightHandle: x2=bounds.right()-8; x1=x2-20; y2=bounds.y()+8; y1=y2+20; break; case KoFlake::RightMiddleHandle: x2=bounds.right()-8; x1=x2-20; y1=bounds.center().y(); y2=y1; break; case KoFlake::BottomRightHandle: x2=bounds.right()-8; x1=x2-20; y2=bounds.bottom()-8; y1=y2-20; break; case KoFlake::BottomMiddleHandle: x1=bounds.center().x(); x2=x1; y2=bounds.bottom()-8; y1=y2-20; break; case KoFlake::BottomLeftHandle: x2=bounds.left()+8; x1=x2+20; y2=bounds.bottom()-8; y1=y2-20; break; case KoFlake::LeftMiddleHandle: x2=bounds.left()+8; x1=x2+20; y1=bounds.center().y(); y2=y1; break; default: case KoFlake::TopLeftHandle: x2=bounds.left()+8; x1=x2+20; y2=bounds.y()+8; y1=y2+20; break; } painter.drawLine(QLineF(x1, y1, x2, y2)); //pen.setColor(Qt::white); //painter.setPen(pen); //painter.drawLine(QLineF(x1-1, y1-1, x2-1, y2-1)); } QPointF border(HANDLE_DISTANCE, HANDLE_DISTANCE); bounds.adjust(-border.x(), -border.y(), border.x(), border.y()); if(m_rotationHandles) { painter.save(); painter.translate(bounds.x(), bounds.y()); QRectF rect(QPointF(0,0), QSizeF(22, 22)); painter.drawImage(rect, *s_rotateCursor, rect); painter.translate(bounds.width(), 0); painter.rotate(90); if(bounds.width() > 45 && bounds.height() > 45) painter.drawImage(rect, *s_rotateCursor, rect); painter.translate(bounds.height(), 0); painter.rotate(90); painter.drawImage(rect, *s_rotateCursor, rect); painter.translate(bounds.width(), 0); painter.rotate(90); if(bounds.width() > 45 && bounds.height() > 45) painter.drawImage(rect, *s_rotateCursor, rect); painter.restore(); } /*if(m_shearHandles) { pen.setWidthF(0); painter.setPen(pen); QRectF rect(bounds.topLeft(), QSizeF(6, 6)); rect.moveLeft(bounds.x() + bounds.width() /2 -3); painter.drawRect(rect); rect.moveBottom(bounds.bottom()); painter.drawRect(rect); rect.moveLeft(bounds.left()); rect.moveTop(bounds.top() + bounds.width() / 2 -3); painter.drawRect(rect); rect.moveRight(bounds.right()); painter.drawRect(rect); } */ #endif } diff --git a/plugins/defaultTools/defaulttool/ShapeRotateStrategy.cpp b/plugins/defaultTools/defaulttool/ShapeRotateStrategy.cpp index 2507997713c..c6e5516d5a4 100644 --- a/plugins/defaultTools/defaulttool/ShapeRotateStrategy.cpp +++ b/plugins/defaultTools/defaulttool/ShapeRotateStrategy.cpp @@ -1,151 +1,151 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2007-2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ShapeRotateStrategy.h" #include "SelectionDecorator.h" #include "SelectionTransformCommand.h" #include #include #include #include #include #include #include #include #include ShapeRotateStrategy::ShapeRotateStrategy(KoToolBase *tool, const QPointF &clicked, Qt::MouseButtons buttons) : KoInteractionStrategy(tool) , m_initialBoundingRect() , m_start(clicked) { m_initialSelectionMatrix = tool->canvas()->shapeManager()->selection()->transformation(); QList selectedShapes = tool->canvas()->shapeManager()->selection()->selectedShapes(KoFlake::StrippedSelection); foreach(KoShape *shape, selectedShapes) { if( ! shape->isEditable() ) continue; m_selectedShapes << shape; if( m_selectedShapes.count() == 1 ) m_initialBoundingRect = shape->boundingRect(); else m_initialBoundingRect = m_initialBoundingRect.united( shape->boundingRect() ); m_oldTransforms << shape->transformation(); } if( buttons & Qt::RightButton ) m_rotationCenter = tool->canvas()->shapeManager()->selection()->absolutePosition( SelectionDecorator::hotPosition() ); else m_rotationCenter = m_initialBoundingRect.center(); tool->setStatusText( i18n( "Press ALT to rotate in 45 degree steps." ) ); } void ShapeRotateStrategy::handleMouseMove(const QPointF &point, Qt::KeyboardModifiers modifiers) { qreal angle = atan2( point.y() - m_rotationCenter.y(), point.x() - m_rotationCenter.x() ) - atan2( m_start.y() - m_rotationCenter.y(), m_start.x() - m_rotationCenter.x() ); angle = angle / M_PI * 180; // convert to degrees. if(modifiers & (Qt::AltModifier | Qt::ControlModifier)) { // limit to 45 degree angles qreal modula = qAbs(angle); while(modula > 45.0) modula -= 45.0; if(modula > 22.5) modula -= 45.0; angle += (angle>0?-1:1)*modula; } QTransform matrix; matrix.translate(m_rotationCenter.x(), m_rotationCenter.y()); matrix.rotate(angle); matrix.translate(-m_rotationCenter.x(), -m_rotationCenter.y()); QTransform applyMatrix = matrix * m_rotationMatrix.inverted(); m_rotationMatrix = matrix; foreach( KoShape * shape, m_selectedShapes ) { shape->update(); shape->applyAbsoluteTransformation( applyMatrix ); shape->update(); } tool()->canvas()->shapeManager()->selection()->applyAbsoluteTransformation( applyMatrix ); } void ShapeRotateStrategy::handleCustomEvent( KoPointerEvent * event ) { QTransform matrix; matrix.translate(m_rotationCenter.x(), m_rotationCenter.y()); matrix.rotate( 0.1 * event->rotationZ() ); matrix.translate(-m_rotationCenter.x(), -m_rotationCenter.y()); m_rotationMatrix *= matrix; foreach( KoShape * shape, m_selectedShapes ) { shape->update(); shape->applyAbsoluteTransformation( matrix ); shape->update(); } tool()->canvas()->shapeManager()->selection()->applyAbsoluteTransformation( matrix ); } void ShapeRotateStrategy::rotateBy( qreal angle ) { QTransform matrix; matrix.translate(m_rotationCenter.x(), m_rotationCenter.y()); matrix.rotate(angle); matrix.translate(-m_rotationCenter.x(), -m_rotationCenter.y()); QTransform applyMatrix = matrix * m_rotationMatrix.inverted(); m_rotationMatrix = matrix; foreach( KoShape * shape, m_selectedShapes ) { shape->update(); shape->applyAbsoluteTransformation( applyMatrix ); shape->update(); } tool()->canvas()->shapeManager()->selection()->applyAbsoluteTransformation( applyMatrix ); } void ShapeRotateStrategy::paint( QPainter &painter, const KoViewConverter &converter) { SelectionDecorator decorator(KoFlake::NoHandle, true, false); decorator.setSelection(tool()->canvas()->shapeManager()->selection()); decorator.setHandleRadius(handleRadius()); decorator.paint(painter, converter); // paint the rotation center - painter.setPen( QPen(Qt::red)); + painter.setPen(QPen(Qt::red, 0)); painter.setBrush( QBrush(Qt::red)); painter.setRenderHint( QPainter::Antialiasing, true ); QRectF circle( 0, 0, 5, 5 ); circle.moveCenter( converter.documentToView( m_rotationCenter ) ); painter.drawEllipse( circle ); } KUndo2Command* ShapeRotateStrategy::createCommand() { QVector newTransforms; newTransforms.reserve(m_selectedShapes.count()); foreach( KoShape* shape, m_selectedShapes ) newTransforms << shape->transformation(); KoShapeTransformCommand * cmd = new KoShapeTransformCommand( m_selectedShapes, m_oldTransforms, newTransforms ); cmd->setText( kundo2_i18n("Rotate") ); KoSelection * sel = tool()->canvas()->shapeManager()->selection(); new SelectionTransformCommand(sel, m_initialSelectionMatrix, sel->transformation(), cmd); return cmd; } diff --git a/plugins/defaultTools/guidestool/GuidesTool.cpp b/plugins/defaultTools/guidestool/GuidesTool.cpp index adc0d0735fd..acb30431534 100644 --- a/plugins/defaultTools/guidestool/GuidesTool.cpp +++ b/plugins/defaultTools/guidestool/GuidesTool.cpp @@ -1,463 +1,463 @@ /* This file is part of the KDE project * Copyright (C) 2008,2011 Jan Hambrecht * Copyright (C) 2009 Carlos Licea * * 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 "GuidesTool.h" #include "GuidesToolFactory.h" // for the Id #include "GuidesToolOptionWidget.h" #include "InsertGuidesToolOptionWidget.h" #include #include #include #include #include #include #include #include #include GuidesTool::GuidesTool(KoCanvasBase *canvas) : KoToolBase(canvas), m_orientation(Qt::Horizontal), m_index(-1), m_position(0), m_mode(EditGuide), m_options(0), m_isMoving(false) { } GuidesTool::~GuidesTool() { } void GuidesTool::paint(QPainter &painter, const KoViewConverter &converter) { if (m_mode == EditGuide && m_index == -1) return; KoCanvasController *controller = canvas()->canvasController(); QPoint documentOrigin = canvas()->documentOrigin(); QPoint canvasOffset(controller->canvasOffsetX(), controller->canvasOffsetY()); QPointF start, end; if (m_orientation == Qt::Horizontal) { qreal left = -canvasOffset.x() - documentOrigin.x(); qreal right = left + canvas()->canvasWidget()->width(); start = QPointF(left, converter.documentToViewY(m_position)); end = QPointF(right, converter.documentToViewY(m_position)); } else { qreal top = -canvasOffset.y() - documentOrigin.y(); qreal bottom = top + canvas()->canvasWidget()->height(); start = QPointF(converter.documentToViewX(m_position), top); end = QPointF(converter.documentToViewX(m_position), bottom); } - painter.setPen(Qt::red); + painter.setPen(QPen(Qt::red, 0)); painter.drawLine(start, end); } void GuidesTool::repaintDecorations() { canvas()->updateCanvas(updateRectFromGuideLine(m_position, m_orientation)); } QRectF GuidesTool::updateRectFromGuideLine(qreal position, Qt::Orientation orientation) { QRectF rect; KoCanvasController *controller = canvas()->canvasController(); QPoint documentOrigin = canvas()->documentOrigin(); QPoint canvasOffset(controller->canvasOffsetX(), controller->canvasOffsetY()); if (orientation == Qt::Horizontal) { qreal pixelBorder = canvas()->viewConverter()->viewToDocumentY(2.0); rect.setTop(position - pixelBorder); rect.setBottom(position + pixelBorder); rect.setLeft(canvas()->viewConverter()->viewToDocumentX(-canvasOffset.x()-documentOrigin.x())); rect.setWidth(canvas()->viewConverter()->viewToDocumentX(canvas()->canvasWidget()->width())); } else { qreal pixelBorder = canvas()->viewConverter()->viewToDocumentX(2.0); rect.setLeft(position - pixelBorder); rect.setRight(position + pixelBorder); rect.setTop(canvas()->viewConverter()->viewToDocumentY(-canvasOffset.y()-documentOrigin.y())); rect.setHeight(canvas()->viewConverter()->viewToDocumentY(canvas()->canvasWidget()->height())); } return rect; } void GuidesTool::activate(ToolActivation /*toolActivation*/, const QSet &) { if (m_index >= 0) useCursor(m_orientation == Qt::Horizontal ? Qt::SizeVerCursor : Qt::SizeHorCursor); else useCursor(Qt::ArrowCursor); if (m_options) { KoGuidesData *guidesData = canvas()->guidesData(); if (! guidesData) return; m_options->setHorizontalGuideLines(guidesData->horizontalGuideLines()); m_options->setVerticalGuideLines(guidesData->verticalGuideLines()); m_options->selectGuideLine(m_orientation, m_index); m_options->setUnit(canvas()->unit()); } } void GuidesTool::deactivate() { canvas()->canvasWidget()->releaseMouse(); m_mode = EditGuide; m_index = -1; } void GuidesTool::mousePressEvent(KoPointerEvent *event) { GuideLine line = guideLineAtPosition(event->point); if (line.second >= 0) { guideLineSelected(line.first, static_cast(line.second)); m_isMoving = true; } } void GuidesTool::mouseMoveEvent(KoPointerEvent *event) { if (m_mode == EditGuide && ! m_isMoving) { GuideLine line = guideLineAtPosition(event->point); if (line.second < 0) { useCursor(Qt::ArrowCursor); setStatusText(i18n("Double click to add guide line.")); } else { useCursor(line.first == Qt::Horizontal ? Qt::SizeVerCursor : Qt::SizeHorCursor); setStatusText(i18n("Click and drag to move guide line. Double click to remove guide line.")); } } else { setStatusText(""); repaintDecorations(); m_position = m_orientation == Qt::Horizontal ? event->point.y() : event->point.x(); updateGuidePosition(m_position); repaintDecorations(); } } void GuidesTool::mouseReleaseEvent(KoPointerEvent *event) { Q_UNUSED(event); KoGuidesData *guidesData = canvas()->guidesData(); if (! guidesData) { event->ignore(); return; } if (m_mode == AddGuide) { // add the new guide line guidesData->addGuideLine(m_orientation, m_position); } else if (m_mode == EditGuide) { if (m_isMoving) { m_isMoving = false; if (m_orientation == Qt::Horizontal) m_options->setHorizontalGuideLines(guidesData->horizontalGuideLines()); else m_options->setVerticalGuideLines(guidesData->verticalGuideLines()); m_options->selectGuideLine(m_orientation, m_index); } } if (m_mode != EditGuide) emit done(); } void GuidesTool::mouseDoubleClickEvent(KoPointerEvent *event) { KoGuidesData *guidesData = canvas()->guidesData(); if (!guidesData) { event->ignore(); return; } repaintDecorations(); // get guide line at position GuideLine line = guideLineAtPosition(event->point); if (line.second < 0) { // no guide line hit -> insert a new one m_orientation = m_options->orientation(); m_position = m_orientation == Qt::Horizontal ? event->point.y() : event->point.x(); // no guide line hit -> insert a new one guidesData->addGuideLine(m_orientation, m_position); if (m_orientation == Qt::Horizontal) { m_options->setHorizontalGuideLines(guidesData->horizontalGuideLines()); m_index = guidesData->horizontalGuideLines().count()-1; } else { m_options->setVerticalGuideLines(guidesData->verticalGuideLines()); m_index = guidesData->verticalGuideLines().count()-1; } m_options->selectGuideLine(m_orientation, m_index); } else { // guide line hit -> remove it QList lines; if (line.first == Qt::Horizontal) { lines = guidesData->horizontalGuideLines(); lines.removeAt(line.second); guidesData->setHorizontalGuideLines(lines); m_options->setHorizontalGuideLines(lines); m_index = -1; } else { lines = guidesData->verticalGuideLines(); lines.removeAt(line.second); guidesData->setVerticalGuideLines(lines); m_options->setVerticalGuideLines(lines); m_index = -1; } } repaintDecorations(); } void GuidesTool::createGuideLine(Qt::Orientation orientation, qreal position) { m_orientation = orientation; m_index = -1; m_position = position; m_mode = AddGuide; KoToolManager::instance()->switchToolRequested(GuidesToolId); // grab the mouse so we get mouse events as the dragging started on a ruler canvas()->canvasWidget()->grabMouse(); } void GuidesTool::moveGuideLine(Qt::Orientation orientation, int index) { m_orientation = orientation; m_index = index; m_mode = MoveGuide; } void GuidesTool::editGuideLine(Qt::Orientation orientation, int index) { m_orientation = orientation; m_index = index; m_mode = EditGuide; } void GuidesTool::updateGuidePosition(qreal position) { if (m_mode == MoveGuide || m_mode == EditGuide) { KoGuidesData *guidesData = canvas()->guidesData(); if (guidesData) { if (m_orientation == Qt::Horizontal) { QList guideLines = guidesData->horizontalGuideLines(); if (m_index >= 0 && m_index < guideLines.count()) { guideLines[m_index] = position; guidesData->setHorizontalGuideLines(guideLines); } } else { QList guideLines = guidesData->verticalGuideLines(); if (m_index >= 0 && m_index < guideLines.count()) { guideLines[m_index] = position; guidesData->setVerticalGuideLines(guideLines); } } } } } void GuidesTool::guideLineSelected(Qt::Orientation orientation, int index) { KoGuidesData *guidesData = canvas()->guidesData(); if (! guidesData) return; repaintDecorations(); m_orientation = orientation; m_index = index; if (m_orientation == Qt::Horizontal) m_position = guidesData->horizontalGuideLines().value(index); else m_position = guidesData->verticalGuideLines().value(index); repaintDecorations(); } void GuidesTool::guideLinesChanged(Qt::Orientation orientation) { KoGuidesData *guidesData = canvas()->guidesData(); if (! guidesData) return; repaintDecorations(); if (orientation == Qt::Horizontal) guidesData->setHorizontalGuideLines(m_options->horizontalGuideLines()); else guidesData->setVerticalGuideLines(m_options->verticalGuideLines()); if (orientation == m_orientation) { QList lines; if (m_orientation == Qt::Horizontal) lines = guidesData->horizontalGuideLines(); else lines = guidesData->verticalGuideLines(); int oldIndex = m_index; if (lines.count() == 0) m_index = -1; else if (m_index >= lines.count()) m_index = 0; if (m_index >= 0) m_position = lines[m_index]; if (oldIndex != m_index) m_options->selectGuideLine(m_orientation, m_index); } repaintDecorations(); } GuidesTool::GuideLine GuidesTool::guideLineAtPosition(const QPointF &position) { int index = -1; Qt::Orientation orientation = Qt::Horizontal; // check if we are on a guide line KoGuidesData *guidesData = canvas()->guidesData(); if (guidesData && guidesData->showGuideLines()) { qreal minDistance = canvas()->viewConverter()->viewToDocumentX(handleRadius()); int i = 0; foreach (qreal guidePos, guidesData->horizontalGuideLines()) { qreal distance = qAbs(guidePos - position.y()); if (distance < minDistance) { orientation = Qt::Horizontal; index = i; minDistance = distance; } i++; } i = 0; foreach (qreal guidePos, guidesData->verticalGuideLines()) { qreal distance = qAbs(guidePos - position.x()); if (distance < minDistance) { orientation = Qt::Vertical; index = i; minDistance = distance; } i++; } } return QPair(orientation, index); } void GuidesTool::canvasResourceChanged(int key, const QVariant &res) { if (key == KoCanvasResourceManager::Unit) { if (m_options) m_options->setUnit(res.value()); } } QList > GuidesTool::createOptionWidgets() { QList > optionwidgets; m_options = new GuidesToolOptionWidget(); m_options->setWindowTitle(i18n("Guides Editor")); connect(m_options, SIGNAL(guideLineSelected(Qt::Orientation,int)), this, SLOT(guideLineSelected(Qt::Orientation,int))); connect(m_options, SIGNAL(guideLinesChanged(Qt::Orientation)), this, SLOT(guideLinesChanged(Qt::Orientation))); optionwidgets.append(m_options); m_insert = new InsertGuidesToolOptionWidget(); m_insert->setWindowTitle(i18n("Guides Insertor")); connect(m_insert, SIGNAL(createGuides(GuidesTransaction*)), this, SLOT(insertorCreateGuidesSlot(GuidesTransaction*))); optionwidgets.append(m_insert); return optionwidgets; } void GuidesTool::insertorCreateGuidesSlot(GuidesTransaction *result) { KoGuidesData *guidesData = canvas()->guidesData(); const QSizeF pageSize = canvas()->resourceManager()->sizeResource(KoCanvasResourceManager::PageSize); QList< qreal > verticalLines; QList< qreal > horizontalLines; //save previous lines if requested if (!result->erasePreviousGuides) { verticalLines.append(guidesData->verticalGuideLines()); horizontalLines.append(guidesData->horizontalGuideLines()); } else { // trigger repaint at old guide positions foreach(qreal pos, guidesData->verticalGuideLines()) { canvas()->updateCanvas(updateRectFromGuideLine(pos, Qt::Vertical)); } foreach(qreal pos, guidesData->horizontalGuideLines()) { canvas()->updateCanvas(updateRectFromGuideLine(pos, Qt::Horizontal)); } } //vertical guides if (result->insertVerticalEdgesGuides) { verticalLines << 0 << pageSize.width(); } int lastI = result->verticalGuides; qreal verticalJumps = pageSize.width() / (qreal)(result->verticalGuides + 1); for (int i = 1 ; i <= lastI; ++i) { verticalLines << verticalJumps * (qreal)i; } guidesData->setVerticalGuideLines(verticalLines); //horizontal guides lastI = result->horizontalGuides; if (result->insertHorizontalEdgesGuides) { horizontalLines << 0 << pageSize.height(); } qreal horizontalJumps = pageSize.height() / (qreal)(result->horizontalGuides + 1); for (int i = 1 ; i <= lastI; ++i) { horizontalLines << horizontalJumps * (qreal)i; } guidesData->setHorizontalGuideLines(horizontalLines); // trigger repaint at new guide positions foreach(qreal pos, guidesData->verticalGuideLines()) { canvas()->updateCanvas(updateRectFromGuideLine(pos, Qt::Vertical)); } foreach(qreal pos, guidesData->horizontalGuideLines()) { canvas()->updateCanvas(updateRectFromGuideLine(pos, Qt::Horizontal)); } m_orientation = m_options->orientation(); m_index = m_orientation == Qt::Horizontal ? horizontalLines.count()-1 : verticalLines.count()-1; m_options->setHorizontalGuideLines(horizontalLines); m_options->setVerticalGuideLines(verticalLines); m_options->selectGuideLine(m_orientation, m_index); delete result; } diff --git a/plugins/formulashape/elements/RowElement.cpp b/plugins/formulashape/elements/RowElement.cpp index 0faec3d90fc..25fb75940e2 100644 --- a/plugins/formulashape/elements/RowElement.cpp +++ b/plugins/formulashape/elements/RowElement.cpp @@ -1,347 +1,347 @@ /* This file is part of the KDE project Copyright (C) 2001 Andrea Rizzi Ulrich Kuettler 2006 Martin Pfeiffer 2009 Jeremias Epperlein This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "RowElement.h" #include "FormulaCursor.h" #include "FormulaDebug.h" #include #include #include #include RowElement::RowElement( BasicElement* parent ) : BasicElement( parent ) {} RowElement::~RowElement() { qDeleteAll( m_childElements ); } void RowElement::paint( QPainter& painter, AttributeManager* am) { /* RowElement has no visual representance so paint nothing */ Q_UNUSED( painter ) Q_UNUSED( am ) } void RowElement::paintEditingHints ( QPainter& painter, AttributeManager* am ) { Q_UNUSED( am ) if (childElements().count()==0) { painter.save(); QBrush brush (Qt::NoBrush); brush.setColor( Qt::transparent); painter.setBrush(brush); - painter.setPen( Qt::blue); + painter.setPen(QPen(Qt::blue, 0)); painter.drawRect( childrenBoundingRect() ); painter.restore(); } } void RowElement::layout( const AttributeManager* am ) { Q_UNUSED( am ) // there are no attributes that can be processed here if( m_childElements.isEmpty() ) { //set standart values for empty formulas setOrigin( QPointF( 0.0, 0.0 ) ); setWidth( 7.0 ); // standard values setHeight( 10.0 ); setBaseLine( 10.0 ); setChildrenBoundingRect(QRectF(0,0, width(), height())); return; } QPointF origin; qreal width = 0.0; qreal topToBaseline = 0.0; qreal baselineToBottom = 0.0; foreach( BasicElement* child, m_childElements ) // iterate through the children and topToBaseline = qMax( topToBaseline, child->baseLine() ); // find max baseline foreach( BasicElement* child, m_childElements ) // iterate through the children { child->setOrigin( QPointF( width, topToBaseline - child->baseLine() ) ); baselineToBottom = qMax( baselineToBottom, child->height()-child->baseLine() ); width += child->width(); // add their width } setWidth( width ); setHeight( topToBaseline + baselineToBottom ); setBaseLine( topToBaseline ); setChildrenBoundingRect(QRectF(0,0, width, height())); } void RowElement::stretch() { //The elements can grow vertically, so make sure we reposition their vertical //origin appropriately /* foreach( BasicElement* tmpElement, childElements() ) { tmpElement->stretch(); //Set the origin. Note that we ignore the baseline and center the object //vertically //I think we need to FIXME for symmetric situations or something? tmpElement->setOrigin( QPointF(tmpElement->origin().x(), childrenBoundingRect().y() + (childrenBoundingRect().height() - tmpElement->height())/2 )); }*/ } int RowElement::endPosition() const { return m_childElements.count(); } const QList RowElement::childElements() const { return m_childElements; } bool RowElement::insertChild( int position, BasicElement* child ) { if (0<=position && position<=endPosition()) { m_childElements.insert( position, child ); child->setParentElement(this); return true; } else { return false; } } bool RowElement::removeChild( BasicElement* child ) { bool tmp=m_childElements.removeOne(child); if (tmp) { child->setParentElement(0); } return tmp; } bool RowElement::acceptCursor( const FormulaCursor& cursor ) { Q_UNUSED( cursor ) return true; } bool RowElement::moveCursor(FormulaCursor& newcursor, FormulaCursor& oldcursor) { Q_UNUSED (oldcursor) if ((newcursor.direction()==MoveUp) || (newcursor.direction()==MoveDown) || (newcursor.isHome() && newcursor.direction()==MoveLeft) || (newcursor.isEnd() && newcursor.direction()==MoveRight) ) { //the newcursor can't be moved vertically //TODO: check what happens with linebreaks in elements return false; } if (newcursor.isSelecting()) { switch(newcursor.direction()) { case MoveLeft: newcursor+=-1; break; case MoveRight: newcursor+=1; break; default: break; } } else { switch(newcursor.direction()) { case MoveLeft: newcursor.setCurrentElement(m_childElements[newcursor.position()-1]); newcursor.moveEnd(); break; case MoveRight: newcursor.setCurrentElement(m_childElements[newcursor.position()]); newcursor.moveHome(); break; default: break; } } return true; } QLineF RowElement::cursorLine(int position) const { QPointF top=absoluteBoundingRect().topLeft(); if( childElements().isEmpty() ) { // center cursor in elements that have no children top += QPointF( width()/2, 0 ); } else { if ( position==endPosition()) { top += QPointF(width(),0.0); } else { top += QPointF( childElements()[ position ]->boundingRect().left(), 0.0 ); } } QPointF bottom = top + QPointF( 0.0, height() ); return QLineF(top, bottom); } bool RowElement::setCursorTo(FormulaCursor& cursor, QPointF point) { if (m_childElements.isEmpty() || point.x()origin().x()) { cursor.moveTo(this,0); return true; } int i; for (i=0; iboundingRect().right()>=point.x()) { break; } } //check if the point is behind all child elements if (i==m_childElements.count()) { cursor.moveTo(this,endPosition()); return true; } else { if (cursor.isSelecting()) { //we don't need to change current element because we are already in this element if (cursor.mark()<=i) { cursor.setPosition(i+1); } else { cursor.setPosition(i); } return true; } else { return m_childElements[i]->setCursorTo(cursor,point-m_childElements[i]->origin()); } } } ElementType RowElement::elementType() const { return Row; } bool RowElement::readMathMLContent( const KoXmlElement& parent ) { KoXmlElement realParent = parent; // Go deeper in the xml tree and 'skip' the semantics elements. while (!realParent.namedItemNS( KoXmlNS::math, "semantics" ).isNull()) { // while there is a child 'semantics' realParent = realParent.namedItemNS( KoXmlNS::math, "semantics" ).toElement(); // move to it } // Read the actual content. BasicElement* tmpElement = 0; KoXmlElement tmp; forEachElement ( tmp, realParent ) { tmpElement = ElementFactory::createElement( tmp.tagName(), this ); Q_ASSERT( tmpElement ); if ( !tmpElement->readMathML( tmp ) ) { return false; } #if 1 // compact empty mrows and mrows with only one element // Treat rows in rows specially. if (tmpElement->elementType() == Row) { if (tmpElement->childElements().count() == 0) { // We don't load in this case, empty elements in rows are not needed. } else if (tmpElement->childElements().count() == 1) { // An mrow with 1 child is equivalent to the child itself. // So dig it out and place it directly in this row. // // TODO: Investigate, if we should load them nevertheless. RowElement *row = static_cast(tmpElement); BasicElement *child = row->childElements()[0]; row->removeChild(child); delete row; //insertChild(childElements().count(), child); m_childElements << child; } else { // If the mrow has > 1 child, then enter it m_childElements << tmpElement; } } else { // All other elements than mrow are immediately entered. m_childElements << tmpElement; } #else m_childElements << tmpElement; #endif } return true; } int RowElement::positionOfChild(BasicElement* child) const { return m_childElements.indexOf(child); } void RowElement::writeMathMLContent( KoXmlWriter* writer, const QString& ns ) const { foreach( BasicElement* tmp, m_childElements ) tmp->writeMathML( writer, ns ); } BasicElement* RowElement::elementAfter ( int position ) const { if (position1) { return m_childElements[position-1]; } else { return 0; } } QList< BasicElement* > RowElement::elementsBetween ( int pos1, int pos2 ) const { return m_childElements.mid(pos1,pos2-pos1); } bool RowElement::replaceChild ( BasicElement* oldelement, BasicElement* newelement ) { int oldElementIndex = m_childElements.indexOf(oldelement); if ( oldElementIndex < 0) return false; m_childElements.replace(oldElementIndex,newelement); oldelement->setParentElement(0); newelement->setParentElement(this); return true; } bool RowElement::isEmpty() const { return (elementType()==Row && m_childElements.count()==0); } bool RowElement::isInferredRow() const { return true; } diff --git a/plugins/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp b/plugins/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp index 47468a8fb91..4a3e086cddc 100644 --- a/plugins/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp +++ b/plugins/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp @@ -1,494 +1,494 @@ /* 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 #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->shapeManager(), 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 + painter.setPen(QPen(Qt::red, 0)); // 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); 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, const QSet &) { 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); } } QList > KarbonCalligraphyTool::createOptionWidgets() { // if the widget don't exists yet create it QList > widgets; KoFillConfigWidget *fillWidget = new KoFillConfigWidget(0); fillWidget->setWindowTitle(i18n("Fill")); fillWidget->setCanvas(canvas()); 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; //if ( m_selectedPath ) // canvas()->updateCanvas( m_selectedPath->boundingRect() ); } 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(); // null pointer if it the selection isn't a KoPathShape // or if the selection is empty m_selectedPath = dynamic_cast(selection->firstSelectedShape()); // or if it's a KoPathShape but with no or more than one subpaths if (m_selectedPath && m_selectedPath->subpathCount() != 1) m_selectedPath = 0; // or if there ora none or more than 1 shapes selected if (selection->count() != 1) m_selectedPath = 0; // emit signal it there wasn't a selected path and now there is // or the other way around if ((m_selectedPath != 0) != (oldSelectedPath != 0)) emit pathSelectedChanged(m_selectedPath != 0); } diff --git a/plugins/karbonplugins/tools/KarbonGradientEditStrategy.cpp b/plugins/karbonplugins/tools/KarbonGradientEditStrategy.cpp index fb7b17d6e1a..b553b8cca57 100644 --- a/plugins/karbonplugins/tools/KarbonGradientEditStrategy.cpp +++ b/plugins/karbonplugins/tools/KarbonGradientEditStrategy.cpp @@ -1,586 +1,586 @@ /* This file is part of the KDE project * Copyright (C) 2007-2008 Jan Hambrecht * Copyright (C) 2010 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KarbonGradientEditStrategy.h" #include #include #include #include #include #include #include #include #include #include #include #include int GradientStrategy::m_handleRadius = 3; int GradientStrategy::m_grabSensitivity = 3; const qreal stopDistance = 15.0; /// Returns scalar product of two given vectors qreal GradientStrategy::scalarProduct(const QPointF &p1, const QPointF &p2) { return p1.x() * p2.x() + p1.y() * p2.y(); } GradientStrategy::GradientStrategy(KoShape *shape, const QGradient * gradient, Target target) : m_shape(shape) , m_editing(false), m_target(target) , m_gradientLine(0, 1), m_selection(None) , m_selectionIndex(0), m_type(gradient->type()) { if (m_target == Fill) { QSharedPointer fill = qSharedPointerDynamicCast(m_shape->background()); if (fill) m_matrix = fill->transform() * m_shape->absoluteTransformation(0); } else { KoShapeStroke * stroke = dynamic_cast(m_shape->stroke()); if (stroke) m_matrix = stroke->lineBrush().transform() * m_shape->absoluteTransformation(0); } m_stops = gradient->stops(); } void GradientStrategy::setEditing(bool on) { m_editing = on; // if we are going into editing mode, save the old background // for use inside the command emitted when finished if (on) { if (m_target == Fill) { QSharedPointer fill = qSharedPointerDynamicCast(m_shape->background()); if (fill) { m_oldBrush = QBrush(*fill->gradient()); m_oldBrush.setTransform(fill->transform()); } } else { KoShapeStroke * stroke = dynamic_cast(m_shape->stroke()); if (stroke) { m_oldStroke = *stroke; m_oldBrush = stroke->lineBrush(); } } m_newBrush = m_oldBrush; } } bool GradientStrategy::hitHandle(const QPointF &mousePos, const KoViewConverter &converter, bool select) { QRectF roi = grabRect(converter); int handleIndex = 0; foreach(const QPointF & handle, m_handles) { roi.moveCenter(m_matrix.map(handle)); if (roi.contains(mousePos)) { if (select) setSelection(Handle, handleIndex); return true; } handleIndex++; } if (select) setSelection(None); return false; } bool GradientStrategy::hitLine(const QPointF &mousePos, const KoViewConverter &converter, bool select) { qreal maxDistance = converter.viewToDocumentX(grabSensitivity()); if (mouseAtLineSegment(mousePos, maxDistance)) { m_lastMousePos = mousePos; if (select) setSelection(Line); return true; } if (select) setSelection(None); return false; } bool GradientStrategy::hitStop(const QPointF &mousePos, const KoViewConverter &converter, bool select) { QRectF roi = grabRect(converter); QList handles = stopHandles(converter); int stopCount = m_stops.count(); for (int i = 0; i < stopCount; ++i) { roi.moveCenter(handles[i].second); if (roi.contains(mousePos)) { if (select) setSelection(Stop, i); m_lastMousePos = mousePos; return true; } } if (select) setSelection(None); return false; } void GradientStrategy::paintHandle(QPainter &painter, const KoViewConverter &converter, const QPointF &position) { QRectF hr = handleRect(converter); hr.moveCenter(position); painter.drawRect(hr); } void GradientStrategy::paintStops(QPainter &painter, const KoViewConverter &converter) { painter.save(); QRectF hr = handleRect(converter); QPen defPen = painter.pen(); QList handles = stopHandles(converter); int stopCount = m_stops.count(); for (int i = 0; i < stopCount; ++i) { hr.moveCenter(handles[i].second); painter.setPen(defPen); painter.drawLine(handles[i].first, handles[i].second); painter.setBrush(m_stops[i].second); - painter.setPen(invertedColor(m_stops[i].second)); + painter.setPen(QPen(invertedColor(m_stops[i].second), 0)); if (m_selection == Stop && m_selectionIndex == i) { QTransform m; m.translate(hr.center().x(), hr.center().y()); m.rotate(45.0); m.translate(-hr.center().x(), -hr.center().y()); painter.save(); painter.setWorldTransform(m, true); painter.drawRect(hr); painter.restore(); } else painter.drawEllipse(hr); } painter.restore(); } void GradientStrategy::paint(QPainter &painter, const KoViewConverter &converter, bool selected) { m_shape->applyConversion(painter, converter); QPointF startPoint = m_matrix.map(m_handles[m_gradientLine.first]); QPointF stopPoint = m_matrix.map(m_handles[m_gradientLine.second]); // draw the gradient line painter.drawLine(startPoint, stopPoint); // draw the gradient stops if (selected) paintStops(painter, converter); // draw the gradient handles foreach(const QPointF & handle, m_handles) paintHandle(painter, converter, m_matrix.map(handle)); } qreal GradientStrategy::projectToGradientLine(const QPointF &point) { QPointF startPoint = m_matrix.map(m_handles[m_gradientLine.first]); QPointF stopPoint = m_matrix.map(m_handles[m_gradientLine.second]); QPointF diff = stopPoint - startPoint; qreal diffLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y()); if (diffLength == 0.0f) return 0.0f; // project mouse position relative to stop position on gradient line qreal scalar = scalarProduct(point - startPoint, diff / diffLength); return scalar /= diffLength; } bool GradientStrategy::mouseAtLineSegment(const QPointF &mousePos, qreal maxDistance) { qreal scalar = projectToGradientLine(mousePos); if (scalar < 0.0 || scalar > 1.0) return false; // calculate vector between relative mouse position and projected mouse position QPointF startPoint = m_matrix.map(m_handles[m_gradientLine.first]); QPointF stopPoint = m_matrix.map(m_handles[m_gradientLine.second]); QPointF distVec = startPoint + scalar * (stopPoint - startPoint) - mousePos; qreal dist = distVec.x() * distVec.x() + distVec.y() * distVec.y(); if (dist > maxDistance*maxDistance) return false; return true; } void GradientStrategy::handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers) QTransform invMatrix = m_matrix.inverted(); switch (m_selection) { case Line: { uint handleCount = m_handles.count(); QPointF delta = invMatrix.map(mouseLocation) - invMatrix.map(m_lastMousePos); for (uint i = 0; i < handleCount; ++i) m_handles[i] += delta; m_lastMousePos = mouseLocation; break; } case Handle: m_handles[m_selectionIndex] = invMatrix.map(mouseLocation); break; case Stop: { qreal scalar = projectToGradientLine(mouseLocation); scalar = qMax(qreal(0.0), scalar); scalar = qMin(scalar, qreal(1.0)); m_stops[m_selectionIndex].first = scalar; m_lastMousePos = mouseLocation; break; } default: return; } applyChanges(); } bool GradientStrategy::handleDoubleClick(const QPointF &mouseLocation) { if (m_selection == Line) { // double click on gradient line inserts a new gradient stop qreal scalar = projectToGradientLine(mouseLocation); // calculate distance to gradient line QPointF startPoint = m_matrix.map(m_handles[m_gradientLine.first]); QPointF stopPoint = m_matrix.map(m_handles[m_gradientLine.second]); QPointF diff = stopPoint - startPoint; QPointF diffToLine = startPoint + scalar * diff - mouseLocation; qreal distToLine = diffToLine.x() * diffToLine.x() + diffToLine.y() * diffToLine.y(); if (distToLine > m_handleRadius*m_handleRadius) return false; QColor newColor = KoGradientHelper::colorAt(scalar, m_stops); m_stops.append(QGradientStop(scalar, newColor)); } else if (m_selection == Stop) { // double click on stop handle removes gradient stop // do not allow removing one of the last two stops if (m_stops.count() <= 2) return false; m_stops.remove(m_selectionIndex); setSelection(None); } else return false; applyChanges(); return true; } void GradientStrategy::applyChanges() { m_newBrush = brush(); if (m_target == Fill) { QSharedPointer fill = qSharedPointerDynamicCast(m_shape->background()); if (fill) { fill->setGradient(*m_newBrush.gradient()); fill->setTransform(m_newBrush.transform()); } } else { KoShapeStroke * stroke = dynamic_cast(m_shape->stroke()); if (stroke) stroke->setLineBrush(m_newBrush); } } KUndo2Command * GradientStrategy::createCommand(KUndo2Command * parent) { if (m_newBrush == m_oldBrush) return 0; if (m_target == Fill) { QSharedPointer fill = qSharedPointerDynamicCast(m_shape->background()); if (fill) { QSharedPointer newFill(new KoGradientBackground(*fill->gradient(), fill->transform())); fill->setGradient(*m_oldBrush.gradient()); fill->setTransform(m_oldBrush.transform()); return new KoShapeBackgroundCommand(m_shape, newFill, parent); } } else { KoShapeStroke * stroke = dynamic_cast(m_shape->stroke()); if (stroke) { *stroke = m_oldStroke; KoShapeStroke * newStroke = new KoShapeStroke(*stroke); newStroke->setLineBrush(m_newBrush); return new KoShapeStrokeCommand(m_shape, newStroke, parent); } } return 0; } QRectF GradientStrategy::boundingRect(const KoViewConverter &converter) const { // calculate the bounding rect of the handles QRectF bbox(m_matrix.map(m_handles[0]), QSize(0, 0)); for (int i = 1; i < m_handles.count(); ++i) { QPointF handle = m_matrix.map(m_handles[i]); bbox.setLeft(qMin(handle.x(), bbox.left())); bbox.setRight(qMax(handle.x(), bbox.right())); bbox.setTop(qMin(handle.y(), bbox.top())); bbox.setBottom(qMax(handle.y(), bbox.bottom())); } QList handles = stopHandles(converter); foreach(const StopHandle & stopHandle, handles) { QPointF handle = stopHandle.second; bbox.setLeft(qMin(handle.x(), bbox.left())); bbox.setRight(qMax(handle.x(), bbox.right())); bbox.setTop(qMin(handle.y(), bbox.top())); bbox.setBottom(qMax(handle.y(), bbox.bottom())); } // quick hack for gradient stops //bbox.adjust( -stopDistance, -stopDistance, stopDistance, stopDistance ); return bbox.adjusted(-m_handleRadius, -m_handleRadius, m_handleRadius, m_handleRadius); } void GradientStrategy::repaint(const KoViewConverter &converter) const { QRectF gradientRect = boundingRect(converter).adjusted(-1, -1, 1, 1); m_shape->update(m_shape->documentToShape(gradientRect)); m_shape->update(); } const QGradient * GradientStrategy::gradient() { if (m_target == Fill) { QSharedPointer fill = qSharedPointerDynamicCast(m_shape->background()); if (! fill) return 0; return fill->gradient(); } else { KoShapeStroke * stroke = dynamic_cast(m_shape->stroke()); if (! stroke) return 0; return stroke->lineBrush().gradient(); } } GradientStrategy::Target GradientStrategy::target() const { return m_target; } void GradientStrategy::startDrawing(const QPointF &mousePos) { QTransform invMatrix = m_matrix.inverted(); int handleCount = m_handles.count(); for (int handleId = 0; handleId < handleCount; ++handleId) m_handles[handleId] = invMatrix.map(mousePos); setSelection(Handle, handleCount - 1); setEditing(true); } bool GradientStrategy::hasSelection() const { return m_selection != None; } KoShape * GradientStrategy::shape() { return m_shape; } QGradient::Type GradientStrategy::type() const { return m_type; } void GradientStrategy::updateStops() { QBrush brush; if (m_target == Fill) { QSharedPointer fill = qSharedPointerDynamicCast(m_shape->background()); if (fill) m_stops = fill->gradient()->stops(); } else { KoShapeStroke * stroke = dynamic_cast(m_shape->stroke()); if (stroke) { brush = stroke->lineBrush(); if (brush.gradient()) m_stops = brush.gradient()->stops(); } } } int GradientStrategy::selectedColorStop() const { if (m_selection == Stop) return m_selectionIndex; else return -1; } GradientStrategy::SelectionType GradientStrategy::selection() const { return m_selection; } void GradientStrategy::setGradientLine(int start, int stop) { m_gradientLine = QPair(start, stop); } QRectF GradientStrategy::handleRect(const KoViewConverter &converter) const { return converter.viewToDocument(QRectF(0, 0, 2*m_handleRadius, 2*m_handleRadius)); } QRectF GradientStrategy::grabRect(const KoViewConverter &converter) const { return converter.viewToDocument(QRectF(0, 0, 2*m_grabSensitivity, 2*m_grabSensitivity)); } void GradientStrategy::setSelection(SelectionType selection, int index) { m_selection = selection; m_selectionIndex = index; } QColor GradientStrategy::invertedColor(const QColor &color) { return QColor(255 - color.red(), 255 - color.green(), 255 - color.blue()); } QList GradientStrategy::stopHandles(const KoViewConverter &converter) const { // get the gradient line start and end point in document coordinates QPointF start = m_matrix.map(m_handles[m_gradientLine.first]); QPointF stop = m_matrix.map(m_handles[m_gradientLine.second]); // calculate orthogonal vector to the gradient line // using the cross product of the line vector and the negative z-axis QPointF diff = stop - start; QPointF ortho(-diff.y(), diff.x()); qreal orthoLength = sqrt(ortho.x() * ortho.x() + ortho.y() * ortho.y()); if (orthoLength == 0.0) ortho = QPointF(stopDistance, 0.0f); else ortho *= stopDistance / orthoLength; // make handles have always the same distance to the gradient line // independent of acual zooming ortho = converter.viewToDocument(ortho); QList handles; foreach(const QGradientStop & stop, m_stops) { QPointF base = start + stop.first * diff; handles.append(StopHandle(base, base + ortho)); } return handles; } ///////////////////////////////////////////////////////////////// // strategy implementations ///////////////////////////////////////////////////////////////// LinearGradientStrategy::LinearGradientStrategy(KoShape *shape, const QLinearGradient *gradient, Target target) : GradientStrategy(shape, gradient, target) { Q_ASSERT(gradient->coordinateMode() == QGradient::ObjectBoundingMode); QSizeF size(shape->size()); m_handles.append(KoFlake::toAbsolute(gradient->start(), size)); m_handles.append(KoFlake::toAbsolute(gradient->finalStop(), size)); } QBrush LinearGradientStrategy::brush() { QSizeF size(m_shape->size()); QLinearGradient gradient(KoFlake::toRelative(m_handles[start], size), KoFlake::toRelative(m_handles[stop], size)); gradient.setCoordinateMode(QGradient::ObjectBoundingMode); gradient.setStops(m_stops); gradient.setSpread(m_oldBrush.gradient()->spread()); QBrush brush = QBrush(gradient); brush.setTransform(m_oldBrush.transform()); return brush; } RadialGradientStrategy::RadialGradientStrategy(KoShape *shape, const QRadialGradient *gradient, Target target) : GradientStrategy(shape, gradient, target) { Q_ASSERT(gradient->coordinateMode() == QGradient::ObjectBoundingMode); QSizeF size(shape->size()); QPointF absoluteCenter(KoFlake::toAbsolute(gradient->center(), size)); qreal radius = gradient->radius() * size.width(); m_handles.append(absoluteCenter); m_handles.append(KoFlake::toAbsolute(gradient->focalPoint(), size)); m_handles.append(absoluteCenter + QPointF(radius, 0)); setGradientLine(0, 2); } QBrush RadialGradientStrategy::brush() { QSizeF size(m_shape->size()); QPointF relativeCenter(KoFlake::toRelative(m_handles[center], size)); QPointF d = KoFlake::toRelative(m_handles[radius], size) - relativeCenter; qreal r = sqrt(d.x()*d.x() + d.y()*d.y()); QRadialGradient gradient(relativeCenter, r, KoFlake::toRelative(m_handles[focal], size)); gradient.setCoordinateMode(QGradient::ObjectBoundingMode); gradient.setStops(m_stops); gradient.setSpread(m_oldBrush.gradient()->spread()); QBrush brush = QBrush(gradient); brush.setTransform(m_oldBrush.transform()); return brush; } ConicalGradientStrategy::ConicalGradientStrategy(KoShape *shape, const QConicalGradient *gradient, Target target) : GradientStrategy(shape, gradient, target) { Q_ASSERT(gradient->coordinateMode() == QGradient::ObjectBoundingMode); QSizeF size(m_shape->size()); qreal scale = 0.25 * (size.height() + size.width()); qreal angle = gradient->angle() * M_PI / 180.0; QPointF center(KoFlake::toAbsolute(gradient->center(), size)); m_handles.append(center); m_handles.append(center + scale * QPointF(cos(angle), -sin(angle))); } QBrush ConicalGradientStrategy::brush() { QPointF d = m_handles[direction] - m_handles[center]; qreal angle = atan2(-d.y(), d.x()) / M_PI * 180.0; if (angle < 0.0) angle += 360; QConicalGradient gradient(KoFlake::toRelative( m_handles[center], m_shape->size()), angle); gradient.setCoordinateMode(QGradient::ObjectBoundingMode); gradient.setStops(m_stops); gradient.setSpread(m_oldBrush.gradient()->spread()); QBrush brush = QBrush(gradient); brush.setTransform(m_oldBrush.transform()); return brush; } diff --git a/plugins/karbonplugins/tools/KarbonGradientTool.cpp b/plugins/karbonplugins/tools/KarbonGradientTool.cpp index c8d095fd8fd..a962167a90b 100644 --- a/plugins/karbonplugins/tools/KarbonGradientTool.cpp +++ b/plugins/karbonplugins/tools/KarbonGradientTool.cpp @@ -1,587 +1,587 @@ /* This file is part of the KDE project * Copyright (C) 2007-2008,2011 Jan Hambrecht * Copyright (C) 2007,2010 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KarbonGradientTool.h" #include "KarbonGradientEditStrategy.h" #include "KarbonCursor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // helper function GradientStrategy * createStrategy(KoShape * shape, const QGradient * gradient, GradientStrategy::Target target) { if (! shape || ! gradient) return 0; if (gradient->type() == QGradient::LinearGradient) return new LinearGradientStrategy(shape, static_cast(gradient), target); else if (gradient->type() == QGradient::RadialGradient) return new RadialGradientStrategy(shape, static_cast(gradient), target); else if (gradient->type() == QGradient::ConicalGradient) return new ConicalGradientStrategy(shape, static_cast(gradient), target); else return 0; } KarbonGradientTool::KarbonGradientTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_gradient(0) , m_currentStrategy(0) , m_hoverStrategy(0) , m_gradientWidget(0) , m_currentCmd(0) , m_oldSnapStrategies(0) { } KarbonGradientTool::~KarbonGradientTool() { delete m_gradient; } void KarbonGradientTool::paint(QPainter &painter, const KoViewConverter &converter) { painter.setBrush(Qt::green); //TODO make configurable - painter.setPen(Qt::blue); //TODO make configurable + painter.setPen(QPen(Qt::blue, 0)); //TODO make configurable foreach(GradientStrategy *strategy, m_strategies) { bool current = (strategy == m_currentStrategy); painter.save(); if (current) { painter.setBrush(Qt::red); //TODO make configurable } strategy->paint(painter, converter, current); painter.restore(); } } void KarbonGradientTool::repaintDecorations() { foreach(GradientStrategy *strategy, m_strategies) canvas()->updateCanvas(strategy->boundingRect(*canvas()->viewConverter())); } void KarbonGradientTool::mousePressEvent(KoPointerEvent *event) { Q_UNUSED(event); if (!m_gradient) return; // do we have a selected gradient ? if (m_currentStrategy) { // now select whatever we hit if (m_currentStrategy->hitHandle(event->point, *canvas()->viewConverter(), true) || m_currentStrategy->hitStop(event->point, *canvas()->viewConverter(), true) || m_currentStrategy->hitLine(event->point, *canvas()->viewConverter(), true)) { m_currentStrategy->setEditing(true); m_currentStrategy->repaint(*canvas()->viewConverter()); return; } m_currentStrategy->repaint(*canvas()->viewConverter()); } // are we hovering over a gradient ? if (m_hoverStrategy) { // now select whatever we hit if (m_hoverStrategy->hitHandle(event->point, *canvas()->viewConverter(), true) || m_hoverStrategy->hitStop(event->point, *canvas()->viewConverter(), true) || m_hoverStrategy->hitLine(event->point, *canvas()->viewConverter(), true)) { m_currentStrategy = m_hoverStrategy; m_hoverStrategy = 0; m_currentStrategy->setEditing(true); m_currentStrategy->repaint(*canvas()->viewConverter()); return; } } qreal grabDist = canvas()->viewConverter()->viewToDocumentX(GradientStrategy::grabSensitivity()); QRectF roi(QPointF(), QSizeF(grabDist, grabDist)); roi.moveCenter(event->point); // check if we are on a shape without a gradient yet QList shapes = canvas()->shapeManager()->shapesAt(roi); KoSelection * selection = canvas()->shapeManager()->selection(); KoGradientEditWidget::GradientTarget target = m_gradientWidget->target(); GradientStrategy * newStrategy = 0; foreach(KoShape * shape, shapes) { if (! selection->isSelected(shape)) continue; if (target == KoGradientEditWidget::FillGradient) { // target is fill so check the background style if (! dynamic_cast(shape->background().data())) { QSharedPointer fill(new KoGradientBackground(*m_gradient)); m_currentCmd = new KoShapeBackgroundCommand(shape, fill); shape->setBackground(fill); newStrategy = createStrategy(shape, m_gradient, GradientStrategy::Fill); } } else { // target is stroke so check the stroke style KoShapeStroke * stroke = dynamic_cast(shape->stroke()); if (! stroke) { stroke = new KoShapeStroke(1.0); stroke->setLineBrush(QBrush(*m_gradient)); m_currentCmd = new KoShapeStrokeCommand(shape, stroke); shape->setStroke(stroke); newStrategy = createStrategy(shape, m_gradient, GradientStrategy::Stroke); break; } else { Qt::BrushStyle style = stroke->lineBrush().style(); if (style < Qt::LinearGradientPattern || style > Qt::RadialGradientPattern) { KoShapeStroke * newStroke = new KoShapeStroke(*stroke); newStroke->setLineBrush(QBrush(*m_gradient)); m_currentCmd = new KoShapeStrokeCommand(shape, newStroke); stroke->setLineBrush(QBrush(*m_gradient)); newStrategy = createStrategy(shape, m_gradient, GradientStrategy::Stroke); break; } } } } if (newStrategy) { m_currentStrategy = newStrategy; m_strategies.insert(m_currentStrategy->shape(), m_currentStrategy); m_currentStrategy->startDrawing(event->point); } } void KarbonGradientTool::mouseMoveEvent(KoPointerEvent *event) { m_hoverStrategy = 0; // do we have a selected gradient ? if (m_currentStrategy) { // are we editing the current selected gradient ? if (m_currentStrategy->isEditing()) { QPointF mousePos = event->point; // snap to bounding box when moving handles if (m_currentStrategy->selection() == GradientStrategy::Handle) mousePos = canvas()->snapGuide()->snap(mousePos, event->modifiers()); m_currentStrategy->repaint(*canvas()->viewConverter()); m_currentStrategy->handleMouseMove(mousePos, event->modifiers()); m_currentStrategy->repaint(*canvas()->viewConverter()); return; } // are we on a gradient handle ? else if (m_currentStrategy->hitHandle(event->point, *canvas()->viewConverter(), false)) { m_currentStrategy->repaint(*canvas()->viewConverter()); useCursor(KarbonCursor::needleMoveArrow()); emit statusTextChanged(i18n("Drag to move gradient position.")); return; } // are we on a gradient stop handle ? else if (m_currentStrategy->hitStop(event->point, *canvas()->viewConverter(), false)) { m_currentStrategy->repaint(*canvas()->viewConverter()); useCursor(KarbonCursor::needleMoveArrow()); const QGradient * g = m_currentStrategy->gradient(); if (g && g->stops().count() > 2) emit statusTextChanged(i18n("Drag to move color stop. Double click to remove color stop.")); else emit statusTextChanged(i18n("Drag to move color stop.")); return; } // are we near the gradient line ? else if (m_currentStrategy->hitLine(event->point, *canvas()->viewConverter(), false)) { m_currentStrategy->repaint(*canvas()->viewConverter()); useCursor(Qt::SizeAllCursor); emit statusTextChanged(i18n("Drag to move gradient position. Double click to insert color stop.")); return; } } // we have no selected gradient, so lets check if at least // the mouse hovers over another gradient (handles and line) // first check if we hit any handles foreach(GradientStrategy *strategy, m_strategies) { if (strategy->hitHandle(event->point, *canvas()->viewConverter(), false)) { m_hoverStrategy = strategy; useCursor(KarbonCursor::needleMoveArrow()); return; } } // now check if we hit any lines foreach(GradientStrategy *strategy, m_strategies) { if (strategy->hitLine(event->point, *canvas()->viewConverter(), false)) { m_hoverStrategy = strategy; useCursor(Qt::SizeAllCursor); return; } } useCursor(KarbonCursor::needleArrow()); } void KarbonGradientTool::mouseReleaseEvent(KoPointerEvent *event) { Q_UNUSED(event) // if we are editing, get out of edit mode and add a command to the stack if (m_currentStrategy) { KUndo2Command * cmd = m_currentStrategy->createCommand(m_currentCmd); canvas()->addCommand(m_currentCmd ? m_currentCmd : cmd); m_currentCmd = 0; if (m_gradientWidget) { m_gradientWidget->setGradient(*m_currentStrategy->gradient()); if (m_currentStrategy->target() == GradientStrategy::Fill) m_gradientWidget->setTarget(KoGradientEditWidget::FillGradient); else m_gradientWidget->setTarget(KoGradientEditWidget::StrokeGradient); m_gradientWidget->setStopIndex(m_currentStrategy->selectedColorStop()); } m_currentStrategy->setEditing(false); } } void KarbonGradientTool::mouseDoubleClickEvent(KoPointerEvent *event) { if (! m_currentStrategy) return; canvas()->updateCanvas(m_currentStrategy->boundingRect(*canvas()->viewConverter())); if (m_currentStrategy->handleDoubleClick(event->point)) { KUndo2Command * cmd = m_currentStrategy->createCommand(m_currentCmd); canvas()->addCommand(m_currentCmd ? m_currentCmd : cmd); m_currentCmd = 0; if (m_gradientWidget) { m_gradientWidget->setGradient(*m_currentStrategy->gradient()); if (m_currentStrategy->target() == GradientStrategy::Fill) m_gradientWidget->setTarget(KoGradientEditWidget::FillGradient); else m_gradientWidget->setTarget(KoGradientEditWidget::StrokeGradient); } canvas()->updateCanvas(m_currentStrategy->boundingRect(*canvas()->viewConverter())); } } void KarbonGradientTool::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_I: { uint handleRadius = GradientStrategy::handleRadius(); if (event->modifiers() & Qt::ControlModifier) handleRadius--; else handleRadius++; // XXX: this is a KoDocumentResourceController feature, but shouldn't it be canvas? canvas()->shapeController()->resourceManager()->setHandleRadius(handleRadius); } break; default: event->ignore(); return; } event->accept(); } void KarbonGradientTool::activate(ToolActivation toolActivation, const QSet &shapes) { Q_UNUSED(toolActivation); if (shapes.isEmpty()) { emit done(); return; } initialize(); repaintDecorations(); useCursor(KarbonCursor::needleArrow()); // save old enabled snap strategies, set bounding box snap strategy m_oldSnapStrategies = canvas()->snapGuide()->enabledSnapStrategies(); canvas()->snapGuide()->enableSnapStrategies(KoSnapGuide::BoundingBoxSnapping); canvas()->snapGuide()->reset(); } void KarbonGradientTool::initialize() { if (m_currentStrategy && m_currentStrategy->isEditing()) return; m_hoverStrategy = 0; QList selectedShapes = canvas()->shapeManager()->selection()->selectedShapes(); QList strategies = m_strategies.values(); // remove all gradient strategies no longer applicable foreach(GradientStrategy * strategy, strategies) { // is this gradient shape still selected ? if (! selectedShapes.contains(strategy->shape()) || ! strategy->shape()->isEditable()) { m_strategies.remove(strategy->shape(), strategy); delete strategy; if (m_currentStrategy == strategy) m_currentStrategy = 0; continue; } // is the gradient a fill gradient but shape has no fill gradient anymore ? if (strategy->target() == GradientStrategy::Fill) { QSharedPointer fill = qSharedPointerDynamicCast(strategy->shape()->background()); if (! fill || ! fill->gradient() || fill->gradient()->type() != strategy->type()) { // delete the gradient m_strategies.remove(strategy->shape(), strategy); delete strategy; if (m_currentStrategy == strategy) m_currentStrategy = 0; continue; } } // is the gradient a stroke gradient but shape has no stroke gradient anymore ? if (strategy->target() == GradientStrategy::Stroke) { KoShapeStroke * stroke = dynamic_cast(strategy->shape()->stroke()); if (! stroke || ! stroke->lineBrush().gradient() || stroke->lineBrush().gradient()->type() != strategy->type()) { // delete the gradient m_strategies.remove(strategy->shape(), strategy); delete strategy; if (m_currentStrategy == strategy) m_currentStrategy = 0; continue; } } } // now create new strategies if needed foreach(KoShape * shape, selectedShapes) { if (! shape->isEditable()) continue; bool strokeExists = false; bool fillExists = false; // check which gradient strategies exist for this shape foreach(GradientStrategy * strategy, m_strategies.values(shape)) { if (strategy->target() == GradientStrategy::Fill) { fillExists = true; strategy->updateStops(); } if (strategy->target() == GradientStrategy::Stroke) { strokeExists = true; strategy->updateStops(); } } if (! fillExists) { QSharedPointer fill = qSharedPointerDynamicCast(shape->background()); if (fill) { GradientStrategy * fillStrategy = createStrategy(shape, fill->gradient(), GradientStrategy::Fill); if (fillStrategy) { m_strategies.insert(shape, fillStrategy); fillStrategy->repaint(*canvas()->viewConverter()); } } } if (! strokeExists) { KoShapeStroke * stroke = dynamic_cast(shape->stroke()); if (stroke) { GradientStrategy * strokeStrategy = createStrategy(shape, stroke->lineBrush().gradient(), GradientStrategy::Stroke); if (strokeStrategy) { m_strategies.insert(shape, strokeStrategy); strokeStrategy->repaint(*canvas()->viewConverter()); } } } } if (m_strategies.count() == 0) { // create a default gradient m_gradient = new QLinearGradient(QPointF(0,0), QPointF(1,1)); m_gradient->setCoordinateMode(QGradient::ObjectBoundingMode); m_gradient->setColorAt(0.0, Qt::white); m_gradient->setColorAt(1.0, Qt::green); return; } // automatically select strategy when editing single shape if (selectedShapes.count() == 1 && m_strategies.count()) { if (! m_currentStrategy || ! m_strategies.values().contains(m_currentStrategy)) m_currentStrategy = m_strategies.values().first(); } delete m_gradient; GradientStrategy * strategy = m_currentStrategy ? m_currentStrategy : m_strategies.values().first(); GradientStrategy::setHandleRadius(handleRadius()); GradientStrategy::setGrabSensitivity(grabSensitivity()); m_gradient = KoFlake::cloneGradient(strategy->gradient()); if (m_gradientWidget) { if (m_gradient) { m_gradientWidget->setGradient(*m_gradient); } if (strategy->target() == GradientStrategy::Fill) m_gradientWidget->setTarget(KoGradientEditWidget::FillGradient); else m_gradientWidget->setTarget(KoGradientEditWidget::StrokeGradient); } } void KarbonGradientTool::deactivate() { delete m_gradient; m_gradient = 0; m_currentStrategy = 0; m_hoverStrategy = 0; qDeleteAll(m_strategies); m_strategies.clear(); // restore previously set snap strategies canvas()->snapGuide()->enableSnapStrategies(m_oldSnapStrategies); canvas()->snapGuide()->reset(); } void KarbonGradientTool::documentResourceChanged(int key, const QVariant & res) { switch (key) { case KoDocumentResourceManager::HandleRadius: foreach(GradientStrategy *strategy, m_strategies) strategy->repaint(*canvas()->viewConverter()); GradientStrategy::setHandleRadius(res.toUInt()); foreach(GradientStrategy *strategy, m_strategies) strategy->repaint(*canvas()->viewConverter()); break; case KoDocumentResourceManager::GrabSensitivity: GradientStrategy::setGrabSensitivity(res.toUInt()); break; default: return; } } QList > KarbonGradientTool::createOptionWidgets() { m_gradientWidget = new KoGradientEditWidget(); if (m_gradient) { m_gradientWidget->setGradient(*m_gradient); } connect(m_gradientWidget, SIGNAL(changed()), this, SLOT(gradientChanged())); KoResourceServer * rserver = KoResourceServerProvider::instance()->gradientServer(); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); KoResourceItemChooser * chooser = new KoResourceItemChooser(adapter, m_gradientWidget); chooser->setObjectName("KarbonGradientChooser"); chooser->setColumnCount(1); connect(chooser, SIGNAL(resourceSelected(KoResource*)), this, SLOT(gradientSelected(KoResource*))); QList > widgets; m_gradientWidget->setWindowTitle(i18n("Edit Gradient")); widgets.append(m_gradientWidget); chooser->setWindowTitle(i18n("Predefined Gradients")); widgets.append(chooser); return widgets; } void KarbonGradientTool::gradientSelected(KoResource * resource) { if (! resource) return; KoAbstractGradient * gradient = dynamic_cast(resource); if (! gradient) return; QGradient * newGradient = gradient->toQGradient(); if (newGradient) { m_gradientWidget->setGradient(*newGradient); gradientChanged(); delete newGradient; } } void KarbonGradientTool::gradientChanged() { QList selectedShapes = canvas()->shapeManager()->selection()->selectedShapes(); QGradient::Type type = m_gradientWidget->type(); QGradient::Spread spread = m_gradientWidget->spread(); QGradientStops stops = m_gradientWidget->stops(); if (m_gradientWidget->target() == KoGradientEditWidget::FillGradient) { QList > newFills; foreach(KoShape * shape, selectedShapes) { QSharedPointer newFill; QSharedPointer oldFill = qSharedPointerDynamicCast(shape->background()); if (oldFill) { QGradient * g = KoGradientHelper::convertGradient(oldFill->gradient(), type); g->setSpread(spread); g->setStops(stops); newFill = QSharedPointer(new KoGradientBackground(g, oldFill->transform())); } else { QGradient * g = KoGradientHelper::defaultGradient(type, spread, stops); newFill = QSharedPointer(new KoGradientBackground(g)); } newFills.append(newFill); } canvas()->addCommand(new KoShapeBackgroundCommand(selectedShapes, newFills)); } else { QList newStrokes; foreach(KoShape * shape, selectedShapes) { KoShapeStroke * stroke = dynamic_cast(shape->stroke()); KoShapeStroke * newStroke = 0; if (stroke) newStroke = new KoShapeStroke(*stroke); else newStroke = new KoShapeStroke(1.0); QBrush newGradient; if (newStroke->lineBrush().gradient()) { QGradient * g = KoGradientHelper::convertGradient(newStroke->lineBrush().gradient(), type); g->setSpread(spread); g->setStops(stops); newGradient = QBrush(*g); delete g; } else { QGradient * g = KoGradientHelper::defaultGradient(type, spread, stops); newGradient = QBrush(*g); delete g; } newStroke->setLineBrush(newGradient); newStrokes.append(newStroke); } canvas()->addCommand(new KoShapeStrokeCommand(selectedShapes, newStrokes)); } initialize(); } diff --git a/plugins/karbonplugins/tools/KarbonPatternTool.cpp b/plugins/karbonplugins/tools/KarbonPatternTool.cpp index ed1b0ebaad0..2e7a1634406 100644 --- a/plugins/karbonplugins/tools/KarbonPatternTool.cpp +++ b/plugins/karbonplugins/tools/KarbonPatternTool.cpp @@ -1,348 +1,348 @@ /* 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 "KarbonPatternTool.h" #include "KarbonPatternEditStrategy.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KarbonPatternTool::KarbonPatternTool(KoCanvasBase *canvas) : KoToolBase(canvas), m_currentStrategy(0), m_optionsWidget(0) { } KarbonPatternTool::~KarbonPatternTool() { } void KarbonPatternTool::paint(QPainter &painter, const KoViewConverter &converter) { painter.setBrush(Qt::green); //TODO make configurable - painter.setPen(Qt::blue); //TODO make configurable + painter.setPen(QPen(Qt::blue, 0)); //TODO make configurable // paint all the strategies foreach(KarbonPatternEditStrategyBase *strategy, m_strategies) { if (strategy == m_currentStrategy) continue; painter.save(); strategy->paint(painter, converter); painter.restore(); } // paint selected strategy with another color if (m_currentStrategy) { painter.setBrush(Qt::red); //TODO make configurable m_currentStrategy->paint(painter, converter); } } void KarbonPatternTool::repaintDecorations() { foreach(KarbonPatternEditStrategyBase *strategy, m_strategies) canvas()->updateCanvas(strategy->boundingRect()); } void KarbonPatternTool::mousePressEvent(KoPointerEvent *event) { //m_currentStrategy = 0; foreach(KarbonPatternEditStrategyBase *strategy, m_strategies) { if (strategy->selectHandle(event->point, *canvas()->viewConverter())) { m_currentStrategy = strategy; m_currentStrategy->repaint(); useCursor(Qt::SizeAllCursor); break; } } if (m_currentStrategy) { m_currentStrategy->setEditing(true); updateOptionsWidget(); } } void KarbonPatternTool::mouseMoveEvent(KoPointerEvent *event) { if (m_currentStrategy) { m_currentStrategy->repaint(); if (m_currentStrategy->isEditing()) { m_currentStrategy->handleMouseMove(event->point, event->modifiers()); m_currentStrategy->repaint(); return; } } foreach(KarbonPatternEditStrategyBase *strategy, m_strategies) { if (strategy->selectHandle(event->point, *canvas()->viewConverter())) { useCursor(Qt::SizeAllCursor); return; } } useCursor(Qt::ArrowCursor); } void KarbonPatternTool::mouseReleaseEvent(KoPointerEvent *event) { Q_UNUSED(event) // if we are editing, get out of edit mode and add a command to the stack if (m_currentStrategy && m_currentStrategy->isEditing()) { m_currentStrategy->setEditing(false); KUndo2Command * cmd = m_currentStrategy->createCommand(); if (cmd) canvas()->addCommand(cmd); updateOptionsWidget(); } } void KarbonPatternTool::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_I: { KoDocumentResourceManager *rm = canvas()->shapeController()->resourceManager(); uint handleRadius = rm->handleRadius(); if (event->modifiers() & Qt::ControlModifier) handleRadius--; else handleRadius++; rm->setHandleRadius(handleRadius); } break; default: event->ignore(); return; } event->accept(); } void KarbonPatternTool::initialize() { if (m_currentStrategy && m_currentStrategy->isEditing()) return; QList selectedShapes = canvas()->shapeManager()->selection()->selectedShapes(); // remove all pattern strategies no longer applicable foreach(KarbonPatternEditStrategyBase * strategy, m_strategies) { // is this gradient shape still selected ? if (! selectedShapes.contains(strategy->shape()) || ! strategy->shape()->isEditable()) { m_strategies.remove(strategy->shape()); if (m_currentStrategy == strategy) m_currentStrategy = 0; delete strategy; continue; } // does the shape has no fill pattern anymore ? QSharedPointer fill = qSharedPointerDynamicCast(strategy->shape()->background()); if (! fill) { // delete the gradient m_strategies.remove(strategy->shape()); if (m_currentStrategy == strategy) m_currentStrategy = 0; delete strategy; continue; } strategy->updateHandles(); strategy->repaint(); } KoImageCollection *imageCollection = canvas()->shapeController()->resourceManager()->imageCollection(); // now create new strategies if needed foreach(KoShape *shape, selectedShapes) { if (! shape->isEditable()) continue; // do we already have a strategy for that shape? if (m_strategies.contains(shape)) continue; if (qSharedPointerDynamicCast(shape->background())) { KarbonPatternEditStrategyBase * s = new KarbonOdfPatternEditStrategy(shape, imageCollection); m_strategies.insert(shape, s); s->repaint(); } } // automatically select strategy when editing single shape if (m_strategies.count() == 1 && ! m_currentStrategy) { m_currentStrategy = m_strategies.begin().value(); updateOptionsWidget(); } if (m_currentStrategy) m_currentStrategy->repaint(); } void KarbonPatternTool::activate(ToolActivation toolActivation, const QSet &shapes) { Q_UNUSED(toolActivation); if (shapes.isEmpty()) { emit done(); return; } initialize(); KarbonPatternEditStrategyBase::setHandleRadius(handleRadius()); KarbonPatternEditStrategyBase::setGrabSensitivity(grabSensitivity()); useCursor(Qt::ArrowCursor); connect(canvas()->shapeManager(), SIGNAL(selectionContentChanged()), this, SLOT(initialize())); } void KarbonPatternTool::deactivate() { // we are not interested in selection content changes when not active disconnect(canvas()->shapeManager(), SIGNAL(selectionContentChanged()), this, SLOT(initialize())); foreach(KarbonPatternEditStrategyBase * strategy, m_strategies) { strategy->repaint(); } qDeleteAll(m_strategies); m_strategies.clear(); foreach(KoShape *shape, canvas()->shapeManager()->selection()->selectedShapes()) shape->update(); m_currentStrategy = 0; } void KarbonPatternTool::documentResourceChanged(int key, const QVariant & res) { switch (key) { case KoDocumentResourceManager::HandleRadius: foreach(KarbonPatternEditStrategyBase *strategy, m_strategies) strategy->repaint(); KarbonPatternEditStrategyBase::setHandleRadius(res.toUInt()); foreach(KarbonPatternEditStrategyBase *strategy, m_strategies) strategy->repaint(); break; case KoDocumentResourceManager::GrabSensitivity: KarbonPatternEditStrategyBase::setGrabSensitivity(res.toUInt()); break; default: return; } } QList > KarbonPatternTool::createOptionWidgets() { QList > widgets; m_optionsWidget = new KarbonPatternOptionsWidget(); connect(m_optionsWidget, SIGNAL(patternChanged()), this, SLOT(patternChanged())); KoResourceServer * rserver = KoResourceServerProvider::instance()->patternServer(); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); KoResourceItemChooser * chooser = new KoResourceItemChooser(adapter, m_optionsWidget); chooser->setObjectName("KarbonPatternChooser"); connect(chooser, SIGNAL(resourceSelected(KoResource*)), this, SLOT(patternSelected(KoResource*))); m_optionsWidget->setWindowTitle(i18n("Pattern Options")); widgets.append(m_optionsWidget); chooser->setWindowTitle(i18n("Patterns")); widgets.append(chooser); updateOptionsWidget(); return widgets; } void KarbonPatternTool::patternSelected(KoResource * resource) { KoPattern * currentPattern = dynamic_cast(resource); if (! currentPattern || ! currentPattern->valid()) return; KoImageCollection *imageCollection = canvas()->shapeController()->resourceManager()->imageCollection(); if (imageCollection) { QList selectedShapes = canvas()->shapeManager()->selection()->selectedShapes(); QSharedPointer newFill(new KoPatternBackground(imageCollection)); newFill->setPattern(currentPattern->pattern()); canvas()->addCommand(new KoShapeBackgroundCommand(selectedShapes, newFill)); initialize(); } } void KarbonPatternTool::updateOptionsWidget() { if (m_optionsWidget && m_currentStrategy) { QSharedPointer fill = qSharedPointerDynamicCast(m_currentStrategy->shape()->background()); if (fill) { m_optionsWidget->setRepeat(fill->repeat()); m_optionsWidget->setReferencePoint(fill->referencePoint()); m_optionsWidget->setReferencePointOffset(fill->referencePointOffset()); m_optionsWidget->setTileRepeatOffset(fill->tileRepeatOffset()); m_optionsWidget->setPatternSize(fill->patternDisplaySize().toSize()); } } } void KarbonPatternTool::patternChanged() { if (m_currentStrategy) { KoShape * shape = m_currentStrategy->shape(); QSharedPointer oldFill = qSharedPointerDynamicCast(shape->background()); if (! oldFill) return; KoImageCollection *imageCollection = canvas()->shapeController()->resourceManager()->imageCollection(); if (! imageCollection) return; QSharedPointer newFill(new KoPatternBackground(imageCollection)); if (! newFill) return; newFill->setTransform(oldFill->transform()); newFill->setPattern(oldFill->pattern()); newFill->setRepeat(m_optionsWidget->repeat()); newFill->setReferencePoint(m_optionsWidget->referencePoint()); newFill->setReferencePointOffset(m_optionsWidget->referencePointOffset()); newFill->setTileRepeatOffset(m_optionsWidget->tileRepeatOffset()); newFill->setPatternDisplaySize(m_optionsWidget->patternSize()); canvas()->addCommand(new KoShapeBackgroundCommand(shape, newFill)); } } diff --git a/plugins/karbonplugins/tools/filterEffectTool/FilterEffectSceneItems.cpp b/plugins/karbonplugins/tools/filterEffectTool/FilterEffectSceneItems.cpp index dcf729177e3..010c87c1d2b 100644 --- a/plugins/karbonplugins/tools/filterEffectTool/FilterEffectSceneItems.cpp +++ b/plugins/karbonplugins/tools/filterEffectTool/FilterEffectSceneItems.cpp @@ -1,303 +1,303 @@ /* This file is part of the KDE project * Copyright (c) 2009 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "FilterEffectSceneItems.h" #include "KoFilterEffect.h" #include #include #include #include #include const QSizeF ConnectorSize = QSize(20, 20); const qreal ItemWidth = 15 * ConnectorSize.height(); const qreal FontSize = 0.8 * ConnectorSize.height(); ConnectorItem::ConnectorItem(ConnectorType type, int index, QGraphicsItem * parent) : QGraphicsEllipseItem(parent), m_type(type), m_index(index) { if (m_type == Output) setBrush(QBrush(Qt::red)); else if (m_type == Input) setBrush(QBrush(Qt::green)); setAcceptDrops(true); setRect(QRectF(QPointF(), ConnectorSize)); } void ConnectorItem::setCenter(const QPointF &position) { QRectF r = rect(); r.moveCenter(position); setRect(r); } ConnectorItem::ConnectorType ConnectorItem::connectorType() { return m_type; } int ConnectorItem::connectorIndex() const { return m_index; } KoFilterEffect * ConnectorItem::effect() const { if (!parentItem()) return 0; EffectItemBase * effectItem = dynamic_cast(parentItem()); if (!effectItem) return 0; return effectItem->effect(); } ConnectorMimeData::ConnectorMimeData(ConnectorItem *connector) : m_connector(connector) { } ConnectorItem * ConnectorMimeData::connector() const { return m_connector; } EffectItemBase::EffectItemBase(KoFilterEffect *effect) : QGraphicsRectItem(0), m_effect(effect) { setZValue(1); setFlags(QGraphicsItem::ItemIsSelectable); setAcceptDrops(true); setHandlesChildEvents(true); } void EffectItemBase::createText(const QString &text) { QGraphicsSimpleTextItem * textItem = new QGraphicsSimpleTextItem(text, this); QFont font = textItem->font(); font.setPointSize(FontSize); textItem->setFont(font); QRectF textBox = textItem->boundingRect(); QPointF offset = rect().center() - textBox.center(); textItem->translate(offset.x(), offset.y()); } void EffectItemBase::createOutput(const QPointF &position, const QString &name) { ConnectorItem * connector = new ConnectorItem(ConnectorItem::Output, 0, this); connector->setCenter(position); m_outputPosition = position; m_outputName = name; } void EffectItemBase::createInput(const QPointF &position) { int inputCount = m_inputPositions.count(); ConnectorItem * connector = new ConnectorItem(ConnectorItem::Input, inputCount, this); connector->setCenter(position); m_inputPositions.append(position); } QPointF EffectItemBase::outputPosition() const { return m_outputPosition; } QPointF EffectItemBase::inputPosition(int index) const { if (index < 0 || index >= m_inputPositions.count()) return QPointF(); return m_inputPositions[index]; } QString EffectItemBase::outputName() const { return m_outputName; } QSizeF EffectItemBase::connectorSize() const { return ConnectorSize; } KoFilterEffect * EffectItemBase::effect() const { return m_effect; } void EffectItemBase::mousePressEvent(QGraphicsSceneMouseEvent *event) { ConnectorItem * connector = connectorAtPosition(event->scenePos()); if (!connector) return; ConnectorMimeData *data = new ConnectorMimeData(connector); QDrag *drag = new QDrag(event->widget()); drag->setMimeData(data); drag->start(); } void EffectItemBase::dragMoveEvent(QGraphicsSceneDragDropEvent * event) { event->ignore(); ConnectorItem * targetItem = connectorAtPosition(event->scenePos()); if (!targetItem) return; const ConnectorMimeData * data = dynamic_cast(event->mimeData()); if (!data) return; ConnectorItem * sourceItem = data->connector(); int sourceItemType = sourceItem->connectorType(); int targetItemType = targetItem->connectorType(); if (sourceItemType == targetItemType) return; // do not accept connection within single effect item if (sourceItem->parentItem() == targetItem->parentItem()) return; if (sourceItemType == ConnectorItem::Input) { // we can only connect input with output above if (sourceItem->scenePos().y() < targetItem->scenePos().y()) return; } if (sourceItemType == ConnectorItem::Output) { // we can only connect output with input below if (sourceItem->scenePos().y() > targetItem->scenePos().y()) return; } event->accept(); } void EffectItemBase::dropEvent(QGraphicsSceneDragDropEvent * event) { ConnectorItem * connector = connectorAtPosition(event->scenePos()); if (!connector) return; const ConnectorMimeData * data = dynamic_cast(event->mimeData()); if (!data) return; } ConnectorItem * EffectItemBase::connectorAtPosition(const QPointF &scenePosition) { foreach(QGraphicsItem *childItem, childItems()) { ConnectorItem * connector = dynamic_cast(childItem); if (!connector) continue; if (connector->contains(connector->mapFromScene(scenePosition))) { return connector; } } return 0; } DefaultInputItem::DefaultInputItem(const QString &name, KoFilterEffect *effect) : EffectItemBase(effect), m_name(name) { setRect(0, 0, ItemWidth, 2*ConnectorSize.height()); createOutput(QPointF(ItemWidth, 0.5*rect().height()), name); createText(name); QLinearGradient g(QPointF(0, 0), QPointF(1, 1)); g.setCoordinateMode(QGradient::ObjectBoundingMode); g.setColorAt(0, Qt::white); g.setColorAt(1, QColor(255, 168, 88)); setBrush(QBrush(g)); } EffectItem::EffectItem(KoFilterEffect *effect) : EffectItemBase(effect) { Q_ASSERT(effect); QRectF circle(QPointF(), ConnectorSize); QPointF position(ItemWidth, ConnectorSize.height()); // create input connectors int requiredInputCount = effect->requiredInputCount(); int usedInputCount = qMax(requiredInputCount, effect->inputs().count()); for (int i = 0; i < usedInputCount; ++i) { createInput(position); position.ry() += 1.5 * ConnectorSize.height(); } // create a new input connector when maximal input count in not reached yet if (usedInputCount < effect->maximalInputCount()) { createInput(position); position.ry() += 1.5 * ConnectorSize.height(); } // create output connector position.ry() += 0.5 * ConnectorSize.height(); createOutput(position, effect->output()); setRect(0, 0, ItemWidth, position.y() + ConnectorSize.height()); createText(effect->id()); QLinearGradient g(QPointF(0, 0), QPointF(1, 1)); g.setCoordinateMode(QGradient::ObjectBoundingMode); g.setColorAt(0, Qt::white); g.setColorAt(1, QColor(0, 192, 192)); setBrush(QBrush(g)); } ConnectionItem::ConnectionItem(EffectItemBase *source, EffectItemBase * target, int targetInput) : QGraphicsPathItem(0), m_source(source), m_target(target), m_targetInput(targetInput) { - setPen(QPen(Qt::black)); + setPen(QPen(Qt::black, 0)); } EffectItemBase * ConnectionItem::sourceItem() const { return m_source; } EffectItemBase * ConnectionItem::targetItem() const { return m_target; } int ConnectionItem::targetInput() const { return m_targetInput; } void ConnectionItem::setSourceItem(EffectItemBase * source) { m_source = source; } void ConnectionItem::setTargetItem(EffectItemBase * target, int targetInput) { m_target = target; m_targetInput = targetInput; } diff --git a/plugins/karbonplugins/tools/filterEffectTool/FilterRegionEditStrategy.cpp b/plugins/karbonplugins/tools/filterEffectTool/FilterRegionEditStrategy.cpp index 8d316685ced..1e66b148407 100644 --- a/plugins/karbonplugins/tools/filterEffectTool/FilterRegionEditStrategy.cpp +++ b/plugins/karbonplugins/tools/filterEffectTool/FilterRegionEditStrategy.cpp @@ -1,94 +1,94 @@ /* This file is part of the KDE project * Copyright (c) 2010 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "FilterRegionEditStrategy.h" #include "FilterRegionChangeCommand.h" #include #include #include #include FilterRegionEditStrategy::FilterRegionEditStrategy(KoToolBase* parent, KoShape * shape, KoFilterEffect *effect, KarbonFilterEffectsTool::EditMode mode) : KoInteractionStrategy(parent), m_effect(effect), m_shape(shape), m_editMode(mode) { Q_ASSERT(m_effect); Q_ASSERT(m_shape); // get the size rect of the shape m_sizeRect = QRectF(QPointF(), m_shape->size()); // get the filter rectangle in shape coordinates m_filterRect = m_effect->filterRectForBoundingRect(m_sizeRect); } void FilterRegionEditStrategy::handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); QPointF shapePoint = m_shape->documentToShape(mouseLocation); if (m_lastPosition.isNull()) { m_lastPosition = shapePoint; } QPointF delta = shapePoint-m_lastPosition; if( delta.isNull()) return; switch(m_editMode) { case KarbonFilterEffectsTool::MoveAll: m_filterRect.translate(delta.x(), delta.y()); break; case KarbonFilterEffectsTool::MoveLeft: m_filterRect.setLeft(m_filterRect.left()+delta.x()); break; case KarbonFilterEffectsTool::MoveRight: m_filterRect.setRight(m_filterRect.right()+delta.x()); break; case KarbonFilterEffectsTool::MoveTop: m_filterRect.setTop(m_filterRect.top()+delta.y()); break; case KarbonFilterEffectsTool::MoveBottom: m_filterRect.setBottom(m_filterRect.bottom()+delta.y()); break; default: // nothing to do here return; } tool()->repaintDecorations(); m_lastPosition = shapePoint; } KUndo2Command *FilterRegionEditStrategy::createCommand() { qreal x = m_filterRect.left() / m_sizeRect.width(); qreal y = m_filterRect.top() / m_sizeRect.height(); qreal w = m_filterRect.width() / m_sizeRect.width(); qreal h = m_filterRect.height() / m_sizeRect.height(); return new FilterRegionChangeCommand(m_effect, QRectF(x,y,w,h), m_shape); } void FilterRegionEditStrategy::finishInteraction(Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); } void FilterRegionEditStrategy::paint(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(converter); // paint the filter subregion rect painter.setBrush(Qt::NoBrush); - painter.setPen(Qt::red); + painter.setPen(QPen(Qt::red, 0)); painter.drawRect(m_filterRect); } diff --git a/plugins/karbonplugins/tools/filterEffectTool/KarbonFilterEffectsTool.cpp b/plugins/karbonplugins/tools/filterEffectTool/KarbonFilterEffectsTool.cpp index ecebdd9cf2a..d8af5cb71ac 100644 --- a/plugins/karbonplugins/tools/filterEffectTool/KarbonFilterEffectsTool.cpp +++ b/plugins/karbonplugins/tools/filterEffectTool/KarbonFilterEffectsTool.cpp @@ -1,527 +1,527 @@ /* This file is part of the KDE project * Copyright (c) 2009-2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KarbonFilterEffectsTool.h" #include "KoFilterEffect.h" #include "KoFilterEffectStack.h" #include "KoFilterEffectFactoryBase.h" #include "KoFilterEffectRegistry.h" #include "KoFilterEffectConfigWidgetBase.h" #include "KoCanvasBase.h" #include "KoDocumentResourceManager.h" #include "KoShapeManager.h" #include "KoViewConverter.h" #include "KoSelection.h" #include "FilterEffectEditWidget.h" #include "FilterEffectResource.h" #include "FilterResourceServerProvider.h" #include "FilterStackSetCommand.h" #include "FilterRegionChangeCommand.h" #include "FilterRegionEditStrategy.h" #include "KoResourceServerAdapter.h" #include "KoResourceSelector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KarbonFilterEffectsTool::Private { public: Private() : filterSelector(0), configSelector(0) , configStack(0), posX(0), posY(0), posW(0), posH(0) , clearButton(0) , currentEffect(0), currentPanel(0), currentShape(0) { } void fillConfigSelector(KoShape *shape, KarbonFilterEffectsTool * tool) { if (!configSelector) return; configSelector->clear(); clearButton->setEnabled(false); if (!shape || !shape->filterEffectStack()) { addWidgetForEffect(0, tool); return; } configSelector->blockSignals(true); int index = 0; foreach(KoFilterEffect *effect, shape->filterEffectStack()->filterEffects()) { configSelector->addItem(QString("%1 - ").arg(index) + effect->name()); index++; } configSelector->blockSignals(false); KoFilterEffect * effect = index > 0 ? shape->filterEffectStack()->filterEffects().first() : 0; addWidgetForEffect(effect, tool); clearButton->setEnabled(shape->filterEffectStack() != 0); } void addWidgetForEffect(KoFilterEffect * filterEffect, KarbonFilterEffectsTool * tool) { // remove current widget if new effect is zero or effect type has changed if (!filterEffect || (currentEffect && filterEffect->id() != currentEffect->id())) { while (configStack->count()) configStack->removeWidget(configStack->widget(0)); } if (! filterEffect) { currentEffect = 0; currentPanel = 0; } else if (!currentEffect || currentEffect->id() != filterEffect->id()) { // when a effect is set and is differs from the previous one // get the config widget and insert it into the option widget currentEffect = filterEffect; KoFilterEffectRegistry * registry = KoFilterEffectRegistry::instance(); KoFilterEffectFactoryBase * factory = registry->value(currentEffect->id()); if (!factory) return; currentPanel = factory->createConfigWidget(); if (! currentPanel) return; currentPanel->layout()->setContentsMargins(0, 0, 0, 0); configStack->insertWidget(0, currentPanel); configStack->layout()->setContentsMargins(0, 0, 0, 0); connect(currentPanel, SIGNAL(filterChanged()), tool, SLOT(filterChanged())); } if (currentPanel) currentPanel->editFilterEffect(filterEffect); updateFilterRegion(); } void updateFilterRegion() { QRectF region = currentEffect ? currentEffect->filterRect() : QRectF(0, 0, 0, 0); posX->blockSignals(true); posX->setValue(100.0*region.x()); posX->blockSignals(false); posX->setEnabled(currentEffect != 0); posY->blockSignals(true); posY->setValue(100.0*region.y()); posY->blockSignals(false); posY->setEnabled(currentEffect != 0); posW->blockSignals(true); posW->setValue(100.0*region.width()); posW->blockSignals(false); posW->setEnabled(currentEffect != 0); posH->blockSignals(true); posH->setValue(100.0*region.height()); posH->blockSignals(false); posH->setEnabled(currentEffect != 0); } EditMode editModeFromMousePosition(const QPointF &mousePosition, KarbonFilterEffectsTool * tool) { if (currentShape && currentShape->filterEffectStack() && currentEffect) { // get the size rect of the shape QRectF sizeRect(QPointF(), currentShape->size()); // get the filter rectangle in shape coordinates QRectF filterRect = currentEffect->filterRectForBoundingRect(sizeRect); // get the transformation from document to shape coordinates QTransform transform = currentShape->absoluteTransformation(0).inverted(); // adjust filter rectangle by grab sensitivity const int grabDistance = tool->grabSensitivity(); QPointF border = tool->canvas()->viewConverter()->viewToDocument(QPointF(grabDistance, grabDistance)); filterRect.adjust(-border.x(), -border.y(), border.x(), border.y()); // map event point from document to shape coordinates QPointF shapePoint = transform.map(mousePosition); // check if the mouse is inside/near our filter rect if (filterRect.contains(shapePoint)) { if (qAbs(shapePoint.x() - filterRect.left()) <= border.x()) { return MoveLeft; } else if (qAbs(shapePoint.x() - filterRect.right()) <= border.x()) { return MoveRight; } else if (qAbs(shapePoint.y() - filterRect.top()) <= border.y()) { return MoveTop; } else if (qAbs(shapePoint.y() - filterRect.bottom()) <= border.y()) { return MoveBottom; } else { return MoveAll; } } else { return None; } } return None; } KoResourceSelector * filterSelector; KComboBox * configSelector; QStackedWidget * configStack; QDoubleSpinBox * posX; QDoubleSpinBox * posY; QDoubleSpinBox * posW; QDoubleSpinBox * posH; QToolButton *clearButton; KoFilterEffect * currentEffect; KoFilterEffectConfigWidgetBase * currentPanel; KoShape * currentShape; }; KarbonFilterEffectsTool::KarbonFilterEffectsTool(KoCanvasBase *canvas) : KoInteractionTool(canvas), d(new Private()) { connect(canvas->shapeManager(), SIGNAL(selectionChanged()), this, SLOT(selectionChanged())); connect(canvas->shapeManager(), SIGNAL(selectionContentChanged()), this, SLOT(selectionChanged())); } KarbonFilterEffectsTool::~KarbonFilterEffectsTool() { delete d; } void KarbonFilterEffectsTool::paint(QPainter &painter, const KoViewConverter &converter) { if (d->currentShape && d->currentShape->filterEffectStack()) { painter.save(); // apply the shape transformation QTransform transform = d->currentShape->absoluteTransformation(&converter); painter.setTransform(transform, true); // apply the zoom transformation KoShape::applyConversion(painter, converter); // get the size rect of the shape QRectF sizeRect(QPointF(), d->currentShape->size()); // get the clipping rect of the filter stack KoFilterEffectStack * filterStack = d->currentShape->filterEffectStack(); QRectF clipRect = filterStack->clipRectForBoundingRect(sizeRect); // finally paint the clipping rect painter.setBrush(Qt::NoBrush); - painter.setPen(Qt::blue); + painter.setPen(QPen(Qt::blue, 0)); painter.drawRect(clipRect); if (currentStrategy()) { currentStrategy()->paint(painter, converter); } else if (d->currentEffect) { QRectF filterRect = d->currentEffect->filterRectForBoundingRect(sizeRect); // paint the filter subregion rect painter.setBrush(Qt::NoBrush); - painter.setPen(Qt::red); + painter.setPen(QPen(Qt::red, 0)); painter.drawRect(filterRect); } painter.restore(); } } void KarbonFilterEffectsTool::repaintDecorations() { if (d->currentShape && d->currentShape->filterEffectStack()) { QRectF bb = d->currentShape->boundingRect(); const int radius = handleRadius(); canvas()->updateCanvas(bb.adjusted(-radius, -radius, radius, radius)); } } void KarbonFilterEffectsTool::activate(ToolActivation toolActivation, const QSet &shapes) { Q_UNUSED(toolActivation); if (shapes.isEmpty()) { emit done(); return; } d->currentShape = canvas()->shapeManager()->selection()->firstSelectedShape(KoFlake::TopLevelSelection); d->fillConfigSelector(d->currentShape, this); } void KarbonFilterEffectsTool::mouseMoveEvent(KoPointerEvent *event) { if (currentStrategy()) { KoInteractionTool::mouseMoveEvent(event); } else { EditMode mode = d->editModeFromMousePosition(event->point, this); switch(mode) { case MoveAll: useCursor(Qt::SizeAllCursor); break; case MoveLeft: case MoveRight: useCursor(Qt::SizeHorCursor); break; case MoveTop: case MoveBottom: useCursor(Qt::SizeVerCursor); break; case None: useCursor(Qt::ArrowCursor); break; } } } KoInteractionStrategy *KarbonFilterEffectsTool::createStrategy(KoPointerEvent *event) { EditMode mode = d->editModeFromMousePosition(event->point, this); if (mode == None) return 0; return new FilterRegionEditStrategy(this, d->currentShape, d->currentEffect, mode); } void KarbonFilterEffectsTool::presetSelected(KoResource *resource) { if (!d->currentShape) return; FilterEffectResource * effectResource = dynamic_cast(resource); if (!effectResource) return; KoFilterEffectStack * filterStack = effectResource->toFilterStack(); if (!filterStack) return; canvas()->addCommand(new FilterStackSetCommand(filterStack, d->currentShape)); d->fillConfigSelector(d->currentShape, this); } void KarbonFilterEffectsTool::editFilter() { QPointer dlg = new QDialog(); dlg->setWindowTitle(i18n("Filter Effect Editor")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); QWidget *mainWidget = new QWidget(0); QVBoxLayout *mainLayout = new QVBoxLayout; dlg->setLayout(mainLayout); mainLayout->addWidget(mainWidget); connect(buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, dlg.data(), &QDialog::close); FilterEffectEditWidget * editor = new FilterEffectEditWidget(dlg); editor->editShape(d->currentShape, canvas()); mainLayout->addWidget(editor); mainLayout->addWidget(buttonBox); dlg->exec(); delete dlg; d->fillConfigSelector(d->currentShape, this); } void KarbonFilterEffectsTool::clearFilter() { if (!d->currentShape) return; if (!d->currentShape->filterEffectStack()) return; canvas()->addCommand(new FilterStackSetCommand(0, d->currentShape)); d->fillConfigSelector(d->currentShape, this); } void KarbonFilterEffectsTool::filterChanged() { if (! d->currentShape) return; d->currentShape->update(); } void KarbonFilterEffectsTool::filterSelected(int index) { if (! d->currentShape || ! d->currentShape->filterEffectStack()) return; KoFilterEffect * effect = 0; QList filterEffects = d->currentShape->filterEffectStack()->filterEffects(); if (index >= 0 && index < filterEffects.count()) { effect = filterEffects[index]; } d->addWidgetForEffect(effect, this); repaintDecorations(); } void KarbonFilterEffectsTool::selectionChanged() { d->currentShape = canvas()->shapeManager()->selection()->firstSelectedShape(KoFlake::TopLevelSelection); d->fillConfigSelector(d->currentShape, this); } void KarbonFilterEffectsTool::regionXChanged(double x) { if (!d->currentEffect) return; QRectF region = d->currentEffect->filterRect(); region.setX(x / 100.0); canvas()->addCommand(new FilterRegionChangeCommand(d->currentEffect, region, d->currentShape)); } void KarbonFilterEffectsTool::regionYChanged(double y) { if (!d->currentEffect) return; QRectF region = d->currentEffect->filterRect(); region.setY(y / 100.0); canvas()->addCommand(new FilterRegionChangeCommand(d->currentEffect, region, d->currentShape)); } void KarbonFilterEffectsTool::regionWidthChanged(double width) { if (!d->currentEffect) return; QRectF region = d->currentEffect->filterRect(); region.setWidth(width / 100.0); canvas()->addCommand(new FilterRegionChangeCommand(d->currentEffect, region, d->currentShape)); } void KarbonFilterEffectsTool::regionHeightChanged(double height) { if (!d->currentEffect) return; QRectF region = d->currentEffect->filterRect(); region.setHeight(height / 100.0); canvas()->addCommand(new FilterRegionChangeCommand(d->currentEffect, region, d->currentShape)); } QList > KarbonFilterEffectsTool::createOptionWidgets() { QList > widgets; FilterResourceServerProvider * serverProvider = FilterResourceServerProvider::instance(); KoResourceServer * server = serverProvider->filterEffectServer(); QSharedPointer adapter(new KoResourceServerAdapter(server)); //--------------------------------------------------------------------- QWidget * addFilterWidget = new QWidget(); addFilterWidget->setObjectName("AddEffect"); QGridLayout * addFilterLayout = new QGridLayout(addFilterWidget); d->filterSelector = new KoResourceSelector(addFilterWidget); d->filterSelector->setResourceAdapter(adapter); d->filterSelector->setDisplayMode(KoResourceSelector::TextMode); d->filterSelector->setColumnCount(1); addFilterLayout->addWidget(new QLabel(i18n("Effects"), addFilterWidget), 0, 0); addFilterLayout->addWidget(d->filterSelector, 0, 1); connect(d->filterSelector, SIGNAL(resourceSelected(KoResource*)), this, SLOT(presetSelected(KoResource*))); connect(d->filterSelector, SIGNAL(resourceApplied(KoResource*)), this, SLOT(presetSelected(KoResource*))); QToolButton * editButton = new QToolButton(addFilterWidget); editButton->setIcon(koIcon("view-filter")); editButton->setToolTip(i18n("View and edit filter")); addFilterLayout->addWidget(editButton, 0, 2); connect(editButton, SIGNAL(clicked()), this, SLOT(editFilter())); d->clearButton = new QToolButton(addFilterWidget); d->clearButton->setIcon(koIcon("edit-delete")); d->clearButton->setToolTip(i18n("Remove filter from object")); addFilterLayout->addWidget(d->clearButton, 0, 3); connect(d->clearButton, SIGNAL(clicked()), this, SLOT(clearFilter())); addFilterWidget->setWindowTitle(i18n("Add Filter")); widgets.append(addFilterWidget); //--------------------------------------------------------------------- QWidget * configFilterWidget = new QWidget(); configFilterWidget->setObjectName("ConfigEffect"); QGridLayout * configFilterLayout = new QGridLayout(configFilterWidget); d->configSelector = new KComboBox(configFilterWidget); configFilterLayout->addWidget(d->configSelector, 0, 0); connect(d->configSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(filterSelected(int))); d->configStack = new QStackedWidget(configFilterWidget); configFilterLayout->addWidget(d->configStack, 1, 0); configFilterLayout->setContentsMargins(0, 0, 0, 0); configFilterWidget->setWindowTitle(i18n("Effect Properties")); widgets.append(configFilterWidget); //--------------------------------------------------------------------- QWidget * filterRegionWidget = new QWidget(); filterRegionWidget->setObjectName("EffectRegion"); QGridLayout * filterRegionLayout = new QGridLayout(filterRegionWidget); d->posX = new QDoubleSpinBox(filterRegionWidget); d->posX->setSuffix("%"); connect(d->posX, SIGNAL(valueChanged(double)), this, SLOT(regionXChanged(double))); filterRegionLayout->addWidget(new QLabel(i18n("X:")), 0, 0); filterRegionLayout->addWidget(d->posX, 0, 1); d->posY = new QDoubleSpinBox(filterRegionWidget); d->posY->setSuffix("%"); connect(d->posY, SIGNAL(valueChanged(double)), this, SLOT(regionYChanged(double))); filterRegionLayout->addWidget(new QLabel(i18n("Y:")), 1, 0); filterRegionLayout->addWidget(d->posY, 1, 1); d->posW = new QDoubleSpinBox(filterRegionWidget); d->posW->setSuffix("%"); connect(d->posW, SIGNAL(valueChanged(double)), this, SLOT(regionWidthChanged(double))); filterRegionLayout->addWidget(new QLabel(i18n("W:")), 0, 2); filterRegionLayout->addWidget(d->posW, 0, 3); d->posH = new QDoubleSpinBox(filterRegionWidget); d->posH->setSuffix("%"); connect(d->posH, SIGNAL(valueChanged(double)), this, SLOT(regionHeightChanged(double))); filterRegionLayout->addWidget(new QLabel(i18n("H:")), 1, 2); filterRegionLayout->addWidget(d->posH, 1, 3); filterRegionLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding), 2, 0); filterRegionLayout->setContentsMargins(0, 0, 0, 0); filterRegionWidget->setWindowTitle(i18n("Effect Region")); widgets.append(filterRegionWidget); //--------------------------------------------------------------------- d->fillConfigSelector(d->currentShape, this); return widgets; } diff --git a/plugins/musicshape/MusicStyle.cpp b/plugins/musicshape/MusicStyle.cpp index 7540de52dcf..ed52e11580d 100644 --- a/plugins/musicshape/MusicStyle.cpp +++ b/plugins/musicshape/MusicStyle.cpp @@ -1,236 +1,236 @@ /* This file is part of the KDE project * Copyright (C) 2007 Marijn Kruisselbrink * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "MusicStyle.h" using namespace MusicCore; MusicStyle::MusicStyle() #ifdef Q_WS_MAC : m_font("Emmentaler 14") #else : m_font("Emmentaler") #endif , m_textAsPath(false) { m_font.setPixelSize(20); m_staffLinePen.setWidthF(0.5); m_staffLinePen.setCapStyle(Qt::RoundCap); m_staffLinePen.setColor(Qt::black); m_stemPen.setWidthF(0.7); m_stemPen.setCapStyle(Qt::FlatCap); m_stemPen.setColor(Qt::black); m_noteDotPen.setWidthF(1.9); m_noteDotPen.setCapStyle(Qt::RoundCap); m_noteDotPen.setColor(Qt::black); } MusicStyle::~MusicStyle() { } QPen MusicStyle::staffLinePen(const QColor& color) { m_staffLinePen.setColor(color); return m_staffLinePen; } QPen MusicStyle::stemPen(const QColor& color) { m_stemPen.setColor(color); return m_stemPen; } QPen MusicStyle::noteDotPen(const QColor& color) { m_noteDotPen.setColor(color); return m_noteDotPen; } qreal MusicStyle::beamLineWidth() { return 3.0; } void MusicStyle::renderText(QPainter& painter, qreal x, qreal y, const QString& text) { QPointF p(x, y); if(m_textAsPath) { QPainterPath textPath; textPath.setFillRule(Qt::OddEvenFill); textPath.addText(p, m_font, text); painter.save(); painter.setBrush(painter.pen().brush().color()); painter.drawPath(textPath); painter.restore(); } else { painter.drawText(p, text); } } bool MusicStyle::textAsPath() const { return m_textAsPath; } void MusicStyle::setTextAsPath(bool drawTextAsPath) { m_textAsPath = drawTextAsPath; } void MusicStyle::renderNoteHead(QPainter& painter, qreal x, qreal y, Duration duration, const QColor& color) { - painter.setPen(QPen(color)); + painter.setPen(QPen(color, 0)); painter.setFont(m_font); switch (duration) { case HundredTwentyEighthNote: case SixtyFourthNote: case ThirtySecondNote: case SixteenthNote: case EighthNote: case QuarterNote: renderText(painter, x, y, QString(0xE125)); break; case HalfNote: renderText(painter, x, y, QString(0xE124)); break; case WholeNote: renderText(painter, x, y, QString(0xE123)); break; case BreveNote: renderText(painter, x, y, QString(0xE122)); break; } } void MusicStyle::renderRest(QPainter& painter, qreal x, qreal y, Duration duration, const QColor& color) { - painter.setPen(QPen(color)); + painter.setPen(QPen(color, 0)); painter.setFont(m_font); QPointF p(x, y); switch (duration) { case HundredTwentyEighthNote: renderText(painter, x, y, QString(0xE10D)); break; case SixtyFourthNote: renderText(painter, x, y, QString(0xE10C)); break; case ThirtySecondNote: renderText(painter, x, y, QString(0xE10B)); break; case SixteenthNote: renderText(painter, x, y, QString(0xE10A)); break; case EighthNote: renderText(painter, x, y, QString(0xE109)); break; case QuarterNote: renderText(painter, x, y, QString(0xE107)); break; case HalfNote: renderText(painter, x, y, QString(0xE101)); break; case WholeNote: renderText(painter, x, y, QString(0xE100)); break; case BreveNote: renderText(painter, x, y, QString(0xE106)); break; } } void MusicStyle::renderClef(QPainter& painter, qreal x, qreal y, Clef::ClefShape shape, const QColor& color) { - painter.setPen(QPen(color)); + painter.setPen(QPen(color, 0)); painter.setFont(m_font); QPointF p(x, y); switch (shape) { case Clef::GClef: renderText(painter, x, y, QString(0xE195)); break; case Clef::FClef: renderText(painter, x, y, QString(0xE193)); break; case Clef::CClef: renderText(painter, x, y, QString(0xE191)); break; } } void MusicStyle::renderAccidental(QPainter& painter, qreal x, qreal y, int accidental, const QColor& color) { - painter.setPen(QPen(color)); + painter.setPen(QPen(color, 0)); painter.setFont(m_font); QPointF p(x, y); switch (accidental) { case 0: renderText(painter, x, y, QString(0xE111)); break; case 1: renderText(painter, x, y, QString(0xE10E)); break; case 2: renderText(painter, x, y, QString(0xE116)); break; case -1: renderText(painter, x, y, QString(0xE112)); break; case -2: renderText(painter, x, y, QString(0xE114)); break; } } void MusicStyle::renderTimeSignatureNumber(QPainter& painter, qreal x, qreal y, qreal w, int number, const QColor& color) { - painter.setPen(QPen(color)); + painter.setPen(QPen(color, 0)); painter.setFont(m_font); QFontMetricsF m(m_font); QString txt = QString::number(number); renderText(painter, x + (w - m.width(txt))/2, y, txt); } void MusicStyle::renderNoteFlags(QPainter& painter, qreal x, qreal y, Duration duration, bool stemsUp, const QColor& color) { - painter.setPen(QPen(color)); + painter.setPen(QPen(color, 0)); painter.setFont(m_font); QPointF p(x + 0.4, y); switch (duration) { case HundredTwentyEighthNote: // no 128 flag in emmentaler, so stack 16th and 32nd on top of each other... renderText(painter, x, y, QString(stemsUp ? 0xE189 : 0xE18F)); renderText(painter, p.x(), p.y() + (stemsUp ? 13 : -13), QString(stemsUp ? 0xE188 : 0xE18E)); break; case SixtyFourthNote: renderText(painter, x, y, QString(stemsUp ? 0xE18A : 0xE190)); break; case ThirtySecondNote: renderText(painter, x, y, QString(stemsUp ? 0xE189 : 0xE18F)); break; case SixteenthNote: renderText(painter, x, y, QString(stemsUp ? 0xE188 : 0xE18E)); break; case EighthNote: renderText(painter, x, y, QString(stemsUp ? 0xE187 : 0xE18B)); break; default: // no flags break; } } diff --git a/plugins/musicshape/Renderer.cpp b/plugins/musicshape/Renderer.cpp index 8c28f73265e..679de969b63 100644 --- a/plugins/musicshape/Renderer.cpp +++ b/plugins/musicshape/Renderer.cpp @@ -1,585 +1,585 @@ /* This file is part of the KDE project * Copyright (C) 2007 Marijn Kruisselbrink * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "Renderer.h" #include "MusicStyle.h" #include "core/Sheet.h" #include "core/Part.h" #include "core/Voice.h" #include "core/Staff.h" #include "core/VoiceBar.h" #include "core/Chord.h" #include "core/Note.h" #include "core/Clef.h" #include "core/Bar.h" #include "core/KeySignature.h" #include "core/TimeSignature.h" #include "core/StaffSystem.h" #include #include using namespace MusicCore; MusicRenderer::MusicRenderer(MusicStyle* style) : m_style(style), m_debug(false) { } void MusicRenderer::renderSheet(QPainter& painter, Sheet* sheet, int firstSystem, int lastSystem) { int firstBar = sheet->staffSystem(firstSystem)->firstBar(); int lastBar = INT_MAX; if (lastSystem < sheet->staffSystemCount()-1) { lastBar = sheet->staffSystem(lastSystem+1)->firstBar()-1; } for (int i = 0; i < sheet->partCount(); i++) { renderPart(painter, sheet->part(i), firstBar, lastBar); } for (int i = firstSystem; i <= lastSystem && i < sheet->staffSystemCount(); i++) { StaffSystem* ss = sheet->staffSystem(i); if (ss->indent() == 0) continue; int b = ss->firstBar(); Bar* bar = sheet->bar(b); qreal by = bar->position().y(); qreal ind = ss->indent(); for (int p = 0; p < sheet->partCount(); p++) { Part* part = sheet->part(p); for (int s = 0; s < part->staffCount(); s++) { Staff* staff = part->staff(s); qreal y = staff->top(); qreal dy = staff->lineSpacing(); painter.setPen(m_style->staffLinePen()); for (int l = 0; l < staff->lineCount(); l++) { painter.drawLine(QPointF(0, by + y + l * dy), QPointF(ind, by + y + l * dy)); } Clef* clef = ss->clef(staff); RenderState foo; qreal x = 15; if (clef) { renderClef(painter, clef, QPointF(x, by), foo, Qt::black, true); x += clef->width() + 15; } KeySignature* ks = staff->lastKeySignatureChange(b); if (ks) { renderKeySignature(painter, ks, QPointF(x, by), foo, Qt::black, true); } } } } } void MusicRenderer::renderPart(QPainter& painter, Part* part, int firstBar, int lastBar, const QColor& color) { if (lastBar < firstBar) return; for (int i = 0; i < part->staffCount(); i++) { renderStaff(painter, part->staff(i), firstBar, lastBar, color); } qreal firstStaff = part->staff(0)->top(); int c = part->staffCount()-1; qreal lastStaff = part->staff(c)->bottom(); for (int b = firstBar; b <= lastBar && b < part->sheet()->barCount(); b++) { Bar* bar = part->sheet()->bar(b); QPointF p = bar->position(); painter.drawLine(QPointF(p.x() + bar->size(), p.y() + firstStaff), QPointF(p.x() + bar->size(), p.y() + lastStaff)); if (m_debug) { - painter.setPen(QPen(Qt::green)); + painter.setPen(QPen(Qt::green, 0)); painter.drawLine(QPointF(p.x(), p.y() + firstStaff - 3), QPointF(p.x(), p.y() + lastStaff + 3)); painter.drawLine(QPointF(p.x() - bar->prefix(), p.y() + firstStaff - 3), QPointF(p.x() - bar->prefix(), p.y() + lastStaff + 3)); } // check if the bar contains any elements, if not render a rest bool hasContents = false; for (int v = 0; v < part->voiceCount(); v++) { if (part->voice(v)->bar(bar)->elementCount() > 0) { hasContents = true; break; } } if (!hasContents) { QPointF pos = bar->position(); qreal w = bar->size(); for (int sid = 0; sid < part->staffCount(); sid++) { Staff* s = part->staff(sid); renderRest(painter, WholeNote, pos + QPointF(w/2, s->top() + s->lineSpacing()), color); } } } for (int i = 0; i < part->voiceCount(); i++) { renderVoice(painter, part->voice(i), firstBar, lastBar, color); } } void MusicRenderer::renderStaff(QPainter& painter, Staff *staff, int firstBar, int lastBar, const QColor& color) { qreal dy = staff->lineSpacing(); qreal y = staff->top(); for (int b = firstBar; b <= lastBar && b < staff->part()->sheet()->barCount(); b++) { Bar* bar = staff->part()->sheet()->bar(b); QPointF p = bar->position(); QPointF prep = bar->prefixPosition() + QPointF(bar->prefix(), 0); painter.setPen(m_style->staffLinePen(color)); for (int i = 0; i < staff->lineCount(); i++) { painter.drawLine(QPointF(p.x(), p.y() + y + i * dy), QPointF(p.x() + bar->size(), p.y() + y + i * dy)); } if (bar->prefix() > 0) { QPointF q = bar->prefixPosition(); for (int i = 0; i < staff->lineCount(); i++) { painter.drawLine(QPointF(q.x(), q.y() + y + i * dy), QPointF(q.x() + bar->prefix(), q.y() + y + i * dy)); } } RenderState state; for (int e = 0; e < bar->staffElementCount(staff); e++) { StaffElement* se = bar->staffElement(staff, e); if (se->startTime() == 0) { renderStaffElement(painter, bar->staffElement(staff, e), prep, state, color); } else { renderStaffElement(painter, bar->staffElement(staff, e), p, state, color); } } } } void MusicRenderer::renderVoice(QPainter& painter, Voice *voice, int firstBar, int lastBar, const QColor& color) { RenderState state; state.clef = 0; for (int b = firstBar; b <= lastBar && b < voice->part()->sheet()->barCount(); b++) { Bar* bar = voice->part()->sheet()->bar(b); QPointF p = bar->position(); VoiceBar* vb = voice->bar(bar); for (int e = 0; e < vb->elementCount(); e++) { if (vb->element(e)->staff()) { state.clef = vb->element(e)->staff()->lastClefChange(b, 0); } renderElement(painter, vb->element(e), voice, p, state, color); } } } void MusicRenderer::renderElement(QPainter& painter, VoiceElement* me, Voice* voice, const QPointF& pos, RenderState& state, const QColor& color) { Q_UNUSED( state ); // unused for now, but will probably be used again in the future qreal top = 0; if (me->staff()) top += me->staff()->top(); if (m_debug) { - painter.setPen(QPen(Qt::blue)); + painter.setPen(QPen(Qt::blue, 0)); painter.drawLine(pos + QPointF(me->x(), top + me->y() - 4), pos + QPointF(me->x(), top + me->y() + me->height() + 4)); painter.drawLine(pos + QPointF(me->x() + me->width(), top + me->y() - 4), pos + QPointF(me->x() + me->width(), top + me->y() + me->height() + 4)); painter.drawLine(pos + QPointF(me->x() - 4, top + me->y()), pos + QPointF(me->x() + me->width() + 4, top + me->y())); painter.drawLine(pos + QPointF(me->x() - 4, top + me->y() + me->height()), pos + QPointF(me->x() + me->width() + 4, top + me->y() + me->height())); - painter.setPen(QPen(Qt::red)); + painter.setPen(QPen(Qt::red, 0)); painter.drawLine(pos + QPointF(me->x() + me->beatline(), top + me->y() - 10), pos + QPointF(me->x() + me->beatline(), top + me->y() + me->height() + 10)); } // TODO: make this less hacky Chord *c = dynamic_cast(me); if (c) renderChord(painter, c, voice, pos, color); } void MusicRenderer::renderStaffElement(QPainter& painter, MusicCore::StaffElement* se, const QPointF& pos, RenderState& state, const QColor& color) { qreal top = 0; top += se->staff()->top(); if (m_debug) { - painter.setPen(QPen(Qt::blue)); + painter.setPen(QPen(Qt::blue, 0)); painter.drawLine(pos + QPointF(se->x(), top + se->y() - 20), pos + QPointF(se->x(), top + se->y() + 20)); painter.drawLine(pos + QPointF(se->x() + se->width(), top + se->y() - 20), pos + QPointF(se->x() + se->width(), top + se->y() + 20)); painter.drawLine(pos + QPointF(se->x() - 10, top + se->y()), pos + QPointF(se->x() + se->width() + 10, top + se->y())); painter.drawLine(pos + QPointF(se->x() - 10, top + se->y() + se->height()), pos + QPointF(se->x() + se->width() + 10, top + se->y() + se->height())); } Clef *cl = dynamic_cast(se); if (cl) renderClef(painter, cl, pos, state, color); KeySignature *ks = dynamic_cast(se); if (ks) renderKeySignature(painter, ks, pos, state, color); TimeSignature* ts = dynamic_cast(se); if (ts) renderTimeSignature(painter, ts, pos, color); } void MusicRenderer::renderClef(QPainter& painter, Clef *c, const QPointF& pos, RenderState& state, const QColor& color, bool ignoreOwnPos) { Q_UNUSED(color); state.clef = c; Staff* s = c->staff(); m_style->renderClef(painter, pos.x() + (ignoreOwnPos ? 0 : c->x()), pos.y() + s->top() + (s->lineCount() - c->line()) * s->lineSpacing(), c->shape()); } void MusicRenderer::renderKeySignature(QPainter& painter, KeySignature* ks, const QPointF& pos, RenderState& state, const QColor& color, bool ignoreOwnPos) { Q_UNUSED(color); Staff * s = ks->staff(); qreal curx = pos.x() + (ignoreOwnPos ? 0 : ks->x()); // draw naturals for sharps int idx = 3; for (int i = 0; i < 7; i++) { if (ks->cancel(idx) > 0) { int line = 10; if (state.clef) line = state.clef->pitchToLine(idx); while (line < 0) line += 7; while (line >= 6) line -= 7; m_style->renderAccidental( painter, curx, pos.y() + s->top() + line * s->lineSpacing() / 2, 0 ); curx += 6; } idx = (idx + 4) % 7; } // draw naturals for flats idx = 6; for (int i = 0; i < 7; i++) { if (ks->cancel(idx) < 0) { int line = 10; if (state.clef) line = state.clef->pitchToLine(idx); while (line < 0) line += 7; while (line >= 6) line -= 7; m_style->renderAccidental( painter, curx, pos.y() + s->top() + line * s->lineSpacing() / 2, 0 ); curx += 6; } idx = (idx + 3) % 7; } // draw sharps idx = 3; for (int i = 0; i < 7; i++) { if (ks->accidentals(idx) > 0) { int line = 10; if (state.clef) line = state.clef->pitchToLine(idx); while (line < 0) line += 7; while (line >= 6) line -= 7; m_style->renderAccidental( painter, curx, pos.y() + s->top() + line * s->lineSpacing() / 2, 1 ); curx += 6; } idx = (idx + 4) % 7; } // draw flats idx = 6; for (int i = 0; i < 7; i++) { if (ks->accidentals(idx) < 0) { int line = 10; if (state.clef) line = state.clef->pitchToLine(idx); while (line < 0) line += 7; while (line >= 6) line -= 7; m_style->renderAccidental( painter, curx, pos.y() + s->top() + line * s->lineSpacing() / 2, -1 ); curx += 6; } idx = (idx + 3) % 7; } } void MusicRenderer::renderTimeSignature(QPainter& painter, TimeSignature* ts, const QPointF& pos, const QColor& color) { Q_UNUSED(color); Staff* s = ts->staff(); qreal hh = 0.5 * (s->lineCount() - 1) * s->lineSpacing(); m_style->renderTimeSignatureNumber( painter, pos.x() + ts->x(), pos.y() + s->top() + hh, ts->width(), ts->beats()); m_style->renderTimeSignatureNumber( painter, pos.x() + ts->x(), pos.y() + s->top() + 2*hh, ts->width(), ts->beat()); } void MusicRenderer::renderRest(QPainter& painter, Duration duration, const QPointF& pos, const QColor& color) { m_style->renderRest(painter, pos.x(), pos.y(), duration, color); } void MusicRenderer::renderChord(QPainter& painter, Chord* chord, Voice* voice, const QPointF& ref, const QColor& color) { qreal x = chord->x(); if (chord->noteCount() == 0) { // a rest Staff *s = chord->staff(); renderRest(painter, chord->duration(), ref + QPointF(x, s->top() + (2 - (chord->duration() == WholeNote)) * s->lineSpacing()), color); return; } int topLine = 0, bottomLine = 0; VoiceBar* vb = chord->voiceBar(); Bar* bar = vb->bar(); Sheet* sheet = voice->part()->sheet(); int barIdx = bar->sheet()->indexOfBar(bar); qreal topy = 1e9, bottomy = -1e9; Staff* topStaff = 0, *bottomStaff = 0; qreal mainNoteX = (chord->stemDirection() == StemUp ? chord->stemX() - 6 : chord->stemX()); qreal alternateNoteX = mainNoteX + (chord->stemDirection() == StemUp ? 6 : -6); bool prevAlternate = false; qreal maxNoteX = 0; QMultiMap dots; Chord* nextChord = 0; for (int i = 0; i < chord->noteCount(); i++) { Note *n = chord->note(i); Staff * s = n->staff(); Clef* clef = s->lastClefChange(barIdx); int line = 10; if (clef) line = clef->pitchToLine(n->pitch()); qreal noteX = mainNoteX; if (i > 0) { int prevPitch = chord->note(i-1)->pitch(); if (abs(prevPitch - n->pitch()) <= 1 && !prevAlternate) { noteX = alternateNoteX; } } if (i < chord->noteCount()-1 && chord->stemDirection() == StemDown) { int pitch = n->pitch(); int nPitch = chord->note(i+1)->pitch(); if (abs(pitch - nPitch) <= 1 && !prevAlternate) { noteX = alternateNoteX; } } prevAlternate = noteX != mainNoteX; if (noteX > maxNoteX) maxNoteX = noteX; if (line > 9) { // lines under the bar painter.setPen(m_style->staffLinePen(color)); for (int i = 10; i <= line; i+= 2) { qreal y = s->top() + i * s->lineSpacing() / 2; painter.drawLine(ref + QPointF(noteX - 4, y), ref + QPointF(noteX + 10, y)); } } else if (line < -1) { // lines above the bar painter.setPen(m_style->staffLinePen(color)); for (int i = -2; i >= line; i-= 2) { qreal y = s->top() + i * s->lineSpacing() / 2; painter.drawLine(ref + QPointF(noteX - 4, y), ref + QPointF(noteX + 10, y)); } } qreal ypos = s->top() + line * s->lineSpacing() / 2; if (ypos < topy) { topy = ypos; topLine = line; topStaff = s; } if (ypos > bottomy) { bottomy = ypos; bottomLine = line; bottomStaff = s; } m_style->renderNoteHead( painter, ref.x() + noteX, ref.y() + s->top() + line * s->lineSpacing() / 2, chord->duration(), color ); // render accidentals if (n->drawAccidentals()) { m_style->renderAccidental( painter, ref.x() + x, ref.y() + /*chord->y() +*/ s->top() + line * s->lineSpacing() / 2, n->accidentals(), color ); } dots.insert(s, line); if (n->isStartTie()) { // render tie for this note... if (!nextChord) { // figure out what the next chord in this voice is bool afterCurrent = false; for (int e = 0; e < vb->elementCount(); e++) { if (afterCurrent) { nextChord = dynamic_cast(vb->element(e)); if (nextChord) break; } else { if (vb->element(e) == chord) { afterCurrent = true; } } } if (!nextChord) { // check the next bar int nextBar = sheet->indexOfBar(bar)+1; if (nextBar < sheet->barCount()) { VoiceBar* nextVB = voice->bar(nextBar); for (int e = 0; e < nextVB->elementCount(); e++) { nextChord = dynamic_cast(nextVB->element(e)); if (nextChord) break; } } } } // okay, now nextChord is the chord to which the tie should go if (nextChord) { QPointF startPos = bar->position() + QPointF(1 + chord->x() + chord->width(), ypos); QPointF endPos = nextChord->voiceBar()->bar()->position() + QPointF(nextChord->x() - 1, ypos); if (bar->position().y() < nextChord->voiceBar()->bar()->position().y() - 1e-6) { endPos = bar->position() + QPointF(bar->size(), 0); } endPos.setY(startPos.y()); QPointF c1a = startPos + QPointF(2, 4); QPointF c2a = endPos + QPointF(-2, 4); QPointF c1b = startPos + QPointF(2, 5); QPointF c2b = endPos + QPointF(-2, 5); QPainterPath p; p.moveTo(startPos); p.cubicTo(c1a, c2a, endPos); p.cubicTo(c2b, c1b, startPos); painter.setPen(Qt::NoPen);//m_style->slurPen(color)); painter.setBrush(QBrush(color)); painter.drawPath(p); } } } // calculate correct positioning of dots // render dots of notes painter.setPen(m_style->noteDotPen(color)); foreach (Staff* s, dots.keys()) { QList lines = dots.values(s); qSort(lines); int lastLine = INT_MIN; bool moveGroupDown = true; for (int i = 0; i < lines.size(); i++) { int line = lines[i]; if (line % 2 == 0) { line--; } if (line == lastLine) { if (moveGroupDown) { lines[i-1] += 2; for (int j = i-2; j >= 0; j--) { if (lines[j] == lines[j+1]) { lines[j] += 2; } else { break; } } } else { line -= 2; } moveGroupDown = !moveGroupDown; } lines[i] = line; lastLine = line; } foreach (int line, lines) { qreal dotX = maxNoteX + 11; for (int i = 0; i < chord->dots(); i++) { painter.drawPoint(ref + QPointF(dotX, s->top() + line * s->lineSpacing() / 2)); dotX += 3; } } } qreal stemLen = chord->stemLength() * 2; if (stemLen != 0.0 && stemLen != -0.0) { qreal stemX = chord->stemX(); bool stemsUp = chord->stemDirection() == StemUp; painter.setPen(m_style->stemPen(color)); if (stemsUp) { painter.drawLine(ref + QPointF(stemX, chord->stemEndY()), ref + QPointF(stemX, bottomStaff->top() + bottomLine * bottomStaff->lineSpacing() / 2)); if (chord->beamType(0) == BeamFlag) { m_style->renderNoteFlags( painter, ref.x() + stemX, ref.y() + chord->stemEndY(), chord->duration(), stemsUp, color ); } } else { painter.drawLine(ref + QPointF(stemX, topStaff->top() + topLine * topStaff->lineSpacing() / 2), ref + QPointF(stemX, chord->stemEndY())); if (chord->beamType(0) == BeamFlag) { m_style->renderNoteFlags( painter, ref.x() + stemX, ref.y() + chord->stemEndY(), chord->duration(), stemsUp, color ); } } painter.setPen(QPen(Qt::NoPen)); painter.setBrush(QBrush(color)); for (int i = 0; i < chord->beamCount(); i++) { if (chord->beamType(i) == BeamStart) { const Chord* endChord = chord->beamEnd(i); QPointF beamStart(chord->stemX(), chord->stemEndY()); QPointF beamEnd(endChord->stemX(), endChord->stemEndY()); if (stemsUp) { beamStart += QPointF(0, topStaff->lineSpacing() * i); beamEnd += QPointF(0, topStaff->lineSpacing() * i); } else { beamStart -= QPointF(0, bottomStaff->lineSpacing() * i); beamEnd -= QPointF(0, bottomStaff->lineSpacing() * i); } QPointF dir(0, (stemsUp ? 1 : -1) * m_style->beamLineWidth()); QPointF p[4]; p[0] = ref + beamStart; p[1] = ref + beamEnd; p[2] = p[1] + dir; p[3] = p[0] + dir; painter.drawConvexPolygon(p, 4); } else if (chord->beamType(i) == BeamForwardHook || chord->beamType(i) == BeamBackwardHook) { QPointF beamStart(chord->stemX(), chord->stemEndY()); qreal dir = 6; if (chord->beamType(i) == BeamBackwardHook) dir = -dir; if (stemsUp) { beamStart += QPointF(0, topStaff->lineSpacing() * i); } else { beamStart -= QPointF(0, bottomStaff->lineSpacing() * i); } QPointF beamEnd = beamStart + QPointF(dir, dir * chord->beamDirection()); QPointF bdir(0, (stemsUp ? 1 : -1) * m_style->beamLineWidth()); QPointF p[4]; p[0] = ref + beamStart; p[1] = ref + beamEnd; p[2] = p[1] + bdir; p[3] = p[0] + bdir; painter.drawConvexPolygon(p, 4); } } } } void MusicRenderer::renderNote(QPainter& painter, Duration duration, const QPointF& pos, qreal stemLength, const QColor& color) { m_style->renderNoteHead(painter, pos.x(), pos.y(), duration, color); if (duration <= HalfNote) { painter.setPen(m_style->stemPen(color)); painter.drawLine(pos + QPointF(6, -stemLength), pos + QPointF(6, 0)); } if (duration <= EighthNote) { m_style->renderNoteFlags(painter, pos.x()+6, pos.y() - stemLength, duration, true, color); } } void MusicRenderer::renderAccidental(QPainter& painter, int accidentals, const QPointF& pos, const QColor& color) { m_style->renderAccidental( painter, pos.x(), pos.y(), accidentals, color ); } diff --git a/plugins/pictureshape/CropWidget.cpp b/plugins/pictureshape/CropWidget.cpp index 60f006f3e55..f2e382d2e62 100644 --- a/plugins/pictureshape/CropWidget.cpp +++ b/plugins/pictureshape/CropWidget.cpp @@ -1,244 +1,244 @@ /* This file is part of the KDE project Copyright 2011 Silvio Heinrich This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "CropWidget.h" #include "PictureShape.h" #include "KoImageData.h" #include #include #include qreal calcScale(const QSizeF& imgSize, const QSizeF viewSize, bool fitView) { if (qFuzzyCompare(imgSize.width(), qreal(0)) || qFuzzyCompare(imgSize.height(), qreal(0)) || qFuzzyCompare(viewSize.width(), qreal(0)) || qFuzzyCompare(viewSize.height(), qreal(0))) { return 1; } qreal viewAspect = viewSize.width() / viewSize.height(); qreal imgAspect = imgSize.width() / imgSize.height(); if (fitView) { if (viewAspect > imgAspect) { return viewSize.height() / imgSize.height(); } else { return viewSize.width() / imgSize.width(); } } else { if (viewAspect > imgAspect) { return viewSize.width() / imgSize.width(); } else { return viewSize.height() / imgSize.height(); } } } QRectF centerRectHorizontally(const QRectF& rect, const QSizeF viewSize) { QSizeF diff = viewSize - rect.size(); return QRectF(diff.width() / 2.0, rect.y(), rect.width(), rect.height()); } bool compareRects(const QRectF &a, const QRectF &b, qreal epsilon) { qreal x = qAbs(a.x() - b.x()); qreal y = qAbs(a.y() - b.y()); qreal w = qAbs(a.width() - b.width()); qreal h = qAbs(a.height() - b.height()); return x <= epsilon && y <= epsilon && w <= epsilon && h <= epsilon; } // ---------------------------------------------------------------- // CropWidget::CropWidget(QWidget *parent): QWidget(parent) , m_pictureShape(0) , m_isMousePressed(false) , m_undoLast(false) { setMinimumSize(100, 100); setMouseTracking(true); } void CropWidget::paintEvent(QPaintEvent *event) { Q_UNUSED(event); if(!m_pictureShape || m_imageRect.isNull()) return; QPainter painter(this); QImage image = m_pictureShape->imageData()->image(); painter.translate(m_imageRect.topLeft()); painter.scale(m_imageRect.width(), m_imageRect.height()); painter.drawImage(QRectF(0, 0, 1, 1), image); painter.drawRect(m_selectionRect.getRect()); painter.setBrush(QBrush(Qt::yellow)); for (int i=0; iclipPath(); if (clipPath) { painter.scale(0.01, 0.01); // the path is defined in 100x100 equaling shapesize painter.setBrush(Qt::NoBrush); - painter.setPen(Qt::red); + painter.setPen(QPen(Qt::red, 0)); painter.drawPath(clipPath->path()); } } void CropWidget::mousePressEvent(QMouseEvent *event) { m_selectionRect.beginDragging(toUniformCoord(event->posF())); m_isMousePressed = true; } void CropWidget::mouseMoveEvent(QMouseEvent *event) { QPointF pos = toUniformCoord(event->posF()); SelectionRect::HandleFlags flags = m_selectionRect.getHandleFlags(pos); switch (flags) { case SelectionRect::TOP_HANDLE: case SelectionRect::BOTTOM_HANDLE: QWidget::setCursor(Qt::SizeVerCursor); break; case SelectionRect::LEFT_HANDLE: case SelectionRect::RIGHT_HANDLE: QWidget::setCursor(Qt::SizeHorCursor); break; case SelectionRect::LEFT_HANDLE|SelectionRect::TOP_HANDLE: case SelectionRect::RIGHT_HANDLE|SelectionRect::BOTTOM_HANDLE: QWidget::setCursor(Qt::SizeFDiagCursor); break; case SelectionRect::LEFT_HANDLE|SelectionRect::BOTTOM_HANDLE: case SelectionRect::RIGHT_HANDLE|SelectionRect::TOP_HANDLE: QWidget::setCursor(Qt::SizeBDiagCursor); break; case SelectionRect::INSIDE_RECT: QWidget::setCursor(Qt::SizeAllCursor); break; default: QWidget::setCursor(Qt::ArrowCursor); break; } if (m_isMousePressed) { m_selectionRect.doDragging(pos); update(); emitCropRegionChanged(); } } void CropWidget::mouseReleaseEvent(QMouseEvent *event) { Q_UNUSED(event); m_selectionRect.finishDragging(); m_isMousePressed = false; emitCropRegionChanged(); m_undoLast = false; // we are done dragging } void CropWidget::resizeEvent(QResizeEvent* event) { Q_UNUSED(event); calcImageRect(); } void CropWidget::setPictureShape(PictureShape *shape) { m_pictureShape = shape; calcImageRect(); m_oldSelectionRect = shape->cropRect(); m_selectionRect.setRect(shape->cropRect()); m_selectionRect.setConstrainingRect(QRectF(0, 0, 1, 1)); m_selectionRect.setHandleSize(0.04); //emit sigCropRegionChanged(shape->cropRect()); update(); } void CropWidget::setCropRect(const QRectF &rect) { m_selectionRect.setRect(rect); emitCropRegionChanged(); } void CropWidget::setKeepPictureProportion(bool keepProportion) { qreal aspect = keepProportion ? (m_pictureShape->size().width() / m_pictureShape->size().height()) : 0.0; m_selectionRect.setConstrainingAspectRatio(aspect); emitCropRegionChanged(); } void CropWidget::maximizeCroppedArea() { m_selectionRect.setRect(QRectF(0, 0, 1, 1)); emitCropRegionChanged(); } QPointF CropWidget::toUniformCoord(const QPointF& coord) const { QPointF result = coord - m_imageRect.topLeft(); return QPointF(result.x() / m_imageRect.width(), result.y() / m_imageRect.height()); } QPointF CropWidget::fromUniformCoord(const QPointF& coord) const { return m_imageRect.topLeft() + QPointF(coord.x()*m_imageRect.width(), coord.y()*m_imageRect.height()); } void CropWidget::emitCropRegionChanged() { if (!compareRects(m_oldSelectionRect, m_selectionRect.getRect(), 0.01)) { m_oldSelectionRect = m_selectionRect.getRect(); emit sigCropRegionChanged(m_selectionRect.getRect(), m_undoLast); update(); m_undoLast = m_isMousePressed; } } void CropWidget::calcImageRect() { if (m_pictureShape) { QSizeF imageSize = m_pictureShape->imageData()->image().size(); imageSize = imageSize * calcScale(imageSize, size(), true); m_imageRect = centerRectHorizontally (QRect(0, 0, imageSize.width(), imageSize.height()), size()); m_selectionRect.setAspectRatio(m_imageRect.width() / m_imageRect.height()); } else { m_imageRect = QRectF(); } } diff --git a/plugins/pluginshape/PluginShape.cpp b/plugins/pluginshape/PluginShape.cpp index 5f2daaa26ae..41025b7a239 100644 --- a/plugins/pluginshape/PluginShape.cpp +++ b/plugins/pluginshape/PluginShape.cpp @@ -1,125 +1,125 @@ /* This file is part of the KDE project * Copyright (C) 2010 Vidhyapria Arunkumar * Copyright (C) 2010 Amit Aggarwal * * 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 "PluginShape.h" #include #include #include #include #include #include #include #include PluginShape::PluginShape() : KoFrameShape(KoXmlNS::draw, "plugin") { setKeepAspectRatio(true); } PluginShape::~PluginShape() { } void PluginShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { QRectF pixelsF = converter.documentToView(QRectF(QPointF(0,0), size())); painter.fillRect(pixelsF, QColor(Qt::yellow)); - painter.setPen(Qt::blue); + painter.setPen(QPen(Qt::blue, 0)); QString mimetype = i18n("Unknown"); if (!m_mimetype.isEmpty()) { mimetype = m_mimetype; } painter.drawText(pixelsF, Qt::AlignCenter, i18n("Plugin of mimetype: %1", mimetype)); } void PluginShape::saveOdf(KoShapeSavingContext &context) const { KoXmlWriter &writer = context.xmlWriter(); writer.startElement("draw:frame"); saveOdfAttributes(context, OdfAllAttributes); writer.startElement("draw:plugin"); // cannot use "this" as referent for context.xmlid, already done for getting the xml:id for the frame // so (randomly) choosing m_xlinkhref to get another, separate unique referent for this shape const QString xmlId = context.xmlid(&m_xlinkhref, QLatin1String("plugin"), KoElementReference::Counter).toString(); writer.addAttribute("xml:id", xmlId); writer.addAttribute("draw:mime-type", m_mimetype); writer.addAttribute("xlink:type", m_xlinktype); writer.addAttribute("xlink:show", m_xlinkshow); writer.addAttribute("xlink:actuate", m_xlinkactuate); writer.addAttribute("xlink:href", m_xlinkhref); QMap::const_iterator itr = m_drawParams.constBegin(); while (itr != m_drawParams.constEnd()) { writer.startElement("draw:param", true); writer.addAttribute("draw:name", itr.key()); writer.addAttribute("draw:value", itr.value()); writer.endElement(); // draw:param ++itr; } writer.endElement(); // draw:plugin saveOdfCommonChildElements(context); writer.endElement(); // draw:frame } bool PluginShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { loadOdfAttributes(element, context, OdfAllAttributes); return loadOdfFrame(element, context); } bool PluginShape::loadOdfFrameElement(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_UNUSED(context); if(element.isNull()) { return false; } if(element.localName() == "plugin") { m_mimetype = element.attributeNS(KoXmlNS::draw, "mime-type"); m_xlinktype = element.attributeNS(KoXmlNS::xlink, "type"); m_xlinkshow = element.attributeNS(KoXmlNS::xlink, "show"); m_xlinkactuate = element.attributeNS(KoXmlNS::xlink, "actuate"); m_xlinkhref = element.attributeNS(KoXmlNS::xlink, "href"); m_drawParams.clear(); if(element.hasChildNodes()) { KoXmlNode node = element.firstChild(); while(!node.isNull()) { if(node.isElement()) { KoXmlElement nodeElement = node.toElement(); if(nodeElement.localName() == "param") { QString name = nodeElement.attributeNS(KoXmlNS::draw, "name"); if(!name.isEmpty()) { m_drawParams.insert(name,nodeElement.attributeNS(KoXmlNS::draw, "value")); } } } node = node.nextSibling(); } } return true; } return false; } diff --git a/plugins/staging/templateshape/TemplateShape.cpp b/plugins/staging/templateshape/TemplateShape.cpp index 4ff76aa79f4..bd8fd00de0e 100644 --- a/plugins/staging/templateshape/TemplateShape.cpp +++ b/plugins/staging/templateshape/TemplateShape.cpp @@ -1,93 +1,93 @@ /* This file is part of the KDE project * * Copyright (C) 2012 Inge Wallin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // Own #include "TemplateShape.h" // Qt #include // KF5 #include // Calligra #include #include #include #include #include #include #include // This shape //#include "Foo.h" TemplateShape::TemplateShape() : QObject() , KoShape() // , m_member() // initiate members here. { } TemplateShape::~TemplateShape() { } void TemplateShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context) { - painter.setPen(QPen(QColor(0, 0, 0))); + painter.setPen(QPen(QColor(0, 0, 0), 0)); // Example painting code: Draw a rectangle around the shape painter.drawRect(converter.documentToView(QRectF(QPoint(0, 0), size()))); } void TemplateShape::saveOdf(KoShapeSavingContext &context) const { KoXmlWriter &writer = context.xmlWriter(); // Example code: Save with calligra:template as the top XML element. writer.startElement("calligra:template"); // Save shape attributes that were loaded using loadOdfAttributes. saveOdfAttributes(context, OdfAllAttributes); writer.endElement(); // calligra:template } bool TemplateShape::loadOdf(const KoXmlElement &templateElement, KoShapeLoadingContext &context) { kDebug(31000) << "========================== Starting Template shape"; kDebug(31000) <<"Loading ODF element: " << templateElement.tagName(); // Load all standard odf attributes and store into the KoShape loadOdfAttributes(templateElement, context, OdfAllAttributes); // Template: Load the actual content of the shape here. return true; } void TemplateShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const { } diff --git a/plugins/staging/threedshape/SceneObject.cpp b/plugins/staging/threedshape/SceneObject.cpp index 2a627a7ebb4..d65b4403e4a 100644 --- a/plugins/staging/threedshape/SceneObject.cpp +++ b/plugins/staging/threedshape/SceneObject.cpp @@ -1,232 +1,232 @@ /* This file is part of the KDE project * * Copyright (C) 2012 Inge Wallin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // Own #include "SceneObject.h" // Qt #include #include #include #include // Calligra #include #include #include #include #include #include #include #include #include #include // 3D shape #include "Objects.h" #include "ThreedDebug.h" SceneObject::SceneObject(Object3D *parent, bool topLevel) : Object3D(parent) #if IMPLEMENT_AS_SHAPECONTAINER , KoShapeContainer() #endif , m_topLevel(topLevel) , m_threeDParams(0) { } SceneObject::~SceneObject() { delete m_threeDParams; qDeleteAll(m_objects); } /// reimplemented from KoShapeContainer void SceneObject::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) { Q_UNUSED(painter); Q_UNUSED(converter); Q_UNUSED(paintcontext); } void SceneObject::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context) { Q_UNUSED(context); //painter.setPen(QPen(QColor(172, 196, 206))); - painter.setPen(QPen(QColor(0, 0, 0))); + painter.setPen(QPen(QColor(0, 0, 0), 0)); #if 1 painter.drawRect(converter.documentToView(QRectF(QPoint(0, 0), size()))); #else QRectF rect = converter.documentToView(boundingRect()); QRectF rect = converter.documentToView(QRectF(QPoint(0, 0), size())); painter.drawRect(rect); #endif debugThreed << "boundingRect: " << boundingRect(); debugThreed << "outlineRect: " << outlineRect(); #if 0 // Taken from the vector shape QRectF rect(QPointF(0,0), m_size); painter.save(); // Draw a simple cross in a rectangle just to indicate that there is something here. - painter.setPen(QPen(QColor(172, 196, 206))); + painter.setPen(QPen(QColor(172, 196, 206), 0)); painter.drawRect(rect); painter.drawLine(rect.topLeft(), rect.bottomRight()); painter.drawLine(rect.bottomLeft(), rect.topRight()); painter.restore(); #endif } void SceneObject::saveOdf(KoShapeSavingContext &context) const { if (m_topLevel) { saveObjectOdf(context); } } void SceneObject::saveObjectOdf(KoShapeSavingContext &context) const { KoXmlWriter &writer = context.xmlWriter(); writer.startElement("dr3d:scene"); if (m_topLevel) { saveOdfAttributes(context, (OdfAdditionalAttributes | OdfMandatories | OdfGeometry)); } else { saveOdfAttributes(context, (OdfAdditionalAttributes | OdfMandatories)); } if (m_topLevel && m_threeDParams) m_threeDParams->saveOdfAttributes(writer); // 2.1 Light sources if (m_topLevel && m_threeDParams) m_threeDParams->saveOdfChildren(writer); // 2.2 Objects in the scene foreach (const Object3D *object, m_objects) { object->saveObjectOdf(context); } writer.endElement(); // dr3d:scene } bool SceneObject::loadOdf(const KoXmlElement &sceneElement, KoShapeLoadingContext &context) { // Load style information. if (m_topLevel) { loadOdfAttributes(sceneElement, context, (OdfAdditionalAttributes | OdfMandatories | OdfGeometry)); } else { loadOdfAttributes(sceneElement, context, (OdfAdditionalAttributes | OdfMandatories)); } Object3D::loadOdf(sceneElement, context); // Load the view parameters. if (m_topLevel) { m_threeDParams = load3dScene(sceneElement); } // Load the child elements, i.e the scene itself. // From the ODF 1.1 spec section 9.4.1: // // The elements that may be contained in the element are: // * Title (short accessible name) – see section 9.2.20. // * Long description (in support of accessibility) – see section 9.2.20. // * Light – see section 9.4.2. (handled by Ko3DScene) // // * Scene – see section 9.4.1. [All of these can be 0 or more.] // * Cube – see section 9.4.3. // * Sphere – see section 9.4.4. // * Extrude – see section 9.4.5. // * Rotate – see section 9.4.6. // // The lights are skipped here, they are taken care of by the call // to load3dScene() above. KoXmlElement elem; forEachElement(elem, sceneElement) { if (elem.localName() == "scene" && elem.namespaceURI() == KoXmlNS::dr3d) { SceneObject *scene = new SceneObject(this, false); scene->loadOdf(elem, context); m_objects.append(scene); #if IMPLEMENT_AS_SHAPECONTAINER addShape(scene); #endif } else if (elem.localName() == "sphere" && elem.namespaceURI() == KoXmlNS::dr3d) { Sphere *sphere = new Sphere(this); sphere->loadOdf(elem, context); m_objects.append(sphere); #if IMPLEMENT_AS_SHAPECONTAINER addShape(sphere); #endif } else if (elem.localName() == "cube" && elem.namespaceURI() == KoXmlNS::dr3d) { Cube *cube = new Cube(this); cube->loadOdf(elem, context); m_objects.append(cube); #if IMPLEMENT_AS_SHAPECONTAINER addShape(cube); #endif } else if (elem.localName() == "extrude" && elem.namespaceURI() == KoXmlNS::dr3d) { Extrude *extrude = new Extrude(this); extrude->loadOdf(elem, context); m_objects.append(extrude); #if IMPLEMENT_AS_SHAPECONTAINER addShape(extrude); #endif } else if (elem.localName() == "rotate" && elem.namespaceURI() == KoXmlNS::dr3d) { Rotate *rotate = new Rotate(this); rotate->loadOdf(elem, context); m_objects.append(rotate); #if IMPLEMENT_AS_SHAPECONTAINER addShape(rotate); #endif } } debugThreed << "Objects:" << m_objects.size(); #if IMPLEMENT_AS_SHAPECONTAINER debugThreed << "Objects in shape container:" << shapeCount(); #endif return true; } void SceneObject::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const { Q_UNUSED(converter); Q_UNUSED(asynchronous); } diff --git a/plugins/stencilsdocker/CollectionTreeWidget.cpp b/plugins/stencilsdocker/CollectionTreeWidget.cpp index 6fb36bba198..829677ecba9 100644 --- a/plugins/stencilsdocker/CollectionTreeWidget.cpp +++ b/plugins/stencilsdocker/CollectionTreeWidget.cpp @@ -1,326 +1,326 @@ /* This file is part of the Calligra project * Copyright (C) 2010-2014 Yue Liu * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "CollectionTreeWidget.h" #include "StencilListView.h" #include "CollectionItemModel.h" #include "StencilBoxDebug.h" #include #include #include #include #include #include #include #include #include #include #include #include SheetDelegate::SheetDelegate(QTreeView* view, QWidget* parent) : QItemDelegate(parent), m_view(view) { } // style comes from qt designer // https://qt.gitorious.org/qt/qttools/source/src/designer/src/lib/shared/sheet_delegate.cpp void SheetDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { const QAbstractItemModel* model = index.model(); Q_ASSERT(model); if (!model->parent(index).isValid()) { // this is a top-level item. QStyleOptionButton buttonOption; buttonOption.state = option.state; #ifdef Q_OS_MAC buttonOption.state |= QStyle::State_Raised; #endif buttonOption.state &= ~QStyle::State_HasFocus; buttonOption.rect = option.rect; buttonOption.palette = option.palette; buttonOption.features = QStyleOptionButton::None; painter->save(); QColor buttonColor(230, 230, 230); QBrush buttonBrush = option.palette.button(); if (!buttonBrush.gradient() && buttonBrush.texture().isNull()) buttonColor = buttonBrush.color(); QColor outlineColor = buttonColor.darker(150); QColor highlightColor = buttonColor.lighter(130); // Only draw topline if the previous item is expanded QModelIndex previousIndex = model->index(index.row() - 1, index.column()); bool drawTopline = (index.row() > 0 && m_view->isExpanded(previousIndex)); int highlightOffset = drawTopline ? 1 : 0; QLinearGradient gradient(option.rect.topLeft(), option.rect.bottomLeft()); gradient.setColorAt(0, buttonColor.lighter(102)); gradient.setColorAt(1, buttonColor.darker(106)); painter->setPen(Qt::NoPen); painter->setBrush(gradient); painter->drawRect(option.rect); - painter->setPen(highlightColor); + painter->setPen(QPen(highlightColor, 0)); painter->drawLine(option.rect.topLeft() + QPoint(0, highlightOffset), option.rect.topRight() + QPoint(0, highlightOffset)); - painter->setPen(outlineColor); + painter->setPen(QPen(outlineColor, 0)); if (drawTopline) painter->drawLine(option.rect.topLeft(), option.rect.topRight()); painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight()); painter->restore(); QStyleOption branchOption; static const int i = 9; // ### hardcoded in qcommonstyle.cpp QRect r = option.rect; branchOption.rect = QRect(r.left() + i / 2, r.top() + (r.height() - i) / 2, i, i); branchOption.palette = option.palette; branchOption.state = QStyle::State_Children; if (m_view->isExpanded(index)) branchOption.state |= QStyle::State_Open; m_view->style()->drawPrimitive(QStyle::PE_IndicatorBranch, &branchOption, painter, m_view); // draw text QRect textrect = QRect(r.left() + i * 2, r.top(), r.width() - ((5 * i) / 2), r.height()); QString text = elidedText(option.fontMetrics, textrect.width(), Qt::ElideMiddle, model->data(index, Qt::DisplayRole).toString()); m_view->style()->drawItemText(painter, textrect, Qt::AlignCenter, option.palette, m_view->isEnabled(), text); } else { QItemDelegate::paint(painter, option, index); } } QSize SheetDelegate::sizeHint(const QStyleOptionViewItem& opt, const QModelIndex& index) const { QSize sz = QItemDelegate::sizeHint(opt, index) + QSize(2, 2); return sz; } CollectionTreeWidget::CollectionTreeWidget(QWidget* parent): QTreeWidget(parent) { header()->hide(); header()->setResizeMode(QHeaderView::Stretch); setRootIsDecorated(false); setItemsExpandable(true); setFocusPolicy(Qt::NoFocus); setIndentation(0); setColumnCount(1); setVerticalScrollMode(ScrollPerPixel); setAcceptDrops(true); setItemDelegate(new SheetDelegate(this, this)); connect(this, SIGNAL(itemPressed(QTreeWidgetItem*,int)), this, SLOT(handleMousePress(QTreeWidgetItem*))); loadOptions(); } CollectionTreeWidget::~CollectionTreeWidget() { saveOptions(); } void CollectionTreeWidget::setFamilyMap(QMap map) { m_familyMap = map; } void CollectionTreeWidget::regenerateFilteredMap() { QMapIterator i(m_familyMap); while (i.hasNext()) { i.next(); i.value()->setViewMode(m_viewMode); QSortFilterProxyModel* proxy = new QSortFilterProxyModel(); proxy->setSourceModel(i.value()); m_filteredMap.insert(i.key(), proxy); } //regenerate category view QMapIterator j(m_filteredMap); while (j.hasNext()) { j.next(); QTreeWidgetItem* category = new QTreeWidgetItem(this); category->setText(0, j.key()); addStencilListView(category, m_viewMode, j.value()); } } //Link a StencilListView to each TreeWidgetItem void CollectionTreeWidget::addStencilListView(QTreeWidgetItem* parent, QListView::ViewMode viewMode, QSortFilterProxyModel* model) { QTreeWidgetItem* embed_item = new QTreeWidgetItem(parent); embed_item->setFlags(Qt::ItemIsEnabled); StencilListView* categoryView = new StencilListView(); categoryView->setViewMode(viewMode); categoryView->setModel(model); setItemWidget(embed_item, 0, categoryView); } StencilListView* CollectionTreeWidget::stencilListViewAt(int idx) const { StencilListView* rc = 0; if (QTreeWidgetItem* cat_item = topLevelItem(idx)) { if (QTreeWidgetItem* embedItem = cat_item->child(0)) { rc = qobject_cast(itemWidget(embedItem, 0)); } } Q_ASSERT(rc); return rc; } void CollectionTreeWidget::saveOptions() { KConfigGroup group = KSharedConfig::openConfig()->group("Stencil Box"); group.writeEntry("viewMode", (int)m_viewMode); } void CollectionTreeWidget::loadOptions() { KConfigGroup group = KSharedConfig::openConfig()->group("Stencil Box"); int viewMode = group.readEntry("viewMode", (int)QListView::IconMode); m_viewMode = (QListView::ViewMode)viewMode; updateViewMode(); } void CollectionTreeWidget::handleMousePress(QTreeWidgetItem* item) { if (item && !item->parent() && QApplication::mouseButtons() == Qt::LeftButton) setItemExpanded(item, !isItemExpanded(item)); } void CollectionTreeWidget::slotListMode() { m_viewMode = QListView::ListMode; updateViewMode(); } void CollectionTreeWidget::slotIconMode() { m_viewMode = QListView::IconMode; updateViewMode(); } void CollectionTreeWidget::updateViewMode() { QMapIterator i(m_familyMap); while (i.hasNext()) { i.next(); i.value()->setViewMode(m_viewMode); } if (const int numTopLevels = topLevelItemCount()) { for (int i = numTopLevels - 1; i >= 0; --i) { StencilListView* categoryView = stencilListViewAt(i); if (m_viewMode != categoryView->viewMode()) { categoryView->setViewMode(m_viewMode); categoryView->setMovement(QListView::Static); categoryView->setDragDropMode(QAbstractItemView::DragDrop); adjustStencilListSize(topLevelItem(i)); } } } updateGeometries(); } void CollectionTreeWidget::adjustStencilListSize(QTreeWidgetItem* cat_item) { QTreeWidgetItem* embedItem = cat_item->child(0); if (embedItem == 0) return; StencilListView* list_widget = static_cast(itemWidget(embedItem, 0)); list_widget->setFixedWidth(header()->width()); list_widget->doItemsLayout(); const int height = qMax(list_widget->contentsSize().height() , 1); list_widget->setFixedHeight(height); embedItem->setSizeHint(0, QSize(-1, height - 1)); } void CollectionTreeWidget::setFilter(QRegExp regExp) { QMapIterator j(m_filteredMap); while (j.hasNext()) { j.next(); j.value()->setFilterRegExp(regExp); j.value()->setFilterRole(Qt::UserRole + 1); } for (int i = 0; i < topLevelItemCount(); i++) { QTreeWidgetItem* tl = topLevelItem(i); StencilListView* categoryView = stencilListViewAt(i); QAbstractItemModel* model = categoryView->model(); const bool categoryEnabled = model->rowCount() > 0; if (categoryView->model()->rowCount() > 0) { categoryView->adjustSize(); adjustStencilListSize(tl); } setRowHidden(i, QModelIndex(), !categoryEnabled); } updateGeometries(); } void CollectionTreeWidget::resizeEvent(QResizeEvent* e) { QTreeWidget::resizeEvent(e); if (const int numTopLevels = topLevelItemCount()) { for (int i = numTopLevels - 1; i >= 0; --i) { adjustStencilListSize(topLevelItem(i)); } } } void CollectionTreeWidget::contextMenuEvent(QContextMenuEvent* e) { QMenu menu; menu.addAction(i18n("Expand all"), this, SLOT(expandAll())); menu.addAction(i18n("Collapse all"), this, SLOT(collapseAll())); menu.addSeparator(); QAction* listModeAction = menu.addAction(i18n("List View")); QAction* iconModeAction = menu.addAction(i18n("Icon View")); listModeAction->setCheckable(true); iconModeAction->setCheckable(true); QActionGroup* viewModeGroup = new QActionGroup(&menu); viewModeGroup->addAction(listModeAction); viewModeGroup->addAction(iconModeAction); if (m_viewMode == QListView::IconMode) { iconModeAction->setChecked(true); } else { listModeAction->setChecked(true); } connect(listModeAction, SIGNAL(triggered()), SLOT(slotListMode())); connect(iconModeAction, SIGNAL(triggered()), SLOT(slotIconMode())); e->accept(); menu.exec(mapToGlobal(e->pos())); } diff --git a/plugins/textshape/TextShape.cpp b/plugins/textshape/TextShape.cpp index dec30b66b73..689199101b6 100644 --- a/plugins/textshape/TextShape.cpp +++ b/plugins/textshape/TextShape.cpp @@ -1,435 +1,435 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008-2010 Thorsten Zachmann * Copyright (C) 2008 Pierre Stirnweiss \pierre.stirnweiss_calligra@gadz.org> * Copyright (C) 2010 KO GmbH * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TextShape.h" #include "ShrinkToFitShapeContainer.h" #include #include "SimpleRootAreaProvider.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include TextShape::TextShape(KoInlineTextObjectManager *inlineTextObjectManager, KoTextRangeManager *textRangeManager) : KoShapeContainer(new KoTextShapeContainerModel()) , KoFrameShape(KoXmlNS::draw, "text-box") , m_pageProvider(0) , m_imageCollection(0) , m_paragraphStyle(0) , m_clip(true) { setShapeId(TextShape_SHAPEID); m_textShapeData = new KoTextShapeData(); setUserData(m_textShapeData); SimpleRootAreaProvider *provider = new SimpleRootAreaProvider(m_textShapeData, this); KoTextDocument(m_textShapeData->document()).setInlineTextObjectManager(inlineTextObjectManager); KoTextDocument(m_textShapeData->document()).setTextRangeManager(textRangeManager); m_layout = new KoTextDocumentLayout(m_textShapeData->document(), provider); m_textShapeData->document()->setDocumentLayout(m_layout); setCollisionDetection(true); QObject::connect(m_layout, SIGNAL(layoutIsDirty()), m_layout, SLOT(scheduleLayout())); } TextShape::~TextShape() { } void TextShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { painter.save(); applyConversion(painter, converter); KoBorder *border = this->border(); if (border) { paintBorder(painter, converter); } else if (paintContext.showTextShapeOutlines) { // No need to paint the outlines if there is a real border. if (qAbs(rotation()) > 1) painter.setRenderHint(QPainter::Antialiasing); - QPen pen(QColor(210, 210, 210)); // use cosmetic pen + QPen pen(QColor(210, 210, 210), 0); // use cosmetic pen QPointF onePixel = converter.viewToDocument(QPointF(1.0, 1.0)); QRectF rect(QPointF(0.0, 0.0), size() - QSizeF(onePixel.x(), onePixel.y())); painter.setPen(pen); painter.drawRect(rect); } painter.restore(); if (m_textShapeData->isDirty()) { // not layouted yet. return; } QTextDocument *doc = m_textShapeData->document(); Q_ASSERT(doc); KoTextDocumentLayout *lay = qobject_cast(doc->documentLayout()); Q_ASSERT(lay); lay->showInlineObjectVisualization(paintContext.showInlineObjectVisualization); applyConversion(painter, converter); if (background()) { QPainterPath p; p.addRect(QRectF(QPointF(), size())); background()->paint(painter, converter, paintContext, p); } // this enables to use the same shapes on different pages showing different page numbers if (m_pageProvider) { KoTextPage *page = m_pageProvider->page(this); if (page) { // this is used to not trigger repaints if layout during the painting is done m_paintRegion = painter.clipRegion(); if (!m_textShapeData->rootArea()->page() || page->pageNumber() != m_textShapeData->rootArea()->page()->pageNumber()) { m_textShapeData->rootArea()->setPage(page); // takes over ownership of the page } else { delete page; } } } KoTextDocumentLayout::PaintContext pc; QAbstractTextDocumentLayout::Selection selection; KoTextEditor *textEditor = KoTextDocument(m_textShapeData->document()).textEditor(); selection.cursor = *(textEditor->cursor()); QPalette palette = pc.textContext.palette; selection.format.setBackground(palette.brush(QPalette::Highlight)); selection.format.setForeground(palette.brush(QPalette::HighlightedText)); pc.textContext.selections.append(selection); pc.textContext.selections += KoTextDocument(doc).selections(); pc.viewConverter = &converter; pc.imageCollection = m_imageCollection; pc.showFormattingCharacters = paintContext.showFormattingCharacters; pc.showTableBorders = paintContext.showTableBorders; pc.showSectionBounds = paintContext.showSectionBounds; pc.showSpellChecking = paintContext.showSpellChecking; pc.showSelections = paintContext.showSelections; // When clipping the painter we need to make sure not to cutoff cosmetic pens which // may used to draw e.g. table-borders for user convenience when on screen (but not // on e.g. printing). Such cosmetic pens are special cause they will always have the // same pen-width (1 pixel) independent of zoom-factor or painter transformations and // are not taken into account in any border-calculations. QRectF clipRect = outlineRect(); qreal cosmeticPenX = 1 * 72. / painter.device()->logicalDpiX(); qreal cosmeticPenY = 1 * 72. / painter.device()->logicalDpiY(); painter.setClipRect(clipRect.adjusted(-cosmeticPenX, -cosmeticPenY, cosmeticPenX, cosmeticPenY), Qt::IntersectClip); painter.save(); painter.translate(0, -m_textShapeData->documentOffset()); m_textShapeData->rootArea()->paint(&painter, pc); // only need to draw ourselves painter.restore(); m_paintRegion = QRegion(); } QPointF TextShape::convertScreenPos(const QPointF &point) const { QPointF p = absoluteTransformation(0).inverted().map(point); return p + QPointF(0.0, m_textShapeData->documentOffset()); } QPainterPath TextShape::outline() const { QPainterPath path; path.addRect(QRectF(QPointF(0,0), size())); return path; } QRectF TextShape::outlineRect() const { if (m_textShapeData->rootArea()) { QRectF rect = m_textShapeData->rootArea()->boundingRect(); rect.moveTop(rect.top() - m_textShapeData->rootArea()->top()); if (m_clip) { rect.setHeight(size().height()); } return rect | QRectF(QPointF(0, 0), size()); } return QRectF(QPointF(0,0), size()); } void TextShape::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(shape); KoShapeContainer::shapeChanged(type, shape); if (type == PositionChanged || type == SizeChanged || type == CollisionDetected) { m_textShapeData->setDirty(); } } void TextShape::saveOdf(KoShapeSavingContext &context) const { KoXmlWriter & writer = context.xmlWriter(); QString textHeight = additionalAttribute("fo:min-height"); const_cast(this)->removeAdditionalAttribute("fo:min-height"); writer.startElement("draw:frame"); // if the TextShape is wrapped in a shrink to fit container we need to save the geometry of the container as // the geomerty of the shape might have been changed. if (ShrinkToFitShapeContainer *stf = dynamic_cast(this->parent())) { stf->saveOdfAttributes(context, OdfSize | OdfPosition | OdfTransformation ); saveOdfAttributes(context, OdfAdditionalAttributes | OdfMandatories | OdfCommonChildElements); } else { saveOdfAttributes(context, OdfAllAttributes); } writer.startElement("draw:text-box"); if (! textHeight.isEmpty()) writer.addAttribute("fo:min-height", textHeight); KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); int index = -1; if (lay) { int i = 0; foreach (KoShape *shape, lay->shapes()) { if (shape == this) { index = i; } else if (index >= 0) { writer.addAttribute("draw:chain-next-name", shape->name()); break; } ++i; } } const bool saveMyText = index == 0; // only save the text once. m_textShapeData->saveOdf(context, 0, 0, saveMyText ? -1 : 0); writer.endElement(); // draw:text-box saveOdfCommonChildElements(context); writer.endElement(); // draw:frame } QString TextShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const { Qt::Alignment vAlign(m_textShapeData->verticalAlignment()); QString verticalAlign = "top"; if (vAlign == Qt::AlignBottom) { verticalAlign = "bottom"; } else if ( vAlign == Qt::AlignVCenter ) { verticalAlign = "middle"; } style.addProperty("draw:textarea-vertical-align", verticalAlign); KoTextShapeData::ResizeMethod resize = m_textShapeData->resizeMethod(); if (resize == KoTextShapeData::AutoGrowWidth || resize == KoTextShapeData::AutoGrowWidthAndHeight) style.addProperty("draw:auto-grow-width", "true"); if (resize != KoTextShapeData::AutoGrowHeight && resize != KoTextShapeData::AutoGrowWidthAndHeight) style.addProperty("draw:auto-grow-height", "false"); if (resize == KoTextShapeData::ShrinkToFitResize) style.addProperty("draw:fit-to-size", "true"); m_textShapeData->saveStyle(style, context); return KoShape::saveStyle(style, context); } void TextShape::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) { KoShape::loadStyle(element, context); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); QString verticalAlign(styleStack.property(KoXmlNS::draw, "textarea-vertical-align")); Qt::Alignment alignment(Qt::AlignTop); if (verticalAlign == "bottom") { alignment = Qt::AlignBottom; } else if (verticalAlign == "justify") { // not yet supported alignment = Qt::AlignVCenter; } else if (verticalAlign == "middle") { alignment = Qt::AlignVCenter; } m_textShapeData->setVerticalAlignment(alignment); const QString fitToSize = styleStack.property(KoXmlNS::draw, "fit-to-size"); KoTextShapeData::ResizeMethod resize = KoTextShapeData::NoResize; if (fitToSize == "true" || fitToSize == "shrink-to-fit") { // second is buggy value from impress resize = KoTextShapeData::ShrinkToFitResize; } else { // An explicit svg:width or svg:height defined do change the default value (means those value // used if not explicit defined otherwise) for auto-grow-height and auto-grow-height. So // they are mutable exclusive. // It is not clear (means we did not test and took care of it) what happens if both are // defined and are in conflict with each other or how the fit-to-size is related to this. QString autoGrowWidth = styleStack.property(KoXmlNS::draw, "auto-grow-width"); if (autoGrowWidth.isEmpty()) { autoGrowWidth = element.hasAttributeNS(KoXmlNS::svg, "width") ? "false" : "true"; } QString autoGrowHeight = styleStack.property(KoXmlNS::draw, "auto-grow-height"); if (autoGrowHeight.isEmpty()) { autoGrowHeight = element.hasAttributeNS(KoXmlNS::svg, "height") ? "false" : "true"; } if (autoGrowWidth == "true") { resize = autoGrowHeight == "true" ? KoTextShapeData::AutoGrowWidthAndHeight : KoTextShapeData::AutoGrowWidth; } else if (autoGrowHeight == "true") { resize = KoTextShapeData::AutoGrowHeight; } } m_textShapeData->setResizeMethod(resize); } bool TextShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { m_textShapeData->document()->setUndoRedoEnabled(false); loadOdfAttributes(element, context, OdfAllAttributes); // this cannot be done in loadStyle as that fill the style stack wrongly and therefor it results // in wrong data to be loaded. m_textShapeData->loadStyle(element, context); #ifndef NWORKAROUND_ODF_BUGS KoTextShapeData::ResizeMethod method = m_textShapeData->resizeMethod(); if (KoOdfWorkaround::fixAutoGrow(method, context)) { KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); if (lay) { SimpleRootAreaProvider *provider = dynamic_cast(lay->provider()); if (provider) { provider->m_fixAutogrow = true; } } } #endif bool answer = loadOdfFrame(element, context); m_textShapeData->document()->setUndoRedoEnabled(true); return answer; } bool TextShape::loadOdfFrame(const KoXmlElement &element, KoShapeLoadingContext &context) { // If the loadOdfFrame from the base class for draw:text-box fails, check // for table:table, because that is a legal child of draw:frame in ODF 1.2. if (!KoFrameShape::loadOdfFrame(element, context)) { const KoXmlElement &possibleTableElement(KoXml::namedItemNS(element, KoXmlNS::table, "table")); if (possibleTableElement.isNull()) { return false; } else { return loadOdfFrameElement(possibleTableElement, context); } } return true; } bool TextShape::loadOdfFrameElement(const KoXmlElement &element, KoShapeLoadingContext &context) { bool ok = m_textShapeData->loadOdf(element, context, 0, this); if (ok) ShrinkToFitShapeContainer::tryWrapShape(this, element, context); return ok; } void TextShape::update() const { KoShapeContainer::update(); } void TextShape::update(const QRectF &shape) const { // this is done to avoid updates which are called during the paint event and not needed. if (!m_paintRegion.contains(shape.toRect())) { KoShape::update(shape); } } void TextShape::waitUntilReady(const KoViewConverter &, bool asynchronous) const { Q_UNUSED(asynchronous); KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); if (m_textShapeData->isDirty()) { // Do a simple layout-call which will make sure to relayout till things are done. If more // layouts are scheduled then we don't need to wait for them here but can just continue. lay->layout(); } } KoImageCollection *TextShape::imageCollection() { return m_imageCollection; } void TextShape::updateDocumentData() { if (m_layout) { KoTextDocument document(m_textShapeData->document()); m_layout->setStyleManager(document.styleManager()); m_layout->setInlineTextObjectManager(document.inlineTextObjectManager()); m_layout->setTextRangeManager(document.textRangeManager()); m_layout->setChangeTracker(document.changeTracker()); } } diff --git a/plugins/textshape/TextTool.cpp b/plugins/textshape/TextTool.cpp index 8c411c7abc3..b2ce49f89e1 100644 --- a/plugins/textshape/TextTool.cpp +++ b/plugins/textshape/TextTool.cpp @@ -1,3122 +1,3122 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2008, 2012 Pierre Stirnweiss * Copyright (C) 2009 KO GmbH * Copyright (C) 2011 Mojtaba Shahi Senobari * Copyright (C) 2014 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TextTool.h" #include "TextEditingPluginContainer.h" #include "dialogs/SimpleCharacterWidget.h" #include "dialogs/SimpleParagraphWidget.h" #include "dialogs/SimpleTableWidget.h" #include "dialogs/SimpleInsertWidget.h" #include "dialogs/ParagraphSettingsDialog.h" #include "dialogs/StyleManagerDialog.h" #include "dialogs/InsertCharacter.h" #include "dialogs/FontDia.h" #include "dialogs/TableDialog.h" #include "dialogs/SectionFormatDialog.h" #include "dialogs/SectionsSplitDialog.h" #include "dialogs/SimpleTableWidget.h" #include "commands/AutoResizeCommand.h" #include "commands/ChangeListLevelCommand.h" #include "FontSizeAction.h" #include "FontFamilyAction.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AnnotationTextShape.h" #define AnnotationShape_SHAPEID "AnnotationTextShapeID" #include "KoShapeBasedDocumentBase.h" #include #include #include #include class TextToolSelection : public KoToolSelection { public: TextToolSelection(QWeakPointer editor) : KoToolSelection(0) , m_editor(editor) { } bool hasSelection() { if (!m_editor.isNull()) { return m_editor.data()->hasSelection(); } return false; } QWeakPointer m_editor; }; static bool hit(const QKeySequence &input, KStandardShortcut::StandardShortcut shortcut) { foreach (const QKeySequence & ks, KStandardShortcut::shortcut(shortcut)) { if (input == ks) return true; } return false; } TextTool::TextTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_textShape(0) , m_textShapeData(0) , m_changeTracker(0) , m_allowActions(true) , m_allowAddUndoCommand(true) , m_allowResourceManagerUpdates(true) , m_prevCursorPosition(-1) , m_caretTimer(this) , m_caretTimerState(true) , m_currentCommand(0) , m_currentCommandHasChildren(false) , m_specialCharacterDocker(0) , m_textTyping(false) , m_textDeleting(false) , m_editTipTimer(this) , m_delayedEnsureVisible(false) , m_toolSelection(0) , m_tableDraggedOnce(false) , m_tablePenMode(false) , m_lastImMicroFocus(QRectF(0,0,0,0)) , m_drag(0) { setTextMode(true); createActions(); m_unit = canvas->resourceManager()->unitResource(KoCanvasResourceManager::Unit); foreach (KoTextEditingPlugin* plugin, textEditingPluginContainer()->values()) { connect(plugin, SIGNAL(startMacro(QString)), this, SLOT(startMacro(QString))); connect(plugin, SIGNAL(stopMacro()), this, SLOT(stopMacro())); const QHash actions = plugin->actions(); QHash::ConstIterator i = actions.begin(); while (i != actions.end()) { addAction(i.key(), i.value()); ++i; } } // setup the context list. QSignalMapper *signalMapper = new QSignalMapper(this); connect(signalMapper, SIGNAL(mapped(QString)), this, SLOT(startTextEditingPlugin(QString))); QList list; list.append(this->action("format_font")); foreach (const QString &key, KoTextEditingRegistry::instance()->keys()) { KoTextEditingFactory *factory = KoTextEditingRegistry::instance()->value(key); if (factory->showInMenu()) { QAction *a = new QAction(factory->title(), this); connect(a, SIGNAL(triggered()), signalMapper, SLOT(map())); signalMapper->setMapping(a, factory->id()); list.append(a); addAction(QString("apply_%1").arg(factory->id()), a); } } setPopupActionList(list); connect(canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SLOT(shapeAddedToCanvas())); m_caretTimer.setInterval(500); connect(&m_caretTimer, SIGNAL(timeout()), this, SLOT(blinkCaret())); m_editTipTimer.setInterval(500); m_editTipTimer.setSingleShot(true); connect(&m_editTipTimer, SIGNAL(timeout()), this, SLOT(showEditTip())); } void TextTool::createActions() { bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); m_actionConfigureSection = new QAction(koIconNeededWithSubs("", "configure-text-section", "configure"), i18n("Configure current section"), this); addAction("configure_section", m_actionConfigureSection); connect(m_actionConfigureSection, SIGNAL(triggered(bool)), this, SLOT(configureSection())); m_actionInsertSection = new QAction(koIconNeededWithSubs("", "insert-text-section", "insert-text"), i18n("Insert new section"), this); addAction("insert_section", m_actionInsertSection); connect(m_actionInsertSection, SIGNAL(triggered(bool)), this, SLOT(insertNewSection())); m_actionSplitSections = new QAction(koIconNeededWithSubs("", "text-section-split", "split"), i18n("Insert paragraph between sections"), this); addAction("split_sections", m_actionSplitSections); connect(m_actionSplitSections, SIGNAL(triggered(bool)), this, SLOT(splitSections())); m_actionPasteAsText = new QAction(koIcon("edit-paste"), i18n("Paste As Text"), this); addAction("edit_paste_text", m_actionPasteAsText); m_actionPasteAsText->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_V); connect(m_actionPasteAsText, SIGNAL(triggered(bool)), this, SLOT(pasteAsText())); m_actionFormatBold = new QAction(koIcon("format-text-bold"), i18n("Bold"), this); addAction("format_bold", m_actionFormatBold); m_actionFormatBold->setShortcut(Qt::CTRL + Qt::Key_B); m_actionFormatBold->setCheckable(true); connect(m_actionFormatBold, SIGNAL(triggered(bool)), this, SLOT(bold(bool))); m_actionFormatItalic = new QAction(koIcon("format-text-italic"), i18n("Italic"), this); addAction("format_italic", m_actionFormatItalic); m_actionFormatItalic->setShortcut(Qt::CTRL + Qt::Key_I); m_actionFormatItalic->setCheckable(true); connect(m_actionFormatItalic, SIGNAL(triggered(bool)), this, SLOT(italic(bool))); m_actionFormatUnderline = new QAction(koIcon("format-text-underline"), i18nc("Text formatting", "Underline"), this); addAction("format_underline", m_actionFormatUnderline); m_actionFormatUnderline->setShortcut(Qt::CTRL + Qt::Key_U); m_actionFormatUnderline->setCheckable(true); connect(m_actionFormatUnderline, SIGNAL(triggered(bool)), this, SLOT(underline(bool))); m_actionFormatStrikeOut = new QAction(koIcon("format-text-strikethrough"), i18n("Strikethrough"), this); addAction("format_strike", m_actionFormatStrikeOut); m_actionFormatStrikeOut->setCheckable(true); connect(m_actionFormatStrikeOut, SIGNAL(triggered(bool)), this, SLOT(strikeOut(bool))); QActionGroup *alignmentGroup = new QActionGroup(this); m_actionAlignLeft = new QAction(koIcon("format-justify-left"), i18n("Align Left"), this); addAction("format_alignleft", m_actionAlignLeft); m_actionAlignLeft->setShortcut(Qt::CTRL + Qt::Key_L); m_actionAlignLeft->setCheckable(true); alignmentGroup->addAction(m_actionAlignLeft); connect(m_actionAlignLeft, SIGNAL(triggered(bool)), this, SLOT(alignLeft())); m_actionAlignRight = new QAction(koIcon("format-justify-right"), i18n("Align Right"), this); addAction("format_alignright", m_actionAlignRight); m_actionAlignRight->setShortcut(Qt::CTRL + Qt::Key_R); m_actionAlignRight->setCheckable(true); alignmentGroup->addAction(m_actionAlignRight); connect(m_actionAlignRight, SIGNAL(triggered(bool)), this, SLOT(alignRight())); m_actionAlignCenter = new QAction(koIcon("format-justify-center"), i18n("Align Center"), this); addAction("format_aligncenter", m_actionAlignCenter); m_actionAlignCenter->setShortcut(Qt::CTRL + Qt::Key_E); m_actionAlignCenter->setCheckable(true); alignmentGroup->addAction(m_actionAlignCenter); connect(m_actionAlignCenter, SIGNAL(triggered(bool)), this, SLOT(alignCenter())); m_actionAlignBlock = new QAction(koIcon("format-justify-fill"), i18n("Align Block"), this); addAction("format_alignblock", m_actionAlignBlock); m_actionAlignBlock->setShortcut(Qt::CTRL + Qt::Key_J); m_actionAlignBlock->setCheckable(true); alignmentGroup->addAction(m_actionAlignBlock); connect(m_actionAlignBlock, SIGNAL(triggered(bool)), this, SLOT(alignBlock())); m_actionChangeDirection = new QAction(koIcon("format-text-direction-rtl"), i18n("Change text direction"), this); addAction("change_text_direction", m_actionChangeDirection); m_actionChangeDirection->setToolTip(i18n("Change writing direction")); m_actionChangeDirection->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_D); m_actionChangeDirection->setCheckable(true); connect(m_actionChangeDirection, SIGNAL(triggered()), this, SLOT(textDirectionChanged())); m_actionFormatSuper = new QAction(koIcon("format-text-superscript"), i18n("Superscript"), this); m_actionFormatSuper->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_P); addAction("format_super", m_actionFormatSuper); m_actionFormatSuper->setCheckable(true); connect(m_actionFormatSuper, SIGNAL(triggered(bool)), this, SLOT(superScript(bool))); m_actionFormatSub = new QAction(koIcon("format-text-subscript"), i18n("Subscript"), this); m_actionFormatSub->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_B); addAction("format_sub", m_actionFormatSub); m_actionFormatSub->setCheckable(true); connect(m_actionFormatSub, SIGNAL(triggered(bool)), this, SLOT(subScript(bool))); const char* const increaseIndentActionIconName = QApplication::isRightToLeft() ? koIconNameCStr("format-indent-less") : koIconNameCStr("format-indent-more"); m_actionFormatIncreaseIndent = new QAction( QIcon::fromTheme(QLatin1String(increaseIndentActionIconName)), i18n("Increase Indent"), this); addAction("format_increaseindent", m_actionFormatIncreaseIndent); connect(m_actionFormatIncreaseIndent, SIGNAL(triggered()), this, SLOT(increaseIndent())); const char* const decreaseIndentActionIconName = QApplication::isRightToLeft() ? koIconNameCStr("format-indent-more") : koIconNameCStr("format-indent-less"); m_actionFormatDecreaseIndent = new QAction(QIcon::fromTheme(QLatin1String(decreaseIndentActionIconName)), i18n("Decrease Indent"), this); addAction("format_decreaseindent", m_actionFormatDecreaseIndent); connect(m_actionFormatDecreaseIndent, SIGNAL(triggered()), this, SLOT(decreaseIndent())); QAction *action = new QAction(koIcon("format-list-unordered"), i18n("Toggle List or List Level Formatting"), this); action->setToolTip(i18n("Toggle list on/off, or change format of current level")); addAction("format_list", action); action = new QAction(i18n("Increase Font Size"), this); action->setShortcut(Qt::CTRL + Qt::Key_Greater); addAction("fontsizeup", action); connect(action, SIGNAL(triggered()), this, SLOT(increaseFontSize())); action = new QAction(i18n("Decrease Font Size"), this); action->setShortcut(Qt::CTRL + Qt::Key_Less); addAction("fontsizedown", action); connect(action, SIGNAL(triggered()), this, SLOT(decreaseFontSize())); m_actionFormatFontFamily = new KoFontFamilyAction(this); m_actionFormatFontFamily->setText(i18n("Font Family")); addAction("format_fontfamily", m_actionFormatFontFamily); connect(m_actionFormatFontFamily, SIGNAL(triggered(QString)), this, SLOT(setFontFamily(QString))); m_variableMenu = new KActionMenu(i18n("Variable"), this); addAction("insert_variable", m_variableMenu); // ------------------- Actions with a key binding and no GUI item action = new QAction(i18n("Insert Non-Breaking Space"), this); addAction("nonbreaking_space", action); action->setShortcut(Qt::CTRL + Qt::Key_Space); connect(action, SIGNAL(triggered()), this, SLOT(nonbreakingSpace())); action = new QAction(i18n("Insert Non-Breaking Hyphen"), this); addAction("nonbreaking_hyphen", action); action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Minus); connect(action, SIGNAL(triggered()), this, SLOT(nonbreakingHyphen())); action = new QAction(i18n("Insert Index"), this); action->setShortcut(Qt::CTRL + Qt::Key_T); addAction("insert_index", action); connect(action, SIGNAL(triggered()), this, SLOT(insertIndexMarker())); action = new QAction(i18n("Insert Soft Hyphen"), this); addAction("soft_hyphen", action); //action->setShortcut(Qt::CTRL + Qt::Key_Minus); // TODO this one is also used for the kde-global zoom-out :( connect(action, SIGNAL(triggered()), this, SLOT(softHyphen())); if (useAdvancedText) { action = new QAction(i18n("Line Break"), this); addAction("line_break", action); action->setShortcut(Qt::SHIFT + Qt::Key_Return); connect(action, SIGNAL(triggered()), this, SLOT(lineBreak())); action = new QAction(koIcon("insert-pagebreak"), i18n("Page Break"), this); addAction("insert_framebreak", action); action->setShortcut(Qt::CTRL + Qt::Key_Return); connect(action, SIGNAL(triggered()), this, SLOT(insertFrameBreak())); action->setToolTip(i18n("Insert a page break")); action->setWhatsThis(i18n("All text after this point will be moved into the next page.")); } action = new QAction(i18n("Font..."), this); addAction("format_font", action); action->setShortcut(Qt::ALT + Qt::CTRL + Qt::Key_F); action->setToolTip(i18n("Change character size, font, boldface, italics etc.")); action->setWhatsThis(i18n("Change the attributes of the currently selected characters.")); connect(action, SIGNAL(triggered()), this, SLOT(selectFont())); m_actionFormatFontSize = new FontSizeAction(i18n("Font Size"), this); addAction("format_fontsize", m_actionFormatFontSize); connect(m_actionFormatFontSize, SIGNAL(fontSizeChanged(qreal)), this, SLOT(setFontSize(qreal))); m_actionFormatTextColor = new KoColorPopupAction(this); m_actionFormatTextColor->setIcon(koIcon("format-text-color")); m_actionFormatTextColor->setToolTip(i18n("Text Color...")); m_actionFormatTextColor->setText(i18n("Text Color")); addAction("format_textcolor", m_actionFormatTextColor); connect(m_actionFormatTextColor, SIGNAL(colorChanged(KoColor)), this, SLOT(setTextColor(KoColor))); m_actionFormatBackgroundColor = new KoColorPopupAction(this); m_actionFormatBackgroundColor->setIcon(koIcon("format-fill-color")); m_actionFormatBackgroundColor->setToolTip(i18n("Background Color...")); m_actionFormatBackgroundColor->setText(i18n("Background")); addAction("format_backgroundcolor", m_actionFormatBackgroundColor); connect(m_actionFormatBackgroundColor, SIGNAL(colorChanged(KoColor)), this, SLOT(setBackgroundColor(KoColor))); m_growWidthAction = new QAction(koIcon("zoom-fit-best"), i18n("Grow To Fit Width"), this); addAction("grow_to_fit_width", m_growWidthAction); m_growWidthAction->setCheckable(true); connect(m_growWidthAction, SIGNAL(triggered(bool)), this, SLOT(setGrowWidthToFit(bool))); m_growHeightAction = new QAction(koIcon("zoom-fit-best"), i18n("Grow To Fit Height"), this); addAction("grow_to_fit_height", m_growHeightAction); m_growHeightAction->setCheckable(true); connect(m_growHeightAction, SIGNAL(triggered(bool)), this, SLOT(setGrowHeightToFit(bool))); m_shrinkToFitAction = new QAction(koIcon("zoom-fit-best"), i18n("Shrink To Fit"), this); addAction("shrink_to_fit", m_shrinkToFitAction); m_shrinkToFitAction->setCheckable(true); connect(m_shrinkToFitAction, SIGNAL(triggered(bool)), this, SLOT(setShrinkToFit(bool))); if (useAdvancedText) { action = new QAction(koIcon("insert-table"), i18n("Insert Custom..."), this); addAction("insert_table", action); action->setToolTip(i18n("Insert a table into the document.")); connect(action, SIGNAL(triggered()), this, SLOT(insertTable())); action = new QAction(koIcon("edit-table-insert-row-above"), i18n("Row Above"), this); action->setToolTip(i18n("Insert Row Above")); addAction("insert_tablerow_above", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableRowAbove())); action = new QAction(koIcon("edit-table-insert-row-below"), i18n("Row Below"), this); action->setToolTip(i18n("Insert Row Below")); addAction("insert_tablerow_below", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableRowBelow())); action = new QAction(koIcon("edit-table-insert-column-left"), i18n("Column Left"), this); action->setToolTip(i18n("Insert Column Left")); addAction("insert_tablecolumn_left", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableColumnLeft())); action = new QAction(koIcon("edit-table-insert-column-right"), i18n("Column Right"), this); action->setToolTip(i18n("Insert Column Right")); addAction("insert_tablecolumn_right", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableColumnRight())); action = new QAction(koIcon("edit-table-delete-column"), i18n("Column"), this); action->setToolTip(i18n("Delete Column")); addAction("delete_tablecolumn", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteTableColumn())); action = new QAction(koIcon("edit-table-delete-row"), i18n("Row"), this); action->setToolTip(i18n("Delete Row")); addAction("delete_tablerow", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteTableRow())); action = new QAction(koIcon("edit-table-cell-merge"), i18n("Merge Cells"), this); addAction("merge_tablecells", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(mergeTableCells())); action = new QAction(koIcon("edit-table-cell-split"), i18n("Split Cells"), this); addAction("split_tablecells", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(splitTableCells())); action = new QAction(koIcon("borderpainter"), "", this); action->setToolTip(i18n("Select a border style and paint that style onto a table")); addAction("activate_borderpainter", action); } action = new QAction(i18n("Paragraph..."), this); addAction("format_paragraph", action); action->setShortcut(Qt::ALT + Qt::CTRL + Qt::Key_P); action->setToolTip(i18n("Change paragraph margins, text flow, borders, bullets, numbering etc.")); action->setWhatsThis(i18n("

Change paragraph margins, text flow, borders, bullets, numbering etc.

Select text in multiple paragraphs to change the formatting of all selected paragraphs.

If no text is selected, the paragraph where the cursor is located will be changed.

")); connect(action, SIGNAL(triggered()), this, SLOT(formatParagraph())); action = new QAction(i18n("Style Manager..."), this); action->setShortcut(Qt::ALT + Qt::CTRL + Qt::Key_S); action->setToolTip(i18n("Change attributes of styles")); action->setWhatsThis(i18n("

Change font and paragraph attributes of styles.

Multiple styles can be changed using the dialog box.

")); addAction("format_stylist", action); connect(action, SIGNAL(triggered()), this, SLOT(showStyleManager())); action = KStandardAction::selectAll(this, SLOT(selectAll()), this); addAction("edit_select_all", action); action = new QAction(i18n("Special Character..."), this); action->setIcon(koIcon("character-set")); action->setShortcut(Qt::ALT + Qt::SHIFT + Qt::Key_C); addAction("insert_specialchar", action); action->setToolTip(i18n("Insert one or more symbols or characters not found on the keyboard")); action->setWhatsThis(i18n("Insert one or more symbols or characters not found on the keyboard.")); connect(action, SIGNAL(triggered()), this, SLOT(insertSpecialCharacter())); action = new QAction(i18n("Repaint"), this); action->setIcon(koIcon("view-refresh")); addAction("repaint", action); connect(action, SIGNAL(triggered()), this, SLOT(relayoutContent())); action = new QAction(i18n("Insert Comment"), this); addAction("insert_annotation", action); action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C); connect(action, SIGNAL(triggered()), this, SLOT(insertAnnotation())); #ifndef NDEBUG action = new QAction("Paragraph Debug", this); // do NOT add i18n! action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::ALT + Qt::Key_P); addAction("detailed_debug_paragraphs", action); connect(action, SIGNAL(triggered()), this, SLOT(debugTextDocument())); action = new QAction("Styles Debug", this); // do NOT add i18n! action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::ALT + Qt::Key_S); addAction("detailed_debug_styles", action); connect(action, SIGNAL(triggered()), this, SLOT(debugTextStyles())); #endif } #ifndef NDEBUG #include "tests/MockShapes.h" #include #include #include TextTool::TextTool(MockCanvas *canvas) // constructor for our unit tests; : KoToolBase(canvas), m_textShape(0), m_textShapeData(0), m_changeTracker(0), m_allowActions(true), m_allowAddUndoCommand(true), m_allowResourceManagerUpdates(true), m_prevCursorPosition(-1), m_caretTimer(this), m_caretTimerState(true), m_currentCommand(0), m_currentCommandHasChildren(false), m_specialCharacterDocker(0), m_textEditingPlugins(0) , m_editTipTimer(this) , m_delayedEnsureVisible(false) , m_tableDraggedOnce(false) , m_tablePenMode(false) { // we could init some vars here, but we probably don't have to QLocale::setDefault(QLocale("en")); QTextDocument *document = new QTextDocument(); // this document is leaked KoInlineTextObjectManager *inlineManager = new KoInlineTextObjectManager(); KoTextDocument(document).setInlineTextObjectManager(inlineManager); KoTextRangeManager *locationManager = new KoTextRangeManager(); KoTextDocument(document).setTextRangeManager(locationManager); m_textEditor = new KoTextEditor(document); KoTextDocument(document).setTextEditor(m_textEditor.data()); m_toolSelection = new TextToolSelection(m_textEditor); m_changeTracker = new KoChangeTracker(); KoTextDocument(document).setChangeTracker(m_changeTracker); KoTextDocument(document).setUndoStack(new KUndo2Stack()); } #endif TextTool::~TextTool() { delete m_toolSelection; } void TextTool::showEditTip() { if (!m_textShapeData || m_editTipPointedAt.position == -1) return; QTextCursor c(m_textShapeData->document()); c.setPosition(m_editTipPointedAt.position); QString text = "

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

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

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

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

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width(); } QToolTip::hideText(); if (toolTipWidth) { QRect keepRect(m_editTipPos - QPoint(3,3), QSize(6,6)); QToolTip::showText(m_editTipPos - QPoint(toolTipWidth/2, 0), text, canvas()->canvasWidget(), keepRect); } } void TextTool::blinkCaret() { if (!(canvas()->canvasWidget() ? canvas()->canvasWidget()->hasFocus() : canvas()->canvasItem()->hasFocus())) { m_caretTimer.stop(); m_caretTimerState = false; // not visible. } else { m_caretTimerState = !m_caretTimerState; } repaintCaret(); } void TextTool::relayoutContent() { KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); foreach (KoTextLayoutRootArea *rootArea, lay->rootAreas()) { rootArea->setDirty(); } lay->emitLayoutIsDirty(); } void TextTool::paint(QPainter &painter, const KoViewConverter &converter) { if (m_textEditor.isNull()) return; if (canvas() && (( canvas()->canvasWidget() && canvas()->canvasWidget()->hasFocus()) || (canvas()->canvasItem() && canvas()->canvasItem()->hasFocus()) ) && !m_caretTimer.isActive()) { // make sure we blink m_caretTimer.start(); m_caretTimerState = true; } if (!m_caretTimerState) m_caretTimer.setInterval(500); // we set it lower during typing, so set it back to normal if (!m_textShapeData) return; if (m_textShapeData->isDirty()) return; qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); painter.save(); QTransform shapeMatrix = m_textShape->absoluteTransformation(&converter); shapeMatrix.scale(zoomX, zoomY); shapeMatrix.translate(0, -m_textShapeData->documentOffset()); // Possibly draw table dragging visual cues const qreal boxHeight = 20; if (m_tableDragInfo.tableHit == KoPointedAt::ColumnDivider) { QPointF anchorPos = m_tableDragInfo.tableDividerPos - QPointF(m_dx, 0.0); if (m_tableDragInfo.tableColumnDivider > 0) { //let's draw left qreal w = m_tableDragInfo.tableLeadSize - m_dx; QRectF rect(anchorPos - QPointF(w, 0.0), QSizeF(w, 0.0)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setHeight(boxHeight); drawRect.moveTop(drawRect.top() - 1.5 * boxHeight); QString label = m_unit.toUserStringValue(w); int labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); - painter.setPen(QColor(0, 0, 0, 196)); + painter.setPen(QPen(QColor(0, 0, 0, 196), 0)); if (labelWidth + 10 < drawRect.width()) { QPointF centerLeft(drawRect.left(), drawRect.center().y()); QPointF centerRight(drawRect.right(), drawRect.center().y()); painter.drawLine(centerLeft, drawRect.center() - QPointF(labelWidth/2+5, 0.0)); painter.drawLine(centerLeft, centerLeft + QPointF(7, -5)); painter.drawLine(centerLeft, centerLeft + QPointF(7, 5)); painter.drawLine(drawRect.center() + QPointF(labelWidth/2+5, 0.0), centerRight); painter.drawLine(centerRight, centerRight + QPointF(-7, -5)); painter.drawLine(centerRight, centerRight + QPointF(-7, 5)); painter.drawText(drawRect, Qt::AlignCenter, label); } } if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns()) { //let's draw right qreal w = m_tableDragInfo.tableTrailSize + m_dx; QRectF rect(anchorPos, QSizeF(w, 0.0)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setHeight(boxHeight); drawRect.moveTop(drawRect.top() - 1.5 * boxHeight); QString label; int labelWidth; if (m_tableDragWithShift) { label = i18n("follows along"); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); drawRect.setWidth(2 * labelWidth); QLinearGradient g(drawRect.topLeft(), drawRect.topRight()); g.setColorAt(0.6, QColor(255, 64, 64, 196)); g.setColorAt(1.0, QColor(255, 64, 64, 0)); QBrush brush(g); painter.fillRect(drawRect, brush); } else { label = m_unit.toUserStringValue(w); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); drawRect.setHeight(boxHeight); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); } - painter.setPen(QColor(0, 0, 0, 196)); + painter.setPen(QPen(QColor(0, 0, 0, 196), 0)); if (labelWidth + 10 < drawRect.width()) { QPointF centerLeft(drawRect.left(), drawRect.center().y()); QPointF centerRight(drawRect.right(), drawRect.center().y()); painter.drawLine(centerLeft, drawRect.center() - QPointF(labelWidth/2+5, 0.0)); painter.drawLine(centerLeft, centerLeft + QPointF(7, -5)); painter.drawLine(centerLeft, centerLeft + QPointF(7, 5)); if (!m_tableDragWithShift) { painter.drawLine(drawRect.center() + QPointF(labelWidth/2+5, 0.0), centerRight); painter.drawLine(centerRight, centerRight + QPointF(-7, -5)); painter.drawLine(centerRight, centerRight + QPointF(-7, 5)); } painter.drawText(drawRect, Qt::AlignCenter, label); } if (!m_tableDragWithShift) { // let's draw a helper text too label = i18n("Press shift to not resize this"); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); labelWidth += 10; //if (labelWidth < drawRect.width()) { drawRect.moveTop(drawRect.top() + boxHeight); drawRect.moveLeft(drawRect.left() + (drawRect.width() - labelWidth)/2); drawRect.setWidth(labelWidth); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); painter.drawText(drawRect, Qt::AlignCenter, label); } } } } // Possibly draw table dragging visual cues if (m_tableDragInfo.tableHit == KoPointedAt::RowDivider) { QPointF anchorPos = m_tableDragInfo.tableDividerPos - QPointF(0.0, m_dy); if (m_tableDragInfo.tableRowDivider > 0) { qreal h = m_tableDragInfo.tableLeadSize - m_dy; QRectF rect(anchorPos - QPointF(0.0, h), QSizeF(0.0, h)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setWidth(boxHeight); drawRect.moveLeft(drawRect.left() - 1.5 * boxHeight); QString label = m_unit.toUserStringValue(h); QRectF labelRect = QFontMetrics(QToolTip::font()).boundingRect(label); labelRect.setHeight(boxHeight); labelRect.setWidth(labelRect.width() + 10); labelRect.moveTopLeft(drawRect.center() - QPointF(labelRect.width(), labelRect.height())/2); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); painter.fillRect(labelRect, QColor(64, 255, 64, 196)); - painter.setPen(QColor(0, 0, 0, 196)); + painter.setPen(QPen(QColor(0, 0, 0, 196), 0)); if (labelRect.height() + 10 < drawRect.height()) { QPointF centerTop(drawRect.center().x(), drawRect.top()); QPointF centerBottom(drawRect.center().x(), drawRect.bottom()); painter.drawLine(centerTop, drawRect.center() - QPointF(0.0, labelRect.height()/2+5)); painter.drawLine(centerTop, centerTop + QPointF(-5, 7)); painter.drawLine(centerTop, centerTop + QPointF(5, 7)); painter.drawLine(drawRect.center() + QPointF(0.0, labelRect.height()/2+5), centerBottom); painter.drawLine(centerBottom, centerBottom + QPointF(-5, -7)); painter.drawLine(centerBottom, centerBottom + QPointF(5, -7)); } painter.drawText(labelRect, Qt::AlignCenter, label); } } if (m_caretTimerState) { // Lets draw the caret ourselves, as the Qt method doesn't take cursor // charFormat into consideration. QTextBlock block = m_textEditor.data()->block(); if (block.isValid()) { int posInParag = m_textEditor.data()->position() - block.position(); if (posInParag <= block.layout()->preeditAreaPosition()) posInParag += block.layout()->preeditAreaText().length(); QTextLine tl = block.layout()->lineForTextPosition(m_textEditor.data()->position() - block.position()); if (tl.isValid()) { painter.setRenderHint(QPainter::Antialiasing, false); QRectF rect = caretRect(m_textEditor.data()->cursor()); QPointF baselinePoint; if (tl.ascent() > 0) { QFontMetricsF fm(m_textEditor.data()->charFormat().font(), painter.device()); rect.setY(rect.y() + tl.ascent() - qMin(tl.ascent(), fm.ascent())); rect.setHeight(qMin(tl.ascent(), fm.ascent()) + qMin(tl.descent(), fm.descent())); baselinePoint = QPoint(rect.x(), rect.y() + tl.ascent()); } else { //line only filled with characters-without-size (eg anchors) // layout will make sure line has height of block font QFontMetricsF fm(block.charFormat().font(), painter.device()); rect.setHeight(fm.ascent() + fm.descent()); baselinePoint = QPoint(rect.x(), rect.y() + fm.ascent()); } QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomLeft())); drawRect.setWidth(2); painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination); if (m_textEditor.data()->isEditProtected(true)) { QRectF circleRect(shapeMatrix.map(baselinePoint),QSizeF(14, 14)); circleRect.translate(-6.5, -6.5); QPen pen(QColor(16, 255, 255)); pen.setWidthF(2.0); painter.setPen(pen); painter.setRenderHint(QPainter::Antialiasing, true); painter.drawEllipse(circleRect); painter.drawLine(circleRect.topLeft() + QPointF(4.5,4.5), circleRect.bottomRight() - QPointF(4.5,4.5)); } else { painter.fillRect(drawRect, QColor(128, 255, 128)); } } } } painter.restore(); } void TextTool::updateSelectedShape(const QPointF &point, bool noDocumentChange) { QRectF area(point, QSizeF(1, 1)); if (m_textEditor.data()->hasSelection()) repaintSelection(); else repaintCaret(); QList sortedShapes = canvas()->shapeManager()->shapesAt(area, true); qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); for (int count = sortedShapes.count() - 1; count >= 0; count--) { KoShape *shape = sortedShapes.at(count); if (shape->isContentProtected()) continue; TextShape *textShape = dynamic_cast(shape); if (textShape) { if (textShape != m_textShape) { if (static_cast(textShape->userData())->document() != m_textShapeData->document()) { //we should only change to another document if allowed if (noDocumentChange) { return; } // if we change to another textdocument we need to remove selection in old document // or it would continue to be painted etc m_textEditor.data()->setPosition(m_textEditor.data()->position()); } m_textShape = textShape; setShapeData(static_cast(m_textShape->userData())); // This is how we inform the rulers of the active range // For now we will not consider table cells, but just give the shape dimensions QVariant v; QRectF rect(QPoint(), m_textShape->size()); rect = m_textShape->absoluteTransformation(0).mapRect(rect); v.setValue(rect); canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, v); } return; } } } void TextTool::mousePressEvent(KoPointerEvent *event) { if (m_textEditor.isNull()) return; // request the software keyboard, if any if (event->button() == Qt::LeftButton && qApp->autoSipEnabled()) { QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(qApp->style()->styleHint(QStyle::SH_RequestSoftwareInputPanel)); // the two following bools just make it all a lot easier to read in the following if() // basically, we require a widget for this to work (passing nullptr to QApplication::sendEvent // crashes) and there are three tests any one of which can be true to trigger the event const bool hasWidget = canvas()->canvasWidget(); const bool hasItem = canvas()->canvasItem(); if ((behavior == QStyle::RSIP_OnMouseClick && (hasWidget || hasItem)) || (hasWidget && canvas()->canvasWidget()->hasFocus()) || (hasItem && canvas()->canvasItem()->hasFocus())) { QEvent event(QEvent::RequestSoftwareInputPanel); if (hasWidget) { QApplication::sendEvent(canvas()->canvasWidget(), &event); } else { QApplication::sendEvent(canvas()->canvasItem(), &event); } } } bool shiftPressed = event->modifiers() & Qt::ShiftModifier; updateSelectedShape(event->point, shiftPressed); KoSelection *selection = canvas()->shapeManager()->selection(); if (m_textShape && !selection->isSelected(m_textShape) && m_textShape->isSelectable()) { selection->deselectAll(); selection->select(m_textShape); } KoPointedAt pointedAt = hitTest(event->point); m_tableDraggedOnce = false; m_clickWithinSelection = false; if (pointedAt.position != -1) { m_tablePenMode = false; if ((event->button() == Qt::LeftButton) && !shiftPressed && m_textEditor.data()->hasSelection() && m_textEditor.data()->isWithinSelection(pointedAt.position)) { m_clickWithinSelection = true; m_draggingOrigin = event->pos(); //we store the pixel pos } else if (! (event->button() == Qt::RightButton && m_textEditor.data()->hasSelection() && m_textEditor.data()->isWithinSelection(pointedAt.position))) { m_textEditor.data()->setPosition(pointedAt.position, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); useCursor(Qt::IBeamCursor); } m_tableDragInfo.tableHit = KoPointedAt::None; if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } } else { if (event->button() == Qt::RightButton) { m_tablePenMode = false; KoTextEditingPlugin *plugin = textEditingPluginContainer()->spellcheck(); if (plugin) plugin->setCurrentCursorPosition(m_textShapeData->document(), -1); event->ignore(); } else if (m_tablePenMode) { m_textEditor.data()->beginEditBlock(kundo2_i18n("Change Border Formatting")); if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { if (pointedAt.tableColumnDivider < pointedAt.table->columns()) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider, KoBorder::LeftBorder, m_tablePenBorderData); } if (pointedAt.tableColumnDivider > 0) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider - 1, KoBorder::RightBorder, m_tablePenBorderData); } } else if (pointedAt.tableHit == KoPointedAt::RowDivider) { if (pointedAt.tableRowDivider < pointedAt.table->rows()) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider, KoBorder::TopBorder, m_tablePenBorderData); } if (pointedAt.tableRowDivider > 0) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider-1, pointedAt.tableColumnDivider, KoBorder::BottomBorder, m_tablePenBorderData); } } m_textEditor.data()->endEditBlock(); } else { m_tableDragInfo = pointedAt; m_tablePenMode = false; } return; } if (shiftPressed) // altered selection. repaintSelection(); else repaintCaret(); updateSelectionHandler(); updateStyleManager(); updateActions(); //activate context-menu for spelling-suggestions if (event->button() == Qt::RightButton) { KoTextEditingPlugin *plugin = textEditingPluginContainer()->spellcheck(); if (plugin) plugin->setCurrentCursorPosition(m_textShapeData->document(), m_textEditor.data()->position()); event->ignore(); } if (event->button() == Qt::MidButton) { // Paste const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Selection); // on windows we do not have data if we try to paste this selection if (data) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data, canvas()->resourceManager()); editingPluginEvents(); } } } void TextTool::setShapeData(KoTextShapeData *data) { bool docChanged = !data || !m_textShapeData || m_textShapeData->document() != data->document(); if (m_textShapeData) { disconnect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); } m_textShapeData = data; if (!m_textShapeData) return; connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); if (docChanged) { if (!m_textEditor.isNull()) { disconnect(m_textEditor.data(), SIGNAL(textFormatChanged()), this, SLOT(updateActions())); } m_textEditor = KoTextDocument(m_textShapeData->document()).textEditor(); Q_ASSERT(m_textEditor.data()); if (!m_toolSelection) { m_toolSelection = new TextToolSelection(m_textEditor.data()); } else { m_toolSelection->m_editor = m_textEditor.data(); } m_variableMenu->menu()->clear(); KoTextDocument document(m_textShapeData->document()); foreach (QAction *action, document.inlineTextObjectManager()->createInsertVariableActions(canvas())) { m_variableMenu->addAction(action); connect(action, SIGNAL(triggered()), this, SLOT(returnFocusToCanvas())); } connect(m_textEditor.data(), SIGNAL(textFormatChanged()), this, SLOT(updateActions())); updateActions(); } } void TextTool::updateSelectionHandler() { if (m_textEditor) { emit selectionChanged(m_textEditor.data()->hasSelection()); if (m_textEditor.data()->hasSelection()) { QClipboard *clipboard = QApplication::clipboard(); if (clipboard->supportsSelection()) clipboard->setText(m_textEditor.data()->selectedText(), QClipboard::Selection); } } KoCanvasResourceManager *p = canvas()->resourceManager(); m_allowResourceManagerUpdates = false; if (m_textEditor && m_textShapeData) { p->setResource(KoText::CurrentTextPosition, m_textEditor.data()->position()); p->setResource(KoText::CurrentTextAnchor, m_textEditor.data()->anchor()); QVariant variant; variant.setValue(m_textShapeData->document()); p->setResource(KoText::CurrentTextDocument, variant); } else { p->clearResource(KoText::CurrentTextPosition); p->clearResource(KoText::CurrentTextAnchor); p->clearResource(KoText::CurrentTextDocument); } m_allowResourceManagerUpdates = true; } QMimeData *TextTool::generateMimeData() const { if (!m_textShapeData || m_textEditor.isNull() || !m_textEditor.data()->hasSelection()) return 0; int from = m_textEditor.data()->position(); int to = m_textEditor.data()->anchor(); KoTextOdfSaveHelper saveHelper(m_textShapeData->document(), from, to); KoTextDrag drag; #ifdef SHOULD_BUILD_RDF KoDocumentResourceManager *rm = 0; if (canvas()->shapeController()) { rm = canvas()->shapeController()->resourceManager(); } if (rm && rm->hasResource(KoText::DocumentRdf)) { KoDocumentRdfBase *rdf = qobject_cast(rm->resource(KoText::DocumentRdf).value()); if (rdf) { saveHelper.setRdfModel(rdf->model()); } } #endif drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper); QTextDocumentFragment fragment = m_textEditor.data()->selection(); drag.setData("text/plain", fragment.toPlainText().toUtf8()); return drag.takeMimeData(); } TextEditingPluginContainer *TextTool::textEditingPluginContainer() { m_textEditingPlugins = canvas()->resourceManager()-> resource(TextEditingPluginContainer::ResourceId).value(); if (m_textEditingPlugins == 0) { m_textEditingPlugins = new TextEditingPluginContainer(canvas()->resourceManager()); QVariant variant; variant.setValue(m_textEditingPlugins.data()); canvas()->resourceManager()->setResource(TextEditingPluginContainer::ResourceId, variant); foreach (KoTextEditingPlugin* plugin, m_textEditingPlugins->values()) { connect(plugin, SIGNAL(startMacro(QString)), this, SLOT(startMacro(QString))); connect(plugin, SIGNAL(stopMacro()), this, SLOT(stopMacro())); const QHash actions = plugin->actions(); QHash::ConstIterator i = actions.begin(); while (i != actions.end()) { addAction(i.key(), i.value()); ++i; } } } return m_textEditingPlugins; } void TextTool::copy() const { QMimeData *mimeData = generateMimeData(); if (mimeData) { QApplication::clipboard()->setMimeData(mimeData); } } void TextTool::deleteSelection() { m_textEditor.data()->deleteChar(); editingPluginEvents(); } bool TextTool::paste() { const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Clipboard); // on windows we do not have data if we try to paste the selection if (!data) return false; // since this is not paste-as-text we will not paste in urls, but instead let KoToolProxy solve it if (data->hasUrls()) return false; if (data->hasFormat(KoOdf::mimeType(KoOdf::Text)) || data->hasText()) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data); editingPluginEvents(); return true; } return false; } void TextTool::cut() { if (m_textEditor.data()->hasSelection()) { copy(); KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Cut")); m_textEditor.data()->deleteChar(false, topCmd); m_textEditor.data()->endEditBlock(); } } QStringList TextTool::supportedPasteMimeTypes() const { QStringList list; list << "text/plain" << "application/vnd.oasis.opendocument.text"; return list; } void TextTool::dragMoveEvent(QDragMoveEvent *event, const QPointF &point) { if (event->mimeData()->hasFormat(KoOdf::mimeType(KoOdf::Text)) || event->mimeData()->hasFormat(KoOdf::mimeType(KoOdf::OpenOfficeClipboard)) || event->mimeData()->hasText()) { if (m_drag) { event->setDropAction(Qt::MoveAction); event->accept(); } else if (event->proposedAction() == Qt::CopyAction) { event->acceptProposedAction(); } else { event->ignore(); return; } KoPointedAt pointedAt = hitTest(point); if (pointedAt.position == -1) { event->ignore(); } if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } if (m_preDragSelection.cursor.isNull()) { repaintSelection(); m_preDragSelection.cursor = QTextCursor(*m_textEditor.data()->cursor()); if (m_drag) { // Make a selection that looks like the current cursor selection // so we can move the real carent around freely QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); m_preDragSelection.format = QTextCharFormat(); m_preDragSelection.format.setBackground(qApp->palette().brush(QPalette::Highlight)); m_preDragSelection.format.setForeground(qApp->palette().brush(QPalette::HighlightedText)); sels.append(m_preDragSelection); KoTextDocument(m_textShapeData->document()).setSelections(sels); } // else we wantt the selection ot disappaear } repaintCaret(); // will erase caret m_textEditor.data()->setPosition(pointedAt.position); repaintCaret(); // will paint caret in new spot // Selection has visually not appeared at a new spot so no need to repaint it } } void TextTool::dragLeaveEvent(QDragLeaveEvent *event) { if (m_drag) { // restore the old selections QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); sels.pop_back(); KoTextDocument(m_textShapeData->document()).setSelections(sels); } repaintCaret(); // will erase caret in old spot m_textEditor.data()->setPosition(m_preDragSelection.cursor.anchor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.position(), QTextCursor::KeepAnchor); repaintCaret(); // will paint caret in new spot if (!m_drag) { repaintSelection(); // will paint selection again } // mark that we now are back to normal selection m_preDragSelection.cursor = QTextCursor(); event->accept(); } void TextTool::dropEvent(QDropEvent *event, const QPointF &) { if (m_drag) { // restore the old selections QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); sels.pop_back(); KoTextDocument(m_textShapeData->document()).setSelections(sels); } QTextCursor insertCursor(*m_textEditor.data()->cursor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.anchor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.position(), QTextCursor::KeepAnchor); repaintSelection(); // will erase the selection in new spot if (m_drag) { m_textEditor.data()->deleteChar(); } m_prevCursorPosition = insertCursor.position(); m_textEditor.data()->setPosition(m_prevCursorPosition); m_textEditor.data()->paste(canvas(), event->mimeData()); m_textEditor.data()->setPosition(m_prevCursorPosition); //since the paste made insertCursor we can now use that for the end position m_textEditor.data()->setPosition(insertCursor.position(), QTextCursor::KeepAnchor); // mark that we no are back to normal selection m_preDragSelection.cursor = QTextCursor(); event->accept(); } KoPointedAt TextTool::hitTest(const QPointF & point) const { if (!m_textShape || !m_textShapeData) { return KoPointedAt(); } QPointF p = m_textShape->convertScreenPos(point); KoTextLayoutRootArea *rootArea = m_textShapeData->rootArea(); return rootArea ? rootArea->hitTest(p, Qt::FuzzyHit) : KoPointedAt(); } void TextTool::mouseDoubleClickEvent(KoPointerEvent *event) { if (canvas()->shapeManager()->shapeAt(event->point) != m_textShape) { event->ignore(); // allow the event to be used by another return; } if (event->modifiers() & Qt::ShiftModifier) { // When whift is pressed we behave as a single press return mousePressEvent(event); } m_textEditor.data()->select(QTextCursor::WordUnderCursor); m_clickWithinSelection = false; repaintSelection(); updateSelectionHandler(); } void TextTool::mouseTripleClickEvent(KoPointerEvent *event) { if (canvas()->shapeManager()->shapeAt(event->point) != m_textShape) { event->ignore(); // allow the event to be used by another return; } if (event->modifiers() & Qt::ShiftModifier) { // When whift is pressed we behave as a single press return mousePressEvent(event); } m_textEditor.data()->clearSelection(); m_textEditor.data()->movePosition(QTextCursor::StartOfBlock); m_textEditor.data()->movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); m_clickWithinSelection = false; repaintSelection(); updateSelectionHandler(); } void TextTool::mouseMoveEvent(KoPointerEvent *event) { m_editTipPos = event->globalPos(); if (event->buttons()) { updateSelectedShape(event->point, true); } m_editTipTimer.stop(); if (QToolTip::isVisible()) QToolTip::hideText(); KoPointedAt pointedAt = hitTest(event->point); if (event->buttons() == Qt::NoButton) { if (m_tablePenMode) { if (pointedAt.tableHit == KoPointedAt::ColumnDivider || pointedAt.tableHit == KoPointedAt::RowDivider) { useTableBorderCursor(); } else { useCursor(Qt::IBeamCursor); } // do nothing else return; } if (!m_textShapeData || pointedAt.position < 0) { if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { useCursor(Qt::SplitHCursor); m_draggingOrigin = event->point; } else if (pointedAt.tableHit == KoPointedAt::RowDivider) { if (pointedAt.tableRowDivider > 0) { useCursor(Qt::SplitVCursor); m_draggingOrigin = event->point; } else useCursor(Qt::IBeamCursor); } else { useCursor(Qt::IBeamCursor); } return; } QTextCursor mouseOver(m_textShapeData->document()); mouseOver.setPosition(pointedAt.position); if (m_changeTracker && m_changeTracker->containsInlineChanges(mouseOver.charFormat())) { m_editTipPointedAt = pointedAt; if (QToolTip::isVisible()) { QTimer::singleShot(0, this, SLOT(showEditTip())); }else { m_editTipTimer.start(); } } if ((pointedAt.bookmark || !pointedAt.externalHRef.isEmpty()) || pointedAt.note || (pointedAt.noteReference>0)) { if (event->modifiers() & Qt::ControlModifier) { useCursor(Qt::PointingHandCursor); } m_editTipPointedAt = pointedAt; if (QToolTip::isVisible()) { QTimer::singleShot(0, this, SLOT(showEditTip())); }else { m_editTipTimer.start(); } return; } // check if mouse pointer is over shape with hyperlink KoShape *selectedShape = canvas()->shapeManager()->shapeAt(event->point); if (selectedShape != 0 && selectedShape != m_textShape && selectedShape->hyperLink().size() != 0) { useCursor(Qt::PointingHandCursor); return; } useCursor(Qt::IBeamCursor); // Set Arrow Cursor when mouse is on top of annotation shape. if (selectedShape) { if (selectedShape->shapeId() == "AnnotationTextShapeID") { QPointF point(event->point); if (point.y() <= (selectedShape->position().y() + 25)) useCursor(Qt::ArrowCursor); } } return; } else { if (m_tableDragInfo.tableHit == KoPointedAt::ColumnDivider) { m_tableDragWithShift = event->modifiers() & Qt::ShiftModifier; if(m_tableDraggedOnce) { canvas()->shapeController()->resourceManager()->undoStack()->undo(); } KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Adjust Column Width")); m_dx = m_draggingOrigin.x() - event->point.x(); if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns() && m_tableDragInfo.tableTrailSize + m_dx < 0) { m_dx = -m_tableDragInfo.tableTrailSize; } if (m_tableDragInfo.tableColumnDivider > 0) { if (m_tableDragInfo.tableLeadSize - m_dx < 0) { m_dx = m_tableDragInfo.tableLeadSize; } m_textEditor.data()->adjustTableColumnWidth(m_tableDragInfo.table, m_tableDragInfo.tableColumnDivider - 1, m_tableDragInfo.tableLeadSize - m_dx, topCmd); } else { m_textEditor.data()->adjustTableWidth(m_tableDragInfo.table, -m_dx, 0.0); } if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns()) { if (!m_tableDragWithShift) { m_textEditor.data()->adjustTableColumnWidth(m_tableDragInfo.table, m_tableDragInfo.tableColumnDivider, m_tableDragInfo.tableTrailSize + m_dx, topCmd); } } else { m_tableDragWithShift = true; // act like shift pressed } if (m_tableDragWithShift) { m_textEditor.data()->adjustTableWidth(m_tableDragInfo.table, 0.0, m_dx); } m_textEditor.data()->endEditBlock(); m_tableDragInfo.tableDividerPos.setY(m_textShape->convertScreenPos(event->point).y()); if (m_tableDraggedOnce) { //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) canvas()->canvasWidget()->update(); if (canvas()->canvasItem()) canvas()->canvasItem()->update(); } m_tableDraggedOnce = true; } else if (m_tableDragInfo.tableHit == KoPointedAt::RowDivider) { if(m_tableDraggedOnce) { canvas()->shapeController()->resourceManager()->undoStack()->undo(); } if (m_tableDragInfo.tableRowDivider > 0) { KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Adjust Row Height")); m_dy = m_draggingOrigin.y() - event->point.y(); if (m_tableDragInfo.tableLeadSize - m_dy < 0) { m_dy = m_tableDragInfo.tableLeadSize; } m_textEditor.data()->adjustTableRowHeight(m_tableDragInfo.table, m_tableDragInfo.tableRowDivider - 1, m_tableDragInfo.tableLeadSize - m_dy, topCmd); m_textEditor.data()->endEditBlock(); m_tableDragInfo.tableDividerPos.setX(m_textShape->convertScreenPos(event->point).x()); if (m_tableDraggedOnce) { //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) canvas()->canvasWidget()->update(); if (canvas()->canvasItem()) canvas()->canvasItem()->update(); } m_tableDraggedOnce = true; } } else if (m_tablePenMode) { // do nothing } else if (m_clickWithinSelection) { if (!m_drag && (event->pos() - m_draggingOrigin).manhattanLength() >= QApplication::startDragDistance()) { QMimeData *mimeData = generateMimeData(); if (mimeData) { m_drag = new QDrag(canvas()->canvasWidget()); m_drag->setMimeData(mimeData); m_drag->exec(Qt::MoveAction | Qt::CopyAction, Qt::CopyAction); m_drag = 0; } } } else { useCursor(Qt::IBeamCursor); if (pointedAt.position == m_textEditor.data()->position()) return; if (pointedAt.position >= 0) { if (m_textEditor.data()->hasSelection()) repaintSelection(); // will erase selection else repaintCaret(); m_textEditor.data()->setPosition(pointedAt.position, QTextCursor::KeepAnchor); if (m_textEditor.data()->hasSelection()) repaintSelection(); else repaintCaret(); } } updateSelectionHandler(); } } void TextTool::mouseReleaseEvent(KoPointerEvent *event) { event->ignore(); editingPluginEvents(); m_tableDragInfo.tableHit = KoPointedAt::None; if (m_tableDraggedOnce) { m_tableDraggedOnce = false; //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) canvas()->canvasWidget()->update(); if (canvas()->canvasItem()) canvas()->canvasItem()->update(); } if (!m_textShapeData) return; // check if mouse pointer is not over some shape with hyperlink KoShape *selectedShape = canvas()->shapeManager()->shapeAt(event->point); if (selectedShape != 0 && selectedShape != m_textShape && selectedShape->hyperLink().size() != 0) { QString url = selectedShape->hyperLink(); runUrl(event, url); return; } KoPointedAt pointedAt = hitTest(event->point); if (m_clickWithinSelection && !m_drag) { if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } repaintCaret(); // will erase caret repaintSelection(); // will erase selection m_textEditor.data()->setPosition(pointedAt.position); repaintCaret(); // will paint caret in new spot } // Is there an anchor here ? if ((event->modifiers() & Qt::ControlModifier) && !m_textEditor.data()->hasSelection()) { if (pointedAt.bookmark) { m_textEditor.data()->setPosition(pointedAt.bookmark->rangeStart()); ensureCursorVisible(); event->accept(); return; } if (pointedAt.note) { m_textEditor.data()->setPosition(pointedAt.note->textFrame()->firstPosition()); ensureCursorVisible(); event->accept(); return; } if (pointedAt.noteReference>0) { m_textEditor.data()->setPosition(pointedAt.noteReference); ensureCursorVisible(); event->accept(); return; } if (!pointedAt.externalHRef.isEmpty()) { runUrl(event, pointedAt.externalHRef); } } } void TextTool::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 TextTool::keyPressEvent(QKeyEvent *event) { int destinationPosition = -1; // for those cases where the moveOperation is not relevant; QTextCursor::MoveOperation moveOperation = QTextCursor::NoMove; KoTextEditor *textEditor = m_textEditor.data(); m_tablePenMode = false; // keypress always stops the table (border) pen mode Q_ASSERT(textEditor); if (event->key() == Qt::Key_Backspace) { if (!textEditor->hasSelection() && textEditor->block().textList() && (textEditor->position() == textEditor->block().position()) && !(m_changeTracker && m_changeTracker->recordChanges())) { if (!textEditor->blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) { // backspace at beginning of numbered list item, makes it unnumbered textEditor->toggleListNumbering(false); } else { KoListLevelProperties llp; llp.setLabelType(KoListStyle::None); llp.setLevel(0); // backspace on numbered, empty parag, removes numbering. textEditor->setListProperties(llp); } } else if (textEditor->position() > 0 || textEditor->hasSelection()) { if (!textEditor->hasSelection() && event->modifiers() & Qt::ControlModifier) { // delete prev word. textEditor->movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor); } textEditor->deletePreviousChar(); editingPluginEvents(); } } else if ((event->key() == Qt::Key_Tab) && ((!textEditor->hasSelection() && (textEditor->position() == textEditor->block().position())) || (textEditor->block().document()->findBlock(textEditor->anchor()) != textEditor->block().document()->findBlock(textEditor->position()))) && textEditor->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::IncreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, 1); textEditor->addCommand(cll); editingPluginEvents(); } else if ((event->key() == Qt::Key_Backtab) && ((!textEditor->hasSelection() && (textEditor->position() == textEditor->block().position())) || (textEditor->block().document()->findBlock(textEditor->anchor()) != textEditor->block().document()->findBlock(textEditor->position()))) && textEditor->block().textList() && !(m_changeTracker && m_changeTracker->recordChanges())) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::DecreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, 1); textEditor->addCommand(cll); editingPluginEvents(); } else if (event->key() == Qt::Key_Delete) { if (!textEditor->hasSelection() && event->modifiers() & Qt::ControlModifier) {// delete next word. textEditor->movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor); } // the event only gets through when the Del is not used in the app // if the app forwards Del then deleteSelection is used textEditor->deleteChar(); editingPluginEvents(); } else if ((event->key() == Qt::Key_Left) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Left; } else if ((event->key() == Qt::Key_Right) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Right; } else if ((event->key() == Qt::Key_Up) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Up; } else if ((event->key() == Qt::Key_Down) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Down; } else { // check for shortcuts. QKeySequence item(event->key() | ((Qt::ControlModifier | Qt::AltModifier) & event->modifiers())); if (hit(item, KStandardShortcut::Begin)) { // Goto beginning of the document. Default: Ctrl-Home destinationPosition = 0; } else if (hit(item, KStandardShortcut::End)) { // Goto end of the document. Default: Ctrl-End if (m_textShapeData) { QTextBlock last = m_textShapeData->document()->lastBlock(); destinationPosition = last.position() + last.length() - 1; } } else if (hit(item, KStandardShortcut::Prior)) { // page up // Scroll up one page. Default: Prior event->ignore(); // let app level actions handle it return; } else if (hit(item, KStandardShortcut::Next)) { // Scroll down one page. Default: Next event->ignore(); // let app level actions handle it return; } else if (hit(item, KStandardShortcut::BeginningOfLine)) // Goto beginning of current line. Default: Home moveOperation = QTextCursor::StartOfLine; else if (hit(item, KStandardShortcut::EndOfLine)) // Goto end of current line. Default: End moveOperation = QTextCursor::EndOfLine; else if (hit(item, KStandardShortcut::BackwardWord)) moveOperation = QTextCursor::WordLeft; else if (hit(item, KStandardShortcut::ForwardWord)) moveOperation = QTextCursor::WordRight; #ifdef Q_WS_MAC // Don't reject "alt" key, it may be used for typing text on Mac OS else if ((event->modifiers() & Qt::ControlModifier) #else else if ((event->modifiers() & (Qt::ControlModifier | Qt::AltModifier)) #endif || event->text().length() == 0 || event->key() == Qt::Key_Escape) { event->ignore(); return; } else if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) { m_prevCursorPosition = textEditor->position(); textEditor->newLine(); updateActions(); editingPluginEvents(); } else if ((event->key() == Qt::Key_Tab || !(event->text().length() == 1 && !event->text().at(0).isPrint()))) { // insert the text m_prevCursorPosition = textEditor->position(); startingSimpleEdit(); //signal editing plugins that this is a simple edit textEditor->insertText(event->text()); editingPluginEvents(); } } if (moveOperation != QTextCursor::NoMove || destinationPosition != -1) { useCursor(Qt::BlankCursor); bool shiftPressed = event->modifiers() & Qt::ShiftModifier; if (textEditor->hasSelection()) repaintSelection(); // will erase selection else repaintCaret(); QTextBlockFormat format = textEditor->blockFormat(); KoText::Direction dir = static_cast(format.intProperty(KoParagraphStyle::TextProgressionDirection)); bool isRtl; if (dir == KoText::AutoDirection) isRtl = textEditor->block().text().isRightToLeft(); else isRtl = dir == KoText::RightLeftTopBottom; if (isRtl) { // if RTL toggle direction of cursor movement. switch (moveOperation) { case QTextCursor::Left: moveOperation = QTextCursor::Right; break; case QTextCursor::Right: moveOperation = QTextCursor::Left; break; case QTextCursor::WordRight: moveOperation = QTextCursor::WordLeft; break; case QTextCursor::WordLeft: moveOperation = QTextCursor::WordRight; break; default: break; } } int prevPosition = textEditor->position(); if (moveOperation != QTextCursor::NoMove) textEditor->movePosition(moveOperation, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); else textEditor->setPosition(destinationPosition, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); if (moveOperation == QTextCursor::Down && prevPosition == textEditor->position()) { // change behavior a little big from Qt; at the bottom of the doc we go to the end of the doc textEditor->movePosition(QTextCursor::End, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); } if (shiftPressed) // altered selection. repaintSelection(); else repaintCaret(); updateActions(); editingPluginEvents(); } if (m_caretTimer.isActive()) { // make the caret not blink but decide on the action if its visible or not. m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret on while typing } if (moveOperation != QTextCursor::NoMove) // this difference in handling is need to prevent leaving a trail of old cursors onscreen ensureCursorVisible(); else m_delayedEnsureVisible = true; updateActions(); updateSelectionHandler(); } QVariant TextTool::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) return QVariant(); switch (query) { case Qt::ImMicroFocus: { // The rectangle covering the area of the input cursor in widget coordinates. QRectF rect = caretRect(textEditor->cursor()); rect.moveTop(rect.top() - m_textShapeData->documentOffset()); QTransform shapeMatrix = m_textShape->absoluteTransformation(&converter); qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); shapeMatrix.scale(zoomX, zoomY); rect = shapeMatrix.mapRect(rect); return rect.toRect(); } case Qt::ImFont: // The currently used font for text input. return textEditor->charFormat().font(); case Qt::ImCursorPosition: // The logical position of the cursor within the text surrounding the input area (see ImSurroundingText). return textEditor->position() - textEditor->block().position(); case Qt::ImSurroundingText: // The plain text around the input area, for example the current paragraph. return textEditor->block().text(); case Qt::ImCurrentSelection: // The currently selected text. return textEditor->selectedText(); default: ; // Qt 4.6 adds ImMaximumTextLength and ImAnchorPosition } return QVariant(); } void TextTool::inputMethodEvent(QInputMethodEvent *event) { KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) return; if (event->replacementLength() > 0) { textEditor->setPosition(textEditor->position() + event->replacementStart()); for (int i = event->replacementLength(); i > 0; --i) { textEditor->deleteChar(); } } if (!event->commitString().isEmpty()) { QKeyEvent ke(QEvent::KeyPress, -1, 0, event->commitString()); keyPressEvent(&ke); // The cursor may reside in a different block before vs. after keyPressEvent. QTextBlock block = textEditor->block(); QTextLayout *layout = block.layout(); Q_ASSERT(layout); layout->setPreeditArea(-1, QString()); } else { QTextBlock block = textEditor->block(); QTextLayout *layout = block.layout(); Q_ASSERT(layout); layout->setPreeditArea(textEditor->position() - block.position(), event->preeditString()); const_cast(textEditor->document())->markContentsDirty(textEditor->position(), event->preeditString().length()); } event->accept(); } void TextTool::ensureCursorVisible(bool moveView) { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) return; bool upToDate; QRectF cRect = caretRect(textEditor->cursor(), &upToDate); KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); KoTextLayoutRootArea *rootArea = lay->rootAreaForPoint(cRect.center()); if (rootArea && rootArea->associatedShape() && m_textShapeData->rootArea() != rootArea) { // If we have changed root area we need to update m_textShape and m_textShapeData m_textShape = static_cast(rootArea->associatedShape()); Q_ASSERT(m_textShape); disconnect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); m_textShapeData = static_cast(m_textShape->userData()); Q_ASSERT(m_textShapeData); connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); } if (!moveView) { return; } if (! upToDate) { // paragraph is not yet layouted. // The number one usecase for this is when the user pressed enter. // try to do it on next caret blink m_delayedEnsureVisible = true; return; // we shouldn't move to an obsolete position } cRect.moveTop(cRect.top() - m_textShapeData->documentOffset()); canvas()->ensureVisible(m_textShape->absoluteTransformation(0).mapRect(cRect)); } void TextTool::keyReleaseEvent(QKeyEvent *event) { event->accept(); } void TextTool::updateActions() { bool notInAnnotation = !dynamic_cast(m_textShape); KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) { return; } m_allowActions = false; //Update the characterStyle related GUI elements QTextCharFormat cf = textEditor->charFormat(); m_actionFormatBold->setChecked(cf.fontWeight() > QFont::Normal); m_actionFormatItalic->setChecked(cf.fontItalic()); m_actionFormatUnderline->setChecked(cf.intProperty(KoCharacterStyle::UnderlineType) != KoCharacterStyle::NoLineType); m_actionFormatStrikeOut->setChecked(cf.intProperty(KoCharacterStyle::StrikeOutType) != KoCharacterStyle::NoLineType); bool super = false, sub = false; switch (cf.verticalAlignment()) { case QTextCharFormat::AlignSuperScript: super = true; break; case QTextCharFormat::AlignSubScript: sub = true; break; default:; } m_actionFormatSuper->setChecked(super); m_actionFormatSub->setChecked(sub); m_actionFormatFontSize->setFontSize(cf.font().pointSizeF()); m_actionFormatFontFamily->setFont(cf.font().family()); KoTextShapeData::ResizeMethod resizemethod = KoTextShapeData::AutoResize; if(m_textShapeData) { resizemethod = m_textShapeData->resizeMethod(); } m_shrinkToFitAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_shrinkToFitAction->setChecked(resizemethod == KoTextShapeData::ShrinkToFitResize); m_growWidthAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_growWidthAction->setChecked(resizemethod == KoTextShapeData::AutoGrowWidth || resizemethod == KoTextShapeData::AutoGrowWidthAndHeight); m_growHeightAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_growHeightAction->setChecked(resizemethod == KoTextShapeData::AutoGrowHeight || resizemethod == KoTextShapeData::AutoGrowWidthAndHeight); //update paragraphStyle GUI element QTextBlockFormat bf = textEditor->blockFormat(); if (bf.hasProperty(KoParagraphStyle::TextProgressionDirection)) { switch(bf.intProperty(KoParagraphStyle::TextProgressionDirection)) { case KoText::RightLeftTopBottom: m_actionChangeDirection->setChecked(true); break; case KoText::LeftRightTopBottom: default: m_actionChangeDirection->setChecked(false); break; } } else { m_actionChangeDirection->setChecked(textEditor->block().text().isRightToLeft()); } if (bf.alignment() == Qt::AlignLeading || bf.alignment() == Qt::AlignTrailing) { bool revert = (textEditor->block().layout()->textOption().textDirection() == Qt::RightToLeft); if ((bf.alignment() == Qt::AlignLeading) ^ revert) m_actionAlignLeft->setChecked(true); else m_actionAlignRight->setChecked(true); } else if (bf.alignment() == Qt::AlignHCenter) m_actionAlignCenter->setChecked(true); if (bf.alignment() == Qt::AlignJustify) m_actionAlignBlock->setChecked(true); else if (bf.alignment() == (Qt::AlignLeft | Qt::AlignAbsolute)) m_actionAlignLeft->setChecked(true); else if (bf.alignment() == (Qt::AlignRight | Qt::AlignAbsolute)) m_actionAlignRight->setChecked(true); if (textEditor->block().textList()) { QTextListFormat listFormat = textEditor->block().textList()->format(); if(listFormat.intProperty(KoListStyle::Level) > 1) { m_actionFormatDecreaseIndent->setEnabled(true); } else { m_actionFormatDecreaseIndent->setEnabled(false); } if (listFormat.intProperty(KoListStyle::Level) < 10) { m_actionFormatIncreaseIndent->setEnabled(true); } else { m_actionFormatIncreaseIndent->setEnabled(false); } } else { m_actionFormatDecreaseIndent->setEnabled(textEditor->blockFormat().leftMargin() > 0.); } m_allowActions = true; bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); if (useAdvancedText) { action("insert_table")->setEnabled(notInAnnotation); bool hasTable = textEditor->currentTable(); action("insert_tablerow_above")->setEnabled(hasTable && notInAnnotation); action("insert_tablerow_below")->setEnabled(hasTable && notInAnnotation); action("insert_tablecolumn_left")->setEnabled(hasTable && notInAnnotation); action("insert_tablecolumn_right")->setEnabled(hasTable && notInAnnotation); action("delete_tablerow")->setEnabled(hasTable && notInAnnotation); action("delete_tablecolumn")->setEnabled(hasTable && notInAnnotation); action("merge_tablecells")->setEnabled(hasTable && notInAnnotation); action("split_tablecells")->setEnabled(hasTable && notInAnnotation); action("activate_borderpainter")->setEnabled(hasTable && notInAnnotation); } action("insert_annotation")->setEnabled(notInAnnotation); ///TODO if selection contains several different format emit blockChanged(textEditor->block()); emit charFormatChanged(cf, textEditor->blockCharFormat()); emit blockFormatChanged(bf); } void TextTool::updateStyleManager() { if (!m_textShapeData) return; KoStyleManager *styleManager = KoTextDocument(m_textShapeData->document()).styleManager(); emit styleManagerChanged(styleManager); //TODO move this to its own method m_changeTracker = KoTextDocument(m_textShapeData->document()).changeTracker(); } void TextTool::activate(ToolActivation toolActivation, const QSet &shapes) { Q_UNUSED(toolActivation); m_caretTimer.start(); m_caretTimerState = true; foreach (KoShape *shape, shapes) { m_textShape = dynamic_cast(shape); if (m_textShape) break; } if (!m_textShape) { // none found emit done(); // This is how we inform the rulers of the active range // No shape means no active range canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, QVariant(QRectF())); return; } // This is how we inform the rulers of the active range // For now we will not consider table cells, but just give the shape dimensions QVariant v; QRectF rect(QPoint(), m_textShape->size()); rect = m_textShape->absoluteTransformation(0).mapRect(rect); v.setValue(rect); canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, v); if ((!m_oldTextEditor.isNull()) && m_oldTextEditor.data()->document() != static_cast(m_textShape->userData())->document()) { m_oldTextEditor.data()->setPosition(m_oldTextEditor.data()->position()); //we need to redraw like this so we update the old textshape whereever it may be if (canvas()->canvasWidget()) canvas()->canvasWidget()->update(); } setShapeData(static_cast(m_textShape->userData())); useCursor(Qt::IBeamCursor); updateStyleManager(); repaintSelection(); updateSelectionHandler(); updateActions(); if (m_specialCharacterDocker) m_specialCharacterDocker->setEnabled(true); } void TextTool::deactivate() { m_caretTimer.stop(); m_caretTimerState = false; repaintCaret(); m_textShape = 0; // This is how we inform the rulers of the active range // No shape means no active range canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, QVariant(QRectF())); m_oldTextEditor = m_textEditor; setShapeData(0); updateSelectionHandler(); if (m_specialCharacterDocker) { m_specialCharacterDocker->setEnabled(false); m_specialCharacterDocker->setVisible(false); } } void TextTool::repaintDecorations() { if (m_textShapeData) repaintSelection(); } void TextTool::repaintCaret() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) return; KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); Q_UNUSED(lay); // If we have changed root area we need to update m_textShape and m_textShapeData if (m_delayedEnsureVisible) { m_delayedEnsureVisible = false; ensureCursorVisible(); return; } ensureCursorVisible(false); // ensures the various vars are updated bool upToDate; QRectF repaintRect = caretRect(textEditor->cursor(), &upToDate); repaintRect.moveTop(repaintRect.top() - m_textShapeData->documentOffset()); if (repaintRect.isValid()) { repaintRect = m_textShape->absoluteTransformation(0).mapRect(repaintRect); // Make sure there is enough space to show an icon QRectF iconSize = canvas()->viewConverter()->viewToDocument(QRect(0, 0, 18, 18)); repaintRect.setX(repaintRect.x() - iconSize.width() / 2); repaintRect.setRight(repaintRect.right() + iconSize.width() / 2); repaintRect.setTop(repaintRect.y() - iconSize.height() / 2); repaintRect.setBottom(repaintRect.bottom() + iconSize.height() / 2); canvas()->updateCanvas(repaintRect); } } void TextTool::repaintSelection() { KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) return; QTextCursor cursor = *textEditor->cursor(); QList shapes; KoTextDocumentLayout *lay = qobject_cast(textEditor->document()->documentLayout()); Q_ASSERT(lay); foreach (KoShape* shape, lay->shapes()) { TextShape *textShape = dynamic_cast(shape); if (textShape == 0) // when the shape is being deleted its no longer a TextShape but a KoShape continue; //Q_ASSERT(!shapes.contains(textShape)); if (!shapes.contains(textShape)) { shapes.append(textShape); } } // loop over all shapes that contain the text and update per shape. QRectF repaintRect = textRect(cursor); foreach (TextShape *ts, shapes) { QRectF rect = repaintRect; rect.moveTop(rect.y() - ts->textShapeData()->documentOffset()); rect = ts->absoluteTransformation(0).mapRect(rect); QRectF r = ts->boundingRect().intersected(rect); canvas()->updateCanvas(r); } } QRectF TextTool::caretRect(QTextCursor *cursor, bool *upToDate) const { QTextCursor tmpCursor(*cursor); tmpCursor.setPosition(cursor->position()); // looses the anchor QRectF rect = textRect(tmpCursor); if (rect.size() == QSizeF(0,0)) { if (upToDate) { *upToDate = false; } rect = m_lastImMicroFocus; // prevent block changed but layout not done } else { if (upToDate) { *upToDate = true; } m_lastImMicroFocus = rect; } return rect; } QRectF TextTool::textRect(QTextCursor &cursor) const { if (!m_textShapeData) return QRectF(); KoTextEditor *textEditor = m_textEditor.data(); KoTextDocumentLayout *lay = qobject_cast(textEditor->document()->documentLayout()); return lay->selectionBoundingBox(cursor); } KoToolSelection* TextTool::selection() { return m_toolSelection; } QList > TextTool::createOptionWidgets() { QList > widgets; SimpleCharacterWidget *scw = new SimpleCharacterWidget(this, 0); SimpleParagraphWidget *spw = new SimpleParagraphWidget(this, 0); if (m_textEditor.data()) { // connect(m_textEditor.data(), SIGNAL(paragraphStyleApplied(KoParagraphStyle*)), spw, SLOT(slotParagraphStyleApplied(KoParagraphStyle*))); // connect(m_textEditor.data(), SIGNAL(characterStyleApplied(KoCharacterStyle*)), scw, SLOT(slotCharacterStyleApplied(KoCharacterStyle*))); //initialise the char- and par- widgets with the current block and formats. scw->setCurrentBlockFormat(m_textEditor.data()->blockFormat()); scw->setCurrentFormat(m_textEditor.data()->charFormat(), m_textEditor.data()-> blockCharFormat()); spw->setCurrentBlock(m_textEditor.data()->block()); spw->setCurrentFormat(m_textEditor.data()->blockFormat()); } SimpleTableWidget *stw = new SimpleTableWidget(this, 0); SimpleInsertWidget *siw = new SimpleInsertWidget(this, 0); /* We do not use these for now. Let's see if they become useful at a certain point in time. If not, we can remove the whole chain (SimpleCharWidget, SimpleParWidget, DockerStyleComboModel) if (m_textShapeData && KoTextDocument(m_textShapeData->document()).styleManager()) { scw->setInitialUsedStyles(KoTextDocument(m_textShapeData->document()).styleManager()->usedCharacterStyles()); spw->setInitialUsedStyles(KoTextDocument(m_textShapeData->document()).styleManager()->usedParagraphStyles()); } */ // Connect to/with simple character widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), scw, SLOT(setStyleManager(KoStyleManager*))); connect(this, SIGNAL(charFormatChanged(QTextCharFormat,QTextCharFormat)), scw, SLOT(setCurrentFormat(QTextCharFormat,QTextCharFormat))); connect(this, SIGNAL(blockFormatChanged(QTextBlockFormat)), scw, SLOT(setCurrentBlockFormat(QTextBlockFormat))); connect(scw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(scw, SIGNAL(characterStyleSelected(KoCharacterStyle*)), this, SLOT(setStyle(KoCharacterStyle*))); connect(scw, SIGNAL(newStyleRequested(QString)), this, SLOT(createStyleFromCurrentCharFormat(QString))); connect(scw, SIGNAL(showStyleManager(int)), this, SLOT(showStyleManager(int))); // Connect to/with simple paragraph widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), spw, SLOT(setStyleManager(KoStyleManager*))); connect(this, SIGNAL(blockChanged(QTextBlock)), spw, SLOT(setCurrentBlock(QTextBlock))); connect(this, SIGNAL(blockFormatChanged(QTextBlockFormat)), spw, SLOT(setCurrentFormat(QTextBlockFormat))); connect(spw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(spw, SIGNAL(paragraphStyleSelected(KoParagraphStyle*)), this, SLOT(setStyle(KoParagraphStyle*))); connect(spw, SIGNAL(newStyleRequested(QString)), this, SLOT(createStyleFromCurrentBlockFormat(QString))); connect(spw, SIGNAL(showStyleManager(int)), this, SLOT(showStyleManager(int))); // Connect to/with simple table widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), stw, SLOT(setStyleManager(KoStyleManager*))); connect(stw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(stw, SIGNAL(tableBorderDataUpdated(KoBorder::BorderData)), this, SLOT(setTableBorderData(KoBorder::BorderData))); // Connect to/with simple insert widget (docker) connect(siw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(siw, SIGNAL(insertTableQuick(int,int)), this, SLOT(insertTableQuick(int,int))); updateStyleManager(); if (m_textShape) { updateActions(); } scw->setWindowTitle(i18n("Character")); widgets.append(scw); spw->setWindowTitle(i18n("Paragraph")); widgets.append(spw); bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); if (useAdvancedText) { stw->setWindowTitle(i18n("Table")); widgets.append(stw); siw->setWindowTitle(i18n("Insert")); widgets.append(siw); } return widgets; } void TextTool::returnFocusToCanvas() { canvas()->canvasWidget()->setFocus(); } void TextTool::startEditing(KUndo2Command* command) { m_currentCommand = command; m_currentCommandHasChildren = true; } void TextTool::stopEditing() { m_currentCommand = 0; m_currentCommandHasChildren = false; } void TextTool::insertNewSection() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) return; textEditor->newSection(); } void TextTool::configureSection() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) return; SectionFormatDialog *dia = new SectionFormatDialog(0, m_textEditor.data()); dia->exec(); delete dia; returnFocusToCanvas(); updateActions(); } void TextTool::splitSections() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) return; SectionsSplitDialog *dia = new SectionsSplitDialog(0, m_textEditor.data()); dia->exec(); delete dia; returnFocusToCanvas(); updateActions(); } void TextTool::pasteAsText() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) return; const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Clipboard); // on windows we do not have data if we try to paste this selection if (!data) return; if (data->hasFormat(KoOdf::mimeType(KoOdf::Text)) || data->hasText()) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data, true); editingPluginEvents(); } } void TextTool::bold(bool bold) { m_textEditor.data()->bold(bold); } void TextTool::italic(bool italic) { m_textEditor.data()->italic(italic); } void TextTool::underline(bool underline) { m_textEditor.data()->underline(underline); } void TextTool::strikeOut(bool strikeOut) { m_textEditor.data()->strikeOut(strikeOut); } void TextTool::nonbreakingSpace() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->insertText(QString(QChar(Qt::Key_nobreakspace))); } void TextTool::nonbreakingHyphen() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->insertText(QString(QChar(0x2013))); } void TextTool::softHyphen() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->insertText(QString(QChar(Qt::Key_hyphen))); } void TextTool::lineBreak() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->insertText(QString(QChar(0x2028))); } void TextTool::alignLeft() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignLeft | Qt::AlignAbsolute); } void TextTool::alignRight() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignRight | Qt::AlignAbsolute); } void TextTool::alignCenter() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignHCenter); } void TextTool::alignBlock() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignJustify); } void TextTool::superScript(bool on) { if (!m_allowActions || !m_textEditor.data()) return; if (on) m_actionFormatSub->setChecked(false); m_textEditor.data()->setVerticalTextAlignment(on ? Qt::AlignTop : Qt::AlignVCenter); } void TextTool::subScript(bool on) { if (!m_allowActions || !m_textEditor.data()) return; if (on) m_actionFormatSuper->setChecked(false); m_textEditor.data()->setVerticalTextAlignment(on ? Qt::AlignBottom : Qt::AlignVCenter); } void TextTool::increaseIndent() { if (!m_allowActions || !m_textEditor.data()) return; if (m_textEditor.data()->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::IncreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*(m_textEditor.data()->cursor()), type, 1); m_textEditor.data()->addCommand(cll); editingPluginEvents(); } else { m_textEditor.data()->increaseIndent(); } updateActions(); } void TextTool::decreaseIndent() { if (!m_allowActions || !m_textEditor.data()) return; if (m_textEditor.data()->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::DecreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*(m_textEditor.data()->cursor()), type, 1); m_textEditor.data()->addCommand(cll); editingPluginEvents(); } else { m_textEditor.data()->decreaseIndent(); } updateActions(); } void TextTool::decreaseFontSize() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->decreaseFontSize(); } void TextTool::increaseFontSize() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->increaseFontSize(); } void TextTool::setFontFamily(const QString &font) { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setFontFamily(font); } void TextTool::setFontSize (qreal size) { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setFontSize(size); } void TextTool::insertIndexMarker() { // TODO handle result when we figure out how to report errors from a tool. m_textEditor.data()->insertIndexMarker(); } void TextTool::insertFrameBreak() { m_textEditor.data()->insertFrameBreak(); ensureCursorVisible(); m_delayedEnsureVisible = true; } void TextTool::setStyle(KoCharacterStyle *style) { KoCharacterStyle *charStyle = style; //if the given KoCharacterStyle is null, set the KoParagraphStyle character properties if (!charStyle){ charStyle = static_cast(KoTextDocument(m_textShapeData->document()).styleManager()->paragraphStyle(m_textEditor.data()->blockFormat().intProperty(KoParagraphStyle::StyleId))); } if (charStyle) { m_textEditor.data()->setStyle(charStyle); updateActions(); } } void TextTool::setStyle(KoParagraphStyle *style) { m_textEditor.data()->setStyle(style); updateActions(); } void TextTool::insertTable() { TableDialog *dia = new TableDialog(0); if (dia->exec() == TableDialog::Accepted) m_textEditor.data()->insertTable(dia->rows(), dia->columns()); delete dia; updateActions(); } void TextTool::insertTableQuick(int rows, int columns) { m_textEditor.data()->insertTable(rows, columns); updateActions(); } void TextTool::insertTableRowAbove() { m_textEditor.data()->insertTableRowAbove(); } void TextTool::insertTableRowBelow() { m_textEditor.data()->insertTableRowBelow(); } void TextTool::insertTableColumnLeft() { m_textEditor.data()->insertTableColumnLeft(); } void TextTool::insertTableColumnRight() { m_textEditor.data()->insertTableColumnRight(); } void TextTool::deleteTableColumn() { m_textEditor.data()->deleteTableColumn(); } void TextTool::deleteTableRow() { m_textEditor.data()->deleteTableRow(); } void TextTool::mergeTableCells() { m_textEditor.data()->mergeTableCells(); } void TextTool::splitTableCells() { m_textEditor.data()->splitTableCells(); } void TextTool::useTableBorderCursor() { static const unsigned char data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x80, 0x7e, 0x00, 0x00, 0x40, 0x3f, 0x00, 0x00, 0xa0, 0x1f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xf4, 0x03, 0x00, 0x00, 0xe4, 0x01, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x80, 0x41, 0x00, 0x00, 0x40, 0x32, 0x00, 0x00, 0xa0, 0x0f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; QBitmap result(32, 32); result.fill(Qt::color0); QPainter painter(&result); painter.drawPixmap(0, 0, QBitmap::fromData(QSize(25, 23), data)); QBitmap brushMask = result.createHeuristicMask(false); useCursor(QCursor(result, brushMask, 1, 21)); } void TextTool::setTableBorderData(const KoBorder::BorderData &data) { m_tablePenMode = true; m_tablePenBorderData = data; } void TextTool::formatParagraph() { ParagraphSettingsDialog *dia = new ParagraphSettingsDialog(this, m_textEditor.data()); dia->setUnit(canvas()->unit()); dia->setImageCollection(m_textShape->imageCollection()); dia->exec(); delete dia; returnFocusToCanvas(); } void TextTool::testSlot(bool on) { qDebug() << "signal received. bool:" << on; } void TextTool::selectAll() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) return; const int selectionLength = qAbs(textEditor->position() - textEditor->anchor()); textEditor->movePosition(QTextCursor::End); textEditor->setPosition(0, QTextCursor::KeepAnchor); repaintSelection(); if (selectionLength != qAbs(textEditor->position() - textEditor->anchor())) // it actually changed emit selectionChanged(true); } void TextTool::startMacro(const QString &title) { if (title != i18n("Key Press") && title !=i18n("Autocorrection")) //dirty hack while waiting for refactor of text editing m_textTyping = false; else m_textTyping = true; if (title != i18n("Delete") && title != i18n("Autocorrection")) //same dirty hack as above m_textDeleting = false; else m_textDeleting = true; if (m_currentCommand) return; class MacroCommand : public KUndo2Command { public: MacroCommand(const KUndo2MagicString &title) : KUndo2Command(title), m_first(true) {} virtual void redo() { if (! m_first) KUndo2Command::redo(); m_first = false; } virtual bool mergeWith(const KUndo2Command *) { return false; } bool m_first; }; /** * FIXME: The messages genearted by the Text Tool might not be * properly translated, since we don't control it in * type-safe way. * * The title is already translated string, we just don't * have any type control over it. */ KUndo2MagicString title_workaround = kundo2_noi18n(title); m_currentCommand = new MacroCommand(title_workaround); m_currentCommandHasChildren = false; } void TextTool::stopMacro() { if (!m_currentCommand) return; if (! m_currentCommandHasChildren) delete m_currentCommand; m_currentCommand = 0; } void TextTool::showStyleManager(int styleId) { if (!m_textShapeData) return; KoStyleManager *styleManager = KoTextDocument(m_textShapeData->document()).styleManager(); Q_ASSERT(styleManager); if (!styleManager) return; //don't crash StyleManagerDialog *dia = new StyleManagerDialog(canvas()->canvasWidget()); dia->setStyleManager(styleManager); dia->setUnit(canvas()->unit()); KoParagraphStyle *paragraphStyle = styleManager->paragraphStyle(styleId); if (paragraphStyle) { dia->setParagraphStyle(paragraphStyle); } KoCharacterStyle *characterStyle = styleManager->characterStyle(styleId); if (characterStyle) { dia->setCharacterStyle(characterStyle); } dia->show(); } void TextTool::startTextEditingPlugin(const QString &pluginId) { KoTextEditingPlugin *plugin = textEditingPluginContainer()->plugin(pluginId); if (plugin) { if (m_textEditor.data()->hasSelection()) { plugin->checkSection(m_textShapeData->document(), m_textEditor.data()->selectionStart(), m_textEditor.data()->selectionEnd()); } else plugin->finishedWord(m_textShapeData->document(), m_textEditor.data()->position()); } } void TextTool::canvasResourceChanged(int key, const QVariant &var) { if (m_textEditor.isNull()) return; if (!m_textShapeData) return; if (m_allowResourceManagerUpdates == false) return; if (key == KoText::CurrentTextPosition) { repaintSelection(); m_textEditor.data()->setPosition(var.toInt()); ensureCursorVisible(); } else if (key == KoText::CurrentTextAnchor) { repaintSelection(); int pos = m_textEditor.data()->position(); m_textEditor.data()->setPosition(var.toInt()); m_textEditor.data()->setPosition(pos, QTextCursor::KeepAnchor); } else if (key == KoCanvasResourceManager::Unit) { m_unit = var.value(); } else return; repaintSelection(); } void TextTool::insertSpecialCharacter() { if (m_specialCharacterDocker == 0) { m_specialCharacterDocker = new InsertCharacter(canvas()->canvasWidget()); connect(m_specialCharacterDocker, SIGNAL(insertCharacter(QString)), this, SLOT(insertString(QString))); } m_specialCharacterDocker->show(); } void TextTool::insertString(const QString& string) { m_textEditor.data()->insertText(string); returnFocusToCanvas(); } void TextTool::selectFont() { FontDia *fontDlg = new FontDia(m_textEditor.data()); fontDlg->exec(); delete fontDlg; returnFocusToCanvas(); } void TextTool::shapeAddedToCanvas() { qDebug(); if (m_textShape) { KoSelection *selection = canvas()->shapeManager()->selection(); KoShape *shape = selection->firstSelectedShape(); if (shape != m_textShape && canvas()->shapeManager()->shapes().contains(m_textShape)) { // this situation applies when someone, not us, changed the selection by selecting another // text shape. Possibly by adding one. // Deselect the new shape again, so we can keep editing what we were already editing selection->select(m_textShape); selection->deselect(shape); } } } void TextTool::shapeDataRemoved() { m_textShapeData = 0; m_textShape = 0; if (!m_textEditor.isNull() && !m_textEditor.data()->cursor()->isNull()) { const QTextDocument *doc = m_textEditor.data()->document(); Q_ASSERT(doc); KoTextDocumentLayout *lay = qobject_cast(doc->documentLayout()); if (!lay || lay->shapes().isEmpty()) { emit done(); return; } m_textShape = static_cast(lay->shapes().first()); m_textShapeData = static_cast(m_textShape->userData()); connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); } } void TextTool::createStyleFromCurrentBlockFormat(const QString &name) { KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoParagraphStyle *paragraphStyle = new KoParagraphStyle(m_textEditor.data()->blockFormat(), m_textEditor.data()->charFormat()); paragraphStyle->setName(name); styleManager->add(paragraphStyle); m_textEditor.data()->setStyle(paragraphStyle); emit charFormatChanged(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); emit blockFormatChanged(m_textEditor.data()->blockFormat()); } void TextTool::createStyleFromCurrentCharFormat(const QString &name) { KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoCharacterStyle *originalCharStyle = styleManager->characterStyle(m_textEditor.data()->charFormat().intProperty(KoCharacterStyle::StyleId)); KoCharacterStyle *autoStyle; if (!originalCharStyle) { KoCharacterStyle blankStyle; originalCharStyle = &blankStyle; autoStyle = originalCharStyle->autoStyle(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); autoStyle->setParentStyle(0); } else { autoStyle = originalCharStyle->autoStyle(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); } autoStyle->setName(name); styleManager->add(autoStyle); m_textEditor.data()->setStyle(autoStyle); emit charFormatChanged(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); } // ---------- editing plugins methods. void TextTool::editingPluginEvents() { if (m_prevCursorPosition == -1 || m_prevCursorPosition == m_textEditor.data()->position()) { qDebug()<<"m_prevCursorPosition="<position()="<position(); return; } QTextBlock block = m_textEditor.data()->block(); if (! block.contains(m_prevCursorPosition)) { qDebug()<<"m_prevCursorPosition="<position(); if (from > to) qSwap(from, to); QString section = block.text().mid(from - block.position(), to - from); qDebug()<<"from="<values()) { plugin->finishedWord(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::finishedParagraph() { if (m_textShapeData && textEditingPluginContainer()) { foreach (KoTextEditingPlugin* plugin, textEditingPluginContainer()->values()) { plugin->finishedParagraph(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::startingSimpleEdit() { if (m_textShapeData && textEditingPluginContainer()) { foreach (KoTextEditingPlugin* plugin, textEditingPluginContainer()->values()) { plugin->startingSimpleEdit(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::setTextColor(const KoColor &color) { m_textEditor.data()->setTextColor(color.toQColor()); } void TextTool::setBackgroundColor(const KoColor &color) { m_textEditor.data()->setTextBackgroundColor(color.toQColor()); } void TextTool::setGrowWidthToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoGrowWidth, enabled)); updateActions(); } void TextTool::setGrowHeightToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoGrowHeight, enabled)); updateActions(); } void TextTool::setShrinkToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::ShrinkToFitResize, enabled)); updateActions(); } void TextTool::runUrl(KoPointerEvent *event, QString &url) { QUrl _url = QUrl::fromLocalFile(url); if (_url.isLocalFile()) { QMimeDatabase db; QString type = db.mimeTypeForUrl(_url).name(); if (KRun::isExecutableFile(_url, type)) { QString question = i18n("This link points to the program or script '%1'.\n" "Malicious programs can harm your computer. " "Are you sure that you want to run this program?", url); // this will also start local programs, so adding a "don't warn again" // checkbox will probably be too dangerous int choice = KMessageBox::warningYesNo(0, question, i18n("Open Link?")); if (choice != KMessageBox::Yes) return; } } event->accept(); new KRun(_url, 0); } void TextTool::debugTextDocument() { #ifndef NDEBUG if (!m_textShapeData) return; const int CHARSPERLINE = 80; // TODO Make configurable using ENV var? const int CHARPOSITION = 278301935; KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoInlineTextObjectManager *inlineManager = document.inlineTextObjectManager(); QTextBlock block = m_textShapeData->document()->begin(); for (;block.isValid(); block = block.next()) { QVariant var = block.blockFormat().property(KoParagraphStyle::StyleId); if (!var.isNull()) { KoParagraphStyle *ps = styleManager->paragraphStyle(var.toInt()); qDebug() << "--- Paragraph Style:" << (ps ? ps->name() : QString()) << var.toInt(); } var = block.charFormat().property(KoCharacterStyle::StyleId); if (!var.isNull()) { KoCharacterStyle *cs = styleManager->characterStyle(var.toInt()); qDebug() << "--- Character Style:" << (cs ? cs->name() : QString()) << var.toInt(); } int lastPrintedChar = -1; QTextBlock::iterator it; QString fragmentText; QList inlineCharacters; for (it = block.begin(); !it.atEnd(); ++it) { QTextFragment fragment = it.fragment(); if (!fragment.isValid()) continue; QTextCharFormat fmt = fragment.charFormat(); qDebug() << "changeId: " << fmt.property(KoCharacterStyle::ChangeTrackerId); const int fragmentStart = fragment.position() - block.position(); for (int i = fragmentStart; i < fragmentStart + fragment.length(); i += CHARSPERLINE) { if (lastPrintedChar == fragmentStart-1) fragmentText += '|'; if (lastPrintedChar < fragmentStart || i > fragmentStart) { QString debug = block.text().mid(lastPrintedChar, CHARSPERLINE); lastPrintedChar += CHARSPERLINE; if (lastPrintedChar > block.length()) debug += "\\n"; qDebug() << debug; } var = fmt.property(KoCharacterStyle::StyleId); QString charStyleLong, charStyleShort; if (! var.isNull()) { // named style charStyleShort = QString::number(var.toInt()); KoCharacterStyle *cs = styleManager->characterStyle(var.toInt()); if (cs) charStyleLong = cs->name(); } if (inlineManager && fmt.hasProperty(KoCharacterStyle::InlineInstanceId)) { QTextCharFormat inlineFmt = fmt; inlineFmt.setProperty(CHARPOSITION, fragmentStart); inlineCharacters << inlineFmt; } if (fragment.length() > charStyleLong.length()) fragmentText += charStyleLong; else if (fragment.length() > charStyleShort.length()) fragmentText += charStyleShort; else if (fragment.length() >= 2) fragmentText += QChar(8230); // elipses int rest = fragmentStart - (lastPrintedChar-CHARSPERLINE) + fragment.length() - fragmentText.length(); rest = qMin(rest, CHARSPERLINE - fragmentText.length()); if (rest >= 2) fragmentText = QString("%1%2").arg(fragmentText).arg(' ', rest); if (rest >= 0) fragmentText += '|'; if (fragmentText.length() >= CHARSPERLINE) { qDebug() << fragmentText; fragmentText.clear(); } } } if (!fragmentText.isEmpty()) { qDebug() << fragmentText; } else if (block.length() == 1) { // no actual tet qDebug() << "\\n"; } foreach (const QTextCharFormat &cf, inlineCharacters) { KoInlineObject *object= inlineManager->inlineTextObject(cf); qDebug() << "At pos:" << cf.intProperty(CHARPOSITION) << object; // qDebug() << "-> id:" << cf.intProperty(577297549); } QTextList *list = block.textList(); if (list) { if (list->format().hasProperty(KoListStyle::StyleId)) { KoListStyle *ls = styleManager->listStyle(list->format().intProperty(KoListStyle::StyleId)); qDebug() << " List style applied:" << ls->styleId() << ls->name(); } else qDebug() << " +- is a list..." << list; } } #endif } void TextTool::debugTextStyles() { #ifndef NDEBUG if (!m_textShapeData) return; KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); QSet seenStyles; foreach (KoParagraphStyle *style, styleManager->paragraphStyles()) { qDebug() << style->styleId() << style->name() << (styleManager->defaultParagraphStyle() == style ? "[Default]" : ""); KoListStyle *ls = style->listStyle(); if (ls) { // optional ;) qDebug() << " +- ListStyle: " << ls->styleId() << ls->name() << (ls == styleManager->defaultListStyle() ? "[Default]":""); foreach (int level, ls->listLevels()) { KoListLevelProperties llp = ls->levelProperties(level); qDebug() << " | level" << llp.level() << " style (enum):" << llp.labelType(); if (llp.bulletCharacter().unicode() != 0) { qDebug() << " | bullet" << llp.bulletCharacter(); } } seenStyles << ls->styleId(); } } bool first = true; foreach (KoCharacterStyle *style, styleManager->characterStyles()) { if (seenStyles.contains(style->styleId())) continue; if (first) { qDebug() << "--- Character styles ---"; first = false; } qDebug() << style->styleId() << style->name(); qDebug() << style->font(); } first = true; foreach (KoListStyle *style, styleManager->listStyles()) { if (seenStyles.contains(style->styleId())) continue; if (first) { qDebug() << "--- List styles ---"; first = false; } qDebug() << style->styleId() << style->name() << (style == styleManager->defaultListStyle() ? "[Default]":""); } #endif } void TextTool::textDirectionChanged() { if (!m_allowActions || !m_textEditor.data()) return; QTextBlockFormat blockFormat; if (m_actionChangeDirection->isChecked()) { blockFormat.setProperty(KoParagraphStyle::TextProgressionDirection, KoText::RightLeftTopBottom); } else { blockFormat.setProperty(KoParagraphStyle::TextProgressionDirection, KoText::LeftRightTopBottom); } m_textEditor.data()->mergeBlockFormat(blockFormat); } void TextTool::setListLevel(int level) { if (level < 1 || level > 10) { return; } KoTextEditor *textEditor = m_textEditor.data(); if (textEditor->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::SetLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, level); textEditor->addCommand(cll); editingPluginEvents(); } } void TextTool::insertAnnotation() { AnnotationTextShape *shape = (AnnotationTextShape*)KoShapeRegistry::instance()->value(AnnotationShape_SHAPEID)->createDefaultShape(canvas()->shapeController()->resourceManager()); textEditor()->addAnnotation(shape); // Set annotation creator. KConfig cfg("calligrarc"); cfg.reparseConfiguration(); KConfigGroup authorGroup(&cfg, "Author"); QStringList profiles = authorGroup.readEntry("profile-names", QStringList()); KSharedConfig::openConfig()->reparseConfiguration(); KConfigGroup appAuthorGroup( KSharedConfig::openConfig(), "Author"); QString profile = appAuthorGroup.readEntry("active-profile", ""); KConfigGroup cgs(&authorGroup, "Author-" + profile); if (profiles.contains(profile)) { KConfigGroup cgs(&authorGroup, "Author-" + profile); shape->setCreator(cgs.readEntry("creator")); } else { if (profile == "anonymous") { shape->setCreator("Anonymous"); } else { KUser user(KUser::UseRealUserID); shape->setCreator(user.property(KUser::FullName).toString()); } } // Set Annotation creation date. shape->setDate(QDate::currentDate().toString(Qt::ISODate)); } diff --git a/plugins/textshape/dialogs/StylesDelegate.cpp b/plugins/textshape/dialogs/StylesDelegate.cpp index 7b4ad4d6721..faa90fbf835 100644 --- a/plugins/textshape/dialogs/StylesDelegate.cpp +++ b/plugins/textshape/dialogs/StylesDelegate.cpp @@ -1,306 +1,306 @@ /* This file is part of the KDE project * Copyright (C) 2011 C. Boemann * Copyright (C) 2011-2012 Pierre Stirnweiss * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "StylesDelegate.h" #include "AbstractStylesModel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include StylesDelegate::StylesDelegate() : QStyledItemDelegate(), m_editButtonPressed(false), m_deleteButtonPressed(false), m_enableEditButton(true) { m_buttonSize = 16; m_buttonDistance = 2; } void StylesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optionV1, const QModelIndex &index) const { QStyleOptionViewItemV4 option = optionV1; initStyleOption(&option, index); if (!index.data(AbstractStylesModel::isTitleRole).toBool()) { QStyledItemDelegate::paint(painter, option, index); //the following is needed to find out if the view has vertical scrollbars. If there is no view just paint and do not attempt to draw the control buttons. //this is needed because it seems that the option.rect given does not exclude the vertical scrollBar. This means that we can draw the button in an area that is going to be covered by the vertical scrollBar. const QAbstractItemView *view = static_cast(option.widget); if (!view){ return; } QScrollBar *scrollBar = view->verticalScrollBar(); int scrollBarWidth = 0; if (scrollBar->isVisible()) { scrollBarWidth = scrollBar->width(); } if (!index.isValid() || !(option.state & QStyle::State_MouseOver)) { return; } // Delete style button. int dx1 = option.rect.width() - qMin(option.rect.height()-2, m_buttonSize) - m_buttonSize - m_buttonDistance -2; int dy1 = 1 + (option.rect.height()-qMin(option.rect.height(), m_buttonSize))/2; int dx2 = -m_buttonSize - m_buttonDistance -2; int dy2 = -1 -(option.rect.height()-qMin(option.rect.height(), m_buttonSize))/2; /* TODO: when we can safely delete styles, re-enable this QStyleOptionButton optDel; if (!m_deleteButtonPressed) { optDel.state |= QStyle::State_Enabled; } optDel.icon = koIcon("edit-delete"); optDel.features |= QStyleOptionButton::Flat; optDel.rect = option.rect.adjusted(dx1 - scrollBarWidth, dy1, dx2 - scrollBarWidth, dy2); view->style()->drawControl(QStyle::CE_PushButton, &optDel, painter, 0); */ // Open style manager dialog button. if (!m_enableEditButton) { // when we don't want edit icon return; } dx1 = option.rect.width() - qMin(option.rect.height()-2, m_buttonSize) -2; dy1 = 1 + (option.rect.height()-qMin(option.rect.height(), m_buttonSize))/2; dx2 = -2; dy2 = -1 -(option.rect.height()-qMin(option.rect.height(), m_buttonSize))/2; QStyleOptionButton optEdit; if (!m_editButtonPressed) { optEdit.state |= QStyle::State_Enabled; } optEdit.icon = koIcon("document-properties"); optEdit.features |= QStyleOptionButton::Flat; optEdit.rect = option.rect.adjusted(dx1 - scrollBarWidth, dy1, dx2 - scrollBarWidth, dy2); view->style()->drawControl(QStyle::CE_PushButton, &optEdit, painter, 0); } else { const QString category = index.data().toString(); const QRect optRect = option.rect; QFont font(QApplication::font()); font.setBold(true); const QFontMetrics fontMetrics = QFontMetrics(font); QColor outlineColor = option.palette.text().color(); outlineColor.setAlphaF(0.35); //BEGIN: top left corner { painter->save(); - painter->setPen(outlineColor); + painter->setPen(QPen(outlineColor, 0)); const QPointF topLeft(optRect.topLeft()); QRectF arc(topLeft, QSizeF(4, 4)); arc.translate(0.5, 0.5); painter->drawArc(arc, 1440, 1440); painter->restore(); } //END: top left corner //BEGIN: left vertical line { QPoint start(optRect.topLeft()); start.ry() += 3; QPoint verticalGradBottom(optRect.topLeft()); verticalGradBottom.ry() += fontMetrics.height() + 5; QLinearGradient gradient(start, verticalGradBottom); gradient.setColorAt(0, outlineColor); gradient.setColorAt(1, Qt::transparent); painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); } //END: left vertical line //BEGIN: horizontal line { QPoint start(optRect.topLeft()); start.rx() += 3; QPoint horizontalGradTop(optRect.topLeft()); horizontalGradTop.rx() += optRect.width() - 6; painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor); } //END: horizontal line //BEGIN: top right corner { painter->save(); - painter->setPen(outlineColor); + painter->setPen(QPen(outlineColor, 0)); QPointF topRight(optRect.topRight()); topRight.rx() -= 4; QRectF arc(topRight, QSizeF(4, 4)); arc.translate(0.5, 0.5); painter->drawArc(arc, 0, 1440); painter->restore(); } //END: top right corner //BEGIN: right vertical line { QPoint start(optRect.topRight()); start.ry() += 3; QPoint verticalGradBottom(optRect.topRight()); verticalGradBottom.ry() += fontMetrics.height() + 5; QLinearGradient gradient(start, verticalGradBottom); gradient.setColorAt(0, outlineColor); gradient.setColorAt(1, Qt::transparent); painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); } //END: right vertical line //BEGIN: text { QRect textRect(option.rect); textRect.setTop(textRect.top() + 7); textRect.setLeft(textRect.left() + 7); textRect.setHeight(fontMetrics.height()); textRect.setRight(textRect.right() - 7); painter->save(); painter->setFont(font); QColor penColor(option.palette.text().color()); penColor.setAlphaF(0.6); painter->setPen(penColor); painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, category); painter->restore(); } //END: text } } QSize StylesDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(option); return index.data(Qt::SizeHintRole).toSize(); } bool StylesDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &optionV1, const QModelIndex &index) { Q_UNUSED(model); QStyleOptionViewItemV4 option = optionV1; initStyleOption(&option, index); //the following is needed to find out if the view has vertical scrollbars. If not just paint and do not attempt to draw the control buttons. //this is needed because it seems that the option.rect given does not exclude the vertical scrollBar. This means that we can draw the button in an area that is going to be covered by the vertical scrollBar. const QAbstractItemView *view = static_cast(option.widget); if (!view){ return false; } QScrollBar *scrollBar = view->verticalScrollBar(); int scrollBarWidth = 0; if (scrollBar->isVisible()) { scrollBarWidth = scrollBar->width(); } if (event->type() == QEvent::MouseButtonPress) { QMouseEvent *mouseEvent = static_cast(event); int dx1 = option.rect.width()- qMin(option.rect.height()-2, m_buttonSize) - m_buttonSize - m_buttonDistance -2; int dy1 = 1 + (option.rect.height()-qMin(option.rect.height(), m_buttonSize))/2; int dx2 = - m_buttonSize - m_buttonDistance -2; int dy2 = -1 -(option.rect.height()-qMin(option.rect.height(), m_buttonSize))/2; /*TODO: when we can safely delete styles, re-enable this QRect delRect = option.rect.adjusted(dx1 - scrollBarWidth, dy1, dx2 - scrollBarWidth, dy2); if (delRect.contains(mouseEvent->pos())) { m_deleteButtonPressed = true; } else { m_deleteButtonPressed = false; } */ dx1 = option.rect.width() - qMin(option.rect.height()-2, m_buttonSize) -2; dy1 = 1 + (option.rect.height()-qMin(option.rect.height(), m_buttonSize))/2; dx2 = -2; dy2 = -1 -(option.rect.height()-qMin(option.rect.height(), m_buttonSize))/2; QRect editRect = option.rect.adjusted(dx1 - scrollBarWidth, dy1, dx2 - scrollBarWidth, dy2); if (editRect.contains(mouseEvent->pos())){ m_editButtonPressed = true; } else { m_editButtonPressed = false; } emit needsUpdate(index); } if (event->type() == QEvent::MouseButtonRelease) { m_deleteButtonPressed = false; m_editButtonPressed = false; emit needsUpdate(index); if (index.flags() == Qt::NoItemFlags) { //if the item is NoItemFlagged, it means it is a separator in the view. In that case, we should not close the combo's drop down. return true; } QMouseEvent *mouseEvent = static_cast(event); int dx1 = option.rect.width() - qMin(option.rect.height()-2, m_buttonSize) - m_buttonSize - m_buttonDistance -2; int dy1 = 1 + (option.rect.height()-qMin(option.rect.height(), m_buttonSize))/2; int dx2 = - m_buttonSize - m_buttonDistance -2; int dy2 = -1 -(option.rect.height()-qMin(option.rect.height(), m_buttonSize))/2; /*TODO: when we can safely delete styles, re-enable this QRect delRect = option.rect.adjusted(dx1 - scrollBarWidth, dy1, dx2 - scrollBarWidth, dy2); if (delRect.contains(mouseEvent->pos())) { emit deleteStyleButtonClicked(index); return true; } */ dx1 = option.rect.width() - qMin(option.rect.height()-2, m_buttonSize) -2; dy1 = 1 + (option.rect.height()-qMin(option.rect.height(), m_buttonSize))/2; dx2 = -2; dy2 = -1 -(option.rect.height()-qMin(option.rect.height(), m_buttonSize))/2; QRect editRect = option.rect.adjusted(dx1 - scrollBarWidth, dy1, dx2 - scrollBarWidth, dy2); if (editRect.contains(mouseEvent->pos())){ emit styleManagerButtonClicked(index); return true; } emit clickedInItem(index); return true; //returning true here means the QComboBox mouseRelease code will not get called. The effect of it is that hidePopup will not get called. StylesCombo calls it in the corresponding slot. } if (event->type() == QEvent::MouseMove) { QMouseEvent *mouseEvent = static_cast(event); int dx1 = option.rect.width() - qMin(option.rect.height()-2, m_buttonSize) - m_buttonSize - m_buttonDistance -2; int dy1 = 1 + (option.rect.height()-qMin(option.rect.height(), m_buttonSize))/2; int dx2 = - m_buttonSize - m_buttonDistance -2; int dy2 = -1 -(option.rect.height()-qMin(option.rect.height(), m_buttonSize))/2; /*TODO: when we can safely delete styles, re-enable this QRect delRect = option.rect.adjusted(dx1 - scrollBarWidth, dy1, dx2 - scrollBarWidth, dy2); if (!delRect.contains(mouseEvent->pos())) { m_deleteButtonPressed = false; } */ dx1 = option.rect.width() - qMin(option.rect.height()-2, m_buttonSize) -2; dy1 = 1 + (option.rect.height()-qMin(option.rect.height(), m_buttonSize))/2; dx2 = -2; dy2 = -1 -(option.rect.height()-qMin(option.rect.height(), m_buttonSize))/2; QRect editRect = option.rect.adjusted(dx1 - scrollBarWidth, dy1, dx2 - scrollBarWidth, dy2); if (!editRect.contains(mouseEvent->pos())){ m_editButtonPressed = false; } emit needsUpdate(index); return false; } return false; } void StylesDelegate::setEditButtonEnable(bool enable) { m_enableEditButton = enable; } diff --git a/plugins/vectorshape/VectorShape.cpp b/plugins/vectorshape/VectorShape.cpp index 3e05de3c438..ddaf2bec70a 100644 --- a/plugins/vectorshape/VectorShape.cpp +++ b/plugins/vectorshape/VectorShape.cpp @@ -1,505 +1,505 @@ /* This file is part of the KDE project * * Copyright (C) 2009 - 2011 Inge Wallin * Copyright (C) 2011 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // Own #include "VectorShape.h" // Posix #include // Qt #include #include #include #include #include #include #include #include // Calligra #include "KoUnit.h" #include "KoStore.h" #include "KoXmlNS.h" #include "KoXmlReader.h" #include "KoXmlWriter.h" #include #include #include #include #include // Wmf support #include "WmfPainterBackend.h" // Vector shape #include "VectorDebug.h" #include "EmfParser.h" #include "EmfOutputPainterStrategy.h" #include "EmfOutputDebugStrategy.h" #include "SvmParser.h" #include "SvmPainterBackend.h" // Comment out to get uncached painting, which is good for debugging //#define VECTORSHAPE_PAINT_UNCACHED // Comment out to get unthreaded painting, which is good for debugging //#define VECTORSHAPE_PAINT_UNTHREADED VectorShape::VectorShape() : KoFrameShape( KoXmlNS::draw, "image" ) , m_type(VectorTypeNone) , m_isRendering(false) { setShapeId(VectorShape_SHAPEID); // Default size of the shape. KoShape::setSize( QSizeF( CM_TO_POINT( 8 ), CM_TO_POINT( 5 ) ) ); m_cache.setMaxCost(3); } VectorShape::~VectorShape() { // Wait for the render-thread to finish before the shape is allowed to be // destroyed so we can make sure to prevent crashes or unwanted // side-effects. Maybe as alternate we could just kill the render-thread... QMutexLocker locker(&m_mutex); } // Methods specific to the vector shape. QByteArray VectorShape::compressedContents() const { return m_contents; } VectorShape::VectorType VectorShape::vectorType() const { return m_type; } void VectorShape::setCompressedContents(const QByteArray &newContents, VectorType vectorType) { QMutexLocker locker(&m_mutex); m_contents = newContents; m_type = vectorType; m_cache.clear(); update(); } // ---------------------------------------------------------------- // Painting RenderThread::RenderThread(const QByteArray &contents, VectorShape::VectorType type, const QSizeF &size, const QSize &boundingSize, qreal zoomX, qreal zoomY) : QObject(), QRunnable(), m_contents(contents), m_type(type), m_size(size), m_boundingSize(boundingSize), m_zoomX(zoomX), m_zoomY(zoomY) { setAutoDelete(true); } RenderThread::~RenderThread() { } void RenderThread::run() { QImage *image = new QImage(m_boundingSize, QImage::Format_ARGB32); image->fill(0); QPainter painter; if (!painter.begin(image)) { warnVector << "Failed to create image-cache"; delete image; image = 0; } else { painter.scale(m_zoomX, m_zoomY); draw(painter); painter.end(); } emit finished(m_boundingSize, image); } void RenderThread::draw(QPainter &painter) { // If the data is uninitialized, e.g. because loading failed, draw the null shape. if (m_contents.isEmpty()) { drawNull(painter); return; } // Actually draw the contents switch (m_type) { case VectorShape::VectorTypeWmf: drawWmf(painter); break; case VectorShape::VectorTypeEmf: drawEmf(painter); break; case VectorShape::VectorTypeSvm: drawSvm(painter); break; case VectorShape::VectorTypeSvg: drawSvg(painter); break; case VectorShape::VectorTypeNone: default: drawNull(painter); } } void RenderThread::drawNull(QPainter &painter) const { QRectF rect(QPointF(0,0), m_size); painter.save(); // Draw a simple cross in a rectangle just to indicate that there is something here. - painter.setPen(QPen(QColor(172, 196, 206))); + painter.setPen(QPen(QColor(172, 196, 206), 0)); painter.drawRect(rect); painter.drawLine(rect.topLeft(), rect.bottomRight()); painter.drawLine(rect.bottomLeft(), rect.topRight()); painter.restore(); } void RenderThread::drawWmf(QPainter &painter) const { Libwmf::WmfPainterBackend wmfPainter(&painter, m_size); if (!wmfPainter.load(m_contents)) { drawNull(painter); return; } painter.save(); // Actually paint the WMF. wmfPainter.play(); painter.restore(); } void RenderThread::drawEmf(QPainter &painter) const { // FIXME: Make emfOutput use QSizeF QSize shapeSizeInt( m_size.width(), m_size.height() ); //debugVector << "-------------------------------------------"; //debugVector << "size: " << shapeSizeInt << m_size; //debugVector << "position: " << position(); //debugVector << "-------------------------------------------"; Libemf::Parser emfParser; #if 1 // Set to 0 to get debug output // Create a new painter output strategy. Last param = true means keep aspect ratio. Libemf::OutputPainterStrategy emfPaintOutput( painter, shapeSizeInt, true ); emfParser.setOutput( &emfPaintOutput ); #else Libemf::OutputDebugStrategy emfDebugOutput; emfParser.setOutput( &emfDebugOutput ); #endif emfParser.load(m_contents); } void RenderThread::drawSvm(QPainter &painter) const { QSize shapeSizeInt( m_size.width(), m_size.height() ); Libsvm::SvmParser svmParser; // Create a new painter backend. Libsvm::SvmPainterBackend svmPaintOutput(&painter, shapeSizeInt); svmParser.setBackend(&svmPaintOutput); svmParser.parse(m_contents); } void RenderThread::drawSvg(QPainter &painter) const { QSvgRenderer renderer(m_contents); renderer.render(&painter, QRectF(0, 0, m_size.width(), m_size.height())); } void VectorShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { #ifdef VECTORSHAPE_PAINT_UNCACHED bool useCache = false; #else bool useCache = true; #endif #ifdef VECTORSHAPE_PAINT_UNTHREADED bool asynchronous = false; #else // Since the backends may use QPainter::drawText we need to make sure to only // use threads if the font-backend supports that what is in most cases. bool asynchronous = QFontDatabase::supportsThreadedFontRendering(); #endif QImage *cache = render(converter, asynchronous, useCache); if (cache) { // paint cached image Q_ASSERT(!cache->isNull()); QVector clipRects = painter.clipRegion().rects(); foreach (const QRect &rc, clipRects) { painter.drawImage(rc.topLeft(), *cache, rc); } } } void VectorShape::renderFinished(const QSize &boundingSize, QImage *image) { if (image) { m_cache.insert(boundingSize.height(), image); update(); } m_isRendering = false; } // ---------------------------------------------------------------- // Loading and Saving void VectorShape::saveOdf(KoShapeSavingContext & context) const { QMutexLocker locker(&m_mutex); KoEmbeddedDocumentSaver &fileSaver = context.embeddedSaver(); KoXmlWriter &xmlWriter = context.xmlWriter(); QString fileName = fileSaver.getFilename("VectorImages/Image"); QByteArray mimeType; switch (m_type) { case VectorTypeWmf: mimeType = "image/x-wmf"; break; case VectorTypeEmf: mimeType = "image/x-emf"; break; case VectorTypeSvm: mimeType = "image/x-svm"; // mimetype as used inside LO/AOO break; case VectorTypeSvg: mimeType = "image/svg+xml"; default: // FIXME: What here? mimeType = "application/x-what"; break; } xmlWriter.startElement("draw:frame"); saveOdfAttributes(context, OdfAllAttributes); fileSaver.embedFile(xmlWriter, "draw:image", fileName, mimeType, qUncompress(m_contents)); xmlWriter.endElement(); // draw:frame } bool VectorShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { //debugVector <<"Loading ODF frame in the vector shape. Element = " << element.tagName(); loadOdfAttributes(element, context, OdfAllAttributes); return loadOdfFrame(element, context); } inline static int read32(const char *buffer, const int offset) { // little endian int result = (int) buffer[offset]; result |= (int) buffer[offset+1] << 8; result |= (int) buffer[offset+2] << 16; result |= (int) buffer[offset+3] << 24; return result; } // Load the actual contents within the vector shape. bool VectorShape::loadOdfFrameElement(const KoXmlElement & element, KoShapeLoadingContext &context) { //debugVector <<"Loading ODF element: " << element.tagName(); QMutexLocker locker(&m_mutex); // Get the reference to the vector file. If there is no href, then just return. const QString href = element.attribute("href"); if (href.isEmpty()) return false; // Try to open the embedded file. KoStore *store = context.odfLoadingContext().store(); bool result = store->open(href); if (!result) { return false; } int size = store->size(); if (size < 88) { store->close(); return false; } m_contents = store->read(size); store->close(); if (m_contents.count() < size) { debugVector << "Too few bytes read: " << m_contents.count() << " instead of " << size; return false; } // Try to recognize the type. We should do this before the // compression below, because that's a semi-expensive operation. m_type = vectorType(m_contents); // Return false if we didn't manage to identify the type. if (m_type == VectorTypeNone) return false; // Compress for biiiig memory savings. m_contents = qCompress(m_contents); return true; } void VectorShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const { render(converter, asynchronous, true); } QImage* VectorShape::render(const KoViewConverter &converter, bool asynchronous, bool useCache) const { QRectF rect = converter.documentToView(boundingRect()); int id = rect.size().toSize().height(); QImage *cache = useCache ? m_cache[id] : 0; if (!cache || cache->isNull()) { // recreate the cached image cache = 0; if (!m_isRendering) { m_isRendering = true; qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); QMutexLocker locker(&m_mutex); const QByteArray uncompressedContents = m_type != VectorShape::VectorTypeNone ? qUncompress(m_contents) : QByteArray(); RenderThread *t = new RenderThread(uncompressedContents, m_type, size(), rect.size().toSize(), zoomX, zoomY); connect(t, SIGNAL(finished(QSize,QImage*)), this, SLOT(renderFinished(QSize,QImage*))); if (asynchronous) { // render and paint the image threaded QThreadPool::globalInstance()->start(t); } else { // non-threaded rendering and painting of the image t->run(); cache = m_cache[id]; } } } return cache; } VectorShape::VectorType VectorShape::vectorType(const QByteArray &newContents) { VectorType vectorType; if (isWmf(newContents)) { vectorType = VectorShape::VectorTypeWmf; } else if (isEmf(newContents)) { vectorType = VectorShape::VectorTypeEmf; } else if (isSvm(newContents)) { vectorType = VectorShape::VectorTypeSvm; } else if (isSvg(newContents)) { vectorType = VectorShape::VectorTypeSvg; } else { vectorType = VectorShape::VectorTypeNone; } return vectorType; } bool VectorShape::isWmf(const QByteArray &bytes) { debugVector << "Check for WMF"; const char *data = bytes.constData(); const int size = bytes.count(); if (size < 10) return false; // This is how the 'file' command identifies a WMF. if (data[0] == '\327' && data[1] == '\315' && data[2] == '\306' && data[3] == '\232') { // FIXME: Is this a compressed wmf? Check it up. debugVector << "WMF identified: header 1"; return true; } if (data[0] == '\002' && data[1] == '\000' && data[2] == '\011' && data[3] == '\000') { debugVector << "WMF identified: header 2"; return true; } if (data[0] == '\001' && data[1] == '\000' && data[2] == '\011' && data[3] == '\000') { debugVector << "WMF identified: header 3"; return true; } return false; } bool VectorShape::isEmf(const QByteArray &bytes) { debugVector << "Check for EMF"; const char *data = bytes.constData(); const int size = bytes.count(); // This is how the 'file' command identifies an EMF. // 1. Check type qint32 mark = read32(data, 0); if (mark != 0x00000001) { //debugVector << "Not an EMF: mark = " << mark << " instead of 0x00000001"; return false; } // 2. An EMF has the string " EMF" at the start + offset 40. if (size > 44 && data[40] == ' ' && data[41] == 'E' && data[42] == 'M' && data[43] == 'F') { debugVector << "EMF identified"; return true; } return false; } bool VectorShape::isSvm(const QByteArray &bytes) { debugVector << "Check for SVM"; // Check the SVM signature. if (bytes.startsWith("VCLMTF")) { debugVector << "SVM identified"; return true; } return false; } bool VectorShape::isSvg(const QByteArray &bytes) { debugVector << "Check for SVG"; return (bytes.contains("svg")); } diff --git a/plugins/videoshape/VideoShape.cpp b/plugins/videoshape/VideoShape.cpp index a15531f8add..1e0a21332c9 100644 --- a/plugins/videoshape/VideoShape.cpp +++ b/plugins/videoshape/VideoShape.cpp @@ -1,182 +1,182 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007, 2009 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2009 C. Boemann * Copyright (C) 2012 Gopalakrishna Bhat A * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "VideoShape.h" #include #include #include #include #ifdef SHOULD_BUILD_THUMBNAIL #include #endif #include "VideoDebug.h" #include #include #include #include #include #include #include #include #include VideoShape::VideoShape() : KoFrameShape(KoXmlNS::draw, "plugin") , m_videoCollection(0) , m_videoEventAction(new VideoEventAction(this)) #ifdef SHOULD_BUILD_THUMBNAIL , m_thumbnailer(new VideoThumbnailer()) #endif , m_oldVideoData(0) , m_icon(koIcon("video-x-generic")) { setKeepAspectRatio(true); addEventAction(m_videoEventAction); } VideoShape::~VideoShape() { #ifdef SHOULD_BUILD_THUMBNAIL delete m_thumbnailer; #endif } void VideoShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { QRectF pixelsF = converter.documentToView(QRectF(QPointF(0,0), size())); VideoData *currentVideoData = videoData(); #ifdef SHOULD_BUILD_THUMBNAIL if (currentVideoData && currentVideoData != m_oldVideoData) { //generate thumbnails m_oldVideoData = currentVideoData; m_thumbnailer->createThumbnail(currentVideoData, pixelsF.size().toSize()); } QImage thumnailImage = m_thumbnailer->thumbnail(); if (thumnailImage.isNull()) { painter.fillRect(pixelsF, QColor(Qt::gray)); - painter.setPen(QPen()); + painter.setPen(QPen(Qt::black, 0)); painter.drawRect(pixelsF); m_icon.paint(&painter, pixelsF.toRect()); } else { painter.drawImage(pixelsF, thumnailImage); } #else painter.fillRect(pixelsF, QColor(Qt::gray)); - painter.setPen(QPen()); + painter.setPen(QPen(Qt::black, 0)); painter.drawRect(pixelsF); m_icon.paint(&painter, pixelsF.toRect()); #endif } void VideoShape::saveOdf(KoShapeSavingContext &context) const { // make sure we have a valid image data pointer before saving VideoData *videoData = qobject_cast(userData()); if (videoData == 0) return; KoXmlWriter &writer = context.xmlWriter(); writer.startElement("draw:frame"); saveOdfAttributes(context, OdfAllAttributes); writer.startElement("draw:plugin"); // In the spec, only the xlink:href attribute is marked as mandatory, cool :) QString name = videoData->tagForSaving(m_videoCollection->saveCounter); writer.addAttribute("xlink:type", "simple"); writer.addAttribute("xlink:show", "embed"); writer.addAttribute("xlink:actuate", "onLoad"); writer.addAttribute("xlink:href", name); writer.addAttribute("draw:mime-type", "application/vnd.sun.star.media"); writer.endElement(); // draw:plugin saveOdfCommonChildElements(context); writer.endElement(); // draw:frame context.addDataCenter(m_videoCollection); } bool VideoShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { loadOdfAttributes(element, context, OdfAllAttributes); return loadOdfFrame(element, context); } bool VideoShape::loadOdfFrameElement(const KoXmlElement &element, KoShapeLoadingContext &context) { /* the loading of the attributes might set the event actions which removes the m_videoEventAction * when there are other eventactions for the shape. Therefore we need to add it again. It is no * problem to add it again as internally a set is used and so it is not problematic when it is * already set. */ addEventAction(m_videoEventAction); if (m_videoCollection) { const QString href = element.attribute("href"); // this can happen in case it is a presentation:placeholder if (!href.isEmpty()) { QUrl url = QUrl::fromUserInput(href); VideoData *data=0; if(href.startsWith("../")) { // file is outside store QUrl url = context.odfLoadingContext().store()->urlOfStore(); QString path = url.path(); if (!path.endsWith(QLatin1Char('/'))) { path.append(QLatin1Char('/')); } path.append(href.mid(3)); url.setPath(path); data = m_videoCollection->createExternalVideoData(url, false); } else if(!url.isRelative()) { // file is outside store and absolute data = m_videoCollection->createExternalVideoData(QUrl::fromUserInput(href), false); } else { // file is inside store KoStore *store = context.odfLoadingContext().store(); data = m_videoCollection->createVideoData(href, store); } setUserData(data); } } return true; } VideoCollection *VideoShape::videoCollection() const { return m_videoCollection; } void VideoShape::setVideoCollection(VideoCollection *collection) { m_videoCollection = collection; } VideoData *VideoShape::videoData() const { return qobject_cast(userData()); } diff --git a/qtquick/CQTextDocumentCanvas.cpp b/qtquick/CQTextDocumentCanvas.cpp index 23b7926989f..3e57f946b9a 100644 --- a/qtquick/CQTextDocumentCanvas.cpp +++ b/qtquick/CQTextDocumentCanvas.cpp @@ -1,840 +1,840 @@ /* * This file is part of the KDE project * * Copyright (C) 2013 Shantanu Tushar * Copyright (C) 2013 Sujith Haridasan * Copyright (C) 2013 Arjen Hiemstra * * 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 "CQTextDocumentCanvas.h" #include "CQCanvasController.h" #include "CQTextDocumentModel.h" #include "CQTextDocumentNotesModel.h" #include "gemini/ViewModeSwitchEvent.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class CQTextDocumentCanvas::Private { public: Private() : canvas(0), findText(0), documentModel(0), document(0), pageNumber(0), throttleTimer(new QTimer()), currentTool(0), notes(0), textEditor(0) { throttleTimer->setInterval(200); throttleTimer->setSingleShot(true); } KWCanvasItem *canvas; QString searchTerm; KoFindText *findText; CQTextDocumentModel *documentModel; KWDocument* document; KoPart* part; QSize documentSize; int pageNumber; QPoint currentPoint; QObjectList linkTargets; QTimer* throttleTimer; KoToolBase* currentTool; CQTextDocumentNotesModel* notes; KoTextEditor* textEditor; void updateLinkTargets() { qDeleteAll(linkTargets); linkTargets.clear(); if (!canvas) { return; } foreach(const KoShape* shape, canvas->shapeManager()->shapes()) { if (!shape->hyperLink().isEmpty()) { QObject * obj = new QObject(documentModel); obj->setProperty("linkRect", shape->boundingRect()); obj->setProperty("linkTarget", QUrl(shape->hyperLink())); linkTargets.append(obj); } } foreach(QTextDocument* text, findText->documents()) { QTextBlock block = text->rootFrame()->firstCursorPosition().block(); for (; block.isValid(); block = block.next()) { block.begin(); QTextBlock::iterator it; for (it = block.begin(); !(it.atEnd()); ++it) { QTextFragment fragment = it.fragment(); if (fragment.isValid()) { QTextCharFormat format = fragment.charFormat(); if (format.isAnchor()) { // This is an anchor, store target and position... QObject * obj = new QObject(documentModel); QRectF rect = getFragmentPosition(block, fragment); KWPage page = document->pageManager()->page(rect.left()); rect.translate(page.topMargin(), page.rightMargin()); rect = canvas->viewMode()->documentToView(rect, canvas->viewConverter()); rect.translate(page.pageNumber() * (page.topMargin() + page.bottomMargin()) + 20, 0); obj->setProperty("linkRect", rect); obj->setProperty("linkTarget", QUrl(format.anchorHref())); linkTargets.append(obj); } } } } } } QRectF getFragmentPosition(QTextBlock block, QTextFragment fragment) { // TODO this only produces a position for the first part, if the link spans more than one line... // Need to sort that somehow, unfortunately probably by slapping this code into the above function. // For now leave it like this, more important things are needed. QTextLayout* layout = block.layout(); QTextLine line = layout->lineForTextPosition(fragment.position() - block.position()); if (!line.isValid()) { // fragment has no valid position and consequently no line... return QRectF(); } qreal top = line.position().y(); qreal bottom = line.position().y() + line.height(); qreal left = line.cursorToX(fragment.position() - block.position()); qreal right = line.cursorToX((fragment.position() - block.position()) + fragment.length()); QRectF fragmentPosition(QPointF(top, left), QPointF(bottom, right)); return fragmentPosition.adjusted(layout->position().x(), layout->position().y(), 0, 0); } QList deepShapeFind(const QList& shapes) { QList allShapes; foreach(KoShape* shape, shapes) { allShapes.append(shape); KoShapeContainer *container = dynamic_cast(shape); if (container) { allShapes.append(deepShapeFind(container->shapes())); } } return allShapes; } QRectF getCursorPosition(int position) { QPointF point; QTextBlock block = textEditor->document()->findBlock(position); QTextLayout* layout = block.layout(); QTextLine line = layout->lineForTextPosition(position - block.position()); if (!line.isValid()) { // fragment has no valid position and consequently no line... return QRectF(); } qreal top = line.position().y(); qreal left = line.cursorToX(position - block.position()); point = QPointF(left + layout->position().y(), top + layout->position().x()); KoShape* shape = canvas->shapeManager()->selection()->firstSelectedShape(); point += shape->position(); while(KoShapeContainer* parent = shape->parent()) { point += parent->position(); } KWPage page = document->pageManager()->page(point.y()); // point += QPointF(page.rightMargin(), page.pageNumber() * page.topMargin() // + (page.pageNumber() - 1) * page.bottomMargin()); // if (page.pageNumber() > 1) // point += QPointF(0, 20); point += QPointF(0, (page.pageNumber() - 1) * (page.topMargin() + 20)); // point = canvas->viewConverter()->documentToView(point); return canvas->viewConverter()->documentToView(QRectF(point, QSizeF(0, line.height()))); } }; CQTextDocumentCanvas::CQTextDocumentCanvas(QDeclarativeItem* parent) : CQCanvasBase(parent), d(new Private) { setAcceptedMouseButtons(Qt::LeftButton); d->findText = new KoFindText(this); connect (d->findText, SIGNAL(updateCanvas()), SLOT(updateCanvas())); connect (d->findText, SIGNAL(matchFound(KoFindMatch)), SLOT(findMatchFound(KoFindMatch))); connect (d->findText, SIGNAL(noMatchFound()), SLOT(findNoMatchFound())); connect (KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), SLOT(currentToolChanged(KoCanvasController*,int))); } CQTextDocumentCanvas::~CQTextDocumentCanvas() { d->part->removeMainWindow(d->part->currentMainwindow()); KoToolManager::instance()->removeCanvasController(d->canvas->canvasController()); delete d; } void CQTextDocumentCanvas::openFile(const QString& uri) { emit loadingBegun(); KoDocumentEntry entry; QList pluginLoaders = KoPluginLoader::pluginLoaders("calligra/parts"); Q_FOREACH (QPluginLoader *loader, pluginLoaders) { if (loader->fileName().contains(QLatin1String("wordspart"))) { entry = KoDocumentEntry(loader); pluginLoaders.removeOne(loader); break; } } qDeleteAll(pluginLoaders); if (entry.isEmpty()) { qWarning("Unable to load Words plugin, aborting!"); return; } // QT5TODO: ownership of d->part unclear d->part = entry.createKoPart(); KoDocument* document = d->part->document(); document->setAutoSave(0); document->setCheckAutoSaveFile(false); QUrl url(uri); if (url.scheme() == "newfile") { KWDocument* doc = qobject_cast(document); doc->initEmpty(); KWPageStyle style = doc->pageManager()->defaultPageStyle(); Q_ASSERT(style.isValid()); KoColumns columns; columns.count = url.queryItemValue("columncount").toInt(); columns.gapWidth = url.queryItemValue("columngap").toDouble(); style.setColumns(columns); KoPageLayout layout = style.pageLayout(); layout.format = KoPageFormat::formatFromString(url.queryItemValue("pageformat")); layout.orientation = (KoPageFormat::Orientation)url.queryItemValue("pageorientation").toInt(); layout.height = MM_TO_POINT(url.queryItemValue("height").toDouble()); layout.width = MM_TO_POINT(url.queryItemValue("width").toDouble()); if (url.queryItemValue("facingpages").toInt() == 1) { layout.bindingSide = MM_TO_POINT(url.queryItemValue("leftmargin").toDouble()); layout.pageEdge = MM_TO_POINT(url.queryItemValue("rightmargin").toDouble()); layout.leftMargin = layout.rightMargin = -1; } else { layout.bindingSide = layout.pageEdge = -1; layout.leftMargin = MM_TO_POINT(url.queryItemValue("leftmargin").toDouble()); layout.rightMargin = MM_TO_POINT(url.queryItemValue("rightmargin").toDouble()); } layout.topMargin = MM_TO_POINT(url.queryItemValue("topmargin").toDouble()); layout.bottomMargin = MM_TO_POINT(url.queryItemValue("bottommargin").toDouble()); style.setPageLayout(layout); doc->setUnit(KoUnit::fromSymbol(url.queryItemValue("unit"))); doc->relayout(); } else if (url.scheme() == "template") { qApp->setOverrideCursor(Qt::BusyCursor); // Nip away the manually added template:// bit of the uri passed from the caller bool ok = document->loadNativeFormat(uri.mid(11)); document->setModified(false); document->undoStack()->clear(); if (ok) { QString mimeType = QMimeDatabase().mimeTypeForUrl(url).name(); // in case this is a open document template remove the -template from the end mimeType.remove( QRegExp( "-template$" ) ); document->setMimeTypeAfterLoading(mimeType); document->resetURL(); document->setEmpty(); } else { document->showLoadingErrorDialog(); document->initEmpty(); } qApp->restoreOverrideCursor(); } else { document->openUrl(url); } document->setModified(false); qApp->processEvents(); d->canvas = dynamic_cast (d->part->canvasItem(d->part->document())); createAndSetCanvasControllerOn(d->canvas); createAndSetZoomController(d->canvas); updateZoomControllerAccordingToDocument(document); d->canvas->resourceManager()->setResource(KoDocumentResourceManager::HandleRadius, 9); d->canvas->resourceManager()->setResource(KoDocumentResourceManager::GrabSensitivity, 9); QGraphicsWidget *graphicsWidget = dynamic_cast(d->canvas); graphicsWidget->setParentItem(this); graphicsWidget->installEventFilter(this); graphicsWidget->setVisible(true); graphicsWidget->setGeometry(x(), y(), width(), height()); if (d->pageNumber >= 1) { gotoPage(d->pageNumber, document); } QList texts; KoFindText::findTextInShapes(d->canvas->shapeManager()->shapes(), texts); d->findText->setDocuments(texts); d->document = qobject_cast(document); d->documentModel = new CQTextDocumentModel(this, d->document, d->canvas->shapeManager()); emit documentModelChanged(); emit thumbnailSizeChanged(); connect(d->documentModel, SIGNAL(thumbnailSizeChanged()), SIGNAL(thumbnailSizeChanged())); d->updateLinkTargets(); emit linkTargetsChanged(); connect(d->canvas->shapeManager(), SIGNAL(selectionChanged()), SIGNAL(textEditorChanged())); connect(d->canvas->shapeManager(), SIGNAL(selectionChanged()), SIGNAL(shapeTransparencyChanged())); d->notes = new CQTextDocumentNotesModel(this); emit notesChanged(); emit textEditorChanged(); emit loadingFinished(); } void CQTextDocumentCanvas::gotoPage(int pageNumber, KoDocument *document) { const KWDocument *kwDoc = static_cast(document); KWPage currentTextDocPage = kwDoc->pageManager()->page(pageNumber); QRectF rect = d->canvas->viewConverter()->documentToView(currentTextDocPage.rect()); canvasController()->pan(rect.topLeft().toPoint() - d->canvas->viewConverter()->documentToView(canvasController()->documentOffset()).toPoint()); alignTopWith(rect.top()); updateCanvas(); } int CQTextDocumentCanvas::cameraY() const { return d->currentPoint.y(); } qreal CQTextDocumentCanvas::pagePosition(int pageIndex) { KWPage page = d->document->pageManager()->page(pageIndex); // a very silly heuristic for ensuring the page number changes if we change pages. // this means we don't have to glue the canvas and controlleritem together too close, // but yes, it does look a bit silly. QTimer::singleShot(0, d->throttleTimer, SLOT(stop())); QTimer::singleShot(0, this, SIGNAL(currentPageNumberChanged())); return d->canvas->viewMode()->documentToView(page.rect().topLeft(), d->canvas->viewConverter()).y(); } qreal CQTextDocumentCanvas::shapeTransparency() const { if (d->canvas && d->canvas->shapeManager()) { KoShape* shape = d->canvas->shapeManager()->selection()->firstSelectedShape(); if (shape) { return shape->transparency(); } } return CQCanvasBase::shapeTransparency(); } void CQTextDocumentCanvas::setShapeTransparency(qreal newTransparency) { if (d->canvas && d->canvas->shapeManager()) { KoShape* shape = d->canvas->shapeManager()->selection()->firstSelectedShape(); if (shape) { if (!qFuzzyCompare(1 + shape->transparency(), 1 + newTransparency)) { shape->setTransparency(newTransparency); CQCanvasBase::setShapeTransparency(newTransparency); } } } } QObject* CQTextDocumentCanvas::textEditor() { if (d->canvas) { if (d->textEditor) { disconnect(d->textEditor, SIGNAL(cursorPositionChanged()), this, SIGNAL(selectionChanged())); } d->textEditor = KoTextEditor::getTextEditorFromCanvas(d->canvas); if (d->textEditor) { disconnect(d->textEditor, SIGNAL(cursorPositionChanged()), this, SIGNAL(selectionChanged())); } emit selectionChanged(); return d->textEditor; } return 0; } bool CQTextDocumentCanvas::hasSelection() const { if (d->textEditor) { return d->textEditor->hasSelection(); } return false; } QRectF CQTextDocumentCanvas::selectionStartPos() const { if (d->textEditor) { return d->getCursorPosition(d->textEditor->selectionStart()); } return QRectF(0,0,0,0); } QRectF CQTextDocumentCanvas::selectionEndPos() const { if (d->textEditor) { return d->getCursorPosition(d->textEditor->selectionEnd()); } return QRectF(0,0,0,0); } QObject* CQTextDocumentCanvas::zoomAction() const { if (zoomController() && zoomController()->zoomAction()) { return zoomController()->zoomAction(); } return 0; } QSizeF CQTextDocumentCanvas::thumbnailSize() const { if (d->documentModel) { return d->documentModel->thumbnailSize(); } return QSizeF(); } void CQTextDocumentCanvas::setThumbnailSize(const QSizeF& newSize) { if (d->documentModel) { d->documentModel->setThumbnailSize(newSize.toSize()); } emit thumbnailSizeChanged(); } void CQTextDocumentCanvas::deselectEverything() { KoTextEditor* editor = KoTextEditor::getTextEditorFromCanvas(d->canvas); if (editor) { editor->clearSelection(); } d->canvas->shapeManager()->selection()->deselectAll(); updateCanvas(); } QObject* CQTextDocumentCanvas::notes() const { return d->notes; } void CQTextDocumentCanvas::addSticker(const QString& imageUrl) { QSvgRenderer renderer(QUrl(imageUrl).toLocalFile()); // Prepare a QImage with desired characteritisc QImage image(200, 200, QImage::Format_ARGB32); image.fill(Qt::transparent); // Get QPainter that paints to the image QPainter painter(&image); renderer.render(&painter); painter.end(); KoProperties* params = new KoProperties(); params->setProperty("qimage", image); KoShapeFactoryBase* factory = KoShapeRegistry::instance()->get("PictureShape"); if (factory) { KoShape* shape = factory->createShape(params, d->document->resourceManager()); QPointF pos = d->canvas->viewToDocument(d->canvas->documentOffset() + QPointF(d->canvas->size().width() / 2, d->canvas->size().height() / 2)); KoShapeAnchor *anchor = new KoShapeAnchor(shape); anchor->setAnchorType(KoShapeAnchor::AnchorPage); anchor->setHorizontalPos(KoShapeAnchor::HFromLeft); anchor->setVerticalPos(KoShapeAnchor::VFromTop); anchor->setHorizontalRel(KoShapeAnchor::HPage); anchor->setVerticalRel(KoShapeAnchor::VPage); shape->setAnchor(anchor); shape->setPosition(pos); shape->scale(0.2, 0.2); // KWShapeCreateCommand *cmd = new KWShapeCreateCommand(d->document, shape); KoSelection *selection = d->canvas->shapeManager()->selection(); selection->deselectAll(); selection->select(shape); // d->canvas->addCommand(cmd); d->canvas->shapeManager()->addShape(shape); d->notes->addEntry("", imageUrl, "Neutral", shape); } } void CQTextDocumentCanvas::addNote(const QString& text, const QString& color, const QString& imageUrl) { QSvgRenderer renderer(QUrl(imageUrl).toLocalFile()); // Prepare a QImage with desired characteritisc QImage image(400, 200, QImage::Format_ARGB32); image.fill(Qt::transparent); // Get QPainter that paints to the image QPainter painter(&image); painter.setRenderHint(QPainter::Antialiasing, true); painter.setRenderHint(QPainter::SmoothPixmapTransform, true); painter.setRenderHint(QPainter::TextAntialiasing, true); renderer.render(&painter, image.rect()); QFont font; font.setFamily("Permanent Marker"); font.setStyle(QFont::StyleNormal); font.setPixelSize(40); - painter.setPen(QColor(color)); + painter.setPen(QPen(QColor(color), 0)); painter.setFont(font); painter.drawText(image.rect().adjusted(10, 10, -20, -20), Qt::AlignCenter | Qt::TextWordWrap, text); painter.end(); KoProperties* params = new KoProperties(); params->setProperty("qimage", image); KoShapeFactoryBase* factory = KoShapeRegistry::instance()->get("PictureShape"); if (factory) { KoShape* shape = factory->createShape(params, d->document->resourceManager()); QPointF pos = d->canvas->viewToDocument(d->canvas->documentOffset() + QPointF(d->canvas->size().width() / 2, d->canvas->size().height() / 2)); KoShapeAnchor *anchor = new KoShapeAnchor(shape); anchor->setAnchorType(KoShapeAnchor::AnchorPage); anchor->setHorizontalPos(KoShapeAnchor::HFromLeft); anchor->setVerticalPos(KoShapeAnchor::VFromTop); anchor->setHorizontalRel(KoShapeAnchor::HPage); anchor->setVerticalRel(KoShapeAnchor::VPage); shape->setAnchor(anchor); shape->setPosition(pos); shape->rotate(-15); shape->scale(0.3, 0.3); // KWShapeCreateCommand *cmd = new KWShapeCreateCommand(d->document, shape); KoSelection *selection = d->canvas->shapeManager()->selection(); selection->deselectAll(); selection->select(shape); // d->canvas->addCommand(cmd); d->canvas->shapeManager()->addShape(shape); d->notes->addEntry(text, "", color, shape); } } void CQTextDocumentCanvas::setCameraY(int cameraY) { d->currentPoint.setY (cameraY); emit cameraYChanged(); } void CQTextDocumentCanvas::alignTopWith(int y) { d->currentPoint.setY(y); emit cameraYChanged(); } int CQTextDocumentCanvas::currentPageNumber() const { if (d->document && !d->throttleTimer->isActive()) { // Can't use this at the moment, we sort of don't have the right one, because derp :P //d->canvas->resourceManager()->resource(KoCanvasResourceManager::CurrentPage).toInt(); d->throttleTimer->start(); const KWDocument *kwDoc = static_cast(d->document); d->pageNumber = kwDoc->pageManager()->page(d->canvas->viewMode()->viewToDocument(d->canvas->documentOffset(), d->canvas->viewConverter())).pageNumber(); } return d->pageNumber; } void CQTextDocumentCanvas::setCurrentPageNumber(const int& currentPageNumber) { if (d->pageNumber != currentPageNumber) { gotoPage(currentPageNumber, d->document); } } void CQTextDocumentCanvas::render(QPainter* painter, const QRectF& target) { Q_UNUSED(target) QStyleOptionGraphicsItem option; option.exposedRect = QRect(0, 0, width(), height()); option.rect = option.exposedRect.toAlignedRect(); d->canvas->canvasItem()->paint(painter, &option); } bool CQTextDocumentCanvas::event( QEvent* event ) { switch(static_cast(event->type())) { case ViewModeSwitchEvent::AboutToSwitchViewModeEvent: { ViewModeSynchronisationObject* syncObject = static_cast(event)->synchronisationObject(); if (d->canvas) { syncObject->documentOffset = d->canvas->documentOffset(); syncObject->zoomLevel = zoomController()->zoomAction()->effectiveZoom(); syncObject->activeToolId = KoToolManager::instance()->activeToolId(); syncObject->shapes = d->canvas->shapeManager()->shapes(); syncObject->initialized = true; } return true; } case ViewModeSwitchEvent::SwitchedToTouchModeEvent: { ViewModeSynchronisationObject* syncObject = static_cast(event)->synchronisationObject(); if (d->canvas && syncObject->initialized) { d->canvas->shapeManager()->setShapes(syncObject->shapes); KoToolManager::instance()->switchToolRequested("PageToolFactory_ID"); qApp->processEvents(); zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, syncObject->zoomLevel); qApp->processEvents(); emit positionShouldChange(syncObject->documentOffset); } return true; } // case KisTabletEvent::TabletPressEx: // case KisTabletEvent::TabletReleaseEx: // emit interactionStarted(); // d->canvas->inputManager()->eventFilter(this, event); // return true; // case KisTabletEvent::TabletMoveEx: // d->tabletEventCount++; //Note that this will wraparound at some point; This is intentional. // #ifdef Q_OS_X11 // if (d->tabletEventCount % 2 == 0) // #endif // d->canvas->inputManager()->eventFilter(this, event); // return true; default: break; } return QDeclarativeItem::event( event ); } void CQTextDocumentCanvas::currentToolChanged(KoCanvasController* controller, int uniqueToolId) { Q_UNUSED(controller) Q_UNUSED(uniqueToolId) d->currentTool = qobject_cast(KoToolManager::instance()->toolById(d->canvas, KoToolManager::instance()->activeToolId())); } void CQTextDocumentCanvas::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* e) { QMouseEvent me(e->type(), e->pos().toPoint(), e->button(), e->buttons(), e->modifiers()); KoPointerEvent pe(&me, d->canvas->viewToDocument(e->pos() + d->canvas->documentOffset())); d->currentTool->mouseDoubleClickEvent(&pe); updateCanvas(); emit selectionChanged(); e->setAccepted(me.isAccepted()); } void CQTextDocumentCanvas::mouseMoveEvent(QGraphicsSceneMouseEvent* e) { QMouseEvent me(e->type(), e->pos().toPoint(), e->button(), e->buttons(), e->modifiers()); KoPointerEvent pe(&me, d->canvas->viewToDocument(e->pos() + d->canvas->documentOffset())); d->currentTool->mouseMoveEvent(&pe); updateCanvas(); emit selectionChanged(); e->setAccepted(me.isAccepted()); } void CQTextDocumentCanvas::mousePressEvent(QGraphicsSceneMouseEvent* e) { QMouseEvent me(e->type(), e->pos().toPoint(), e->button(), e->buttons(), e->modifiers()); KoPointerEvent pe(&me, d->canvas->viewToDocument(e->pos() + d->canvas->documentOffset())); d->currentTool->mousePressEvent(&pe); updateCanvas(); emit selectionChanged(); e->setAccepted(me.isAccepted()); } void CQTextDocumentCanvas::mouseReleaseEvent(QGraphicsSceneMouseEvent* e) { QMouseEvent me(e->type(), e->pos().toPoint(), e->button(), e->buttons(), e->modifiers()); KoPointerEvent pe(&me, d->canvas->viewToDocument(e->pos() + d->canvas->documentOffset())); d->currentTool->mouseReleaseEvent(&pe); updateCanvas(); emit selectionChanged(); e->setAccepted(me.isAccepted()); } void CQTextDocumentCanvas::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) { if (d->canvas) { QGraphicsWidget *widget = dynamic_cast(d->canvas); if (widget) { widget->setGeometry(newGeometry); } } QDeclarativeItem::geometryChanged(newGeometry, oldGeometry); } void CQTextDocumentCanvas::createAndSetCanvasControllerOn(KoCanvasBase* canvas) { //TODO: pass a proper action collection CQCanvasController *controller = new CQCanvasController(new KActionCollection(this)); setCanvasController(controller); connect (controller, SIGNAL(documentSizeChanged(QSize)), SLOT(updateDocumentSize(QSize))); controller->setCanvas(canvas); KoToolManager::instance()->addController (controller); } void CQTextDocumentCanvas::createAndSetZoomController(KoCanvasBase* canvas) { KoZoomHandler* zoomHandler = static_cast (canvas->viewConverter()); setZoomController(new KoZoomController(canvasController(), zoomHandler, new KActionCollection(this))); KWCanvasItem *kwCanvasItem = static_cast(canvas); connect (kwCanvasItem, SIGNAL(documentSize(QSizeF)), zoomController(), SLOT(setDocumentSize(QSizeF))); connect (canvasController()->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), SIGNAL(currentPageNumberChanged())); connect (canvasController()->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), kwCanvasItem, SLOT(setDocumentOffset(QPoint))); connect (zoomController(), SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)), SIGNAL(zoomActionChanged())); kwCanvasItem->updateSize(); emit zoomActionChanged(); } void CQTextDocumentCanvas::updateZoomControllerAccordingToDocument(const KoDocument* document) { const KWDocument *kwDoc = static_cast(document); zoomController()->setPageSize (kwDoc->pageManager()->begin().rect().size()); } QString CQTextDocumentCanvas::searchTerm() const { return d->searchTerm; } void CQTextDocumentCanvas::setSearchTerm(const QString& term) { d->searchTerm = term; if (!term.isEmpty()) { d->findText->find(term); } emit searchTermChanged(); } void CQTextDocumentCanvas::findMatchFound(const KoFindMatch &match) { QTextCursor cursor = match.location().value(); d->canvas->canvasItem()->update(); d->canvas->resourceManager()->setResource (KoText::CurrentTextAnchor, cursor.anchor()); d->canvas->resourceManager()->setResource (KoText::CurrentTextPosition, cursor.position()); } void CQTextDocumentCanvas::findNoMatchFound() { qDebug() << "Match for " << d->searchTerm << " not found"; } void CQTextDocumentCanvas::updateCanvas() { KWCanvasItem* kwCanvasItem = dynamic_cast (d->canvas); kwCanvasItem->update(); } void CQTextDocumentCanvas::findNext() { d->findText->findNext(); } void CQTextDocumentCanvas::findPrevious() { d->findText->findPrevious(); } QObject* CQTextDocumentCanvas::documentModel() const { return d->documentModel; } KWDocument* CQTextDocumentCanvas::document() const { return d->document; } QObject* CQTextDocumentCanvas::doc() const { return d->document; } QObject* CQTextDocumentCanvas::part() const { return d->part; } QObjectList CQTextDocumentCanvas::linkTargets() const { return d->linkTargets; } QSize CQTextDocumentCanvas::documentSize() const { return d->documentSize; } void CQTextDocumentCanvas::updateDocumentSize(const QSize& size) { d->documentSize = size; emit documentSizeChanged(); } diff --git a/sheets/part/Headers.cpp b/sheets/part/Headers.cpp index a1ea015e19c..fbc49c3f5bc 100644 --- a/sheets/part/Headers.cpp +++ b/sheets/part/Headers.cpp @@ -1,1161 +1,1161 @@ /* This file is part of the KDE project Copyright 2006 Robert Knight Copyright 2006 Inge Wallin Copyright 2006 Stefan Nikolaus Copyright 1999-2002,2004 Laurent Montel Copyright 2002-2005 Ariya Hidayat Copyright 1999-2004 David Faure Copyright 2004-2005 Meni Livne Copyright 2001-2003 Philipp Mueller Copyright 2002-2003 Norbert Andres Copyright 2003 Hamish Rodda Copyright 2003 Joseph Wenninger Copyright 2003 Lukas Tinkl Copyright 2000-2002 Werner Trobin Copyright 2002 Harri Porten Copyright 2002 John Dailey Copyright 2002 Daniel Naber Copyright 1999-2000 Torben Weis Copyright 1999-2000 Stephan Kulow Copyright 2000 Bernd Wuebben Copyright 2000 Wilco Greven Copyright 2000 Simon Hausmann Copyright 1999 Boris Wedl Copyright 1999 Reginald Stadlbauer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Local #include "Headers.h" // Qt #include #include #include #include #include // KF5 #include #include // Calligra #include #include #include #include #include // Sheets #include "CanvasBase.h" #include "Cell.h" #include "Doc.h" #include "calligra_sheets_limits.h" #include "RowColumnFormat.h" #include "RowFormatStorage.h" #include "Sheet.h" #include "ElapsedTime_p.h" // commands #include "commands/RowColumnManipulators.h" // ui #include "ui/Selection.h" using namespace Calligra::Sheets; /**************************************************************** * * RowHeader * ****************************************************************/ RowHeader::RowHeader(CanvasBase *_canvas) : m_pCanvas(_canvas), m_bSelection(false), m_iSelectionAnchor(1), m_bResize(false), m_lSize(0), m_bMousePressed(false), m_cellToolIsActive(true), m_font(KoGlobal::defaultFont()) { } RowHeader::~RowHeader() { } void RowHeader::mousePress(KoPointerEvent * _ev) { if (!m_cellToolIsActive) return; register Sheet * const sheet = m_pCanvas->activeSheet(); if (!sheet) return; if (_ev->button() == Qt::LeftButton) { m_bMousePressed = true; m_pCanvas->enableAutoScroll(); } double ev_PosY = m_pCanvas->zoomHandler()->unzoomItY(_ev->pos().y()) + m_pCanvas->yOffset(); double dHeight = m_pCanvas->zoomHandler()->unzoomItY(height()); m_bResize = false; m_bSelection = false; // We were editing a cell -> save value and get out of editing mode m_pCanvas->selection()->emitCloseEditor(true); // save changes // Find the first visible row and the y position of this row. qreal y; int row = sheet->topRow(m_pCanvas->yOffset(), y); // Did the user click between two rows? while (y < (dHeight + m_pCanvas->yOffset()) && (!m_bResize) && row <= KS_rowMax) { double h = sheet->rowFormats()->rowHeight(row); row++; if (row > KS_rowMax) row = KS_rowMax; if ((ev_PosY >= y + h - 2) && (ev_PosY <= y + h + 1) && !(sheet->rowFormats()->isHiddenOrFiltered(row) && row == 1)) m_bResize = true; y += h; } //if row is hide and it's the first row //you mustn't resize it. qreal tmp2; int tmpRow = sheet->topRow(ev_PosY - 1, tmp2); if (sheet->rowFormats()->isHiddenOrFiltered(tmpRow) && tmpRow == 1) m_bResize = false; // So the user clicked between two rows ? if (m_bResize) { // Determine row to resize qreal tmp; m_iResizedRow = sheet->topRow(ev_PosY - 1, tmp); if (!sheet->isProtected()) paintSizeIndicator(_ev->pos().y()); } else { if (_ev->button() != Qt::RightButton) { m_bSelection = true; } qreal tmp; int hit_row = sheet->topRow(ev_PosY, tmp); if (hit_row > KS_rowMax) return; m_iSelectionAnchor = hit_row; if (!m_pCanvas->selection()->contains(QPoint(1, hit_row)) || !(_ev->button() == Qt::RightButton) || !m_pCanvas->selection()->isRowSelected()) { QPoint newMarker(1, hit_row); QPoint newAnchor(KS_colMax, hit_row); if (_ev->modifiers() == Qt::ControlModifier) { m_pCanvas->selection()->extend(QRect(newAnchor, newMarker)); } else if (_ev->modifiers() == Qt::ShiftModifier) { m_pCanvas->selection()->update(newMarker); } else { m_pCanvas->selection()->initialize(QRect(newAnchor, newMarker)); } } if (_ev->button() == Qt::RightButton) { m_pCanvas->mousePressed(_ev); } } } void RowHeader::mouseRelease(KoPointerEvent * _ev) { if (!m_cellToolIsActive) return; m_pCanvas->disableAutoScroll(); if (m_lSize) m_lSize->hide(); m_bMousePressed = false; register Sheet * const sheet = m_pCanvas->activeSheet(); if (!sheet) return; double ev_PosY = m_pCanvas->zoomHandler()->unzoomItY(_ev->pos().y()) + m_pCanvas->yOffset(); if (m_bResize) { // Remove size indicator painted by paintSizeIndicator removeSizeIndicator(); QRect rect; rect.setCoords(1, m_iResizedRow, KS_colMax, m_iResizedRow); if (m_pCanvas->selection()->isRowSelected()) { if (m_pCanvas->selection()->contains(QPoint(1, m_iResizedRow))) { rect = m_pCanvas->selection()->lastRange(); } } double height = 0.0; double y = sheet->rowPosition(m_iResizedRow); if (ev_PosY - y <= 0.0) height = 0.0; else height = ev_PosY - y; if (height != 0.0) { ResizeRowManipulator* command = new ResizeRowManipulator(); command->setSheet(sheet); command->setSize(height); command->add(Region(rect, sheet)); if (!command->execute()) delete command; } else { // hide HideShowManipulator* command = new HideShowManipulator(); command->setSheet(sheet); command->setManipulateRows(true); command->add(Region(rect, sheet)); if (!command->execute()) delete command; } delete m_lSize; m_lSize = 0; } else if (m_bSelection) { QRect rect = m_pCanvas->selection()->lastRange(); // TODO: please don't remove. Right now it's useless, but it's for a future feature // Norbert bool m_frozen = false; if (m_frozen) { debugSheets << "selected: T" << rect.top() << " B" << rect.bottom(); int i; QList hiddenRows; for (i = rect.top(); i <= rect.bottom(); ++i) { if (sheet->rowFormats()->isHidden(i)) { hiddenRows.append(i); } } if (hiddenRows.count() > 0) { if (m_pCanvas->selection()->isColumnSelected()) { KMessageBox::error(/* XXX TODO this*/0, i18n("Area is too large.")); return; } HideShowManipulator* command = new HideShowManipulator(); command->setSheet(sheet); command->setManipulateRows(true); command->setReverse(true); command->add(*m_pCanvas->selection()); command->execute(); } } } m_bSelection = false; m_bResize = false; } void RowHeader::equalizeRow(double resize) { if (resize != 0.0) { ResizeRowManipulator* command = new ResizeRowManipulator(); command->setSheet(m_pCanvas->activeSheet()); command->setSize(qMax(2.0, resize)); command->add(*m_pCanvas->selection()); if (!command->execute()) delete command; } else { // hide HideShowManipulator* command = new HideShowManipulator(); command->setSheet(m_pCanvas->activeSheet()); command->setManipulateRows(true); command->add(*m_pCanvas->selection()); if (!command->execute()) delete command; } } void RowHeader::mouseDoubleClick(KoPointerEvent*) { if (!m_cellToolIsActive) return; register Sheet * const sheet = m_pCanvas->activeSheet(); if (!sheet) return; if (sheet->isProtected()) return; AdjustColumnRowManipulator* command = new AdjustColumnRowManipulator(); command->setSheet(sheet); command->setAdjustRow(true); command->add(*m_pCanvas->selection()); command->execute(); } void RowHeader::mouseMove(KoPointerEvent* _ev) { if (!m_cellToolIsActive) { setCursor(Qt::ArrowCursor); return; } register Sheet * const sheet = m_pCanvas->activeSheet(); if (!sheet) return; qreal ev_PosY = m_pCanvas->zoomHandler()->unzoomItY(_ev->pos().y()) + m_pCanvas->yOffset(); qreal dHeight = m_pCanvas->zoomHandler()->unzoomItY(height()); // The button is pressed and we are resizing ? if (m_bResize) { if (!sheet->isProtected()) paintSizeIndicator(_ev->pos().y()); } // The button is pressed and we are selecting ? else if (m_bSelection) { qreal y; int row = sheet->topRow(ev_PosY, y); if (row > KS_rowMax || row <= 0) return; QPoint newAnchor = m_pCanvas->selection()->anchor(); QPoint newMarker = m_pCanvas->selection()->marker(); newMarker.setY(row); newAnchor.setY(m_iSelectionAnchor); m_pCanvas->selection()->update(newMarker); if (_ev->pos().y() < 0) m_pCanvas->setVertScrollBarPos(qMax(0, ev_PosY)); else if (_ev->pos().y() > m_pCanvas->height()) { if (row < KS_rowMax) { const qreal rowHeight = sheet->rowFormats()->rowHeight(row + 1); y = sheet->rowPosition(row + 1); m_pCanvas->setVertScrollBarPos(ev_PosY + rowHeight - dHeight); } } } // No button is pressed and the mouse is just moved else { //What is the internal size of 1 pixel const double unzoomedPixel = m_pCanvas->zoomHandler()->unzoomItY(1.0); qreal y; int tmpRow = sheet->topRow(m_pCanvas->yOffset(), y); while (y < dHeight + m_pCanvas->yOffset() && tmpRow <= KS_rowMax) { double h = sheet->rowFormats()->visibleHeight(tmpRow); //if col is hide and it's the first column //you mustn't resize it. if (ev_PosY >= y + h - 2 * unzoomedPixel && ev_PosY <= y + h + unzoomedPixel && !(sheet->rowFormats()->isHiddenOrFiltered(tmpRow) && tmpRow == 1)) { setCursor(Qt::SplitVCursor); return; } y += h; tmpRow++; } setCursor(Qt::ArrowCursor); } } void RowHeader::paint(QPainter* painter, const QRectF& painterRect) { register Sheet * const sheet = m_pCanvas->activeSheet(); if (!sheet) return; // ElapsedTime et( "Painting vertical header", ElapsedTime::PrintOnlyTime ); // FIXME Stefan: Make use of clipping. Find the repaint call after the scrolling. // debugSheetsRender << event->rect(); // painting rectangle const QRectF paintRect = m_pCanvas->zoomHandler()->viewToDocument(painterRect); // the painter painter->setRenderHint(QPainter::TextAntialiasing); // fonts QFont normalFont(m_font); QFont boldFont(normalFont); boldFont.setBold(true); // background brush/color const QBrush backgroundBrush(palette().window()); const QColor backgroundColor(backgroundBrush.color()); // selection brush/color QColor selectionColor(palette().highlight().color()); selectionColor.setAlpha(127); const QBrush selectionBrush(selectionColor); qreal yPos; // Get the top row and the current y-position int y = sheet->topRow(qMax(0, paintRect.y() + m_pCanvas->yOffset()), yPos); // Align to the offset yPos = yPos - m_pCanvas->yOffset(); const KoViewConverter *converter = m_pCanvas->zoomHandler(); const qreal width = this->width() - 1; QSet selectedRows; QSet affectedRows; if (!m_pCanvas->selection()->referenceSelectionMode() && m_cellToolIsActive) { selectedRows = m_pCanvas->selection()->rowsSelected(); affectedRows = m_pCanvas->selection()->rowsAffected(); } // Loop through the rows, until we are out of range while (yPos <= paintRect.bottom() && y <= KS_rowMax) { const bool selected = (selectedRows.contains(y)); const bool highlighted = (!selected && affectedRows.contains(y)); if (sheet->rowFormats()->isHiddenOrFiltered(y)) { ++y; continue; } const qreal rawHeight = sheet->rowFormats()->rowHeight(y); const qreal height = converter->documentToViewY(rawHeight); const QRectF rect(0, converter->documentToViewY(yPos), width, height); if (selected || highlighted) { - painter->setPen(selectionColor.dark(150)); + painter->setPen(QPen(selectionColor.dark(150), 0)); painter->setBrush(selectionBrush); } else { - painter->setPen(backgroundColor.dark(150)); + painter->setPen(QPen(backgroundColor.dark(150), 0)); painter->setBrush(backgroundBrush); } painter->drawRect(rect); const QString rowText = QString::number(y); // Reset painter painter->setFont(normalFont); painter->setPen(palette().text().color()); if (selected) painter->setPen(palette().highlightedText().color()); else if (highlighted) painter->setFont(boldFont); QFontMetricsF fm(painter->font()); #if 0 if (height < fm.ascent() - fm.descent()) { // try to scale down the font to make it fit QFont font = painter->font(); qreal maxSize = font.pointSizeF(); qreal minSize = maxSize / 2; while (minSize > 1) { font.setPointSizeF(minSize); const QFontMetricsF fm2(font); if (height >= fm2.ascent() - fm2.descent()) break; minSize /= 2; } while (minSize < 0.99 * maxSize) { qreal middle = (maxSize + minSize) / 2; font.setPointSizeF(middle); const QFontMetricsF fm2(font); if (height >= fm2.ascent() - fm2.descent()) { minSize = middle; } else { maxSize = middle; } } painter->setFont(font); fm = QFontMetricsF(font); } #endif if (height >= fm.ascent() - fm.descent()) { painter->drawText(rect, Qt::AlignCenter, rowText); } yPos += rawHeight; y++; } } void RowHeader::focusOut(QFocusEvent*) { m_pCanvas->disableAutoScroll(); m_bMousePressed = false; } void RowHeader::doToolChanged(const QString& toolId) { m_cellToolIsActive = toolId.startsWith(QLatin1String("KSpread")); update(); } void RowHeader::setHeaderFont(const QFont &font) { m_font = font; update(); } QFont RowHeader::headerFont() const { return m_font; } /**************************************************************** * * ColumnHeader * ****************************************************************/ ColumnHeader::ColumnHeader(CanvasBase *_canvas) : m_pCanvas(_canvas), m_bSelection(false), m_iSelectionAnchor(1), m_bResize(false), m_lSize(0), m_bMousePressed(false), m_cellToolIsActive(true), m_font(KoGlobal::defaultFont()) { } ColumnHeader::~ColumnHeader() { } void ColumnHeader::mousePress(KoPointerEvent * _ev) { if (!m_cellToolIsActive) return; if (_ev->button() == Qt::LeftButton) { m_bMousePressed = true; m_pCanvas->enableAutoScroll(); } const register Sheet * const sheet = m_pCanvas->activeSheet(); if (!sheet) return; // We were editing a cell -> save value and get out of editing mode m_pCanvas->selection()->emitCloseEditor(true); // save changes double ev_PosX; double dWidth = m_pCanvas->zoomHandler()->unzoomItX(width()); if (sheet->layoutDirection() == Qt::RightToLeft) ev_PosX = dWidth - m_pCanvas->zoomHandler()->unzoomItX(_ev->pos().x()) + m_pCanvas->xOffset(); else ev_PosX = m_pCanvas->zoomHandler()->unzoomItX(_ev->pos().x()) + m_pCanvas->xOffset(); m_bResize = false; m_bSelection = false; // Find the first visible column and the x position of this column. qreal x; const double unzoomedPixel = m_pCanvas->zoomHandler()->unzoomItX(1.0); if (sheet->layoutDirection() == Qt::RightToLeft) { int tmpCol = sheet->leftColumn(m_pCanvas->xOffset(), x); debugSheets << "evPos:" << ev_PosX << ", x:" << x << ", COL:" << tmpCol; while (ev_PosX > x && (!m_bResize) && tmpCol <= KS_colMax) { double w = sheet->columnFormat(tmpCol)->width(); debugSheets << "evPos:" << ev_PosX << ", x:" << x << ", w:" << w << ", COL:" << tmpCol; ++tmpCol; if (tmpCol > KS_colMax) tmpCol = KS_colMax; //if col is hide and it's the first column //you mustn't resize it. if (ev_PosX >= x + w - unzoomedPixel && ev_PosX <= x + w + unzoomedPixel && !(sheet->columnFormat(tmpCol)->isHiddenOrFiltered() && tmpCol == 1)) { m_bResize = true; } x += w; } //if col is hide and it's the first column //you mustn't resize it. qreal tmp2; tmpCol = sheet->leftColumn(dWidth - ev_PosX + 1, tmp2); if (sheet->columnFormat(tmpCol)->isHiddenOrFiltered() && tmpCol == 0) { debugSheets << "No resize:" << tmpCol << "," << sheet->columnFormat(tmpCol)->isHiddenOrFiltered(); m_bResize = false; } debugSheets << "Resize:" << m_bResize; } else { int col = sheet->leftColumn(m_pCanvas->xOffset(), x); // Did the user click between two columns? while (x < (dWidth + m_pCanvas->xOffset()) && (!m_bResize) && col <= KS_colMax) { double w = sheet->columnFormat(col)->width(); col++; if (col > KS_colMax) col = KS_colMax; if ((ev_PosX >= x + w - unzoomedPixel) && (ev_PosX <= x + w + unzoomedPixel) && !(sheet->columnFormat(col)->isHiddenOrFiltered() && col == 1)) m_bResize = true; x += w; } //if col is hide and it's the first column //you mustn't resize it. qreal tmp2; int tmpCol = sheet->leftColumn(ev_PosX - 1, tmp2); if (sheet->columnFormat(tmpCol)->isHiddenOrFiltered() && tmpCol == 1) m_bResize = false; } // So the user clicked between two rows ? if (m_bResize) { // Determine the column to resize qreal tmp; if (sheet->layoutDirection() == Qt::RightToLeft) { m_iResizedColumn = sheet->leftColumn(ev_PosX - 1, tmp); // debugSheets <<"RColumn:" << m_iResizedColumn <<", PosX:" << ev_PosX; if (!sheet->isProtected()) paintSizeIndicator(_ev->pos().x()); } else { m_iResizedColumn = sheet->leftColumn(ev_PosX - 1, tmp); if (!sheet->isProtected()) paintSizeIndicator(_ev->pos().x()); } // debugSheets <<"Column:" << m_iResizedColumn; } else { if (_ev->button() != Qt::RightButton) { m_bSelection = true; } qreal tmp; int hit_col = sheet->leftColumn(ev_PosX, tmp); if (hit_col > KS_colMax) return; m_iSelectionAnchor = hit_col; if (!m_pCanvas->selection()->contains(QPoint(hit_col, 1)) || !(_ev->button() == Qt::RightButton) || !m_pCanvas->selection()->isColumnSelected()) { QPoint newMarker(hit_col, 1); QPoint newAnchor(hit_col, KS_rowMax); if (_ev->modifiers() == Qt::ControlModifier) { m_pCanvas->selection()->extend(QRect(newAnchor, newMarker)); } else if (_ev->modifiers() == Qt::ShiftModifier) { m_pCanvas->selection()->update(newMarker); } else { m_pCanvas->selection()->initialize(QRect(newAnchor, newMarker)); } } if (_ev->button() == Qt::RightButton) { m_pCanvas->mousePressed(_ev); } } } void ColumnHeader::mouseRelease(KoPointerEvent * _ev) { if (!m_cellToolIsActive) return; m_pCanvas->disableAutoScroll(); if (m_lSize) m_lSize->hide(); m_bMousePressed = false; register Sheet * const sheet = m_pCanvas->activeSheet(); if (!sheet) return; if (m_bResize) { double dWidth = m_pCanvas->zoomHandler()->unzoomItX(width()); double ev_PosX; // Remove size indicator painted by paintSizeIndicator removeSizeIndicator(); QRect rect; rect.setCoords(m_iResizedColumn, 1, m_iResizedColumn, KS_rowMax); if (m_pCanvas->selection()->isColumnSelected()) { if (m_pCanvas->selection()->contains(QPoint(m_iResizedColumn, 1))) { rect = m_pCanvas->selection()->lastRange(); } } double width = 0.0; double x; if (sheet->layoutDirection() == Qt::RightToLeft) ev_PosX = dWidth - m_pCanvas->zoomHandler()->unzoomItX(_ev->pos().x()) + m_pCanvas->xOffset(); else ev_PosX = m_pCanvas->zoomHandler()->unzoomItX(_ev->pos().x()) + m_pCanvas->xOffset(); x = sheet->columnPosition(m_iResizedColumn); if (ev_PosX - x <= 0.0) width = 0.0; else width = ev_PosX - x; if (width != 0.0) { ResizeColumnManipulator* command = new ResizeColumnManipulator(); command->setSheet(sheet); command->setSize(width); command->add(Region(rect, sheet)); if (!command->execute()) delete command; } else { // hide HideShowManipulator* command = new HideShowManipulator(); command->setSheet(sheet); command->setManipulateColumns(true); command->add(Region(rect, sheet)); if (!command->execute()) delete command; } delete m_lSize; m_lSize = 0; } else if (m_bSelection) { QRect rect = m_pCanvas->selection()->lastRange(); // TODO: please don't remove. Right now it's useless, but it's for a future feature // Norbert bool m_frozen = false; if (m_frozen) { debugSheets << "selected: L" << rect.left() << " R" << rect.right(); int i; QList hiddenCols; for (i = rect.left(); i <= rect.right(); ++i) { if (sheet->columnFormat(i)->isHidden()) { hiddenCols.append(i); } } if (hiddenCols.count() > 0) { if (m_pCanvas->selection()->isRowSelected()) { KMessageBox::error(0 /* XXX TODO this */, i18n("Area is too large.")); return; } HideShowManipulator* command = new HideShowManipulator(); command->setSheet(sheet); command->setManipulateColumns(true); command->setReverse(true); command->add(*m_pCanvas->selection()); command->execute(); } } } m_bSelection = false; m_bResize = false; } void ColumnHeader::equalizeColumn(double resize) { if (resize != 0.0) { ResizeColumnManipulator* command = new ResizeColumnManipulator(); command->setSheet(m_pCanvas->activeSheet()); command->setSize(qMax(2.0, resize)); command->add(*m_pCanvas->selection()); if (!command->execute()) delete command; } else { // hide HideShowManipulator* command = new HideShowManipulator(); command->setSheet(m_pCanvas->activeSheet()); command->setManipulateColumns(true); command->add(*m_pCanvas->selection()); if (!command->execute()) delete command; } } void ColumnHeader::mouseDoubleClick(KoPointerEvent*) { if (!m_cellToolIsActive) return; register Sheet * const sheet = m_pCanvas->activeSheet(); if (!sheet) return; if (sheet->isProtected()) return; AdjustColumnRowManipulator* command = new AdjustColumnRowManipulator(); command->setSheet(sheet); command->setAdjustColumn(true); command->add(*m_pCanvas->selection()); command->execute(); } void ColumnHeader::mouseMove(KoPointerEvent* _ev) { if (!m_cellToolIsActive) return; register Sheet * const sheet = m_pCanvas->activeSheet(); if (!sheet) return; double dWidth = m_pCanvas->zoomHandler()->unzoomItX(width()); double ev_PosX; if (sheet->layoutDirection() == Qt::RightToLeft) ev_PosX = dWidth - m_pCanvas->zoomHandler()->unzoomItX(_ev->pos().x()) + m_pCanvas->xOffset(); else ev_PosX = m_pCanvas->zoomHandler()->unzoomItX(_ev->pos().x()) + m_pCanvas->xOffset(); // The button is pressed and we are resizing ? if (m_bResize) { if (!sheet->isProtected()) paintSizeIndicator(_ev->pos().x()); } // The button is pressed and we are selecting ? else if (m_bSelection) { qreal x; int col = sheet->leftColumn(ev_PosX, x); if (col > KS_colMax || col <= 0) return; QPoint newMarker = m_pCanvas->selection()->marker(); QPoint newAnchor = m_pCanvas->selection()->anchor(); newMarker.setX(col); newAnchor.setX(m_iSelectionAnchor); m_pCanvas->selection()->update(newMarker); if (sheet->layoutDirection() == Qt::RightToLeft) { if (_ev->pos().x() < width() - m_pCanvas->width()) { const ColumnFormat *cl = sheet->columnFormat(col + 1); x = sheet->columnPosition(col + 1); m_pCanvas->setHorizScrollBarPos(- (int)((ev_PosX + cl->width()) - dWidth)); } else if (_ev->pos().x() > width()) m_pCanvas->setHorizScrollBarPos(- (ev_PosX - dWidth + m_pCanvas->zoomHandler()->unzoomItX(m_pCanvas->width()))); } else { if (_ev->pos().x() < 0) m_pCanvas->setHorizScrollBarPos(ev_PosX); else if (_ev->pos().x() > m_pCanvas->width()) { if (col < KS_colMax) { const ColumnFormat *cl = sheet->columnFormat(col + 1); x = sheet->columnPosition(col + 1); m_pCanvas->setHorizScrollBarPos(ev_PosX + cl->width() - dWidth); } } } } // No button is pressed and the mouse is just moved else { //What is the internal size of 1 pixel const double unzoomedPixel = m_pCanvas->zoomHandler()->unzoomItX(1.0); qreal x; if (sheet->layoutDirection() == Qt::RightToLeft) { int tmpCol = sheet->leftColumn(m_pCanvas->xOffset(), x); while (ev_PosX > x && tmpCol <= KS_colMax) { double w = sheet->columnFormat(tmpCol)->visibleWidth(); ++tmpCol; //if col is hide and it's the first column //you mustn't resize it. if (ev_PosX >= x + w - unzoomedPixel && ev_PosX <= x + w + unzoomedPixel && !(sheet->columnFormat(tmpCol)->isHiddenOrFiltered() && tmpCol == 0)) { setCursor(Qt::SplitHCursor); return; } x += w; } setCursor(Qt::ArrowCursor); } else { int tmpCol = sheet->leftColumn(m_pCanvas->xOffset(), x); while (x < m_pCanvas->zoomHandler()->unzoomItY(width()) + m_pCanvas->xOffset() && tmpCol <= KS_colMax) { double w = sheet->columnFormat(tmpCol)->visibleWidth(); //if col is hide and it's the first column //you mustn't resize it. if (ev_PosX >= x + w - unzoomedPixel && ev_PosX <= x + w + unzoomedPixel && !(sheet->columnFormat(tmpCol)->isHiddenOrFiltered() && tmpCol == 1)) { setCursor(Qt::SplitHCursor); return; } x += w; tmpCol++; } setCursor(Qt::ArrowCursor); } } } void ColumnHeader::resize(const QSizeF& size, const QSizeF& oldSize) { register Sheet * const sheet = m_pCanvas->activeSheet(); if (!sheet) return; // workaround to allow horizontal resizing and zoom changing when sheet // direction and interface direction don't match (e.g. an RTL sheet on an // LTR interface) if (sheet->layoutDirection() == Qt::RightToLeft && !QApplication::isRightToLeft()) { int dx = size.width() - oldSize.width(); scroll(dx, 0); } else if (sheet->layoutDirection() == Qt::LeftToRight && QApplication::isRightToLeft()) { int dx = size.width() - oldSize.width(); scroll(-dx, 0); } } void ColumnHeader::paint(QPainter* painter, const QRectF& painterRect) { register Sheet * const sheet = m_pCanvas->activeSheet(); if (!sheet) return; // ElapsedTime et( "Painting horizontal header", ElapsedTime::PrintOnlyTime ); // FIXME Stefan: Make use of clipping. Find the repaint call after the scrolling. // debugSheetsRender << event->rect(); // painting rectangle const QRectF paintRect = m_pCanvas->zoomHandler()->viewToDocument(painterRect); // the painter painter->setRenderHint(QPainter::TextAntialiasing); // fonts QFont normalFont(m_font); QFont boldFont(normalFont); boldFont.setBold(true); // background brush/color const QBrush backgroundBrush(palette().window()); const QColor backgroundColor(backgroundBrush.color()); // selection brush/color QColor selectionColor(palette().highlight().color()); selectionColor.setAlpha(127); const QBrush selectionBrush(selectionColor); qreal xPos; int x; if (sheet->layoutDirection() == Qt::RightToLeft) { //Get the left column and the current x-position x = sheet->leftColumn(int(m_pCanvas->zoomHandler()->unzoomItX(width()) - paintRect.x() + m_pCanvas->xOffset()), xPos); //Align to the offset xPos = m_pCanvas->zoomHandler()->unzoomItX(width()) - xPos + m_pCanvas->xOffset(); } else { //Get the left column and the current x-position x = sheet->leftColumn(int(paintRect.x() + m_pCanvas->xOffset()), xPos); //Align to the offset xPos = xPos - m_pCanvas->xOffset(); } const KoViewConverter *converter = m_pCanvas->zoomHandler(); const qreal height = this->height() - 1; QSet selectedColumns; QSet affectedColumns; if (!m_pCanvas->selection()->referenceSelectionMode() && m_cellToolIsActive) { selectedColumns = m_pCanvas->selection()->columnsSelected(); affectedColumns = m_pCanvas->selection()->columnsAffected(); } int deltaX = 1; if (sheet->layoutDirection() == Qt::RightToLeft) { if (x > KS_colMax) x = KS_colMax; xPos -= sheet->columnFormat(x)->width(); deltaX = -1; } //Loop through the columns, until we are out of range while (xPos <= paintRect.right() && x <= KS_colMax) { bool selected = (selectedColumns.contains(x)); bool highlighted = (!selected && affectedColumns.contains(x)); const ColumnFormat *columnFormat = sheet->columnFormat(x); if (columnFormat->isHiddenOrFiltered()) { ++x; continue; } const qreal width = converter->documentToViewX(columnFormat->width()); const QRectF rect(converter->documentToViewX(xPos), 0, width, height); if (selected || highlighted) { - painter->setPen(selectionColor.dark(150)); + painter->setPen(QPen(selectionColor.dark(150), 0)); painter->setBrush(selectionBrush); } else { - painter->setPen(backgroundColor.dark(150)); + painter->setPen(QPen(backgroundColor.dark(150), 0)); painter->setBrush(backgroundBrush); } painter->drawRect(rect); // Reset painter painter->setFont(normalFont); painter->setPen(palette().text().color()); if (selected) painter->setPen(palette().highlightedText().color()); else if (highlighted) painter->setFont(boldFont); QString colText = sheet->getShowColumnNumber() ? QString::number(x) : Cell::columnName(x); QFontMetricsF fm(painter->font()); #if 0 if (width < fm.width(colText)) { // try to scale down the font to make it fit QFont font = painter->font(); qreal maxSize = font.pointSizeF(); qreal minSize = maxSize / 2; while (minSize > 1) { font.setPointSizeF(minSize); const QFontMetricsF fm2(font); if (width >= fm2.width(colText)) break; minSize /= 2; } while (minSize < 0.99 * maxSize) { qreal middle = (maxSize + minSize) / 2; font.setPointSizeF(middle); const QFontMetricsF fm2(font); if (width >= fm2.width(colText)) { minSize = middle; } else { maxSize = middle; } } painter->setFont(font); fm = QFontMetricsF(font); } #endif if (width >= fm.width(colText)) { #if 0 switch (x % 3) { case 0: colText = QString::number(height) + 'h'; break; case 1: colText = QString::number(fm.ascent()) + 'a'; break; case 2: colText = QString::number(fm.descent()) + 'd'; break; } #endif painter->drawText(rect, Qt::AlignCenter, colText); } xPos += columnFormat->width(); x += deltaX; } } void ColumnHeader::focusOut(QFocusEvent*) { m_pCanvas->disableAutoScroll(); m_bMousePressed = false; } void ColumnHeader::doToolChanged(const QString& toolId) { m_cellToolIsActive = toolId.startsWith(QLatin1String("KSpread")); update(); } void ColumnHeader::setHeaderFont(const QFont &font) { m_font = font; update(); } QFont ColumnHeader::headerFont() const { return m_font; } /**************************************************************** * * SelectAllButton * ****************************************************************/ SelectAllButton::SelectAllButton(CanvasBase* canvasBase) : m_canvasBase(canvasBase) , m_mousePressed(false) , m_cellToolIsActive(true) { } SelectAllButton::~SelectAllButton() { } void SelectAllButton::paint(QPainter* painter, const QRectF& painterRect) { // the painter painter->setClipRect(painterRect); // if all cells are selected if (m_canvasBase->selection()->isAllSelected() && !m_canvasBase->selection()->referenceSelectionMode() && m_cellToolIsActive) { // selection brush/color QColor selectionColor(palette().highlight().color()); selectionColor.setAlpha(127); const QBrush selectionBrush(selectionColor); - painter->setPen(selectionColor.dark(150)); + painter->setPen(QPen(selectionColor.dark(150), 0)); painter->setBrush(selectionBrush); } else { // background brush/color const QBrush backgroundBrush(palette().window()); const QColor backgroundColor(backgroundBrush.color()); - painter->setPen(backgroundColor.dark(150)); + painter->setPen(QPen(backgroundColor.dark(150), 0)); painter->setBrush(backgroundBrush); } painter->drawRect(painterRect.adjusted(0, 0, -1, -1)); } void SelectAllButton::mousePress(KoPointerEvent* event) { if (!m_cellToolIsActive) return; if (event->button() == Qt::LeftButton) m_mousePressed = true; } void SelectAllButton::mouseRelease(KoPointerEvent* event) { if (!m_cellToolIsActive) return; Q_UNUSED(event); if (!m_mousePressed) return; m_mousePressed = false; m_canvasBase->selection()->selectAll(); } void SelectAllButton::doToolChanged(const QString& toolId) { m_cellToolIsActive = toolId.startsWith(QLatin1String("KSpread")); update(); } diff --git a/sheets/part/TabBar.cpp b/sheets/part/TabBar.cpp index e9ae45c8b38..677fc2700c0 100644 --- a/sheets/part/TabBar.cpp +++ b/sheets/part/TabBar.cpp @@ -1,857 +1,857 @@ /* This file is part of the KDE project Copyright (C) 2003 Ariya Hidayat Copyright (C) 2003 Norbert Andres Copyright (C) 2002 Laurent Montel Copyright (C) 1999 David Faure Copyright (C) 1999 Boris Wedl Copyright (C) 1998-2000 Torben Weis 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 "TabBar.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // TODO // improvement possibilities // - use offscreen buffer to reduce flicker even more // - keep track of tabs, only (re)layout when necessary // - paint all tabs to buffer, show only by shifting // - customizable button pixmaps // - use QStyle to paint the tabs & buttons (is it good/possible?) namespace Calligra { namespace Sheets { class TabBarPrivate { public: TabBar* tabbar; // scroll buttons QToolButton* scrollFirstButton; QToolButton* scrollLastButton; QToolButton* scrollBackButton; QToolButton* scrollForwardButton; // read-only: no mouse drag, double-click, right-click bool readOnly; // list of all tabs, in order of appearance QStringList tabs; // array of QRect for each visible tabs QList tabRects; // leftmost tab (or rightmost for right-to-left layouts) int firstTab; // rightmost tab (or leftmost for right-to-left layouts) int lastTab; // the active tab in the range form 1..n. // if this value is 0, that means that no tab is active. int activeTab; // unusable space on the left, taken by the scroll buttons int offset; // when the user drag the tab (in order to move it) // this is the target position, it's 0 if no tab is dragged int targetTab; // wheel movement since selected tab was last changed by the // mouse wheel int wheelDelta; // true if autoscroll is active bool autoScroll; // calculate the bounding rectangle for each visible tab void layoutTabs(); // reposition scroll buttons void layoutButtons(); // find a tab whose bounding rectangle contains the pos // return -1 if no such tab is found int tabAt(const QPoint& pos); // draw a single tab void drawTab(QPainter& painter, QRect& rect, const QString& text, bool active); // draw a marker to indicate tab moving void drawMoveMarker(QPainter& painter, int x, int y); // update the enable/disable status of scroll buttons void updateButtons(); // get the font to use on the tabs QFont font(bool selected); }; // built-in pixmap for scroll-first button static const char * const arrow_leftmost_xpm[] = { "10 10 2 1", " c None", ". c #000000", " ", " . . ", " . .. ", " . ... ", " . .... ", " . ... ", " . .. ", " . . ", " ", " " }; // built-in pixmap for scroll-last button static const char * const arrow_rightmost_xpm[] = { "10 10 2 1", " c None", ". c #000000", " ", " . . ", " .. . ", " ... . ", " .... . ", " ... . ", " .. . ", " . . ", " ", " " }; // built-in pixmap for scroll-left button static const char * const arrow_left_xpm[] = { "10 10 2 1", " c None", ". c #000000", " ", " . ", " .. ", " ... ", " .... ", " ... ", " .. ", " . ", " ", " " }; // built-in pixmap for scroll-right button static const char * const arrow_right_xpm[] = { "10 10 2 1", " c None", ". c #000000", " ", " . ", " .. ", " ... ", " .... ", " ... ", " .. ", " . ", " ", " " }; void TabBarPrivate::layoutTabs() { tabRects.clear(); QFont f = font(true); QFontMetrics fm(f, tabbar); if (tabbar->isLeftToRight()) { // left to right int x = 0; for (int c = 0; c < tabs.count(); c++) { QRect rect; if (c >= firstTab - 1) { QString text = tabs[ c ]; int tw = fm.width(text) + 4; rect = QRect(x, 0, tw + 20, tabbar->height()); x = x + tw + 20; } tabRects.append(rect); } lastTab = tabRects.count(); for (int i = 0; i < tabRects.count(); i++) if (tabRects[i].right() - 10 + offset > tabbar->width()) { lastTab = i; break; } } else { // right to left int x = tabbar->width() - offset; for (int c = 0; c < tabs.count(); c++) { QRect rect; if (c >= firstTab - 1) { QString text = tabs[ c ]; int tw = fm.width(text) + 4; rect = QRect(x - tw - 20, 0, tw + 20, tabbar->height()); x = x - tw - 20; } tabRects.append(rect); } lastTab = tabRects.count(); for (int i = tabRects.count() - 1; i > 0; i--) if (tabRects[i].left() > 0) { lastTab = i + 1; break; } } } int TabBarPrivate::tabAt(const QPoint& pos) { for (int i = 0; i < tabRects.count(); i++) { QRect rect = tabRects[ i ]; if (rect.isNull()) continue; if (rect.contains(pos)) return i; } return -1; // not found } void TabBarPrivate::drawTab(QPainter& painter, QRect& rect, const QString& text, bool active) { QPolygon polygon; if (tabbar->isLeftToRight()) polygon << QPoint(rect.x(), rect.y()) << QPoint(rect.x(), rect.bottom() - 3) << QPoint(rect.x() + 2, rect.bottom()) << QPoint(rect.right() - 4, rect.bottom()) << QPoint(rect.right() - 2, rect.bottom() - 2) << QPoint(rect.right() + 5, rect.top()); else polygon << QPoint(rect.right(), rect.top()) << QPoint(rect.right(), rect.bottom() - 3) << QPoint(rect.right() - 2, rect.bottom()) << QPoint(rect.x() + 4, rect.bottom()) << QPoint(rect.x() + 2, rect.bottom() - 2) << QPoint(rect.x() - 5, rect.top()); painter.save(); // fill it first QBrush bg = tabbar->palette().background(); if (active) bg = tabbar->palette().base(); painter.setBrush(bg); painter.setPen(QPen(Qt::NoPen)); painter.drawPolygon(polygon); // draw the lines - painter.setPen(tabbar->palette().color(QPalette::Dark)); + painter.setPen(QPen(tabbar->palette().color(QPalette::Dark), 0)); painter.setRenderHint(QPainter::Antialiasing); if (!active) { const bool reverseLayout = tabbar->isRightToLeft(); painter.drawLine(rect.x() - (reverseLayout ? 5 : 0), rect.y(), rect.right() + (reverseLayout ? 0 : 5), rect.top()); } painter.drawPolyline(polygon); painter.setPen(tabbar->palette().color(QPalette::ButtonText)); QFont f = font(active); painter.setFont(f); QFontMetrics fm = painter.fontMetrics(); int tx = rect.x() + (rect.width() - fm.width(text)) / 2; int ty = rect.y() + (rect.height() - fm.height()) / 2 + fm.ascent(); painter.drawText(tx, ty, text); painter.restore(); } void TabBarPrivate::drawMoveMarker(QPainter& painter, int x, int y) { QPolygon movmark; movmark << QPoint(x, y) << QPoint(x + 7, y) << QPoint(x + 4, y + 6); QBrush oldBrush = painter.brush(); painter.setBrush(Qt::black); painter.drawPolygon(movmark); painter.setBrush(oldBrush); } void TabBarPrivate::layoutButtons() { int bw = tabbar->height(); int w = tabbar->width(); offset = bw * 4; if (tabbar->isLeftToRight()) { scrollFirstButton->setGeometry(0, 0, bw, bw); scrollFirstButton->setIcon(QIcon(QPixmap(arrow_leftmost_xpm))); scrollBackButton->setGeometry(bw, 0, bw, bw); scrollBackButton->setIcon(QIcon(QPixmap(arrow_left_xpm))); scrollForwardButton->setGeometry(bw*2, 0, bw, bw); scrollForwardButton->setIcon(QIcon(QPixmap(arrow_right_xpm))); scrollLastButton->setGeometry(bw*3, 0, bw, bw); scrollLastButton->setIcon(QIcon(QPixmap(arrow_rightmost_xpm))); } else { scrollFirstButton->setGeometry(w - bw, 0, bw, bw); scrollFirstButton->setIcon(QIcon(QPixmap(arrow_rightmost_xpm))); scrollBackButton->setGeometry(w - 2*bw, 0, bw, bw); scrollBackButton->setIcon(QIcon(QPixmap(arrow_right_xpm))); scrollForwardButton->setGeometry(w - 3*bw, 0, bw, bw); scrollForwardButton->setIcon(QIcon(QPixmap(arrow_left_xpm))); scrollLastButton->setGeometry(w - 4*bw, 0, bw, bw); scrollLastButton->setIcon(QIcon(QPixmap(arrow_leftmost_xpm))); } } void TabBarPrivate::updateButtons() { scrollFirstButton->setEnabled(tabbar->canScrollBack()); scrollBackButton->setEnabled(tabbar->canScrollBack()); scrollForwardButton->setEnabled(tabbar->canScrollForward()); scrollLastButton->setEnabled(tabbar->canScrollForward()); } QFont TabBarPrivate::font(bool selected) { QFont f = QFontDatabase::systemFont(QFontDatabase::TitleFont); if (selected) f.setBold(true); return f; } // creates a new tabbar TabBar::TabBar(QWidget* parent, const char* /*name*/) : QWidget(parent) , d(new TabBarPrivate) { d->tabbar = this; d->readOnly = false; d->firstTab = 1; d->lastTab = 0; d->activeTab = 0; d->targetTab = 0; d->wheelDelta = 0; d->autoScroll = false; d->offset = 64; // initialize the scroll buttons d->scrollFirstButton = new QToolButton(this); connect(d->scrollFirstButton, SIGNAL(clicked()), this, SLOT(scrollFirst())); d->scrollLastButton = new QToolButton(this); connect(d->scrollLastButton, SIGNAL(clicked()), this, SLOT(scrollLast())); d->scrollBackButton = new QToolButton(this); connect(d->scrollBackButton, SIGNAL(clicked()), this, SLOT(scrollBack())); d->scrollForwardButton = new QToolButton(this); connect(d->scrollForwardButton, SIGNAL(clicked()), this, SLOT(scrollForward())); d->layoutButtons(); d->updateButtons(); } // destroys the tabbar TabBar::~TabBar() { delete d; } // adds a new visible tab void TabBar::addTab(const QString& text) { d->tabs.append(text); update(); } // removes a tab void TabBar::removeTab(const QString& text) { int i = d->tabs.indexOf(text); if (i == -1) return; if (d->activeTab == i + 1) d->activeTab = 0; d->tabs.removeAll(text); update(); } // removes all tabs void TabBar::clear() { d->tabs.clear(); d->activeTab = 0; d->firstTab = 1; update(); } bool TabBar::readOnly() const { return d->readOnly; } void TabBar::setReadOnly(bool ro) { d->readOnly = ro; } void TabBar::setTabs(const QStringList& list) { QString left, active; if (d->activeTab > 0) active = d->tabs[ d->activeTab-1 ]; if (d->firstTab > 0 && d->firstTab <= d->tabs.size()) left = d->tabs[ d->firstTab-1 ]; d->tabs = list; if (!left.isNull()) { d->firstTab = d->tabs.indexOf(left) + 1; if (d->firstTab > (int)d->tabs.count()) d->firstTab = 1; if (d->firstTab <= 0) d->firstTab = 1; } d->activeTab = 0; if (!active.isNull()) setActiveTab(active); update(); } QStringList TabBar::tabs() const { return d->tabs; } unsigned TabBar::count() const { return d->tabs.count(); } bool TabBar::canScrollBack() const { if (d->tabs.count() == 0) return false; return d->firstTab > 1; } bool TabBar::canScrollForward() const { if (d->tabs.count() == 0) return false; return d->lastTab < (int)d->tabs.count(); } void TabBar::scrollBack() { if (!canScrollBack()) return; d->firstTab--; if (d->firstTab < 1) d->firstTab = 1; d->layoutTabs(); d->updateButtons(); update(); } void TabBar::scrollForward() { if (!canScrollForward()) return; d->firstTab ++; if (d->firstTab > (int)d->tabs.count()) d->firstTab = d->tabs.count(); d->layoutTabs(); d->updateButtons(); update(); } void TabBar::scrollFirst() { if (!canScrollBack()) return; d->firstTab = 1; d->layoutTabs(); d->updateButtons(); update(); } void TabBar::scrollLast() { if (!canScrollForward()) return; d->layoutTabs(); if (!isRightToLeft()) { int fullWidth = d->tabRects[ d->tabRects.count()-1 ].right(); int delta = fullWidth - width() + d->offset; for (int i = 0; i < d->tabRects.count(); i++) if (d->tabRects[i].x() > delta) { d->firstTab = i + 1; break; } } else { // FIXME optimize this, perhaps without loop for (; d->firstTab <= (int)d->tabRects.count();) { int x = d->tabRects[ d->tabRects.count()-1 ].x(); if (x > 0) break; d->firstTab++; d->layoutTabs(); } } d->layoutTabs(); d->updateButtons(); update(); } void TabBar::ensureVisible(const QString& tab) { int i = d->tabs.indexOf(tab); if (i == -1) return; i++; // already visible, then do nothing if ((i >= d->firstTab) && (i <= d->lastTab)) return; if (i < d->firstTab) while (i < d->firstTab) scrollBack(); if (i > d->lastTab) while (i > d->lastTab) scrollForward(); } void TabBar::moveTab(int tab, int target) { QString tabName = d->tabs.takeAt(tab); if (target > tab) target--; if (target >= d->tabs.count()) d->tabs.append(tabName); else d->tabs.insert(target, tabName); if (d->activeTab == tab + 1) d->activeTab = target + 1; update(); } void TabBar::setActiveTab(const QString& text) { int i = d->tabs.indexOf(text); if (i == -1) return; if (i + 1 == d->activeTab) return; d->activeTab = i + 1; d->updateButtons(); update(); emit tabChanged(text); } void TabBar::autoScrollBack() { if (!d->autoScroll) return; scrollBack(); if (!canScrollBack()) d->autoScroll = false; else QTimer::singleShot(400, this, SLOT(autoScrollBack())); } void TabBar::autoScrollForward() { if (!d->autoScroll) return; scrollForward(); if (!canScrollForward()) d->autoScroll = false; else QTimer::singleShot(400, this, SLOT(autoScrollForward())); } void TabBar::paintEvent(QPaintEvent*) { if (d->tabs.count() == 0) { update(); return; } d->layoutTabs(); d->updateButtons(); QPainter painter(this); if (!isRightToLeft()) painter.translate(d->offset, 0); if (!isRightToLeft()) painter.translate(5, 0); // draw first all non-active, visible tabs for (int c = d->tabRects.count() - 1; c >= 0; c--) { QRect rect = d->tabRects[ c ]; if (rect.isNull()) continue; QString text = d->tabs[ c ]; d->drawTab(painter, rect, text, false); } // draw the active tab if (d->activeTab > 0) { QRect rect = d->tabRects[ d->activeTab-1 ]; if (!rect.isNull()) { QString text = d->tabs[ d->activeTab-1 ]; d->drawTab(painter, rect, text, true); } } // draw the move marker if (d->targetTab > 0) { int p = qMin(d->targetTab, (int)d->tabRects.count()); QRect rect = d->tabRects[ p-1 ]; if (!rect.isNull()) { int x = !isRightToLeft() ? rect.x() : rect.right() - 7; if (d->targetTab > (int)d->tabRects.count()) x = !isRightToLeft() ? rect.right() - 7 : rect.x() - 3; d->drawMoveMarker(painter, x, rect.y()); } } } void TabBar::resizeEvent(QResizeEvent*) { d->layoutButtons(); d->updateButtons(); update(); } QSize TabBar::sizeHint() const { return QSize(40, style()->pixelMetric(QStyle::PM_ScrollBarExtent)); } void TabBar::renameTab(const QString& old_name, const QString& new_name) { d->tabs.replace(d->tabs.indexOf(old_name), new_name); update(); } QString TabBar::activeTab() const { if (d->activeTab == 0) return QString(); else return d->tabs[ d->activeTab-1 ]; } void TabBar::mousePressEvent(QMouseEvent* ev) { if (d->tabs.count() == 0) { update(); return; } d->layoutTabs(); QPoint pos = ev->pos(); if (!isRightToLeft()) pos = pos - QPoint(d->offset, 0); int tab = d->tabAt(pos) + 1; if ((tab > 0) && (tab != d->activeTab)) { d->activeTab = tab; update(); emit tabChanged(d->tabs[ d->activeTab-1]); // scroll if partially visible if (d->tabRects[ tab-1 ].right() > width() - d->offset) scrollForward(); } if (ev->button() == Qt::RightButton) if (!d->readOnly) emit contextMenu(ev->globalPos()); } void TabBar::mouseReleaseEvent(QMouseEvent* ev) { if (d->readOnly) return; d->autoScroll = false; if (ev->button() == Qt::LeftButton && d->targetTab != 0) { emit tabMoved(d->activeTab - 1, d->targetTab - 1); d->targetTab = 0; } } void TabBar::mouseMoveEvent(QMouseEvent* ev) { if (d->readOnly) return; QPoint pos = ev->pos(); if (!isRightToLeft()) pos = pos - QPoint(d->offset, 0); // check if user drags a tab to move it int i = d->tabAt(pos) + 1; if ((i > 0) && (i != d->targetTab)) { if (i == d->activeTab) i = 0; if (i == d->activeTab + 1) i = 0; if (i != d->targetTab) { d->targetTab = i; d->autoScroll = false; update(); } } // drag past the very latest visible tab // e.g move a tab to the last ordering position QRect r = d->tabRects[ d->tabRects.count()-1 ]; bool moveToLast = false; if (r.isValid()) { if (!isRightToLeft()) if (pos.x() > r.right()) if (pos.x() < width()) moveToLast = true; if (isRightToLeft()) if (pos.x() < r.x()) if (pos.x() > 0) moveToLast = true; } if (moveToLast) if (d->targetTab != (int)d->tabRects.count() + 1) { d->targetTab = d->tabRects.count() + 1; d->autoScroll = false; update(); } // outside far too left ? activate autoscroll... if (pos.x() < 0 && !d->autoScroll) { d->autoScroll = true; autoScrollBack(); } // outside far too right ? activate autoscroll... int w = width() - d->offset; if (pos.x() > w && !d->autoScroll) { d->autoScroll = true; autoScrollForward(); } } void TabBar::mouseDoubleClickEvent(QMouseEvent* ev) { int offset = isRightToLeft() ? 0 : d->offset; if (ev->pos().x() > offset) if (!d->readOnly) emit doubleClicked(); } void TabBar::wheelEvent(QWheelEvent * e) { if (d->tabs.count() == 0) { update(); return; } // Currently one wheel movement is a delta of 120. // The 'unused' delta is stored for devices that allow // a higher scrolling resolution. // The delta required to move one tab is one wheel movement: const int deltaRequired = 120; d->wheelDelta += e->delta(); int tabDelta = - (d->wheelDelta / deltaRequired); d->wheelDelta = d->wheelDelta % deltaRequired; int numTabs = d->tabs.size(); if (d->activeTab + tabDelta > numTabs) { // Would take us past the last tab d->activeTab = numTabs; } else if (d->activeTab + tabDelta < 1) { // Would take us before the first tab d->activeTab = 1; } else { d->activeTab = d->activeTab + tabDelta; } // Find the left and right edge of the new tab. If we're // going forward, and the right of the new tab isn't visible // then scroll forward. Likewise, if going back, and the // left of the new tab isn't visible, then scroll back. int activeTabRight = d->tabRects[ d->activeTab-1 ].right(); int activeTabLeft = d->tabRects[ d->activeTab-1 ].left(); if (tabDelta > 0 && activeTabRight > width() - d->offset) { scrollForward(); } else if (tabDelta < 0 && activeTabLeft < width() - d->offset) { scrollBack(); } update(); emit tabChanged(d->tabs[ d->activeTab-1]); } } // namespace Sheets } // namespace Calligra diff --git a/sheets/ui/CellToolBase_p.cpp b/sheets/ui/CellToolBase_p.cpp index d184dacdf21..a058554257c 100644 --- a/sheets/ui/CellToolBase_p.cpp +++ b/sheets/ui/CellToolBase_p.cpp @@ -1,1341 +1,1341 @@ /* This file is part of the KDE project Copyright 2006-2008 Stefan Nikolaus Copyright 2006 Robert Knight Copyright 2006 Inge Wallin Copyright 1999-2002,2004 Laurent Montel Copyright 2002-2005 Ariya Hidayat Copyright 1999-2004 David Faure Copyright 2004-2005 Meni Livne Copyright 2001-2003 Philipp Mueller Copyright 2002-2003 Norbert Andres Copyright 2003 Hamish Rodda Copyright 2003 Joseph Wenninger Copyright 2003 Lukas Tinkl Copyright 2000-2002 Werner Trobin Copyright 2002 Harri Porten Copyright 2002 John Dailey Copyright 2002 Daniel Naber Copyright 1999-2000 Torben Weis Copyright 1999-2000 Stephan Kulow Copyright 2000 Bernd Wuebben Copyright 2000 Wilco Greven Copyright 2000 Simon Hausmann Copyright 1999 Boris Wedl Copyright 1999 Reginald Stadlbauer 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 "CellToolBase_p.h" #include "CellToolBase.h" // Sheets #include "ApplicationSettings.h" #include "CalculationSettings.h" #include "CellStorage.h" #include "Map.h" #include "RowColumnFormat.h" #include "RowFormatStorage.h" #include "Selection.h" #include "Sheet.h" // commands #include "commands/DataManipulators.h" #include "commands/StyleCommand.h" // ui #include "ui/CellEditor.h" #include "ui/ExternalEditor.h" #include "ui/SheetView.h" // Calligra #include #include #include #include #include #include // KF5 #include #include #include // Qt #include #include #include #include using namespace Calligra::Sheets; void CellToolBase::Private::updateEditor(const Cell& cell) { if (!externalEditor) return; const Cell& theCell = cell.isPartOfMerged() ? cell.masterCell() : cell; const Style style = theCell.style(); if (q->selection()->activeSheet()->isProtected() && style.hideFormula()) { externalEditor->setPlainText(theCell.displayText()); } else if (q->selection()->activeSheet()->isProtected() && style.hideAll()) { externalEditor->clear(); } else { externalEditor->setPlainText(theCell.userInput()); } } #define ACTION_EXEC( name, command ) { \ QAction *a = q->action(name); \ const bool blocked = a->blockSignals(true); \ a->command; \ a->blockSignals(blocked); \ } void CellToolBase::Private::updateActions(const Cell& cell) { const Style style = cell.style(); // -- font actions -- ACTION_EXEC("bold", setChecked(style.bold())); ACTION_EXEC("italic", setChecked(style.italic())); ACTION_EXEC("underline", setChecked(style.underline())); ACTION_EXEC("strikeOut", setChecked(style.strikeOut())); static_cast(q->action("font"))->setFont(style.fontFamily()); static_cast(q->action("fontSize"))->setFontSize(style.fontSize()); // -- horizontal alignment actions -- ACTION_EXEC("alignLeft", setChecked(style.halign() == Style::Left)); ACTION_EXEC("alignCenter", setChecked(style.halign() == Style::Center)); ACTION_EXEC("alignRight", setChecked(style.halign() == Style::Right)); // -- vertical alignment actions -- ACTION_EXEC("alignTop", setChecked(style.valign() == Style::Top)); ACTION_EXEC("alignMiddle", setChecked(style.valign() == Style::Middle)); ACTION_EXEC("alignBottom", setChecked(style.valign() == Style::Bottom)); ACTION_EXEC("verticalText", setChecked(style.verticalText())); ACTION_EXEC("wrapText", setChecked(style.wrapText())); Format::Type ft = style.formatType(); ACTION_EXEC("percent", setChecked(ft == Format::Percentage)); ACTION_EXEC("currency", setChecked(ft == Format::Money)); const bool showFormulas = q->selection()->activeSheet()->getShowFormula(); q->action("alignLeft")->setEnabled(!showFormulas); q->action("alignCenter")->setEnabled(!showFormulas); q->action("alignRight")->setEnabled(!showFormulas); if (!q->selection()->activeSheet()->isProtected() || style.notProtected()) { q->action("clearComment")->setEnabled(!cell.comment().isEmpty()); q->action("decreaseIndentation")->setEnabled(style.indentation() > 0.0); } // Now, activate/deactivate some actions depending on what is selected. if (!q->selection()->activeSheet()->isProtected()) { const bool colSelected = q->selection()->isColumnSelected(); const bool rowSelected = q->selection()->isRowSelected(); // -- column & row actions -- q->action("resizeCol")->setEnabled(!rowSelected); q->action("insertColumn")->setEnabled(!rowSelected); q->action("deleteColumn")->setEnabled(!rowSelected); q->action("hideColumn")->setEnabled(!rowSelected); q->action("equalizeCol")->setEnabled(!rowSelected); q->action("resizeRow")->setEnabled(!colSelected); q->action("deleteRow")->setEnabled(!colSelected); q->action("insertRow")->setEnabled(!colSelected); q->action("hideRow")->setEnabled(!colSelected); q->action("equalizeRow")->setEnabled(!colSelected); // -- data insert actions -- q->action("textToColumns")->setEnabled(!rowSelected); const bool simpleSelection = q->selection()->isSingular() || colSelected || rowSelected; q->action("sheetFormat")->setEnabled(!simpleSelection); q->action("sort")->setEnabled(!simpleSelection); q->action("sortDec")->setEnabled(!simpleSelection); q->action("sortInc")->setEnabled(!simpleSelection); q->action("mergeCells")->setEnabled(!simpleSelection); q->action("mergeCellsHorizontal")->setEnabled(!simpleSelection); q->action("mergeCellsVertical")->setEnabled(!simpleSelection); q->action("fillRight")->setEnabled(!simpleSelection); q->action("fillUp")->setEnabled(!simpleSelection); q->action("fillDown")->setEnabled(!simpleSelection); q->action("fillLeft")->setEnabled(!simpleSelection); q->action("createStyleFromCell")->setEnabled(simpleSelection); // just from one cell const bool contiguousSelection = q->selection()->isContiguous(); q->action("subtotals")->setEnabled(contiguousSelection); } } void CellToolBase::Private::setProtectedActionsEnabled(bool enable) { // Enable/disable actions. const QList actions = q->actions().values(); for (int i = 0; i < actions.count(); ++i) actions[i]->setEnabled(enable); q->action("insertFormula")->setEnabled(enable); if (externalEditor) externalEditor->setEnabled(enable); // These actions are always enabled. q->action("copy")->setEnabled(true); q->action("gotoCell")->setEnabled(true); q->action("edit_find")->setEnabled(true); q->action("edit_find_next")->setEnabled(true); q->action("edit_find_last")->setEnabled(true); } void CellToolBase::Private::processEnterKey(QKeyEvent* event) { // array is true, if ctrl+alt are pressed bool array = (event->modifiers() & Qt::AltModifier) && (event->modifiers() & Qt::ControlModifier); /* save changes to the current editor */ q->deleteEditor(true, array); /* use the configuration setting to see which direction we're supposed to move when enter is pressed. */ Calligra::Sheets::MoveTo direction = q->selection()->activeSheet()->map()->settings()->moveToValue(); //if shift Button clicked inverse move direction if (event->modifiers() & Qt::ShiftModifier) { switch (direction) { case Bottom: direction = Top; break; case Top: direction = Bottom; break; case Left: direction = Right; break; case Right: direction = Left; break; case BottomFirst: direction = BottomFirst; break; case NoMovement: direction = NoMovement; break; } } /* never extend a selection with the enter key -- the shift key reverses direction, not extends the selection */ QRect r(moveDirection(direction, false)); event->accept(); // QKeyEvent } void CellToolBase::Private::processArrowKey(QKeyEvent *event) { /* NOTE: hitting the tab key also calls this function. Don't forget to account for it */ register Sheet * const sheet = q->selection()->activeSheet(); if (!sheet) return; /* save changes to the current editor */ q->selection()->emitCloseEditor(true); Calligra::Sheets::MoveTo direction = Bottom; bool makingSelection = event->modifiers() & Qt::ShiftModifier; switch (event->key()) { case Qt::Key_Down: direction = Bottom; break; case Qt::Key_Up: direction = Top; break; case Qt::Key_Left: if (sheet->layoutDirection() == Qt::RightToLeft) direction = Right; else direction = Left; break; case Qt::Key_Right: if (sheet->layoutDirection() == Qt::RightToLeft) direction = Left; else direction = Right; break; case Qt::Key_Tab: direction = Right; break; case Qt::Key_Backtab: //Shift+Tab moves to the left direction = Left; makingSelection = false; break; default: Q_ASSERT(false); break; } QRect r(moveDirection(direction, makingSelection)); event->accept(); // QKeyEvent } void CellToolBase::Private::processEscapeKey(QKeyEvent * event) { q->selection()->emitCloseEditor(false); // discard changes event->accept(); // QKeyEvent } bool CellToolBase::Private::processHomeKey(QKeyEvent* event) { register Sheet * const sheet = q->selection()->activeSheet(); if (!sheet) return false; bool makingSelection = event->modifiers() & Qt::ShiftModifier; if (q->editor()) { // We are in edit mode -> go beginning of line QApplication::sendEvent(q->editor()->widget(), event); return false; } else { QPoint destination; /* start at the first used cell in the row and cycle through the right until we find a cell that has some output text. But don't look past the current marker. The end result we want is to move to the left to the first cell with text, or just to the first column if there is no more text to the left. But why? In excel, home key sends you to the first column always. We might want to change to that behavior. */ if (event->modifiers() & Qt::ControlModifier) { /* ctrl + Home will always just send us to location (1,1) */ destination = QPoint(1, 1); } else { QPoint marker = q->selection()->marker(); Cell cell = sheet->cellStorage()->firstInRow(marker.y(), CellStorage::VisitContent); while (!cell.isNull() && cell.column() < marker.x() && cell.isEmpty()) { cell = sheet->cellStorage()->nextInRow(cell.column(), cell.row(), CellStorage::VisitContent); } int col = (!cell.isNull() ? cell.column() : 1); if (col == marker.x()) col = 1; destination = QPoint(col, marker.y()); } if (q->selection()->marker() == destination) return false; if (makingSelection) { q->selection()->update(destination); } else { q->selection()->initialize(destination, sheet); } q->scrollToCell(destination); event->accept(); // QKeyEvent } return true; } bool CellToolBase::Private::processEndKey(QKeyEvent *event) { register Sheet * const sheet = q->selection()->activeSheet(); if (!sheet) return false; bool makingSelection = event->modifiers() & Qt::ShiftModifier; Cell cell; QPoint marker = q->selection()->marker(); if (q->editor()) { // We are in edit mode -> go end of line QApplication::sendEvent(q->editor()->widget(), event); return false; } else { // move to the last used cell in the row int col = 1; cell = sheet->cellStorage()->lastInRow(marker.y(), CellStorage::VisitContent); while (!cell.isNull() && cell.column() > q->selection()->marker().x() && cell.isEmpty()) { cell = sheet->cellStorage()->prevInRow(cell.column(), cell.row(), CellStorage::VisitContent); } col = (cell.isNull()) ? q->maxCol() : cell.column(); QPoint destination(col, marker.y()); if (destination == marker) return false; if (makingSelection) { q->selection()->update(destination); } else { q->selection()->initialize(destination, sheet); } q->scrollToCell(destination); event->accept(); // QKeyEvent } return true; } bool CellToolBase::Private::processPriorKey(QKeyEvent *event) { bool makingSelection = event->modifiers() & Qt::ShiftModifier; q->selection()->emitCloseEditor(true); // save changes QPoint marker = q->selection()->marker(); QPoint destination(marker.x(), qMax(1, marker.y() - 10)); if (destination == marker) return false; if (makingSelection) { q->selection()->update(destination); } else { q->selection()->initialize(destination, q->selection()->activeSheet()); } q->scrollToCell(destination); event->accept(); // QKeyEvent return true; } bool CellToolBase::Private::processNextKey(QKeyEvent *event) { bool makingSelection = event->modifiers() & Qt::ShiftModifier; q->selection()->emitCloseEditor(true); // save changes QPoint marker = q->selection()->marker(); QPoint destination(marker.x(), qMax(1, marker.y() + 10)); if (marker == destination) return false; if (makingSelection) { q->selection()->update(destination); } else { q->selection()->initialize(destination, q->selection()->activeSheet()); } q->scrollToCell(destination); event->accept(); // QKeyEvent return true; } void CellToolBase::Private::processOtherKey(QKeyEvent *event) { register Sheet * const sheet = q->selection()->activeSheet(); // No null character ... if (event->text().isEmpty() || !q->selection()->activeSheet()->map()->isReadWrite() || !sheet || sheet->isProtected()) { event->accept(); // QKeyEvent } else { if (!q->editor()) { // Switch to editing mode q->createEditor(); } // Send it to the embedded editor. QApplication::sendEvent(q->editor()->widget(), event); } } bool CellToolBase::Private::processControlArrowKey(QKeyEvent *event) { register Sheet * const sheet = q->selection()->activeSheet(); if (!sheet) return false; bool makingSelection = event->modifiers() & Qt::ShiftModifier; Cell cell; Cell lastCell; QPoint destination; bool searchThroughEmpty = true; int row; int col; QPoint marker = q->selection()->marker(); /* here, we want to move to the first or last cell in the given direction that is actually being used. Ignore empty cells and cells on hidden rows/columns */ switch (event->key()) { //Ctrl+Qt::Key_Up case Qt::Key_Up: cell = Cell(sheet, marker.x(), marker.y()); if ((!cell.isNull()) && (!cell.isEmpty()) && (marker.y() != 1)) { lastCell = cell; row = marker.y() - 1; cell = Cell(sheet, cell.column(), row); while ((!cell.isNull()) && (row > 0) && (!cell.isEmpty())) { if (!sheet->rowFormats()->isHiddenOrFiltered(cell.row())) { lastCell = cell; searchThroughEmpty = false; } row--; if (row > 0) cell = Cell(sheet, cell.column(), row); } cell = lastCell; } if (searchThroughEmpty) { cell = sheet->cellStorage()->prevInColumn(marker.x(), marker.y(), CellStorage::VisitContent); while ((!cell.isNull()) && (cell.isEmpty() || (sheet->rowFormats()->isHiddenOrFiltered(cell.row())))) { cell = sheet->cellStorage()->prevInColumn(cell.column(), cell.row(), CellStorage::VisitContent); } } if (cell.isNull()) row = 1; else row = cell.row(); int lastHiddenOrFiltered; while (sheet->rowFormats()->isHiddenOrFiltered(row, &lastHiddenOrFiltered)) { row = lastHiddenOrFiltered + 1; } destination.setX(qBound(1, marker.x(), q->maxCol())); destination.setY(qBound(1, row, q->maxRow())); break; //Ctrl+Qt::Key_Down case Qt::Key_Down: cell = Cell(sheet, marker.x(), marker.y()); if ((!cell.isNull()) && (!cell.isEmpty()) && (marker.y() != q->maxRow())) { lastCell = cell; row = marker.y() + 1; cell = Cell(sheet, cell.column(), row); while ((!cell.isNull()) && (row < q->maxRow()) && (!cell.isEmpty())) { if (!(sheet->rowFormats()->isHiddenOrFiltered(cell.row()))) { lastCell = cell; searchThroughEmpty = false; } row++; cell = Cell(sheet, cell.column(), row); } cell = lastCell; } if (searchThroughEmpty) { cell = sheet->cellStorage()->nextInColumn(marker.x(), marker.y(), CellStorage::VisitContent); while ((!cell.isNull()) && (cell.isEmpty() || (sheet->rowFormats()->isHiddenOrFiltered(cell.row())))) { cell = sheet->cellStorage()->nextInColumn(cell.column(), cell.row(), CellStorage::VisitContent); } } if (cell.isNull()) row = marker.y(); else row = cell.row(); int firstHiddenOrFiltered; while (row >= 1 && sheet->rowFormats()->isHiddenOrFiltered(row, 0, &firstHiddenOrFiltered)) { row = firstHiddenOrFiltered - 1; } destination.setX(qBound(1, marker.x(), q->maxCol())); destination.setY(qBound(1, row, q->maxRow())); break; //Ctrl+Qt::Key_Left case Qt::Key_Left: if (sheet->layoutDirection() == Qt::RightToLeft) { cell = Cell(sheet, marker.x(), marker.y()); if ((!cell.isNull()) && (!cell.isEmpty()) && (marker.x() != q->maxCol())) { lastCell = cell; col = marker.x() + 1; cell = Cell(sheet, col, cell.row()); while ((!cell.isNull()) && (col < q->maxCol()) && (!cell.isEmpty())) { if (!(sheet->columnFormat(cell.column())->isHiddenOrFiltered())) { lastCell = cell; searchThroughEmpty = false; } col++; cell = Cell(sheet, col, cell.row()); } cell = lastCell; } if (searchThroughEmpty) { cell = sheet->cellStorage()->nextInRow(marker.x(), marker.y(), CellStorage::VisitContent); while ((!cell.isNull()) && (cell.isEmpty() || (sheet->columnFormat(cell.column())->isHiddenOrFiltered()))) { cell = sheet->cellStorage()->nextInRow(cell.column(), cell.row(), CellStorage::VisitContent); } } if (cell.isNull()) col = marker.x(); else col = cell.column(); while (sheet->columnFormat(col)->isHiddenOrFiltered()) { col--; } destination.setX(qBound(1, col, q->maxCol())); destination.setY(qBound(1, marker.y(), q->maxRow())); } else { cell = Cell(sheet, marker.x(), marker.y()); if ((!cell.isNull()) && (!cell.isEmpty()) && (marker.x() != 1)) { lastCell = cell; col = marker.x() - 1; cell = Cell(sheet, col, cell.row()); while ((!cell.isNull()) && (col > 0) && (!cell.isEmpty())) { if (!(sheet->columnFormat(cell.column())->isHiddenOrFiltered())) { lastCell = cell; searchThroughEmpty = false; } col--; if (col > 0) cell = Cell(sheet, col, cell.row()); } cell = lastCell; } if (searchThroughEmpty) { cell = sheet->cellStorage()->prevInRow(marker.x(), marker.y(), CellStorage::VisitContent); while ((!cell.isNull()) && (cell.isEmpty() || (sheet->columnFormat(cell.column())->isHiddenOrFiltered()))) { cell = sheet->cellStorage()->prevInRow(cell.column(), cell.row(), CellStorage::VisitContent); } } if (cell.isNull()) col = 1; else col = cell.column(); while (sheet->columnFormat(col)->isHiddenOrFiltered()) { col++; } destination.setX(qBound(1, col, q->maxCol())); destination.setY(qBound(1, marker.y(), q->maxRow())); } break; //Ctrl+Qt::Key_Right case Qt::Key_Right: if (sheet->layoutDirection() == Qt::RightToLeft) { cell = Cell(sheet, marker.x(), marker.y()); if ((!cell.isNull()) && (!cell.isEmpty()) && (marker.x() != 1)) { lastCell = cell; col = marker.x() - 1; cell = Cell(sheet, col, cell.row()); while ((!cell.isNull()) && (col > 0) && (!cell.isEmpty())) { if (!(sheet->columnFormat(cell.column())->isHiddenOrFiltered())) { lastCell = cell; searchThroughEmpty = false; } col--; if (col > 0) cell = Cell(sheet, col, cell.row()); } cell = lastCell; } if (searchThroughEmpty) { cell = sheet->cellStorage()->prevInRow(marker.x(), marker.y(), CellStorage::VisitContent); while ((!cell.isNull()) && (cell.isEmpty() || (sheet->columnFormat(cell.column())->isHiddenOrFiltered()))) { cell = sheet->cellStorage()->prevInRow(cell.column(), cell.row(), CellStorage::VisitContent); } } if (cell.isNull()) col = 1; else col = cell.column(); while (sheet->columnFormat(col)->isHiddenOrFiltered()) { col++; } destination.setX(qBound(1, col, q->maxCol())); destination.setY(qBound(1, marker.y(), q->maxRow())); } else { cell = Cell(sheet, marker.x(), marker.y()); if ((!cell.isNull()) && (!cell.isEmpty()) && (marker.x() != q->maxCol())) { lastCell = cell; col = marker.x() + 1; cell = Cell(sheet, col, cell.row()); while ((!cell.isNull()) && (col < q->maxCol()) && (!cell.isEmpty())) { if (!(sheet->columnFormat(cell.column())->isHiddenOrFiltered())) { lastCell = cell; searchThroughEmpty = false; } col++; cell = Cell(sheet, col, cell.row()); } cell = lastCell; } if (searchThroughEmpty) { cell = sheet->cellStorage()->nextInRow(marker.x(), marker.y(), CellStorage::VisitContent); while ((!cell.isNull()) && (cell.isEmpty() || (sheet->columnFormat(cell.column())->isHiddenOrFiltered()))) { cell = sheet->cellStorage()->nextInRow(cell.column(), cell.row(), CellStorage::VisitContent); } } if (cell.isNull()) col = marker.x(); else col = cell.column(); while (sheet->columnFormat(col)->isHiddenOrFiltered()) { col--; } destination.setX(qBound(1, col, q->maxCol())); destination.setY(qBound(1, marker.y(), q->maxRow())); } break; } if (marker == destination) return false; if (makingSelection) { q->selection()->update(destination); } else { q->selection()->initialize(destination, sheet); } q->scrollToCell(destination); return true; } bool CellToolBase::Private::formatKeyPress(QKeyEvent * _ev) { if (!(_ev->modifiers() & Qt::ControlModifier)) return false; int key = _ev->key(); if (key != Qt::Key_Exclam && key != Qt::Key_At && key != Qt::Key_Ampersand && key != Qt::Key_Dollar && key != Qt::Key_Percent && key != Qt::Key_AsciiCircum && key != Qt::Key_NumberSign) return false; StyleCommand* command = new StyleCommand(); command->setSheet(q->selection()->activeSheet()); switch (_ev->key()) { case Qt::Key_Exclam: command->setText(kundo2_i18n("Number Format")); command->setFormatType(Format::Number); command->setPrecision(2); break; case Qt::Key_Dollar: command->setText(kundo2_i18n("Currency Format")); command->setFormatType(Format::Money); command->setPrecision(q->selection()->activeSheet()->map()->calculationSettings()->locale()->monetaryDecimalPlaces()); break; case Qt::Key_Percent: command->setText(kundo2_i18n("Percentage Format")); command->setFormatType(Format::Percentage); break; case Qt::Key_At: command->setText(kundo2_i18n("Time Format")); command->setFormatType(Format::SecondeTime); break; case Qt::Key_NumberSign: command->setText(kundo2_i18n("Date Format")); command->setFormatType(Format::ShortDate); break; case Qt::Key_AsciiCircum: command->setText(kundo2_i18n("Scientific Format")); command->setFormatType(Format::Scientific); break; case Qt::Key_Ampersand: command->setText(kundo2_i18n("Change Border")); command->setTopBorderPen(QPen(q->canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->setBottomBorderPen(QPen(q->canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->setLeftBorderPen(QPen(q->canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->setRightBorderPen(QPen(q->canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); break; default: delete command; return false; } command->add(*q->selection()); command->execute(); _ev->accept(); // QKeyEvent return true; } QRect CellToolBase::Private::moveDirection(Calligra::Sheets::MoveTo direction, bool extendSelection) { debugSheetsUI << "Canvas::moveDirection"; register Sheet * const sheet = q->selection()->activeSheet(); if (!sheet) return QRect(); QPoint destination; QPoint cursor = q->selection()->cursor(); QPoint cellCorner = cursor; Cell cell(sheet, cursor.x(), cursor.y()); /* cell is either the same as the marker, or the cell that is forced obscuring the marker cell */ if (cell.isPartOfMerged()) { cell = cell.masterCell(); cellCorner = QPoint(cell.column(), cell.row()); } /* how many cells must we move to get to the next cell? */ int offset = 0; const ColumnFormat *cl = 0; switch (direction) /* for each case, figure out how far away the next cell is and then keep going one row/col at a time after that until a visible row/col is found NEVER use cell.column() or cell.row() -- it might be a default cell */ { case Bottom: offset = cell.mergedYCells() - (cursor.y() - cellCorner.y()) + 1; while (((cursor.y() + offset) <= q->maxRow()) && sheet->rowFormats()->isHiddenOrFiltered(cursor.y() + offset)) { offset++; } destination = QPoint(cursor.x(), qMin(cursor.y() + offset, q->maxRow())); break; case Top: offset = (cellCorner.y() - cursor.y()) - 1; while (((cursor.y() + offset) >= 1) && sheet->rowFormats()->isHiddenOrFiltered(cursor.y() + offset)) { offset--; } destination = QPoint(cursor.x(), qMax(cursor.y() + offset, 1)); break; case Left: offset = (cellCorner.x() - cursor.x()) - 1; cl = sheet->columnFormat(cursor.x() + offset); while (((cursor.x() + offset) >= 1) && cl->isHiddenOrFiltered()) { offset--; cl = sheet->columnFormat(cursor.x() + offset); } destination = QPoint(qMax(cursor.x() + offset, 1), cursor.y()); break; case Right: offset = cell.mergedXCells() - (cursor.x() - cellCorner.x()) + 1; cl = sheet->columnFormat(cursor.x() + offset); while (((cursor.x() + offset) <= q->maxCol()) && cl->isHiddenOrFiltered()) { offset++; cl = sheet->columnFormat(cursor.x() + offset); } destination = QPoint(qMin(cursor.x() + offset, q->maxCol()), cursor.y()); break; case BottomFirst: offset = cell.mergedYCells() - (cursor.y() - cellCorner.y()) + 1; while (((cursor.y() + offset) <= q->maxRow()) && sheet->rowFormats()->isHiddenOrFiltered(cursor.y() + offset)) { ++offset; } destination = QPoint(1, qMin(cursor.y() + offset, q->maxRow())); break; case NoMovement: destination = cursor; break; } if (extendSelection) { q->selection()->update(destination); } else { q->selection()->initialize(destination, sheet); } q->scrollToCell(destination); updateEditor(Cell(q->selection()->activeSheet(), q->selection()->cursor())); return QRect(cursor, destination); } void CellToolBase::Private::paintSelection(QPainter &painter, const QRectF &viewRect) { if (q->selection()->referenceSelection() || q->editor()) { return; } Sheet *const sheet = q->selection()->activeSheet(); // save the painter state painter.save(); // disable antialiasing painter.setRenderHint(QPainter::Antialiasing, false); // Extend the clip rect by one in each direction to avoid artefacts caused by rounding errors. // TODO Stefan: This unites the region's rects. May be bad. Check! painter.setClipRegion(painter.clipRegion().boundingRect().adjusted(-1, -1, 1, 1)); QLineF line; QPen pen(QApplication::palette().text().color(), q->canvas()->viewConverter()->viewToDocumentX(2.0)); painter.setPen(pen); const Calligra::Sheets::Selection* selection = q->selection(); const QRect currentRange = selection->extendToMergedAreas(QRect(selection->anchor(), selection->marker())); Region::ConstIterator end(selection->constEnd()); for (Region::ConstIterator it(selection->constBegin()); it != end; ++it) { const QRect range = (*it)->isAll() ? (*it)->rect() : selection->extendToMergedAreas((*it)->rect()); // Only the active element (the one with the anchor) will be drawn with a border const bool current = (currentRange == range); double positions[4]; bool paintSides[4]; retrieveMarkerInfo(range, viewRect, positions, paintSides); double left = positions[0]; double top = positions[1]; double right = positions[2]; double bottom = positions[3]; if (sheet->layoutDirection() == Qt::RightToLeft) { // The painter's origin is translated by the negative canvas offset. // viewRect.left() is the canvas offset. Add it once to the // coordinates. Then, the upper left corner of the canvas has to // match the correct document position, which is the scrolling // offset (viewRect.left()) plus the width of the visible area // (viewRect.width()); that's the right border (left+width). const qreal offset = /*2 * viewRect.left() +*/ viewRect.width(); left = offset - positions[2]; right = offset - positions[0]; } bool paintLeft = paintSides[0]; bool paintTop = paintSides[1]; bool paintRight = paintSides[2]; bool paintBottom = paintSides[3]; if (sheet->layoutDirection() == Qt::RightToLeft) { paintLeft = paintSides[2]; paintRight = paintSides[0]; } const double unzoomedPixelX = q->canvas()->viewConverter()->viewToDocumentX(1.0); const double unzoomedPixelY = q->canvas()->viewConverter()->viewToDocumentY(1.0); // get the transparent selection color QColor selectionColor(QApplication::palette().highlight().color()); selectionColor.setAlpha(127); if (current) { // save old clip region const QRegion clipRegion = painter.clipRegion(); // clip out the cursor region const QRect cursor = QRect(selection->cursor(), selection->cursor()); const QRect extCursor = selection->extendToMergedAreas(cursor); QRectF cursorRect = sheet->cellCoordinatesToDocument(extCursor); if (sheet->layoutDirection() == Qt::RightToLeft) { // See comment above. const qreal offset = /*2 * viewRect.left() +*/ viewRect.width(); const qreal left = offset - cursorRect.right(); const qreal right = offset - cursorRect.left(); cursorRect.setLeft(left); cursorRect.setRight(right); } cursorRect.adjust(unzoomedPixelX, unzoomedPixelY, unzoomedPixelX, unzoomedPixelY); painter.setClipRegion(clipRegion.subtracted(cursorRect.toRect())); // draw the transparent selection background painter.fillRect(QRectF(left, top, right - left, bottom - top), selectionColor); // restore clip region painter.setClipRegion(clipRegion); } else { // draw the transparent selection background painter.fillRect(QRectF(left, top, right - left, bottom - top), selectionColor); } if (paintTop) { line = QLineF(left, top, right, top); painter.drawLine(line); } if (selection->activeSheet()->layoutDirection() == Qt::RightToLeft) { if (paintRight) { line = QLineF(right, top, right, bottom); painter.drawLine(line); } if (paintLeft && paintBottom && current) { /* then the 'handle' in the bottom left corner is visible. */ line = QLineF(left, top, left, bottom - 3 * unzoomedPixelY); painter.drawLine(line); line = QLineF(left + 4 * unzoomedPixelX, bottom, right + unzoomedPixelY, bottom); painter.drawLine(line); painter.fillRect(QRectF(left - 2 * unzoomedPixelX, bottom - 2 * unzoomedPixelY, 5 * unzoomedPixelX, 5 * unzoomedPixelY), painter.pen().color()); } else { if (paintLeft) { line = QLineF(left, top, left, bottom); painter.drawLine(line); } if (paintBottom) { line = QLineF(left, bottom, right, bottom); painter.drawLine(line); } } } else { // activeSheet()->layoutDirection() == Qt::LeftToRight if (paintLeft) { line = QLineF(left, top, left, bottom); painter.drawLine(line); } if (paintRight && paintBottom && current) { /* then the 'handle' in the bottom right corner is visible. */ line = QLineF(right, top, right, bottom - 3 * unzoomedPixelY); painter.drawLine(line); line = QLineF(left, bottom, right - 3 * unzoomedPixelX, bottom); painter.drawLine(line); painter.fillRect(QRectF(right - 2 * unzoomedPixelX, bottom - 2 * unzoomedPixelX, 5 * unzoomedPixelX, 5 * unzoomedPixelY), painter.pen().color()); } else { if (paintRight) { line = QLineF(right, top, right, bottom); painter.drawLine(line); } if (paintBottom) { line = QLineF(left, bottom, right, bottom); painter.drawLine(line); } } } } // restore painter state painter.restore(); } void CellToolBase::Private::paintReferenceSelection(QPainter &painter, const QRectF &viewRect) { Q_UNUSED(viewRect); if (!q->selection()->referenceSelection()) { return; } // save painter state painter.save(); // Define the reference selection handle. const qreal pixelX = q->canvas()->viewConverter()->viewToDocumentX(1); const qreal pixelY = q->canvas()->viewConverter()->viewToDocumentY(1); const QRectF handleArea(-3 * pixelX, -3 * pixelY, 6 * pixelX, 6 * pixelY); // A list of already found regions to color the same region with the same color. QSet alreadyFoundRegions; // The colors for the referenced ranges and the color index. const QList colors = q->selection()->colors(); int index = 0; // Iterate over the referenced ranges. const Region::ConstIterator end(q->selection()->constEnd()); for (Region::ConstIterator it(q->selection()->constBegin()); it != end; ++it) { Sheet *const sheet = (*it)->sheet(); // Only paint ranges or cells on the current sheet if (sheet != q->selection()->activeSheet()) { index++; continue; } // Only paint a reference once. if (alreadyFoundRegions.contains((*it)->name())) { continue; } alreadyFoundRegions.insert((*it)->name()); const QRect range = q->selection()->extendToMergedAreas((*it)->rect()); QRectF area = sheet->cellCoordinatesToDocument(range); // Convert region from sheet coordinates to canvas coordinates for use with the painter // retrieveMarkerInfo(region,viewRect,positions,paintSides); // Now adjust the highlight rectangle is slightly inside the cell borders (this means // that multiple highlighted cells look nicer together as the borders do not clash) area.adjust(pixelX, pixelY, -pixelX, -pixelY); // The current color. const QColor color = colors[index++ % colors.size()]; // Paint the reference range's outline. if ((*it)->sheet()->layoutDirection() == Qt::RightToLeft) { // See comment in paintSelection(). const qreal offset = /*2 * viewRect.left() +*/ viewRect.width(); const qreal left = offset - area.right(); const qreal right = offset - area.left(); area.setLeft(left); area.setRight(right); } painter.setBrush(QBrush()); - painter.setPen(color); + painter.setPen(QPen(color, 0)); painter.drawRect(area); // Now draw the size grip (the little rectangle on the bottom right-hand corner of // the range which the user can click and drag to resize the region) - painter.setPen(Qt::white); + painter.setPen(QPen(Qt::white, 0)); painter.setBrush(color); const bool rtl = sheet->layoutDirection() == Qt::RightToLeft; const QPointF corner(rtl ? area.bottomLeft() : area.bottomRight()); painter.drawRect(handleArea.translated(corner)); } // restore painter state painter.restore(); } void CellToolBase::Private::retrieveMarkerInfo(const QRect &cellRange, const QRectF &viewRect, double positions[], bool paintSides[]) { // Everything is in document coordinates here. // The layout direction, which is view dependent, is applied afterwards. const Sheet* sheet = q->selection()->activeSheet(); const QRectF visibleRect = sheet->cellCoordinatesToDocument(cellRange); /* these vars are used for clarity, the array for simpler function arguments */ qreal left = visibleRect.left(); qreal top = visibleRect.top(); qreal right = visibleRect.right(); qreal bottom = visibleRect.bottom(); /* left, top, right, bottom */ paintSides[0] = (viewRect.left() <= left) && (left <= viewRect.right()) && (bottom >= viewRect.top()) && (top <= viewRect.bottom()); paintSides[1] = (viewRect.top() <= top) && (top <= viewRect.bottom()) && (right >= viewRect.left()) && (left <= viewRect.right()); paintSides[2] = (viewRect.left() <= right) && (right <= viewRect.right()) && (bottom >= viewRect.top()) && (top <= viewRect.bottom()); paintSides[3] = (viewRect.top() <= bottom) && (bottom <= viewRect.bottom()) && (right >= viewRect.left()) && (left <= viewRect.right()); positions[0] = qMax(left, viewRect.left()); positions[1] = qMax(top, viewRect.top()); positions[2] = qMin(right, viewRect.right()); positions[3] = qMin(bottom, viewRect.bottom()); } QList CellToolBase::Private::popupActionList() const { QList actions; const Cell cell = Cell(q->selection()->activeSheet(), q->selection()->marker()); const bool isProtected = !q->selection()->activeSheet()->map()->isReadWrite() || (q->selection()->activeSheet()->isProtected() && !(cell.style().notProtected() && q->selection()->isSingular())); if (!isProtected) { actions.append(q->action("cellStyle")); actions.append(popupMenuActions["separator1"]); actions.append(q->action("cut")); } actions.append(q->action("copy")); if (!isProtected) { actions.append(q->action("paste")); actions.append(q->action("specialPaste")); actions.append(q->action("pasteWithInsertion")); actions.append(popupMenuActions["separator2"]); actions.append(q->action("clearAll")); actions.append(q->action("adjust")); actions.append(q->action("setDefaultStyle")); actions.append(q->action("setAreaName")); if (!q->selection()->isColumnOrRowSelected()) { actions.append(popupMenuActions["separator3"]); actions.append(popupMenuActions["insertCell"]); actions.append(popupMenuActions["deleteCell"]); } else if (q->selection()->isColumnSelected()) { actions.append(q->action("resizeCol")); actions.append(popupMenuActions["adjustColumn"]); actions.append(popupMenuActions["separator4"]); actions.append(popupMenuActions["insertColumn"]); actions.append(popupMenuActions["deleteColumn"]); actions.append(q->action("hideColumn")); q->action("showSelColumns")->setEnabled(false); const ColumnFormat* columnFormat; Region::ConstIterator endOfList = q->selection()->constEnd(); for (Region::ConstIterator it = q->selection()->constBegin(); it != endOfList; ++it) { QRect range = (*it)->rect(); int col; for (col = range.left(); col < range.right(); ++col) { columnFormat = q->selection()->activeSheet()->columnFormat(col); if (columnFormat->isHidden()) { q->action("showSelColumns")->setEnabled(true); actions.append(q->action("showSelColumns")); break; } } if (range.left() > 1 && col == range.right()) { bool allHidden = true; for (col = 1; col < range.left(); ++col) { columnFormat = q->selection()->activeSheet()->columnFormat(col); allHidden &= columnFormat->isHidden(); } if (allHidden) { q->action("showSelColumns")->setEnabled(true); actions.append(q->action("showSelColumns")); break; } } else { break; } } } else if (q->selection()->isRowSelected()) { actions.append(q->action("resizeRow")); actions.append(popupMenuActions["adjustRow"]); actions.append(popupMenuActions["separator5"]); actions.append(popupMenuActions["insertRow"]); actions.append(popupMenuActions["deleteRow"]); actions.append(q->action("hideRow")); q->action("showSelRows")->setEnabled(false); Region::ConstIterator endOfList = q->selection()->constEnd(); for (Region::ConstIterator it = q->selection()->constBegin(); it != endOfList; ++it) { QRect range = (*it)->rect(); int row; for (row = range.top(); row < range.bottom(); ++row) { if (q->selection()->activeSheet()->rowFormats()->isHidden(row)) { q->action("showSelRows")->setEnabled(true); actions.append(q->action("showSelRows")); break; } } if (range.top() > 1 && row == range.bottom()) { bool allHidden = true; for (row = 1; row < range.top(); ++row) { allHidden &= q->selection()->activeSheet()->rowFormats()->isHidden(row); } if (allHidden) { q->action("showSelRows")->setEnabled(true); actions.append(q->action("showSelRows")); break; } } else { break; } } } actions.append(popupMenuActions["separator6"]); actions.append(popupMenuActions["comment"]); if (!cell.comment().isEmpty()) { actions.append(popupMenuActions["clearComment"]); } if (testListChoose(q->selection())) { actions.append(popupMenuActions["separator7"]); actions.append(popupMenuActions["listChoose"]); } } return actions; } void CellToolBase::Private::createPopupMenuActions() { QAction* action = 0; for (int i = 1; i <= 7; ++i) { action = new QAction(q); action->setSeparator(true); popupMenuActions.insert(QString("separator%1").arg(i), action); } action = new QAction(koIcon("insertcell"), i18n("Insert Cells..."), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(insertCells())); popupMenuActions.insert("insertCell", action); action = new QAction(koIcon("removecell"), i18n("Delete Cells..."), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(deleteCells())); popupMenuActions.insert("deleteCell", action); action = new QAction(koIcon("adjustcol"), i18n("Adjust Column"), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(adjustColumn())); popupMenuActions.insert("adjustColumn", action); action = new QAction(koIcon("edit-table-insert-column-left"), i18n("Insert Columns"), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(insertColumn())); popupMenuActions.insert("insertColumn", action); action = new QAction(koIcon("edit-table-delete-column"), i18n("Delete Columns"), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(deleteColumn())); popupMenuActions.insert("deleteColumn", action); action = new QAction(koIcon("adjustrow"), i18n("Adjust Row"), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(adjustRow())); popupMenuActions.insert("adjustRow", action); action = new QAction(koIcon("edit-table-insert-row-above"), i18n("Insert Rows"), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(insertRow())); popupMenuActions.insert("insertRow", action); action = new QAction(koIcon("edit-table-delete-row"), i18n("Delete Rows"), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(deleteRow())); popupMenuActions.insert("deleteRow", action); action = new QAction(i18n("Selection List..."), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(listChoosePopupMenu())); popupMenuActions.insert("listChoose", action); action = new QAction(koIcon("edit-comment"), i18n("Comment"), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(comment())); popupMenuActions.insert("comment", action); action = new QAction(koIcon("delete-comment"),i18n("Clear Comment"), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(clearComment())); popupMenuActions.insert("clearComment", action); } bool CellToolBase::Private::testListChoose(Selection *selection) const { const Sheet *const sheet = selection->activeSheet(); const Cell cursorCell(sheet, selection->cursor()); const CellStorage *const storage = sheet->cellStorage(); const Region::ConstIterator end(selection->constEnd()); for (Region::ConstIterator it(selection->constBegin()); it != end; ++it) { const QRect range = (*it)->rect(); if (cursorCell.column() < range.left() || cursorCell.column() > range.right()) { continue; // next range } Cell cell; if (range.top() == 1) { cell = storage->firstInColumn(cursorCell.column(), CellStorage::Values); } else { cell = storage->nextInColumn(cursorCell.column(), range.top() - 1, CellStorage::Values); } while (!cell.isNull() && cell.row() <= range.bottom()) { if (cell.isDefault() || cell.isPartOfMerged() || cell.isFormula() || cell.isTime() || cell.isDate() || cell.value().isNumber() || cell.value().asString().isEmpty() || (cell == cursorCell)) { cell = storage->nextInColumn(cell.column(), cell.row(), CellStorage::Values); continue; } if (cell.userInput() != cursorCell.userInput()) { return true; } cell = storage->nextInColumn(cell.column(), cell.row(), CellStorage::Values); } } return false; } diff --git a/sheets/ui/CellView.cpp b/sheets/ui/CellView.cpp index 4a0aaf75a1b..a1bd3e7494f 100644 --- a/sheets/ui/CellView.cpp +++ b/sheets/ui/CellView.cpp @@ -1,2282 +1,2282 @@ /* This file is part of the KDE project Copyright 2010 Marijn Kruisselbrink Copyright 2006-2007 Stefan Nikolaus Copyright 2005 Raphael Langerhorst Copyright 2004-2005 Tomas Mecir Copyright 2004-2006 Inge Wallin Copyright 1999-2002,2004,2005 Laurent Montel Copyright 2002-2005 Ariya Hidayat Copyright 2001-2003 Philipp Mueller Copyright 2002-2003 Norbert Andres Copyright 2003 Reinhart Geiser Copyright 2003-2005 Meni Livne Copyright 2003 Peter Simonsson Copyright 1999-2002 David Faure Copyright 2000-2002 Werner Trobin Copyright 1999,2002 Harri Porten Copyright 2002 John Dailey Copyright 1998-2000 Torben Weis Copyright 2000 Bernd Wuebben Copyright 2000 Simon Hausmann Copyright 1999 Michael Reiher Copyright 1999 Boris Wedl Copyright 1998-1999 Reginald Stadlbauer 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; only version 2 of the License. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Local #include "CellView.h" // Qt #include #include #include #include #include #include #include #include #ifdef CALLIGRA_SHEETS_MT #include #include #include #endif // KF5 #include #include // Calligra #include #include // Sheets #include "ApplicationSettings.h" #include "CalculationSettings.h" #include "CellStorage.h" #include "Condition.h" #include "Map.h" #include "PrintSettings.h" #include "RowColumnFormat.h" #include "RowFormatStorage.h" #include "Selection.h" #include "Sheet.h" #include "SheetPrint.h" #include "SheetView.h" #include "StyleManager.h" #include "Value.h" #include "ValueFormatter.h" using namespace Calligra::Sheets; const int s_borderSpace = 1; class Q_DECL_HIDDEN CellView::Private : public QSharedData { public: Private(Style* defaultStyle, qreal defaultWidth, qreal defaultHeight) : style(*defaultStyle) , width(defaultWidth) , height(defaultHeight) , rtlOffset(0.0) , textX(0.0) , textY(0.0) , textWidth(0.0) , textHeight(0.0) , textLinesCount(0) , shrinkToFitFontSize(0.0) , hidden(false) , merged(false) , fittingHeight(true) , fittingWidth(true) , filterButton(false) , obscuredCellsX(0) , obscuredCellsY(0) , richText(0) #ifdef CALLIGRA_SHEETS_MT , mutex(new QMutex()) #endif {} ~Private() { } Style style; qreal width; qreal height; // difference in position between topleft corner of cell // and where painting should be started in the case of // merged and/or obscured cells in an rtl document qreal rtlOffset; // Position and dimension of displayed text. // Doc coordinate system; points; no zoom qreal textX; qreal textY; qreal textWidth; qreal textHeight; int textLinesCount; qreal shrinkToFitFontSize; bool hidden : 1; bool merged : 1; bool fittingHeight : 1; bool fittingWidth : 1; bool filterButton : 1; // NOTE Stefan: A cell is either obscured by an other one or obscures others itself. // But never both at the same time, so we can share the memory for this. int obscuredCellsX : 16; // KS_colMax int obscuredCellsY : 24; // KS_rowMax // This is the text we want to display. Not necessarily the same // as the user input, e.g. Cell::userInput()="1" and displayText="1.00". QString displayText; QSharedPointer richText; #ifdef CALLIGRA_SHEETS_MT QSharedPointer mutex; #endif public: void calculateCellBorders(const Cell&, SheetView* sheetView); void checkForFilterButton(const Cell&); void calculateTextSize(const QFont& font, const QFontMetricsF& fontMetrics); void calculateHorizontalTextSize(const QFont& font, const QFontMetricsF& fontMetrics); void calculateVerticalTextSize(const QFont& font, const QFontMetricsF& fontMetrics); void calculateAngledTextSize(const QFont& font, const QFontMetricsF& fontMetrics); void calculateRichTextSize(const QFont& font, const QFontMetricsF& fontMetrics); void truncateText(const QFont& font, const QFontMetricsF& fontMetrics); void truncateHorizontalText(const QFont& font, const QFontMetricsF& fontMetrics); void truncateVerticalText(const QFont& font, const QFontMetricsF& fontMetrics); void truncateAngledText(const QFont& font, const QFontMetricsF& fontMetrics); QFont calculateFont() const; QTextOption textOptions() const; }; QFont CellView::Private::calculateFont() const { QFont f = style.font(); if (shrinkToFitFontSize > 0.0) f.setPointSizeF(shrinkToFitFontSize); return f; } CellView::CellView(SheetView* sheetView) : d(new Private(sheetView->sheet()->map()->styleManager()->defaultStyle(), sheetView->sheet()->map()->defaultColumnFormat()->width(), sheetView->sheet()->map()->defaultRowFormat()->height())) { } CellView::CellView(SheetView* sheetView, int col, int row) : d(sheetView->defaultCellView().d) { detach(); Q_ASSERT(1 <= col && col <= KS_colMax); Q_ASSERT(1 <= row && row <= KS_rowMax); const Sheet* sheet = sheetView->sheet(); Cell cell(sheet, col, row); // create the effective style if (cell.isPartOfMerged()) { d->merged = true; Cell masterCell = cell.masterCell(); d->style = sheetView->cellView(masterCell.column(), masterCell.row()).style(); } else { // lookup the 'normal' style Style style = cell.style(); if (!style.isDefault()) d->style = style; // use conditional formatting attributes Conditions conditions = cell.conditions(); const Style conditionalStyle = conditions.testConditions(cell); if (!conditionalStyle.isEmpty()) { d->style.merge(conditionalStyle); } } if (cell.width() != sheetView->sheet()->map()->defaultColumnFormat()->width()) d->width = cell.width(); if (cell.height() != sheetView->sheet()->map()->defaultRowFormat()->height()) d->height = cell.height(); if (cell.sheet()->layoutDirection() == Qt::RightToLeft && cell.doesMergeCells()) { for (int i = 1; i <= cell.mergedXCells(); i++) { d->rtlOffset += cell.sheet()->columnFormat(cell.column() + i)->width(); } } if (sheet->columnFormat(col)->isHiddenOrFiltered() || sheet->rowFormats()->isHiddenOrFiltered(row) || (sheet->columnFormat(col)->width() <= sheetView->viewConverter()->viewToDocumentY(2)) || (sheet->rowFormats()->rowHeight(row) <= sheetView->viewConverter()->viewToDocumentY(2))) { d->hidden = true; d->height = 0.0; d->width = 0.0; return; // nothing more to do } d->checkForFilterButton(cell); // do not touch the other Private members, just return here. if (cell.isDefault()) return; Value value; // Display a formula if warranted. If not, simply display the value. if (cell.isFormula() && cell.sheet()->getShowFormula() && !(cell.sheet()->isProtected() && d->style.hideFormula())) { d->displayText = cell.userInput(); value.setFormat(Value::fmt_String); } else if (!cell.isEmpty()) { // Format the value appropriately and set the display text. // The format of the resulting value is used below to determine the alignment. d->displayText = cell.displayText(d->style, &value); QSharedPointer doc = cell.richText(); if (!doc.isNull()) d->richText = QSharedPointer(doc->clone()); } // Hide zero. if (sheet->getHideZero() && cell.value().isNumber() && cell.value().asFloat() == 0.0) d->displayText.clear(); // If text is empty, there's nothing more to do. if (d->displayText.isEmpty()) return; // horizontal align if (d->style.halign() == Style::HAlignUndefined) { // errors are always centered if (cell.value().type() == Value::Error) d->style.setHAlign(Style::Center); // if the format is text, align it according to the text direction else if (d->style.formatType() == Format::Text || value.format() == Value::fmt_String) d->style.setHAlign(d->displayText.isRightToLeft() ? Style::Right : Style::Left); // if the value is a boolean, center-align else if (cell.value().type() == Value::Boolean) d->style.setHAlign(Style::Center); // if the style does not define a specific format, align it according to the sheet layout else d->style.setHAlign(cell.sheet()->layoutDirection() == Qt::RightToLeft ? Style::Left : Style::Right); } // force left alignment, if there's a formula and it should be shown if (cell.isFormula() && sheet->getShowFormula() && !(sheet->isProtected() && d->style.hideFormula())) d->style.setHAlign(Style::Left); // figure out what border each side of the cell has d->calculateCellBorders(cell, sheetView); makeLayout(sheetView, cell); } CellView::CellView(const CellView& other) : d(other.d) { } CellView& CellView::operator=(const CellView& other) { d = other.d; return *this; } CellView::~CellView() { } void CellView::detach() { d.detach(); if (!d->richText.isNull()) { #ifdef CALLIGRA_SHEETS_MT QMutexLocker(d->mutex.data()); #endif d->richText = QSharedPointer(d->richText->clone()); } #ifdef CALLIGRA_SHEETS_MT d->mutex = QSharedPointer(new QMutex()); #endif } Style CellView::style() const { return d->style; } qreal CellView::textWidth() const { return d->textWidth; } qreal CellView::textHeight() const { return d->textHeight; } QRectF CellView::textRect() const { return QRectF(d->textX, d->textY, d->textWidth, d->textWidth); } QString CellView::testAnchor(SheetView* sheetView, const Cell& cell, qreal x, qreal y) const { if (sheetView->isObscured(cell.cellPosition())) { QPoint obscuringCell = sheetView->obscuringCell(cell.cellPosition()); Sheet* sheet = cell.sheet(); Cell otherCell = Cell(sheet, obscuringCell.x(), obscuringCell.y()); const CellView& otherView = sheetView->cellView(otherCell.column(), otherCell.row()); if (cell.column() != otherCell.column()) x += sheet->columnPosition(cell.column()) - sheet->columnPosition(otherCell.column()); if (cell.row() != otherCell.row()) y += sheet->rowPosition(cell.row()) - sheet->rowPosition(otherCell.row()); return otherView.testAnchor(sheetView, otherCell, x, y); } if (cell.link().isEmpty()) return QString(); if (x > d->textX) if (x < d->textX + d->textWidth) if (y > d->textY - d->textHeight) if (y < d->textY) return cell.link(); return QString(); } bool CellView::hitTestFilterButton(const Cell& cell, const QRect& cellRect, const QPoint& position) const { if (!d->filterButton) return false; QStyleOptionComboBox options; options.direction = cell.sheet()->layoutDirection(); options.editable = true; // options.fontMetrics = painter.fontMetrics(); options.frame = false; options.rect = cellRect; // options.subControls = QStyle::SC_ComboBoxEditField | QStyle::SC_ComboBoxArrow; return QApplication::style()->hitTestComplexControl(QStyle::CC_ComboBox, &options, position) == QStyle::SC_ComboBoxArrow; } // ================================================================ // Painting // Paint the cell. This is the main function that calls a lot of // helper functions. // // `paintRect' is the rectangle that we should paint on in document coordinates. // If the cell does not overlap this, we can return immediately. // `coordinate' is the origin (the upper left) of the cell in document // coordinates. // void CellView::paintCellContents(const QRectF& /*paintRect*/, QPainter& painter, const QRegion &clipRegion, const QPointF& coord, const Cell& cell, SheetView* sheetView) const { if (d->hidden) return; if (d->merged) return; if (sheetView->isObscured(cell.cellPosition())) return; // somehow sometimes the obscured cell info in SheetView and the one stored in the // actual cell views gets out of sync... for now this hack will do to fix that if (d->obscuredCellsX || d->obscuredCellsY) { sheetView->obscureCells(cell.cellPosition(), d->obscuredCellsX, d->obscuredCellsY); } // ---------------- Start the actual painting. ---------------- const QPointF coordinate(coord.x() - d->rtlOffset, coord.y()); // If the rect of this cell doesn't intersect the rect that should // be painted, we can skip the rest and return. (Note that we need // to calculate `left' first before we can do this.) const QRectF cellRect(coordinate, QSizeF(d->width, d->height)); // Does the cell intersect the clipped painting region? if (!clipRegion.intersects(cellRect.toRect())) return; // 0. Paint possible filter button if (d->filterButton && !dynamic_cast(painter.device())) paintFilterButton(painter, coordinate, cell, sheetView); // 1. Paint possible comment indicator. if (!dynamic_cast(painter.device()) || cell.sheet()->printSettings()->printCommentIndicator()) paintCommentIndicator(painter, coordinate, cell); // 2. Paint possible formula indicator. if (!dynamic_cast(painter.device()) || cell.sheet()->printSettings()->printFormulaIndicator()) { paintFormulaIndicator(painter, coordinate, cell); paintMatrixElementIndicator(painter, coordinate, cell); } // 3. Paint possible indicator for clipped text. paintMoreTextIndicator(painter, coordinate); // 5. Paint the text in the cell unless: // a) it is empty // b) something indicates that the text should not be painted // c) the sheet is protected and the cell is hidden. if (!d->displayText.isEmpty() && (!dynamic_cast(painter.device()) || style().printText()) && !(cell.sheet()->isProtected() && style().hideAll())) { paintText(painter, coordinate, cell); } } void CellView::Private::calculateCellBorders(const Cell& cell, SheetView* sheetView) { const int col = cell.column(); const int row = cell.row(); const Sheet* sheet = sheetView->sheet(); if (col != 1) { Style otherStyle = Cell(sheet, col - 1, row).style(); if (style.leftPenValue() < otherStyle.rightPenValue()) style.setLeftBorderPen(otherStyle.rightBorderPen()); } if (col != KS_colMax) { Style otherStyle = Cell(sheet, col + 1, row).style(); if (style.rightPenValue() < otherStyle.leftPenValue()) style.setRightBorderPen(otherStyle.leftBorderPen()); } if (row != 1) { Style otherStyle = Cell(sheet, col, row - 1).style(); if (style.topPenValue() < otherStyle.bottomPenValue()) style.setTopBorderPen(otherStyle.bottomBorderPen()); } if (row != KS_rowMax) { Style otherStyle = Cell(sheet, col, row + 1).style(); if (style.bottomPenValue() < otherStyle.topPenValue()) style.setBottomBorderPen(otherStyle.topBorderPen()); } } void CellView::paintCellBorders(const QRectF& paintRegion, QPainter& painter, const QRegion &clipRegion, const QPointF& coord, const QRect& cellRegion, const Cell& cell, SheetView* sheetView) const { const QPointF coordinate(coord.x() - d->rtlOffset, coord.y()); // If the rect of this cell doesn't intersect the rect that should // be painted, we can skip the rest and return. (Note that we need // to calculate `left' first before we can do this.) const QRectF cellRect(coordinate.x(), coordinate.y(), d->width, d->height); // Does the cell intersect the clipped painting region? if (!clipRegion.intersects(cellRect.toRect())) return; const int col = cell.column(); const int row = cell.row(); CellView::Borders paintBorder = CellView::NoBorder; // borders // NOTE Stefan: the borders of the adjacent cells are taken for the case, // that the cell is located on the edge of the cell range, // that is painted. // NOTE Sebsauer: this won't work for merged cells if (col == 1) paintBorder |= LeftBorder; else if (!(d->style.leftPenValue() < sheetView->cellView(col - 1, row).style().rightPenValue())) // if ( d->style.leftPenValue() >= sheetView->cellView( col - 1, row ).style().rightPenValue() ) paintBorder |= LeftBorder; if (col == KS_colMax) paintBorder |= CellView::RightBorder; else if (!(d->style.rightPenValue() < sheetView->cellView(col + 1, row).style().leftPenValue())) // if (d->style.rightPenValue() > sheetView->cellView(col + 1, row).style().leftPenValue()) paintBorder |= CellView::RightBorder; if (row == 1) paintBorder |= TopBorder; else if (!(d->style.topPenValue() < sheetView->cellView(col, row - 1).style().bottomPenValue())) // if ( d->style.topPenValue() >= sheetView->cellView( col, row - 1 ).style().bottomPenValue() ) paintBorder |= TopBorder; if (row == KS_rowMax) paintBorder |= BottomBorder; else if (!(d->style.bottomPenValue() < sheetView->cellView(col, row + 1).style().topPenValue())) // if (d->style.bottomPenValue() >= sheetView->cellView(col, row + 1).style().topPenValue()) paintBorder |= BottomBorder; // Paint border if outermost cell or if the pen is more "worth" // than the border pen of the cell on the other side of the // border or if the cell on the other side is not painted. In // the latter case get the pen that is of more "worth" if (col == cellRegion.right()) paintBorder |= CellView::RightBorder; if (row == cellRegion.bottom()) paintBorder |= CellView::BottomBorder; if (col == cellRegion.left()) paintBorder |= CellView::LeftBorder; if (row == cellRegion.top()) paintBorder |= CellView::TopBorder; // ---------------- Start the actual painting. ---------------- // 2. Paint the borders of the cell if no other cell is forcing this // one, i.e. this cell is not part of a merged cell. // // If we print pages, then we disable clipping, otherwise borders are // cut in the middle at the page borders. if (dynamic_cast(painter.device())) painter.setClipping(false); // Paint the borders if this cell is not part of another merged cell. if (!d->merged) { paintCustomBorders(painter, paintRegion, coordinate, paintBorder, sheetView->sheet()->layoutDirection() == Qt::RightToLeft); } // Turn clipping back on. if (dynamic_cast(painter.device())) painter.setClipping(true); // 3. Paint diagonal lines and page borders. paintCellDiagonalLines(painter, coordinate); paintPageBorders(painter, coordinate, paintBorder, cell); } // // Paint the background of this cell. // void CellView::paintCellBackground(QPainter& painter, const QRegion &clipRegion, const QPointF& coordinate) const { if (d->merged) return; const QRectF cellRect = QRectF(coordinate, QSizeF(d->width, d->height)).translated(-d->rtlOffset, 0); // Does the cell intersect the clipped painting region? if (!clipRegion.intersects(cellRect.toRect())) return; QBrush bgbrush = d->style.backgroundBrush(); if (d->style.backgroundColor().isValid() && d->style.backgroundColor() != QApplication::palette().base().color()) { // optimization to not draw the background-color if the background-brush would overwrite it. if (bgbrush.style() != Qt::SolidPattern || bgbrush.color().alphaF() < 1.) { // disable antialiasing painter.setRenderHint(QPainter::Antialiasing, false); // Simply fill the cell with its background color, painter.fillRect(cellRect, d->style.backgroundColor()); // restore antialiasing painter.setRenderHint(QPainter::Antialiasing, true); } } if (bgbrush.style() != Qt::NoBrush) { // Draw the background pattern. painter.fillRect(cellRect, bgbrush); } } // Paint the standard light grey borders that are always visible. // void CellView::paintDefaultBorders(QPainter& painter, const QRegion &clipRegion, const QRectF& paintRect, const QPointF &coord, Borders paintBorder, const QRect& cellRegion, const Cell& cell, SheetView* sheetView) const { const QPointF coordinate(coord.x() - d->rtlOffset, coord.y()); // Should the default borders be shown? if (!cell.sheet()->getShowGrid()) return; // Does the cell intersect the clipped painting region? if (!clipRegion.intersects(QRectF(coordinate, QSizeF(d->width, d->height)).toRect())) return; // Don't draw the border if a background fill-color was define. if (d->style.backgroundColor().isValid()) return; // disable antialiasing painter.setRenderHint(QPainter::Antialiasing, false); /* *** Notes about optimization *** This function was painting the top, left, right & bottom lines in almost all cells previously, contrary to what the comment below says should happen. There doesn't appear to be a UI option to enable or disable showing of the grid when printing at the moment, so I have disabled drawing of right and bottom borders for all cells. I also couldn't work out under what conditions the variables dt / db would come out as anything other than 0 in the code for painting the various borders. The cell.effTopBorderPen / cell.effBottomBorderPen calls were taking up a lot of time according some profiling I did. If that code really is necessary, we need to find a more efficient way of getting the widths than grabbing the whole QPen object and asking it. --Robert Knight (robertknight@gmail.com) */ const bool paintingToExternalDevice = dynamic_cast(painter.device()); const int col = cell.column(); const int row = cell.row(); paintBorder = CellView::NoBorder; // borders // Paint border if outermost cell or if the pen is more "worth" // than the border pen of the cell on the other side of the // border or if the cell on the other side is not painted. In // the latter case get the pen that is of more "worth" // Each cell is responsible for drawing it's top and left portions // of the "default" grid. --Or not drawing it if it shouldn't be // there. It's also responsible to paint the right and bottom, if // it is the last cell on a print out. // NOTE Stefan: the borders of the adjacent cells are taken for the case, // that the cell is located on the edge of the cell range, // that is painted. if (col == 1) paintBorder |= LeftBorder; else if (!(d->style.leftPenValue() < sheetView->cellView(col - 1, row).style().rightPenValue())) // if ( d->style.leftPenValue() >= sheetView->cellView( col - 1, row ).style().rightPenValue() ) paintBorder |= LeftBorder; if (col == KS_colMax) paintBorder |= CellView::RightBorder; else if (!(d->style.rightPenValue() < sheetView->cellView(col + cell.mergedXCells(), row).style().leftPenValue())) { if (d->style.rightPenValue() > sheetView->cellView(col + cell.mergedXCells(), row).style().leftPenValue()) paintBorder |= CellView::RightBorder; } if (row == 1) paintBorder |= TopBorder; else if (!(d->style.topPenValue() < sheetView->cellView(col, row - 1).style().bottomPenValue())) // if ( d->style.topPenValue() >= sheetView->cellView( col, row - 1 ).style().bottomPenValue() ) paintBorder |= TopBorder; if (row == KS_rowMax) paintBorder |= BottomBorder; else if (!(d->style.bottomPenValue() < sheetView->cellView(col, row + cell.mergedYCells()).style().topPenValue())) { if (d->style.bottomPenValue() >= sheetView->cellView(col, row + cell.mergedYCells()).style().topPenValue()) paintBorder |= BottomBorder; } // Check merging... if (d->merged) { // by default: none ... paintBorder = NoBorder; // left and top, only if it's the left or top of the merged cell if (cell.column() == cell.masterCell().column()) paintBorder |= LeftBorder; else if (cell.row() == cell.masterCell().row()) paintBorder |= TopBorder; // right and bottom only, if it's the outermost border of the cell region being painted // checked later below... } // Check obscuring... if (sheetView->isObscured(cell.cellPosition())) { // by default: none ... paintBorder = NoBorder; // left and top, only if it's the left or top of the obscuring cell const QPoint obscuringCell = sheetView->obscuringCell(cell.cellPosition()); if (cell.column() == obscuringCell.x()) paintBorder |= LeftBorder; else if (cell.row() == obscuringCell.y()) paintBorder |= TopBorder; // right and bottom only, if it's the outermost border of the cell region being painted // checked later below... } // Force painting, if it's the outermost border of the cell region being painted... if (col == cellRegion.right()) paintBorder |= CellView::RightBorder; if (row == cellRegion.bottom()) paintBorder |= CellView::BottomBorder; if (col == cellRegion.left()) paintBorder |= CellView::LeftBorder; if (row == cellRegion.top()) paintBorder |= CellView::TopBorder; // Check, if a custom border exists and the default border is not necessary... if (d->style.leftBorderPen().style() != Qt::NoPen) paintBorder &= ~LeftBorder; if (d->style.topBorderPen().style() != Qt::NoPen) paintBorder &= ~TopBorder; if (d->style.rightBorderPen().style() != Qt::NoPen) paintBorder &= ~RightBorder; if (d->style.bottomBorderPen().style() != Qt::NoPen) paintBorder &= ~BottomBorder; // Check if the neighbor-cells do have a background fill-color in which case the border is not drawn. if(col > 1 && sheetView->cellView(col - 1, row).style().backgroundColor().isValid()) paintBorder &= ~LeftBorder; if(col < KS_colMax && sheetView->cellView(col + 1, row).style().backgroundColor().isValid()) paintBorder &= ~RightBorder; if(row > 1 && sheetView->cellView(col, row - 1).style().backgroundColor().isValid()) paintBorder &= ~TopBorder; if(row < KS_rowMax && sheetView->cellView(col, row + 1).style().backgroundColor().isValid()) paintBorder &= ~BottomBorder; // Check if we're in right-to-left mode, and if so swap left and right border bits if (cell.sheet()->layoutDirection() == Qt::RightToLeft) { Borders lrBorder = paintBorder & (LeftBorder | RightBorder); paintBorder &= ~(LeftBorder | RightBorder); if (lrBorder & LeftBorder) { paintBorder |= RightBorder; } if (lrBorder & RightBorder) { paintBorder |= LeftBorder; } } // Set the single-pixel width pen for drawing the borders with. // NOTE Stefan: Use a cosmetic pen (width = 0), because we want the grid always one pixel wide painter.setPen(QPen(cell.sheet()->map()->settings()->gridColor(), 0, Qt::SolidLine)); QLineF line; // The left border. if (paintBorder & LeftBorder) { int dt = 0; int db = 0; #if 0 // CALLIGRA_SHEETS_WIP_STYLE_BORDER if (cellRef.x() > 1) { Cell *cell_west = Cell(cell.sheet(), cellRef.x() - 1, cellRef.y()); QPen t = cell_west->effTopBorderPen(cellRef.x() - 1, cellRef.y()); QPen b = cell_west->effBottomBorderPen(cellRef.x() - 1, cellRef.y()); if (t.style() != Qt::NoPen) dt = (t.width() + 1) / 2; if (b.style() != Qt::NoPen) db = (t.width() / 2); } #endif // If we are on paper printout, we limit the length of the lines. // On paper, we always have full cells, on screen not. if (paintingToExternalDevice) { line = QLineF(qMax(paintRect.left(), coordinate.x()), qMax(paintRect.top(), coordinate.y() + dt), qMin(paintRect.right(), coordinate.x()), qMin(paintRect.bottom(), coordinate.y() + d->height - db)); } else { line = QLineF(coordinate.x(), coordinate.y() + dt, coordinate.x(), coordinate.y() + d->height - db); } painter.drawLine(line); } // The top border. if (paintBorder & TopBorder) { int dl = 0; int dr = 0; #if 0 // CALLIGRA_SHEETS_WIP_STYLE_BORDER if (cellRef.y() > 1) { Cell *cell_north = Cell(cell.sheet(), cellRef.x(), cellRef.y() - 1); QPen l = cell_north->effLeftBorderPen(cellRef.x(), cellRef.y() - 1); QPen r = cell_north->effRightBorderPen(cellRef.x(), cellRef.y() - 1); if (l.style() != Qt::NoPen) dl = (l.width() - 1) / 2 + 1; if (r.style() != Qt::NoPen) dr = r.width() / 2; } #endif // If we are on paper printout, we limit the length of the lines. // On paper, we always have full cells, on screen not. if (paintingToExternalDevice) { line = QLineF(qMax(paintRect.left(), coordinate.x() + dl), qMax(paintRect.top(), coordinate.y()), qMin(paintRect.right(), coordinate.x() + d->width - dr), qMin(paintRect.bottom(), coordinate.y())); } else { line = QLineF(coordinate.x() + dl, coordinate.y(), coordinate.x() + d->width - dr, coordinate.y()); } painter.drawLine(line); } // The right border. if (paintBorder & RightBorder) { int dt = 0; int db = 0; #if 0 // CALLIGRA_SHEETS_WIP_STYLE_BORDER if (cellRef.x() < KS_colMax) { Cell *cell_east = Cell(cell.sheet(), cellRef.x() + 1, cellRef.y()); QPen t = cell_east->effTopBorderPen(cellRef.x() + 1, cellRef.y()); QPen b = cell_east->effBottomBorderPen(cellRef.x() + 1, cellRef.y()); if (t.style() != Qt::NoPen) dt = (t.width() + 1) / 2; if (b.style() != Qt::NoPen) db = (t.width() / 2); } #endif //painter.setPen( QPen( cell.sheet()->map()->settings()->gridColor(), 1, Qt::SolidLine ) ); // If we are on paper printout, we limit the length of the lines. // On paper, we always have full cells, on screen not. if (dynamic_cast(painter.device())) { line = QLineF(qMax(paintRect.left(), coordinate.x() + d->width), qMax(paintRect.top(), coordinate.y() + dt), qMin(paintRect.right(), coordinate.x() + d->width), qMin(paintRect.bottom(), coordinate.y() + d->height - db)); } else { line = QLineF(coordinate.x() + d->width, coordinate.y() + dt, coordinate.x() + d->width, coordinate.y() + d->height - db); } painter.drawLine(line); } // The bottom border. if (paintBorder & BottomBorder) { int dl = 0; int dr = 0; #if 0 // CALLIGRA_SHEETS_WIP_STYLE_BORDER if (cellRef.y() < KS_rowMax) { Cell *cell_south = Cell(cell.sheet(), cellRef.x(), cellRef.y() + 1); QPen l = cell_south->effLeftBorderPen(cellRef.x(), cellRef.y() + 1); QPen r = cell_south->effRightBorderPen(cellRef.x(), cellRef.y() + 1); if (l.style() != Qt::NoPen) dl = (l.width() - 1) / 2 + 1; if (r.style() != Qt::NoPen) dr = r.width() / 2; } #endif // If we are on paper printout, we limit the length of the lines. // On paper, we always have full cells, on screen not. if (dynamic_cast(painter.device())) { line = QLineF(qMax(paintRect.left(), coordinate.x() + dl), qMax(paintRect.top(), coordinate.y() + d->height), qMin(paintRect.right(), coordinate.x() + d->width - dr), qMin(paintRect.bottom(), coordinate.y() + d->height)); } else { line = QLineF(coordinate.x() + dl, coordinate.y() + d->height, coordinate.x() + d->width - dr, coordinate.y() + d->height); } painter.drawLine(line); } // restore antialiasing painter.setRenderHint(QPainter::Antialiasing, true); } // Paint a comment indicator if the cell has a comment. // void CellView::paintCommentIndicator(QPainter& painter, const QPointF& coordinate, const Cell& cell) const { // Point the little corner if there is a comment attached // to this cell. if ((!cell.comment().isEmpty()) && d->width > 10.0 && d->height > 10.0 && (cell.sheet()->printSettings()->printCommentIndicator() || (!dynamic_cast(painter.device()) && cell.sheet()->getShowCommentIndicator()))) { QColor penColor = Qt::red; // If background has high red part, switch to blue. if (qRed(d->style.backgroundColor().rgb()) > 127 && qGreen(d->style.backgroundColor().rgb()) < 80 && qBlue(d->style.backgroundColor().rgb()) < 80) { penColor = Qt::blue; } // Get the triangle. QPolygonF polygon(3); polygon.clear(); if (cell.sheet()->layoutDirection() == Qt::RightToLeft) { polygon << QPointF(coordinate.x() + 6.0, coordinate.y()); polygon << QPointF(coordinate.x(), coordinate.y()); polygon << QPointF(coordinate.x(), coordinate.y() + 6.0); } else { polygon << QPointF(coordinate.x() + cell.width() - 5.0, coordinate.y()); polygon << QPointF(coordinate.x() + cell.width(), coordinate.y()); polygon << QPointF(coordinate.x() + cell.width(), coordinate.y() + 5.0); } // And draw it. painter.setBrush(QBrush(penColor)); painter.setPen(Qt::NoPen); painter.drawPolygon(polygon); } } // Paint a small rectangle if this cell holds a formula. // void CellView::paintFormulaIndicator(QPainter& painter, const QPointF& coordinate, const Cell& cell) const { if (cell.isFormula() && cell.sheet()->getShowFormulaIndicator() && d->width > 10.0 && d->height > 10.0) { QColor penColor = Qt::blue; // If background has high blue part, switch to red. if (qRed(d->style.backgroundColor().rgb()) < 80 && qGreen(d->style.backgroundColor().rgb()) < 80 && qBlue(d->style.backgroundColor().rgb()) > 127) { penColor = Qt::red; } // Get the triangle... QPolygonF polygon(3); polygon.clear(); if (cell.sheet()->layoutDirection() == Qt::RightToLeft) { polygon << QPointF(coordinate.x() + d->width - 6.0, coordinate.y() + d->height); polygon << QPointF(coordinate.x() + d->width, coordinate.y() + d->height); polygon << QPointF(coordinate.x() + d->width, coordinate.y() + d->height - 6.0); } else { polygon << QPointF(coordinate.x(), coordinate.y() + d->height - 6.0); polygon << QPointF(coordinate.x(), coordinate.y() + d->height); polygon << QPointF(coordinate.x() + 6.0, coordinate.y() + d->height); } // ...and draw it. painter.setBrush(QBrush(penColor)); painter.setPen(Qt::NoPen); painter.drawPolygon(polygon); } } // Paint a small rectangle if this cell is an element of a matrix. // void CellView::paintMatrixElementIndicator(QPainter& painter, const QPointF& coordinate, const Cell& cell) const { if (cell.isLocked() && cell.sheet()->getShowFormulaIndicator() && d->width > 10.0 && d->height > 10.0) { QColor penColor = Qt::blue; // If background has high blue part, switch to red. if (qRed(d->style.backgroundColor().rgb()) < 80 && qGreen(d->style.backgroundColor().rgb()) < 80 && qBlue(d->style.backgroundColor().rgb()) > 127) { penColor = Qt::red; } // Get the triangle... QPolygonF polygon(3); polygon.clear(); if (cell.sheet()->layoutDirection() == Qt::RightToLeft) { polygon << QPointF(coordinate.x() + d->width - 6.0, coordinate.y()); polygon << QPointF(coordinate.x() + d->width, coordinate.y()); polygon << QPointF(coordinate.x() + d->width, coordinate.y() + 6.0); } else { polygon << QPointF(coordinate.x(), coordinate.y() + 6.0); polygon << QPointF(coordinate.x(), coordinate.y()); polygon << QPointF(coordinate.x() + 6.0, coordinate.y()); } // ...and draw it. painter.setBrush(QBrush(penColor)); painter.setPen(Qt::NoPen); painter.drawPolygon(polygon); } } // Paint an indicator that the text in the cell is cut. // void CellView::paintMoreTextIndicator(QPainter& painter, const QPointF& coordinate) const { if (d->style.shrinkToFit()) return; // Show a red triangle when it's not possible to write all text in cell. // Don't print the red triangle if we're printing. if (!d->fittingWidth && !dynamic_cast(painter.device()) && d->height > 4.0 && d->width > 4.0) { QColor penColor = Qt::red; // If background has high red part, switch to blue. if (qRed(d->style.backgroundColor().rgb()) > 127 && qGreen(d->style.backgroundColor().rgb()) < 80 && qBlue(d->style.backgroundColor().rgb()) < 80) { penColor = Qt::blue; } // Get the triangle... QPolygonF polygon(3); polygon.clear(); if (d->displayText.isRightToLeft()) { polygon << QPointF(coordinate.x() + 4.0, coordinate.y() + d->height / 2.0 - 4.0); polygon << QPointF(coordinate.x(), coordinate.y() + d->height / 2.0); polygon << QPointF(coordinate.x() + 4.0, coordinate.y() + d->height / 2.0 + 4.0); } else { polygon << QPointF(coordinate.x() + d->width - 4.0, coordinate.y() + d->height / 2.0 - 4.0); polygon << QPointF(coordinate.x() + d->width, coordinate.y() + d->height / 2.0); polygon << QPointF(coordinate.x() + d->width - 4.0, coordinate.y() + d->height / 2.0 + 4.0); } // ...and paint it. painter.setBrush(QBrush(penColor)); painter.setPen(Qt::NoPen); painter.drawPolygon(polygon); } } static int fixAngle(int angle) { angle = ((angle % 360) + 360) % 360; // now angle is between 0 and 359, but 181-359 should be -179 - -1 if (angle > 180) angle = angle - 360; return angle; } // Paint the real contents of a cell - the text. // void CellView::paintText(QPainter& painter, const QPointF& coordinate, const Cell& cell) const { QColor textColorPrint = d->style.fontColor(); // Resolve the text color if invalid (=default). if (!textColorPrint.isValid()) { if (dynamic_cast(painter.device())) textColorPrint = Qt::black; else textColorPrint = QApplication::palette().text().color(); QColor bgColor = d->style.backgroundColor(); if (bgColor.isValid()) { qreal contrast = KColorUtils::contrastRatio(bgColor, textColorPrint); if (contrast < 3) textColorPrint = QColor(255 - textColorPrint.red(), 255 - textColorPrint.green(), 255 - textColorPrint.blue()); } } - QPen tmpPen(textColorPrint); + QPen tmpPen(textColorPrint, 0); QFont font = d->calculateFont(); // Check for red font color for negative values. if (cell.value().isNumber() && !(cell.sheet()->getShowFormula() && !(cell.sheet()->isProtected() && style().hideFormula()))) { if (style().floatColor() == Style::NegRed && cell.value().asFloat() < 0.0) tmpPen.setColor(Qt::red); } // Check for blue color, for hyperlink. if (!cell.link().isEmpty()) { tmpPen.setColor(QApplication::palette().link().color()); font.setUnderline(true); } painter.setPen(tmpPen); qreal indent = 0.0; qreal offsetCellTooShort = 0.0; const Style::HAlign hAlign = d->style.halign(); const Style::VAlign vAlign = d->style.valign(); // Apply indent if text is align to left not when text is at right or middle. if (hAlign == Style::Left && !cell.isEmpty()) { indent = d->style.indentation(); } // Made an offset, otherwise ### is under red triangle. if (hAlign == Style::Right && !cell.isEmpty() && !d->fittingWidth) offsetCellTooShort = 4; KoPostscriptPaintDevice device; const QFontMetricsF fontMetrics(font, &device); qreal fontOffset = 0.0; if (style().valign() == Style::Bottom || style().valign() == Style::VAlignUndefined) { // The descent can be bigger then the underlinePos which seems to be the case at least // with thai characters. So, to be sure we are not losing the bottom characters we are // using either the underlinePos() (plus 1 for the underline itself) or the descent() // whatever is bigger to be sure we do not but of anything, neither parts of the font // nor the an optional displayed underline. // According to the docs this is still not perfect cause some unusual character in // an exotic language can still be bigger but we ignore that here. fontOffset = qMax(fontMetrics.underlinePos() + 1, fontMetrics.descent()); } const int tmpAngle = fixAngle(d->style.angle()); const bool tmpVerticalText = d->style.verticalText(); // force multiple rows on explicitly set line breaks const bool tmpMultiRow = d->style.wrapText() || d->displayText.contains('\n'); const bool tmpVDistributed = vAlign == Style::VJustified || vAlign == Style::VDistributed; const bool tmpRichText = !d->richText.isNull(); // set a clipping region for non-rotated text painter.save(); if (tmpAngle == 0) { painter.setClipRect(QRectF(coordinate.x(), coordinate.y(), d->width, d->height), Qt::IntersectClip); } // Actually paint the text. // There are 5 possible cases: // - One line of plain text , horizontal // - Angled text // - Multiple rows of plain text , horizontal // - Vertical text // - Rich text if (!tmpMultiRow && !tmpVerticalText && !tmpAngle && !tmpRichText) { // Case 1: The simple case, one line, no angle. const QPointF position(indent + coordinate.x() - offsetCellTooShort, coordinate.y() + d->textY - fontOffset); drawText(painter, position, d->displayText.split('\n'), cell); } else if (tmpAngle != 0) { // Case 2: an angle. painter.rotate(tmpAngle); qreal x; if (tmpAngle > 0) x = indent + d->textX + coordinate.x(); else x = indent + d->textX + coordinate.x() - (fontMetrics.descent() + fontMetrics.ascent()) * ::sin(tmpAngle * M_PI / 180); qreal y; if (tmpAngle > 0) y = coordinate.y() + d->textY; else y = coordinate.y() + d->textY + d->textHeight; if (tmpAngle < -90 || tmpAngle > 90) { x += d->textWidth; } const QPointF position(x * ::cos(tmpAngle * M_PI / 180) + y * ::sin(tmpAngle * M_PI / 180), -x * ::sin(tmpAngle * M_PI / 180) + y * ::cos(tmpAngle * M_PI / 180)); drawText(painter, position, d->displayText.split('\n'), cell); painter.rotate(-tmpAngle); } else if (tmpMultiRow && !tmpVerticalText && !tmpRichText) { // Case 3: Multiple rows, but horizontal. const QPointF position(indent + coordinate.x(), coordinate.y() + d->textY); const qreal space = d->height - d->textHeight; const qreal lineSpacing = tmpVDistributed && space > 0 ? space / (d->textLinesCount - 1) : 0; drawText(painter, position, d->displayText.split('\n'), cell, lineSpacing); } else if (tmpVerticalText && !d->displayText.isEmpty()) { // Case 4: Vertical text. QStringList textLines = d->displayText.split('\n'); qreal dx = 0.0; qreal space = d->width - d->textWidth; if (space > 0) { switch (hAlign) { case Style::Center: case Style::HAlignUndefined: dx += space / 2; break; case Style::Right: dx += space; break; default: break; } } for (int i = 0; i < textLines.count(); ++i) { QStringList textColumn; for (int j = 0; j < textLines[i].count(); ++j) textColumn << QString(textLines[i][j]); const QPointF position(indent + coordinate.x() + dx, coordinate.y() + d->textY); drawText(painter, position, textColumn, cell); dx += fontMetrics.maxWidth(); } } else if (tmpRichText) { // Case 5: Rich text. #ifdef CALLIGRA_SHEETS_MT QMutexLocker(d->mutex.data()); #endif QTextDocument* doc = d->richText->clone(); doc->setDefaultTextOption(d->textOptions()); doc->setUseDesignMetrics(true); const QPointF position(coordinate.x() + indent, coordinate.y() + d->textY - d->textHeight); painter.translate(position); QAbstractTextDocumentLayout::PaintContext ctx; ctx.palette.setColor(QPalette::Text, textColorPrint); doc->documentLayout()->draw(&painter, ctx); delete doc; // painter.drawRect(QRectF(QPointF(0, 0), QSizeF(d->textWidth, d->textHeight))); } painter.restore(); } // Paint page borders on the page. Only do this on the screen. // void CellView::paintPageBorders(QPainter& painter, const QPointF& coordinate, Borders paintBorder, const Cell& cell) const { // Not screen? Return immediately. if (dynamic_cast(painter.device())) return; if (! cell.sheet()->isShowPageOutline()) return; SheetPrint* const print = cell.sheet()->print(); const PrintSettings *const settings = cell.sheet()->printSettings(); const QRect printRange = settings->printRegion().lastRange(); // Draw page borders QLineF line; if (cell.column() >= printRange.left() && cell.column() <= printRange.right() + 1 && cell.row() >= printRange.top() && cell.row() <= printRange.bottom() + 1) { if (print->isColumnOnNewPage(cell.column()) && cell.row() <= printRange.bottom()) { - painter.setPen(cell.sheet()->map()->settings()->pageOutlineColor()); + painter.setPen(QPen(cell.sheet()->map()->settings()->pageOutlineColor(), 0)); if (cell.sheet()->layoutDirection() == Qt::RightToLeft) line = QLineF(coordinate.x() + d->width, coordinate.y(), coordinate.x() + d->width, coordinate.y() + d->height); else line = QLineF(coordinate.x(), coordinate.y(), coordinate.x(), coordinate.y() + d->height); painter.drawLine(line); } if (print->isRowOnNewPage(cell.row()) && (cell.column() <= printRange.right())) { - painter.setPen(cell.sheet()->map()->settings()->pageOutlineColor()); + painter.setPen(QPen(cell.sheet()->map()->settings()->pageOutlineColor(), 0)); line = QLineF(coordinate.x(), coordinate.y(), coordinate.x() + d->width, coordinate.y()); painter.drawLine(line); } if (paintBorder & RightBorder) { if (print->isColumnOnNewPage(cell.column() + 1) && cell.row() <= printRange.bottom()) { - painter.setPen(cell.sheet()->map()->settings()->pageOutlineColor()); + painter.setPen(QPen(cell.sheet()->map()->settings()->pageOutlineColor(), 0)); if (cell.sheet()->layoutDirection() == Qt::RightToLeft) line = QLineF(coordinate.x(), coordinate.y(), coordinate.x(), coordinate.y() + d->height); else line = QLineF(coordinate.x() + d->width, coordinate.y(), coordinate.x() + d->width, coordinate.y() + d->height); painter.drawLine(line); } } if (paintBorder & BottomBorder) { if (print->isRowOnNewPage(cell.row() + 1) && cell.column() <= printRange.right()) { - painter.setPen(cell.sheet()->map()->settings()->pageOutlineColor()); + painter.setPen(QPen(cell.sheet()->map()->settings()->pageOutlineColor(), 0)); line = QLineF(coordinate.x(), coordinate.y() + d->height, coordinate.x() + d->width, coordinate.y() + d->height); painter.drawLine(line); } } } } // Paint the cell borders. // void CellView::paintCustomBorders(QPainter& painter, const QRectF& paintRect, const QPointF& coordinate, Borders paintBorder, bool rtl) const { //Sanity check: If we are not painting any of the borders then the function //really shouldn't be called at all. if (paintBorder == NoBorder) return; // Must create copies of these since otherwise the zoomIt() // operation will be performed on them repeatedly. QPen leftPen(d->style.leftBorderPen()); QPen rightPen(d->style.rightBorderPen()); QPen topPen(d->style.topBorderPen()); QPen bottomPen(d->style.bottomBorderPen()); // if in right-to-left mode, swap left&right pens and bits if (rtl) { qSwap(leftPen, rightPen); Borders lrBorder = paintBorder & (LeftBorder | RightBorder); paintBorder &= ~(LeftBorder | RightBorder); if (lrBorder & LeftBorder) { paintBorder |= RightBorder; } if (lrBorder & RightBorder) { paintBorder |= LeftBorder; } } // Determine the pens that should be used for drawing // the borders. // NOTE Stefan: This prevents cosmetic pens (width==0). int left_penWidth = qMax(1, (leftPen.width())); int right_penWidth = qMax(1, (rightPen.width())); int top_penWidth = qMax(1, (topPen.width())); int bottom_penWidth = qMax(1, (bottomPen.width())); leftPen.setWidth(left_penWidth); rightPen.setWidth(right_penWidth); topPen.setWidth(top_penWidth); bottomPen.setWidth(bottom_penWidth); QLineF line; if ((paintBorder & LeftBorder) && leftPen.style() != Qt::NoPen) { painter.setPen(leftPen); //debugSheetsRender <<" painting left border of cell" << name(); // If we are on paper printout, we limit the length of the lines. // On paper, we always have full cells, on screen not. if (dynamic_cast(painter.device())) { if (coordinate.x() >= paintRect.left() + left_penWidth / 2) line = QLineF(coordinate.x() , qMax(paintRect.top(), coordinate.y()), coordinate.x(), qMin(paintRect.bottom(), coordinate.y() + d->height)); } else { line = QLineF(coordinate.x(), coordinate.y(), coordinate.x(), coordinate.y() + d->height); } painter.drawLine(line); } if ((paintBorder & RightBorder) && rightPen.style() != Qt::NoPen) { painter.setPen(rightPen); //debugSheetsRender <<" painting right border of cell" << name(); // If we are on paper printout, we limit the length of the lines. // On paper, we always have full cells, on screen not. if (dynamic_cast(painter.device())) { // Only print the right border if it is visible. if (coordinate.x() + d->width <= paintRect.right() + right_penWidth / 2) line = QLineF(coordinate.x() + d->width, qMax(paintRect.top(), coordinate.y()), coordinate.x() + d->width, qMin(paintRect.bottom(), coordinate.y() + d->height)); } else { line = QLineF(coordinate.x() + d->width, coordinate.y(), coordinate.x() + d->width, coordinate.y() + d->height); } painter.drawLine(line); } if ((paintBorder & TopBorder) && topPen.style() != Qt::NoPen) { painter.setPen(topPen); //debugSheetsRender <<" painting top border of cell" << name() // << " [" << coordinate.x() << "," << coordinate.x() + d->width // << ": " << coordinate.x() + d->width - coordinate.x() << "]" << endl; // If we are on paper printout, we limit the length of the lines. // On paper, we always have full cells, on screen not. if (dynamic_cast(painter.device())) { if (coordinate.y() >= paintRect.top() + top_penWidth / 2) line = QLineF(qMax(paintRect.left(), coordinate.x()), coordinate.y(), qMin(paintRect.right(), coordinate.x() + d->width), coordinate.y()); } else { line = QLineF(coordinate.x(), coordinate.y(), coordinate.x() + d->width, coordinate.y()); } painter.drawLine(line); } if ((paintBorder & BottomBorder) && bottomPen.style() != Qt::NoPen) { painter.setPen(bottomPen); //debugSheetsRender <<" painting bottom border of cell" << name() // << " [" << coordinate.x() << "," << coordinate.x() + d->width // << ": " << coordinate.x() + d->width - coordinate.x() << "]" << endl; // If we are on paper printout, we limit the length of the lines. // On paper, we always have full cells, on screen not. if (dynamic_cast(painter.device())) { if (coordinate.y() + d->height <= paintRect.bottom() + bottom_penWidth / 2) line = QLineF(qMax(paintRect.left(), coordinate.x()), coordinate.y() + d->height, qMin(paintRect.right(), coordinate.x() + d->width), coordinate.y() + d->height); } else { line = QLineF(coordinate.x(), coordinate.y() + d->height, coordinate.x() + d->width, coordinate.y() + d->height); } painter.drawLine(line); } } // Paint diagonal lines through the cell. // void CellView::paintCellDiagonalLines(QPainter& painter, const QPointF& coordinate) const { if (d->merged) return; QPen fallDiagonalPen(d->style.fallDiagonalPen()); QPen goUpDiagonalPen(d->style.goUpDiagonalPen()); if (fallDiagonalPen.style() != Qt::NoPen) { painter.setPen(fallDiagonalPen); painter.drawLine(QLineF(coordinate.x(), coordinate.y(), coordinate.x() + d->width, coordinate.y() + d->height)); } if (goUpDiagonalPen.style() != Qt::NoPen) { painter.setPen(goUpDiagonalPen); painter.drawLine(QLineF(coordinate.x(), coordinate.y() + d->height, coordinate.x() + d->width, coordinate.y())); } } void CellView::paintFilterButton(QPainter& painter, const QPointF& coordinate, const Cell& cell, SheetView* sheetView) const { Q_UNUSED(cell); QStyleOptionComboBox options; options.direction = cell.sheet()->layoutDirection(); options.editable = true; options.fontMetrics = painter.fontMetrics(); options.frame = false; options.rect = sheetView->viewConverter()->documentToView(QRectF(coordinate, QSizeF(d->width, d->height))).toRect(); options.subControls =/* QStyle::SC_ComboBoxEditField | */QStyle::SC_ComboBoxArrow; painter.save(); painter.scale(sheetView->viewConverter()->viewToDocumentX(1.0), sheetView->viewConverter()->viewToDocumentY(1.0)); QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &options, &painter); painter.restore(); } // Cut d->displayText, so that it only holds the part that can be displayed. // // Used in paintText(). // QString CellView::textDisplaying(const QFontMetricsF& fm, const Cell& cell) { Style::HAlign hAlign = style().halign(); if (!d->fittingWidth) hAlign = Style::Left; // force left alignment, if text does not fit const bool isNumeric = cell.value().isNumber(); if (style().wrapText() || d->richText) { // For wrapping text and richtext always draw all text return d->displayText; } else if (style().angle() != 0) { // Rotated text, return all text return d->displayText; } else if (!style().verticalText()) { // Non-vertical text: the ordinary case. // Not enough space but align to left qreal len = 0.0; // If it fits in the width, chopping won't do anything if (d->fittingWidth) { return d->displayText; } len = d->width; #if 0 for (int i = cell.column(); i <= cell.column() + d->obscuredCellsX; i++) { ColumnFormat *cl2 = cell.sheet()->columnFormat(i); len += cl2->width() - 1.0; //-1.0 because the pixel in between 2 cells is shared between both cells } #endif QString tmp; qreal tmpIndent = 0.0; if (!cell.isEmpty()) tmpIndent = style().indentation(); KLocale* locale = cell.sheet()->map()->calculationSettings()->locale(); // Estimate worst case length to reduce the number of iterations. int start = qRound((len - 4.0 - 1.0 - tmpIndent) / fm.width('.')); start = qMin(d->displayText.length(), start); int idxOfDecimal = d->displayText.indexOf(locale->decimalSymbol()); if (idxOfDecimal < 0) idxOfDecimal = d->displayText.length(); // Start out with the whole text, cut one character at a time, and // when the text finally fits, return it. for (int i = start; i >= 0; i--) { //Note that numbers are always treated as left-aligned since if we have to cut digits off, they should //always be the least significant ones at the end of the string if (hAlign == Style::Left || hAlign == Style::HAlignUndefined || isNumeric) tmp = d->displayText.left(i); else if (hAlign == Style::Right) tmp = d->displayText.right(i); else tmp = d->displayText.mid((d->displayText.length() - i) / 2, i); if (isNumeric) { //For numeric values, we can cut off digits after the decimal point to make it fit, //but not the integer part of the number. //If this number still contains a fraction part then we don't need to do anything, if we have run //out of space to fit even the integer part of the number then display ######### //TODO Perhaps try to display integer part in standard form if there is not enough room for it? if (i < idxOfDecimal) { //first try removing thousands separators before replacing text with ###### QString tmp2 = d->displayText; tmp2.remove(locale->thousandsSeparator()); int sepCount = d->displayText.length() - tmp2.length(); if (i < idxOfDecimal - sepCount) { tmp = QString().fill('#', i); } else { tmp = tmp2.left(i); } } } // 4 equal length of red triangle +1 point. if (fm.width(tmp) + tmpIndent < len - 4.0 - 1.0) { if (style().angle() != 0) { QString tmp2; const qreal rowHeight = cell.sheet()->rowFormats()->rowHeight(cell.row()); if (d->textHeight > rowHeight) { for (int j = d->displayText.length(); j != 0; j--) { tmp2 = d->displayText.left(j); if (fm.width(tmp2) < rowHeight - 1.0) { return d->displayText.left(qMin(tmp.length(), tmp2.length())); } } } else return tmp; } else return tmp; } } return QString(""); } else if (style().verticalText()) { // Vertical text. const qreal rowHeight = cell.sheet()->rowFormats()->rowHeight(cell.row()); qreal tmpIndent = 0.0; // Not enough space but align to left. qreal len = 0.0; len = d->width; #if 0 for (int i = cell.column(); i <= cell.column() + d->obscuredCellsX; i++) { ColumnFormat *cl2 = cell.sheet()->columnFormat(i); // -1.0 because the pixel in between 2 cells is shared between both cells len += cl2->width() - 1.0; } #endif if (!cell.isEmpty()) tmpIndent = style().indentation(); if ((d->textWidth + tmpIndent > len) || d->textWidth == 0.0) return QString(""); for (int i = d->displayText.length(); i != 0; i--) { if (fm.ascent() + fm.descent() * i < rowHeight - 1.0) return d->displayText.left(i); } return QString(""); } QString tmp; for (int i = d->displayText.length(); i != 0; i--) { tmp = d->displayText.left(i); // 4 equals length of red triangle +1 pixel if (fm.width(tmp) < d->width - 4.0 - 1.0) return tmp; } return QString(); } // End of Painting // ================================================================ // ================================================================ // Layout // Recalculate the entire layout. This includes the following members: // // d->textX, d->textY // d->textWidth, d->textHeight // d->obscuredCellsX, d->obscuredCellsY // d->width, d->height // // and, of course, // // d->displayText // void CellView::makeLayout(SheetView* sheetView, const Cell& cell) { // Up to here, we have just cared about the contents, not the // painting of it. Now it is time to see if the contents fits into // the cell and, if not, maybe rearrange the outtext a bit. // First, create a device independent font and its metrics. KoPostscriptPaintDevice device; QFont font(d->style.font(), &device); QFontMetricsF fontMetrics(font, &device); // Then calculate text dimensions, i.e. d->textWidth and d->textHeight, // and check whether the text fits into the cell dimension by the way. d->calculateTextSize(font, fontMetrics); d->shrinkToFitFontSize = 0.0; //if shrink-to-fit is enabled, try to find a font size so that the string fits into the cell if (d->style.shrinkToFit()) { int lower = 1; int upper = font.pointSize() * 2; int siz = 0; while (lower != upper) { siz = static_cast( std::ceil( lower + ( upper - lower ) / 2. ) ); font.setPointSizeF(siz / 2.); fontMetrics = QFontMetricsF(font, &device); d->calculateTextSize(font, fontMetrics); if (d->fittingWidth) lower = siz; else upper = siz - 1; } d->shrinkToFitFontSize = upper / 2.0; font.setPointSizeF(upper / 2.0); fontMetrics = QFontMetricsF(font, &device); d->calculateTextSize(font, fontMetrics); // update fittingWidth et al d->fittingWidth = true; } // Obscure horizontal cells, if necessary. if (!d->fittingWidth) { obscureHorizontalCells(sheetView, cell); // Recalculate the text dimensions and check whether the text fits. d->calculateTextSize(font, fontMetrics); } // Obscure vertical cells, if necessary. if (!d->fittingHeight) { obscureVerticalCells(sheetView, cell); // Recalculate the text dimensions and check whether the text fits. d->calculateTextSize(font, fontMetrics); } // text still does not fit into cell dimension? if (!d->fittingWidth || !d->fittingHeight) { // Truncate the output text. d->displayText = textDisplaying(fontMetrics, cell); // d->truncateText(font, fontMetrics); // // Recalculate the text dimensions and check whether the text fits. // d->calculateTextSize(font, fontMetrics); } // Recalculate the text offset. textOffset(fontMetrics, cell); } void CellView::calculateCellDimension(const Cell& cell) { Q_UNUSED(cell); #if 0 qreal width = cell.sheet()->columnFormat(cell.column())->width(); qreal height = cell.sheet()->rowFormat(cell.row())->height(); // Calculate extraWidth and extraHeight if we have a merged cell. if (cell.testFlag(Cell::Flag_Merged)) { // FIXME: Introduce qreal extraWidth/Height here and use them // instead (see FIXME about this in paintCell()). for (int x = cell.column() + 1; x <= cell.column() + d->obscuredCellsX; x++) width += cell.sheet()->columnFormat(x)->width(); for (int y = cell.row() + 1; y <= cell.row() + d->obscuredCellsY; y++) height += cell.sheet()->rowFormat(y)->height(); } // Cache the newly calculated extraWidth and extraHeight if we have // already allocated a struct for it. Otherwise it will be zero, so // don't bother. if (cell.d->hasExtra()) { cell.d->extra()->extraWidth = width; cell.d->extra()->extraHeight = height; } #endif } // Recalculate d->textX and d->textY. // // Used in makeLayout(). // void CellView::textOffset(const QFontMetricsF& fontMetrics, const Cell& cell) { Q_UNUSED(cell) const qreal ascent = fontMetrics.ascent(); const Style::HAlign hAlign = d->style.halign(); const Style::VAlign vAlign = d->style.valign(); const int tmpAngle = fixAngle(d->style.angle()); const bool tmpVerticalText = d->style.verticalText(); const bool tmpMultiRow = d->style.wrapText() || d->displayText.contains('\n'); const bool tmpRichText = !d->richText.isNull(); qreal w = d->width; qreal h = d->height; // doc coordinate system; no zoom applied const qreal effTop = s_borderSpace + 0.5 * d->style.topBorderPen().width(); const qreal effBottom = h - s_borderSpace - 0.5 * d->style.bottomBorderPen().width(); // Calculate d->textY based on the vertical alignment and a few // other inputs. switch (vAlign) { case Style::VJustified: case Style::Top: { if (tmpAngle == 0 && tmpRichText) { d->textY = effTop + d->textHeight; } else if (tmpAngle == 0) { d->textY = effTop + ascent; } else if (tmpAngle < 0) { d->textY = effTop; } else { d->textY = effTop + ascent * ::cos(tmpAngle * M_PI / 180); } break; } case Style::VAlignUndefined: // fall through case Style::Bottom: { if (!tmpVerticalText && !tmpMultiRow && !tmpAngle && !tmpRichText) { d->textY = effBottom; } else if (tmpAngle != 0) { if (tmpAngle < 0) { d->textY = effBottom - d->textHeight; } else { d->textY = effBottom - d->textHeight + ascent * ::cos(tmpAngle * M_PI / 180); } if (tmpAngle < -90 || tmpAngle > 90) { d->textY += ascent * ::cos(tmpAngle * M_PI / 180); } } else if (tmpRichText) { d->textY = effBottom; } else if (tmpMultiRow && !tmpVerticalText) { d->textY = effBottom - d->textHeight + ascent; } else { // vertical text // Is enough place available? if (effBottom - effTop - d->textHeight > 0) { d->textY = effBottom - d->textHeight + ascent; } else { d->textY = effTop + ascent; } } break; } case Style::VDistributed: if (!tmpVerticalText && !tmpAngle && d->textLinesCount > 1) { d->textY = effTop + ascent; break; } // fall through case Style::Middle: { if (!tmpVerticalText && !tmpMultiRow && !tmpAngle && !tmpRichText) { d->textY = (h - d->textHeight) / 2 + ascent; } else if (tmpAngle != 0) { // Is enough place available? if (effBottom - effTop - d->textHeight > 0) { if (tmpAngle < 0) { d->textY = (h - d->textHeight) / 2; } else { d->textY = (h - d->textHeight) / 2 + ascent * ::cos(tmpAngle * M_PI / 180); } } else { if (tmpAngle < 0) { d->textY = effTop; } else { d->textY = effTop + ascent * ::cos(tmpAngle * M_PI / 180); } } } else if (tmpRichText && !tmpVerticalText) { d->textY = (h - d->textHeight) / 2 + d->textHeight; } else if (tmpMultiRow && !tmpVerticalText) { // Is enough place available? if (effBottom - effTop - d->textHeight > 0) { d->textY = (h - d->textHeight) / 2 + ascent; } else { d->textY = effTop + ascent; } } else { // Is enough place available? if (effBottom - effTop - d->textHeight > 0) { d->textY = (h - d->textHeight) / 2 + ascent; } else d->textY = effTop + ascent; } break; } } // Calculate d->textX based on alignment and textwidth. switch (hAlign) { case Style::Left: d->textX = 0.5 * d->style.leftBorderPen().width() + s_borderSpace; break; case Style::Right: d->textX = w - s_borderSpace - d->textWidth - 0.5 * d->style.rightBorderPen().width(); break; case Style::Center: d->textX = 0.5 * (w - s_borderSpace - d->textWidth - 0.5 * d->style.rightBorderPen().width()); break; default: break; } } void CellView::obscureHorizontalCells(SheetView* sheetView, const Cell& masterCell) { if (d->hidden) return; qreal extraWidth = 0.0; const Style::HAlign align = d->style.halign(); // Get indentation. This is only used for left aligned text. qreal indent = 0.0; if (align == Style::Left && !masterCell.isEmpty()) indent = style().indentation(); // Set d->fittingWidth to false, if the text is vertical or angled, and too // high for the cell. if (style().verticalText() || style().angle() != 0) if (d->textHeight >= d->height) d->fittingWidth = false; // Do we have to occupy additional cells to the right? This is only // done for cells that have no merged cells in the Y direction. // // FIXME: Check if all cells along the merged edge to the right are // empty and use the extra space? No, probably not. // if (d->textWidth + indent > (d->width - 2 * s_borderSpace - style().leftBorderPen().width() - style().rightBorderPen().width()) && masterCell.mergedYCells() == 0) { const int effectiveCol = masterCell.column() + masterCell.mergedXCells(); int col = effectiveCol; // Find free cells to the right of this one. enum { Undefined, EnoughSpace, NotEnoughSpace } status = Undefined; while (status == Undefined) { Cell nextCell = Cell(masterCell.sheet(), col + 1, masterCell.row()).masterCell(); if (nextCell.isEmpty()) { extraWidth += nextCell.width(); col += 1 + nextCell.mergedXCells(); // Enough space? if (d->textWidth + indent <= (d->width + extraWidth - 2 * s_borderSpace - style().leftBorderPen().width() - style().rightBorderPen().width())) status = EnoughSpace; } else // Not enough space, but the next cell is not empty status = NotEnoughSpace; } // Try to use additional space from the neighboring cells that // were calculated in the last step. // // Currently this is only done for left aligned cells. We have to // check to make sure we haven't already force-merged enough cells // // FIXME: Why not right/center aligned text? // // FIXME: Shouldn't we check to see if end == -1 here before // setting d->fittingWidth to false? // if (style().halign() == Style::Left || (style().halign() == Style::HAlignUndefined && !masterCell.value().isNumber())) { if (col > effectiveCol) { d->obscuredCellsX = col - effectiveCol; d->width += extraWidth; if (sheetView->sheet()->layoutDirection() == Qt::RightToLeft) { d->rtlOffset += extraWidth; } const QRect obscuredRange(effectiveCol + 1, masterCell.row(), d->obscuredCellsX, 1); sheetView->obscureCells(masterCell.cellPosition(), d->obscuredCellsX, d->obscuredCellsY); // Not enough space if (status == NotEnoughSpace) d->fittingWidth = false; } else d->fittingWidth = false; } else d->fittingWidth = false; } } void CellView::obscureVerticalCells(SheetView* sheetView, const Cell& masterCell) { if (d->hidden) return; qreal extraHeight = 0.0; // Do we have to occupy additional cells at the bottom ? // // FIXME: Setting to make the current cell grow. // if (d->displayText.contains('\n') && d->textHeight > (d->height - 2 * s_borderSpace - style().topBorderPen().width() - style().bottomBorderPen().width())) { const int effectiveRow = masterCell.row() + masterCell.mergedYCells(); int row = effectiveRow; // Find free cells bottom to this one enum { Undefined, EnoughSpace, NotEnoughSpace } status = Undefined; while (status == Undefined) { Cell nextCell = Cell(masterCell.sheet(), masterCell.column(), row + 1).masterCell(); bool isEmpty = true; for (int col = 0; col < masterCell.mergedXCells() + d->obscuredCellsX+1; col++) { Cell cellNext = Cell(masterCell.sheet(), masterCell.column() + col, row + 1).masterCell(); if (!cellNext.isEmpty()) { isEmpty = false; break; } } if (isEmpty) { extraHeight += nextCell.height(); row += 1 + nextCell.mergedYCells(); // Enough space ? if (d->textHeight <= (d->height + extraHeight - 2 * s_borderSpace - style().topBorderPen().width() - style().bottomBorderPen().width())) status = EnoughSpace; } else // Not enough space, but the next cell is not empty. status = NotEnoughSpace; } // Check to make sure we haven't already force-merged enough cells. if (row > effectiveRow) { d->obscuredCellsY = row - effectiveRow; d->height += extraHeight; const QRect obscuredRange(masterCell.column(), effectiveRow + 1, 1, d->obscuredCellsY); sheetView->obscureCells(masterCell.cellPosition(), d->obscuredCellsX, d->obscuredCellsY); // Not enough space if (status == NotEnoughSpace) d->fittingHeight = false; } else d->fittingHeight = false; } } void CellView::drawText(QPainter& painter, const QPointF& location, const QStringList& textLines, const Cell& cell, qreal lineSpacing) const { Q_UNUSED(cell) KoPostscriptPaintDevice device; const QFont font(d->calculateFont(), &device); const QFontMetricsF fontMetrics(font, &device); const qreal leading = fontMetrics.leading(); const QTextOption options = d->textOptions(); const bool tmpVerticalText = d->style.verticalText(); const bool tmpAngled = fixAngle(d->style.angle()) != 0; const qreal tmpIndent = cell.isEmpty() || d->style.halign() != Style::Left ? 0.0 : style().indentation(); const qreal lineWidth = tmpAngled ? 1e9 : tmpVerticalText ? fontMetrics.maxWidth() : (d->width - 2 * s_borderSpace - 0.5 * d->style.leftBorderPen().width() - 0.5 * d->style.rightBorderPen().width()) - tmpIndent; qreal offset = 1.0 - fontMetrics.ascent(); for (int i = 0; i < textLines.count(); ++i) { QTextLayout textLayout(textLines[i], font); textLayout.setCacheEnabled(true); textLayout.setTextOption(options); textLayout.beginLayout(); qreal height = 0.0; forever { // we don't need to break if the text no longer fits in the cell; // a clipping region is set on the painter to make sure we won't draw outside the cell QTextLine line = textLayout.createLine(); if (!line.isValid()) break; line.setLineWidth(lineWidth); height += leading; line.setPosition(QPointF((s_borderSpace + 0.5 * d->style.leftBorderPen().widthF()), height)); height += line.height() + lineSpacing; } textLayout.endLayout(); textLayout.draw(&painter, QPointF(location.x(), (location.y() + offset))); offset += height; } } qreal CellView::cellHeight() const { return d->height; } qreal CellView::cellWidth() const { return d->width; } bool CellView::dimensionFits() const { return d->fittingHeight && d->fittingWidth; } void CellView::Private::checkForFilterButton(const Cell& cell) { const Database database = cell.database(); if (database.isEmpty() || !database.displayFilterButtons()) { filterButton = false; return; } if (database.orientation() == Qt::Horizontal) filterButton = database.range().firstRange().left() == cell.column(); else // Qt::Vertical filterButton = database.range().firstRange().top() == cell.row(); } void CellView::Private::calculateTextSize(const QFont& font, const QFontMetricsF& fontMetrics) { if (style.angle() != 0) calculateAngledTextSize(font, fontMetrics); else if (style.verticalText()) calculateVerticalTextSize(font, fontMetrics); else if (richText) calculateRichTextSize(font, fontMetrics); else calculateHorizontalTextSize(font, fontMetrics); } void CellView::Private::calculateHorizontalTextSize(const QFont& font, const QFontMetricsF& fontMetrics) { const QStringList textLines = displayText.split('\n'); const qreal leading = fontMetrics.leading(); const QTextOption options = textOptions(); const qreal tmpIndent = style.halign() != Style::Left ? 0.0 : style.indentation(); const qreal lineWidth = (width - 2 * s_borderSpace - 0.5 * style.leftBorderPen().width() - 0.5 * style.rightBorderPen().width()) - tmpIndent; textHeight = 0.0; textWidth = 0.0; textLinesCount = 0; fittingHeight = true; fittingWidth = true; for (int i = 0; i < textLines.count(); ++i) { textWidth = qMax(textWidth, fontMetrics.width(textLines[i])); QTextLayout textLayout(textLines[i], font); textLayout.setTextOption(options); textLayout.beginLayout(); forever { QTextLine line = textLayout.createLine(); if (!line.isValid()) break; // forever line.setLineWidth(lineWidth); textHeight += leading + line.height(); if ((textHeight - fontMetrics.descent()) > (height - 2 * s_borderSpace - 0.5 * style.topBorderPen().width() - 0.5 * style.bottomBorderPen().width())) { fittingHeight = false; break; // forever } } textLinesCount += textLayout.lineCount(); textLayout.endLayout(); } // The width fits, if the text is wrapped or all lines are smaller than the cell width. fittingWidth = style.wrapText() || textWidth <= lineWidth; } void CellView::Private::calculateVerticalTextSize(const QFont& font, const QFontMetricsF& fontMetrics) { Q_UNUSED(font) int rows = 0; const QStringList textLines = displayText.split('\n'); for (int i = 0; i < textLines.count(); ++i) rows = qMax(rows, textLines[i].count()); textHeight = (fontMetrics.ascent() + fontMetrics.descent()) * rows; textWidth = (displayText.count('\n') + 1) * fontMetrics.maxWidth(); fittingHeight = textHeight <= this->width; fittingWidth = textWidth <= this->height; } void CellView::Private::calculateAngledTextSize(const QFont& font, const QFontMetricsF& fontMetrics) { Q_UNUSED(font) const qreal angle = fixAngle(style.angle()); QStringList lines = displayText.split('\n'); const qreal height = fontMetrics.ascent() + fontMetrics.descent() * lines.count(); qreal width = 0; foreach (const QString& line, lines) { width = qMax(width, fontMetrics.width(line)); } textHeight = qAbs(height * ::cos(angle * M_PI / 180)) + qAbs(width * ::sin(angle * M_PI / 180)); textWidth = qAbs(height * ::sin(angle * M_PI / 180)) + qAbs(width * ::cos(angle * M_PI / 180)); fittingHeight = textHeight <= this->width; fittingWidth = textWidth <= this->height; } void CellView::Private::calculateRichTextSize(const QFont& font, const QFontMetricsF& fontMetrics) { Q_UNUSED(fontMetrics); #ifdef CALLIGRA_SHEETS_MT QMutexLocker(mutex.data()); #endif richText->setDefaultFont(font); richText->setDocumentMargin(0); const qreal lineWidth = width - 2 * s_borderSpace - 0.5 * style.leftBorderPen().widthF() - 0.5 * style.rightBorderPen().widthF(); if (style.wrapText()) richText->setTextWidth(lineWidth); else richText->setTextWidth(-1); const QSizeF textSize = richText->size(); textHeight = textSize.height(); textWidth = textSize.width(); textLinesCount = richText->lineCount(); // TODO: linescount is not correct, and distributed vertical alignment doesn't // work anyway for richtext at the momemnt fittingHeight = textHeight <= (height - 2 * s_borderSpace - 0.5 * style.topBorderPen().widthF() - 0.5 * style.bottomBorderPen().widthF()); fittingWidth = textWidth <= lineWidth; } void CellView::Private::truncateText(const QFont& font, const QFontMetricsF& fontMetrics) { if (style.angle() != 0) truncateAngledText(font, fontMetrics); else if (style.verticalText()) truncateVerticalText(font, fontMetrics); else truncateHorizontalText(font, fontMetrics); } void CellView::Private::truncateHorizontalText(const QFont& font, const QFontMetricsF& fontMetrics) { if (!style.wrapText()) { const QStringList textLines = displayText.split('\n'); displayText.clear(); qreal height = font.pointSizeF(); for (int i = 0; i < textLines.count(); ++i) { if (height > this->height) break; int count = 0; while (count < textLines[i].count() && fontMetrics.width(textLines[i].left(count)) <= this->width) ++count; displayText += textLines[i].left(count); height += fontMetrics.height(); if (height <= this->height) displayText += '\n'; } } // else it is handled by QTextLayout } void CellView::Private::truncateVerticalText(const QFont& font, const QFontMetricsF& fontMetrics) { Q_UNUSED(font); Q_UNUSED(fontMetrics); } void CellView::Private::truncateAngledText(const QFont& font, const QFontMetricsF& fontMetrics) { Q_UNUSED(font); Q_UNUSED(fontMetrics); } QTextOption CellView::Private::textOptions() const { QTextOption options; switch (style.halign()) { default: case Style::Left: options.setAlignment(Qt::AlignLeft); break; case Style::Right: options.setAlignment(Qt::AlignRight); break; case Style::Center: options.setAlignment(Qt::AlignHCenter); break; case Style::Justified: options.setAlignment(Qt::AlignJustify); break; } // The text consists of a single character, if it's vertical. Always center it. if (style.verticalText()) options.setAlignment(Qt::AlignHCenter); options.setWrapMode(style.wrapText() ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap); options.setUseDesignMetrics(true); return options; } diff --git a/sheets/ui/SheetView.cpp b/sheets/ui/SheetView.cpp index c9754aee6d9..6260d1fda25 100644 --- a/sheets/ui/SheetView.cpp +++ b/sheets/ui/SheetView.cpp @@ -1,770 +1,770 @@ /* This file is part of the KDE project Copyright 2006 Stefan Nikolaus This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Local #include "SheetView.h" #include #include #include #ifdef CALLIGRA_SHEETS_MT #include #include #include #include #include #endif #include #include "CellView.h" #include "calligra_sheets_limits.h" #include "PointStorage.h" #include "RectStorage.h" #include "Region.h" #include "RowColumnFormat.h" #include "RowFormatStorage.h" #include "Sheet.h" using namespace Calligra::Sheets; struct CellPaintData { CellPaintData(const CellView &cellView, const Cell &cell, const QPointF &coordinate) : cellView(cellView) , cell(cell) , coordinate(coordinate) {} CellView cellView; Cell cell; QPointF coordinate; }; class Q_DECL_HIDDEN SheetView::Private { public: Private() #ifdef CALLIGRA_SHEETS_MT : cacheMutex(QMutex::Recursive) #endif {} const Sheet* sheet; const KoViewConverter* viewConverter; QRect visibleRect; QCache cache; #ifdef CALLIGRA_SHEETS_MT QMutex cacheMutex; #endif QRegion cachedArea; CellView* defaultCellView; // The maximum accessed cell range used for the scrollbar ranges. QSize accessedCellRange; FusionStorage* obscuredInfo; QSize obscuredRange; // size of the bounding box of obscuredInfo #ifdef CALLIGRA_SHEETS_MT QReadWriteLock obscuredLock; #endif PointStorage highlightedCells; QPoint activeHighlight; #ifdef CALLIGRA_SHEETS_MT QReadWriteLock highlightLock; #endif QColor highlightColor; QColor highlightMaskColor; QColor activeHighlightColor; public: Cell cellToProcess(int col, int row, QPointF& coordinate, QSet& processedMergedCells, const QRect& visRect); #ifdef CALLIGRA_SHEETS_MT CellView cellViewToProcess(Cell& cell, QPointF& coordinate, QSet& processedObscuredCells, SheetView* sheetView, const QRect& visRect); #else const CellView& cellViewToProcess(Cell& cell, QPointF& coordinate, QSet& processedObscuredCells, SheetView* sheetView, const QRect& visRect); #endif }; Cell SheetView::Private::cellToProcess(int col, int row, QPointF& coordinate, QSet& processedMergedCells, const QRect& visRect) { Cell cell(sheet, col, row); if (cell.isPartOfMerged()) { cell = cell.masterCell(); // if the rect of visible cells contains this master cell, it was already painted if (visRect.contains(cell.cellPosition())) { coordinate.setY(coordinate.y() + sheet->rowFormats()->rowHeight(row)); return Cell(); // next row } // if the out of bounds master cell was already painted, there's nothing more to do if (processedMergedCells.contains(cell)) { coordinate.setY(coordinate.y() + sheet->rowFormats()->rowHeight(row)); return Cell(); // next row } processedMergedCells.insert(cell); // take the coordinate of the master cell if (sheet->layoutDirection() == Qt::RightToLeft) { for (int c = cell.column()+1; c <= col; ++c) coordinate.setX(coordinate.x() + sheet->columnFormat(c)->width()); } else { for (int c = cell.column(); c < col; ++c) coordinate.setX(coordinate.x() - sheet->columnFormat(c)->width()); } for (int r = cell.row(); r < row; ++r) coordinate.setY(coordinate.y() - sheet->rowFormats()->rowHeight(r)); } return cell; } #ifdef CALLIGRA_SHEETS_MT CellView SheetView::Private::cellViewToProcess(Cell& cell, QPointF& coordinate, QSet& processedObscuredCells, SheetView* sheetView, const QRect& visRect) #else const CellView& SheetView::Private::cellViewToProcess(Cell& cell, QPointF& coordinate, QSet& processedObscuredCells, SheetView* sheetView, const QRect& visRect) #endif { const int col = cell.column(); const int row = cell.row(); const QPoint cellPos = cell.cellPosition(); #ifdef CALLIGRA_SHEETS_MT CellView cellView = sheetView->cellView(col, row); #else const CellView& cellView = sheetView->cellView(col, row); #endif if (sheetView->isObscured(cellPos)) { // if the rect of visible cells contains the obscuring cell, it was already painted if (visRect.contains(sheetView->obscuringCell(cellPos))) { coordinate.setY(coordinate.y() + sheet->rowFormats()->rowHeight(row)); cell = Cell(); return cellView; // next row } cell = Cell(sheet, sheetView->obscuringCell(cellPos)); if (processedObscuredCells.contains(cell)) { coordinate.setY(coordinate.y() + sheet->rowFormats()->rowHeight(row)); cell = Cell(); return cellView; // next row } processedObscuredCells.insert(cell); // take the coordinate of the obscuring cell if (sheet->layoutDirection() == Qt::RightToLeft) { for (int c = cell.column()+1; c <= col; ++c) coordinate.setX(coordinate.x() + sheet->columnFormat(c)->width()); } else { for (int c = cell.column(); c < col; ++c) coordinate.setX(coordinate.x() - sheet->columnFormat(c)->width()); } for (int r = cell.row(); r < row; ++r) coordinate.setY(coordinate.y() - sheet->rowFormats()->rowHeight(r)); // use the CellView of the obscuring cell return sheetView->cellView(cell.column(), cell.row()); } return cellView; } SheetView::SheetView(const Sheet* sheet) : QObject(const_cast(sheet)) , d(new Private) { d->sheet = sheet; d->viewConverter = 0; d->visibleRect = QRect(1, 1, 0, 0); d->cache.setMaxCost(10000); d->defaultCellView = createDefaultCellView(); d->accessedCellRange = sheet->usedArea().size().expandedTo(QSize(256, 256)); d->obscuredInfo = new FusionStorage(sheet->map()); d->obscuredRange = QSize(0, 0); d->highlightMaskColor = QColor(0, 0, 0, 128); d->activeHighlightColor = QColor(255, 127, 0, 128); } SheetView::~SheetView() { delete d->defaultCellView; delete d->obscuredInfo; delete d; } const Sheet* SheetView::sheet() const { return d->sheet; } void SheetView::setViewConverter(const KoViewConverter* viewConverter) { Q_ASSERT(viewConverter); d->viewConverter = viewConverter; } const KoViewConverter* SheetView::viewConverter() const { Q_ASSERT(d->viewConverter); return d->viewConverter; } #ifdef CALLIGRA_SHEETS_MT CellView SheetView::cellView(const QPoint& pos) #else const CellView& SheetView::cellView(const QPoint& pos) #endif { return cellView(pos.x(), pos.y()); } #ifdef CALLIGRA_SHEETS_MT CellView SheetView::cellView(int col, int row) #else const CellView& SheetView::cellView(int col, int row) #endif { Q_ASSERT(1 <= col && col <= KS_colMax); Q_ASSERT(1 <= row && col <= KS_rowMax); #ifdef CALLIGRA_SHEETS_MT QMutexLocker ml(&d->cacheMutex); #endif CellView *v = d->cache.object(QPoint(col, row)); if (!v) { v = createCellView(col, row); d->cache.insert(QPoint(col, row), v); d->cachedArea += QRect(col, row, 1, 1); } #ifdef CALLIGRA_SHEETS_MT // create a copy as long as the mutex is locked CellView cellViewCopy = *v; return cellViewCopy; #else return *v; #endif } void SheetView::setPaintCellRange(const QRect& rect) { #ifdef CALLIGRA_SHEETS_MT QMutexLocker ml(&d->cacheMutex); #endif d->visibleRect = rect & QRect(1, 1, KS_colMax, KS_rowMax); d->cache.setMaxCost(2 * rect.width() * rect.height()); } QRect SheetView::paintCellRange() const { return d->visibleRect; } void SheetView::invalidateRegion(const Region& region) { QRegion qregion; Region::ConstIterator end(region.constEnd()); for (Region::ConstIterator it(region.constBegin()); it != end; ++it) { qregion += (*it)->rect(); } // reduce to the cached area qregion &= d->cachedArea; QVector rects = qregion.rects(); for (int i = 0; i < rects.count(); ++i) invalidateRange(rects[i]); } void SheetView::invalidate() { #ifdef CALLIGRA_SHEETS_MT QMutexLocker ml(&d->cacheMutex); #endif delete d->defaultCellView; d->defaultCellView = createDefaultCellView(); d->cache.clear(); d->cachedArea = QRegion(); delete d->obscuredInfo; d->obscuredInfo = new FusionStorage(d->sheet->map()); d->obscuredRange = QSize(0, 0); } void SheetView::paintCells(QPainter& painter, const QRectF& paintRect, const QPointF& topLeft, CanvasBase*, const QRect& visibleRect) { const QRect& visRect = visibleRect.isValid() ? visibleRect : d->visibleRect; // paintRect: the canvas area, that should be painted; in document coordinates; // no layout direction consideration; scrolling offset applied; // independent from painter transformations // topLeft: the document coordinate of the top left cell's top left corner; // no layout direction consideration; independent from painter // transformations // NOTE Stefan: The painting is split into several steps. In each of these all cells in // d->visibleRect are traversed. This may appear suboptimal at the first look, but // ensures that the borders are not erased by the background of adjacent cells. // debugSheets << "paintRect:" << paintRect; // debugSheets << "topLeft:" << topLeft; QRegion clipRect(painter.clipRegion()); // 0. Paint the sheet background if (!sheet()->backgroundImage().isNull()) { //TODO support all the different properties Sheet::BackgroundImageProperties properties = sheet()->backgroundImageProperties(); if( properties.repeat == Sheet::BackgroundImageProperties::Repeat ) { const int firstCol = visRect.left(); const int firstRow = visRect.top(); const int firstColPosition = d->sheet->columnPosition(firstCol); const int firstRowPosition = d->sheet->rowPosition(firstRow); const int imageWidth = sheet()->backgroundImage().rect().width(); const int imageHeight = sheet()->backgroundImage().rect().height(); int xBackground = firstColPosition - (firstColPosition % imageWidth); int yBackground = firstRowPosition - (firstRowPosition % imageHeight); const int lastCol = visRect.right(); const int lastRow = visRect.bottom(); const int lastColPosition = d->sheet->columnPosition(lastCol); const int lastRowPosition = d->sheet->rowPosition(lastRow); while( xBackground < lastColPosition ) { int y = yBackground; while( y < lastRowPosition ) { painter.drawImage(QRect(xBackground, y, imageWidth, imageHeight), sheet()->backgroundImage()); y += imageHeight; } xBackground += imageWidth; } } } // 1. Paint the cell background // Handle right-to-left layout. // In an RTL sheet the cells have to be painted at their opposite horizontal // location on the canvas, meaning that column A will be the rightmost column // on screen, column B will be to the left of it and so on. Here we change // the horizontal coordinate at which we start painting the cell in case the // sheet's direction is RTL. const bool rightToLeft = sheet()->layoutDirection() == Qt::RightToLeft; const QPointF startCoordinate(rightToLeft ? paintRect.width() - topLeft.x() : topLeft.x(), topLeft.y()); QPointF coordinate(startCoordinate); // debugSheets << "start coordinate:" << coordinate; QSet processedMergedCells; QSet processedObscuredCells; QList cached_cells; for (int col = visRect.left(); col <= visRect.right(); ++col) { if (d->sheet->columnFormat(col)->isHiddenOrFiltered()) continue; if (rightToLeft) coordinate.setX(coordinate.x() - d->sheet->columnFormat(col)->width()); // debugSheets <<"coordinate:" << coordinate; for (int row = visRect.top(); row <= visRect.bottom(); ++row) { int lastHiddenRow; if (d->sheet->rowFormats()->isHiddenOrFiltered(row, &lastHiddenRow)) { row = lastHiddenRow; continue; } // save the coordinate const QPointF savedCoordinate = coordinate; // figure out, if any and which cell has to be painted (may be a master cell) Cell cell = d->cellToProcess(col, row, coordinate, processedMergedCells, visRect); if (!cell) continue; // figure out, which CellView to use (may be one for an obscuring cell) CellPaintData cpd(d->cellViewToProcess(cell, coordinate, processedObscuredCells, this, visRect), cell, coordinate); if (!cell) continue; cpd.cellView.paintCellBackground(painter, clipRect, coordinate); cached_cells.append(cpd); // restore coordinate coordinate = savedCoordinate; coordinate.setY(coordinate.y() + d->sheet->rowFormats()->rowHeight(row)); } coordinate.setY(topLeft.y()); if (!rightToLeft) coordinate.setX(coordinate.x() + d->sheet->columnFormat(col)->width()); } // 2. Paint the cell content including markers (formula, comment, ...) for (QList::ConstIterator it(cached_cells.constBegin()); it != cached_cells.constEnd(); ++it) { it->cellView.paintCellContents(paintRect, painter, clipRect, it->coordinate, it->cell, this); } // 3. Paint the default borders coordinate = startCoordinate; processedMergedCells.clear(); for (int col = visRect.left(); col <= visRect.right(); ++col) { if (d->sheet->columnFormat(col)->isHiddenOrFiltered()) continue; if (rightToLeft) coordinate.setX(coordinate.x() - d->sheet->columnFormat(col)->width()); for (int row = visRect.top(); row <= visRect.bottom(); ++row) { int lastHiddenRow; if (d->sheet->rowFormats()->isHiddenOrFiltered(row, &lastHiddenRow)) { row = lastHiddenRow; continue; } // For borders even cells, that are merged in, need to be traversed. // Think of a merged cell with a set border and one its neighbours has a thicker border. // but: also the master cell of a merged cell always needs to be processed const QPointF savedCoordinate = coordinate; Cell cell = d->cellToProcess(col, row, coordinate, processedMergedCells, visRect); if (!!cell && (cell.column() != col || cell.row() != row)) { const CellView cellView = this->cellView(cell.cellPosition()); cellView.paintDefaultBorders(painter, clipRect, paintRect, coordinate, CellView::LeftBorder | CellView::RightBorder | CellView::TopBorder | CellView::BottomBorder, visRect, cell, this); } coordinate = savedCoordinate; const CellView cellView = this->cellView(col, row); cellView.paintDefaultBorders(painter, clipRect, paintRect, coordinate, CellView::LeftBorder | CellView::RightBorder | CellView::TopBorder | CellView::BottomBorder, visRect, Cell(d->sheet, col, row), this); coordinate.setY(coordinate.y() + d->sheet->rowFormats()->rowHeight(row)); } coordinate.setY(topLeft.y()); if (!rightToLeft) coordinate.setX(coordinate.x() + d->sheet->columnFormat(col)->width()); } // 4. Paint the custom borders, diagonal lines and page borders coordinate = startCoordinate; processedMergedCells.clear(); processedObscuredCells.clear(); for (int col = visRect.left(); col <= visRect.right(); ++col) { if (d->sheet->columnFormat(col)->isHiddenOrFiltered()) continue; if (rightToLeft) coordinate.setX(coordinate.x() - d->sheet->columnFormat(col)->width()); for (int row = visRect.top(); row <= visRect.bottom(); ++row) { int lastHiddenRow; if (d->sheet->rowFormats()->isHiddenOrFiltered(row, &lastHiddenRow)) { row = lastHiddenRow; continue; } // For borders even cells, that are merged in, need to be traversed. // Think of a merged cell with a set border and one its neighbours has a thicker border. // but: also the master cell of a merged cell always needs to be processed const QPointF savedCoordinate = coordinate; Cell cell = d->cellToProcess(col, row, coordinate, processedMergedCells, visRect); if (!!cell && (cell.column() != col || cell.row() != row)) { const CellView cellView = this->cellView(cell.cellPosition()); cellView.paintCellBorders(paintRect, painter, clipRect, coordinate, visRect, cell, this); } coordinate = savedCoordinate; Cell theCell(sheet(), col, row); const CellView cellView = d->cellViewToProcess(theCell, coordinate, processedObscuredCells, this, visRect); if (!!theCell && (theCell.column() != col || theCell.row() != row)) { cellView.paintCellBorders(paintRect, painter, clipRect, coordinate, visRect, theCell, this); } const CellView cellView2 = this->cellView(col, row); coordinate = savedCoordinate; cellView2.paintCellBorders(paintRect, painter, clipRect, coordinate, visRect, Cell(sheet(), col, row), this); coordinate.setY(coordinate.y() + d->sheet->rowFormats()->rowHeight(row)); } coordinate.setY(topLeft.y()); if (!rightToLeft) coordinate.setX(coordinate.x() + d->sheet->columnFormat(col)->width()); } // 5. Paint cell highlighting if (hasHighlightedCells()) { QPointF active = activeHighlight(); QPainterPath p; const CellPaintData* activeData = 0; for (QList::ConstIterator it(cached_cells.constBegin()); it != cached_cells.constEnd(); ++it) { if (isHighlighted(it->cell.cellPosition())) { p.addRect(it->coordinate.x(), it->coordinate.y(), it->cellView.cellWidth(), it->cellView.cellHeight()); if (it->cell.cellPosition() == active) { activeData = &*it; } } } painter.setPen(Qt::NoPen); if (d->highlightColor.isValid()) { painter.setBrush(QBrush(d->highlightColor)); painter.drawPath(p); } if (d->highlightMaskColor.isValid()) { QPainterPath base; base.addRect(painter.clipPath().boundingRect().adjusted(-5, -5, 5, 5)); p = base.subtracted(p); painter.setBrush(QBrush(d->highlightMaskColor)); painter.drawPath(p); } if (activeData && d->activeHighlightColor.isValid()) { painter.setBrush(QBrush(d->activeHighlightColor)); - painter.setPen(QPen(Qt::black)); + painter.setPen(QPen(Qt::black, 0)); painter.drawRect(QRectF(activeData->coordinate.x(), activeData->coordinate.y(), activeData->cellView.cellWidth(), activeData->cellView.cellHeight())); } } } void SheetView::invalidateRange(const QRect& range) { #ifdef CALLIGRA_SHEETS_MT QMutexLocker ml(&d->cacheMutex); #endif QRegion obscuredRegion; const int right = range.right(); for (int col = range.left(); col <= right; ++col) { const int bottom = range.bottom(); for (int row = range.top(); row <= bottom; ++row) { const QPoint p(col, row); if (!d->cache.contains(p)) continue; if (obscuresCells(p) || isObscured(p)) { obscuredRegion += obscuredArea(p); obscureCells(p, 0, 0); } d->cache.remove(p); } } d->cachedArea -= range; obscuredRegion &= d->cachedArea; foreach (const QRect& rect, obscuredRegion.rects()) { invalidateRange(rect); } } void SheetView::obscureCells(const QPoint &position, int numXCells, int numYCells) { #ifdef CALLIGRA_SHEETS_MT QWriteLocker(&d->obscuredLock); #endif // Start by un-obscuring cells that we might be obscuring right now const QPair pair = d->obscuredInfo->containedPair(position); if (!pair.first.isNull()) d->obscuredInfo->insert(Region(pair.first.toRect()), false); // Obscure the cells if (numXCells != 0 || numYCells != 0) d->obscuredInfo->insert(Region(position.x(), position.y(), numXCells + 1, numYCells + 1), true); QRect obscuredArea = d->obscuredInfo->usedArea(); QSize newObscuredRange(obscuredArea.right(), obscuredArea.bottom()); if (newObscuredRange != d->obscuredRange) { d->obscuredRange = newObscuredRange; emit obscuredRangeChanged(d->obscuredRange); } } QPoint SheetView::obscuringCell(const QPoint &obscuredCell) const { #ifdef CALLIGRA_SHEETS_MT QReadLocker(&d->obscuredLock); #endif const QPair pair = d->obscuredInfo->containedPair(obscuredCell); if (pair.first.isNull()) return obscuredCell; if (pair.second == false) return obscuredCell; return pair.first.toRect().topLeft(); } QSize SheetView::obscuredRange(const QPoint &obscuringCell) const { #ifdef CALLIGRA_SHEETS_MT QReadLocker(&d->obscuredLock); #endif const QPair pair = d->obscuredInfo->containedPair(obscuringCell); if (pair.first.isNull()) return QSize(0, 0); if (pair.second == false) return QSize(0, 0); // Not the master cell? if (pair.first.toRect().topLeft() != obscuringCell) return QSize(0, 0); return pair.first.toRect().size() - QSize(1, 1); } QRect SheetView::obscuredArea(const QPoint &cell) const { #ifdef CALLIGRA_SHEETS_MT QReadLocker(&d->obscuredLock); #endif const QPair pair = d->obscuredInfo->containedPair(cell); if (pair.first.isNull()) return QRect(cell, QSize(1, 1)); if (pair.second == false) return QRect(cell, QSize(1, 1)); // Not the master cell? return pair.first.toRect(); } bool SheetView::isObscured(const QPoint &cell) const { #ifdef CALLIGRA_SHEETS_MT QReadLocker(&d->obscuredLock); #endif const QPair pair = d->obscuredInfo->containedPair(cell); if (pair.first.isNull()) return false; if (pair.second == false) return false; // master cell? if (pair.first.toRect().topLeft() == cell) return false; return true; } bool SheetView::obscuresCells(const QPoint &cell) const { #ifdef CALLIGRA_SHEETS_MT QReadLocker(&d->obscuredLock); #endif const QPair pair = d->obscuredInfo->containedPair(cell); if (pair.first.isNull()) return false; if (pair.second == false) return false; // master cell? if (pair.first.toRect().topLeft() != cell) return false; return true; } QSize SheetView::totalObscuredRange() const { #ifdef CALLIGRA_SHEETS_MT QReadLocker(&d->obscuredLock); #endif return d->obscuredRange; } #ifdef CALLIGRA_SHEETS_MT CellView SheetView::defaultCellView() const #else const CellView& SheetView::defaultCellView() const #endif { return *d->defaultCellView; } void SheetView::updateAccessedCellRange(const QPoint& location) { const QSize cellRange = d->accessedCellRange.expandedTo(QSize(location.x(), location.y())); if (d->accessedCellRange != cellRange || location.isNull()) { d->accessedCellRange = cellRange; const int col = qMin(KS_colMax, cellRange.width() + 10); const int row = qMin(KS_rowMax, cellRange.height() + 10); const double width = sheet()->columnPosition(col) + sheet()->columnFormat(col)->width(); const double height = sheet()->rowPosition(row) + sheet()->rowFormats()->rowHeight(row); emit visibleSizeChanged(QSizeF(width, height)); } } CellView* SheetView::createDefaultCellView() { return new CellView(this); } CellView* SheetView::createCellView(int col, int row) { return new CellView(this, col, row); } bool SheetView::isHighlighted(const QPoint &cell) const { #ifdef CALLIGRA_SHEETS_MT QReadLocker(&d->highlightLock); #endif return d->highlightedCells.lookup(cell.x(), cell.y()); } void SheetView::setHighlighted(const QPoint &cell, bool isHighlighted) { #ifdef CALLIGRA_SHEETS_MT QWriteLocker(&d->highlightLock); #endif bool oldHadHighlights = d->highlightedCells.count() > 0; bool oldVal; if (isHighlighted) { oldVal = d->highlightedCells.insert(cell.x(), cell.y(), true); } else { oldVal = d->highlightedCells.take(cell.x(), cell.y()); } if (oldHadHighlights != (d->highlightedCells.count() > 0)) { invalidate(); } else if (oldVal != isHighlighted) { invalidateRegion(Region(cell)); } } bool SheetView::hasHighlightedCells() const { #ifdef CALLIGRA_SHEETS_MT QReadLocker(&d->highlightLock); #endif return d->highlightedCells.count() > 0; } void SheetView::clearHighlightedCells() { #ifdef CALLIGRA_SHEETS_MT QWriteLocker(&d->highlightLock); #endif d->activeHighlight = QPoint(); if (d->highlightedCells.count()) { d->highlightedCells.clear(); invalidate(); } } QPoint SheetView::activeHighlight() const { return d->activeHighlight; } void SheetView::setActiveHighlight(const QPoint &cell) { QPoint oldVal = d->activeHighlight; d->activeHighlight = cell; if (oldVal != cell) { Region r; if (!oldVal.isNull()) r.add(oldVal); if (!cell.isNull()) r.add(cell); invalidateRegion(r); } } void SheetView::setHighlightColor(const QColor &color) { d->highlightColor = color; if (hasHighlightedCells()) { invalidate(); } } void SheetView::setHighlightMaskColor(const QColor &color) { d->highlightMaskColor = color; if (hasHighlightedCells()) { invalidate(); } } void SheetView::setActiveHighlightColor(const QColor &color) { d->activeHighlightColor = color; if (hasHighlightedCells()) { invalidate(); } } diff --git a/stage/part/KPrPlaceholderShape.cpp b/stage/part/KPrPlaceholderShape.cpp index 1a98871f837..8d44c1b0079 100644 --- a/stage/part/KPrPlaceholderShape.cpp +++ b/stage/part/KPrPlaceholderShape.cpp @@ -1,117 +1,117 @@ /* This file is part of the KDE project * Copyright (C) 2008 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or ( at your option ) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KPrPlaceholderShape.h" #include #include #include #include #include #include #include "KPrPlaceholderStrategy.h" KPrPlaceholderShape::KPrPlaceholderShape() : m_strategy( 0 ) { } KPrPlaceholderShape::KPrPlaceholderShape( const QString & presentationClass ) : m_strategy( 0 ) { m_strategy = KPrPlaceholderStrategy::create( presentationClass ); } KPrPlaceholderShape::~KPrPlaceholderShape() { delete m_strategy; } void KPrPlaceholderShape::paint( QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) { QRectF rect( QPointF( 0, 0 ), size() ); if ( m_strategy ) { m_strategy->paint( painter, converter, rect, paintcontext); } else { applyConversion( painter, converter ); - QPen pen( Qt::gray ); + QPen pen(Qt::gray, 0); pen.setStyle( Qt::DashLine ); painter.setPen( pen ); painter.drawRect( rect ); } } bool KPrPlaceholderShape::loadOdf( const KoXmlElement & element, KoShapeLoadingContext &context ) { loadOdfAttributes(element, context, OdfAdditionalAttributes); #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixPresentationPlaceholder(this); #endif delete m_strategy; m_strategy = KPrPlaceholderStrategy::create( additionalAttribute( "presentation:class" ) ); if ( m_strategy == 0 ) { return false; } // first check if we can create a placeholder before we load the attributes loadOdfAttributes(element, context, OdfMandatories | OdfTransformation | OdfGeometry | OdfCommonChildElements); m_strategy->loadOdf( element, context ); return true; } void KPrPlaceholderShape::saveOdf( KoShapeSavingContext & context ) const { KoXmlWriter & writer = context.xmlWriter(); writer.startElement( "draw:frame" ); saveOdfAttributes( context, OdfAllAttributes ); if ( m_strategy ) { m_strategy->saveOdf( context ); } saveOdfCommonChildElements( context ); writer.endElement(); // draw:frame } KoShape *KPrPlaceholderShape::createShape(KoDocumentResourceManager *documentResources) { Q_ASSERT( m_strategy ); KoShape * shape = 0; if ( m_strategy ) { shape = m_strategy->createShape(documentResources); } return shape; } void KPrPlaceholderShape::initStrategy(KoDocumentResourceManager *documentResources) { Q_ASSERT( m_strategy ); if ( m_strategy ) { m_strategy->init(documentResources); } } KoShapeUserData * KPrPlaceholderShape::userData() const { Q_ASSERT( m_strategy ); return m_strategy ? m_strategy->userData() : 0; } diff --git a/stage/part/KPrPlaceholderStrategy.cpp b/stage/part/KPrPlaceholderStrategy.cpp index 54313a30612..23c56394e95 100644 --- a/stage/part/KPrPlaceholderStrategy.cpp +++ b/stage/part/KPrPlaceholderStrategy.cpp @@ -1,173 +1,173 @@ /* This file is part of the KDE project * Copyright (C) 2008 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KPrPlaceholderStrategy.h" #include "KPrPlaceholderPictureStrategy.h" #include "KPrPlaceholderTextStrategy.h" #include "StageDebug.h" #include #include #include #include #include #include #include #include #include #include static const class PlaceholderData { public: const char * m_presentationClass; const char * m_shapeId; const char * m_xmlElement; const char * m_text; } placeholderData[] = { { "title", "TextShapeID", "", I18N_NOOP( "Double click to add a title" ) }, { "outline", "TextShapeID", "", I18N_NOOP( "Double click to add an outline" ) }, { "subtitle", "TextShapeID", "", I18N_NOOP( "Double click to add a text" ) }, { "text", "TextShapeID", "", I18N_NOOP( "Double click to add a text" ) }, { "notes", "TextShapeID", "", I18N_NOOP( "Double click to add notes" ) }, /* { "date-time", "TextShapeID", "", I18N_NOOP( "Double click to add data/time" ) }, { "footer", "TextShapeID", "", I18N_NOOP( "Double click to add footer" ) }, { "header", "TextShapeID", "", I18N_NOOP( "Double click to add header" ) }, { "page-number", "TextShapeID", "", I18N_NOOP( "Double click to add page number" ) }, */ { "graphic", "PictureShape", "", I18N_NOOP( "Double click to add a picture" ) }, { "chart", "ChartShape", "", I18N_NOOP( "Double click to add a chart" ) }, { "object", "ChartShape", "", I18N_NOOP( "Double click to add an object" ) } }; static QMap s_placeholderMap; void fillPlaceholderMap() { const unsigned int numPlaceholderData = sizeof( placeholderData ) / sizeof( *placeholderData ); for ( unsigned int i = 0; i < numPlaceholderData; ++i ) { s_placeholderMap.insert( placeholderData[i].m_presentationClass, &placeholderData[i] ); } } KPrPlaceholderStrategy * KPrPlaceholderStrategy::create( const QString & presentationClass ) { if ( s_placeholderMap.isEmpty() ) { fillPlaceholderMap(); } KPrPlaceholderStrategy * strategy = 0; if ( presentationClass == "graphic" ) { strategy = new KPrPlaceholderPictureStrategy(); } // TODO make nice else if ( presentationClass == "outline" || presentationClass == "title" || presentationClass == "subtitle" ) { strategy = new KPrPlaceholderTextStrategy( presentationClass ); } else { if ( s_placeholderMap.contains( presentationClass ) ) { strategy = new KPrPlaceholderStrategy( presentationClass ); } else { warnStage << "Unsupported placeholder strategy:" << presentationClass; } } return strategy; } bool KPrPlaceholderStrategy::supported( const QString & presentationClass ) { if ( s_placeholderMap.isEmpty() ) { fillPlaceholderMap(); } return s_placeholderMap.contains( presentationClass ); } KPrPlaceholderStrategy::KPrPlaceholderStrategy( const QString & presentationClass ) : m_placeholderData( s_placeholderMap[presentationClass] ) { } KPrPlaceholderStrategy::~KPrPlaceholderStrategy() { } KoShape *KPrPlaceholderStrategy::createShape(KoDocumentResourceManager *rm) { KoShape * shape = 0; KoShapeFactoryBase * factory = KoShapeRegistry::instance()->value( m_placeholderData->m_shapeId ); Q_ASSERT( factory ); if ( factory ) { shape = factory->createDefaultShape(rm); } return shape; } void KPrPlaceholderStrategy::paint( QPainter & painter, const KoViewConverter &converter, const QRectF & rect, KoShapePaintingContext &/*paintcontext*/) { KoShape::applyConversion( painter, converter ); QPen penText( Qt::black ); painter.setPen( penText ); //painter.setFont() QTextOption options( Qt::AlignCenter ); options.setWrapMode( QTextOption::WordWrap ); painter.drawText( rect, text(), options ); - QPen pen( Qt::gray ); + QPen pen(Qt::gray, 0); //pen.setStyle( Qt::DashLine ); // endless loop //pen.setStyle( Qt::DotLine ); // endless loop //pen.setStyle( Qt::DashDotLine ); // endless loop painter.setPen( pen ); painter.drawRect( rect ); } void KPrPlaceholderStrategy::saveOdf( KoShapeSavingContext & context ) { KoXmlWriter & writer = context.xmlWriter(); writer.addCompleteElement( m_placeholderData->m_xmlElement ); } bool KPrPlaceholderStrategy::loadOdf( const KoXmlElement & element, KoShapeLoadingContext & context ) { Q_UNUSED( element ); Q_UNUSED( context ); return true; } QString KPrPlaceholderStrategy::text() const { return i18n( m_placeholderData->m_text ); } void KPrPlaceholderStrategy::init(KoDocumentResourceManager *) { } KoShapeUserData * KPrPlaceholderStrategy::userData() const { return 0; } diff --git a/stage/part/KPrPlaceholderTextStrategy.cpp b/stage/part/KPrPlaceholderTextStrategy.cpp index b42029b260d..b7884b8d75c 100644 --- a/stage/part/KPrPlaceholderTextStrategy.cpp +++ b/stage/part/KPrPlaceholderTextStrategy.cpp @@ -1,176 +1,176 @@ /* This file is part of the KDE project * Copyright (C) 2008-2009 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KPrPlaceholderTextStrategy.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KPrPlaceholderTextStrategy::KPrPlaceholderTextStrategy( const QString & presentationClass ) : KPrPlaceholderStrategy( presentationClass ) , m_textShape( 0 ) { } KPrPlaceholderTextStrategy::~KPrPlaceholderTextStrategy() { delete m_textShape; } KoShape *KPrPlaceholderTextStrategy::createShape(KoDocumentResourceManager *documentResources) { KoShape * shape = KPrPlaceholderStrategy::createShape(documentResources); if ( m_textShape ) { KoTextShapeData * data = qobject_cast( m_textShape->userData() ); KoTextShapeData * newData = qobject_cast( shape->userData() ); if ( data && newData ) { QTextCursor cursor( data->document() ); QTextCursor newCursor( newData->document() ); KoTextDocument textDocument( newData->document() ); QTextBlockFormat blockFormat( cursor.blockFormat() ); newCursor.setBlockFormat( blockFormat ); QTextCharFormat chatFormat( cursor.blockCharFormat() ); newCursor.setBlockCharFormat( chatFormat ); } } return shape; } void KPrPlaceholderTextStrategy::paint( QPainter & painter, const KoViewConverter &converter, const QRectF & rect, KoShapePaintingContext &paintcontext) { if ( m_textShape ) { painter.save(); m_textShape->setSize( rect.size() ); // this code is needed to make sure the text of the textshape is layouted before it is painted KoTextShapeData * shapeData = qobject_cast( m_textShape->userData() ); QTextDocument * document = shapeData->document(); KoTextDocumentLayout * lay = qobject_cast( document->documentLayout() ); if ( lay ) { lay->layout(); } m_textShape->paint( painter, converter, paintcontext); KoShape::applyConversion( painter, converter ); - QPen pen( Qt::gray ); + QPen pen(Qt::gray, 0); //pen.setStyle( Qt::DashLine ); // endless loop painter.setPen( pen ); painter.drawRect( rect ); painter.restore(); } else { KPrPlaceholderStrategy::paint( painter, converter, rect, paintcontext); } } void KPrPlaceholderTextStrategy::saveOdf( KoShapeSavingContext & context ) { if (m_textShape) { KoTextShapeData *shapeData = qobject_cast(m_textShape->userData()); if (shapeData) { KoStyleManager *styleManager = KoTextDocument(shapeData->document()).styleManager(); if (styleManager) { QTextDocument *document = shapeData->document(); QTextBlock block = document->begin(); QString styleName = KoTextWriter::saveParagraphStyle(block, styleManager, context); context.xmlWriter().addAttribute("draw:text-style-name", styleName); } } } KPrPlaceholderStrategy::saveOdf( context ); } bool KPrPlaceholderTextStrategy::loadOdf( const KoXmlElement & element, KoShapeLoadingContext & context ) { if (KoTextSharedLoadingData *textSharedData = dynamic_cast(context.sharedData(KOTEXT_SHARED_LOADING_ID))) { KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("TextShapeID"); Q_ASSERT(factory); delete m_textShape; m_textShape = factory->createDefaultShape(context.documentResourceManager()); KoTextShapeData *shapeData = qobject_cast(m_textShape->userData()); shapeData->document()->setUndoRedoEnabled(false); QTextDocument *document = shapeData->document(); QTextCursor cursor(document); QTextBlock block = cursor.block(); const QString styleName = element.attributeNS(KoXmlNS::presentation, "style-name"); if (!styleName.isEmpty()) { const KoXmlElement *style = context.odfLoadingContext().stylesReader().findStyle(styleName, "presentation", context.odfLoadingContext().useStylesAutoStyles()); if (style) { KoParagraphStyle paragraphStyle; paragraphStyle.loadOdf(style, context); paragraphStyle.applyStyle(block, false); // TODO t.zachmann is the false correct? } } const QString textStyleName = element.attributeNS(KoXmlNS::draw, "text-style-name"); if (!textStyleName.isEmpty()) { KoParagraphStyle *style = textSharedData->paragraphStyle(textStyleName, context.odfLoadingContext().useStylesAutoStyles()); if (style) { style->applyStyle(block, false); // TODO t.zachmann is the false correct? } } cursor.insertText(text()); shapeData->setDirty(); shapeData->document()->setUndoRedoEnabled(true); } return true; } void KPrPlaceholderTextStrategy::init(KoDocumentResourceManager *documentResources) { KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value( "TextShapeID" ); Q_ASSERT( factory ); KoProperties props; props.setProperty("text", text()); delete m_textShape; m_textShape = factory->createShape(&props, documentResources); } KoShapeUserData * KPrPlaceholderTextStrategy::userData() const { return m_textShape ? m_textShape->userData() : 0; } diff --git a/stage/part/KPrPresentationHighlightWidget.cpp b/stage/part/KPrPresentationHighlightWidget.cpp index 54bb5d54423..ee27dd31b3b 100644 --- a/stage/part/KPrPresentationHighlightWidget.cpp +++ b/stage/part/KPrPresentationHighlightWidget.cpp @@ -1,77 +1,77 @@ /* This file is part of the KDE project * Copyright (C) 2009 Alexia Allanic * Copyright (C) 2009 Jean-Nicolas Artaud * Copyright (C) 2009 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KPrPresentationHighlightWidget.h" #include #include #include #include KPrPresentationHighlightWidget::KPrPresentationHighlightWidget( KoPACanvasBase * canvas ) : KPrPresentationToolEventForwarder(canvas) , m_size( canvas->canvasWidget()->size() ) { // The focus and the track for have the mouse position every time setFocusPolicy( Qt::StrongFocus ); setMouseTracking( true ); // Size of the canvas is saved because it's used in the paintEvent resize( m_size ); m_center = QCursor::pos(); update(); } KPrPresentationHighlightWidget::~KPrPresentationHighlightWidget() { } /** paintEvent call with the update in the mouseMoveEvent */ void KPrPresentationHighlightWidget::paintEvent( QPaintEvent * event ) { Q_UNUSED(event); QPainter painter( this ); - QPen myPen; + QPen myPen(Qt::black, 0); QColor c( Qt::black ); // TODO make alpha configurable c.setAlphaF( 0.5 ); // The circle we want QPainterPath ellipse; // TODO make radius configurable ellipse.addEllipse( m_center.x() - 75, m_center.y() - 75, 150, 150 ); // All the 'background' QPainterPath myPath; myPath.addRect( 0, 0, m_size.rwidth(), m_size.rheight() ); // We draw the difference painter.setPen( myPen ); painter.fillPath( myPath.subtracted( ellipse ), c ); } /** Take the mouse position every time the mouse is moving */ void KPrPresentationHighlightWidget::mouseMoveEvent( QMouseEvent* e ) { // Save the position of the mouse m_center = e->pos(); // Update the screen : move the circle with a paint event // TODO maybe only update what has changed update(); } diff --git a/stage/part/dockers/KPrPreviewWidget.cpp b/stage/part/dockers/KPrPreviewWidget.cpp index a37929c36ab..eb69c1e7477 100644 --- a/stage/part/dockers/KPrPreviewWidget.cpp +++ b/stage/part/dockers/KPrPreviewWidget.cpp @@ -1,146 +1,147 @@ /* This file is part of the KDE project Copyright (C) 2008 Sven Langkamp This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License 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 "KPrPreviewWidget.h" #include #include #include #include #include #include "StageDebug.h" #include "KPrPage.h" #include "pageeffects/KPrPageEffect.h" #include "pageeffects/KPrPageEffectRunner.h" KPrPreviewWidget::KPrPreviewWidget( QWidget* parent ) : QWidget( parent ), m_pageEffect(0), m_pageEffectRunner(0), m_page(0) { connect( &m_timeLine, SIGNAL(valueChanged(qreal)), this, SLOT(animate()) ); } KPrPreviewWidget::~KPrPreviewWidget() { } void KPrPreviewWidget::paintEvent( QPaintEvent *event ) { QPainter p(this); if(m_pageEffectRunner && m_timeLine.state() == QTimeLine::Running) { m_pageEffectRunner->paint(p); } else if(m_page && !m_newPage.isNull()) { p.drawPixmap(rect().topLeft(), m_newPage); } else { p.drawLine(rect().topLeft(), rect().bottomRight()); p.drawLine(rect().topRight(), rect().bottomLeft()); } QPen pen(Qt::SolidLine); + pen.setWidth(0); pen.setColor(palette().color(QPalette::Mid)); p.setPen(pen); QRect framerect = rect(); framerect.setWidth(framerect.width() - 1); framerect.setHeight(framerect.height() - 1); p.drawRect(framerect); QWidget::paintEvent( event ); } void KPrPreviewWidget::resizeEvent( QResizeEvent* event ) { if(m_page) updatePixmaps(); if(m_pageEffectRunner) { m_pageEffectRunner->setOldPage( m_oldPage ); m_pageEffectRunner->setNewPage( m_newPage ); } QWidget::resizeEvent( event ); } void KPrPreviewWidget::setPageEffect( KPrPageEffect* pageEffect, KPrPage* page, KPrPage* prevpage ) { delete m_pageEffect; m_pageEffect = pageEffect; delete m_pageEffectRunner; m_pageEffectRunner = 0; m_page = page; m_prevpage = prevpage; if(m_page) { updatePixmaps(); if(m_pageEffect) { m_pageEffectRunner = new KPrPageEffectRunner( m_oldPage, m_newPage, this, m_pageEffect ); } } update(); } void KPrPreviewWidget::animate() { if ( m_pageEffectRunner ) { m_pageEffectRunner->next( m_timeLine.currentTime() ); } } void KPrPreviewWidget::runPreview() { if(m_pageEffect) { m_timeLine.setDuration( m_pageEffect->duration() ); m_timeLine.setCurrentTime( 0 ); m_timeLine.start(); } } void KPrPreviewWidget::updatePixmaps() { if(!m_page || !isVisible()) return; m_newPage = m_page->thumbnail( size() ); if(m_newPage.isNull()) return; if(m_prevpage && m_prevpage != m_page) { m_oldPage = m_prevpage->thumbnail( size() ); } else { QPixmap oldPage( size() ); oldPage.fill( QColor(Qt::black) ); m_oldPage = oldPage; } } void KPrPreviewWidget::mousePressEvent( QMouseEvent* event ) { event->accept(); runPreview(); } diff --git a/stage/part/tools/animationtool/KPrAnimationTool.cpp b/stage/part/tools/animationtool/KPrAnimationTool.cpp index 608cfea76ae..7b0b1c980c4 100644 --- a/stage/part/tools/animationtool/KPrAnimationTool.cpp +++ b/stage/part/tools/animationtool/KPrAnimationTool.cpp @@ -1,307 +1,307 @@ /* This file is part of the KDE project Copyright (C) 2008 C. Boemann Copyright (C) 2012 Paul Mendez 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 "KPrAnimationTool.h" //Qt Headers #include #include #include //KF5 Headers #include //Calligra Headers #include #include #include #include #include #include #include #include #include #include #include #include //internal Headers #include #include "KPrClickActionDocker.h" #include "KPrShapeAnimationDocker.h" #include "KPrShapeApplicationData.h" #include "animations/KPrAnimateMotion.h" #include "KPrPageData.h" const int HANDLE_DISTANCE = 10; KPrAnimationTool::KPrAnimationTool(KoCanvasBase *canvas) : KoPathTool(canvas) , m_currentMotionPathSelected(0) , m_pathShapeManager(0) , m_initializeTool(true) , m_shapeAnimationWidget(0) { } KPrAnimationTool::~KPrAnimationTool() { cleanMotionPathManager(); delete m_pathShapeManager; } void KPrAnimationTool::paint( QPainter &painter, const KoViewConverter &converter) { foreach (KoShape *shape, canvas()->shapeManager()->selection()->selectedShapes()) { painter.save(); // save the original painter transformation QTransform painterMatrix = painter.worldTransform(); - painter.setPen(Qt::green); + painter.setPen(QPen(Qt::green, 0)); // apply the shape transformation on top of the old painter transformation painter.setWorldTransform(shape->absoluteTransformation(&converter) *painterMatrix); // apply the zoom factor KoShape::applyConversion(painter, converter); // draw the shape bounding rect painter.drawRect(QRectF(QPointF(), shape->size())); painterMatrix = painter.worldTransform(); painter.restore(); } // Paint motion paths QMapIterator i(m_animateMotionMap); while (i.hasNext()) { i.next(); QSizeF pageSize = getPageSize(); if (i.value()->currentPageSize() != pageSize) { i.value()->getPath(1, pageSize); } } if (m_pathShapeManager) { m_pathShapeManager->paint(painter, converter, false); } KoPathTool::paint(painter, converter); } void KPrAnimationTool::activate(ToolActivation toolActivation, const QSet &shapes) { Q_UNUSED(toolActivation); Q_UNUSED(shapes); useCursor(m_selectCursor); repaintDecorations(); if (!m_pathShapeManager) { m_pathShapeManager = new KoShapeManager(canvas()); } if (m_initializeTool) { reloadMotionPaths(); connect((static_cast(canvas()))->koPAView()->proxyObject, SIGNAL(activePageChanged()), this, SLOT(reloadMotionPaths())); if (m_shapeAnimationWidget) { connect((static_cast(canvas()))->koPAView()->proxyObject, SIGNAL(activePageChanged()), m_shapeAnimationWidget, SLOT(slotActivePageChanged())); } } // Init parent tool if motion path shape is selected QList selectedShapes; foreach(KoShape *shape, shapes) { KoPathShape *pathShape = dynamic_cast(shape); if (shape->isEditable() && pathShape && !shape->isPrintable()) { if (m_currentMotionPathSelected == pathShape) { return; } selectedShapes.append(pathShape); } } if (!selectedShapes.isEmpty()) { KoPathTool::activate(toolActivation, shapes); } } void KPrAnimationTool::deactivate() { // Clean shape manager of motion paths cleanMotionPathManager(); disconnect((static_cast(canvas()))->koPAView()->proxyObject, SIGNAL(activePageChanged()), this, SLOT(reloadMotionPaths())); disconnect((static_cast(canvas()))->koPAView()->proxyObject, SIGNAL(activePageChanged()), m_shapeAnimationWidget, SLOT(slotActivePageChanged())); m_initializeTool = true; delete m_pathShapeManager; m_pathShapeManager = 0; KoPathTool::deactivate(); } void KPrAnimationTool::mousePressEvent( KoPointerEvent *event ) { //If no shape was click deselect all KoSelection *selection = canvas()->shapeManager()->selection(); foreach (KoShape *shape, selection->selectedShapes()) { shape->update(); } selection->deselectAll(); //Select clicked shape KoShape *shape = canvas()->shapeManager()->shapeAt(event->point); if (shape) { selection->select(shape); selection->update(); shape->update(); } // Init Path tool if motion shape is selected shape = m_pathShapeManager->shapeAt(event->point); if (KoPathShape *pathShape = dynamic_cast(shape)) { if (!pathShape->isPrintable()) { QSet shapes; shapes << pathShape; m_initializeTool = false; activate(DefaultActivation, shapes); m_currentMotionPathSelected = pathShape; } } KoPathTool::mousePressEvent(event); } void KPrAnimationTool::repaintDecorations() { if (canvas()->shapeManager()->selection()->count() > 0) { canvas()->updateCanvas(handlesSize()); } KoPathTool::repaintDecorations(); } QRectF KPrAnimationTool::handlesSize() { QRectF bound = canvas()->shapeManager()->selection()->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; } QList > KPrAnimationTool::createOptionWidgets() { KPrPageEffectDocker *effectWidget = new KPrPageEffectDocker( ); effectWidget->setView((static_cast(canvas()))->koPAView()); KPrClickActionDocker *clickActionWidget = new KPrClickActionDocker(); clickActionWidget->setView((static_cast(canvas()))->koPAView()); m_shapeAnimationWidget = new KPrShapeAnimationDocker(); m_shapeAnimationWidget->setView((static_cast(canvas()))->koPAView()); connect(m_shapeAnimationWidget, SIGNAL(shapeAnimationsChanged(KoShape*)), this, SLOT(verifyMotionPathChanged(KoShape*))); connect(m_shapeAnimationWidget, SIGNAL(motionPathAddedRemoved()), this, SLOT(reloadMotionPaths())); connect((static_cast(canvas()))->koPAView()->proxyObject, SIGNAL(activePageChanged()), m_shapeAnimationWidget, SLOT(slotActivePageChanged())); QList > widgets; effectWidget->setWindowTitle(i18n("Slide Transitions")); widgets.append(effectWidget); clickActionWidget->setWindowTitle(i18n("Shape Click Actions")); widgets.append(clickActionWidget); m_shapeAnimationWidget->setWindowTitle(i18n("Shape Animations")); widgets.append(m_shapeAnimationWidget); return widgets; } void KPrAnimationTool::initMotionPathShapes() { cleanMotionPathManager(); //Load motion paths Data KPrPageData *pageData = dynamic_cast((static_cast(canvas()))->koPAView()->activePage()); Q_ASSERT(pageData); KPrShapeAnimations *animations = &(pageData->animations()); for (int j = 0; j < animations->rowCount(); j++) { KPrShapeAnimation *anim = animations->animationByRow(j); if (anim->presetClass() == KPrShapeAnimation::MotionPath) { for (int i = 0; i < anim->animationCount(); i++) { if (KPrAnimateMotion *motion = dynamic_cast(anim->animationAt(i))) { // Load motion path QSizeF pageSize = getPageSize(); KoPathShape *path = motion->getPath(1, pageSize); m_animateMotionMap.insert(path, motion); m_shapesMap.insert(path, anim->shape()); // change stroke apparience KoShapeStroke *stroke = new KoShapeStroke(); QVector dashes; qreal space = 8; dashes << 1 << space << 3 << space; stroke->setLineStyle(Qt::DashLine, dashes); stroke->setLineWidth(1); stroke->setColor(Qt::gray); path->setStroke(stroke); addPathShape(path); } } } } } void KPrAnimationTool::addPathShape(KoPathShape *pathShape) { if (!m_pathShapeManager) { return; } pathShape->setPrintable(false); m_pathShapeManager->addShape(pathShape); } QSizeF KPrAnimationTool::getPageSize() { QSizeF pageSize = static_cast(canvas())->koPAView()->zoomController()->pageSize(); return pageSize; } void KPrAnimationTool::cleanMotionPathManager() { if (!m_pathShapeManager) { return; } foreach(KoShape *shape, m_pathShapeManager->shapes()) { m_pathShapeManager->remove(shape); } m_animateMotionMap.clear(); m_shapesMap.clear(); m_currentMotionPathSelected = 0; } void KPrAnimationTool::reloadMotionPaths() { // Remove handles m_pointSelection.clear(); m_pointSelection.setSelectedShapes(QList()); m_pointSelection.update(); // Load motion paths initMotionPathShapes(); } void KPrAnimationTool::verifyMotionPathChanged(KoShape *shape) { QMapIterator i(m_shapesMap); while (i.hasNext()) { i.next(); if (i.value() == shape) { reloadMotionPaths(); } } } diff --git a/stage/part/tools/animationtool/KPrTimeLineHeader.cpp b/stage/part/tools/animationtool/KPrTimeLineHeader.cpp index 2b2f8ad0b9e..6685f11007c 100644 --- a/stage/part/tools/animationtool/KPrTimeLineHeader.cpp +++ b/stage/part/tools/animationtool/KPrTimeLineHeader.cpp @@ -1,150 +1,150 @@ /* This file is part of the KDE project * Copyright (C) 2012 Paul Mendez * * 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 "KPrTimeLineHeader.h" #include "KPrAnimationsTimeLineView.h" #include "KPrShapeAnimations.h" #include //Qt Headers #include #include #include #include #include #include #include //Default height of the header const int HEADER_HEIGHT = 20; KPrTimeLineHeader::KPrTimeLineHeader(QWidget *parent) : QWidget(parent) { m_mainView = qobject_cast(parent); Q_ASSERT(m_mainView); setMinimumSize(minimumSizeHint()); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); } QSize KPrTimeLineHeader::minimumSizeHint() const { return QSize(m_mainView->totalWidth() * 0.25, HEADER_HEIGHT); } void KPrTimeLineHeader::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QPainter painter(this); painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); paintHeader(&painter, height()); painter.setPen(QPen(palette().button().color().darker(), 0.5)); painter.drawRect(0, 0, width(), height()); } void KPrTimeLineHeader::paintHeader(QPainter *painter, const int RowHeight) { int scroll = m_mainView->scrollArea()->horizontalScrollBar()->value(); QFontMetrics fm(font()); int minimumSize = fm.width(QString("W%1W").arg("seconds")); if (scroll < (m_mainView->totalWidth()-m_mainView->widthOfColumn(KPrShapeAnimations::StartTime) - minimumSize)) { //Seconds Header QRect rect(0, 0, m_mainView->totalWidth() - m_mainView->widthOfColumn(KPrShapeAnimations::StartTime) - scroll, RowHeight); paintHeaderItem(painter, rect, i18n("seconds")); } else if (scroll < (m_mainView->totalWidth()-m_mainView->widthOfColumn(KPrShapeAnimations::StartTime)) - 2) { QRect rect(0, 0, m_mainView->totalWidth()-m_mainView->widthOfColumn(KPrShapeAnimations::StartTime) - scroll, RowHeight); paintHeaderItem(painter, rect, QString("")); } // Paint time scale header QRect rect(m_mainView->totalWidth() - m_mainView->widthOfColumn(KPrShapeAnimations::StartTime) - scroll, 0, m_mainView->widthOfColumn(KPrShapeAnimations::StartTime), RowHeight); paintHeaderItem(painter, rect, QString()); paintTimeScale(painter, rect); } void KPrTimeLineHeader::paintHeaderItem(QPainter *painter, const QRect &rect, const QString &text) { //Paint Background QStyleOptionHeader option; option.initFrom(this); option.rect = rect; style()->drawControl(QStyle::CE_HeaderSection, &option, painter, this); //Paint Border m_mainView->paintItemBorder(painter, palette(), rect); //Paint Text painter->setPen(palette().buttonText().color()); painter->drawText(rect, text, QTextOption(Qt::AlignCenter)); } void KPrTimeLineHeader::paintTimeScale(QPainter *painter, const QRect &rect) { // set a marging const int Padding = 3; - painter->setPen(palette().windowText().color()); + painter->setPen(QPen(palette().windowText().color(), 0)); painter->setFont(QFont("", 8)); // calculate size of scale steps int totalWidth = m_mainView->widthOfColumn(KPrShapeAnimations::StartTime); int stepScale = m_mainView->stepsScale(); int stepRatio = m_mainView->numberOfSteps() / m_mainView->stepsScale(); int stepSize = totalWidth / stepRatio; // Draw lines on steps and numbers for (int x = 0; x < totalWidth-Padding; x += stepSize) { int z = x + rect.x() + Padding; if (z > 0) { // Draw numbers qreal number = x/stepSize*stepScale; painter->drawText(((z - 19) > 1 ? (z - 19) : (z - 16)), rect.y(), 38, rect.height(), Qt::AlignCenter, QString("%1").arg(number)); } } // Draw substeps const int substeps = 5; stepSize = totalWidth / (stepRatio * substeps); for (qreal x = 0; x < (totalWidth - Padding); x += stepSize) { int z = x + rect.x() + Padding; if (z > 0) { painter->drawLine(z, 1, z, 3); painter->drawLine(z, rect.height() - 4, z, rect.height() - 2); } } } bool KPrTimeLineHeader::eventFilter(QObject *target, QEvent *event) { int ExtraWidth = 10; if (QScrollArea *scrollArea = m_mainView->scrollArea()) { if (target == scrollArea && event->type() == QEvent::Resize) { if (QResizeEvent *resizeEvent = static_cast(event)) { QSize size = resizeEvent->size(); size.setHeight(sizeHint().height()); int width = size.width() + ExtraWidth - scrollArea->verticalScrollBar()->sizeHint().width(); size.setWidth(width); setMinimumSize(QSize(m_mainView->totalWidth() - m_mainView->widthOfColumn(KPrShapeAnimations::StartTime), size.height())); resize(size); } } } return QWidget::eventFilter(target, event); } diff --git a/stage/part/tools/animationtool/KPrTimeLineView.cpp b/stage/part/tools/animationtool/KPrTimeLineView.cpp index a3463182e79..9243c0d8e25 100644 --- a/stage/part/tools/animationtool/KPrTimeLineView.cpp +++ b/stage/part/tools/animationtool/KPrTimeLineView.cpp @@ -1,478 +1,478 @@ /* This file is part of the KDE project * Copyright (C) 2012 Paul Mendez * * 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 "KPrTimeLineView.h" //Stage Headers #include "KPrAnimationsTimeLineView.h" #include "tools/animationtool/KPrAnimationGroupProxyModel.h" #include "KPrShapeAnimations.h" //QT HEADERS #include #include #include #include #include #include #include #include #include #include #include #include #include #include //Default height for rows const int LINE_HEIGHT = 25; const int BAR_MARGIN = 2; const int RESIZE_RADIUS = 2; //Default invalid value for columns and rows index const int INVALID = -1; KPrTimeLineView::KPrTimeLineView(QWidget *parent) : QWidget(parent) , m_resize(false) , m_move(false) , m_resizedRow(INVALID) , startDragPos(0) , m_adjust(false) { m_mainView = qobject_cast(parent); Q_ASSERT(m_mainView); setFocusPolicy(Qt::WheelFocus); setMinimumSize(minimumSizeHint()); setMouseTracking(true); } QSize KPrTimeLineView::sizeHint() const { int rows = m_mainView->model() ? m_mainView->rowCount() : 1; return QSize(m_mainView->totalWidth(), rows * m_mainView->rowsHeight()); } QSize KPrTimeLineView::minimumSizeHint() const { int rows = m_mainView->model() ? m_mainView->rowCount() : 1; return QSize(m_mainView->totalWidth(), rows * m_mainView->rowsHeight()); } bool KPrTimeLineView::eventFilter(QObject *target, QEvent *event) { if (QScrollArea *scrollArea = m_mainView->scrollArea()) { if (target == scrollArea && event->type() == QEvent::Resize) { if (QResizeEvent *resizeEvent = static_cast(event)) { const int ExtraWidth = 5; QSize size = resizeEvent->size(); size.setHeight(sizeHint().height()); int width = size.width() - (ExtraWidth + scrollArea->verticalScrollBar()->sizeHint().width()); size.setWidth(width); resize(size); } } } return QWidget::eventFilter(target, event); } void KPrTimeLineView::keyPressEvent(QKeyEvent *event) { if (m_mainView->model()) { int row = INVALID; int column = INVALID; if (event->key() == Qt::Key_Left) { column = qMax(m_mainView->startColumn(), m_mainView->selectedColumn() - 1); } else if (event->key() == Qt::Key_Right) { column = qMin(m_mainView->endColumn(), m_mainView->selectedColumn() + 1); } else if (event->key() == Qt::Key_Up) { row = qMax(0, m_mainView->selectedRow() - 1); } else if (event->key() == Qt::Key_Down) { row = qMin(m_mainView->model()->rowCount() - 1, m_mainView->selectedRow() + 1); } row = row == INVALID ? m_mainView->selectedRow() : row; column = column == INVALID ? m_mainView->selectedColumn() : column; if (row != m_mainView->selectedRow() || column != m_mainView->selectedColumn()) { QModelIndex index = m_mainView->model()->index(row, column); m_mainView->setCurrentIndex(index); emit clicked(index); return; } } QWidget::keyPressEvent(event); } void KPrTimeLineView::mousePressEvent(QMouseEvent *event) { int row = rowAt(event->y()); int column = columnAt(event->x()); m_mainView->setSelectedRow(row); m_mainView->setSelectedColumn(column); // Request context menu if (event->button()== Qt::RightButton) { emit customContextMenuRequested(event->pos()); } // Check if user wants to move the time bars if (event->button() == Qt::LeftButton) { if (column == KPrShapeAnimations::StartTime) { m_resize = false; m_move = false; QRectF lineRect = getRowRect(row, column); QRectF endLineRect = QRectF(lineRect.right() - RESIZE_RADIUS, lineRect.top(), RESIZE_RADIUS * 2, lineRect.height()); // If the user clicks near the end of the line they could resize otherwise they move the bar. if (endLineRect.contains(event->x(), event->y())) { m_resize = true; m_resizedRow = row; setCursor(Qt::SizeHorCursor); } else { m_resize = false; m_move = false; if (lineRect.contains(event->x(), event->y())) { startDragPos = event->x() - lineRect.x(); m_move = true; m_resizedRow = row; setCursor(Qt::DragMoveCursor); } } } } emit clicked(m_mainView->model()->index(row, column)); } void KPrTimeLineView::mouseMoveEvent(QMouseEvent *event) { // Resize the bar if (m_resize) { const qreal subSteps = 0.2; int startPos = 0; for (int i = 0; i < KPrShapeAnimations::StartTime; i++) { startPos = startPos + m_mainView->widthOfColumn(i); } int row = m_resizedRow; //calculate real start qreal startOffSet = m_mainView->calculateStartOffset(row) / 1000.0; qreal start = m_mainView->model()->data(m_mainView->model()->index(row, KPrShapeAnimations::StartTime)).toInt() / 1000.0; qreal duration = m_mainView->model()->data(m_mainView->model()->index(row, KPrShapeAnimations::Duration)).toInt() / 1000.0; qreal totalSteps = m_mainView->numberOfSteps(); qreal stepSize = m_mainView->widthOfColumn( KPrShapeAnimations::StartTime) / totalSteps; if ((event->pos().x() > (startPos + startOffSet*stepSize + stepSize * start - 5)) && ((event->pos().x()) < (startPos + m_mainView->widthOfColumn( KPrShapeAnimations::StartTime)))) { qreal newLength = (event->pos().x() - startPos - stepSize * start) / (stepSize) - startOffSet; newLength = qFloor((newLength - modD(newLength, subSteps)) * 100.0) / 100.0; // update bar length m_mainView->model()->setData(m_mainView->model()->index(row, KPrShapeAnimations::Duration), newLength * 1000); emit timeValuesChanged(m_mainView->model()->index(row, KPrShapeAnimations::Duration)); m_adjust = false; if (newLength < duration) m_adjust = true; } else if ( ((event->pos().x()) > (startPos + m_mainView->widthOfColumn( KPrShapeAnimations::StartTime)))) { m_mainView->incrementScale(); m_adjust = true; } update(); } //Move the bar if (m_move) { const int Padding = 2; int startPos = 0; const qreal subSteps = 0.2; for (int i = 0; i < KPrShapeAnimations::StartTime; i++) { startPos = startPos + m_mainView->widthOfColumn(i); } int row = m_resizedRow; //calculate real start qreal startOffSet = m_mainView->calculateStartOffset(row) / 1000; qreal duration = m_mainView->model()->data(m_mainView->model()->index(row, KPrShapeAnimations::Duration)).toInt() / 1000.0; qreal start = m_mainView->model()->data(m_mainView->model()->index(row, KPrShapeAnimations::StartTime)).toInt() / 1000.0; qreal totalSteps = m_mainView->numberOfSteps(); qreal stepSize = m_mainView->widthOfColumn(KPrShapeAnimations::StartTime) / totalSteps; qreal Threshold = 0.4; if ((event->pos().x() > (startPos + startDragPos + startOffSet*stepSize)) && ((event->pos().x() + (duration * stepSize - startDragPos) + Padding * 2) < (startPos+m_mainView->widthOfColumn( KPrShapeAnimations::StartTime)))) { qreal newPos = (event->pos().x() - (startPos + startDragPos)) / (stepSize) - startOffSet; newPos = qFloor((newPos - modD(newPos, subSteps)) * 100.0) / 100.0; // update bar position m_mainView->model()->setData(m_mainView->model()->index(row, KPrShapeAnimations::StartTime), newPos * 1000); emit timeValuesChanged(m_mainView->model()->index(row, KPrShapeAnimations::StartTime)); m_adjust = false; if (newPos <= start) { m_adjust = true; } } else if (((event->pos().x() + (duration*stepSize-startDragPos) + Padding * 2) > (startPos + m_mainView->widthOfColumn( KPrShapeAnimations::StartTime)))) { m_mainView->incrementScale(); } else if (event->pos().x() < (startPos + startDragPos + startOffSet * stepSize + Threshold)) { m_mainView->changeStartLimit(row); } update(); } int row = rowAt(event->y()); int column = columnAt(event->x()); if (column == KPrShapeAnimations::StartTime) { QRectF lineRect = getRowRect(row, column); QRectF endLineRect = QRectF(lineRect.right() - RESIZE_RADIUS, lineRect.top() + BAR_MARGIN, RESIZE_RADIUS * 2, lineRect.height() - 2 * BAR_MARGIN); // If the user is near the end of the line they could resize if (endLineRect.contains(event->x(), event->y())) { setCursor(Qt::SizeHorCursor); } else { if (lineRect.contains(event->x(), event->y())) { setCursor(Qt::DragMoveCursor); } else { setCursor(Qt::ArrowCursor); } } } QWidget::mouseMoveEvent(event); } void KPrTimeLineView::mouseReleaseEvent(QMouseEvent *event) { m_resize = false; m_move = false; if (m_adjust) { m_mainView->adjustScale(); m_adjust = false; } m_mainView->animationsModel()->endTimeLineEdition(); setCursor(Qt::ArrowCursor); QWidget::mouseReleaseEvent(event); update(); } bool KPrTimeLineView::event(QEvent *event) { if (event->type() == QEvent::ToolTip) { QHelpEvent *helpEvent = static_cast(event); QModelIndex index = m_mainView->model()->index(rowAt(helpEvent->pos().y()),columnAt(helpEvent->pos().x())); if (index.isValid()) { QString text = m_mainView->model()->data(index, Qt::ToolTipRole).toString(); QToolTip::showText(helpEvent->globalPos(), text); } else { QToolTip::hideText(); event->ignore(); } return true; } return QWidget::event(event); } int KPrTimeLineView::rowAt(int ypos) { int row = static_cast(ypos / m_mainView->rowsHeight()); return row; } int KPrTimeLineView::columnAt(int xpos) { int column; if (xpos < m_mainView->widthOfColumn(KPrShapeAnimations::ShapeThumbnail)) { column = KPrShapeAnimations::ShapeThumbnail; } else if (xpos < m_mainView->widthOfColumn(KPrShapeAnimations::ShapeThumbnail) + m_mainView->widthOfColumn( KPrShapeAnimations::AnimationIcon)) { column = KPrShapeAnimations::AnimationIcon; } else { column = KPrShapeAnimations::StartTime; } return column; } QRectF KPrTimeLineView::getRowRect(const int row, const int column) { int startPos = 0; for (int i = 0; i < KPrShapeAnimations::StartTime; i++) { startPos = startPos + m_mainView->widthOfColumn(i); } int y = row * m_mainView->rowsHeight(); QRect rect(startPos, y, startPos+m_mainView->widthOfColumn(column), m_mainView->rowsHeight()); const int lineHeight = qMin(LINE_HEIGHT, rect.height()); const int yCenter = (rect.height() - lineHeight) / 2; qreal stepSize = m_mainView->widthOfColumn(KPrShapeAnimations::StartTime) / m_mainView->numberOfSteps(); qreal duration = m_mainView->model()->data(m_mainView->model()->index(row, KPrShapeAnimations::Duration)).toInt() / 1000.0; int startOffSet = m_mainView->calculateStartOffset(row); qreal start = (m_mainView->model()->data(m_mainView->model()->index(row, KPrShapeAnimations::StartTime)).toInt() + startOffSet) / 1000.0; return QRectF(rect.x() + stepSize * start, rect.y() + yCenter, stepSize * duration, lineHeight); } void KPrTimeLineView::paintEvent(QPaintEvent *event) { if (!m_mainView->model()) { return; } const int rowHeight = m_mainView->rowsHeight(); const int minY = qMax(0, event->rect().y() - rowHeight); const int maxY = minY + event->rect().height() + rowHeight; QPainter painter(this); painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); int row = minY / rowHeight; int y = row * rowHeight; int rowCount = m_mainView->rowCount(); for (; row < rowCount; ++row) { paintRow(&painter, row, y, rowHeight); y += rowHeight; if (y > maxY) { break; } } } void KPrTimeLineView::paintRow(QPainter *painter, int row, int y, const int RowHeight) { int start = 0; //Column 0 int column = KPrShapeAnimations::ShapeThumbnail; paintIconRow(painter, start, y, row, column, RowHeight - 2, RowHeight); //Column 1 column = KPrShapeAnimations::AnimationIcon; start = start + m_mainView->widthOfColumn(column - 1); paintIconRow(painter, start, y, row, column, RowHeight / 2, RowHeight); //Column 2 (6 y 7) column = KPrShapeAnimations::StartTime; start = start + m_mainView->widthOfColumn(column - 1); QRect rect(start, y, m_mainView->widthOfColumn(column), RowHeight); paintItemBackground(painter, rect, row == m_mainView->selectedRow()); paintLine(painter, row, rect, row == m_mainView->selectedRow()); } void KPrTimeLineView::paintLine(QPainter *painter, int row, const QRect &rect, bool selected) { QColor m_color = m_mainView->barColor(row); const int lineHeight = qMin(LINE_HEIGHT , rect.height()); const int vPadding = (rect.height() - lineHeight) / 2; qreal stepSize = m_mainView->widthOfColumn(KPrShapeAnimations::StartTime) / m_mainView->numberOfSteps(); qreal startOffSet = m_mainView->calculateStartOffset(row) / 1000.0; qreal duration = m_mainView->model()->data(m_mainView->model()->index(row, KPrShapeAnimations::Duration)).toInt() / 1000.0; qreal start = m_mainView->model()->data(m_mainView->model()->index(row, KPrShapeAnimations::StartTime)).toInt() / 1000.0 + startOffSet; QRectF lineRect(rect.x() + stepSize * start, rect.y() + vPadding, stepSize * duration, lineHeight); QRectF fillRect (lineRect.x(),lineRect.y() + BAR_MARGIN, lineRect.width(), lineRect.height() - BAR_MARGIN * 2); QLinearGradient s_grad(lineRect.center().x(), lineRect.top(), lineRect.center().x(), lineRect.bottom()); if (selected) { s_grad.setColorAt(0, m_color.darker(150)); s_grad.setColorAt(0.5, m_color.lighter(150)); s_grad.setColorAt(1, m_color.darker(150)); s_grad.setSpread(QGradient::ReflectSpread); painter->fillRect(fillRect, s_grad); } else { s_grad.setColorAt(0, m_color.darker(200)); s_grad.setColorAt(0.5, m_color.lighter(125)); s_grad.setColorAt(1, m_color.darker(200)); s_grad.setSpread(QGradient::ReflectSpread); painter->fillRect(fillRect, s_grad); } QRect startRect(lineRect.x(), lineRect.y(), 3, lineRect.height()); painter->fillRect(startRect, Qt::black); QRect endRect(lineRect.x() + lineRect.width(), lineRect.y(), 3, lineRect.height()); painter->fillRect(endRect, Qt::black); } void KPrTimeLineView::paintTextRow(QPainter *painter, int x, int y, int row, int column, const int RowHeight) { QRect rect(x,y,m_mainView->widthOfColumn(column), RowHeight); paintItemBackground(painter, rect, row == m_mainView->selectedRow()); painter->drawText(rect, m_mainView->model()->data(m_mainView->model()->index(row, column)).toString(), QTextOption(Qt::AlignCenter)); } void KPrTimeLineView::paintIconRow(QPainter *painter, int x, int y, int row, int column, int iconSize, const int RowHeight) { QRect rect(x,y,m_mainView->widthOfColumn(column), RowHeight); paintItemBackground(painter, rect, row == m_mainView->selectedRow()); QPixmap thumbnail = (m_mainView->model()->data(m_mainView->model()->index(row,column), Qt::DecorationRole)).value(); thumbnail.scaled(iconSize, iconSize , Qt::KeepAspectRatio); int width = 0; int height = 0; if (thumbnail.width() > thumbnail.height()) { width = iconSize; height = width * thumbnail.height() / thumbnail.width(); } else { height = iconSize; width = height * thumbnail.width() / thumbnail.height(); } qreal centerX = (m_mainView->widthOfColumn(column) - width) / 2; qreal centerY = (RowHeight - height) / 2; QRectF target(rect.x() + centerX, rect.y() + centerY, width, height); painter->save(); if (row == m_mainView->selectedRow()) { painter->setCompositionMode(QPainter::CompositionMode_ColorBurn); } painter->drawPixmap(target, thumbnail, thumbnail.rect()); painter->restore(); } double KPrTimeLineView::modD(double x, double y) { int intPart = static_cast(x / y); return x - static_cast(intPart) * y; } void KPrTimeLineView::paintItemBackground(QPainter *painter, const QRect &rect, bool selected) { QLinearGradient gradient(rect.center().x(), rect.top(), rect.center().x(), rect.bottom()); QColor color = palette().highlight().color(); gradient.setColorAt(0, color.lighter(125)); gradient.setColorAt(1, color); painter->fillRect(rect, selected ? gradient : palette().base()); m_mainView->paintItemBorder(painter, palette(), rect); - painter->setPen(selected ? palette().highlightedText().color() - : palette().windowText().color()); + painter->setPen(QPen(selected ? palette().highlightedText().color() + : palette().windowText().color(), 0)); }