Krita Source Code Documentation
Loading...
Searching...
No Matches
SvgTextCursor.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2023 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6#include "SvgTextCursor.h"
7#include "KoCanvasBase.h"
14#include "SvgTextShortCuts.h"
15
17#include "KoSvgPaste.h"
18#include "KoColorBackground.h"
19#include "KoShapeStroke.h"
20#include "KoColor.h"
21
22#include "KoViewConverter.h"
24#include "kis_painting_tweaks.h"
25#include "KoCanvasController.h"
28
29#include "kundo2command.h"
30#include <QTimer>
31#include <QDebug>
32#include <QClipboard>
33#include <QMimeData>
34#include <QApplication>
35#include <QKeyEvent>
36#include <QKeySequence>
37#include <QAction>
38#include <kis_assert.h>
39#include <QInputMethodEvent>
40#include <QBuffer>
41#include <QWidget>
42
43
45 int start = -1;
46 int length = 0;
47 KoSvgText::TextDecorations decor = KoSvgText::DecorationNone;
49 bool thick = false;
50
51 void setDecorationFromQStyle(QTextCharFormat::UnderlineStyle s) {
52 // whenever qt sets an underlinestyle it always sets the underline.
53 decor.setFlag(KoSvgText::DecorationUnderline, s != QTextCharFormat::NoUnderline);
54 if (s == QTextCharFormat::DotLine) {
56 } else if (s == QTextCharFormat::DashUnderline) {
58 } else if (s == QTextCharFormat::WaveUnderline) {
60 } else if (s == QTextCharFormat::SpellCheckUnderline) {
62#ifdef Q_OS_MACOS
64#endif
65 } else {
67 }
68 }
69
70 void setDecorationFromQTextCharFormat(QTextCharFormat format) {
71 if (format.hasProperty(QTextFormat::FontUnderline)) {
72 decor.setFlag(KoSvgText::DecorationUnderline, format.property(QTextFormat::FontUnderline).toBool());
73 }
74 if (format.hasProperty(QTextFormat::FontOverline)) {
75 decor.setFlag(KoSvgText::DecorationOverline, format.property(QTextFormat::FontOverline).toBool());
76 }
77 if (format.hasProperty(QTextFormat::FontStrikeOut)) {
78 decor.setFlag(KoSvgText::DecorationLineThrough, format.property(QTextFormat::FontStrikeOut).toBool());
79 }
80
81 if (format.hasProperty(QTextFormat::TextUnderlineStyle)) {
82 setDecorationFromQStyle(format.underlineStyle());
83 }
90 if (format.hasProperty(QTextFormat::BackgroundBrush)) {
91 thick = format.background().isOpaque();
92#ifdef Q_OS_LINUX
93 if (style == KoSvgText::Dashed) {
95 }
96#endif
97
98 }
100 // Ensure a underline is always set.
102 }
103 }
104};
105
106struct Q_DECL_HIDDEN SvgTextCursor::Private {
108 bool isAddingCommand = false;
109 int pos = 0;
110 int anchor = 0;
111 KoSvgTextShape *shape {nullptr};
112
115 bool cursorVisible = false;
116
117 QPainterPath cursorShape;
122 int cursorWidth = 1;
123 QPainterPath selection;
125
126
127 // This is used to adjust cursorpositions better on text-shape relayouts.
128 int posIndex = 0;
129 int anchorIndex = 0;
130
131 bool visualNavigation = true;
132 bool pasteRichText = true;
133
134 SvgTextInsertCommand *preEditCommand {nullptr};
135 int preEditStart = -1;
136 int preEditLength = -1;
138 QPainterPath IMEDecoration;
140 bool blockQueryUpdates = false;
141
143
145};
146
148 d(new Private)
149{
150 d->canvas = canvas;
151 if (d->canvas->canvasController()) {
152 // Mockcanvas in the tests has no canvas controller.
153 connect(d->canvas->canvasController()->proxyObject, SIGNAL(sizeChanged(QSize)), this, SLOT(updateInputMethodItemTransform()));
154 connect(d->canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
155 this, SLOT(canvasResourceChanged(int,QVariant)));
156 }
157 d->interface = new SvgTextCursorPropertyInterface(this);
158}
159
161{
163 d->cursorFlash.stop();
164 d->cursorFlashLimit.stop();
165 d->shape = nullptr;
166}
167
169{
170 return d->shape;
171}
172
174{
175 if (d->shape) {
177 d->shape->removeShapeChangeListener(this);
178 }
179 d->shape = textShape;
180 if (d->shape) {
181 d->shape->addShapeChangeListener(this);
183 d->pos = d->shape->posForIndex(d->shape->plainText().size());
184 } else {
185 d->pos = 0;
186 }
187 d->anchor = 0;
188 updateCursor(true);
190}
191
192void SvgTextCursor::setCaretSetting(int cursorWidth, int cursorFlash, int cursorFlashLimit)
193{
194 d->cursorFlash.setInterval(cursorFlash/2);
195 d->cursorFlashLimit.setInterval(cursorFlashLimit);
196 d->cursorWidth = cursorWidth;
197 connect(&d->cursorFlash, SIGNAL(timeout()), this, SLOT(blinkCursor()));
198 connect(&d->cursorFlashLimit, SIGNAL(timeout()), this, SLOT(stopBlinkCursor()));
199}
200
201void SvgTextCursor::setVisualMode(bool visualMode)
202{
203 d->visualNavigation = visualMode;
204}
205
206void SvgTextCursor::setPasteRichTextByDefault(const bool pasteRichText)
207{
208 d->pasteRichText = pasteRichText;
209}
210
212{
213 return d->pos;
214}
215
217{
218 return d->anchor;
219}
220
221void SvgTextCursor::setPos(int pos, int anchor)
222{
223 d->pos = pos;
224 d->anchor = anchor;
225 updateCursor();
227}
228
229void SvgTextCursor::setPosToPoint(QPointF point, bool moveAnchor)
230{
231 if (d->shape) {
232 int pos = d->shape->posForPointLineSensitive(d->shape->documentToShape(point));
233 if (d->preEditCommand) {
234 int start = d->shape->indexForPos(d->preEditStart);
235 int end = start + d->preEditLength;
236 int posIndex = d->shape->indexForPos(pos);
237 if (posIndex > start && posIndex <= end) {
238 qApp->inputMethod()->invokeAction(QInputMethod::Click, posIndex - start);
239 return;
240 } else {
242 }
243 }
244
245 const int finalPos = d->shape->posForIndex(d->shape->plainText().size());
246 d->pos = qBound(0, pos, finalPos);
247 if (moveAnchor || d->anchor < 0 || d->anchor > finalPos) {
248 d->anchor = d->pos;
249 }
250 updateCursor();
252 }
253}
254
255void SvgTextCursor::moveCursor(MoveMode mode, bool moveAnchor)
256{
257 if (d->shape) {
258
259 const int finalPos = d->shape->posForIndex(d->shape->plainText().size());
260 d->pos = qBound(0, moveModeResult(mode, d->pos, d->visualNavigation), finalPos);
261
262 if (moveAnchor) {
263 d->anchor = d->pos;
264 }
266 updateCursor();
267 }
268}
269
271{
272
273 if (d->shape) {
274 //KUndo2Command *parentCmd = new KUndo2Command;
275 if (hasSelection()) {
276 SvgTextRemoveCommand *removeCmd = removeSelectionImpl(false);
277 addCommandToUndoAdapter(removeCmd);
278 }
279
280 SvgTextInsertCommand *insertCmd = new SvgTextInsertCommand(d->shape, d->pos, d->anchor, text);
281 addCommandToUndoAdapter(insertCmd);
282
283 }
284}
285
287{
288 if (d->shape) {
289 //KUndo2Command *parentCmd = new KUndo2Command;
290 if (hasSelection()) {
291 SvgTextRemoveCommand *removeCmd = removeSelectionImpl(false);
292 addCommandToUndoAdapter(removeCmd);
293 }
294
295 SvgTextInsertRichCommand *cmd = new SvgTextInsertRichCommand(d->shape, insert, d->pos, d->anchor);
297
298 }
299}
300
302{
303 if (d->shape) {
304 SvgTextRemoveCommand *removeCmd;
305 if (hasSelection()) {
306 removeCmd = removeSelectionImpl(true);
307 addCommandToUndoAdapter(removeCmd);
308 } else {
309 int posA = moveModeResult(first, d->pos, d->visualNavigation);
310 int posB = moveModeResult(second, d->pos, d->visualNavigation);
311
312 int posStart = qMin(posA, posB);
313 int posEnd = qMax(posA, posB);
314 int indexEnd = d->shape->indexForPos(posEnd);
315 int length = indexEnd - d->shape->indexForPos(posStart);
316
317 removeCmd = new SvgTextRemoveCommand(d->shape, indexEnd, d->pos, d->anchor, length, true);
318 addCommandToUndoAdapter(removeCmd);
319 }
320 }
321}
322
324{
325 if (d->shape) {
326 SvgTextRemoveCommand *removeCmd;
327 if (hasSelection()) {
328 removeCmd = removeSelectionImpl(true);
329 addCommandToUndoAdapter(removeCmd);
330 } else {
331 int lastIndex = d->shape->indexForPos(d->pos);
332 removeCmd = new SvgTextRemoveCommand(d->shape, lastIndex, d->pos, d->anchor, 1, true);
333 addCommandToUndoAdapter(removeCmd);
334 }
335 }
336}
337
338QPair<KoSvgTextProperties, KoSvgTextProperties> SvgTextCursor::currentTextProperties() const
339{
340 if (d->shape) {
341 return QPair<KoSvgTextProperties, KoSvgTextProperties>(d->shape->propertiesForPos(d->pos), d->shape->propertiesForPos(d->pos, true));
342 }
343 return QPair<KoSvgTextProperties, KoSvgTextProperties>();
344}
345
347{
348 if (!d->shape) return QList<KoSvgTextProperties>();
349 int start = -1;
350 int end = -1;
351 if (d->pos != d->anchor) {
352 start = qMin(d->pos, d->anchor);
353 end = qMax(d->pos, d->anchor);
354 }
355 return d->shape->propertiesForRange(start, end);
356}
357
358void SvgTextCursor::mergePropertiesIntoSelection(const KoSvgTextProperties props, const QSet<KoSvgTextProperties::PropertyId> removeProperties)
359{
360 if (d->shape) {
361 int start = -1;
362 int end = -1;
363 if (hasSelection()) {
364 start = d->pos;
365 end = d->anchor;
366 }
367 KUndo2Command *cmd = new SvgTextMergePropertiesRangeCommand(d->shape, props, start, end, removeProperties);
369 }
370}
371
373{
374 KUndo2Command *removeCmd = removeSelectionImpl(true);
375 addCommandToUndoAdapter(removeCmd);
376}
377
379{
380 SvgTextRemoveCommand *removeCmd = nullptr;
381 if (d->shape) {
382 if (d->anchor != d->pos) {
383 int end = d->shape->indexForPos(qMax(d->anchor, d->pos));
384 int length = d->shape->indexForPos(qMax(d->anchor, d->pos)) - d->shape->indexForPos(qMin(d->anchor, d->pos));
385 removeCmd = new SvgTextRemoveCommand(d->shape, end, d->pos, d->anchor, length, allowCleanUp, parent);
386 }
387 }
388 return removeCmd;
389}
390
392{
393 if (d->shape) {
394 int start = d->shape->indexForPos(qMin(d->anchor, d->pos));
395 int length = d->shape->indexForPos(qMax(d->anchor, d->pos)) - start;
396 QString copied = d->shape->plainText().mid(start, length);
397 std::unique_ptr<KoSvgTextShape> copy = d->shape->copyRange(start, length);
398 QClipboard *cb = QApplication::clipboard();
399
400 if (copy) {
401 KoSvgTextShapeMarkupConverter converter(copy.get());
402 QString svg;
403 QString styles;
404 QString html;
405 QMimeData *svgData = new QMimeData();
406 if (converter.convertToSvg(&svg, &styles)) {
407 QString svgDoc = QString("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"2.0\">%1\n%2</svg>").arg(styles).arg(svg);
408 svgData->setData(QLatin1String("image/svg+xml"), svgDoc.toUtf8());
409 }
410 svgData->setText(copied);
411 if (converter.convertToHtml(&html))
412 svgData->setHtml(html);
413 cb->setMimeData(svgData);
414 } else {
415 cb->setText(copied);
416 }
417
418 }
419}
420
422{
423 bool success = d->pasteRichText? pasteRichText(): pastePlainText();
424 return success;
425}
426
428{
429 bool success = false;
430 if (d->shape) {
431 QClipboard *cb = QApplication::clipboard();
432 const QMimeData *mimeData = cb->mimeData();
433 KoSvgPaste shapePaste;
434 if (shapePaste.hasShapes()) {
435 QList<KoShape*> shapes = shapePaste.fetchShapes(d->shape->boundingRect(), 72.0);
436 while (shapes.size() > 0) {
437 KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape*>(shapes.takeFirst());
438 if (textShape) {
439 insertRichText(textShape);
440 success = true;
441 }
442 }
443 } else if (mimeData->hasHtml()) {
444 QString html = mimeData->html();
445 KoSvgTextShape *insert = new KoSvgTextShape();
446 KoSvgTextShapeMarkupConverter converter(insert);
447 QString svg;
448 QString styles;
449 if (converter.convertFromHtml(html, &svg, &styles)
450 && converter.convertFromSvg(svg, styles, d->shape->boundingRect(), 72.0) ) {
451 insertRichText(insert);
452 success = true;
453 }
454 }
455
456 if (!success) {
457 success = pastePlainText();
458 }
459 }
460 return success;
461}
462
464{
465 bool success = false;
466 QClipboard *cb = QApplication::clipboard();
467 const QMimeData *mimeData = cb->mimeData();
468 if (mimeData->hasText()) {
469 insertText(mimeData->text());
470 success = true;
471 }
472 return success;
473}
474
476{
477 setPos(d->pos, d->pos);
478}
479
480static QColor bgColorForCaret(QColor c) {
481
482 return KisPaintingTweaks::luminosityCoarse(c) > 0.8? QColor(0, 0, 0, 64) : QColor(255, 255, 255, 64);
483}
484
485void SvgTextCursor::paintDecorations(QPainter &gc, QColor selectionColor, int decorationThickness)
486{
487 if (d->shape) {
488 gc.save();
489 gc.setTransform(d->shape->absoluteTransformation(), true);
490 if (d->pos != d->anchor) {
491 gc.save();
492 gc.setOpacity(0.5);
493 QBrush brush(selectionColor);
494 gc.fillPath(d->selection, brush);
495 gc.restore();
496 } else {
497 if (d->cursorVisible) {
498 QPen pen;
499 pen.setCosmetic(true);
500 QColor c = d->cursorColor.isValid()? d->cursorColor: Qt::black;
501 pen.setColor(bgColorForCaret(c));
502 pen.setWidth((d->cursorWidth + 2) * decorationThickness);
503 gc.setPen(pen);
504 gc.drawPath(d->cursorShape);
505 pen.setColor(c);
506 pen.setWidth(d->cursorWidth * decorationThickness);
507 gc.setPen(pen);
508 gc.drawPath(d->cursorShape);
509
510 }
511 }
512 if (d->preEditCommand) {
513 gc.save();
514 QBrush brush(selectionColor);
515 gc.setOpacity(0.5);
516 gc.fillPath(d->IMEDecoration, brush);
517 gc.restore();
518 }
519 gc.restore();
520 }
521}
522
523QVariant SvgTextCursor::inputMethodQuery(Qt::InputMethodQuery query) const
524{
525 dbgTools << "receiving inputmethod query" << query;
526
527 // Because we set the input item transform to be shape->document->view->widget->window,
528 // the coordinates here should be in shape coordinates.
529 switch(query) {
530 case Qt::ImEnabled:
531 return d->shape? true: false;
532 break;
533 case Qt::ImCursorRectangle:
534 // The platform integration will always define the cursor as the 'left side' handle.
535 if (d->shape) {
536 QPointF caret1(d->cursorCaret.p1());
537 QPointF caret2(d->cursorCaret.p2());
538
539
540 QRectF rect = QRectF(caret1, caret2).normalized();
541 if (!rect.isValid()) {
542 if (rect.height() < 1) {
543 rect.adjust(0, -1, 0, 0);
544 }
545 if (rect.width() < 1) {
546 rect.adjust(0, 0, 1, 0);
547 }
548
549 }
550 return rect.toAlignedRect();
551 }
552 break;
553 case Qt::ImAnchorRectangle:
554 // The platform integration will always define the anchor as the 'right side' handle.
555 if (d->shape) {
556 QPointF caret1(d->anchorCaret.p1());
557 QPointF caret2(d->anchorCaret.p2());
558 QRectF rect = QRectF(caret1, caret2).normalized();
559 if (rect.isEmpty()) {
560 if (rect.height() < 1) {
561 rect.adjust(0, -1, 0, 0);
562 }
563 if (rect.width() < 1) {
564 rect = rect.adjusted(-1, 0, 0, 0).normalized();
565 }
566 }
567 return rect.toAlignedRect();
568 }
569 break;
570 //case Qt::ImFont: // not sure what this is used for, but we cannot sent out without access to properties.
571 case Qt::ImAbsolutePosition:
572 case Qt::ImCursorPosition:
573 if (d->shape) {
574 return d->shape->indexForPos(d->pos);
575 }
576 break;
577 case Qt::ImSurroundingText:
578 if (d->shape) {
579 QString surroundingText = d->shape->plainText();
580 int preEditIndex = d->preEditCommand? d->shape->indexForPos(d->preEditStart): 0;
581 surroundingText.remove(preEditIndex, d->preEditLength);
582 return surroundingText;
583 }
584 break;
585 case Qt::ImCurrentSelection:
586 if (d->shape) {
587 QString surroundingText = d->shape->plainText();
588 int preEditIndex = d->preEditCommand? d->shape->indexForPos(d->preEditStart): 0;
589 surroundingText.remove(preEditIndex, d->preEditLength);
590 int start = d->shape->indexForPos(qMin(d->anchor, d->pos));
591 int length = d->shape->indexForPos(qMax(d->anchor, d->pos)) - start;
592 return surroundingText.mid(start, length);
593 }
594 break;
595 case Qt::ImTextBeforeCursor:
596 if (d->shape) {
597 int start = d->shape->indexForPos(d->pos);
598 QString surroundingText = d->shape->plainText();
599 int preEditIndex = d->preEditCommand? d->shape->indexForPos(d->preEditStart): 0;
600 surroundingText.remove(preEditIndex, d->preEditLength);
601 return surroundingText.left(start);
602 }
603 break;
604 case Qt::ImTextAfterCursor:
605 if (d->shape) {
606 int start = d->shape->indexForPos(d->pos);
607 QString surroundingText = d->shape->plainText();
608 int preEditIndex = d->preEditCommand? d->shape->indexForPos(d->preEditStart): 0;
609 surroundingText.remove(preEditIndex, d->preEditLength);
610 return surroundingText.right(start);
611 }
612 break;
613 case Qt::ImMaximumTextLength:
614 return QVariant(); // infinite text length!
615 break;
616 case Qt::ImAnchorPosition:
617 if (d->shape) {
618 return d->shape->indexForPos(d->anchor);
619 }
620 break;
621 case Qt::ImHints:
622 // It would be great to use Qt::ImhNoTextHandles or Qt::ImhNoEditMenu,
623 // but neither are implemented for anything but web platform integration
624 return Qt::ImhMultiLine;
625 break;
626 // case Qt::ImPreferredLanguage: // requires access to properties.
627 // case Qt::ImPlatformData: // this is only for iOS at time of writing.
628 case Qt::ImEnterKeyType:
629 if (d->shape) {
630 return Qt::EnterKeyDefault; // because input method hint is always multiline, this will show a return key.
631 }
632 break;
633 // case Qt::ImInputItemClipRectangle // whether the input item is clipped?
634 default:
635 return QVariant();
636 }
637 return QVariant();
638}
639
640void SvgTextCursor::inputMethodEvent(QInputMethodEvent *event)
641{
642 dbgTools << "Commit:"<< event->commitString() << "predit:"<< event->preeditString();
643 dbgTools << "Replacement:"<< event->replacementStart() << event->replacementLength();
644
645 QRectF updateRect = d->shape? d->shape->boundingRect(): QRectF();
646 d->blockQueryUpdates = true;
647 SvgTextShapeManagerBlocker blocker(d->canvas->shapeManager());
648
649 bool isGettingInput = !event->commitString().isEmpty() || !event->preeditString().isEmpty()
650 || event->replacementLength() > 0;
651
652 // Remove previous preedit string.
653 if (d->preEditCommand) {
654 d->preEditCommand->undo();
655 d->preEditCommand = 0;
656 d->preEditStart = -1;
657 d->preEditLength = -1;
658 updateRect |= d->shape? d->shape->boundingRect(): QRectF();
659 }
660
661 if (!d->shape || !isGettingInput) {
662 blocker.unlock();
663 d->canvas->shapeManager()->update(updateRect);
664 event->ignore();
665 return;
666 }
667
668
669 // remove the selection if any.
671
672 // set the text insertion pos to replacement start and also remove replacement length, if any.
673 int originalPos = d->pos;
674 int index = d->shape->indexForPos(d->pos) + event->replacementStart();
675 d->pos = d->shape->posForIndex(index);
676 if (event->replacementLength() > 0) {
678 index + event->replacementLength(),
679 originalPos,
680 d->anchor,
681 event->replacementLength(),
682 false);
684 }
685
686 // add the commit string, if any.
687 if (!event->commitString().isEmpty()) {
688 insertText(event->commitString());
689 }
690
691 // set the selection...
692 Q_FOREACH(const QInputMethodEvent::Attribute attribute, event->attributes()) {
693 if (attribute.type == QInputMethodEvent::Selection) {
694 d->pos = d->shape->posForIndex(attribute.start);
695 int index = d->shape->indexForPos(d->pos);
696 d->anchor = d->shape->posForIndex(index + attribute.length);
697 }
698 }
699
700
701 // insert a preedit string, if any.
702 if (!event->preeditString().isEmpty()) {
703 int index = d->shape->indexForPos(d->pos);
704 d->preEditCommand = new SvgTextInsertCommand(d->shape, d->pos, d->anchor, event->preeditString());
705 d->preEditCommand->redo();
706 d->preEditLength = event->preeditString().size();
707 d->preEditStart = d->shape->posForIndex(index, true);
708 } else {
709 d->preEditCommand = 0;
710 }
711
712 // Apply the cursor offset for the preedit.
714 Q_FOREACH(const QInputMethodEvent::Attribute attribute, event->attributes()) {
715 dbgTools << "attribute: "<< attribute.type << "start: " << attribute.start
716 << "length: " << attribute.length << "val: " << attribute.value;
717 // Text Format is about setting the look of the preedit string, and there can be multiple per event
718 // we primarily interpret the underline. When a background color is set, we increase the underline
719 // thickness, as that's what is actually supposed to happen according to the comments in the
720 // platform input contexts for both macOS and Windows.
721
722 if (attribute.type == QInputMethodEvent::TextFormat) {
723 QVariant val = attribute.value;
724 QTextCharFormat form = val.value<QTextFormat>().toCharFormat();
725
726 if (attribute.length == 0 || attribute.start < 0 || !attribute.value.isValid()) {
727 continue;
728 }
729
730 int positionA = -1;
731 int positionB = -1;
732 if (!styleMap.isEmpty()) {
733 for (int i = 0; i < styleMap.size(); i++) {
734 if (attribute.start >= styleMap.at(i).start
735 && attribute.start < styleMap.at(i).start + styleMap.at(i).length) {
736 positionA = i;
737 }
738 if (attribute.start + attribute.length > styleMap.at(i).start
739 && attribute.start + attribute.length <= styleMap.at(i).start + styleMap.at(i).length) {
740 positionB = i;
741 }
742 }
743
744 if (positionA > -1 && positionA == positionB) {
745 IMEDecorationInfo decoration1 = styleMap.at(positionA);
746 IMEDecorationInfo decoration2 = decoration1;
747 IMEDecorationInfo decoration3 = decoration1;
748 decoration3.start = (attribute.start+attribute.length);
749 decoration3.length = (decoration1.start + decoration1.length) - decoration3.start;
750 decoration1.length = attribute.start - decoration1.start;
751 decoration2.start = attribute.start;
752 decoration2.length = attribute.length;
753 if (decoration1.length > 0) {
754 styleMap[positionA] = decoration1;
755 if (decoration2.length > 0) {
756 positionA += 1;
757 styleMap.insert(positionA, decoration2);
758 }
759 } else {
760 styleMap[positionA] = decoration2;
761 }
762 if (decoration3.length > 0) {
763 styleMap.insert(positionA + 1, decoration3);
764 }
765 } else if (positionA > -1 && positionB > -1
766 && positionA != positionB) {
767 IMEDecorationInfo decoration1 = styleMap.at(positionA);
768 IMEDecorationInfo decoration2 = decoration1;
769 IMEDecorationInfo decoration3 = styleMap.at(positionB);
770 IMEDecorationInfo decoration4 = decoration3;
771 decoration2.length = (decoration1.start + decoration1.length) - attribute.start;
772 decoration1.length = attribute.start - decoration1.start;
773 decoration2.start = attribute.start;
774
775 decoration4.start = (attribute.start+attribute.length);
776 decoration3.length = (decoration3.start + decoration3.length) - decoration4.start;
777 decoration3.length = decoration4.start - decoration3.start;
778 if (decoration1.length > 0) {
779 styleMap[positionA] = decoration1;
780 if (decoration2.length > 0) {
781 positionA += 1;
782 styleMap.insert(positionA, decoration2);
783 }
784 } else {
785 styleMap[positionA] = decoration2;
786 }
787
788 if (decoration3.length > 0) {
789 styleMap[positionB] = decoration3;
790 if (decoration4.length > 0) {
791 styleMap.insert(positionB + 1, decoration4);
792 }
793 } else {
794 styleMap[positionB] = decoration4;
795 }
796 }
797 }
798
799 if (positionA > -1 && !styleMap.isEmpty()) {
800
801 for(int i = positionA; i <= positionB; i++) {
802 IMEDecorationInfo decoration = styleMap.at(i);
803 decoration.setDecorationFromQTextCharFormat(form);
804 styleMap[i] = decoration;
805 }
806
807 } else {
808 IMEDecorationInfo decoration;
809 decoration.start = attribute.start;
810 decoration.length = attribute.length;
811 decoration.setDecorationFromQTextCharFormat(form);
812 styleMap.append(decoration);
813 }
814
815 // QInputMethodEvent::Language is about setting the locale on the given preedit string, which is not possible yet.
816 // QInputMethodEvent::Ruby is supposedly ruby info for the preedit string, but none of the platform integrations
817 // actually implement this at time of writing, and it may have been something from a previous live of Qt's.
818 } else if (attribute.type == QInputMethodEvent::Cursor) {
819 if (d->preEditStart < 0) {
820 d->anchor = d->pos;
821 } else {
822 int index = d->shape->indexForPos(d->preEditStart);
823 d->pos = d->shape->posForIndex(index + attribute.start);
824 d->anchor = d->pos;
825 }
826
827 // attribute value is the cursor color, and should be used to paint the cursor.
828 // attribute length is about whether the cursor should be visible at all...
829 }
830 }
831
832 blocker.unlock();
833 d->blockQueryUpdates = false;
834 qApp->inputMethod()->update(Qt::ImQueryInput);
835 updateRect |= d->shape->boundingRect();
836 d->shape->updateAbsolute(updateRect);
837 d->styleMap = styleMap;
840 updateCursor();
841 event->accept();
842}
843
845{
846 if (d->shape) {
847 Q_EMIT updateCursorDecoration(d->shape->shapeToDocument(d->cursorShape.boundingRect()) | d->oldCursorRect);
848 d->cursorVisible = !d->cursorVisible;
849 }
850}
851
853{
854 d->cursorFlash.stop();
855 d->cursorFlashLimit.stop();
856 d->cursorVisible = true;
857 if (d->shape) {
858 Q_EMIT updateCursorDecoration(d->shape->shapeToDocument(d->cursorShape.boundingRect()) | d->oldCursorRect);
859 }
860}
861
863{
864 // Mockcanvas in the tests has no window.
865 if (!d->canvas->canvasWidget()) {
866 return;
867 }
868 QPoint pos = d->canvas->canvasWidget()->mapTo(d->canvas->canvasWidget()->window(), QPoint());
869 QTransform widgetToWindow = QTransform::fromTranslate(pos.x(), pos.y());
870 QTransform inputItemTransform = widgetToWindow;
871 QRectF inputRect = d->canvas->canvasWidget()->geometry();
872 if (d->shape) {
873 inputRect = d->shape->outlineRect().normalized();
874 QTransform shapeTransform = d->shape->absoluteTransformation();
875 QTransform docToView = d->canvas->viewConverter()->documentToView();
876 QTransform viewToWidget = d->canvas->viewConverter()->viewToWidget();
877 inputItemTransform = shapeTransform * docToView * viewToWidget * widgetToWindow;
878 }
879 qApp->inputMethod()->setInputItemTransform(inputItemTransform);
880 qApp->inputMethod()->setInputItemRectangle(inputRect);
881 if (!d->blockQueryUpdates) {
882 qApp->inputMethod()->update(Qt::ImQueryInput);
883 }
884}
885
886void SvgTextCursor::canvasResourceChanged(int key, const QVariant &value)
887{
889 return;
890
892 KoSvgTextProperties shapeProps = hasSelection()? d->shape->propertiesForPos(d->pos, true): d->shape->textProperties();
895 if (bg != shapeProps.background()) {
897 QVariant::fromValue(KoSvgText::BackgroundProperty(bg)));
898 }
899 } else if (key == KoCanvasResource::BackgroundColor) {
901 if (shapeProps.stroke()) {
902 stroke = qSharedPointerDynamicCast<KoShapeStroke>(shapeProps.stroke());
903 }
904 stroke->setColor(value.value<KoColor>().toQColor());
905 if (stroke != shapeProps.stroke()) {
907 QVariant::fromValue(KoSvgText::StrokeProperty(stroke)));
908 }
909 }
910 if (!props.isEmpty()) {
912 }
913}
914
916{
917 if (d->shape) {
918 QVariant newVal;
919 QList<KoSvgTextProperties> p = d->shape->propertiesForRange(qMin(d->pos, d->anchor), qMax(d->pos, d->anchor));
920 for(auto it = p.begin(); it != p.end(); it++) {
921 if (property == KoSvgTextProperties::FontWeightId) {
922 int value = it->property(property, QVariant(400)).toInt();
923 newVal = value == 400? QVariant(700): QVariant(400);
924 if (value == 400) break;
925 } else if (property == KoSvgTextProperties::FontStyleId) {
926 KoSvgText::CssFontStyleData value = it->property(property, QVariant(QFont::StyleNormal)).value<KoSvgText::CssFontStyleData>();
928 if (value.style == QFont::StyleNormal) {
929 newSlant.style = QFont::StyleItalic;
930 } else {
931 newSlant.style = QFont::StyleNormal;
932 newSlant.slantValue.customValue = 0;
933 newSlant.slantValue.isAuto = true;
934 }
935 newVal = QVariant::fromValue(newSlant);
936 if (value.style == QFont::StyleNormal) break;
937 } else if (property == KoSvgTextProperties::TextDecorationLineId) {
938 KoSvgText::TextDecorations decor = it->propertyOrDefault(KoSvgTextProperties::TextDecorationLineId).value<KoSvgText::TextDecorations>();
939 KoSvgText::TextDecorations newDecor;
940 if (decor.testFlag(KoSvgText::DecorationUnderline)) {
941 newDecor.setFlag(KoSvgText::DecorationUnderline, false);
942 newVal = QVariant::fromValue(newDecor);
943 } else {
944 newDecor.setFlag(KoSvgText::DecorationUnderline, true);
945 newVal = QVariant::fromValue(newDecor);
946 break;
947 }
948 }
949 }
950 KoSvgTextProperties properties;
951 properties.setProperty(property, newVal);
953 }
954}
955
957{
958 QAction *action = dynamic_cast<QAction*>(QObject::sender());
959 if (!action || !d->shape) return;
960
961 QList<KoSvgTextProperties> p = d->shape->propertiesForRange(qMin(d->pos, d->anchor), qMax(d->pos, d->anchor));
963 if (properties.isEmpty()) return;
965}
966
968{
969 return d->pos != d->anchor;
970}
971
973{
974 Q_UNUSED(type);
975 Q_UNUSED(shape);
976 d->pos = d->shape->posForIndex(d->posIndex);
977 d->anchor = d->shape->posForIndex(d->anchorIndex);
978 updateCursor(true);
981}
982
984{
985 d->pos = pos;
986 d->anchor = anchor;
987 updateCursor();
989}
990
992{
993 d->interface->emitSelectionChange();
994}
995
996void SvgTextCursor::keyPressEvent(QKeyEvent *event)
997{
999
1000 if (d->preEditCommand) {
1001 //MacOS will keep sending keyboard events during IME handling.
1002 event->accept();
1003 return;
1004 }
1005
1006 bool select = event->modifiers().testFlag(Qt::ShiftModifier);
1007
1008 if (!((Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier) & event->modifiers())) {
1009
1010 switch (event->key()) {
1011 case Qt::Key_Right:
1013 event->accept();
1014 break;
1015 case Qt::Key_Left:
1017 event->accept();
1018 break;
1019 case Qt::Key_Up:
1021 event->accept();
1022 break;
1023 case Qt::Key_Down:
1025 event->accept();
1026 break;
1027 case Qt::Key_Delete:
1029 event->accept();
1030 break;
1031 case Qt::Key_Backspace:
1033 event->accept();
1034 break;
1035 case Qt::Key_Return:
1036 case Qt::Key_Enter:
1037 insertText("\n");
1038 event->accept();
1039 break;
1040 default:
1041 event->ignore();
1042 }
1043
1044 if (event->isAccepted()) {
1045 return;
1046 }
1047 }
1048 if (acceptableInput(event)) {
1049 insertText(event->text());
1050 event->accept();
1051 return;
1052 }
1053
1054 KoSvgTextProperties props = d->shape->textProperties();
1055
1058
1059 // Qt's keysequence stuff doesn't handle vertical, so to test all the standard keyboard shortcuts as if it did,
1060 // we reinterpret the direction keys according to direction and writing mode, and test against that.
1061
1062 int newKey = event->key();
1063
1064 if (direction == KoSvgText::DirectionRightToLeft) {
1065 switch (newKey) {
1066 case Qt::Key_Left:
1067 newKey = Qt::Key_Right;
1068 break;
1069 case Qt::Key_Right:
1070 newKey = Qt::Key_Left;
1071 break;
1072 default:
1073 break;
1074 }
1075 }
1076
1077 if (mode == KoSvgText::VerticalRL) {
1078 switch (newKey) {
1079 case Qt::Key_Left:
1080 newKey = Qt::Key_Down;
1081 break;
1082 case Qt::Key_Right:
1083 newKey = Qt::Key_Up;
1084 break;
1085 case Qt::Key_Up:
1086 newKey = Qt::Key_Left;
1087 break;
1088 case Qt::Key_Down:
1089 newKey = Qt::Key_Right;
1090 break;
1091 default:
1092 break;
1093 }
1094 } else if (mode == KoSvgText::VerticalRL) {
1095 switch (newKey) {
1096 case Qt::Key_Left:
1097 newKey = Qt::Key_Up;
1098 break;
1099 case Qt::Key_Right:
1100 newKey = Qt::Key_Down;
1101 break;
1102 case Qt::Key_Up:
1103 newKey = Qt::Key_Left;
1104 break;
1105 case Qt::Key_Down:
1106 newKey = Qt::Key_Right;
1107 break;
1108 default:
1109 break;
1110 }
1111 }
1112
1113 QKeySequence testSequence(event->modifiers() | newKey);
1114
1115
1116 // Note for future, when we have format changing actions:
1117 // We'll need to test format change actions before the standard
1118 // keys, as one of the standard keys for deleting a line is ctrl+u
1119 // which would probably be expected to do underline before deleting.
1120
1121 Q_FOREACH(QAction *action, d->actions) {
1122 if (action->shortcut() == testSequence) {
1123 event->accept();
1124 action->trigger();
1125 break;
1126 }
1127 }
1128 if (event->isAccepted()) return;
1129
1130 // This first set is already tested above, however, if they still
1131 // match, then it's one of the extra sequences for MacOs, which
1132 // seem to be purely logical, instead of the visual set we tested
1133 // above.
1134 if (testSequence == QKeySequence::MoveToNextChar) {
1136 event->accept();
1137 } else if (testSequence == QKeySequence::SelectNextChar) {
1139 event->accept();
1140 } else if (testSequence == QKeySequence::MoveToPreviousChar) {
1142 event->accept();
1143 } else if (testSequence == QKeySequence::SelectPreviousChar) {
1145 event->accept();
1146 } else if (testSequence == QKeySequence::MoveToNextLine) {
1148 event->accept();
1149 } else if (testSequence == QKeySequence::SelectNextLine) {
1151 event->accept();
1152 } else if (testSequence == QKeySequence::MoveToPreviousLine) {
1154 event->accept();
1155 } else if (testSequence == QKeySequence::SelectPreviousLine) {
1157 event->accept();
1158
1159 } else if (testSequence == QKeySequence::MoveToNextWord) {
1161 event->accept();
1162 } else if (testSequence == QKeySequence::SelectNextWord) {
1164 event->accept();
1165 } else if (testSequence == QKeySequence::MoveToPreviousWord) {
1167 event->accept();
1168 } else if (testSequence == QKeySequence::SelectPreviousWord) {
1170 event->accept();
1171
1172 } else if (testSequence == QKeySequence::MoveToStartOfLine) {
1174 event->accept();
1175 } else if (testSequence == QKeySequence::SelectStartOfLine) {
1177 event->accept();
1178 } else if (testSequence == QKeySequence::MoveToEndOfLine) {
1180 event->accept();
1181 } else if (testSequence == QKeySequence::SelectEndOfLine) {
1183 event->accept();
1184
1185 } else if (testSequence == QKeySequence::MoveToStartOfBlock
1186 || testSequence == QKeySequence::MoveToStartOfDocument) {
1188 event->accept();
1189 } else if (testSequence == QKeySequence::SelectStartOfBlock
1190 || testSequence == QKeySequence::SelectStartOfDocument) {
1192 event->accept();
1193
1194 } else if (testSequence == QKeySequence::MoveToEndOfBlock
1195 || testSequence == QKeySequence::MoveToEndOfDocument) {
1197 event->accept();
1198 } else if (testSequence == QKeySequence::SelectEndOfBlock
1199 || testSequence == QKeySequence::SelectEndOfDocument) {
1201 event->accept();
1202
1203 }else if (testSequence == QKeySequence::DeleteStartOfWord) {
1205 event->accept();
1206 } else if (testSequence == QKeySequence::DeleteEndOfWord) {
1208 event->accept();
1209 } else if (testSequence == QKeySequence::DeleteEndOfLine) {
1211 event->accept();
1212 } else if (testSequence == QKeySequence::DeleteCompleteLine) {
1214 event->accept();
1215 } else if (testSequence == QKeySequence::Backspace) {
1217 event->accept();
1218 } else if (testSequence == QKeySequence::Delete) {
1220 event->accept();
1221
1222 } else if (testSequence == QKeySequence::InsertLineSeparator
1223 || testSequence == QKeySequence::InsertParagraphSeparator) {
1224 insertText("\n");
1225 event->accept();
1226 } else {
1227 event->ignore();
1228 }
1229}
1230
1232{
1233 return d->isAddingCommand;
1234}
1235
1237{
1238 d->cursorFlash.start();
1239 d->cursorFlashLimit.start();
1240 d->cursorVisible = false;
1241 blinkCursor();
1242}
1243
1245{
1247}
1248
1249bool SvgTextCursor::registerPropertyAction(QAction *action, const QString &name)
1250{
1251 if (SvgTextShortCuts::configureAction(action, name)) {
1252 d->actions.append(action);
1253 connect(action, SIGNAL(triggered(bool)), this, SLOT(propertyAction()));
1254 return true;
1255 } else if (name == "svg_insert_special_character") {
1256 d->actions.append(action);
1257 connect(action, SIGNAL(triggered(bool)), this, SIGNAL(sigOpenGlyphPalette()));
1258 return true;
1259 } else if (name == "svg_paste_rich_text") {
1260 d->actions.append(action);
1261 connect(action, SIGNAL(triggered(bool)), this, SLOT(pasteRichText()));
1262 return true;
1263 } else if (name == "svg_paste_plain_text") {
1264 d->actions.append(action);
1265 connect(action, SIGNAL(triggered(bool)), this, SLOT(pastePlainText()));
1266 return true;
1267 }
1268 return false;
1269}
1270
1275
1276void SvgTextCursor::updateCursor(bool firstUpdate)
1277{
1278 if (d->shape) {
1279 d->oldCursorRect = d->shape->shapeToDocument(d->cursorShape.boundingRect());
1280 d->posIndex = d->shape->indexForPos(d->pos);
1281 d->anchorIndex = d->shape->indexForPos(d->anchor);
1282 emit selectionChanged();
1283 }
1284 d->cursorColor = QColor();
1285 d->cursorShape = d->shape? d->shape->cursorForPos(d->pos, d->cursorCaret, d->cursorColor): QPainterPath();
1286
1287 if (!d->blockQueryUpdates) {
1288 qApp->inputMethod()->update(Qt::ImQueryInput);
1289 }
1291 d->interface->emitSelectionChange();
1292 if (!(d->canvas->canvasWidget() && d->canvas->canvasController())) {
1293 // Mockcanvas in the tests has neither.
1294 return;
1295 }
1296 if (d->shape && !firstUpdate) {
1297 QRectF rect = d->shape->shapeToDocument(d->cursorShape.boundingRect());
1298 d->canvas->canvasController()->ensureVisibleDoc(rect, false);
1299 }
1300 if (d->canvas->canvasWidget()->hasFocus()) {
1301 d->cursorFlash.start();
1302 d->cursorFlashLimit.start();
1303 d->cursorVisible = false;
1304 blinkCursor();
1305 }
1306}
1307
1309{
1310 if (d->shape) {
1311 d->oldSelectionRect = d->shape->shapeToDocument(d->selection.boundingRect());
1312 d->shape->cursorForPos(d->anchor, d->anchorCaret, d->cursorColor);
1313 d->selection = d->shape->selectionBoxes(d->pos, d->anchor);
1314 Q_EMIT updateCursorDecoration(d->shape->shapeToDocument(d->selection.boundingRect()) | d->oldSelectionRect);
1315 }
1316}
1317
1319{
1320 if (d->shape) {
1321 d->oldIMEDecorationRect = d->shape->shapeToDocument(d->IMEDecoration.boundingRect());
1322 KoSvgText::TextDecorations decor;
1323 decor.setFlag(KoSvgText::DecorationUnderline, true);
1324 d->IMEDecoration = QPainterPath();
1325 if (d->preEditCommand) {
1326 Q_FOREACH(const IMEDecorationInfo info, d->styleMap) {
1327
1328 int startIndex = d->shape->indexForPos(d->preEditStart) + info.start;
1329 int endIndex = startIndex + info.length;
1330 qreal minimum = d->canvas->viewToDocument(QPointF(1, 1)).x();
1331 d->IMEDecoration.addPath(d->shape->underlines(d->shape->posForIndex(startIndex),
1332 d->shape->posForIndex(endIndex),
1333 info.decor,
1334 info.style,
1335 minimum,
1336 info.thick));
1337 d->IMEDecoration.setFillRule(Qt::WindingFill);
1338 }
1339 }
1340
1341 Q_EMIT updateCursorDecoration(d->shape->shapeToDocument(d->IMEDecoration.boundingRect()) | d->oldIMEDecorationRect);
1342 }
1343}
1344
1346{
1347 if (d->canvas) {
1348 if (cmd) {
1349 d->isAddingCommand = true;
1350 d->canvas->addCommand(cmd);
1351 d->isAddingCommand = false;
1352 }
1353 }
1354}
1355
1356int SvgTextCursor::moveModeResult(SvgTextCursor::MoveMode &mode, int &pos, bool visual) const
1357{
1358 int newPos = pos;
1359 switch (mode) {
1360 case MoveNone:
1361 break;
1362 case MoveLeft:
1363 newPos = d->shape->posLeft(pos, visual);
1364 break;
1365 case MoveRight:
1366 newPos = d->shape->posRight(pos, visual);
1367 break;
1368 case MoveUp:
1369 newPos = d->shape->posUp(pos, visual);
1370 break;
1371 case MoveDown:
1372 newPos = d->shape->posDown(pos, visual);
1373 break;
1374 case MovePreviousChar:
1375 newPos = d->shape->previousIndex(pos);
1376 break;
1377 case MoveNextChar:
1378 newPos = d->shape->nextIndex(pos);
1379 break;
1380 case MovePreviousLine:
1381 newPos = d->shape->previousLine(pos);
1382 break;
1383 case MoveNextLine:
1384 newPos = d->shape->nextLine(pos);
1385 break;
1386 case MoveWordLeft:
1387 newPos = d->shape->wordLeft(pos, visual);
1388 if (newPos == pos) {
1389 newPos = d->shape->posLeft(pos, visual);
1390 newPos = d->shape->wordLeft(newPos, visual);
1391 }
1392 break;
1393 case MoveWordRight:
1394 newPos = d->shape->wordRight(pos, visual);
1395 if (newPos == pos) {
1396 newPos = d->shape->posRight(pos, visual);
1397 newPos = d->shape->wordRight(newPos, visual);
1398 }
1399 break;
1400 case MoveWordStart:
1401 newPos = d->shape->wordStart(pos);
1402 if (newPos == pos) {
1403 newPos = d->shape->previousIndex(pos);
1404 newPos = d->shape->wordStart(newPos);
1405 }
1406 break;
1407 case MoveWordEnd:
1408 newPos = d->shape->wordEnd(pos);
1409 if (newPos == pos) {
1410 newPos = d->shape->nextIndex(pos);
1411 newPos = d->shape->wordEnd(newPos);
1412 }
1413 break;
1414 case MoveLineStart:
1415 newPos = d->shape->lineStart(pos);
1416 break;
1417 case MoveLineEnd:
1418 newPos = d->shape->lineEnd(pos);
1419 break;
1420 case ParagraphStart:
1421 newPos = 0;
1422 break;
1423 case ParagraphEnd:
1424 newPos = d->shape->posForIndex(d->shape->plainText().size());
1425 break;
1426 }
1427 return newPos;
1428}
1429
1431bool SvgTextCursor::acceptableInput(const QKeyEvent *event) const
1432{
1433 const QString text = event->text();
1434 if (text.isEmpty())
1435 return false;
1436 const QChar c = text.at(0);
1437 // Formatting characters such as ZWNJ, ZWJ, RLM, etc. This needs to go before the
1438 // next test, since CTRL+SHIFT is sometimes used to input it on Windows.
1439 if (c.category() == QChar::Other_Format)
1440 return true;
1441 // QTBUG-35734: ignore Ctrl/Ctrl+Shift; accept only AltGr (Alt+Ctrl) on German keyboards
1442 if (event->modifiers() == Qt::ControlModifier
1443 || event->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier)) {
1444 return false;
1445 }
1446 if (c.isPrint())
1447 return true;
1448 if (c.category() == QChar::Other_PrivateUse)
1449 return true;
1450 if (c == QLatin1Char('\t'))
1451 return true;
1452 return false;
1453}
1454
1456{
1457 if (!d->preEditCommand) {
1458 return;
1459 }
1460
1461 qApp->inputMethod()->commit();
1462
1463 if (!d->preEditCommand) {
1464 return;
1465 }
1466
1467 d->preEditCommand->undo();
1468 d->preEditCommand = nullptr;
1469 d->preEditStart = -1;
1470 d->preEditLength = 0;
1472 updateCursor();
1473}
1474
1476{
1477 // Only update canvas resources when there's no selection.
1478 // This relies on Krita not setting anything on the text when there's no selection.
1479 if (d->shape && d->canvas->resourceManager() && d->pos == d->anchor) {
1480 KoSvgTextProperties props = hasSelection()? d->shape->propertiesForPos(d->pos, true): d->shape->textProperties();
1481 KoColorBackground *bg = dynamic_cast<KoColorBackground *>(props.background().data());
1482 if (bg) {
1483 KoColor c;
1484 c.fromQColor(bg->color());
1485 c.setOpacity(1.0);
1486 if (c != d->canvas->resourceManager()->foregroundColor()) {
1487 d->canvas->resourceManager()->setForegroundColor(c);
1488 }
1489 }
1490 KoShapeStroke *stroke = dynamic_cast<KoShapeStroke *>(props.stroke().data());
1491 if (stroke && stroke->color().isValid()) {
1492 KoColor c;
1493 c.fromQColor(stroke->color());
1494 c.setOpacity(1.0);
1495 if (c != d->canvas->resourceManager()->backgroundColor()) {
1496 d->canvas->resourceManager()->setBackgroundColor(c);
1497 }
1498 }
1499 Q_FOREACH (QAction *action, d->actions) {
1500 if (action->isCheckable()) {
1501 action->setChecked(SvgTextShortCuts::actionEnabled(action, {props}));
1502 }
1503 }
1504 }
1505}
1506
1514
1516 : KoSvgTextPropertiesInterface(parent), d(new Private(parent))
1517{
1518 connect(&d->compressor, SIGNAL(timeout()), this, SIGNAL(textSelectionChanged()));
1519}
1520
1526{
1527 return d->parent->propertiesForRange();
1528}
1529
1531{
1532 // 9 times out of 10 this is correct, though we could do better by actually
1533 // getting inherited properties for the range and not just defaulting to the paragraph.
1534 return (d->parent->shape() && spanSelection())? d->parent->shape()->textProperties(): KoSvgTextProperties();
1535}
1536
1537void SvgTextCursorPropertyInterface::setPropertiesOnSelected(KoSvgTextProperties properties, QSet<KoSvgTextProperties::PropertyId> removeProperties)
1538{
1539 d->parent->mergePropertiesIntoSelection(properties, removeProperties);
1540}
1541
1543{
1544 return d->parent->hasSelection();
1545}
1546
1548{
1549 // Don't bother updating the selection when there's no shape
1550 // this is so we can use the text properties last used to create new texts.
1551 if (!d->parent->shape()) return;
1552 d->compressor.start();
1553}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
float value(const T *src, size_t ch)
const Params2D p
static QColor bgColorForCaret(QColor c)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
A simple solid color shape background.
QColor color() const
Returns the background color.
void setOpacity(quint8 alpha)
Definition KoColor.cpp:333
void fromQColor(const QColor &c)
Convenient function for converting from a QColor.
Definition KoColor.cpp:213
void toQColor(QColor *c) const
a convenience method for the above.
Definition KoColor.cpp:198
void addShapeChangeListener(ShapeChangeListener *listener)
Definition KoShape.cpp:1360
KoShapeAnchor * anchor() const
ChangeType
Used by shapeChanged() to select which change was made.
Definition KoShape.h:95
QList< KoShape * > fetchShapes(QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize=nullptr)
bool hasShapes()
The KoSvgTextPropertiesInterface class.
@ StrokeId
KoSvgText::StrokeProperty.
@ FillId
KoSvgText::BackgroundProperty.
@ FontStyleId
KoSvgText::CssSlantData.
@ WritingModeId
KoSvgText::WritingMode.
@ DirectionId
KoSvgText::Direction.
@ TextDecorationLineId
Flags, KoSvgText::TextDecorations.
QSharedPointer< KoShapeBackground > background() const
KoShapeStrokeModelSP stroke() const
void setProperty(PropertyId id, const QVariant &value)
QVariant propertyOrDefault(PropertyId id) const
bool convertToSvg(QString *svgText, QString *stylesText)
bool convertToHtml(QString *htmlText)
convertToHtml convert the text in the text shape to html
Interface to interact with the text property manager.
const QScopedPointer< Private > d
virtual bool spanSelection() override
Whether the tool is currently selecting a set of characters instead of whole paragraphs.
SvgTextCursorPropertyInterface(SvgTextCursor *parent)
virtual KoSvgTextProperties getInheritedProperties() override
getInheritedProperties The properties that should be visible when a given property isn't available in...
virtual QList< KoSvgTextProperties > getSelectedProperties() override
getSelectedProperties
virtual void setPropertiesOnSelected(KoSvgTextProperties properties, QSet< KoSvgTextProperties::PropertyId > removeProperties=QSet< KoSvgTextProperties::PropertyId >()) override
setPropertiesOnSelected This sets the properties on the selection. The implementation is responsible ...
The SvgTextMergePropertiesRangeCommand class This sets properties on a specific range in a single tex...
static bool actionEnabled(QAction *action, const QList< KoSvgTextProperties > currentProperties)
static KoSvgTextProperties getModifiedProperties(const QAction *action, QList< KoSvgTextProperties > currentProperties)
static bool configureAction(QAction *action, const QString &name)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define dbgTools
Definition kis_debug.h:48
qreal luminosityCoarse(const QColor &c, bool sRGBtrc)
luminosityCoarse This calculates the luminosity of the given QColor. It uses a very coarse (10 step) ...
@ BackgroundColor
The active background color selected for this canvas.
@ ForegroundColor
The active foreground color selected for this canvas.
TextDecorationStyle
Style of the text-decoration.
Definition KoSvgText.h:265
@ Solid
Draw a solid line.Ex: --—.
Definition KoSvgText.h:266
@ Dashed
Draw a dashed line. Ex: - - - - -.
Definition KoSvgText.h:269
@ Wavy
Draw a wavy line. We currently make a zigzag, ex: ^^^^^.
Definition KoSvgText.h:270
@ Dotted
Draw a dotted line. Ex: .....
Definition KoSvgText.h:268
@ DecorationOverline
Definition KoSvgText.h:260
@ DecorationLineThrough
Definition KoSvgText.h:261
@ DecorationNone
Definition KoSvgText.h:258
@ DecorationUnderline
Definition KoSvgText.h:259
Direction
Base direction used by Bidi algorithm.
Definition KoSvgText.h:48
@ DirectionRightToLeft
Definition KoSvgText.h:50
int start
The startPos from the attribute.
void setDecorationFromQTextCharFormat(QTextCharFormat format)
void setDecorationFromQStyle(QTextCharFormat::UnderlineStyle s)
bool thick
Whether the decoration needs to be doubled in size.
int length
The length from the attribute.
KoSvgText::TextDecorationStyle style
The style.
KoSvgText::TextDecorations decor
Which sides get decorated.
BackgroundProperty is a special wrapper around KoShapeBackground for managing it in KoSvgTextProperti...
Definition KoSvgText.h:714
When style is oblique, a custom slant value can be specified for variable fonts.
Definition KoSvgText.h:475
StrokeProperty is a special wrapper around KoShapeStrokeModel for managing it in KoSvgTextProperties.
Definition KoSvgText.h:733
The SvgTextCursor class.
void setVisualMode(const bool visualMode=true)
setVisualMode set whether the navigation mode is visual or logical. This right now primarily affects ...
void keyPressEvent(QKeyEvent *event)
Handle the cursor-related key events.
KoSvgTextPropertiesInterface * textPropertyInterface()
QList< QAction * > actions
int getAnchor()
Get the current selection anchor. This is the same as position, unless there's a selection.
void setCaretSetting(int cursorWidth=1, int cursorFlash=1000, int cursorFlashLimit=5000)
setCaretSetting Set the caret settings for the cursor. Qt has some standard functionality associated,...
void insertText(QString text)
Insert text at getPos()
QPair< KoSvgTextProperties, KoSvgTextProperties > currentTextProperties() const
currentTextProperties
QVector< IMEDecorationInfo > styleMap
Decoration info (underlines) for the preEdit string to differentiate it from regular text.
const QScopedPointer< Private > d
bool hasSelection() override
return true if the tool currently has something selected that can be copied or deleted.
void setPos(int pos, int anchor)
Set the pos and the anchor.
void addCommandToUndoAdapter(KUndo2Command *cmd)
void notifyShapeChanged(KoShape::ChangeType type, KoShape *shape) override
QRectF oldIMEDecorationRect
Update Rectangle of previous decoration.
void sigOpenGlyphPalette()
void updateCanvasResources()
void insertRichText(KoSvgTextShape *insert)
Insert rich text at getPos();.
SvgTextCursor(KoCanvasBase *canvas)
void inputMethodEvent(QInputMethodEvent *event)
void canvasResourceChanged(int key, const QVariant &value)
void setPosToPoint(QPointF point, bool moveAnchor=true)
Set the pos from a point. This currently does a search inside the text shape.
void moveCursor(MoveMode mode, bool moveAnchor=true)
Move the cursor, and, if you don't want a selection, move the anchor.
void setPasteRichTextByDefault(const bool pasteRichText=true)
setPasteRichText
void mergePropertiesIntoSelection(const KoSvgTextProperties props, const QSet< KoSvgTextProperties::PropertyId > removeProperties=QSet< KoSvgTextProperties::PropertyId >())
bool acceptableInput(const QKeyEvent *event) const
More or less copied from bool QInputControl::isAcceptableInput(const QKeyEvent *event) const.
QList< KoSvgTextProperties > propertiesForRange() const
QVariant inputMethodQuery(Qt::InputMethodQuery query) const
void removeSelection()
removeSelection if there's a selection, creates a text-removal command.
QPainterPath selection
void removeText(MoveMode first, MoveMode second)
removeText remove text relative to the current position. This will move the cursor according to the m...
KoCanvasBase * canvas
int getPos()
Get the current position.
void focusOut()
Stops blinking cursor.
void removeLastCodePoint()
QPainterPath cursorShape
void notifyMarkupChanged() override
bool pastePlainText()
pastePlainText Explicitely paste plaintext at pos.
bool registerPropertyAction(QAction *action, const QString &name)
Register an action.
void selectionChanged()
void focusIn()
Turns on blinking cursor.
void updateCursorDecoration(QRectF updateRect)
void paintDecorations(QPainter &gc, QColor selectionColor, int decorationThickness=1)
void toggleProperty(KoSvgTextProperties::PropertyId property)
bool paste()
paste pastes plain text in the clipboard at pos. Uses pasteRichTextByDefault to determine whether to ...
QPainterPath IMEDecoration
The decorations for the current preedit string.
void setShape(KoSvgTextShape *textShape)
setShape
void updateInputMethodItemTransform()
int moveModeResult(MoveMode &mode, int &pos, bool visual=false) const
SvgTextRemoveCommand * removeSelectionImpl(bool allowCleanUp, KUndo2Command *parent=0)
removeSelection if there's a selection, creates a text-removal command.
void notifyCursorPosChanged(int pos, int anchor) override
KoSvgTextShape * shape
void copy() const
copy copies plain text into the clipboard between anchor and pos.
void updateCursor(bool firstUpdate=false)
update the cursor shape. First update will block ensuring the canvas is visible so setShape won't cau...