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 ¤tRange = 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 }