File indexing completed on 2024-12-22 04:16:42
0001 /* 0002 * SPDX-FileCopyrightText: 2023 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 #include "SvgTextCursor.h" 0007 #include "KoCanvasBase.h" 0008 #include "KoSvgTextProperties.h" 0009 #include "SvgTextInsertCommand.h" 0010 #include "SvgTextRemoveCommand.h" 0011 #include "SvgTextShapeManagerBlocker.h" 0012 0013 #include "KoViewConverter.h" 0014 #include "kis_coordinates_converter.h" 0015 #include "kis_painting_tweaks.h" 0016 #include "KoCanvasController.h" 0017 0018 #include "kundo2command.h" 0019 #include <QTimer> 0020 #include <QDebug> 0021 #include <QClipboard> 0022 #include <QMimeData> 0023 #include <QApplication> 0024 #include <QKeyEvent> 0025 #include <QKeySequence> 0026 #include <QAction> 0027 #include <kis_assert.h> 0028 #include <QInputMethodEvent> 0029 0030 struct IMEDecorationInfo { 0031 int start = -1; ///< The startPos from the attribute. 0032 int length = 0; ///< The length from the attribute. 0033 KoSvgText::TextDecorations decor = KoSvgText::DecorationNone; ///< Which sides get decorated. 0034 KoSvgText::TextDecorationStyle style = KoSvgText::Solid; ///< The style. 0035 bool thick = false; ///< Whether the decoration needs to be doubled in size. 0036 0037 void setDecorationFromQStyle(QTextCharFormat::UnderlineStyle s) { 0038 // whenever qt sets an underlinestyle it always sets the underline. 0039 decor.setFlag(KoSvgText::DecorationUnderline, s != QTextCharFormat::NoUnderline); 0040 if (s == QTextCharFormat::DotLine) { 0041 style = KoSvgText::Dotted; 0042 } else if (s == QTextCharFormat::DashUnderline) { 0043 style = KoSvgText::Dashed; 0044 } else if (s == QTextCharFormat::WaveUnderline) { 0045 style = KoSvgText::Wavy; 0046 } else if (s == QTextCharFormat::SpellCheckUnderline) { 0047 style = KoSvgText::Wavy; 0048 #ifdef Q_OS_MACOS 0049 style = KoSvgText::Dotted; 0050 #endif 0051 } else { 0052 style = KoSvgText::Solid; 0053 } 0054 } 0055 0056 void setDecorationFromQTextCharFormat(QTextCharFormat format) { 0057 if (format.hasProperty(QTextFormat::FontUnderline)) { 0058 decor.setFlag(KoSvgText::DecorationUnderline, format.property(QTextFormat::FontUnderline).toBool()); 0059 } 0060 if (format.hasProperty(QTextFormat::FontOverline)) { 0061 decor.setFlag(KoSvgText::DecorationOverline, format.property(QTextFormat::FontOverline).toBool()); 0062 } 0063 if (format.hasProperty(QTextFormat::FontStrikeOut)) { 0064 decor.setFlag(KoSvgText::DecorationLineThrough, format.property(QTextFormat::FontStrikeOut).toBool()); 0065 } 0066 0067 if (format.hasProperty(QTextFormat::TextUnderlineStyle)) { 0068 setDecorationFromQStyle(format.underlineStyle()); 0069 } 0070 /** 0071 * Because Qt doesn't have a concept of a thick or double underline at time of writing, 0072 * most of Qt's QPA will set the background to a solid color instead. Sometimes the underline 0073 * style is changed (as with IBus). We don't support setting the background right now, so instead 0074 * we'll 'convert' it back to a thick solid underline. 0075 */ 0076 if (format.hasProperty(QTextFormat::BackgroundBrush)) { 0077 thick = format.background().isOpaque(); 0078 #ifdef Q_OS_LINUX 0079 if (style == KoSvgText::Dashed) { 0080 style = KoSvgText::Solid; 0081 } 0082 #endif 0083 0084 } 0085 if (decor == KoSvgText::DecorationNone) { 0086 // Ensure a underline is always set. 0087 decor.setFlag(KoSvgText::DecorationUnderline, true); 0088 } 0089 } 0090 }; 0091 0092 struct Q_DECL_HIDDEN SvgTextCursor::Private { 0093 KoCanvasBase *canvas; 0094 bool isAddingCommand = false; 0095 int pos = 0; 0096 int anchor = 0; 0097 KoSvgTextShape *shape {nullptr}; 0098 0099 QTimer cursorFlash; 0100 QTimer cursorFlashLimit; 0101 bool cursorVisible = false; 0102 0103 QPainterPath cursorShape; 0104 QColor cursorColor; 0105 QRectF oldCursorRect; 0106 QLineF cursorCaret; 0107 QLineF anchorCaret; 0108 int cursorWidth = 1; 0109 QPainterPath selection; 0110 QRectF oldSelectionRect; 0111 0112 0113 // This is used to adjust cursorpositions better on text-shape relayouts. 0114 int posIndex = 0; 0115 int anchorIndex = 0; 0116 0117 bool visualNavigation = true; 0118 0119 SvgTextInsertCommand *preEditCommand {nullptr}; ///< PreEdit string as an command provided by the input method. 0120 int preEditStart = -1; ///< Start of the preEdit string as a cursor pos. 0121 int preEditLength = -1; ///< Length of the preEditString. 0122 QVector<IMEDecorationInfo> styleMap; ///< Decoration info (underlines) for the preEdit string to differentiate it from regular text. 0123 QPainterPath IMEDecoration; ///< The decorations for the current preedit string. 0124 QRectF oldIMEDecorationRect; ///< Update Rectangle of previous decoration. 0125 bool blockQueryUpdates = false; ///< Block qApp->inputMethod->update(), enabled during the inputmethod event flow. 0126 }; 0127 0128 SvgTextCursor::SvgTextCursor(KoCanvasBase *canvas) : 0129 d(new Private) 0130 { 0131 d->canvas = canvas; 0132 if (d->canvas->canvasController()) { 0133 // Mockcanvas in the tests has no canvas controller. 0134 connect(d->canvas->canvasController()->proxyObject, SIGNAL(sizeChanged(QSize)), this, SLOT(updateInputMethodItemTransform())); 0135 } 0136 } 0137 0138 SvgTextCursor::~SvgTextCursor() 0139 { 0140 commitIMEPreEdit(); 0141 d->cursorFlash.stop(); 0142 d->cursorFlashLimit.stop(); 0143 d->shape = nullptr; 0144 } 0145 0146 KoSvgTextShape *SvgTextCursor::shape() const 0147 { 0148 return d->shape; 0149 } 0150 0151 void SvgTextCursor::setShape(KoSvgTextShape *textShape) 0152 { 0153 if (d->shape) { 0154 commitIMEPreEdit(); 0155 d->shape->removeShapeChangeListener(this); 0156 } 0157 d->shape = textShape; 0158 if (d->shape) { 0159 d->shape->addShapeChangeListener(this); 0160 updateInputMethodItemTransform(); 0161 d->pos = d->shape->posForIndex(d->shape->plainText().size()); 0162 } else { 0163 d->pos = 0; 0164 } 0165 d->anchor = 0; 0166 updateCursor(true); 0167 updateSelection(); 0168 } 0169 0170 void SvgTextCursor::setCaretSetting(int cursorWidth, int cursorFlash, int cursorFlashLimit) 0171 { 0172 d->cursorFlash.setInterval(cursorFlash/2); 0173 d->cursorFlashLimit.setInterval(cursorFlashLimit); 0174 d->cursorWidth = cursorWidth; 0175 connect(&d->cursorFlash, SIGNAL(timeout()), this, SLOT(blinkCursor())); 0176 connect(&d->cursorFlashLimit, SIGNAL(timeout()), this, SLOT(stopBlinkCursor())); 0177 } 0178 0179 void SvgTextCursor::setVisualMode(bool visualMode) 0180 { 0181 d->visualNavigation = visualMode; 0182 } 0183 0184 int SvgTextCursor::getPos() 0185 { 0186 return d->pos; 0187 } 0188 0189 int SvgTextCursor::getAnchor() 0190 { 0191 return d->anchor; 0192 } 0193 0194 void SvgTextCursor::setPos(int pos, int anchor) 0195 { 0196 d->pos = pos; 0197 d->anchor = anchor; 0198 updateCursor(); 0199 updateSelection(); 0200 } 0201 0202 void SvgTextCursor::setPosToPoint(QPointF point, bool moveAnchor) 0203 { 0204 if (d->shape) { 0205 int pos = d->pos = d->shape->posForPointLineSensitive(d->shape->documentToShape(point)); 0206 if (d->preEditCommand) { 0207 int start = d->shape->indexForPos(d->preEditStart); 0208 int end = start + d->preEditLength; 0209 int posIndex = d->shape->indexForPos(pos); 0210 if (posIndex > start && posIndex <= end) { 0211 qApp->inputMethod()->invokeAction(QInputMethod::Click, posIndex - start); 0212 return; 0213 } else { 0214 commitIMEPreEdit(); 0215 } 0216 } 0217 0218 d->pos = pos; 0219 if (moveAnchor) { 0220 d->anchor = d->pos; 0221 } 0222 updateCursor(); 0223 updateSelection(); 0224 } 0225 } 0226 0227 void SvgTextCursor::moveCursor(MoveMode mode, bool moveAnchor) 0228 { 0229 if (d->shape) { 0230 0231 d->pos = moveModeResult(mode, d->pos, d->visualNavigation); 0232 0233 if (moveAnchor) { 0234 d->anchor = d->pos; 0235 } 0236 updateSelection(); 0237 updateCursor(); 0238 } 0239 } 0240 0241 void SvgTextCursor::insertText(QString text) 0242 { 0243 0244 if (d->shape) { 0245 //KUndo2Command *parentCmd = new KUndo2Command; 0246 if (hasSelection()) { 0247 SvgTextRemoveCommand *removeCmd = removeSelectionImpl(); 0248 addCommandToUndoAdapter(removeCmd); 0249 } 0250 0251 SvgTextInsertCommand *insertCmd = new SvgTextInsertCommand(d->shape, d->pos, d->anchor, text); 0252 addCommandToUndoAdapter(insertCmd); 0253 0254 } 0255 } 0256 0257 void SvgTextCursor::removeText(SvgTextCursor::MoveMode first, SvgTextCursor::MoveMode second) 0258 { 0259 if (d->shape) { 0260 SvgTextRemoveCommand *removeCmd; 0261 if (hasSelection()) { 0262 removeCmd = removeSelectionImpl(); 0263 addCommandToUndoAdapter(removeCmd); 0264 } else { 0265 int posA = moveModeResult(first, d->pos, d->visualNavigation); 0266 int posB = moveModeResult(second, d->pos, d->visualNavigation); 0267 0268 int posStart = qMin(posA, posB); 0269 int posEnd = qMax(posA, posB); 0270 int indexEnd = d->shape->indexForPos(posEnd); 0271 int length = indexEnd - d->shape->indexForPos(posStart); 0272 0273 removeCmd = new SvgTextRemoveCommand(d->shape, indexEnd, d->pos, d->anchor, length); 0274 addCommandToUndoAdapter(removeCmd); 0275 } 0276 } 0277 } 0278 0279 void SvgTextCursor::removeLastCodePoint() 0280 { 0281 if (d->shape) { 0282 SvgTextRemoveCommand *removeCmd; 0283 if (hasSelection()) { 0284 removeCmd = removeSelectionImpl(); 0285 addCommandToUndoAdapter(removeCmd); 0286 } else { 0287 int lastIndex = d->shape->indexForPos(d->pos); 0288 removeCmd = new SvgTextRemoveCommand(d->shape, lastIndex, d->pos, d->anchor, 1); 0289 addCommandToUndoAdapter(removeCmd); 0290 } 0291 } 0292 } 0293 0294 void SvgTextCursor::removeSelection() 0295 { 0296 KUndo2Command *removeCmd = removeSelectionImpl(); 0297 addCommandToUndoAdapter(removeCmd); 0298 } 0299 0300 SvgTextRemoveCommand *SvgTextCursor::removeSelectionImpl(KUndo2Command *parent) 0301 { 0302 SvgTextRemoveCommand *removeCmd = nullptr; 0303 if (d->shape) { 0304 if (d->anchor != d->pos) { 0305 int end = d->shape->indexForPos(qMax(d->anchor, d->pos)); 0306 int length = d->shape->indexForPos(qMax(d->anchor, d->pos)) - d->shape->indexForPos(qMin(d->anchor, d->pos)); 0307 removeCmd = new SvgTextRemoveCommand(d->shape, end, d->pos, d->anchor, length, parent); 0308 } 0309 } 0310 return removeCmd; 0311 } 0312 0313 void SvgTextCursor::copy() const 0314 { 0315 if (d->shape) { 0316 int start = d->shape->indexForPos(qMin(d->anchor, d->pos)); 0317 int length = d->shape->indexForPos(qMax(d->anchor, d->pos)) - start; 0318 QString copied = d->shape->plainText().mid(start, length); 0319 QClipboard *cb = QApplication::clipboard(); 0320 cb->setText(copied); 0321 0322 } 0323 } 0324 0325 bool SvgTextCursor::paste() 0326 { 0327 bool success = false; 0328 if (d->shape) { 0329 QClipboard *cb = QApplication::clipboard(); 0330 const QMimeData *mimeData = cb->mimeData(); 0331 if (mimeData->hasText()) { 0332 insertText(mimeData->text()); 0333 success = true; 0334 } 0335 } 0336 return success; 0337 } 0338 0339 void SvgTextCursor::deselectText() 0340 { 0341 setPos(d->pos, d->pos); 0342 } 0343 0344 static QColor bgColorForCaret(QColor c) { 0345 0346 return KisPaintingTweaks::luminosityCoarse(c) > 0.8? QColor(0, 0, 0, 64) : QColor(255, 255, 255, 64); 0347 } 0348 0349 void SvgTextCursor::paintDecorations(QPainter &gc, QColor selectionColor, int decorationThickness) 0350 { 0351 if (d->shape) { 0352 gc.save(); 0353 gc.setTransform(d->shape->absoluteTransformation(), true); 0354 if (d->pos != d->anchor) { 0355 gc.save(); 0356 gc.setOpacity(0.5); 0357 QBrush brush(selectionColor); 0358 gc.fillPath(d->selection, brush); 0359 gc.restore(); 0360 } else { 0361 if (d->cursorVisible) { 0362 QPen pen; 0363 pen.setCosmetic(true); 0364 QColor c = d->cursorColor.isValid()? d->cursorColor: Qt::black; 0365 pen.setColor(bgColorForCaret(c)); 0366 pen.setWidth((d->cursorWidth + 2) * decorationThickness); 0367 gc.setPen(pen); 0368 gc.drawPath(d->cursorShape); 0369 pen.setColor(c); 0370 pen.setWidth(d->cursorWidth * decorationThickness); 0371 gc.setPen(pen); 0372 gc.drawPath(d->cursorShape); 0373 0374 } 0375 } 0376 if (d->preEditCommand) { 0377 gc.save(); 0378 QBrush brush(selectionColor); 0379 gc.setOpacity(0.5); 0380 gc.fillPath(d->IMEDecoration, brush); 0381 gc.restore(); 0382 } 0383 gc.restore(); 0384 } 0385 } 0386 0387 QVariant SvgTextCursor::inputMethodQuery(Qt::InputMethodQuery query) const 0388 { 0389 dbgTools << "receiving inputmethod query" << query; 0390 0391 // Because we set the input item transform to be shape->document->view->widget->window, 0392 // the coordinates here should be in shape coordinates. 0393 switch(query) { 0394 case Qt::ImEnabled: 0395 return d->shape? true: false; 0396 break; 0397 case Qt::ImCursorRectangle: 0398 // The platform integration will always define the cursor as the 'left side' handle. 0399 if (d->shape) { 0400 QPointF caret1(d->cursorCaret.p1()); 0401 QPointF caret2(d->cursorCaret.p2()); 0402 0403 0404 QRectF rect = QRectF(caret1, caret2).normalized(); 0405 if (!rect.isValid()) { 0406 if (rect.height() < 1) { 0407 rect.adjust(0, -1, 0, 0); 0408 } 0409 if (rect.width() < 1) { 0410 rect.adjust(0, 0, 1, 0); 0411 } 0412 0413 } 0414 return rect.toAlignedRect(); 0415 } 0416 break; 0417 case Qt::ImAnchorRectangle: 0418 // The platform integration will always define the anchor as the 'right side' handle. 0419 if (d->shape) { 0420 QPointF caret1(d->anchorCaret.p1()); 0421 QPointF caret2(d->anchorCaret.p2()); 0422 QRectF rect = QRectF(caret1, caret2).normalized(); 0423 if (rect.isEmpty()) { 0424 if (rect.height() < 1) { 0425 rect.adjust(0, -1, 0, 0); 0426 } 0427 if (rect.width() < 1) { 0428 rect = rect.adjusted(-1, 0, 0, 0).normalized(); 0429 } 0430 } 0431 return rect.toAlignedRect(); 0432 } 0433 break; 0434 //case Qt::ImFont: // not sure what this is used for, but we cannot sent out without access to properties. 0435 case Qt::ImAbsolutePosition: 0436 case Qt::ImCursorPosition: 0437 if (d->shape) { 0438 return d->shape->indexForPos(d->pos); 0439 } 0440 break; 0441 case Qt::ImSurroundingText: 0442 if (d->shape) { 0443 QString surroundingText = d->shape->plainText(); 0444 int preEditIndex = d->preEditCommand? d->shape->indexForPos(d->preEditStart): 0; 0445 surroundingText.remove(preEditIndex, d->preEditLength); 0446 return surroundingText; 0447 } 0448 break; 0449 case Qt::ImCurrentSelection: 0450 if (d->shape) { 0451 QString surroundingText = d->shape->plainText(); 0452 int preEditIndex = d->preEditCommand? d->shape->indexForPos(d->preEditStart): 0; 0453 surroundingText.remove(preEditIndex, d->preEditLength); 0454 int start = d->shape->indexForPos(qMin(d->anchor, d->pos)); 0455 int length = d->shape->indexForPos(qMax(d->anchor, d->pos)) - start; 0456 return surroundingText.mid(start, length); 0457 } 0458 break; 0459 case Qt::ImTextBeforeCursor: 0460 if (d->shape) { 0461 int start = d->shape->indexForPos(d->pos); 0462 QString surroundingText = d->shape->plainText(); 0463 int preEditIndex = d->preEditCommand? d->shape->indexForPos(d->preEditStart): 0; 0464 surroundingText.remove(preEditIndex, d->preEditLength); 0465 return surroundingText.left(start); 0466 } 0467 break; 0468 case Qt::ImTextAfterCursor: 0469 if (d->shape) { 0470 int start = d->shape->indexForPos(d->pos); 0471 QString surroundingText = d->shape->plainText(); 0472 int preEditIndex = d->preEditCommand? d->shape->indexForPos(d->preEditStart): 0; 0473 surroundingText.remove(preEditIndex, d->preEditLength); 0474 return surroundingText.right(start); 0475 } 0476 break; 0477 case Qt::ImMaximumTextLength: 0478 return QVariant(); // infinite text length! 0479 break; 0480 case Qt::ImAnchorPosition: 0481 if (d->shape) { 0482 return d->shape->indexForPos(d->anchor); 0483 } 0484 break; 0485 case Qt::ImHints: 0486 // It would be great to use Qt::ImhNoTextHandles or Qt::ImhNoEditMenu, 0487 // but neither are implemented for anything but web platform integration 0488 return Qt::ImhMultiLine; 0489 break; 0490 // case Qt::ImPreferredLanguage: // requires access to properties. 0491 // case Qt::ImPlatformData: // this is only for iOS at time of writing. 0492 case Qt::ImEnterKeyType: 0493 if (d->shape) { 0494 return Qt::EnterKeyDefault; // because input method hint is always multiline, this will show a return key. 0495 } 0496 break; 0497 // case Qt::ImInputItemClipRectangle // whether the input item is clipped? 0498 default: 0499 return QVariant(); 0500 } 0501 return QVariant(); 0502 } 0503 0504 void SvgTextCursor::inputMethodEvent(QInputMethodEvent *event) 0505 { 0506 dbgTools << "Commit:"<< event->commitString() << "predit:"<< event->preeditString(); 0507 dbgTools << "Replacement:"<< event->replacementStart() << event->replacementLength(); 0508 0509 QRectF updateRect = d->shape? d->shape->boundingRect(): QRectF(); 0510 d->blockQueryUpdates = true; 0511 SvgTextShapeManagerBlocker blocker(d->canvas->shapeManager()); 0512 0513 bool isGettingInput = !event->commitString().isEmpty() || !event->preeditString().isEmpty() 0514 || event->replacementLength() > 0; 0515 0516 // Remove previous preedit string. 0517 if (d->preEditCommand) { 0518 d->preEditCommand->undo(); 0519 d->preEditCommand = 0; 0520 d->preEditStart = -1; 0521 d->preEditLength = -1; 0522 updateRect |= d->shape? d->shape->boundingRect(): QRectF(); 0523 } 0524 0525 if (!d->shape || !isGettingInput) { 0526 blocker.unlock(); 0527 d->canvas->shapeManager()->update(updateRect); 0528 event->ignore(); 0529 return; 0530 } 0531 0532 0533 // remove the selection if any. 0534 addCommandToUndoAdapter(removeSelectionImpl()); 0535 0536 // set the text insertion pos to replacement start and also remove replacement length, if any. 0537 int originalPos = d->pos; 0538 int index = d->shape->indexForPos(d->pos) + event->replacementStart(); 0539 d->pos = d->shape->posForIndex(index); 0540 if (event->replacementLength() > 0) { 0541 SvgTextRemoveCommand *cmd = new SvgTextRemoveCommand(d->shape, 0542 index + event->replacementLength(), 0543 originalPos, 0544 d->anchor, 0545 event->replacementLength()); 0546 addCommandToUndoAdapter(cmd); 0547 } 0548 0549 // add the commit string, if any. 0550 if (!event->commitString().isEmpty()) { 0551 insertText(event->commitString()); 0552 } 0553 0554 // set the selection... 0555 Q_FOREACH(const QInputMethodEvent::Attribute attribute, event->attributes()) { 0556 if (attribute.type == QInputMethodEvent::Selection) { 0557 d->pos = d->shape->posForIndex(attribute.start); 0558 int index = d->shape->indexForPos(d->pos); 0559 d->anchor = d->shape->posForIndex(index + attribute.length); 0560 } 0561 } 0562 0563 0564 // insert a preedit string, if any. 0565 if (!event->preeditString().isEmpty()) { 0566 int index = d->shape->indexForPos(d->pos); 0567 d->preEditCommand = new SvgTextInsertCommand(d->shape, d->pos, d->anchor, event->preeditString()); 0568 d->preEditCommand->redo(); 0569 d->preEditLength = event->preeditString().size(); 0570 d->preEditStart = d->shape->posForIndex(index, true); 0571 } else { 0572 d->preEditCommand = 0; 0573 } 0574 0575 // Apply the cursor offset for the preedit. 0576 QVector<IMEDecorationInfo> styleMap; 0577 Q_FOREACH(const QInputMethodEvent::Attribute attribute, event->attributes()) { 0578 dbgTools << "attribute: "<< attribute.type << "start: " << attribute.start 0579 << "length: " << attribute.length << "val: " << attribute.value; 0580 // Text Format is about setting the look of the preedit string, and there can be multiple per event 0581 // we primarily interpret the underline. When a background color is set, we increase the underline 0582 // thickness, as that's what is actually supossed to happen according to the comments in the 0583 // platform input contexts for both macOS and Windows. 0584 0585 if (attribute.type == QInputMethodEvent::TextFormat) { 0586 QVariant val = attribute.value; 0587 QTextCharFormat form = val.value<QTextFormat>().toCharFormat(); 0588 0589 if (attribute.length == 0 || attribute.start < 0 || !attribute.value.isValid()) { 0590 continue; 0591 } 0592 0593 int positionA = -1; 0594 int positionB = -1; 0595 if (!styleMap.isEmpty()) { 0596 for (int i = 0; i < styleMap.size(); i++) { 0597 if (attribute.start >= styleMap.at(i).start 0598 && attribute.start < styleMap.at(i).start + styleMap.at(i).length) { 0599 positionA = i; 0600 } 0601 if (attribute.start + attribute.length > styleMap.at(i).start 0602 && attribute.start + attribute.length <= styleMap.at(i).start + styleMap.at(i).length) { 0603 positionB = i; 0604 } 0605 } 0606 0607 if (positionA > -1 && positionA == positionB) { 0608 IMEDecorationInfo decoration1 = styleMap.at(positionA); 0609 IMEDecorationInfo decoration2 = decoration1; 0610 IMEDecorationInfo decoration3 = decoration1; 0611 decoration3.start = (attribute.start+attribute.length); 0612 decoration3.length = (decoration1.start + decoration1.length) - decoration3.start; 0613 decoration1.length = attribute.start - decoration1.start; 0614 decoration2.start = attribute.start; 0615 decoration2.length = attribute.length; 0616 if (decoration1.length > 0) { 0617 styleMap[positionA] = decoration1; 0618 if (decoration2.length > 0) { 0619 positionA += 1; 0620 styleMap.insert(positionA, decoration2); 0621 } 0622 } else { 0623 styleMap[positionA] = decoration2; 0624 } 0625 if (decoration3.length > 0) { 0626 styleMap.insert(positionA + 1, decoration3); 0627 } 0628 } else if (positionA > -1 && positionB > -1 0629 && positionA != positionB) { 0630 IMEDecorationInfo decoration1 = styleMap.at(positionA); 0631 IMEDecorationInfo decoration2 = decoration1; 0632 IMEDecorationInfo decoration3 = styleMap.at(positionB); 0633 IMEDecorationInfo decoration4 = decoration3; 0634 decoration2.length = (decoration1.start + decoration1.length) - attribute.start; 0635 decoration1.length = attribute.start - decoration1.start; 0636 decoration2.start = attribute.start; 0637 0638 decoration4.start = (attribute.start+attribute.length); 0639 decoration3.length = (decoration3.start + decoration3.length) - decoration4.start; 0640 decoration3.length = decoration4.start - decoration3.start; 0641 if (decoration1.length > 0) { 0642 styleMap[positionA] = decoration1; 0643 if (decoration2.length > 0) { 0644 positionA += 1; 0645 styleMap.insert(positionA, decoration2); 0646 } 0647 } else { 0648 styleMap[positionA] = decoration2; 0649 } 0650 0651 if (decoration3.length > 0) { 0652 styleMap[positionB] = decoration3; 0653 if (decoration4.length > 0) { 0654 styleMap.insert(positionB + 1, decoration4); 0655 } 0656 } else { 0657 styleMap[positionB] = decoration4; 0658 } 0659 } 0660 } 0661 0662 if (positionA > -1 && !styleMap.isEmpty()) { 0663 0664 for(int i = positionA; i <= positionB; i++) { 0665 IMEDecorationInfo decoration = styleMap.at(i); 0666 decoration.setDecorationFromQTextCharFormat(form); 0667 styleMap[i] = decoration; 0668 } 0669 0670 } else { 0671 IMEDecorationInfo decoration; 0672 decoration.start = attribute.start; 0673 decoration.length = attribute.length; 0674 decoration.setDecorationFromQTextCharFormat(form); 0675 styleMap.append(decoration); 0676 } 0677 0678 // QInputMethodEvent::Language is about setting the locale on the given preedit string, which is not possible yet. 0679 // QInputMethodEvent::Ruby is supossedly ruby info for the preedit string, but none of the platform integrations 0680 // actually implement this at time of writing, and it may have been something from a previous live of Qt's. 0681 } else if (attribute.type == QInputMethodEvent::Cursor) { 0682 if (d->preEditStart < 0) { 0683 d->anchor = d->pos; 0684 } else { 0685 int index = d->shape->indexForPos(d->preEditStart); 0686 d->pos = d->shape->posForIndex(index + attribute.start); 0687 d->anchor = d->pos; 0688 } 0689 0690 // attribute value is the cursor color, and should be used to paint the cursor. 0691 // attribute length is about whether the cursor should be visible at all... 0692 } 0693 } 0694 0695 blocker.unlock(); 0696 d->blockQueryUpdates = false; 0697 qApp->inputMethod()->update(Qt::ImQueryInput); 0698 updateRect |= d->shape->boundingRect(); 0699 d->shape->updateAbsolute(updateRect); 0700 d->styleMap = styleMap; 0701 updateIMEDecoration(); 0702 updateSelection(); 0703 updateCursor(); 0704 event->accept(); 0705 } 0706 0707 void SvgTextCursor::blinkCursor() 0708 { 0709 if (d->shape) { 0710 emit updateCursorDecoration(d->shape->shapeToDocument(d->cursorShape.boundingRect()) | d->oldCursorRect); 0711 d->cursorVisible = !d->cursorVisible; 0712 } 0713 } 0714 0715 void SvgTextCursor::stopBlinkCursor() 0716 { 0717 d->cursorFlash.stop(); 0718 d->cursorFlashLimit.stop(); 0719 d->cursorVisible = true; 0720 if (d->shape) { 0721 emit updateCursorDecoration(d->shape->shapeToDocument(d->cursorShape.boundingRect()) | d->oldCursorRect); 0722 } 0723 } 0724 0725 void SvgTextCursor::updateInputMethodItemTransform() 0726 { 0727 // Mockcanvas in the tests has no window. 0728 if (!d->canvas->canvasWidget()) { 0729 return; 0730 } 0731 QPoint pos = d->canvas->canvasWidget()->mapTo(d->canvas->canvasWidget()->window(), QPoint()); 0732 QTransform widgetToWindow = QTransform::fromTranslate(pos.x(), pos.y()); 0733 QTransform inputItemTransform = widgetToWindow; 0734 QRectF inputRect = d->canvas->canvasWidget()->geometry(); 0735 if (d->shape) { 0736 inputRect = d->shape->outlineRect().normalized(); 0737 QTransform shapeTransform = d->shape->absoluteTransformation(); 0738 QTransform docToView = d->canvas->viewConverter()->documentToView(); 0739 QTransform viewToWidget = d->canvas->viewConverter()->viewToWidget(); 0740 inputItemTransform = shapeTransform * docToView * viewToWidget * widgetToWindow; 0741 } 0742 qApp->inputMethod()->setInputItemTransform(inputItemTransform); 0743 qApp->inputMethod()->setInputItemRectangle(inputRect); 0744 if (!d->blockQueryUpdates) { 0745 qApp->inputMethod()->update(Qt::ImQueryInput); 0746 } 0747 } 0748 0749 bool SvgTextCursor::hasSelection() 0750 { 0751 return d->pos != d->anchor; 0752 } 0753 0754 void SvgTextCursor::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape) 0755 { 0756 Q_UNUSED(type); 0757 Q_UNUSED(shape); 0758 d->pos = d->shape->posForIndex(d->posIndex); 0759 d->anchor = d->shape->posForIndex(d->anchorIndex); 0760 updateCursor(true); 0761 updateSelection(); 0762 updateInputMethodItemTransform(); 0763 } 0764 0765 void SvgTextCursor::notifyCursorPosChanged(int pos, int anchor) 0766 { 0767 d->pos = pos; 0768 d->anchor = anchor; 0769 updateCursor(); 0770 updateSelection(); 0771 } 0772 0773 void SvgTextCursor::keyPressEvent(QKeyEvent *event) 0774 { 0775 KIS_SAFE_ASSERT_RECOVER_RETURN(d->shape); 0776 0777 if (d->preEditCommand) { 0778 //MacOS will keep sending keyboard events during IME handling. 0779 event->accept(); 0780 return; 0781 } 0782 0783 bool select = event->modifiers().testFlag(Qt::ShiftModifier); 0784 0785 if (!((Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier) & event->modifiers())) { 0786 0787 switch (event->key()) { 0788 case Qt::Key_Right: 0789 moveCursor(SvgTextCursor::MoveRight, !select); 0790 event->accept(); 0791 break; 0792 case Qt::Key_Left: 0793 moveCursor(SvgTextCursor::MoveLeft, !select); 0794 event->accept(); 0795 break; 0796 case Qt::Key_Up: 0797 moveCursor(SvgTextCursor::MoveUp, !select); 0798 event->accept(); 0799 break; 0800 case Qt::Key_Down: 0801 moveCursor(SvgTextCursor::MoveDown, !select); 0802 event->accept(); 0803 break; 0804 case Qt::Key_Delete: 0805 removeText(MoveNone, MoveNextChar); 0806 event->accept(); 0807 break; 0808 case Qt::Key_Backspace: 0809 removeLastCodePoint(); 0810 event->accept(); 0811 break; 0812 case Qt::Key_Return: 0813 case Qt::Key_Enter: 0814 insertText("\n"); 0815 event->accept(); 0816 break; 0817 default: 0818 event->ignore(); 0819 } 0820 0821 if (event->isAccepted()) { 0822 return; 0823 } 0824 } 0825 if (acceptableInput(event)) { 0826 insertText(event->text()); 0827 event->accept(); 0828 return; 0829 } 0830 0831 KoSvgTextProperties props = d->shape->textProperties(); 0832 0833 KoSvgText::WritingMode mode = KoSvgText::WritingMode(props.propertyOrDefault(KoSvgTextProperties::WritingModeId).toInt()); 0834 KoSvgText::Direction direction = KoSvgText::Direction(props.propertyOrDefault(KoSvgTextProperties::DirectionId).toInt()); 0835 0836 // Qt's keysequence stuff doesn't handle vertical, so to test all the standard keyboard shortcuts as if it did, 0837 // we reinterpret the direction keys according to direction and writing mode, and test against that. 0838 0839 int newKey = event->key(); 0840 0841 if (direction == KoSvgText::DirectionRightToLeft) { 0842 switch (newKey) { 0843 case Qt::Key_Left: 0844 newKey = Qt::Key_Right; 0845 break; 0846 case Qt::Key_Right: 0847 newKey = Qt::Key_Left; 0848 break; 0849 default: 0850 break; 0851 } 0852 } 0853 0854 if (mode == KoSvgText::VerticalRL) { 0855 switch (newKey) { 0856 case Qt::Key_Left: 0857 newKey = Qt::Key_Down; 0858 break; 0859 case Qt::Key_Right: 0860 newKey = Qt::Key_Up; 0861 break; 0862 case Qt::Key_Up: 0863 newKey = Qt::Key_Left; 0864 break; 0865 case Qt::Key_Down: 0866 newKey = Qt::Key_Right; 0867 break; 0868 default: 0869 break; 0870 } 0871 } else if (mode == KoSvgText::VerticalRL) { 0872 switch (newKey) { 0873 case Qt::Key_Left: 0874 newKey = Qt::Key_Up; 0875 break; 0876 case Qt::Key_Right: 0877 newKey = Qt::Key_Down; 0878 break; 0879 case Qt::Key_Up: 0880 newKey = Qt::Key_Left; 0881 break; 0882 case Qt::Key_Down: 0883 newKey = Qt::Key_Right; 0884 break; 0885 default: 0886 break; 0887 } 0888 } 0889 0890 QKeySequence testSequence(event->modifiers() | newKey); 0891 0892 0893 // Note for future, when we have format changing actions: 0894 // We'll need to test format change actions before the standard 0895 // keys, as one of the standard keys for deleting a line is ctrl+u 0896 // which would probably be expected to do underline before deleting. 0897 0898 // This first set is already tested above, however, if they still 0899 // match, then it's one of the extra sequences for MacOs, which 0900 // seem to be purely logical, instead of the visual set we tested 0901 // above. 0902 if (testSequence == QKeySequence::MoveToNextChar) { 0903 moveCursor(SvgTextCursor::MoveNextChar, true); 0904 event->accept(); 0905 } else if (testSequence == QKeySequence::SelectNextChar) { 0906 moveCursor(SvgTextCursor::MoveNextChar, false); 0907 event->accept(); 0908 } else if (testSequence == QKeySequence::MoveToPreviousChar) { 0909 moveCursor(SvgTextCursor::MovePreviousChar, true); 0910 event->accept(); 0911 } else if (testSequence == QKeySequence::SelectPreviousChar) { 0912 moveCursor(SvgTextCursor::MovePreviousChar, false); 0913 event->accept(); 0914 } else if (testSequence == QKeySequence::MoveToNextLine) { 0915 moveCursor(SvgTextCursor::MoveNextLine, true); 0916 event->accept(); 0917 } else if (testSequence == QKeySequence::SelectNextLine) { 0918 moveCursor(SvgTextCursor::MoveNextLine, false); 0919 event->accept(); 0920 } else if (testSequence == QKeySequence::MoveToPreviousLine) { 0921 moveCursor(SvgTextCursor::MovePreviousLine, true); 0922 event->accept(); 0923 } else if (testSequence == QKeySequence::SelectPreviousLine) { 0924 moveCursor(SvgTextCursor::MovePreviousLine, false); 0925 event->accept(); 0926 0927 } else if (testSequence == QKeySequence::MoveToNextWord) { 0928 moveCursor(SvgTextCursor::MoveWordEnd, true); 0929 event->accept(); 0930 } else if (testSequence == QKeySequence::SelectNextWord) { 0931 moveCursor(SvgTextCursor::MoveWordEnd, false); 0932 event->accept(); 0933 } else if (testSequence == QKeySequence::MoveToPreviousWord) { 0934 moveCursor(SvgTextCursor::MoveWordStart, true); 0935 event->accept(); 0936 } else if (testSequence == QKeySequence::SelectPreviousWord) { 0937 moveCursor(SvgTextCursor::MoveWordStart, false); 0938 event->accept(); 0939 0940 } else if (testSequence == QKeySequence::MoveToStartOfLine) { 0941 moveCursor(SvgTextCursor::MoveLineStart, true); 0942 event->accept(); 0943 } else if (testSequence == QKeySequence::SelectStartOfLine) { 0944 moveCursor(SvgTextCursor::MoveLineStart, false); 0945 event->accept(); 0946 } else if (testSequence == QKeySequence::MoveToEndOfLine) { 0947 moveCursor(SvgTextCursor::MoveLineEnd, true); 0948 event->accept(); 0949 } else if (testSequence == QKeySequence::SelectEndOfLine) { 0950 moveCursor(SvgTextCursor::MoveLineEnd, false); 0951 event->accept(); 0952 0953 } else if (testSequence == QKeySequence::MoveToStartOfBlock 0954 || testSequence == QKeySequence::MoveToStartOfDocument) { 0955 moveCursor(SvgTextCursor::ParagraphStart, true); 0956 event->accept(); 0957 } else if (testSequence == QKeySequence::SelectStartOfBlock 0958 || testSequence == QKeySequence::SelectStartOfDocument) { 0959 moveCursor(SvgTextCursor::ParagraphStart, false); 0960 event->accept(); 0961 0962 } else if (testSequence == QKeySequence::MoveToEndOfBlock 0963 || testSequence == QKeySequence::MoveToEndOfDocument) { 0964 moveCursor(SvgTextCursor::ParagraphEnd, true); 0965 event->accept(); 0966 } else if (testSequence == QKeySequence::SelectEndOfBlock 0967 || testSequence == QKeySequence::SelectEndOfDocument) { 0968 moveCursor(SvgTextCursor::ParagraphEnd, false); 0969 event->accept(); 0970 0971 }else if (testSequence == QKeySequence::DeleteStartOfWord) { 0972 removeText(MoveWordStart, MoveNone); 0973 event->accept(); 0974 } else if (testSequence == QKeySequence::DeleteEndOfWord) { 0975 removeText(MoveNone, MoveWordEnd); 0976 event->accept(); 0977 } else if (testSequence == QKeySequence::DeleteEndOfLine) { 0978 removeText(MoveNone, MoveLineEnd); 0979 event->accept(); 0980 } else if (testSequence == QKeySequence::DeleteCompleteLine) { 0981 removeText(MoveLineStart, MoveLineEnd); 0982 event->accept(); 0983 } else if (testSequence == QKeySequence::Backspace) { 0984 removeLastCodePoint(); 0985 event->accept(); 0986 } else if (testSequence == QKeySequence::Delete) { 0987 removeText(MoveNone, MoveNextChar); 0988 event->accept(); 0989 0990 } else if (testSequence == QKeySequence::InsertLineSeparator 0991 || testSequence == QKeySequence::InsertParagraphSeparator) { 0992 insertText("\n"); 0993 event->accept(); 0994 } else { 0995 event->ignore(); 0996 } 0997 } 0998 0999 bool SvgTextCursor::isAddingCommand() const 1000 { 1001 return d->isAddingCommand; 1002 } 1003 1004 void SvgTextCursor::focusIn() 1005 { 1006 d->cursorFlash.start(); 1007 d->cursorFlashLimit.start(); 1008 d->cursorVisible = false; 1009 blinkCursor(); 1010 } 1011 1012 void SvgTextCursor::focusOut() 1013 { 1014 stopBlinkCursor(); 1015 } 1016 1017 void SvgTextCursor::updateCursor(bool firstUpdate) 1018 { 1019 if (d->shape) { 1020 d->oldCursorRect = d->shape->shapeToDocument(d->cursorShape.boundingRect()); 1021 d->posIndex = d->shape->indexForPos(d->pos); 1022 d->anchorIndex = d->shape->indexForPos(d->anchor); 1023 } 1024 d->cursorColor = QColor(); 1025 d->cursorShape = d->shape? d->shape->cursorForPos(d->pos, d->cursorCaret, d->cursorColor): QPainterPath(); 1026 1027 if (!d->blockQueryUpdates) { 1028 qApp->inputMethod()->update(Qt::ImQueryInput); 1029 } 1030 if (!(d->canvas->canvasWidget() && d->canvas->canvasController())) { 1031 // Mockcanvas in the tests has neither. 1032 return; 1033 } 1034 if (d->shape && !firstUpdate) { 1035 QRectF rect = d->shape->shapeToDocument(d->cursorShape.boundingRect()); 1036 d->canvas->canvasController()->ensureVisible(d->canvas->viewConverter()->documentToView(rect).adjusted(-1, -1, 1, 1)); 1037 } 1038 if (d->canvas->canvasWidget()->hasFocus()) { 1039 d->cursorFlash.start(); 1040 d->cursorFlashLimit.start(); 1041 d->cursorVisible = false; 1042 blinkCursor(); 1043 } 1044 } 1045 1046 void SvgTextCursor::updateSelection() 1047 { 1048 if (d->shape) { 1049 d->oldSelectionRect = d->shape->shapeToDocument(d->selection.boundingRect()); 1050 d->shape->cursorForPos(d->anchor, d->anchorCaret, d->cursorColor); 1051 d->selection = d->shape->selectionBoxes(d->pos, d->anchor); 1052 emit updateCursorDecoration(d->shape->shapeToDocument(d->selection.boundingRect()) | d->oldSelectionRect); 1053 } 1054 } 1055 1056 void SvgTextCursor::updateIMEDecoration() 1057 { 1058 if (d->shape) { 1059 d->oldIMEDecorationRect = d->shape->shapeToDocument(d->IMEDecoration.boundingRect()); 1060 KoSvgText::TextDecorations decor; 1061 decor.setFlag(KoSvgText::DecorationUnderline, true); 1062 d->IMEDecoration = QPainterPath(); 1063 if (d->preEditCommand) { 1064 Q_FOREACH(const IMEDecorationInfo info, d->styleMap) { 1065 1066 int startIndex = d->shape->indexForPos(d->preEditStart) + info.start; 1067 int endIndex = startIndex + info.length; 1068 qreal minimum = d->canvas->viewToDocument(QPointF(1, 1)).x(); 1069 d->IMEDecoration.addPath(d->shape->underlines(d->shape->posForIndex(startIndex), 1070 d->shape->posForIndex(endIndex), 1071 info.decor, 1072 info.style, 1073 minimum, 1074 info.thick)); 1075 d->IMEDecoration.setFillRule(Qt::WindingFill); 1076 } 1077 } 1078 1079 emit updateCursorDecoration(d->shape->shapeToDocument(d->IMEDecoration.boundingRect()) | d->oldIMEDecorationRect); 1080 } 1081 } 1082 1083 void SvgTextCursor::addCommandToUndoAdapter(KUndo2Command *cmd) 1084 { 1085 if (d->canvas) { 1086 if (cmd) { 1087 d->isAddingCommand = true; 1088 d->canvas->addCommand(cmd); 1089 d->isAddingCommand = false; 1090 } 1091 } 1092 } 1093 1094 int SvgTextCursor::moveModeResult(SvgTextCursor::MoveMode &mode, int &pos, bool visual) const 1095 { 1096 int newPos = pos; 1097 switch (mode) { 1098 case MoveNone: 1099 break; 1100 case MoveLeft: 1101 newPos = d->shape->posLeft(pos, visual); 1102 break; 1103 case MoveRight: 1104 newPos = d->shape->posRight(pos, visual); 1105 break; 1106 case MoveUp: 1107 newPos = d->shape->posUp(pos, visual); 1108 break; 1109 case MoveDown: 1110 newPos = d->shape->posDown(pos, visual); 1111 break; 1112 case MovePreviousChar: 1113 newPos = d->shape->previousIndex(pos); 1114 break; 1115 case MoveNextChar: 1116 newPos = d->shape->nextIndex(pos); 1117 break; 1118 case MovePreviousLine: 1119 newPos = d->shape->previousLine(pos); 1120 break; 1121 case MoveNextLine: 1122 newPos = d->shape->nextLine(pos); 1123 break; 1124 case MoveWordLeft: 1125 newPos = d->shape->wordLeft(pos, visual); 1126 if (newPos == pos) { 1127 newPos = d->shape->posLeft(pos, visual); 1128 newPos = d->shape->wordLeft(newPos, visual); 1129 } 1130 break; 1131 case MoveWordRight: 1132 newPos = d->shape->wordRight(pos, visual); 1133 if (newPos == pos) { 1134 newPos = d->shape->posRight(pos, visual); 1135 newPos = d->shape->wordRight(newPos, visual); 1136 } 1137 break; 1138 case MoveWordStart: 1139 newPos = d->shape->wordStart(pos); 1140 if (newPos == pos) { 1141 newPos = d->shape->previousIndex(pos); 1142 newPos = d->shape->wordStart(newPos); 1143 } 1144 break; 1145 case MoveWordEnd: 1146 newPos = d->shape->wordEnd(pos); 1147 if (newPos == pos) { 1148 newPos = d->shape->nextIndex(pos); 1149 newPos = d->shape->wordEnd(newPos); 1150 } 1151 break; 1152 case MoveLineStart: 1153 newPos = d->shape->lineStart(pos); 1154 break; 1155 case MoveLineEnd: 1156 newPos = d->shape->lineEnd(pos); 1157 break; 1158 case ParagraphStart: 1159 newPos = 0; 1160 break; 1161 case ParagraphEnd: 1162 newPos = d->shape->posForIndex(d->shape->plainText().size()); 1163 break; 1164 } 1165 return newPos; 1166 } 1167 1168 /// More or less copied from bool QInputControl::isAcceptableInput(const QKeyEvent *event) const 1169 bool SvgTextCursor::acceptableInput(const QKeyEvent *event) const 1170 { 1171 const QString text = event->text(); 1172 if (text.isEmpty()) 1173 return false; 1174 const QChar c = text.at(0); 1175 // Formatting characters such as ZWNJ, ZWJ, RLM, etc. This needs to go before the 1176 // next test, since CTRL+SHIFT is sometimes used to input it on Windows. 1177 if (c.category() == QChar::Other_Format) 1178 return true; 1179 // QTBUG-35734: ignore Ctrl/Ctrl+Shift; accept only AltGr (Alt+Ctrl) on German keyboards 1180 if (event->modifiers() == Qt::ControlModifier 1181 || event->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier)) { 1182 return false; 1183 } 1184 if (c.isPrint()) 1185 return true; 1186 if (c.category() == QChar::Other_PrivateUse) 1187 return true; 1188 if (c == QLatin1Char('\t')) 1189 return true; 1190 return false; 1191 } 1192 1193 void SvgTextCursor::commitIMEPreEdit() 1194 { 1195 if (!d->preEditCommand) { 1196 return; 1197 } 1198 1199 qApp->inputMethod()->commit(); 1200 1201 if (!d->preEditCommand) { 1202 return; 1203 } 1204 1205 d->preEditCommand->undo(); 1206 d->preEditCommand = nullptr; 1207 d->preEditStart = -1; 1208 d->preEditLength = 0; 1209 updateIMEDecoration(); 1210 updateCursor(); 1211 }