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 }