diff --git a/plugins/commentshape/CommentShape.cpp b/plugins/commentshape/CommentShape.cpp index 4abe131b454..d5f2442b70b 100644 --- a/plugins/commentshape/CommentShape.cpp +++ b/plugins/commentshape/CommentShape.cpp @@ -1,189 +1,193 @@ /* 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 "CommentShape.h" #include "Globals.h" #include "InitialsCommentShape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TextShapeId "TextShapeID" CommentShape::CommentShape(KoDocumentResourceManager* resourceManager) : KoShapeContainer() , m_active(false) +, m_comment(nullptr) { KoShapeContainer::setSize(initialsBoxSize); - m_comment = KoShapeRegistry::instance()->value(TextShapeId)->createDefaultShape(resourceManager); + KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(TextShapeId); + if (factory) { + m_comment = factory->createDefaultShape(resourceManager); + } if ( !m_comment ) { // m_comment = new KoShape; KMessageBox::error( 0, i18n("The plugin needed for displaying comments is not present."), i18n("Plugin Missing") ); } if ( dynamic_cast( m_comment->userData() ) == 0 ) { KMessageBox::error( 0, i18n("The plugin needed for displaying the comment is not compatible with the current version of the comment shape."), i18n("Plugin Incompatible") ); m_comment->setUserData( new KoTextShapeData ); } m_comment->setSize(commentBoxSize); m_comment->setPosition(commentBoxPoint); m_comment->setVisible(false); QLinearGradient* gradient= new QLinearGradient(commentBoxPoint, QPointF(commentBoxPoint.x(), commentBoxPoint.y() + commentBoxSize.height())); gradient->setCoordinateMode(QGradient::ObjectBoundingMode); gradient->setColorAt(0.0, Qt::yellow); gradient->setColorAt(1.0, QColor(254, 201, 7)); m_comment->setBackground(new KoGradientBackground(gradient)); KoShapeStroke* stroke = new KoShapeStroke; stroke->setLineBrush(QBrush(Qt::black)); stroke->setLineWidth(0.5); m_comment->setStroke(stroke); addShape(m_comment); m_initials = new InitialsCommentShape(); m_initials->setSize(QSizeF(20,20)); m_initials->setSelectable(false); addShape(m_initials); } CommentShape::~CommentShape() { delete m_comment; delete m_initials; } bool CommentShape::loadOdf(const KoXmlElement& element, KoShapeLoadingContext& context) { loadOdfAttributes(element, context, OdfPosition); KoXmlElement child; forEachElement(child, element) { if(child.namespaceURI() == KoXmlNS::dc) { if(child.localName() == "creator") { m_creator = child.text(); QStringList creatorNames = m_creator.split(' '); QString initials; if(KoApplication::isLeftToRight()) { foreach(const QString& name, creatorNames) { initials += name.left(1); } } else { foreach(const QString& name, creatorNames) { initials += name.right(1); } } m_initials->setInitials(initials); } else if(child.localName() == "date") { m_date = QDate::fromString(child.text(), Qt::ISODate); } } else if(child.namespaceURI() == KoXmlNS::text && child.localName() == "p") { commentData()->document()->setHtml(child.text().replace('\n', "
")); } } return true; } void CommentShape::saveOdf(KoShapeSavingContext& context) const { KoXmlWriter& writer = context.xmlWriter(); writer.startElement("officeooo:annotation"); //TODO replace with standarized element name saveOdfAttributes(context, OdfPosition); writer.startElement("dc:creator"); writer.addTextSpan(m_creator); writer.endElement();//dc:creator writer.startElement("dc:date"); writer.addTextSpan(m_date.toString(Qt::ISODate)); writer.endElement();//dc:date writer.startElement("text:p"); writer.addTextSpan(commentData()->document()->toPlainText()); writer.endElement();//text:p writer.endElement();//officeooo:annotation } void CommentShape::paintComponent(QPainter& /*painter*/, const KoViewConverter& /*converter*/, KoShapePaintingContext &) { } void CommentShape::setSize(const QSizeF& /*size*/) { KoShapeContainer::setSize(initialsBoxSize); } void CommentShape::toogleActive() { setActive(!m_active); } bool CommentShape::isActive() const { return m_active; } void CommentShape::setActive(bool active) { m_active = active; if(!m_active) { KoShapeContainer::setSize(initialsBoxSize); } else { KoShapeContainer::setSize(wholeSize); } m_initials->setActive(m_active); m_comment->setVisible(m_active); update(); } KoTextShapeData* CommentShape::commentData() const { return qobject_cast( m_comment->userData() ); } diff --git a/plugins/defaultTools/connectionTool/ConnectionTool.cpp b/plugins/defaultTools/connectionTool/ConnectionTool.cpp index c72c4740afc..63ea7715255 100644 --- a/plugins/defaultTools/connectionTool/ConnectionTool.cpp +++ b/plugins/defaultTools/connectionTool/ConnectionTool.cpp @@ -1,960 +1,963 @@ /* 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(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(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()); + KoShape *shape = nullptr; + if (factory) { + 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 KoConnectionShape *shape = dynamic_cast(m_currentShape); if (shape) { m_currentStrategy = new KoPathConnectionPointStrategy(this, shape, 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/dockers/shapecollection/ShapeCollectionDocker.cpp b/plugins/dockers/shapecollection/ShapeCollectionDocker.cpp index 6e116ad31d5..40642b02f69 100644 --- a/plugins/dockers/shapecollection/ShapeCollectionDocker.cpp +++ b/plugins/dockers/shapecollection/ShapeCollectionDocker.cpp @@ -1,607 +1,610 @@ /* This file is part of the KDE project * Copyright (C) 2018 Dag Andersen * Copyright (C) 2008 Peter Simonsson * * 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 "ShapeCollectionDocker.h" #include "CollectionItemModel.h" #include "OdfCollectionLoader.h" #include "CollectionShapeFactory.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 //This class is needed so that the menu returns a sizehint based on the layout and not on the number (0) of menu items class CollectionMenu : public QMenu { public: CollectionMenu(QWidget * parent = 0); virtual QSize sizeHint() const; }; CollectionMenu::CollectionMenu(QWidget * parent) : QMenu(parent) { } QSize CollectionMenu::sizeHint() const { return layout()->sizeHint(); } // // ShapeCollectionDockerFactory // ShapeCollectionDockerFactory::ShapeCollectionDockerFactory() : KoDockFactoryBase() { } QString ShapeCollectionDockerFactory::id() const { return QString("ShapeCollectionDocker"); } QDockWidget* ShapeCollectionDockerFactory::createDockWidget() { ShapeCollectionDocker* docker = new ShapeCollectionDocker(); return docker; } void ShapeCollectionDocker::locationChanged(Qt::DockWidgetArea area) { resize(0,0); switch(area) { case Qt::TopDockWidgetArea: case Qt::BottomDockWidgetArea: m_spacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::MinimumExpanding); break; case Qt::LeftDockWidgetArea: case Qt::RightDockWidgetArea: m_spacer->changeSize(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); break; default: break; } m_layout->setSizeConstraint(QLayout::SetMinAndMaxSize); m_layout->invalidate(); } // // ShapeCollectionDocker // ShapeCollectionDocker::ShapeCollectionDocker(QWidget* parent) : QDockWidget(parent) { setWindowTitle(i18n("Add Shape")); QWidget* mainWidget = new QWidget(this); m_layout = new QGridLayout(mainWidget); m_layout->setMargin(0); m_layout->setHorizontalSpacing(0); m_layout->setVerticalSpacing(0); m_layout->setSizeConstraint(QLayout::SetMinAndMaxSize); setWidget(mainWidget); m_quickView = new QListView (mainWidget); m_layout->addWidget(m_quickView, 0, 0); m_quickView->setViewMode(QListView::IconMode); m_quickView->setDragDropMode(QListView::DragOnly); m_quickView->setSelectionMode(QListView::SingleSelection); m_quickView->setResizeMode(QListView::Adjust); m_quickView->setFlow(QListView::LeftToRight); m_quickView->setGridSize(QSize(40, 44)); m_quickView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_quickView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_quickView->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_quickView->setTextElideMode(Qt::ElideNone); m_quickView->setWordWrap(true); m_spacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed); m_layout->addItem(m_spacer, 1, 2); m_layout->setRowStretch(1, 1); m_layout->setColumnStretch(2, 1); connect(this, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(locationChanged(Qt::DockWidgetArea))); connect(m_quickView, SIGNAL(clicked(QModelIndex)), this, SLOT(activateShapeCreationToolFromQuick(QModelIndex))); m_moreShapes = new QToolButton(mainWidget); m_moreShapes->setText(i18n("More")); m_moreShapes->setToolButtonStyle(Qt::ToolButtonIconOnly); m_moreShapes->setIconSize(QSize(32, 32)); m_moreShapes->setIcon(koIcon("shape-choose")); m_moreShapes->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_layout->addWidget(m_moreShapes, 0, 1); m_moreShapesContainer = new CollectionMenu(mainWidget); m_moreShapes->setMenu(m_moreShapesContainer); m_moreShapes->setPopupMode(QToolButton::InstantPopup); QGridLayout *containerLayout = new QGridLayout(m_moreShapesContainer); containerLayout->setMargin(4); m_collectionChooser = new QListWidget (m_moreShapesContainer); containerLayout->addWidget(m_collectionChooser, 0, 0, 1, 2); m_collectionChooser->setViewMode(QListView::IconMode); m_collectionChooser->setSelectionMode(QListView::SingleSelection); m_collectionChooser->setResizeMode(QListView::Adjust); m_collectionChooser->setGridSize(QSize(75, 64)); m_collectionChooser->setMovement(QListView::Static); m_collectionChooser->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_collectionChooser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); connect(m_collectionChooser, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(activateShapeCollection(QListWidgetItem*))); m_addCollectionButton = new QToolButton (m_moreShapesContainer); containerLayout->addWidget(m_addCollectionButton, 1, 0); m_addCollectionButton->setIcon(koIcon("list-add")); m_addCollectionButton->setIconSize(QSize(16, 16)); m_addCollectionButton->setToolTip(i18n("Open Shape Collection")); m_addCollectionButton->setPopupMode(QToolButton::InstantPopup); m_addCollectionButton->setVisible(false); m_closeCollectionButton = new QToolButton (m_moreShapesContainer); containerLayout->addWidget(m_closeCollectionButton, 1, 1); m_closeCollectionButton->setIcon(koIcon("list-remove")); m_closeCollectionButton->setIconSize(QSize(16, 16)); m_closeCollectionButton->setToolTip(i18n("Remove Shape Collection")); m_closeCollectionButton->setVisible(false); connect(m_closeCollectionButton, SIGNAL(clicked()), this, SLOT(removeCurrentCollection())); // QT5TODO: app_shape_collections was only used with Flow in 2.x times // Now the StencilBoxDocker parses all the stencils and adds them to the KoShapeRegistry, // worse, sometimes does it before and sometimes after this constructor is run. // As temporary solution StencilBoxDocker only shows all stencils, and only those, // while all other shapes are only available by this docker. // See family "stencil" // if(! KoResourcePaths::resourceDirs("app_shape_collections").isEmpty()) // { // buildAddCollectionMenu(); // } m_collectionView = new QListView (m_moreShapesContainer); containerLayout->addWidget(m_collectionView, 0, 2, -1, 1); m_collectionView->setViewMode(QListView::IconMode); m_collectionView->setDragDropMode(QListView::DragOnly); m_collectionView->setSelectionMode(QListView::SingleSelection); m_collectionView->setResizeMode(QListView::Adjust); m_collectionView->setGridSize(QSize(48+20, 48)); m_collectionView->setFixedSize(QSize(165,345)); m_collectionView->setWordWrap(true); connect(m_collectionView, SIGNAL(clicked(QModelIndex)), this, SLOT(activateShapeCreationTool(QModelIndex))); // Load the default shapes and add them to the combobox loadDefaultShapes(); } void ShapeCollectionDocker::setCanvas(KoCanvasBase *canvas) { setEnabled(canvas != 0); } void ShapeCollectionDocker::unsetCanvas() { setEnabled(false); } inline bool operator<(const struct KoShapeTemplate &a, const struct KoShapeTemplate &b) { if (a.id != b.id) { return a.id < b.id; } return a.templateId < b.templateId; } void ShapeCollectionDocker::loadDefaultShapes() { QMap defaultList; QMap arrowList; QMap funnyList; QMap geometricList; QMap quicklist; QMap > extras; QMap extraNames; int quickCount=0; QStringList quickShapes; quickShapes << "TextShapeID" << "PictureShape" << "ChartShape" << "ArtisticText"; KConfigGroup cfg = KSharedConfig::openConfig()->group("KoShapeCollection"); quickShapes = cfg.readEntry("QuickShapes", quickShapes); foreach(const QString & id, KoShapeRegistry::instance()->keys()) { KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(id); + if (!factory) { + continue; + } // don't show hidden factories if ( factory->hidden() ) { continue; } // don't show stencil factories for now, exclusively available by StencilBox if ( factory->family() == QLatin1String("stencil") ) { continue; } bool oneAdded = false; foreach(const KoShapeTemplate & shapeTemplate, factory->templates()) { oneAdded = true; KoCollectionItem temp; temp.id = shapeTemplate.id; temp.name = shapeTemplate.name; temp.toolTip = shapeTemplate.toolTip; temp.icon = QIcon::fromTheme(shapeTemplate.iconName); temp.properties = shapeTemplate.properties; if(shapeTemplate.family == "funny") funnyList[shapeTemplate] = temp; else if(shapeTemplate.family == "arrow") arrowList[shapeTemplate] = temp; else if(shapeTemplate.family == "geometric") geometricList[shapeTemplate] = temp; else if (factory->family() == "default" || factory->family().isEmpty()) { defaultList[shapeTemplate] = temp; } else { extras[factory->family()][shapeTemplate] = temp; extraNames[factory->family()] = factory->name(); } QString id= temp.id; if (!shapeTemplate.templateId.isEmpty()) { id += '_'+shapeTemplate.templateId; } if (quickShapes.contains(id)) { quicklist[shapeTemplate] = temp; quickCount++; } } if(!oneAdded) { KoCollectionItem temp; KoShapeTemplate tempShape; temp.id = tempShape.id = factory->id(); temp.name = tempShape.name = factory->name(); temp.toolTip = tempShape.toolTip = factory->toolTip(); temp.icon = QIcon::fromTheme(factory->iconName()); tempShape.iconName = factory->iconName(); temp.properties = tempShape.properties = 0; if(factory->family() == "funny") funnyList[tempShape] = temp; else if(factory->family() == "arrow") arrowList[tempShape] = temp; else if(factory->family() == "geometric") geometricList[tempShape] = temp; else defaultList[tempShape] = temp; if(quickShapes.contains(temp.id)) { quicklist[tempShape] = temp; quickCount++; } } } CollectionItemModel* model = new CollectionItemModel(this); model->setShapeTemplateList(defaultList.values()); addCollection("default", i18n("Default"), model); model = new CollectionItemModel(this); model->setShapeTemplateList(geometricList.values()); addCollection("geometric", i18n("Geometrics"), model); QMap >::const_iterator it; for (it = extras.constBegin(); it != extras.constEnd(); ++it) { model = new CollectionItemModel(this); model->setShapeTemplateList(it.value().values()); addCollection(it.key(), extraNames[it.key()], model); } model = new CollectionItemModel(this); model->setShapeTemplateList(arrowList.values()); addCollection("arrow", i18n("Arrows"), model); model = new CollectionItemModel(this); model->setShapeTemplateList(funnyList.values()); addCollection("funny", i18n("Funny"), model); CollectionItemModel* quickModel = new CollectionItemModel(this); quickModel->setShapeTemplateList(quicklist.values()); m_quickView->setModel(quickModel); int fw = m_quickView->frameWidth(); m_quickView->setMaximumSize(QSize(quickCount*40+2*fw+1,44+2*fw+1)); m_quickView->setMinimumSize(QSize(quickCount*40+2*fw+1,44+2*fw+1)); m_collectionChooser->setMinimumSize(QSize(75+2*fw,0)); m_collectionChooser->setMaximumSize(QSize(75+2*fw,1000)); m_collectionChooser->setCurrentRow(0); activateShapeCollection(m_collectionChooser->item(0)); } void ShapeCollectionDocker::activateShapeCreationToolFromQuick(const QModelIndex& index) { m_collectionView->setFont(m_quickView->font()); if(!index.isValid()) return; KoCanvasController* canvasController = KoToolManager::instance()->activeCanvasController(); if(canvasController) { KoCreateShapesTool* tool = KoToolManager::instance()->shapeCreatorTool(canvasController->canvas()); QString id = m_quickView->model()->data(index, Qt::UserRole).toString(); const KoProperties* properties = static_cast(m_quickView->model())->properties(index); tool->setShapeId(id); tool->setShapeProperties(properties); KoToolManager::instance()->switchToolRequested(KoCreateShapesTool_ID); } m_quickView->clearSelection(); } void ShapeCollectionDocker::activateShapeCreationTool(const QModelIndex& index) { if(!index.isValid()) return; KoCanvasController* canvasController = KoToolManager::instance()->activeCanvasController(); if(canvasController) { KoCreateShapesTool* tool = KoToolManager::instance()->shapeCreatorTool(canvasController->canvas()); QString id = m_collectionView->model()->data(index, Qt::UserRole).toString(); const KoProperties* properties = static_cast(m_collectionView->model())->properties(index); tool->setShapeId(id); tool->setShapeProperties(properties); KoToolManager::instance()->switchToolRequested(KoCreateShapesTool_ID); } m_moreShapesContainer->hide(); } void ShapeCollectionDocker::activateShapeCollection(QListWidgetItem *item) { QString id = item->data(Qt::UserRole).toString(); if(m_modelMap.contains(id)) { m_collectionView->setModel(m_modelMap[id]); } else qCritical() << "Didn't find a model with id ==" << id; m_closeCollectionButton->setEnabled(id != "default"); } bool ShapeCollectionDocker::addCollection(const QString& id, const QString& title, CollectionItemModel* model) { if(m_modelMap.contains(id)) return false; m_modelMap.insert(id, model); QListWidgetItem *collectionChooserItem = new QListWidgetItem(koIcon("shape-choose"),title); collectionChooserItem->setData(Qt::UserRole, id); m_collectionChooser->addItem(collectionChooserItem); return true; } void ShapeCollectionDocker::buildAddCollectionMenu() { QStringList dirs = KoResourcePaths::resourceDirs("app_shape_collections"); QMenu* menu = new QMenu(m_addCollectionButton); m_addCollectionButton->setMenu(menu); foreach(const QString & dirName, dirs) { QDir dir(dirName); if(!dir.exists()) continue; QStringList collectionDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); foreach(const QString & collectionDirName, collectionDirs) { scanCollectionDir(dirName + collectionDirName, menu); } } } void ShapeCollectionDocker::scanCollectionDir(const QString& path, QMenu* menu) { QDir dir(path); if(!dir.exists(".directory")) return; KDesktopFile directory(dir.absoluteFilePath(".directory")); KConfigGroup dg = directory.desktopGroup(); QString name = dg.readEntry("Name"); QString icon = dg.readEntry("Icon"); QString type = dg.readEntry("X-KDE-DirType"); if(type == "subdir") { QMenu* submenu = menu->addMenu(QIcon(dir.absoluteFilePath(icon)), name); QStringList collectionDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); foreach(const QString & collectionDirName, collectionDirs) { scanCollectionDir(dir.absoluteFilePath(collectionDirName), submenu); } } else { QAction* action = menu->addAction(QIcon(dir.absoluteFilePath(icon)), name, this, SLOT(loadCollection())); action->setIconText(name); action->setData(QVariant(type + ':' + path + QDir::separator())); action->setEnabled(!m_modelMap.contains(action->data().toString())); } } void ShapeCollectionDocker::loadCollection() { QAction* action = qobject_cast(sender()); if (!action) return; QString path = action->data().toString(); int index = path.indexOf(':'); QString type = path.left(index); path = path.mid(index + 1); if(m_modelMap.contains(path)) return; CollectionItemModel* model = new CollectionItemModel(this); addCollection(path, action->iconText(), model); action->setEnabled(false); if(type == "odg-collection") { OdfCollectionLoader* loader = new OdfCollectionLoader(path, this); connect(loader, SIGNAL(loadingFailed(QString)), this, SLOT(onLoadingFailed(QString))); connect(loader, SIGNAL(loadingFinished()), this, SLOT(onLoadingFinished())); loader->load(); } } void ShapeCollectionDocker::onLoadingFailed(const QString& reason) { OdfCollectionLoader* loader = qobject_cast(sender()); if(loader) { removeCollection(loader->collectionPath()); QList shapeList = loader->shapeList(); qDeleteAll(shapeList); loader->deleteLater(); } KMessageBox::error (this, reason, i18n("Collection Error")); } void ShapeCollectionDocker::onLoadingFinished() { OdfCollectionLoader* loader = qobject_cast(sender()); if(!loader) { qWarning() << "Not called by a OdfCollectionLoader!"; return; } QList templateList; QList shapeList = loader->shapeList(); foreach(KoShape* shape, shapeList) { KoCollectionItem temp; temp.id = loader->collectionPath() + shape->name(); temp.toolTip = shape->name(); temp.icon = generateShapeIcon(shape); templateList.append(temp); CollectionShapeFactory* factory = new CollectionShapeFactory(loader->collectionPath() + shape->name(), shape); KoShapeRegistry::instance()->add(loader->collectionPath() + shape->name(), factory); } CollectionItemModel* model = m_modelMap[loader->collectionPath()]; model->setShapeTemplateList(templateList); loader->deleteLater(); //TODO m_collectionsCombo->setCurrentIndex(m_collectionsCombo->findData(loader->collectionPath())); } QIcon ShapeCollectionDocker::generateShapeIcon(KoShape* shape) { KoZoomHandler converter; qreal diffx = 30 / converter.documentToViewX(shape->size().width()); qreal diffy = 30 / converter.documentToViewY(shape->size().height()); converter.setZoom(qMin(diffx, diffy)); QPixmap pixmap(qRound(converter.documentToViewX(shape->size().width())) + 2, qRound(converter.documentToViewY(shape->size().height())) + 2); pixmap.fill(Qt::white); QPainter painter(&pixmap); painter.setRenderHint(QPainter::Antialiasing, true); painter.translate(1, 1); KoShapePaintingContext paintContext; //FIXME shape->paint(painter, converter, paintContext); painter.end(); return QIcon(pixmap); } void ShapeCollectionDocker::removeCollection(const QString& id) { //TODO m_collectionsCombo->removeItem(m_collectionsCombo->findData(id)); if(m_modelMap.contains(id)) { CollectionItemModel* model = m_modelMap[id]; QList list = model->shapeTemplateList(); foreach(const KoCollectionItem & temp, list) { KoShapeFactoryBase* factory = KoShapeRegistry::instance()->get(temp.id); KoShapeRegistry::instance()->remove(temp.id); delete factory; } m_modelMap.remove(id); delete model; } } void ShapeCollectionDocker::removeCurrentCollection() { //TODO removeCollection(m_collectionsCombo->itemData(m_collectionsCombo->currentIndex()).toString()); } diff --git a/plugins/textshape/TextTool.cpp b/plugins/textshape/TextTool.cpp index 6f52978a776..2f50a800ff3 100644 --- a/plugins/textshape/TextTool.cpp +++ b/plugins/textshape/TextTool.cpp @@ -1,3132 +1,3132 @@ /* 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 "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(QPointer editor) : KoToolSelection(0) , m_editor(editor) { } bool hasSelection() { if (!m_editor.isNull()) { return m_editor.data()->hasSelection(); } return false; } QPointer 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()) { + if (factory && 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-page-break"), 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_autoResizeAction = new QAction(koIcon("zoom-fit-best"), i18n("Auto Resize To Content"), this); addAction("auto_resize", m_autoResizeAction); m_autoResizeAction->setCheckable(true); connect(m_autoResizeAction, SIGNAL(triggered(bool)), this, SLOT(setAutoResize(bool))); 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(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(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(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 caret 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 want the selection to disappear } 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::setAutoResize(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoResize, enabled)); updateActions(); } 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); // ellipsis 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/sheets/Sheet.cpp b/sheets/Sheet.cpp index a4f0a1dccec..5524dddac3e 100644 --- a/sheets/Sheet.cpp +++ b/sheets/Sheet.cpp @@ -1,1851 +1,1854 @@ /* This file is part of the KDE project Copyright 2010 Marijn Kruisselbrink Copyright 2007 Stefan Nikolaus Copyright 1998,1999 Torben Weis Copyright 1999-2007 The KSpread Team 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 "Sheet.h" #include #include #include #include #include #include #include "CellStorage.h" #include "Cluster.h" #include "Damages.h" #include "DependencyManager.h" #include "DocBase.h" #include "FormulaStorage.h" #include "HeaderFooter.h" #include "LoadingInfo.h" #include "Map.h" #include "NamedAreaManager.h" #include "PrintSettings.h" #include "RecalcManager.h" #include "RowColumnFormat.h" #include "RowFormatStorage.h" #include "ShapeApplicationData.h" #include "SheetPrint.h" #include "SheetModel.h" #include "StyleStorage.h" #include "Validity.h" #include "ValueConverter.h" #include "ValueStorage.h" #include "database/Filter.h" namespace Calligra { namespace Sheets { static QString createObjectName(const QString &sheetName) { QString objectName; for (int i = 0; i < sheetName.count(); ++i) { if (sheetName[i].isLetterOrNumber() || sheetName[i] == '_') objectName.append(sheetName[i]); else objectName.append('_'); } return objectName; } class Q_DECL_HIDDEN Sheet::Private { public: Private(Sheet* sheet) : rows(sheet) {} Map* workbook; SheetModel *model; QString name; Qt::LayoutDirection layoutDirection; // true if sheet is hidden bool hide; bool showGrid; bool showFormula; bool showFormulaIndicator; bool showCommentIndicator; bool autoCalc; bool lcMode; bool showColumnNumber; bool hideZero; bool firstLetterUpper; // clusters to hold objects CellStorage* cellStorage; RowFormatStorage rows; ColumnCluster columns; QList shapes; // hold the print object SheetPrint* print; // Indicates whether the sheet should paint the page breaks. // Doing so costs some time, so by default it should be turned off. bool showPageOutline; // Max range of canvas in x and y direction. // Depends on KS_colMax/KS_rowMax and the width/height of all columns/rows QSizeF documentSize; QImage backgroundImage; Sheet::BackgroundImageProperties backgroundProperties; }; Sheet::Sheet(Map* map, const QString &sheetName) : KoShapeUserData(map) , KoShapeBasedDocumentBase() , d(new Private(this)) { d->workbook = map; if (map->doc()) { resourceManager()->setUndoStack(map->doc()->undoStack()); QVariant variant; variant.setValue(map->doc()->sheetAccessModel()); resourceManager()->setResource(::Sheets::CanvasResource::AccessModel, variant); } d->model = new SheetModel(this); d->layoutDirection = QApplication::layoutDirection(); d->name = sheetName; // Set a valid object name, so that we can offer scripting. setObjectName(createObjectName(d->name)); d->cellStorage = new CellStorage(this); d->columns.setAutoDelete(true); d->documentSize = QSizeF(KS_colMax * d->workbook->defaultColumnFormat()->width(), KS_rowMax * d->workbook->defaultRowFormat()->height()); d->hide = false; d->showGrid = true; d->showFormula = false; d->showFormulaIndicator = false; d->showCommentIndicator = true; d->showPageOutline = false; d->lcMode = false; d->showColumnNumber = false; d->hideZero = false; d->firstLetterUpper = false; d->autoCalc = true; d->print = new SheetPrint(this); // document size changes always trigger a visible size change connect(this, SIGNAL(documentSizeChanged(QSizeF)), SIGNAL(visibleSizeChanged())); // CellStorage connections connect(d->cellStorage, SIGNAL(insertNamedArea(Region,QString)), d->workbook->namedAreaManager(), SLOT(insert(Region,QString))); connect(d->cellStorage, SIGNAL(namedAreaRemoved(QString)), d->workbook->namedAreaManager(), SLOT(remove(QString))); } Sheet::Sheet(const Sheet &other) : KoShapeUserData(other.d->workbook) , KoShapeBasedDocumentBase() , ProtectableObject(other) , d(new Private(this)) { d->workbook = other.d->workbook; d->model = new SheetModel(this); // create a unique name int i = 1; do d->name = other.d->name + QString("_%1").arg(i++); while (d->workbook->findSheet(d->name)); // Set a valid object name, so that we can offer scripting. setObjectName(createObjectName(d->name)); d->layoutDirection = other.d->layoutDirection; d->hide = other.d->hide; d->showGrid = other.d->showGrid; d->showFormula = other.d->showFormula; d->showFormulaIndicator = other.d->showFormulaIndicator; d->showCommentIndicator = other.d->showCommentIndicator; d->autoCalc = other.d->autoCalc; d->lcMode = other.d->lcMode; d->showColumnNumber = other.d->showColumnNumber; d->hideZero = other.d->hideZero; d->firstLetterUpper = other.d->firstLetterUpper; d->cellStorage = new CellStorage(*other.d->cellStorage, this); d->rows = other.d->rows; d->columns = other.d->columns; // flake #if 0 // CALLIGRA_SHEETS_WIP_COPY_SHEET_(SHAPES) //FIXME This does not work as copySettings does not work. Also createDefaultShapeAndInit without the correct settings can not work //I think this should use ODF load/save for copying KoShape* shape; const QList shapes = other.d->shapes; for (int i = 0; i < shapes.count(); ++i) { - shape = KoShapeRegistry::instance()->value(shapes[i]->shapeId())->createDefaultShapeAndInit(0); - shape->copySettings(shapes[i]); - addShape(shape); + KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shapes[i]->shapeId()); + if (factory) { + shape = factory->createDefaultShapeAndInit(0); + shape->copySettings(shapes[i]); + addShape(shape); + } } #endif // CALLIGRA_SHEETS_WIP_COPY_SHEET_(SHAPES) d->print = new SheetPrint(this); // FIXME = new SheetPrint(*other.d->print); d->showPageOutline = other.d->showPageOutline; d->documentSize = other.d->documentSize; } Sheet::~Sheet() { //Disable automatic recalculation of dependencies on this sheet to prevent crashes //in certain situations: // //For example, suppose a cell in SheetB depends upon a cell in SheetA. If the cell in SheetB is emptied //after SheetA has already been deleted, the program would try to remove dependencies from the cell in SheetA //causing a crash. setAutoCalculationEnabled(false); delete d->print; delete d->cellStorage; qDeleteAll(d->shapes); delete d; } QAbstractItemModel* Sheet::model() const { return d->model; } QString Sheet::sheetName() const { return d->name; } Map* Sheet::map() const { return d->workbook; } DocBase* Sheet::doc() const { return d->workbook->doc(); } void Sheet::addShape(KoShape* shape) { if (!shape) return; d->shapes.append(shape); shape->setApplicationData(new ShapeApplicationData()); emit shapeAdded(this, shape); } void Sheet::removeShape(KoShape* shape) { if (!shape) return; d->shapes.removeAll(shape); emit shapeRemoved(this, shape); } void Sheet::deleteShapes() { qDeleteAll(d->shapes); d->shapes.clear(); } KoDocumentResourceManager* Sheet::resourceManager() const { return map()->resourceManager(); } QList Sheet::shapes() const { return d->shapes; } Qt::LayoutDirection Sheet::layoutDirection() const { return d->layoutDirection; } void Sheet::setLayoutDirection(Qt::LayoutDirection dir) { d->layoutDirection = dir; } bool Sheet::isHidden() const { return d->hide; } void Sheet::setHidden(bool hidden) { d->hide = hidden; } bool Sheet::getShowGrid() const { return d->showGrid; } void Sheet::setShowGrid(bool _showGrid) { d->showGrid = _showGrid; } bool Sheet::getShowFormula() const { return d->showFormula; } void Sheet::setShowFormula(bool _showFormula) { d->showFormula = _showFormula; } bool Sheet::getShowFormulaIndicator() const { return d->showFormulaIndicator; } void Sheet::setShowFormulaIndicator(bool _showFormulaIndicator) { d->showFormulaIndicator = _showFormulaIndicator; } bool Sheet::getShowCommentIndicator() const { return d->showCommentIndicator; } void Sheet::setShowCommentIndicator(bool _indic) { d->showCommentIndicator = _indic; } bool Sheet::getLcMode() const { return d->lcMode; } void Sheet::setLcMode(bool _lcMode) { d->lcMode = _lcMode; } bool Sheet::isAutoCalculationEnabled() const { return d->autoCalc; } void Sheet::setAutoCalculationEnabled(bool enable) { //Avoid possible recalculation of dependencies if the auto calc setting hasn't changed if (d->autoCalc == enable) return; d->autoCalc = enable; //If enabling automatic calculation, make sure that the dependencies are up-to-date if (enable == true) { map()->dependencyManager()->addSheet(this); map()->recalcManager()->recalcSheet(this); } else { map()->dependencyManager()->removeSheet(this); } } bool Sheet::getShowColumnNumber() const { return d->showColumnNumber; } void Sheet::setShowColumnNumber(bool _showColumnNumber) { d->showColumnNumber = _showColumnNumber; } bool Sheet::getHideZero() const { return d->hideZero; } void Sheet::setHideZero(bool _hideZero) { d->hideZero = _hideZero; } bool Sheet::getFirstLetterUpper() const { return d->firstLetterUpper; } void Sheet::setFirstLetterUpper(bool _firstUpper) { d->firstLetterUpper = _firstUpper; } bool Sheet::isShowPageOutline() const { return d->showPageOutline; } const ColumnFormat* Sheet::columnFormat(int _column) const { const ColumnFormat *p = d->columns.lookup(_column); if (p != 0) return p; return map()->defaultColumnFormat(); } // needed in loading code ColumnFormat *Sheet::nextColumn(int col) const { return d->columns.next(col); } CellStorage* Sheet::cellStorage() const { return d->cellStorage; } const CommentStorage* Sheet::commentStorage() const { return d->cellStorage->commentStorage(); } const ConditionsStorage* Sheet::conditionsStorage() const { return d->cellStorage->conditionsStorage(); } const FormulaStorage* Sheet::formulaStorage() const { return d->cellStorage->formulaStorage(); } const FusionStorage* Sheet::fusionStorage() const { return d->cellStorage->fusionStorage(); } const LinkStorage* Sheet::linkStorage() const { return d->cellStorage->linkStorage(); } const StyleStorage* Sheet::styleStorage() const { return d->cellStorage->styleStorage(); } const ValidityStorage* Sheet::validityStorage() const { return d->cellStorage->validityStorage(); } const ValueStorage* Sheet::valueStorage() const { return d->cellStorage->valueStorage(); } SheetPrint* Sheet::print() const { return d->print; } PrintSettings* Sheet::printSettings() const { return d->print->settings(); } void Sheet::setPrintSettings(const PrintSettings &settings) { d->print->setSettings(settings); // Repaint, if page borders are shown and this is the active sheet. if (isShowPageOutline()) { // Just repaint everything visible; no need to invalidate the visual cache. map()->addDamage(new SheetDamage(this, SheetDamage::ContentChanged)); } } HeaderFooter *Sheet::headerFooter() const { return d->print->headerFooter(); } QSizeF Sheet::documentSize() const { return d->documentSize; } void Sheet::adjustDocumentWidth(double deltaWidth) { d->documentSize.rwidth() += deltaWidth; emit documentSizeChanged(d->documentSize); } void Sheet::adjustDocumentHeight(double deltaHeight) { d->documentSize.rheight() += deltaHeight; emit documentSizeChanged(d->documentSize); } void Sheet::adjustCellAnchoredShapesX(qreal minX, qreal maxX, qreal delta) { foreach (KoShape* s, d->shapes) { if (dynamic_cast(s->applicationData())->isAnchoredToCell()) { if (s->position().x() >= minX && s->position().x() < maxX) { QPointF p = s->position(); p.setX(qMax(minX, p.x() + delta)); s->setPosition(p); } } } } void Sheet::adjustCellAnchoredShapesX(qreal delta, int firstCol, int lastCol) { adjustCellAnchoredShapesX(columnPosition(firstCol), columnPosition(lastCol+1), delta); } void Sheet::adjustCellAnchoredShapesY(qreal minY, qreal maxY, qreal delta) { foreach (KoShape* s, d->shapes) { if (dynamic_cast(s->applicationData())->isAnchoredToCell()) { if (s->position().y() >= minY && s->position().y() < maxY) { QPointF p = s->position(); p.setY(qMax(minY, p.y() + delta)); s->setPosition(p); } } } } void Sheet::adjustCellAnchoredShapesY(qreal delta, int firstRow, int lastRow) { adjustCellAnchoredShapesY(rowPosition(firstRow), rowPosition(lastRow+1), delta); } int Sheet::leftColumn(qreal _xpos, qreal &_left) const { _left = 0.0; int col = 1; double x = columnFormat(col)->visibleWidth(); while (x < _xpos && col < KS_colMax) { _left += columnFormat(col)->visibleWidth(); x += columnFormat(++col)->visibleWidth(); } return col; } int Sheet::rightColumn(double _xpos) const { int col = 1; double x = columnFormat(col)->visibleWidth(); while (x <= _xpos && col < KS_colMax) x += columnFormat(++col)->visibleWidth(); return col; } int Sheet::topRow(qreal _ypos, qreal & _top) const { qreal top; int row = rowFormats()->rowForPosition(_ypos, &top); _top = top; return row; } int Sheet::bottomRow(double _ypos) const { return rowFormats()->rowForPosition(_ypos+1e-9); } QRectF Sheet::cellCoordinatesToDocument(const QRect& cellRange) const { // TODO Stefan: Rewrite to save some iterations over the columns/rows. QRectF rect; rect.setLeft(columnPosition(cellRange.left())); rect.setRight(columnPosition(cellRange.right()) + columnFormat(cellRange.right())->width()); rect.setTop(rowPosition(cellRange.top())); rect.setBottom(rowPosition(cellRange.bottom()) + rowFormats()->rowHeight(cellRange.bottom())); return rect; } QRect Sheet::documentToCellCoordinates(const QRectF &area) const { double width = 0.0; int left = 0; while (width <= area.left()) width += columnFormat(++left)->visibleWidth(); int right = left; while (width < area.right()) width += columnFormat(++right)->visibleWidth(); int top = rowFormats()->rowForPosition(area.top()); int bottom = rowFormats()->rowForPosition(area.bottom()); return QRect(left, top, right - left + 1, bottom - top + 1); } double Sheet::columnPosition(int _col) const { const int max = qMin(_col, KS_colMax); double x = 0.0; for (int col = 1; col < max; ++col) x += columnFormat(col)->visibleWidth(); return x; } double Sheet::rowPosition(int _row) const { const int max = qMin(_row, KS_rowMax+1); return rowFormats()->totalVisibleRowHeight(1, max-1); } ColumnFormat* Sheet::firstCol() const { return d->columns.first(); } ColumnFormat* Sheet::nonDefaultColumnFormat(int _column, bool force_creation) { Q_ASSERT(_column >= 1 && _column <= KS_colMax); ColumnFormat *p = d->columns.lookup(_column); if (p != 0 || !force_creation) return p; p = new ColumnFormat(*map()->defaultColumnFormat()); p->setSheet(this); p->setColumn(_column); d->columns.insertElement(p, _column); return p; } void Sheet::changeCellTabName(QString const & old_name, QString const & new_name) { for (int c = 0; c < formulaStorage()->count(); ++c) { if (formulaStorage()->data(c).expression().contains(old_name)) { int nb = formulaStorage()->data(c).expression().count(old_name + '!'); QString tmp = old_name + '!'; int len = tmp.length(); tmp = formulaStorage()->data(c).expression(); for (int i = 0; i < nb; ++i) { int pos = tmp.indexOf(old_name + '!'); tmp.replace(pos, len, new_name + '!'); } Cell cell(this, formulaStorage()->col(c), formulaStorage()->row(c)); Formula formula(this, cell); formula.setExpression(tmp); cell.setFormula(formula); cell.makeFormula(); } } } void Sheet::insertShiftRight(const QRect& rect) { foreach(Sheet* sheet, map()->sheetList()) { for (int i = rect.top(); i <= rect.bottom(); ++i) { sheet->changeNameCellRef(QPoint(rect.left(), i), false, Sheet::ColumnInsert, sheetName(), rect.right() - rect.left() + 1); } } } void Sheet::insertShiftDown(const QRect& rect) { foreach(Sheet* sheet, map()->sheetList()) { for (int i = rect.left(); i <= rect.right(); ++i) { sheet->changeNameCellRef(QPoint(i, rect.top()), false, Sheet::RowInsert, sheetName(), rect.bottom() - rect.top() + 1); } } } void Sheet::removeShiftUp(const QRect& rect) { foreach(Sheet* sheet, map()->sheetList()) { for (int i = rect.left(); i <= rect.right(); ++i) { sheet->changeNameCellRef(QPoint(i, rect.top()), false, Sheet::RowRemove, sheetName(), rect.bottom() - rect.top() + 1); } } } void Sheet::removeShiftLeft(const QRect& rect) { foreach(Sheet* sheet, map()->sheetList()) { for (int i = rect.top(); i <= rect.bottom(); ++i) { sheet->changeNameCellRef(QPoint(rect.left(), i), false, Sheet::ColumnRemove, sheetName(), rect.right() - rect.left() + 1); } } } void Sheet::insertColumns(int col, int number) { double deltaWidth = 0.0; for (int i = 0; i < number; i++) { deltaWidth -= columnFormat(KS_colMax)->width(); d->columns.insertColumn(col); deltaWidth += columnFormat(col + i)->width(); } // Adjust document width (plus widths of new columns; minus widths of removed columns). adjustDocumentWidth(deltaWidth); foreach(Sheet* sheet, map()->sheetList()) { sheet->changeNameCellRef(QPoint(col, 1), true, Sheet::ColumnInsert, sheetName(), number); } //update print settings d->print->insertColumn(col, number); } void Sheet::insertRows(int row, int number) { d->rows.insertRows(row, number); foreach(Sheet* sheet, map()->sheetList()) { sheet->changeNameCellRef(QPoint(1, row), true, Sheet::RowInsert, sheetName(), number); } //update print settings d->print->insertRow(row, number); } void Sheet::removeColumns(int col, int number) { double deltaWidth = 0.0; for (int i = 0; i < number; ++i) { deltaWidth -= columnFormat(col)->width(); d->columns.removeColumn(col); deltaWidth += columnFormat(KS_colMax)->width(); } // Adjust document width (plus widths of new columns; minus widths of removed columns). adjustDocumentWidth(deltaWidth); foreach(Sheet* sheet, map()->sheetList()) { sheet->changeNameCellRef(QPoint(col, 1), true, Sheet::ColumnRemove, sheetName(), number); } //update print settings d->print->removeColumn(col, number); } void Sheet::removeRows(int row, int number) { d->rows.removeRows(row, number); foreach(Sheet* sheet, map()->sheetList()) { sheet->changeNameCellRef(QPoint(1, row), true, Sheet::RowRemove, sheetName(), number); } //update print settings d->print->removeRow(row, number); } QString Sheet::changeNameCellRefHelper(const QPoint& pos, bool fullRowOrColumn, ChangeRef ref, int nbCol, const QPoint& point, bool isColumnFixed, bool isRowFixed) { QString newPoint; int col = point.x(); int row = point.y(); // update column if (isColumnFixed) newPoint.append('$'); if (ref == ColumnInsert && col + nbCol <= KS_colMax && col >= pos.x() && // Column after the new one : +1 (fullRowOrColumn || row == pos.y())) { // All rows or just one newPoint += Cell::columnName(col + nbCol); } else if (ref == ColumnRemove && col > pos.x() && // Column after the deleted one : -1 (fullRowOrColumn || row == pos.y())) { // All rows or just one newPoint += Cell::columnName(col - nbCol); } else newPoint += Cell::columnName(col); // Update row if (isRowFixed) newPoint.append('$'); if (ref == RowInsert && row + nbCol <= KS_rowMax && row >= pos.y() && // Row after the new one : +1 (fullRowOrColumn || col == pos.x())) { // All columns or just one newPoint += QString::number(row + nbCol); } else if (ref == RowRemove && row > pos.y() && // Row after the deleted one : -1 (fullRowOrColumn || col == pos.x())) { // All columns or just one newPoint += QString::number(row - nbCol); } else newPoint += QString::number(row); if (((ref == ColumnRemove && (col >= pos.x() && col < pos.x() + nbCol) // Column is the deleted one : error && (fullRowOrColumn || row == pos.y())) || (ref == RowRemove && (row >= pos.y() && row < pos.y() + nbCol) // Row is the deleted one : error && (fullRowOrColumn || col == pos.x())) || (ref == ColumnInsert && col + nbCol > KS_colMax && col >= pos.x() // Column after the new one : +1 && (fullRowOrColumn || row == pos.y())) || (ref == RowInsert && row + nbCol > KS_rowMax && row >= pos.y() // Row after the new one : +1 && (fullRowOrColumn || col == pos.x())))) { newPoint = '#' + i18n("Dependency") + '!'; } return newPoint; } QString Sheet::changeNameCellRefHelper(const QPoint& pos, const QRect& rect, bool fullRowOrColumn, ChangeRef ref, int nbCol, const QPoint& point, bool isColumnFixed, bool isRowFixed) { const bool isFirstColumn = pos.x() == rect.left(); const bool isLastColumn = pos.x() == rect.right(); const bool isFirstRow = pos.y() == rect.top(); const bool isLastRow = pos.y() == rect.bottom(); QString newPoint; int col = point.x(); int row = point.y(); // update column if (isColumnFixed) newPoint.append('$'); if (ref == ColumnInsert && col + nbCol <= KS_colMax && col >= pos.x() && // Column after the new one : +1 (fullRowOrColumn || row == pos.y())) { // All rows or just one newPoint += Cell::columnName(col + nbCol); } else if (ref == ColumnRemove && (col > pos.x() || (col == pos.x() && isLastColumn)) && // Column after the deleted one : -1 (fullRowOrColumn || row == pos.y())) { // All rows or just one newPoint += Cell::columnName(col - nbCol); } else newPoint += Cell::columnName(col); // Update row if (isRowFixed) newPoint.append('$'); if (ref == RowInsert && row + nbCol <= KS_rowMax && row >= pos.y() && // Row after the new one : +1 (fullRowOrColumn || col == pos.x())) { // All columns or just one newPoint += QString::number(row + nbCol); } else if (ref == RowRemove && (row > pos.y() || (row == pos.y() && isLastRow)) && // Row after the deleted one : -1 (fullRowOrColumn || col == pos.x())) { // All columns or just one newPoint += QString::number(row - nbCol); } else newPoint += QString::number(row); if (((ref == ColumnRemove && col == pos.x() // Column is the deleted one : error && (fullRowOrColumn || row == pos.y()) && (isFirstColumn && isLastColumn)) || (ref == RowRemove && row == pos.y() // Row is the deleted one : error && (fullRowOrColumn || col == pos.x()) && (isFirstRow && isLastRow)) || (ref == ColumnInsert && col + nbCol > KS_colMax && col >= pos.x() // Column after the new one : +1 && (fullRowOrColumn || row == pos.y())) || (ref == RowInsert && row + nbCol > KS_rowMax && row >= pos.y() // Row after the new one : +1 && (fullRowOrColumn || col == pos.x())))) { newPoint = '#' + i18n("Dependency") + '!'; } return newPoint; } void Sheet::changeNameCellRef(const QPoint& pos, bool fullRowOrColumn, ChangeRef ref, const QString& tabname, int nbCol) { for (int c = 0; c < formulaStorage()->count(); ++c) { QString newText('='); const Tokens tokens = formulaStorage()->data(c).tokens(); for (int t = 0; t < tokens.count(); ++t) { const Token token = tokens[t]; switch (token.type()) { case Token::Cell: case Token::Range: { if (map()->namedAreaManager()->contains(token.text())) { newText.append(token.text()); // simply keep the area name break; } const Region region(token.text(), map()); if (!region.isValid() || !region.isContiguous()) { newText.append(token.text()); break; } if (!region.firstSheet() && tabname != sheetName()) { // nothing to do here newText.append(token.text()); break; } // actually only one element in here, but we need extended access to the element Region::ConstIterator end(region.constEnd()); for (Region::ConstIterator it(region.constBegin()); it != end; ++it) { Region::Element* element = (*it); if (element->type() == Region::Element::Point) { if (element->sheet()) newText.append(element->sheet()->sheetName() + '!'); QString newPoint = changeNameCellRefHelper(pos, fullRowOrColumn, ref, nbCol, element->rect().topLeft(), element->isColumnFixed(), element->isRowFixed()); newText.append(newPoint); } else { // (element->type() == Region::Element::Range) if (element->sheet()) newText.append(element->sheet()->sheetName() + '!'); QString newPoint; newPoint = changeNameCellRefHelper(pos, element->rect(), fullRowOrColumn, ref, nbCol, element->rect().topLeft(), element->isColumnFixed(), element->isRowFixed()); newText.append(newPoint + ':'); newPoint = changeNameCellRefHelper(pos, element->rect(), fullRowOrColumn, ref, nbCol, element->rect().bottomRight(), element->isColumnFixed(), element->isRowFixed()); newText.append(newPoint); } } break; } default: { newText.append(token.text()); break; } } } Cell cell(this, formulaStorage()->col(c), formulaStorage()->row(c)); Formula formula(this, cell); formula.setExpression(newText); cell.setFormula(formula); } } // helper function for Sheet::areaIsEmpty bool Sheet::cellIsEmpty(const Cell& cell, TestType _type) { if (!cell.isPartOfMerged()) { switch (_type) { case Text : if (!cell.userInput().isEmpty()) return false; break; case Validity: if (!cell.validity().isEmpty()) return false; break; case Comment: if (!cell.comment().isEmpty()) return false; break; case ConditionalCellAttribute: if (cell.conditions().conditionList().count() > 0) return false; break; } } return true; } // TODO: convert into a manipulator, similar to the Dilation one bool Sheet::areaIsEmpty(const Region& region, TestType _type) { Region::ConstIterator endOfList = region.constEnd(); for (Region::ConstIterator it = region.constBegin(); it != endOfList; ++it) { QRect range = (*it)->rect(); // Complete rows selected ? if ((*it)->isRow()) { for (int row = range.top(); row <= range.bottom(); ++row) { Cell cell = d->cellStorage->firstInRow(row); while (!cell.isNull()) { if (!cellIsEmpty(cell, _type)) return false; cell = d->cellStorage->nextInRow(cell.column(), row); } } } // Complete columns selected ? else if ((*it)->isColumn()) { for (int col = range.left(); col <= range.right(); ++col) { Cell cell = d->cellStorage->firstInColumn(col); while (!cell.isNull()) { if (!cellIsEmpty(cell, _type)) return false; cell = d->cellStorage->nextInColumn(col, cell.row()); } } } else { Cell cell; int right = range.right(); int bottom = range.bottom(); for (int x = range.left(); x <= right; ++x) for (int y = range.top(); y <= bottom; ++y) { cell = Cell(this, x, y); if (!cellIsEmpty(cell, _type)) return false; } } } return true; } QDomElement Sheet::saveXML(QDomDocument& dd) { QDomElement sheet = dd.createElement("table"); // backward compatibility QString sheetName; for (int i = 0; i < d->name.count(); ++i) { if (d->name[i].isLetterOrNumber() || d->name[i] == ' ' || d->name[i] == '.') sheetName.append(d->name[i]); else sheetName.append('_'); } sheet.setAttribute("name", sheetName); //Laurent: for oasis format I think that we must use style:direction... sheet.setAttribute("layoutDirection", (layoutDirection() == Qt::RightToLeft) ? "rtl" : "ltr"); sheet.setAttribute("columnnumber", QString::number((int)getShowColumnNumber())); sheet.setAttribute("borders", QString::number((int)isShowPageOutline())); sheet.setAttribute("hide", QString::number((int)isHidden())); sheet.setAttribute("hidezero", QString::number((int)getHideZero())); sheet.setAttribute("firstletterupper", QString::number((int)getFirstLetterUpper())); sheet.setAttribute("grid", QString::number((int)getShowGrid())); sheet.setAttribute("printGrid", QString::number((int)print()->settings()->printGrid())); sheet.setAttribute("printCommentIndicator", QString::number((int)print()->settings()->printCommentIndicator())); sheet.setAttribute("printFormulaIndicator", QString::number((int)print()->settings()->printFormulaIndicator())); sheet.setAttribute("showFormula", QString::number((int)getShowFormula())); sheet.setAttribute("showFormulaIndicator", QString::number((int)getShowFormulaIndicator())); sheet.setAttribute("showCommentIndicator", QString::number((int)getShowCommentIndicator())); sheet.setAttribute("lcmode", QString::number((int)getLcMode())); sheet.setAttribute("autoCalc", QString::number((int)isAutoCalculationEnabled())); sheet.setAttribute("borders1.2", "1"); QByteArray pwd; password(pwd); if (!pwd.isNull()) { if (pwd.size() > 0) { QByteArray str = KCodecs::base64Encode(pwd); sheet.setAttribute("protected", QString(str.data())); } else sheet.setAttribute("protected", ""); } // paper parameters QDomElement paper = dd.createElement("paper"); paper.setAttribute("format", printSettings()->paperFormatString()); paper.setAttribute("orientation", printSettings()->orientationString()); sheet.appendChild(paper); QDomElement borders = dd.createElement("borders"); KoPageLayout pageLayout = print()->settings()->pageLayout(); borders.setAttribute("left", QString::number(pageLayout.leftMargin)); borders.setAttribute("top", QString::number(pageLayout.topMargin)); borders.setAttribute("right", QString::number(pageLayout.rightMargin)); borders.setAttribute("bottom", QString::number(pageLayout.bottomMargin)); paper.appendChild(borders); QDomElement head = dd.createElement("head"); paper.appendChild(head); if (!print()->headerFooter()->headLeft().isEmpty()) { QDomElement left = dd.createElement("left"); head.appendChild(left); left.appendChild(dd.createTextNode(print()->headerFooter()->headLeft())); } if (!print()->headerFooter()->headMid().isEmpty()) { QDomElement center = dd.createElement("center"); head.appendChild(center); center.appendChild(dd.createTextNode(print()->headerFooter()->headMid())); } if (!print()->headerFooter()->headRight().isEmpty()) { QDomElement right = dd.createElement("right"); head.appendChild(right); right.appendChild(dd.createTextNode(print()->headerFooter()->headRight())); } QDomElement foot = dd.createElement("foot"); paper.appendChild(foot); if (!print()->headerFooter()->footLeft().isEmpty()) { QDomElement left = dd.createElement("left"); foot.appendChild(left); left.appendChild(dd.createTextNode(print()->headerFooter()->footLeft())); } if (!print()->headerFooter()->footMid().isEmpty()) { QDomElement center = dd.createElement("center"); foot.appendChild(center); center.appendChild(dd.createTextNode(print()->headerFooter()->footMid())); } if (!print()->headerFooter()->footRight().isEmpty()) { QDomElement right = dd.createElement("right"); foot.appendChild(right); right.appendChild(dd.createTextNode(print()->headerFooter()->footRight())); } // print range QDomElement printrange = dd.createElement("printrange-rect"); QRect _printRange = printSettings()->printRegion().lastRange(); int left = _printRange.left(); int right = _printRange.right(); int top = _printRange.top(); int bottom = _printRange.bottom(); //If whole rows are selected, then we store zeros, as KS_colMax may change in future if (left == 1 && right == KS_colMax) { left = 0; right = 0; } //If whole columns are selected, then we store zeros, as KS_rowMax may change in future if (top == 1 && bottom == KS_rowMax) { top = 0; bottom = 0; } printrange.setAttribute("left-rect", QString::number(left)); printrange.setAttribute("right-rect", QString::number(right)); printrange.setAttribute("bottom-rect", QString::number(bottom)); printrange.setAttribute("top-rect", QString::number(top)); sheet.appendChild(printrange); // Print repeat columns QDomElement printRepeatColumns = dd.createElement("printrepeatcolumns"); printRepeatColumns.setAttribute("left", QString::number(printSettings()->repeatedColumns().first)); printRepeatColumns.setAttribute("right", QString::number(printSettings()->repeatedColumns().second)); sheet.appendChild(printRepeatColumns); // Print repeat rows QDomElement printRepeatRows = dd.createElement("printrepeatrows"); printRepeatRows.setAttribute("top", QString::number(printSettings()->repeatedRows().first)); printRepeatRows.setAttribute("bottom", QString::number(printSettings()->repeatedRows().second)); sheet.appendChild(printRepeatRows); //Save print zoom sheet.setAttribute("printZoom", QString::number(printSettings()->zoom())); //Save page limits const QSize pageLimits = printSettings()->pageLimits(); sheet.setAttribute("printPageLimitX", QString::number(pageLimits.width())); sheet.setAttribute("printPageLimitY", QString::number(pageLimits.height())); // Save all cells. const QRect usedArea = this->usedArea(); for (int row = 1; row <= usedArea.height(); ++row) { Cell cell = d->cellStorage->firstInRow(row); while (!cell.isNull()) { QDomElement e = cell.save(dd); if (!e.isNull()) sheet.appendChild(e); cell = d->cellStorage->nextInRow(cell.column(), row); } } // Save all RowFormat objects. int styleIndex = styleStorage()->nextRowStyleIndex(0); int rowFormatRow = 0, lastRowFormatRow = rowFormats()->lastNonDefaultRow(); while (styleIndex || rowFormatRow <= lastRowFormatRow) { int lastRow; bool isDefault = rowFormats()->isDefaultRow(rowFormatRow, &lastRow); if (isDefault && styleIndex <= lastRow) { RowFormat rowFormat(*map()->defaultRowFormat()); rowFormat.setSheet(this); rowFormat.setRow(styleIndex); QDomElement e = rowFormat.save(dd); if (e.isNull()) return QDomElement(); sheet.appendChild(e); styleIndex = styleStorage()->nextRowStyleIndex(styleIndex); } else if (!isDefault) { RowFormat rowFormat(rowFormats(), rowFormatRow); QDomElement e = rowFormat.save(dd); if (e.isNull()) return QDomElement(); sheet.appendChild(e); if (styleIndex == rowFormatRow) styleIndex = styleStorage()->nextRowStyleIndex(styleIndex); } if (isDefault) rowFormatRow = qMin(lastRow+1, styleIndex == 0 ? KS_rowMax : styleIndex); else rowFormatRow++; } // Save all ColumnFormat objects. ColumnFormat* columnFormat = firstCol(); styleIndex = styleStorage()->nextColumnStyleIndex(0); while (columnFormat || styleIndex) { if (columnFormat && (!styleIndex || columnFormat->column() <= styleIndex)) { QDomElement e = columnFormat->save(dd); if (e.isNull()) return QDomElement(); sheet.appendChild(e); if (columnFormat->column() == styleIndex) styleIndex = styleStorage()->nextColumnStyleIndex(styleIndex); columnFormat = columnFormat->next(); } else if (styleIndex) { ColumnFormat columnFormat(*map()->defaultColumnFormat()); columnFormat.setSheet(this); columnFormat.setColumn(styleIndex); QDomElement e = columnFormat.save(dd); if (e.isNull()) return QDomElement(); sheet.appendChild(e); styleIndex = styleStorage()->nextColumnStyleIndex(styleIndex); } } #if 0 // CALLIGRA_SHEETS_KOPART_EMBEDDING foreach(EmbeddedObject* object, doc()->embeddedObjects()) { if (object->sheet() == this) { QDomElement e = object->save(dd); if (e.isNull()) return QDomElement(); sheet.appendChild(e); } } #endif // CALLIGRA_SHEETS_KOPART_EMBEDDING return sheet; } bool Sheet::isLoading() { return map()->isLoading(); } void Sheet::checkContentDirection(QString const & name) { /* set sheet's direction to RTL if sheet name is an RTL string */ if ((name.isRightToLeft())) setLayoutDirection(Qt::RightToLeft); else setLayoutDirection(Qt::LeftToRight); } QRect Sheet::usedArea(bool onlyContent) const { int maxCols = d->cellStorage->columns(!onlyContent); int maxRows = d->cellStorage->rows(!onlyContent); if (!onlyContent) { maxRows = qMax(maxRows, d->rows.lastNonDefaultRow()); const ColumnFormat* col = firstCol(); while (col) { if (col->column() > maxCols) maxCols = col->column(); col = col->next(); } } // flake QRectF shapesBoundingRect; for (int i = 0; i < d->shapes.count(); ++i) shapesBoundingRect |= d->shapes[i]->boundingRect(); const QRect shapesCellRange = documentToCellCoordinates(shapesBoundingRect); maxCols = qMax(maxCols, shapesCellRange.right()); maxRows = qMax(maxRows, shapesCellRange.bottom()); return QRect(1, 1, maxCols, maxRows); } bool Sheet::loadXML(const KoXmlElement& sheet) { bool ok = false; QString sname = sheetName(); if (!map()->loadingInfo()->loadTemplate()) { sname = sheet.attribute("name"); if (sname.isEmpty()) { doc()->setErrorMessage(i18n("Invalid document. Sheet name is empty.")); return false; } } bool detectDirection = true; QString layoutDir = sheet.attribute("layoutDirection"); if (!layoutDir.isEmpty()) { if (layoutDir == "rtl") { detectDirection = false; setLayoutDirection(Qt::RightToLeft); } else if (layoutDir == "ltr") { detectDirection = false; setLayoutDirection(Qt::LeftToRight); } else debugSheets << " Direction not implemented :" << layoutDir; } if (detectDirection) checkContentDirection(sname); /* older versions of KSpread allowed all sorts of characters that the parser won't actually understand. Replace these with '_' Also, the initial character cannot be a space. */ while (sname[0] == ' ') { sname.remove(0, 1); } for (int i = 0; i < sname.length(); i++) { if (!(sname[i].isLetterOrNumber() || sname[i] == ' ' || sname[i] == '.' || sname[i] == '_')) { sname[i] = '_'; } } // validate sheet name, if it differs from the current one if (sname != sheetName()) { /* make sure there are no name collisions with the altered name */ QString testName = sname; QString baseName = sname; int nameSuffix = 0; /* so we don't panic over finding ourself in the following test*/ sname.clear(); while (map()->findSheet(testName) != 0) { nameSuffix++; testName = baseName + '_' + QString::number(nameSuffix); } sname = testName; debugSheets << "Sheet::loadXML: table name =" << sname; setObjectName(sname); setSheetName(sname, true); } // (dynamic_cast(dcopObject()))->sheetNameHasChanged(); if (sheet.hasAttribute("grid")) { setShowGrid((int)sheet.attribute("grid").toInt(&ok)); // we just ignore 'ok' - if it didn't work, go on } if (sheet.hasAttribute("printGrid")) { print()->settings()->setPrintGrid((bool)sheet.attribute("printGrid").toInt(&ok)); // we just ignore 'ok' - if it didn't work, go on } if (sheet.hasAttribute("printCommentIndicator")) { print()->settings()->setPrintCommentIndicator((bool)sheet.attribute("printCommentIndicator").toInt(&ok)); // we just ignore 'ok' - if it didn't work, go on } if (sheet.hasAttribute("printFormulaIndicator")) { print()->settings()->setPrintFormulaIndicator((bool)sheet.attribute("printFormulaIndicator").toInt(&ok)); // we just ignore 'ok' - if it didn't work, go on } if (sheet.hasAttribute("hide")) { setHidden((bool)sheet.attribute("hide").toInt(&ok)); // we just ignore 'ok' - if it didn't work, go on } if (sheet.hasAttribute("showFormula")) { setShowFormula((bool)sheet.attribute("showFormula").toInt(&ok)); // we just ignore 'ok' - if it didn't work, go on } //Compatibility with KSpread 1.1.x if (sheet.hasAttribute("formular")) { setShowFormula((bool)sheet.attribute("formular").toInt(&ok)); // we just ignore 'ok' - if it didn't work, go on } if (sheet.hasAttribute("showFormulaIndicator")) { setShowFormulaIndicator((bool)sheet.attribute("showFormulaIndicator").toInt(&ok)); // we just ignore 'ok' - if it didn't work, go on } if (sheet.hasAttribute("showCommentIndicator")) { setShowCommentIndicator((bool)sheet.attribute("showCommentIndicator").toInt(&ok)); // we just ignore 'ok' - if it didn't work, go on } if (sheet.hasAttribute("borders")) { setShowPageOutline((bool)sheet.attribute("borders").toInt(&ok)); // we just ignore 'ok' - if it didn't work, go on } if (sheet.hasAttribute("lcmode")) { setLcMode((bool)sheet.attribute("lcmode").toInt(&ok)); // we just ignore 'ok' - if it didn't work, go on } if (sheet.hasAttribute("autoCalc")) { setAutoCalculationEnabled((bool)sheet.attribute("autoCalc").toInt(&ok)); // we just ignore 'ok' - if it didn't work, go on } if (sheet.hasAttribute("columnnumber")) { setShowColumnNumber((bool)sheet.attribute("columnnumber").toInt(&ok)); // we just ignore 'ok' - if it didn't work, go on } if (sheet.hasAttribute("hidezero")) { setHideZero((bool)sheet.attribute("hidezero").toInt(&ok)); // we just ignore 'ok' - if it didn't work, go on } if (sheet.hasAttribute("firstletterupper")) { setFirstLetterUpper((bool)sheet.attribute("firstletterupper").toInt(&ok)); // we just ignore 'ok' - if it didn't work, go on } // Load the paper layout KoXmlElement paper = sheet.namedItem("paper").toElement(); if (!paper.isNull()) { KoPageLayout pageLayout; pageLayout.format = KoPageFormat::formatFromString(paper.attribute("format")); pageLayout.orientation = (paper.attribute("orientation") == "Portrait") ? KoPageFormat::Portrait : KoPageFormat::Landscape; // KoXmlElement borders = paper.namedItem("borders").toElement(); if (!borders.isNull()) { pageLayout.leftMargin = MM_TO_POINT(borders.attribute("left").toFloat()); pageLayout.rightMargin = MM_TO_POINT(borders.attribute("right").toFloat()); pageLayout.topMargin = MM_TO_POINT(borders.attribute("top").toFloat()); pageLayout.bottomMargin = MM_TO_POINT(borders.attribute("bottom").toFloat()); } print()->settings()->setPageLayout(pageLayout); QString hleft, hright, hcenter; QString fleft, fright, fcenter; // KoXmlElement head = paper.namedItem("head").toElement(); if (!head.isNull()) { KoXmlElement left = head.namedItem("left").toElement(); if (!left.isNull()) hleft = left.text(); KoXmlElement center = head.namedItem("center").toElement(); if (!center.isNull()) hcenter = center.text(); KoXmlElement right = head.namedItem("right").toElement(); if (!right.isNull()) hright = right.text(); } // KoXmlElement foot = paper.namedItem("foot").toElement(); if (!foot.isNull()) { KoXmlElement left = foot.namedItem("left").toElement(); if (!left.isNull()) fleft = left.text(); KoXmlElement center = foot.namedItem("center").toElement(); if (!center.isNull()) fcenter = center.text(); KoXmlElement right = foot.namedItem("right").toElement(); if (!right.isNull()) fright = right.text(); } print()->headerFooter()->setHeadFootLine(hleft, hcenter, hright, fleft, fcenter, fright); } // load print range KoXmlElement printrange = sheet.namedItem("printrange-rect").toElement(); if (!printrange.isNull()) { int left = printrange.attribute("left-rect").toInt(); int right = printrange.attribute("right-rect").toInt(); int bottom = printrange.attribute("bottom-rect").toInt(); int top = printrange.attribute("top-rect").toInt(); if (left == 0) { //whole row(s) selected left = 1; right = KS_colMax; } if (top == 0) { //whole column(s) selected top = 1; bottom = KS_rowMax; } const Region region(QRect(QPoint(left, top), QPoint(right, bottom)), this); printSettings()->setPrintRegion(region); } // load print zoom if (sheet.hasAttribute("printZoom")) { double zoom = sheet.attribute("printZoom").toDouble(&ok); if (ok) { printSettings()->setZoom(zoom); } } // load page limits if (sheet.hasAttribute("printPageLimitX")) { int pageLimit = sheet.attribute("printPageLimitX").toInt(&ok); if (ok) { printSettings()->setPageLimits(QSize(pageLimit, 0)); } } // load page limits if (sheet.hasAttribute("printPageLimitY")) { int pageLimit = sheet.attribute("printPageLimitY").toInt(&ok); if (ok) { const int horizontalLimit = printSettings()->pageLimits().width(); printSettings()->setPageLimits(QSize(horizontalLimit, pageLimit)); } } // Load the cells KoXmlNode n = sheet.firstChild(); while (!n.isNull()) { KoXmlElement e = n.toElement(); if (!e.isNull()) { QString tagName = e.tagName(); if (tagName == "cell") Cell(this, 1, 1).load(e, 0, 0); // col, row will get overridden in all cases else if (tagName == "row") { RowFormat *rl = new RowFormat(); rl->setSheet(this); if (rl->load(e)) insertRowFormat(rl); delete rl; } else if (tagName == "column") { ColumnFormat *cl = new ColumnFormat(); cl->setSheet(this); if (cl->load(e)) insertColumnFormat(cl); else delete cl; } #if 0 // CALLIGRA_SHEETS_KOPART_EMBEDDING else if (tagName == "object") { EmbeddedCalligraObject *ch = new EmbeddedCalligraObject(doc(), this); if (ch->load(e)) insertObject(ch); else { ch->embeddedObject()->setDeleted(true); delete ch; } } else if (tagName == "chart") { EmbeddedChart *ch = new EmbeddedChart(doc(), this); if (ch->load(e)) insertObject(ch); else { ch->embeddedObject()->setDeleted(true); delete ch; } } #endif // CALLIGRA_SHEETS_KOPART_EMBEDDING } n = n.nextSibling(); } // load print repeat columns KoXmlElement printrepeatcolumns = sheet.namedItem("printrepeatcolumns").toElement(); if (!printrepeatcolumns.isNull()) { int left = printrepeatcolumns.attribute("left").toInt(); int right = printrepeatcolumns.attribute("right").toInt(); printSettings()->setRepeatedColumns(qMakePair(left, right)); } // load print repeat rows KoXmlElement printrepeatrows = sheet.namedItem("printrepeatrows").toElement(); if (!printrepeatrows.isNull()) { int top = printrepeatrows.attribute("top").toInt(); int bottom = printrepeatrows.attribute("bottom").toInt(); printSettings()->setRepeatedRows(qMakePair(top, bottom)); } if (!sheet.hasAttribute("borders1.2")) { convertObscuringBorders(); } loadXmlProtection(sheet); return true; } bool Sheet::loadChildren(KoStore* _store) { Q_UNUSED(_store); #if 0 // CALLIGRA_SHEETS_KOPART_EMBEDDING foreach(EmbeddedObject* object, doc()->embeddedObjects()) { if (object->sheet() == this && (object->getType() == OBJECT_CALLIGRA_PART || object->getType() == OBJECT_CHART)) { debugSheets << "Calligra::Sheets::Sheet::loadChildren"; if (!dynamic_cast(object)->embeddedObject()->loadDocument(_store)) return false; } } #endif // CALLIGRA_SHEETS_KOPART_EMBEDDING return true; } void Sheet::setShowPageOutline(bool b) { if (b == d->showPageOutline) return; d->showPageOutline = b; // Just repaint everything visible; no need to invalidate the visual cache. if (!map()->isLoading()) { map()->addDamage(new SheetDamage(this, SheetDamage::ContentChanged)); } } QImage Sheet::backgroundImage() const { return d->backgroundImage; } void Sheet::setBackgroundImage(const QImage& image) { d->backgroundImage = image; } Sheet::BackgroundImageProperties Sheet::backgroundImageProperties() const { return d->backgroundProperties; } void Sheet::setBackgroundImageProperties(const Sheet::BackgroundImageProperties& properties) { d->backgroundProperties = properties; } void Sheet::insertColumnFormat(ColumnFormat *l) { d->columns.insertElement(l, l->column()); if (!map()->isLoading()) { map()->addDamage(new SheetDamage(this, SheetDamage::ColumnsChanged)); } } void Sheet::insertRowFormat(RowFormat *l) { const int row = l->row(); d->rows.setRowHeight(row, row, l->height()); d->rows.setHidden(row, row, l->isHidden()); d->rows.setFiltered(row, row, l->isFiltered()); d->rows.setPageBreak(row, row, l->hasPageBreak()); if (!map()->isLoading()) { map()->addDamage(new SheetDamage(this, SheetDamage::RowsChanged)); } } void Sheet::deleteColumnFormat(int column) { d->columns.removeElement(column); if (!map()->isLoading()) { map()->addDamage(new SheetDamage(this, SheetDamage::ColumnsChanged)); } } void Sheet::deleteRowFormat(int row) { d->rows.setDefault(row, row); if (!map()->isLoading()) { map()->addDamage(new SheetDamage(this, SheetDamage::RowsChanged)); } } RowFormatStorage* Sheet::rowFormats() { return &d->rows; } const RowFormatStorage* Sheet::rowFormats() const { return &d->rows; } void Sheet::showStatusMessage(const QString &message, int timeout) { emit statusMessage(message, timeout); } void Sheet::hideSheet(bool _hide) { setHidden(_hide); if (_hide) map()->addDamage(new SheetDamage(this, SheetDamage::Hidden)); else map()->addDamage(new SheetDamage(this, SheetDamage::Shown)); } bool Sheet::setSheetName(const QString& name, bool init) { Q_UNUSED(init); if (map()->findSheet(name)) return false; if (isProtected()) return false; if (d->name == name) return true; QString old_name = d->name; d->name = name; // FIXME: Why is the change of a sheet's name not supposed to be propagated here? // If it is not, we have to manually do so in the loading process, e.g. for the // SheetAccessModel in the document's data center map. //if (init) // return true; foreach(Sheet* sheet, map()->sheetList()) { sheet->changeCellTabName(old_name, name); } map()->addDamage(new SheetDamage(this, SheetDamage::Name)); setObjectName(name); // (dynamic_cast(dcopObject()))->sheetNameHasChanged(); return true; } void Sheet::updateLocale() { for (int c = 0; c < valueStorage()->count(); ++c) { Cell cell(this, valueStorage()->col(c), valueStorage()->row(c)); QString text = cell.userInput(); cell.parseUserInput(text); } // Affects the displayed value; rebuild the visual cache. const Region region(1, 1, KS_colMax, KS_rowMax, this); map()->addDamage(new CellDamage(this, region, CellDamage::Appearance)); } void Sheet::convertObscuringBorders() { // FIXME Stefan: Verify that this is not needed anymore. #if 0 /* a word of explanation here: beginning with KSpread 1.2 (actually, cvs of Mar 28, 2002), border information is stored differently. Previously, for a cell obscuring a region, the entire region's border's data would be stored in the obscuring cell. This caused some data loss in certain situations. After that date, each cell stores its own border data, and prints it even if it is an obscured cell (as long as that border isn't across an obscuring border). Anyway, this function is used when loading a file that was stored with the old way of borders. All new files have the sheet attribute "borders1.2" so if that isn't in the file, all the border data will be converted here. It's a bit of a hack but I can't think of a better way and it's not *that* bad of a hack.:-) */ Cell c = d->cellStorage->firstCell(); QPen topPen, bottomPen, leftPen, rightPen; for (; c; c = c->nextCell()) { if (c->extraXCells() > 0 || c->extraYCells() > 0) { const Style* style = this->style(c->column(), c->row()); topPen = style->topBorderPen(); leftPen = style->leftBorderPen(); rightPen = style->rightBorderPen(); bottomPen = style->bottomBorderPen(); c->format()->setTopBorderStyle(Qt::NoPen); c->format()->setLeftBorderStyle(Qt::NoPen); c->format()->setRightBorderStyle(Qt::NoPen); c->format()->setBottomBorderStyle(Qt::NoPen); for (int x = c->column(); x < c->column() + c->extraXCells(); x++) { Cell(this, x, c->row())->setTopBorderPen(topPen); Cell(this, x, c->row() + c->extraYCells())-> setBottomBorderPen(bottomPen); } for (int y = c->row(); y < c->row() + c->extraYCells(); y++) { Cell(this, c->column(), y)->setLeftBorderPen(leftPen); Cell(this, c->column() + c->extraXCells(), y)-> setRightBorderPen(rightPen); } } } #endif } void Sheet::applyDatabaseFilter(const Database &database) { Sheet* const sheet = database.range().lastSheet(); const QRect range = database.range().lastRange(); const int start = database.orientation() == Qt::Vertical ? range.top() : range.left(); const int end = database.orientation() == Qt::Vertical ? range.bottom() : range.right(); for (int i = start + 1; i <= end; ++i) { const bool isFiltered = !database.filter().evaluate(database, i); // debugSheets <<"Filtering column/row" << i <<"?" << isFiltered; if (database.orientation() == Qt::Vertical) { sheet->rowFormats()->setFiltered(i, i, isFiltered); } else { // database.orientation() == Qt::Horizontal sheet->nonDefaultColumnFormat(i)->setFiltered(isFiltered); } } if (database.orientation() == Qt::Vertical) sheet->map()->addDamage(new SheetDamage(sheet, SheetDamage::RowsChanged)); else // database.orientation() == Qt::Horizontal sheet->map()->addDamage(new SheetDamage(sheet, SheetDamage::ColumnsChanged)); cellStorage()->setDatabase(database.range(), Database()); cellStorage()->setDatabase(database.range(), database); map()->addDamage(new CellDamage(this, database.range(), CellDamage::Appearance)); } /********************** * Printout Functions * **********************/ #ifndef NDEBUG void Sheet::printDebug() { int iMaxColumn = d->cellStorage->columns(); int iMaxRow = d->cellStorage->rows(); debugSheets << "Cell | Content | Value [UserInput]"; Cell cell; for (int currentrow = 1 ; currentrow <= iMaxRow ; ++currentrow) { for (int currentcolumn = 1 ; currentcolumn <= iMaxColumn ; currentcolumn++) { cell = Cell(this, currentcolumn, currentrow); if (!cell.isEmpty()) { QString cellDescr = Cell::name(currentcolumn, currentrow).rightJustified(4) + //QString cellDescr = "Cell "; //cellDescr += QString::number(currentrow).rightJustified(3,'0') + ','; //cellDescr += QString::number(currentcolumn).rightJustified(3,'0') + ' '; " | "; QString valueType; QTextStream stream(&valueType); stream << cell.value().type(); cellDescr += valueType.rightJustified(7) + " | " + map()->converter()->asString(cell.value()).asString().rightJustified(5) + QString(" [%1]").arg(cell.userInput()); debugSheets << cellDescr; } } } } #endif } // namespace Sheets } // namespace Calligra diff --git a/sheets/part/Doc.cpp b/sheets/part/Doc.cpp index 60a855716bd..fdee75b8990 100644 --- a/sheets/part/Doc.cpp +++ b/sheets/part/Doc.cpp @@ -1,581 +1,583 @@ /* This file is part of the KDE project Copyright 2007 Stefan Nikolaus Copyright 2007 Thorsten Zachmann Copyright 2005-2006 Inge Wallin Copyright 2004 Ariya Hidayat Copyright 2002-2003 Norbert Andres Copyright 2000-2002 Laurent Montel Copyright 2002 John Dailey Copyright 2002 Phillip Mueller Copyright 2000 Werner Trobin Copyright 1999-2000 Simon Hausmann Copyright 1999 David Faure Copyright 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. */ // Local #include "Doc.h" #include "../DocBase_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SheetsDebug.h" #include "BindingManager.h" #include "CalculationSettings.h" #include "Canvas.h" #include "CanvasItem.h" #include "DependencyManager.h" #include "Factory.h" #include "Formula.h" #include "Function.h" #include "FunctionModuleRegistry.h" #include "HeaderFooter.h" #include "LoadingInfo.h" #include "Localization.h" #include "Map.h" #include "NamedAreaManager.h" #include "PrintSettings.h" #include "RecalcManager.h" #include "Sheet.h" #include "SheetPrint.h" #include "StyleManager.h" #include "Util.h" #include "View.h" #include "SheetAccessModel.h" #include "BindingModel.h" // D-Bus #ifndef QT_NO_DBUS #include "interfaces/MapAdaptor.h" #include "interfaces/SheetAdaptor.h" #include #endif // chart shape #include "plugins/chartshape/ChartShape.h" #include "chart/ChartDialog.h" // ui #include "ui/Selection.h" #include "ui/SheetView.h" using namespace std; using namespace Calligra::Sheets; class Q_DECL_HIDDEN Doc::Private { public: Map *map; static QList s_docs; static int s_docId; // document properties bool configLoadFromFile : 1; QStringList spellListIgnoreAll; SavedDocParts savedDocParts; SheetAccessModel *sheetAccessModel; KoDocumentResourceManager *resourceManager; }; // Make sure an appropriate DTD is available in www/calligra/DTD if changing this value static const char CURRENT_DTD_VERSION[] = "1.2"; /***************************************************************************** * * Doc * *****************************************************************************/ QList Doc::Private::s_docs; int Doc::Private::s_docId = 0; Doc::Doc(KoPart *part) : DocBase(part) , dd(new Private) { Q_ASSERT(part); connect(d->map, SIGNAL(sheetAdded(Sheet*)), this, SLOT(sheetAdded(Sheet*))); #ifndef QT_NO_DBUS new MapAdaptor(d->map); QDBusConnection::sessionBus().registerObject('/' + objectName() + '/' + d->map->objectName(), d->map); #endif // Init chart shape factory with Calligra Sheets' specific configuration panels. KoShapeFactoryBase *chartShape = KoShapeRegistry::instance()->value(ChartShapeId); if (chartShape) { QList panels = ChartDialog::panels(d->map); chartShape->setOptionPanels(panels); + } else { + warnSheets << "chart shape factory not found"; } connect(d->map, SIGNAL(commandAdded(KUndo2Command*)), this, SLOT(addCommand(KUndo2Command*))); // Load the function modules. FunctionModuleRegistry::instance()->loadFunctionModules(); } Doc::~Doc() { //don't save config when words is embedded into konqueror saveConfig(); delete dd; } void Doc::initEmpty() { KSharedConfigPtr config = Factory::global().config(); const int page = config->group("Parameters").readEntry("NbPage", 1); for (int i = 0; i < page; ++i) map()->addNewSheet(); resetURL(); initConfig(); map()->styleManager()->createBuiltinStyles(); KoDocument::initEmpty(); } void Doc::saveConfig() { KSharedConfigPtr config = Factory::global().config(); Q_UNUSED(config); } void Doc::initConfig() { KSharedConfigPtr config = Factory::global().config(); const int page = config->group("Tables Page Layout").readEntry("Default unit page", 0); setUnit(KoUnit::fromListForUi(page, KoUnit::HidePixel)); } int Doc::supportedSpecialFormats() const { return KoDocument::supportedSpecialFormats(); } bool Doc::completeSaving(KoStore* _store) { Q_UNUSED(_store); return true; } QDomDocument Doc::saveXML() { /* don't pull focus away from the editor if this is just a background autosave */ if (!isAutosaving()) {/* FIXME foreach(KoView* view, views()) static_cast(view)->selection()->emitCloseEditor(true); */ emit closeEditor(true); } QDomDocument doc = KoDocument::createDomDocument("tables", "spreadsheet", CURRENT_DTD_VERSION); QDomElement spread = doc.documentElement(); spread.setAttribute("editor", "Calligra Sheets"); spread.setAttribute("mime", "application/x-kspread"); spread.setAttribute("syntaxVersion", QString::number(CURRENT_SYNTAX_VERSION)); if (!d->spellListIgnoreAll.isEmpty()) { QDomElement spellCheckIgnore = doc.createElement("SPELLCHECKIGNORELIST"); spread.appendChild(spellCheckIgnore); for (QStringList::ConstIterator it = d->spellListIgnoreAll.constBegin(); it != d->spellListIgnoreAll.constEnd(); ++it) { QDomElement spellElem = doc.createElement("SPELLCHECKIGNOREWORD"); spellCheckIgnore.appendChild(spellElem); spellElem.setAttribute("word", *it); } } SavedDocParts::const_iterator iter = d->savedDocParts.constBegin(); SavedDocParts::const_iterator end = d->savedDocParts.constEnd(); while (iter != end) { // save data we loaded in the beginning and which has no owner back to file spread.appendChild(iter.value().documentElement()); ++iter; } QDomElement e = map()->save(doc); /*FIXME // Save visual info for the first view, such as active sheet and active cell // It looks like a hack, but reopening a document creates only one view anyway (David) View *const view = static_cast(views().first()); Canvas *const canvas = view->canvasWidget(); e.setAttribute("activeTable", canvas->activeSheet()->sheetName()); e.setAttribute("markerColumn", QString::number(view->selection()->marker().x())); e.setAttribute("markerRow", QString::number(view->selection()->marker().y())); e.setAttribute("xOffset", QString::number(canvas->xOffset())); e.setAttribute("yOffset", QString::number(canvas->yOffset())); */ spread.appendChild(e); setModified(false); return doc; } bool Doc::loadChildren(KoStore* _store) { return map()->loadChildren(_store); } bool Doc::loadXML(const KoXmlDocument& doc, KoStore*) { QPointer updater; if (progressUpdater()) { updater = progressUpdater()->startSubtask(1, "KSpread::Doc::loadXML"); updater->setProgress(0); } d->spellListIgnoreAll.clear(); // KoXmlElement spread = doc.documentElement(); if (spread.attribute("mime") != "application/x-kspread" && spread.attribute("mime") != "application/vnd.kde.kspread") { setErrorMessage(i18n("Invalid document. Expected mimetype application/x-kspread or application/vnd.kde.kspread, got %1" , spread.attribute("mime"))); return false; } bool ok = false; int version = spread.attribute("syntaxVersion").toInt(&ok); map()->setSyntaxVersion(ok ? version : 0); if (map()->syntaxVersion() > CURRENT_SYNTAX_VERSION) { int ret = KMessageBox::warningContinueCancel( 0, i18n("This document was created with a newer version of Calligra Sheets (syntax version: %1)\n" "When you open it with this version of Calligra Sheets, some information may be lost.", map()->syntaxVersion()), i18n("File Format Mismatch"), KStandardGuiItem::cont()); if (ret == KMessageBox::Cancel) { setErrorMessage("USER_CANCELED"); return false; } } // KoXmlElement loc = spread.namedItem("locale").toElement(); if (!loc.isNull()) static_cast(map()->calculationSettings()->locale())->load(loc); if (updater) updater->setProgress(5); KoXmlElement defaults = spread.namedItem("defaults").toElement(); if (!defaults.isNull()) { double dim = defaults.attribute("row-height").toDouble(&ok); if (!ok) return false; map()->setDefaultRowHeight(dim); dim = defaults.attribute("col-width").toDouble(&ok); if (!ok) return false; map()->setDefaultColumnWidth(dim); } KoXmlElement ignoreAll = spread.namedItem("SPELLCHECKIGNORELIST").toElement(); if (!ignoreAll.isNull()) { KoXmlElement spellWord = spread.namedItem("SPELLCHECKIGNORELIST").toElement(); spellWord = spellWord.firstChild().toElement(); while (!spellWord.isNull()) { if (spellWord.tagName() == "SPELLCHECKIGNOREWORD") { d->spellListIgnoreAll.append(spellWord.attribute("word")); } spellWord = spellWord.nextSibling().toElement(); } } if (updater) updater->setProgress(40); // In case of reload (e.g. from konqueror) qDeleteAll(map()->sheetList()); map()->sheetList().clear(); KoXmlElement styles = spread.namedItem("styles").toElement(); if (!styles.isNull()) { if (!map()->styleManager()->loadXML(styles)) { setErrorMessage(i18n("Styles cannot be loaded.")); return false; } } // KoXmlElement mymap = spread.namedItem("map").toElement(); if (mymap.isNull()) { setErrorMessage(i18n("Invalid document. No map tag.")); return false; } if (!map()->loadXML(mymap)) { return false; } // named areas const KoXmlElement areaname = spread.namedItem("areaname").toElement(); if (!areaname.isNull()) map()->namedAreaManager()->loadXML(areaname); //Backwards compatibility with older versions for paper layout if (map()->syntaxVersion() < 1) { KoXmlElement paper = spread.namedItem("paper").toElement(); if (!paper.isNull()) { loadPaper(paper); } } if (updater) updater->setProgress(85); KoXmlElement element(spread.firstChild().toElement()); while (!element.isNull()) { QString tagName(element.tagName()); if (tagName != "locale" && tagName != "map" && tagName != "styles" && tagName != "SPELLCHECKIGNORELIST" && tagName != "areaname" && tagName != "paper") { // belongs to a plugin, load it and save it for later use QDomDocument doc; KoXml::asQDomElement(doc, element); d->savedDocParts[ tagName ] = doc; } element = element.nextSibling().toElement(); } if (updater) updater->setProgress(90); initConfig(); if (updater) updater->setProgress(100); return true; } void Doc::loadPaper(KoXmlElement const & paper) { KoPageLayout pageLayout; pageLayout.format = KoPageFormat::formatFromString(paper.attribute("format")); pageLayout.orientation = (paper.attribute("orientation") == "Portrait") ? KoPageFormat::Portrait : KoPageFormat::Landscape; // KoXmlElement borders = paper.namedItem("borders").toElement(); if (!borders.isNull()) { pageLayout.leftMargin = MM_TO_POINT(borders.attribute("left").toFloat()); pageLayout.rightMargin = MM_TO_POINT(borders.attribute("right").toFloat()); pageLayout.topMargin = MM_TO_POINT(borders.attribute("top").toFloat()); pageLayout.bottomMargin = MM_TO_POINT(borders.attribute("bottom").toFloat()); } //apply to all sheet foreach(Sheet* sheet, map()->sheetList()) { sheet->printSettings()->setPageLayout(pageLayout); } QString hleft, hright, hcenter; QString fleft, fright, fcenter; // KoXmlElement head = paper.namedItem("head").toElement(); if (!head.isNull()) { KoXmlElement left = head.namedItem("left").toElement(); if (!left.isNull()) hleft = left.text(); KoXmlElement center = head.namedItem("center").toElement(); if (!center.isNull()) hcenter = center.text(); KoXmlElement right = head.namedItem("right").toElement(); if (!right.isNull()) hright = right.text(); } // KoXmlElement foot = paper.namedItem("foot").toElement(); if (!foot.isNull()) { KoXmlElement left = foot.namedItem("left").toElement(); if (!left.isNull()) fleft = left.text(); KoXmlElement center = foot.namedItem("center").toElement(); if (!center.isNull()) fcenter = center.text(); KoXmlElement right = foot.namedItem("right").toElement(); if (!right.isNull()) fright = right.text(); } //The macro "" formerly was typed as "" hleft.replace("
", ""); hcenter.replace("
", ""); hright.replace("
", ""); fleft.replace("
", ""); fcenter.replace("
", ""); fright.replace("
", ""); foreach(Sheet* sheet, map()->sheetList()) { sheet->print()->headerFooter()->setHeadFootLine(hleft, hcenter, hright, fleft, fcenter, fright); } } bool Doc::completeLoading(KoStore* store) { debugSheets << "------------------------ COMPLETING --------------------"; setModified(false); bool ok = map()->completeLoading(store); debugSheets << "------------------------ COMPLETION DONE --------------------"; return ok; } bool Doc::docData(QString const & xmlTag, QDomDocument & data) { SavedDocParts::iterator iter = d->savedDocParts.find(xmlTag); if (iter == d->savedDocParts.end()) return false; data = iter.value(); d->savedDocParts.erase(iter); return true; } void Doc::addIgnoreWordAllList(const QStringList & _lst) { d->spellListIgnoreAll = _lst; } QStringList Doc::spellListIgnoreAll() const { return d->spellListIgnoreAll; } void Doc::paintContent(QPainter& painter, const QRect& rect) { paintContent(painter, rect, 0); } void Doc::paintContent(QPainter& painter, const QRect& rect, Sheet* _sheet) { if (rect.isEmpty()) { return; } Sheet *const sheet = _sheet ? _sheet : d->map->sheet(0); const KoPageLayout pageLayout = sheet->printSettings()->pageLayout(); QPixmap thumbnail(pageLayout.width, pageLayout.height); thumbnail.fill(Qt::white); SheetView sheetView(sheet); const qreal zoom = sheet->printSettings()->zoom(); KoZoomHandler zoomHandler; zoomHandler.setZoom(zoom); sheetView.setViewConverter(&zoomHandler); sheetView.setPaintCellRange(sheet->print()->cellRange(1)); // first page QPainter pixmapPainter(&thumbnail); pixmapPainter.setClipRect(QRect(QPoint(0, 0), thumbnail.size())); sheetView.paintCells(pixmapPainter, QRect(0, 0, pageLayout.width, pageLayout.height), QPointF(0,0)); // The pixmap gets scaled to fit the rectangle. painter.drawPixmap(rect & QRect(0, 0, 100, 100), thumbnail); } void Doc::updateAllViews() { emit updateView(); } void Doc::addIgnoreWordAll(const QString & word) { if (d->spellListIgnoreAll.indexOf(word) == -1) d->spellListIgnoreAll.append(word); } void Doc::clearIgnoreWordAll() { d->spellListIgnoreAll.clear(); } void Doc::loadConfigFromFile() { d->configLoadFromFile = true; } bool Doc::configLoadFromFile() const { return d->configLoadFromFile; } void Doc::sheetAdded(Sheet* sheet) { #ifndef QT_NO_DBUS new SheetAdaptor(sheet); QString dbusPath('/' + sheet->map()->objectName() + '/' + sheet->objectName()); if (sheet->parent() && !sheet->parent()->objectName().isEmpty()) { dbusPath.prepend('/' + sheet->parent()->objectName()); } QDBusConnection::sessionBus().registerObject(dbusPath, sheet); #endif } bool Doc::saveOdf(SavingContext &documentContext) { /* don't pull focus away from the editor if this is just a background autosave */ if (!isAutosaving()) { /*FIXME foreach(KoView* view, views()) static_cast(view)->selection()->emitCloseEditor(true); */ emit closeEditor(true); } return DocBase::saveOdf(documentContext); } diff --git a/stage/part/KPrEndOfSlideShowPage.cpp b/stage/part/KPrEndOfSlideShowPage.cpp index f823476a5a9..fec4015dfaa 100644 --- a/stage/part/KPrEndOfSlideShowPage.cpp +++ b/stage/part/KPrEndOfSlideShowPage.cpp @@ -1,80 +1,82 @@ /* 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 "KPrEndOfSlideShowPage.h" #include #include #include #include #include #include #include #include #include "KPrDocument.h" #include "KPrMasterPage.h" #include "StageDebug.h" KPrEndOfSlideShowPage::KPrEndOfSlideShowPage( const QRectF & screenRect, KPrDocument * document ) : KPrPage(new KPrMasterPage(document), document) { qreal ratio = screenRect.width() / screenRect.height(); KoPageLayout pageLayout; pageLayout.height = 510; pageLayout.width = 510 * ratio; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.orientation = screenRect.width() > screenRect.height() ? KoPageFormat::Landscape : KoPageFormat::Portrait; pageLayout.bindingSide = 0; pageLayout.pageEdge = 0; pageLayout.format = KoPageFormat::IsoA3Size; masterPage()->setPageLayout( pageLayout ); masterPage()->setBackground( QSharedPointer( new KoColorBackground( Qt::black ) ) ); KoShapeLayer* layer = new KoShapeLayer; addShape( layer ); KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value( "TextShapeID" ); Q_ASSERT( factory ); if ( factory ) { KoShape * textShape = factory->createDefaultShape(); QTextDocument * document = qobject_cast( textShape->userData() )->document(); QTextCursor cursor( document ); QTextCharFormat format; format.setForeground( QBrush( Qt::white ) ); cursor.mergeCharFormat( format ); cursor.insertText( i18n("End of presentation. Click to exit." ) ); textShape->setPosition( QPointF( 10.0, 10.0 ) ); textShape->setSize( QSizeF( pageLayout.width - 20.0, pageLayout.height - 20.0 ) ); layer->addShape( textShape ); + } else { + warnStage << "text shape factory not found"; } } KPrEndOfSlideShowPage::~KPrEndOfSlideShowPage() { delete masterPage(); } diff --git a/stage/part/KPrNotes.cpp b/stage/part/KPrNotes.cpp index e17d9d4090b..7a915d04d67 100644 --- a/stage/part/KPrNotes.cpp +++ b/stage/part/KPrNotes.cpp @@ -1,230 +1,237 @@ /* This file is part of the KDE project * Copyright (C) 2008 Fredy Yanardi * 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 "KPrNotes.h" #include #include #include #include #include #include #include #include #include #include #include #include "StageDebug.h" #include "KPrDocument.h" #include "KPrPage.h" #include // a helper class to load attributes of the thumbnail shape class ShapeLoaderHelper : public KoShape { public: ShapeLoaderHelper() { } virtual void paint( QPainter &, const KoViewConverter &, KoShapePaintingContext &) { } virtual bool loadOdf( const KoXmlElement & element, KoShapeLoadingContext &context ) { return loadOdfAttributes( element, context, OdfAllAttributes ); } virtual void saveOdf( KoShapeSavingContext & ) const { } }; KPrNotes::KPrNotes( KPrPage *page, KPrDocument * document ) : KoPAPageBase() , m_page( page ) , m_doc( document ) , m_imageCollection( new KoImageCollection() ) { // add default layer KoShapeLayer* layer = new KoShapeLayer; addShape( layer ); // All sizes and positions are hardcoded for now KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("TextShapeID"); Q_ASSERT(factory); - m_textShape = factory->createDefaultShape(m_doc->resourceManager()); - m_textShape->setGeometryProtected(true); - m_textShape->setAdditionalAttribute( "presentation:class", "notes" ); - m_textShape->setPosition(QPointF(62.22, 374.46)); - m_textShape->setSize(QSizeF(489.57, 356.37)); + if (factory) { + m_textShape = factory->createDefaultShape(m_doc->resourceManager()); + m_textShape->setGeometryProtected(true); + m_textShape->setAdditionalAttribute( "presentation:class", "notes" ); + m_textShape->setPosition(QPointF(62.22, 374.46)); + m_textShape->setSize(QSizeF(489.57, 356.37)); + layer->addShape( m_textShape ); + } else { + warnStage << "text shape factory not found"; + } factory = KoShapeRegistry::instance()->value("PictureShape"); Q_ASSERT(factory); - m_thumbnailShape = factory->createDefaultShape(m_doc->resourceManager()); - m_thumbnailShape->setGeometryProtected(true); - m_thumbnailShape->setAdditionalAttribute( "presentation:class", "page" ); - m_thumbnailShape->setPosition(QPointF(108.00, 60.18)); - m_thumbnailShape->setSize(QSizeF(396.28, 296.96)); - - layer->addShape( m_textShape ); - layer->addShape( m_thumbnailShape ); + if (factory) { + m_thumbnailShape = factory->createDefaultShape(m_doc->resourceManager()); + m_thumbnailShape->setGeometryProtected(true); + m_thumbnailShape->setAdditionalAttribute( "presentation:class", "page" ); + m_thumbnailShape->setPosition(QPointF(108.00, 60.18)); + m_thumbnailShape->setSize(QSizeF(396.28, 296.96)); + layer->addShape( m_thumbnailShape ); + } else { + warnStage << "picture shape factory not found"; + } } KPrNotes::~KPrNotes() { delete m_imageCollection; } KoShape *KPrNotes::textShape() { return m_textShape; } void KPrNotes::saveOdf(KoShapeSavingContext &context) const { KoXmlWriter & writer = context.xmlWriter(); writer.startElement("presentation:notes"); context.addOption( KoShapeSavingContext::PresentationShape ); m_textShape->saveOdf(context); context.removeOption( KoShapeSavingContext::PresentationShape ); writer.startElement("draw:page-thumbnail"); m_thumbnailShape->saveOdfAttributes( context, OdfAllAttributes ); writer.addAttribute("draw:page-number", static_cast(context).page()); writer.endElement(); // draw:page-thumbnail KoShapeLayer* layer = static_cast( shapes().last() ); foreach ( KoShape *shape, layer->shapes() ) { if ( shape != m_textShape && shape != m_thumbnailShape ) { shape->saveOdf( context ); } } writer.endElement(); // presentation:notes } bool KPrNotes::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { KoXmlElement child; KoShapeLayer* layer = static_cast( shapes().last() ); forEachElement( child, element ) { if ( child.namespaceURI() != KoXmlNS::draw ) continue; if ( child.tagName() == "page-thumbnail") { ShapeLoaderHelper *helper = new ShapeLoaderHelper(); helper->loadOdf( child, context ); m_thumbnailShape->setSize( helper->size() ); m_thumbnailShape->setTransformation( helper->transformation() ); m_thumbnailShape->setPosition( helper->position() ); m_thumbnailShape->setShapeId( helper->shapeId() ); delete helper; } else /* if ( child.tagName() == "frame") */ { KoShape *shape = KoShapeRegistry::instance()->createShapeFromOdf( child, context ); if ( shape ) { if ( shape->shapeId() == "TextShapeID" && child.hasAttributeNS( KoXmlNS::presentation, "class" ) ) { layer->removeShape( m_textShape ); delete m_textShape; m_textShape = shape; m_textShape->setAdditionalAttribute( "presentation:class", "notes" ); layer->addShape( m_textShape ); } else { layer->addShape( shape ); } } } } return true; } void KPrNotes::paintComponent(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &paintcontext) { Q_UNUSED(painter); Q_UNUSED(converter); Q_UNUSED(paintcontext); } KoPageLayout & KPrNotes::pageLayout() { return m_pageLayout; } const KoPageLayout & KPrNotes::pageLayout() const { return m_pageLayout; } bool KPrNotes::displayMasterShapes() { return false; } void KPrNotes::setDisplayMasterShapes( bool ) { } bool KPrNotes::displayShape(KoShape *) const { return true; } bool KPrNotes::displayMasterBackground() { return false; } void KPrNotes::setDisplayMasterBackground( bool ) { } QImage KPrNotes::thumbImage(const QSize&) { Q_ASSERT( 0 ); return QImage(); } QPixmap KPrNotes::generateThumbnail( const QSize& ) { Q_ASSERT( 0 ); return QPixmap(); } void KPrNotes::updatePageThumbnail() { QSizeF thumbnameSize(m_thumbnailShape->size()); if (!thumbnameSize.isNull()) { // set image at least to 150 dpi we might need more when printing thumbnameSize *= 150 / 72.; // using KoPADocument::pageThumbnail(...) ensures that the page data is up-to-date const QImage pageThumbnail = m_doc->pageThumbImage(m_page, thumbnameSize.toSize()); KoImageData *imageData = m_imageCollection->createImageData(pageThumbnail); m_thumbnailShape->setUserData( imageData ); } } void KPrNotes::paintPage( QPainter & painter, KoZoomHandler & /*zoomHandler*/ ) { Q_UNUSED(painter); // TODO implement when printing page with notes Q_ASSERT( 0 ); } diff --git a/stage/part/KPrPlaceholderStrategy.cpp b/stage/part/KPrPlaceholderStrategy.cpp index 23c56394e95..4dd75615dbc 100644 --- a/stage/part/KPrPlaceholderStrategy.cpp +++ b/stage/part/KPrPlaceholderStrategy.cpp @@ -1,173 +1,175 @@ /* 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); + } else { + warnStage << "no factory found for placeholder"; } 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, 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 b7884b8d75c..77286bb3aa3 100644 --- a/stage/part/KPrPlaceholderTextStrategy.cpp +++ b/stage/part/KPrPlaceholderTextStrategy.cpp @@ -1,176 +1,186 @@ /* 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 +#include "StageDebug.h" + 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, 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); + if (!factory) { + warnStage << "text shape factory not found"; + return false; + } 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 ); + if (!factory) { + warnStage << "text shape factory not found"; + return; + } 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/words/part/KWDocument.cpp b/words/part/KWDocument.cpp index bea98d69c07..35628aeb297 100644 --- a/words/part/KWDocument.cpp +++ b/words/part/KWDocument.cpp @@ -1,782 +1,784 @@ /* This file is part of the KDE project * Copyright (C) 2002-2006 David Faure * Copyright (C) 2005-2010 Thomas Zander * Copyright (C) 2007 Thorsten Zachmann * Copyright (C) 2008 Pierre Ducroquet * Copyright (C) 2008 Sebastian Sauer * Copyright (C) 2010 Boudewijn Rempt * Copyright (C) 2010 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 "KWDocument.h" #include "KWFactory.h" #include "KWView.h" #include "KWCanvas.h" #include "KWCanvasItem.h" #include "KWPageManager.h" #include "KWPage.h" #include "KWPageStyle.h" #include "KWOdfLoader.h" #include "KWOdfWriter.h" #include "frames/KWFrameSet.h" #include "frames/KWTextFrameSet.h" #include "frames/KWFrame.h" #include "frames/KWFrameLayout.h" #include "dialogs/KWFrameDialog.h" #include "KWRootAreaProvider.h" #include "WordsDebug.h" // calligra libs includes #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 #ifdef SHOULD_BUILD_RDF #include #include #endif #include #include // KF5 #include #include #include // Qt #include #include #include #include #include #include KWDocument::KWDocument(KoPart *part) : KoDocument(part) , m_isMasterDocument(false) , m_frameLayout(&m_pageManager, m_frameSets) , m_mainFramesetEverFinished(false) , m_annotationManager(0) { Q_ASSERT(part); m_frameLayout.setDocument(this); resourceManager()->setOdfDocument(this); connect(&m_frameLayout, SIGNAL(newFrameSet(KWFrameSet*)), this, SLOT(addFrameSet(KWFrameSet*))); connect(&m_frameLayout, SIGNAL(removedFrameSet(KWFrameSet*)), this, SLOT(removeFrameSet(KWFrameSet*))); // Init shape Factories with our frame based configuration panels. m_panelFactories = KWFrameDialog::panels(this); foreach (const QString &id, KoShapeRegistry::instance()->keys()) { KoShapeFactoryBase *shapeFactory = KoShapeRegistry::instance()->value(id); - shapeFactory->setOptionPanels(m_panelFactories); + if (shapeFactory) { + shapeFactory->setOptionPanels(m_panelFactories); + } } resourceManager()->setUndoStack(undoStack()); if (documentRdf()) { documentRdf()->linkToResourceManager(resourceManager()); } #ifdef SHOULD_BUILD_RDF { KoDocumentRdf *rdf = new KoDocumentRdf(this); setDocumentRdf(rdf); } #endif /* TODO reenable after release QVariant variant; variant.setValue(new KoChangeTracker(resourceManager())); resourceManager()->setResource(KoText::ChangeTracker, variant); */ m_shapeController = new KoShapeController(0, this); if (inlineTextObjectManager()) { connect(documentInfo(), SIGNAL(infoUpdated(QString,QString)), inlineTextObjectManager(), SLOT(documentInformationUpdated(QString,QString))); } m_annotationManager = new KoAnnotationLayoutManager(this); clear(); } KWDocument::~KWDocument() { qDeleteAll(m_panelFactories); m_config.setUnit(unit()); saveConfig(); qDeleteAll(m_frameSets); } bool KWDocument::isMasterDocument() const { return m_isMasterDocument; } void KWDocument::setIsMasterDocument(bool isMasterDocument) { m_isMasterDocument = isMasterDocument; } // Words adds a couple of dialogs (like KWFrameDialog) which will not call addShape(), but // will call addFrameSet. Which will itself call addSequencedShape() // any call coming in here is due to the undo/redo framework, pasting or for nested frames void KWDocument::addShape(KoShape *shape) { KWFrame *frame = dynamic_cast(shape->applicationData()); debugWords << "shape=" << shape << "frame=" << frame; if (frame == 0) { if (shape->shapeId() == TextShape_SHAPEID) { KWTextFrameSet *tfs = new KWTextFrameSet(this); tfs->setName("Text"); frame = new KWFrame(shape, tfs); } else { KWFrameSet *fs = new KWFrameSet(); fs->setName(shape->shapeId()); frame = new KWFrame(shape, fs); } } Q_ASSERT(KWFrameSet::from(shape)); if (!m_frameSets.contains(KWFrameSet::from(shape))) { addFrameSet(KWFrameSet::from(shape)); } if (!(shape->shapeId() == "AnnotationTextShapeID")) { emit shapeAdded(shape, KoShapeManager::PaintShapeOnAdd); } shape->update(); } void KWDocument::removeShape(KoShape *shape) { debugWords << "shape=" << shape; KWFrameSet *fs = KWFrameSet::from(shape); if (fs) { // not all shapes have to have to be in a frameset if (fs->shapeCount() == 1) // last shape on FrameSet removeFrameSet(fs); // shape and frameset will be deleted when the shape is deleted else fs->removeShape(shape); } else { // not in a frameset, but we still have to remove it from views. emit shapeRemoved(shape); } if (shape->shapeId() == "AnnotationTextShapeID") { annotationLayoutManager()->removeAnnotationShape(shape); } } void KWDocument::shapesRemoved(const QList &shapes, KUndo2Command *command) { QMap > anchors; QMap > annotations; const KoAnnotationManager *annotationManager = textRangeManager()->annotationManager(); foreach (KoShape *shape, shapes) { KoShapeAnchor *anchor = shape->anchor(); if (anchor && anchor->textLocation()) { const QTextDocument *document = anchor->textLocation()->document(); if (document) { KoTextEditor *editor = KoTextDocument(document).textEditor(); anchors[editor].append(anchor); } break; } foreach (const QString &name, annotationManager->annotationNameList()) { KoAnnotation *annotation = annotationManager->annotation(name); if (annotation->annotationShape() == shape) { // Remove From annotation layout manager. KoTextEditor *editor = KoTextDocument(annotation->document()).textEditor(); annotations[editor].append(annotation); break; } } } QMap >::const_iterator anchorIter(anchors.constBegin()); for (; anchorIter != anchors.constEnd(); ++anchorIter) { anchorIter.key()->removeAnchors(anchorIter.value(), command); } QMap >::const_iterator annotationIter(annotations.constBegin()); for (; annotationIter != annotations.constEnd(); ++annotationIter) { annotationIter.key()->removeAnnotations(annotationIter.value(), command); } } QPixmap KWDocument::generatePreview(const QSize &size) { // use first page as preview for all pages KWPage firstPage = pageManager()->begin(); if (! firstPage.isValid()) { // TODO: what to return for no page? return QPixmap(); } // use shape manager from canvasItem even for QWidget environments // if using the shape manager from one of the views there is no guarantee // that the view, its canvas and the shapemanager is not destroyed in between KoShapeManager* shapeManager = static_cast(documentPart()->canvasItem(this))->shapeManager(); return QPixmap::fromImage(firstPage.thumbnail(size, shapeManager, true)); } void KWDocument::paintContent(QPainter &, const QRect &) { } KWPage KWDocument::insertPage(int afterPageNum, const QString &masterPageName) { debugWords << "afterPageNum=" << afterPageNum << "masterPageName=" << masterPageName; //KWPage prevPage = m_document->pageManager().page(m_afterPageNum); KWPageStyle pageStyle = pageManager()->pageStyle(masterPageName); KWPage page = pageManager()->insertPage(afterPageNum + 1, pageStyle); Q_ASSERT(page.isValid()); Q_ASSERT(page.pageNumber() >= 1 && page.pageNumber() <= pageManager()->pageCount()); // Set the y-offset of the new page. KWPage prevPage = page.previous(); if (prevPage.isValid()) { KoInsets padding = pageManager()->padding(); //TODO Shouldn't this be style dependent ? page.setOffsetInDocument(prevPage.offsetInDocument() + prevPage.height() + padding.top + padding.bottom); } else { page.setOffsetInDocument(0.0); } debugWords << "pageNumber=" << page.pageNumber(); // Create the KWTextFrame's for the new KWPage KWFrameLayout *framelayout = frameLayout(); framelayout->createNewFramesForPage(page.pageNumber()); // make sure we have updated the view before we do anything else firePageSetupChanged(); return page; } KWPage KWDocument::appendPage(const QString &masterPageName) { int number = 0; KWPage last = m_pageManager.last(); if (last.isValid()) number = last.pageNumber(); return insertPage(number, masterPageName); } void KWDocument::firePageSetupChanged() { debugWords; if (inlineTextObjectManager()) inlineTextObjectManager()->setProperty(KoInlineObject::PageCount, pageCount()); emit pageSetupChanged(); } void KWDocument::removeFrameSet(KWFrameSet *fs) { debugWords << "frameSet=" << fs; m_frameSets.removeAt(m_frameSets.indexOf(fs)); setModified(true); foreach (KoShape *shape, fs->shapes()) removeSequencedShape(shape); disconnect(fs, SIGNAL(shapeAdded(KoShape*)), this, SLOT(addSequencedShape(KoShape*))); disconnect(fs, SIGNAL(shapeRemoved(KoShape*)), this, SLOT(removeSequencedShape(KoShape*))); } void KWDocument::relayout(QList framesets) { if (framesets.isEmpty()) framesets = m_frameSets; debugWords << "frameSets=" << framesets; // we switch to the interaction tool to avoid crashes if the tool was editing a frame. //KoToolManager::instance()->switchToolRequested(KoInteractionTool_ID); // remove header/footer frames that are not visible. //m_frameLayout.cleanupHeadersFooters(); // create new frames and lay them out on the pages foreach (const KWPage &page, m_pageManager.pages()) { m_frameLayout.createNewFramesForPage(page.pageNumber()); } // re-layout the content displayed within the pages foreach (KWFrameSet *fs, framesets) { KWTextFrameSet *tfs = dynamic_cast(fs); if (!tfs) continue; KoTextDocumentLayout *lay = dynamic_cast(tfs->document()->documentLayout()); Q_ASSERT(lay); if (tfs->textFrameSetType() == Words::MainTextFrameSet && m_layoutProgressUpdater) { connect(lay, SIGNAL(layoutProgressChanged(int)), this, SLOT(layoutProgressChanged(int))); connect(lay, SIGNAL(finishedLayout()), this, SLOT(layoutFinished())); } // schedule all calls so multiple layout calls are compressed lay->scheduleLayout(); } firePageSetupChanged(); } void KWDocument::layoutProgressChanged(int percent) { Q_ASSERT(m_layoutProgressUpdater); m_layoutProgressUpdater->setProgress(percent); } void KWDocument::layoutFinished() { Q_ASSERT(m_layoutProgressUpdater); disconnect(QObject::sender(), SIGNAL(layoutProgressChanged(int)), this, SLOT(layoutProgressChanged(int))); disconnect(QObject::sender(), SIGNAL(finishedLayout()), this, SLOT(layoutFinished())); m_layoutProgressUpdater->setProgress(100); m_layoutProgressUpdater = 0; // free the instance } void KWDocument::addFrameSet(KWFrameSet *fs) { debugWords << "frameSet=" << fs; Q_ASSERT(!m_frameSets.contains(fs)); setModified(true); // Be sure we add headers and footers to the beginning of the m_frameSets QList and every other KWFrameTextType // after them so future operations iterating over that QList always handle headers and footers first. int insertAt = m_frameSets.count(); KWTextFrameSet *tfs = dynamic_cast(fs); if (tfs && Words::isHeaderFooter(tfs)) { insertAt = 0; for(int i = 0; i < m_frameSets.count(); ++i) { KWTextFrameSet *_tfs = dynamic_cast(m_frameSets[i]); if (_tfs && !Words::isHeaderFooter(_tfs)) { insertAt = i; break; } } } m_frameSets.insert(insertAt, fs); foreach (KoShape *shape, fs->shapes()) addSequencedShape(shape); if (KWTextFrameSet *tfs = dynamic_cast(fs)) { Q_ASSERT(tfs->pageManager() == pageManager()); if (tfs->textFrameSetType() == Words::MainTextFrameSet) { KoTextDocumentLayout *lay = dynamic_cast(tfs->document()->documentLayout()); Q_ASSERT(lay); connect(lay, SIGNAL(finishedLayout()), this, SLOT(mainTextFrameSetLayoutDone())); } } connect(fs, SIGNAL(shapeAdded(KoShape*)), this, SLOT(addSequencedShape(KoShape*))); connect(fs, SIGNAL(shapeRemoved(KoShape*)), this, SLOT(removeSequencedShape(KoShape*))); } void KWDocument::addSequencedShape(KoShape *shape) { debugWords << "shape=" << shape << "frameSet=" << KWFrameSet::from(shape); //firePageSetupChanged(); emit shapeAdded(shape, KoShapeManager::AddWithoutRepaint); } void KWDocument::removeSequencedShape(KoShape *shape) { debugWords << "shape=" << shape << "frameSet=" << KWFrameSet::from(shape); emit shapeRemoved(shape); KWPage page = pageManager()->page(shape); if (!page.isValid()) return; if (!page.isAutoGenerated()) return; if (page != pageManager()->last() || page == pageManager()->begin()) return; // can only delete last page. foreach (KWFrameSet *fs, m_frameSets) { foreach (KoShape *s, fs->shapes()) { if (page == pageManager()->page(s)) return; } } //KWPageRemoveCommand *cmd = new KWPageRemoveCommand(this, page); //cmd->redo(); //delete cmd; } void KWDocument::mainTextFrameSetLayoutDone() { m_mainFramesetEverFinished = true; } KWFrameSet *KWDocument::frameSetByName(const QString &name) { foreach (KWFrameSet *fs, m_frameSets) { if (fs->name() == name) return fs; } return 0; } KWTextFrameSet *KWDocument::mainFrameSet() const { return m_frameLayout.mainFrameSet(); } KoInlineTextObjectManager *KWDocument::inlineTextObjectManager() const { QVariant var = resourceManager()->resource(KoText::InlineTextObjectManager); return var.value(); } KoTextRangeManager *KWDocument::textRangeManager() const { QVariant var = resourceManager()->resource(KoText::TextRangeManager); return var.value(); } QString KWDocument::uniqueFrameSetName(const QString &suggestion) { // make up a new name for the frameset, use "[base] [digits]" as template. // Fully translatable naturally :) return renameFrameSet("", suggestion); } QString KWDocument::suggestFrameSetNameForCopy(const QString &base) { // make up a new name for the frameset, use Copy[digits]-[base] as template. // Fully translatable naturally :) return renameFrameSet(i18n("Copy"), base); } QString KWDocument::renameFrameSet(const QString &prefix, const QString &base) { if (! frameSetByName(base)) return base; QString before, after; QRegExp findDigits("\\d+"); int pos = findDigits.indexIn(base); if (pos >= 0) { before = base.left(pos); after = base.mid(pos + findDigits.matchedLength()); } else if (prefix.isEmpty()) before = base + ' '; else { before = prefix; after = ' ' + base; } if (! before.startsWith(prefix)) { before = prefix + before; } int count = 0; while (true) { QString name = QString(before + (count == 0 ? QString() : QString::number(count)) + after).trimmed(); if (! frameSetByName(name)) return name; count++; } } // *** LOADING void KWDocument::initEmpty() { clear(); appendPage("Standard"); Q_ASSERT(resourceManager()->hasResource(KoText::StyleManager)); KoStyleManager *styleManager = resourceManager()->resource(KoText::StyleManager).value(); Q_ASSERT(styleManager); KoParagraphStyle *parag = new KoParagraphStyle(); parag->setName(i18n("Standard")); parag->setFontPointSize(12); parag->setFontWeight(QFont::Normal); styleManager->add(parag); parag = new KoParagraphStyle(); parag->setName(i18n("Document Title")); parag->setFontPointSize(24); parag->setFontWeight(QFont::Bold); parag->setAlignment(Qt::AlignCenter); styleManager->add(parag); parag = new KoParagraphStyle(); parag->setName(i18n("Head 1")); parag->setFontPointSize(20); parag->setFontWeight(QFont::Bold); styleManager->add(parag); parag = new KoParagraphStyle(); parag->setName(i18n("Head 2")); parag->setFontPointSize(16); parag->setFontWeight(QFont::Bold); styleManager->add(parag); parag = new KoParagraphStyle(); parag->setName(i18n("Head 3")); parag->setFontPointSize(12); parag->setFontWeight(QFont::Bold); styleManager->add(parag); parag = new KoParagraphStyle(); parag->setName(i18n("Bullet List")); KoListStyle *list = new KoListStyle(parag); KoListLevelProperties llp = list->levelProperties(0); llp.setLabelType(KoListStyle::BulletCharLabelType); llp.setBulletCharacter(QChar(0x2022)); // Bullet list->setLevelProperties(llp); parag->setListStyle(list); styleManager->add(parag); setMimeTypeAfterLoading("application/vnd.oasis.opendocument.text"); KoDocument::initEmpty(); clearUndoHistory(); } void KWDocument::clear() { // document defaults foreach (const KWPage &page, m_pageManager.pages()) m_pageManager.removePage(page); m_pageManager.clearPageStyles(); m_config.load(this); // re-load values foreach (KWFrameSet *fs, m_frameSets) { removeFrameSet(fs); delete fs; } // industry standard for bleed KoInsets padding; padding.top = MM_TO_POINT(3); padding.bottom = MM_TO_POINT(3); padding.left = MM_TO_POINT(3); padding.right = MM_TO_POINT(3); m_pageManager.setPadding(padding); if (inlineTextObjectManager()) inlineTextObjectManager()->setProperty(KoInlineObject::PageCount, pageCount()); } void KWDocument::setupOpenFileSubProgress() { if (progressUpdater()) { m_layoutProgressUpdater = progressUpdater()->startSubtask(1, "Layouting"); } } bool KWDocument::loadOdf(KoOdfReadStore &odfStore) { clear(); KWOdfLoader loader(this); bool rc = loader.load(odfStore); if (rc) endOfLoading(); return rc; } bool KWDocument::loadXML(const KoXmlDocument &doc, KoStore *store) { Q_UNUSED(doc); Q_UNUSED(store); return false; } void KWDocument::endOfLoading() // called by both oasis and oldxml { debugWords; // Get the master page name of the first page. QString firstPageMasterName; if (mainFrameSet()) { QTextBlock block = mainFrameSet()->document()->firstBlock(); firstPageMasterName = block.blockFormat().stringProperty(KoParagraphStyle::MasterPageName); } appendPage(firstPageMasterName); relayout(); debugWords << "KWDocument::endOfLoading done"; #if 0 // Note that more stuff will happen in completeLoading firePageSetupChanged(); #endif setModified(false); } bool KWDocument::saveOdf(SavingContext &documentContext) { KWOdfWriter writer(this); return writer.save(documentContext.odfStore, documentContext.embeddedSaver); } void KWDocument::updatePagesForStyle(const KWPageStyle &style) { debugWords << "pageStyleName=" << style.name(); QList framesets; foreach(KWFrameSet *fs, frameLayout()->getFrameSets(style)) { KWTextFrameSet* tfs = dynamic_cast(fs); if (tfs) framesets.append(tfs); } int pageNumber = -1; foreach (const KWPage &page, pageManager()->pages()) { if (page.pageStyle() == style) { pageNumber = page.pageNumber(); break; } } //Q_ASSERT(pageNumber >= 1); if (pageNumber < 1) return; foreach(KWFrameSet *fs, framesets) { static_cast(fs)->rootAreaProvider()->clearPages(pageNumber); } relayout(framesets); } void KWDocument::saveConfig() { // KConfigGroup group(KoGlobal::calligraConfig(), "Spelling"); // group.writeEntry("PersonalDict", m_spellCheckPersonalDict); m_config.save(); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup interface = config->group("Interface"); interface.writeEntry("ResolutionX", gridData().gridX()); interface.writeEntry("ResolutionY", gridData().gridY()); } KoShape *KWDocument::findTargetTextShape(KoShape *shape) const { KoShape *result = 0; int area = 0; QRectF br = shape->boundingRect(); // now find the frame that is closest to the frame we want to inline. foreach (KoShape *shape, mainFrameSet()->shapes()) { QRectF intersection = br.intersected(shape->boundingRect()); int intersectArea = qRound(intersection.width() * intersection.height()); if (intersectArea > area) { result = shape; area = intersectArea; } else if (result == 0) { // TODO check distance between frames or something. } } return result; } KoShapeAnchor* KWDocument::anchorOfShape(KoShape *shape) const { Q_ASSERT(mainFrameSet()); Q_ASSERT(shape); KoShapeAnchor *anchor = shape->anchor(); if (!anchor) { anchor = new KoShapeAnchor(shape); anchor->setAnchorType(KoShapeAnchor::AnchorPage); anchor->setHorizontalPos(KoShapeAnchor::HFromLeft); anchor->setVerticalPos(KoShapeAnchor::VFromTop); shape->setAnchor(anchor); } return anchor; } KWFrame *KWDocument::frameOfShape(KoShape* shape) const { while (shape) { KWFrame *answer = dynamic_cast(shape->applicationData()); if (answer) return answer; if (shape->parent() == 0) break; shape = shape->parent(); } KWFrame *answer = dynamic_cast(shape->applicationData()); if (answer == 0) { // this may be a clipping shape containing the frame-shape KoShapeContainer *container = dynamic_cast(shape); if (container && container->shapeCount() == 1) { answer = dynamic_cast(container->shapes()[0]->applicationData()); } } return answer; } KoDocumentInfoDlg *KWDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { KoDocumentInfoDlg *dlg = new KoDocumentInfoDlg(parent, docInfo); KoMainWindow *mainwin = dynamic_cast(parent); if (mainwin) { connect(dlg, SIGNAL(saveRequested()), mainwin, SLOT(slotFileSave())); } #ifdef SHOULD_BUILD_RDF KoPageWidgetItem *rdfEditWidget = new KoDocumentRdfEditWidget(static_cast(documentRdf())); dlg->addPageItem(rdfEditWidget); #endif return dlg; }