File indexing completed on 2024-05-12 16:33:19

0001 /* This file is part of the KDE project
0002  * Copyright (C) 2007,2011 Jan Hambrecht <jaham@gmx.net>
0003  * Copyright (C) 2008 Rob Buis <buis@kde.org>
0004  *
0005  * This library is free software; you can redistribute it and/or
0006  * modify it under the terms of the GNU Library General Public
0007  * License as published by the Free Software Foundation; either
0008  * version 2 of the License, or (at your option) any later version.
0009  *
0010  * This library is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013  * Library General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU Library General Public License
0016  * along with this library; see the file COPYING.LIB.  If not, write to
0017  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018  * Boston, MA 02110-1301, USA.
0019  */
0020 
0021 #include "ArtisticTextTool.h"
0022 #include "ArtisticTextToolSelection.h"
0023 #include "AttachTextToPathCommand.h"
0024 #include "DetachTextFromPathCommand.h"
0025 #include "AddTextRangeCommand.h"
0026 #include "RemoveTextRangeCommand.h"
0027 #include "ArtisticTextShapeConfigWidget.h"
0028 #include "ArtisticTextShapeOnPathWidget.h"
0029 #include "MoveStartOffsetStrategy.h"
0030 #include "SelectTextStrategy.h"
0031 #include "ChangeTextOffsetCommand.h"
0032 #include "ChangeTextFontCommand.h"
0033 #include "ChangeTextAnchorCommand.h"
0034 #include "ReplaceTextRangeCommand.h"
0035 
0036 #include <KoCanvasBase.h>
0037 #include <KoSelection.h>
0038 #include <KoShapeManager.h>
0039 #include <KoPointerEvent.h>
0040 #include <KoPathShape.h>
0041 #include <KoShapeBackground.h>
0042 #include <KoShapeController.h>
0043 #include <KoShapeContainer.h>
0044 #include <KoInteractionStrategy.h>
0045 #include <KoIcon.h>
0046 
0047 #include <klocalizedstring.h>
0048 #include <kstandardaction.h>
0049 #include <QAction>
0050 #include <QDebug>
0051 
0052 #include <QGridLayout>
0053 #include <QToolButton>
0054 #include <QCheckBox>
0055 #include <QPainter>
0056 #include <QPainterPath>
0057 #include <kundo2command.h>
0058 
0059 #include <float.h>
0060 #include <math.h>
0061 
0062 const int BlinkInterval = 500;
0063 
0064 static bool hit(const QKeySequence &input, KStandardShortcut::StandardShortcut shortcut)
0065 {
0066     foreach (const QKeySequence & ks, KStandardShortcut::shortcut(shortcut)) {
0067         if (input == ks)
0068             return true;
0069     }
0070     return false;
0071 }
0072 
0073 ArtisticTextTool::ArtisticTextTool(KoCanvasBase *canvas)
0074     : KoToolBase(canvas), m_selection(canvas, this), m_currentShape(0), m_hoverText(0), m_hoverPath(0), m_hoverHandle(false)
0075     , m_textCursor( -1 ), m_showCursor( true ), m_currentStrategy(0)
0076 {
0077     m_detachPath  = new QAction(koIcon("text-remove-from-path"), i18n("Detach Path"), this);
0078     m_detachPath->setEnabled( false );
0079     connect( m_detachPath, SIGNAL(triggered()), this, SLOT(detachPath()) );
0080     addAction("artistictext_detach_from_path", m_detachPath);
0081 
0082     m_convertText  = new QAction(koIcon("pathshape"), i18n("Convert to Path"), this);
0083     m_convertText->setEnabled( false );
0084     connect( m_convertText, SIGNAL(triggered()), this, SLOT(convertText()) );
0085     addAction("artistictext_convert_to_path", m_convertText);
0086 
0087     m_fontBold = new QAction(koIcon("format-text-bold"), i18n("Bold text"), this);
0088     m_fontBold->setCheckable(true);
0089     connect(m_fontBold, SIGNAL(toggled(bool)), this, SLOT(toggleFontBold(bool)));
0090     addAction("artistictext_font_bold", m_fontBold);
0091 
0092     m_fontItalic = new QAction(koIcon("format-text-italic"), i18n("Italic text"), this);
0093     m_fontItalic->setCheckable(true);
0094     connect(m_fontItalic, SIGNAL(toggled(bool)), this, SLOT(toggleFontItalic(bool)));
0095     addAction("artistictext_font_italic", m_fontItalic);
0096 
0097     m_superScript = new QAction(koIcon("format-text-superscript"), i18n("Superscript"), this);
0098     m_superScript->setCheckable(true);
0099     connect(m_superScript, SIGNAL(triggered()), this, SLOT(setSuperScript()));
0100     addAction("artistictext_superscript", m_superScript);
0101 
0102     m_subScript = new QAction(koIcon("format-text-subscript"), i18n("Subscript"), this);
0103     m_subScript->setCheckable(true);
0104     connect(m_subScript, SIGNAL(triggered()), this, SLOT(setSubScript()));
0105     addAction("artistictext_subscript", m_subScript);
0106 
0107     QAction *anchorStart = new QAction(koIcon("format-justify-left"), i18n("Anchor at Start"), this);
0108     anchorStart->setCheckable( true );
0109     anchorStart->setData(ArtisticTextShape::AnchorStart);
0110     addAction("artistictext_anchor_start", anchorStart);
0111 
0112     QAction *anchorMiddle = new QAction(koIcon("format-justify-center"), i18n("Anchor at Middle"), this);
0113     anchorMiddle->setCheckable( true );
0114     anchorMiddle->setData(ArtisticTextShape::AnchorMiddle);
0115     addAction("artistictext_anchor_middle", anchorMiddle);
0116 
0117     QAction *anchorEnd = new QAction(koIcon("format-justify-right"), i18n("Anchor at End"), this);
0118     anchorEnd->setCheckable( true );
0119     anchorEnd->setData(ArtisticTextShape::AnchorEnd);
0120     addAction("artistictext_anchor_end", anchorEnd);
0121 
0122     m_anchorGroup = new QActionGroup(this);
0123     m_anchorGroup->setExclusive(true);
0124     m_anchorGroup->addAction(anchorStart);
0125     m_anchorGroup->addAction(anchorMiddle);
0126     m_anchorGroup->addAction(anchorEnd);
0127     connect(m_anchorGroup, SIGNAL(triggered(QAction*)), this, SLOT(anchorChanged(QAction*)));
0128 
0129     KoShapeManager *manager = canvas->shapeManager();
0130     connect(manager, SIGNAL(selectionContentChanged()), this, SLOT(textChanged()));
0131 
0132     addAction("edit_select_all", KStandardAction::selectAll(this, SLOT(selectAll()), this));
0133     addAction("edit_deselect_all", KStandardAction::deselect(this, SLOT(deselectAll()), this));
0134 
0135     setTextMode(true);
0136 }
0137 
0138 ArtisticTextTool::~ArtisticTextTool()
0139 {
0140     delete m_currentStrategy;
0141 }
0142 
0143 QTransform ArtisticTextTool::cursorTransform() const
0144 {
0145     if (!m_currentShape)
0146         return QTransform();
0147 
0148     QTransform transform;
0149 
0150     const int textLength = m_currentShape->plainText().length();
0151     if (m_textCursor <= textLength) {
0152         const QPointF pos = m_currentShape->charPositionAt(m_textCursor);
0153         const qreal angle = m_currentShape->charAngleAt(m_textCursor);
0154         QFontMetrics metrics(m_currentShape->fontAt(m_textCursor));
0155 
0156         transform.translate( pos.x() - 1, pos.y() );
0157         transform.rotate( 360. - angle );
0158         transform.translate( 0, metrics.descent() );
0159     } else if (m_textCursor <= textLength + m_linefeedPositions.size()) {
0160         const QPointF pos = m_linefeedPositions.value(m_textCursor-textLength-1);
0161         QFontMetrics metrics(m_currentShape->fontAt(textLength-1));
0162         transform.translate(pos.x(), pos.y());
0163         transform.translate(0, metrics.descent());
0164     }
0165 
0166     return transform * m_currentShape->absoluteTransformation(0);
0167 }
0168 
0169 void ArtisticTextTool::paint( QPainter &painter, const KoViewConverter &converter)
0170 {
0171     if (! m_currentShape)
0172         return;
0173 
0174     if (m_showCursor && m_blinkingCursor.isActive() && !m_currentStrategy) {
0175         painter.save();
0176         m_currentShape->applyConversion( painter, converter );
0177         painter.setBrush( Qt::black );
0178         painter.setWorldTransform( cursorTransform(), true );
0179         painter.setClipping( false );
0180         painter.drawPath( m_textCursorShape );
0181         painter.restore();
0182     }
0183     m_showCursor = !m_showCursor;
0184 
0185     if (m_currentShape->isOnPath()) {
0186         painter.save();
0187         m_currentShape->applyConversion( painter, converter );
0188         if (!m_currentShape->baselineShape()) {
0189             painter.setPen(Qt::DotLine);
0190             painter.setBrush(Qt::NoBrush);
0191             painter.drawPath(m_currentShape->baseline());
0192         }
0193         painter.setPen(QPen(Qt::blue, 0));
0194         painter.setBrush(m_hoverHandle ? Qt::red : Qt::white);
0195         painter.drawPath(offsetHandleShape());
0196         painter.restore();
0197     }
0198     if (m_selection.hasSelection()) {
0199         painter.save();
0200         m_selection.paint(painter, converter);
0201         painter.restore();
0202     }
0203 }
0204 
0205 void ArtisticTextTool::repaintDecorations()
0206 {
0207     canvas()->updateCanvas(offsetHandleShape().boundingRect());
0208     if (m_currentShape && m_currentShape->isOnPath()) {
0209         if (!m_currentShape->baselineShape())
0210             canvas()->updateCanvas(m_currentShape->baseline().boundingRect());
0211     }
0212     m_selection.repaintDecoration();
0213 }
0214 
0215 int ArtisticTextTool::cursorFromMousePosition(const QPointF &mousePosition)
0216 {
0217     if (!m_currentShape)
0218         return -1;
0219 
0220     const QPointF pos = m_currentShape->documentToShape(mousePosition);
0221     const int len = m_currentShape->plainText().length();
0222     int hit = -1;
0223     qreal mindist = DBL_MAX;
0224     for ( int i = 0; i <= len;++i ) {
0225         QPointF center = pos - m_currentShape->charPositionAt(i);
0226         if ( (fabs(center.x()) + fabs(center.y())) < mindist ) {
0227             hit = i;
0228             mindist = fabs(center.x()) + fabs(center.y());
0229         }
0230     }
0231     return hit;
0232 }
0233 
0234 void ArtisticTextTool::mousePressEvent( KoPointerEvent *event )
0235 {
0236     if (m_hoverHandle) {
0237         m_currentStrategy = new MoveStartOffsetStrategy(this, m_currentShape);
0238         event->accept();
0239         return;
0240     }
0241     if (m_hoverText) {
0242         if(m_hoverText == m_currentShape) {
0243             // change the text cursor position
0244             int hitCursorPos = cursorFromMousePosition(event->point);
0245             if (hitCursorPos >= 0) {
0246                 setTextCursorInternal(hitCursorPos);
0247                 m_selection.clear();
0248             }
0249             m_currentStrategy = new SelectTextStrategy(this, m_textCursor);
0250             event->accept();
0251             return;
0252         }
0253     }
0254     event->ignore();
0255 }
0256 
0257 void ArtisticTextTool::mouseMoveEvent( KoPointerEvent *event )
0258 {
0259     m_hoverPath = 0;
0260     m_hoverText = 0;
0261 
0262     if (m_currentStrategy) {
0263         m_currentStrategy->handleMouseMove(event->point, event->modifiers());
0264         return;
0265     }
0266 
0267     const bool textOnPath = m_currentShape && m_currentShape->isOnPath();
0268     if (textOnPath) {
0269         QPainterPath handle = offsetHandleShape();
0270         QPointF handleCenter = handle.boundingRect().center();
0271         if (handleGrabRect(event->point).contains(handleCenter)) {
0272             // mouse is on offset handle
0273             if (!m_hoverHandle)
0274                 canvas()->updateCanvas(handle.boundingRect());
0275             m_hoverHandle = true;
0276         } else {
0277             if (m_hoverHandle)
0278                 canvas()->updateCanvas(handle.boundingRect());
0279             m_hoverHandle = false;
0280         }
0281     }
0282     if (!m_hoverHandle) {
0283         // find text or path shape at cursor position
0284         QList<KoShape*> shapes = canvas()->shapeManager()->shapesAt( handleGrabRect(event->point) );
0285         if (shapes.contains(m_currentShape)) {
0286             m_hoverText = m_currentShape;
0287         } else {
0288             foreach( KoShape * shape, shapes ) {
0289                 ArtisticTextShape * text = dynamic_cast<ArtisticTextShape*>( shape );
0290                 if (text && !m_hoverText) {
0291                     m_hoverText = text;
0292                 }
0293                 KoPathShape * path = dynamic_cast<KoPathShape*>( shape );
0294                 if (path && !m_hoverPath) {
0295                     m_hoverPath = path;
0296                 }
0297                 if(m_hoverPath && m_hoverText)
0298                     break;
0299             }
0300         }
0301     }
0302 
0303     const bool hoverOnBaseline = textOnPath && m_currentShape->baselineShape() == m_hoverPath;
0304     // update cursor and status text
0305     if ( m_hoverText ) {
0306         useCursor( QCursor( Qt::IBeamCursor ) );
0307         if (m_hoverText == m_currentShape)
0308             emit statusTextChanged(i18n("Click to change cursor position."));
0309         else
0310             emit statusTextChanged(i18n("Click to select text shape."));
0311     } else if( m_hoverPath && m_currentShape && !hoverOnBaseline) {
0312         useCursor( QCursor( Qt::PointingHandCursor ) );
0313         emit statusTextChanged(i18n("Double click to put text on path."));
0314     } else  if (m_hoverHandle) {
0315         useCursor( QCursor( Qt::ArrowCursor ) );
0316         emit statusTextChanged( i18n("Drag handle to change start offset.") );
0317     } else {
0318         useCursor( QCursor( Qt::ArrowCursor ) );
0319         if (m_currentShape)
0320             emit statusTextChanged( i18n("Press escape to finish editing.") );
0321         else
0322             emit statusTextChanged("");
0323     }
0324 }
0325 
0326 void ArtisticTextTool::mouseReleaseEvent( KoPointerEvent *event )
0327 {
0328     if (m_currentStrategy) {
0329         m_currentStrategy->finishInteraction(event->modifiers());
0330         KUndo2Command *cmd = m_currentStrategy->createCommand();
0331         if (cmd)
0332             canvas()->addCommand(cmd);
0333         delete m_currentStrategy;
0334         m_currentStrategy = 0;
0335         event->accept();
0336         return;
0337     }
0338     updateActions();
0339     event->ignore();
0340 }
0341 
0342 void ArtisticTextTool::shortcutOverrideEvent(QKeyEvent *event)
0343 {
0344     QKeySequence item(event->key() | ((Qt::ControlModifier | Qt::AltModifier) & event->modifiers()));
0345     if (hit(item, KStandardShortcut::Begin) ||
0346         hit(item, KStandardShortcut::End)) {
0347         event->accept();
0348     }
0349 }
0350 
0351 void ArtisticTextTool::mouseDoubleClickEvent(KoPointerEvent */*event*/)
0352 {
0353     if (m_hoverPath && m_currentShape) {
0354         if (!m_currentShape->isOnPath() || m_currentShape->baselineShape() != m_hoverPath) {
0355             m_blinkingCursor.stop();
0356             m_showCursor = false;
0357             updateTextCursorArea();
0358             canvas()->addCommand( new AttachTextToPathCommand( m_currentShape, m_hoverPath ) );
0359             m_blinkingCursor.start( BlinkInterval );
0360             updateActions();
0361             m_hoverPath = 0;
0362             m_linefeedPositions.clear();
0363             return;
0364         }
0365     }
0366 }
0367 
0368 void ArtisticTextTool::keyPressEvent(QKeyEvent *event)
0369 {
0370     if (event->key() == Qt::Key_Escape) {
0371         event->ignore();
0372         return;
0373     }
0374 
0375     event->accept();
0376     if ( m_currentShape && textCursor() > -1 ) {
0377         switch(event->key())
0378         {
0379         case Qt::Key_Delete:
0380             if (m_selection.hasSelection()) {
0381                 removeFromTextCursor(m_selection.selectionStart(), m_selection.selectionCount());
0382             } else if( textCursor() >= 0 && textCursor() < m_currentShape->plainText().length()) {
0383                 removeFromTextCursor( textCursor(), 1 );
0384             }
0385             break;
0386         case Qt::Key_Backspace:
0387             if (m_selection.hasSelection()) {
0388                 removeFromTextCursor(m_selection.selectionStart(), m_selection.selectionCount());
0389             } else {
0390                 removeFromTextCursor( textCursor()-1, 1 );
0391             }
0392             break;
0393         case Qt::Key_Right:
0394             if (event->modifiers() & Qt::ShiftModifier) {
0395                 int selectionStart, selectionEnd;
0396                 if (m_selection.hasSelection()) {
0397                     selectionStart = m_selection.selectionStart();
0398                     selectionEnd = selectionStart + m_selection.selectionCount();
0399                     if (textCursor() == selectionStart)
0400                         selectionStart = textCursor()+1;
0401                     else if(textCursor() == selectionEnd)
0402                         selectionEnd = textCursor()+1;
0403                 } else {
0404                     selectionStart = textCursor();
0405                     selectionEnd = textCursor()+1;
0406                 }
0407                 m_selection.selectText(selectionStart, selectionEnd);
0408             } else {
0409                 m_selection.clear();
0410             }
0411             setTextCursor(m_currentShape, textCursor() + 1);
0412             break;
0413         case Qt::Key_Left:
0414             if (event->modifiers() & Qt::ShiftModifier) {
0415                 int selectionStart, selectionEnd;
0416                 if (m_selection.hasSelection()) {
0417                     selectionStart = m_selection.selectionStart();
0418                     selectionEnd = selectionStart + m_selection.selectionCount();
0419                     if (textCursor() == selectionStart)
0420                         selectionStart = textCursor()-1;
0421                     else if(textCursor() == selectionEnd)
0422                         selectionEnd = textCursor()-1;
0423                 } else {
0424                     selectionEnd = textCursor();
0425                     selectionStart = textCursor()-1;
0426                 }
0427                 m_selection.selectText(selectionStart, selectionEnd);
0428             } else {
0429                 m_selection.clear();
0430             }
0431             setTextCursor(m_currentShape, textCursor() - 1);
0432             break;
0433         case Qt::Key_Home:
0434             if (event->modifiers() & Qt::ShiftModifier) {
0435                 const int selectionStart = 0;
0436                 const int selectionEnd = m_selection.hasSelection() ? m_selection.selectionStart()+m_selection.selectionCount() : m_textCursor;
0437                 m_selection.selectText(selectionStart, selectionEnd);
0438             } else {
0439                 m_selection.clear();
0440             }
0441             setTextCursor(m_currentShape, 0);
0442             break;
0443         case Qt::Key_End:
0444             if (event->modifiers() & Qt::ShiftModifier) {
0445                 const int selectionStart = m_selection.hasSelection() ? m_selection.selectionStart() : m_textCursor;
0446                 const int selectionEnd = m_currentShape->plainText().length();
0447                 m_selection.selectText(selectionStart, selectionEnd);
0448             } else {
0449                 m_selection.clear();
0450             }
0451             setTextCursor(m_currentShape, m_currentShape->plainText().length());
0452             break;
0453         case Qt::Key_Return:
0454         case Qt::Key_Enter:
0455         {
0456             const int textLength = m_currentShape->plainText().length();
0457             if (m_textCursor >= textLength) {
0458                 // get font metrics for last character
0459                 QFontMetrics metrics(m_currentShape->fontAt(textLength-1));
0460                 const qreal offset = m_currentShape->size().height() + (m_linefeedPositions.size() + 1) * metrics.height();
0461                 m_linefeedPositions.append(QPointF(0, offset));
0462                 setTextCursor(m_currentShape, textCursor()+1);
0463             }
0464             break;
0465         }
0466         default:
0467             if (event->text().isEmpty()) {
0468                 event->ignore();
0469                 return;
0470             }
0471             if (m_selection.hasSelection()) {
0472                 removeFromTextCursor(m_selection.selectionStart(), m_selection.selectionCount());
0473             }
0474             addToTextCursor( event->text() );
0475         }
0476     } else {
0477         event->ignore();
0478     }
0479 }
0480 
0481 void ArtisticTextTool::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
0482 {
0483     Q_UNUSED(toolActivation);
0484     foreach (KoShape *shape, shapes) {
0485         ArtisticTextShape *text = dynamic_cast<ArtisticTextShape*>( shape );
0486         if(text) {
0487             setCurrentShape(text);
0488             break;
0489         }
0490     }
0491     if(!m_currentShape) {
0492         // none found
0493         emit done();
0494         return;
0495     }
0496     useCursor(Qt::ArrowCursor);
0497     m_hoverText = 0;
0498     m_hoverPath = 0;
0499 
0500     updateActions();
0501     emit statusTextChanged( i18n("Press return to finish editing.") );
0502     repaintDecorations();
0503 
0504     KoShapeManager *manager = canvas()->shapeManager();
0505     connect(manager, SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged()));
0506 }
0507 
0508 void ArtisticTextTool::blinkCursor()
0509 {
0510     updateTextCursorArea();
0511 }
0512 
0513 void ArtisticTextTool::deactivate()
0514 {
0515     if ( m_currentShape ) {
0516         if( m_currentShape->plainText().isEmpty() ) {
0517             canvas()->addCommand( canvas()->shapeController()->removeShape( m_currentShape ) );
0518         }
0519         setCurrentShape(0);
0520     }
0521     m_hoverPath = 0;
0522     m_hoverText = 0;
0523 
0524     KoShapeManager *manager = canvas()->shapeManager();
0525     disconnect(manager, SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged()));
0526 }
0527 
0528 void ArtisticTextTool::updateActions()
0529 {
0530     if( m_currentShape ) {
0531         const QFont font = m_currentShape->fontAt(textCursor());
0532         const CharIndex index = m_currentShape->indexOfChar(textCursor());
0533         ArtisticTextRange::BaselineShift baselineShift = ArtisticTextRange::None;
0534         if (index.first >= 0) {
0535             baselineShift = m_currentShape->text().at(index.first).baselineShift();
0536         }
0537         m_fontBold->blockSignals(true);
0538         m_fontBold->setChecked(font.bold());
0539         m_fontBold->blockSignals(false);
0540         m_fontBold->setEnabled(true);
0541         m_fontItalic->blockSignals(true);
0542         m_fontItalic->setChecked(font.italic());
0543         m_fontItalic->blockSignals(false);
0544         m_fontItalic->setEnabled(true);
0545         m_detachPath->setEnabled( m_currentShape->isOnPath() );
0546         m_convertText->setEnabled( true );
0547         m_anchorGroup->blockSignals(true);
0548         foreach(QAction *action, m_anchorGroup->actions()) {
0549             if (action->data().toInt() == m_currentShape->textAnchor())
0550                 action->setChecked(true);
0551         }
0552         m_anchorGroup->blockSignals(false);
0553         m_anchorGroup->setEnabled(true);
0554         m_superScript->blockSignals(true);
0555         m_superScript->setChecked(baselineShift == ArtisticTextRange::Super);
0556         m_superScript->blockSignals(false);
0557         m_subScript->blockSignals(true);
0558         m_subScript->setChecked(baselineShift == ArtisticTextRange::Sub);
0559         m_subScript->blockSignals(false);
0560         m_superScript->setEnabled(true);
0561         m_subScript->setEnabled(true);
0562     } else {
0563         m_detachPath->setEnabled(false);
0564         m_convertText->setEnabled(false);
0565         m_fontBold->setEnabled(false);
0566         m_fontItalic->setEnabled(false);
0567         m_anchorGroup->setEnabled(false);
0568         m_superScript->setEnabled(false);
0569         m_subScript->setEnabled(false);
0570     }
0571 }
0572 
0573 void ArtisticTextTool::detachPath()
0574 {
0575     if( m_currentShape && m_currentShape->isOnPath() ) {
0576         canvas()->addCommand( new DetachTextFromPathCommand( m_currentShape ) );
0577         updateActions();
0578     }
0579 }
0580 
0581 void ArtisticTextTool::convertText()
0582 {
0583     if( ! m_currentShape )
0584         return;
0585 
0586     KoPathShape * path = KoPathShape::createShapeFromPainterPath( m_currentShape->outline() );
0587     path->setParent( m_currentShape->parent() );
0588     path->setZIndex( m_currentShape->zIndex() );
0589     path->setStroke( m_currentShape->stroke() );
0590     path->setBackground( m_currentShape->background() );
0591     path->setTransformation( m_currentShape->transformation() );
0592     path->setShapeId( KoPathShapeId );
0593 
0594     KUndo2Command * cmd = canvas()->shapeController()->addShapeDirect( path );
0595     cmd->setText( kundo2_i18n("Convert to Path") );
0596     canvas()->shapeController()->removeShape( m_currentShape, cmd );
0597     canvas()->addCommand( cmd );
0598 
0599     emit done();
0600 }
0601 
0602 QList<QPointer<QWidget> > ArtisticTextTool::createOptionWidgets()
0603 {
0604     QList<QPointer<QWidget> > widgets;
0605 
0606     ArtisticTextShapeConfigWidget * configWidget = new ArtisticTextShapeConfigWidget(this);
0607     configWidget->setObjectName("ArtisticTextConfigWidget");
0608     configWidget->setWindowTitle(i18n("Text Properties"));
0609     connect(configWidget, SIGNAL(fontFamilyChanged(QFont)), this, SLOT(setFontFamily(QFont)));
0610     connect(configWidget, SIGNAL(fontSizeChanged(int)), this, SLOT(setFontSize(int)));
0611     connect(this, SIGNAL(shapeSelected()), configWidget, SLOT(updateWidget()));
0612     connect(canvas()->shapeManager(), SIGNAL(selectionContentChanged()),
0613             configWidget, SLOT(updateWidget()));
0614     widgets.append(configWidget);
0615 
0616     ArtisticTextShapeOnPathWidget *pathWidget = new ArtisticTextShapeOnPathWidget(this);
0617     pathWidget->setObjectName("ArtisticTextPathWidget");
0618     pathWidget->setWindowTitle(i18n("Text On Path"));
0619     connect(pathWidget, SIGNAL(offsetChanged(int)), this, SLOT(setStartOffset(int)));
0620     connect(this, SIGNAL(shapeSelected()), pathWidget, SLOT(updateWidget()));
0621     connect(canvas()->shapeManager(), SIGNAL(selectionContentChanged()),
0622             pathWidget, SLOT(updateWidget()));
0623     widgets.append(pathWidget);
0624 
0625     if (m_currentShape) {
0626         pathWidget->updateWidget();
0627         configWidget->updateWidget();
0628     }
0629 
0630     return widgets;
0631 }
0632 
0633 KoToolSelection *ArtisticTextTool::selection()
0634 {
0635     return &m_selection;
0636 }
0637 
0638 void ArtisticTextTool::enableTextCursor( bool enable )
0639 {
0640     if ( enable ) {
0641         if( m_currentShape )
0642             setTextCursorInternal( m_currentShape->plainText().length() );
0643         connect( &m_blinkingCursor, SIGNAL(timeout()), this, SLOT(blinkCursor()) );
0644         m_blinkingCursor.start( BlinkInterval );
0645     } else {
0646         m_blinkingCursor.stop();
0647         disconnect( &m_blinkingCursor, SIGNAL(timeout()), this, SLOT(blinkCursor()) );
0648         setTextCursorInternal( -1 );
0649         m_showCursor = false;
0650     }
0651 }
0652 
0653 void ArtisticTextTool::setTextCursor(ArtisticTextShape *textShape, int textCursor)
0654 {
0655     if (!m_currentShape || textShape != m_currentShape)
0656         return;
0657     if (m_textCursor == textCursor || textCursor < 0)
0658         return;
0659     const int textLength = m_currentShape->plainText().length() + m_linefeedPositions.size();
0660     if (textCursor > textLength)
0661         return;
0662     setTextCursorInternal(textCursor);
0663 }
0664 
0665 int ArtisticTextTool::textCursor() const
0666 {
0667     return m_textCursor;
0668 }
0669 
0670 void ArtisticTextTool::updateTextCursorArea() const
0671 {
0672     if( ! m_currentShape || m_textCursor < 0 )
0673         return;
0674 
0675     QRectF bbox = cursorTransform().mapRect( m_textCursorShape.boundingRect() );
0676     canvas()->updateCanvas( bbox );
0677 }
0678 
0679 void ArtisticTextTool::setCurrentShape(ArtisticTextShape *currentShape)
0680 {
0681     if (m_currentShape == currentShape)
0682         return;
0683     enableTextCursor( false );
0684     m_currentShape = currentShape;
0685     m_selection.setSelectedShape(m_currentShape);
0686     if (m_currentShape)
0687         enableTextCursor( true );
0688     emit shapeSelected();
0689 }
0690 
0691 void ArtisticTextTool::setTextCursorInternal( int textCursor )
0692 {
0693     updateTextCursorArea();
0694     m_textCursor = textCursor;
0695     createTextCursorShape();
0696     updateTextCursorArea();
0697     updateActions();
0698     emit shapeSelected();
0699 }
0700 
0701 void ArtisticTextTool::createTextCursorShape()
0702 {
0703     if (m_textCursor < 0 || ! m_currentShape)
0704         return;
0705     const QRectF extents = m_currentShape->charExtentsAt(m_textCursor);
0706     m_textCursorShape = QPainterPath();
0707     m_textCursorShape.addRect( 0, 0, 1, -extents.height() );
0708     m_textCursorShape.closeSubpath();
0709 }
0710 
0711 void ArtisticTextTool::removeFromTextCursor( int from, unsigned int count )
0712 {
0713     if ( from >= 0 ) {
0714         if (m_selection.hasSelection()) {
0715             // clear selection before text is removed, or else selection will be invalid
0716             m_selection.clear();
0717         }
0718         KUndo2Command *cmd = new RemoveTextRangeCommand(this, m_currentShape, from, count);
0719         canvas()->addCommand( cmd );
0720     }
0721 }
0722 
0723 void ArtisticTextTool::addToTextCursor( const QString &str )
0724 {
0725     if ( !str.isEmpty() && m_textCursor > -1 ) {
0726         QString printable;
0727         for ( int i = 0;i < str.length();i++ ) {
0728             if ( str[i].isPrint() )
0729                 printable.append( str[i] );
0730         }
0731         unsigned int len = printable.length();
0732         if ( len ) {
0733             const int textLength = m_currentShape->plainText().length();
0734             if (m_textCursor <= textLength) {
0735                 KUndo2Command *cmd = new AddTextRangeCommand(this, m_currentShape, printable, m_textCursor);
0736                 canvas()->addCommand( cmd );
0737             } else if (m_textCursor > textLength && m_textCursor <= textLength + m_linefeedPositions.size()) {
0738                 const QPointF pos = m_linefeedPositions.value(m_textCursor-textLength-1);
0739                 ArtisticTextRange newLine(printable, m_currentShape->fontAt(textLength-1));
0740                 newLine.setXOffsets(QList<qreal>() << pos.x(), ArtisticTextRange::AbsoluteOffset);
0741                 newLine.setYOffsets(QList<qreal>() << pos.y()-m_currentShape->baselineOffset(), ArtisticTextRange::AbsoluteOffset);
0742                 KUndo2Command *cmd = new AddTextRangeCommand(this, m_currentShape, newLine, m_textCursor);
0743                 canvas()->addCommand( cmd );
0744                 m_linefeedPositions.clear();
0745             }
0746         }
0747     }
0748 }
0749 
0750 void ArtisticTextTool::textChanged()
0751 {
0752     if ( !m_currentShape)
0753         return;
0754 
0755     const QString currentText = m_currentShape->plainText();
0756     if (m_textCursor > currentText.length())
0757         setTextCursorInternal(currentText.length());
0758 }
0759 
0760 void ArtisticTextTool::shapeSelectionChanged()
0761 {
0762     KoSelection *selection = canvas()->shapeManager()->selection();
0763     if (selection->isSelected(m_currentShape))
0764         return;
0765 
0766     foreach (KoShape *shape, selection->selectedShapes()) {
0767         ArtisticTextShape *text = dynamic_cast<ArtisticTextShape*>(shape);
0768         if(text) {
0769             setCurrentShape(text);
0770             break;
0771         }
0772     }
0773 }
0774 
0775 QPainterPath ArtisticTextTool::offsetHandleShape()
0776 {
0777     QPainterPath handle;
0778     if (!m_currentShape || !m_currentShape->isOnPath())
0779         return handle;
0780 
0781     const QPainterPath baseline = m_currentShape->baseline();
0782     const qreal offset = m_currentShape->startOffset();
0783     QPointF offsetPoint = baseline.pointAtPercent(offset);
0784     QSizeF paintSize = handlePaintRect(QPointF()).size();
0785 
0786     handle.moveTo(0, 0);
0787     handle.lineTo(0.5*paintSize.width(), paintSize.height());
0788     handle.lineTo(-0.5*paintSize.width(), paintSize.height());
0789     handle.closeSubpath();
0790 
0791     QTransform transform;
0792     transform.translate( offsetPoint.x(), offsetPoint.y() );
0793     transform.rotate(360. - baseline.angleAtPercent(offset));
0794 
0795     return transform.map(handle);
0796 }
0797 
0798 void ArtisticTextTool::setStartOffset(int offset)
0799 {
0800     if (!m_currentShape || !m_currentShape->isOnPath())
0801         return;
0802 
0803     const qreal newOffset = static_cast<qreal>(offset) / 100.0;
0804     if( newOffset != m_currentShape->startOffset() ) {
0805         canvas()->addCommand(new ChangeTextOffsetCommand(m_currentShape, m_currentShape->startOffset(), newOffset));
0806     }
0807 }
0808 
0809 void ArtisticTextTool::changeFontProperty(FontProperty property, const QVariant &value)
0810 {
0811     if (!m_currentShape || !m_selection.hasSelection())
0812         return;
0813 
0814     // build font ranges
0815     const int selectedCharCount = m_selection.selectionCount();
0816     const int selectedCharStart = m_selection.selectionStart();
0817     QList<ArtisticTextRange> ranges = m_currentShape->text();
0818     CharIndex index = m_currentShape->indexOfChar(selectedCharStart);
0819     if (index.first < 0)
0820         return;
0821 
0822     KUndo2Command * cmd = new KUndo2Command;
0823     int collectedCharCount = 0;
0824     while(collectedCharCount < selectedCharCount) {
0825         ArtisticTextRange &range = ranges[index.first];
0826         QFont font = range.font();
0827         switch(property) {
0828         case BoldProperty:
0829             font.setBold(value.toBool());
0830             break;
0831         case ItalicProperty:
0832             font.setItalic(value.toBool());
0833             break;
0834         case FamilyProperty:
0835             font.setFamily(value.toString());
0836             break;
0837         case SizeProperty:
0838             font.setPointSize(value.toInt());
0839             break;
0840         }
0841 
0842         const int changeCount = qMin(selectedCharCount-collectedCharCount, range.text().count()-index.second);
0843         const int changeStart = selectedCharStart+collectedCharCount;
0844         new ChangeTextFontCommand(m_currentShape, changeStart, changeCount, font, cmd);
0845         index.first++;
0846         index.second = 0;
0847         collectedCharCount += changeCount;
0848     }
0849 
0850     canvas()->addCommand(cmd);
0851 }
0852 
0853 void ArtisticTextTool::toggleFontBold(bool enabled)
0854 {
0855     changeFontProperty(BoldProperty, QVariant(enabled));
0856 }
0857 
0858 void ArtisticTextTool::toggleFontItalic(bool enabled)
0859 {
0860     changeFontProperty(ItalicProperty, QVariant(enabled));
0861 }
0862 
0863 void ArtisticTextTool::anchorChanged(QAction* action)
0864 {
0865     if (!m_currentShape)
0866         return;
0867 
0868     ArtisticTextShape::TextAnchor newAnchor = static_cast<ArtisticTextShape::TextAnchor>(action->data().toInt());
0869     if (newAnchor != m_currentShape->textAnchor()) {
0870         canvas()->addCommand(new ChangeTextAnchorCommand(m_currentShape, newAnchor));
0871     }
0872 }
0873 
0874 void ArtisticTextTool::setFontFamily(const QFont &font)
0875 {
0876     changeFontProperty(FamilyProperty, QVariant(font.family()));
0877 }
0878 
0879 void ArtisticTextTool::setFontSize(int size)
0880 {
0881     changeFontProperty(SizeProperty, QVariant(size));
0882 }
0883 
0884 void ArtisticTextTool::setSuperScript()
0885 {
0886     toggleSubSuperScript(ArtisticTextRange::Super);
0887 }
0888 
0889 void ArtisticTextTool::setSubScript()
0890 {
0891     toggleSubSuperScript(ArtisticTextRange::Sub);
0892 }
0893 
0894 void ArtisticTextTool::toggleSubSuperScript(ArtisticTextRange::BaselineShift mode)
0895 {
0896     if (!m_currentShape || !m_selection.hasSelection())
0897         return;
0898 
0899     const int from = m_selection.selectionStart();
0900     const int count = m_selection.selectionCount();
0901 
0902     QList<ArtisticTextRange> ranges = m_currentShape->copyText(from, count);
0903     const int rangeCount = ranges.count();
0904     if (!rangeCount)
0905         return;
0906 
0907     // determine if we want to disable the specified mode
0908     const bool disableMode = ranges.first().baselineShift() == mode;
0909     const qreal fontSize = m_currentShape->defaultFont().pointSizeF();
0910 
0911     for (int i = 0; i < rangeCount; ++i) {
0912         ArtisticTextRange &currentRange = ranges[i];
0913         QFont font = currentRange.font();
0914         if (disableMode) {
0915             currentRange.setBaselineShift(ArtisticTextRange::None);
0916             font.setPointSizeF(fontSize);
0917         } else {
0918             currentRange.setBaselineShift(mode);
0919             font.setPointSizeF(fontSize*ArtisticTextRange::subAndSuperScriptSizeFactor());
0920         }
0921         currentRange.setFont(font);
0922     }
0923     canvas()->addCommand(new ReplaceTextRangeCommand(m_currentShape, ranges, from, count, this));
0924 }
0925 
0926 void ArtisticTextTool::selectAll()
0927 {
0928     if (m_currentShape) {
0929         m_selection.selectText(0, m_currentShape->plainText().count());
0930     }
0931 }
0932 
0933 void ArtisticTextTool::deselectAll()
0934 {
0935     if (m_currentShape) {
0936         m_selection.clear();
0937     }
0938 }
0939 
0940 QVariant ArtisticTextTool::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const
0941 {
0942     if (!m_currentShape)
0943         return QVariant();
0944 
0945     switch (query) {
0946     case Qt::ImMicroFocus: {
0947         // The rectangle covering the area of the input cursor in widget coordinates.
0948         QRectF rect = m_textCursorShape.boundingRect();
0949         rect.moveTop(rect.bottom());
0950         QTransform shapeMatrix = m_currentShape->absoluteTransformation(&converter);
0951         qreal zoomX, zoomY;
0952         converter.zoom(&zoomX, &zoomY);
0953         shapeMatrix.scale(zoomX, zoomY);
0954         rect = shapeMatrix.mapRect(rect);
0955         return rect.toRect();
0956     }
0957     case Qt::ImFont:
0958         // The currently used font for text input.
0959         return m_currentShape->fontAt(m_textCursor);
0960     case Qt::ImCursorPosition:
0961         // The logical position of the cursor within the text surrounding the input area (see ImSurroundingText).
0962         return m_currentShape->charPositionAt(m_textCursor);
0963     case Qt::ImSurroundingText:
0964         // The plain text around the input area, for example the current paragraph.
0965         return m_currentShape->plainText();
0966     case Qt::ImCurrentSelection:
0967         // The currently selected text.
0968         return QVariant();
0969     default:
0970         ; // Qt 4.6 adds ImMaximumTextLength and ImAnchorPosition
0971     }
0972     return QVariant();
0973 }