presentation/TextMarksItem.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "TextMarksItem.h" | ||
| 2 | |||
| 3 | #include <QAbstractTextDocumentLayout> | ||
| 4 | #include <QPainter> | ||
| 5 | #include <QPainterPath> | ||
| 6 | #include <QQuickTextDocument> | ||
| 7 | #include <QTextBlock> | ||
| 8 | #include <QTextDocument> | ||
| 9 | #include <QTextLayout> | ||
| 10 | #include <QtQml> | ||
| 11 | |||
| 12 | #include <mutex> | ||
| 13 | |||
| 14 | 150 | TextMarksItem::TextMarksItem(QQuickItem *parent) : QQuickPaintedItem(parent) { | |
| 15 |
1/1✓ Branch 8 → 9 taken 150 times.
|
150 | setAntialiasing(true); |
| 16 | // The marks follow the text's size and place; a new size repaints. | ||
| 17 |
1/1✓ Branch 9 → 10 taken 150 times.
|
150 | connect(this, &QQuickItem::widthChanged, this, &TextMarksItem::schedule); |
| 18 |
1/1✓ Branch 11 → 12 taken 150 times.
|
150 | connect(this, &QQuickItem::heightChanged, this, &TextMarksItem::schedule); |
| 19 | 150 | } | |
| 20 | |||
| 21 | 874 | void TextMarksItem::registerTypes() { | |
| 22 | static std::once_flag once; | ||
| 23 |
1/1✓ Branch 2 → 3 taken 874 times.
|
900 | std::call_once(once, [] { qmlRegisterType<TextMarksItem>("MailJD.Text", 1, 0, "TextMarks"); }); |
| 24 | 874 | } | |
| 25 | |||
| 26 | 150 | void TextMarksItem::setTextItem(QQuickItem *item) { | |
| 27 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 150 times.
|
150 | if (m_textItem == item) return; |
| 28 |
1/2✗ Branch 6 → 7 not taken.
✓ Branch 6 → 9 taken 150 times.
|
150 | if (m_textItem) disconnect(m_textItem, nullptr, this, nullptr); |
| 29 | 150 | m_textItem = item; | |
| 30 |
1/2✓ Branch 11 → 12 taken 150 times.
✗ Branch 11 → 29 not taken.
|
150 | if (m_textItem) { |
| 31 | // A rewrap or a padding change moves the marks; the document may be | ||
| 32 | // replaced (a view reused for another message). | ||
| 33 |
2/2✓ Branch 18 → 14 taken 600 times.
✓ Branch 18 → 19 taken 150 times.
|
750 | for (auto signal : {&QQuickItem::widthChanged, &QQuickItem::heightChanged, &QQuickItem::xChanged, &QQuickItem::yChanged}) |
| 34 |
1/1✓ Branch 15 → 16 taken 600 times.
|
600 | connect(m_textItem, signal, this, &TextMarksItem::schedule); |
| 35 |
1/2✓ Branch 22 → 23 taken 150 times.
✗ Branch 22 → 29 not taken.
|
150 | if (m_textItem->metaObject()->indexOfSignal("textChanged()") >= 0) |
| 36 |
3/3✓ Branch 23 → 24 taken 150 times.
✓ Branch 24 → 25 taken 150 times.
✓ Branch 26 → 27 taken 150 times.
|
150 | connect(m_textItem, SIGNAL(textChanged()), this, SLOT(schedule())); |
| 37 | } | ||
| 38 | 150 | emit textItemChanged(); | |
| 39 | 150 | schedule(); | |
| 40 | } | ||
| 41 | |||
| 42 | 155 | void TextMarksItem::setScrollOffset(QPointF offset) { | |
| 43 |
2/2✓ Branch 3 → 4 taken 150 times.
✓ Branch 3 → 5 taken 5 times.
|
155 | if (m_scrollOffset == offset) return; |
| 44 | 5 | m_scrollOffset = offset; | |
| 45 | 5 | emit scrollOffsetChanged(); | |
| 46 | 5 | schedule(); | |
| 47 | } | ||
| 48 | |||
| 49 | 1072 | QTextDocument *TextMarksItem::document() const { | |
| 50 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1072 times.
|
1072 | if (!m_textItem) return nullptr; |
| 51 |
2/2✓ Branch 6 → 7 taken 1072 times.
✓ Branch 7 → 8 taken 1072 times.
|
1072 | auto *quick = m_textItem->property("textDocument").value<QQuickTextDocument *>(); |
| 52 |
1/2✓ Branch 9 → 10 taken 1072 times.
✗ Branch 9 → 12 not taken.
|
1072 | return quick ? quick->textDocument() : nullptr; |
| 53 | } | ||
| 54 | |||
| 55 | 536 | void TextMarksItem::watchDocument() { | |
| 56 |
1/1✓ Branch 2 → 3 taken 536 times.
|
536 | QTextDocument *current = document(); |
| 57 |
2/2✓ Branch 4 → 5 taken 408 times.
✓ Branch 4 → 6 taken 128 times.
|
536 | if (current == m_document) return; |
| 58 |
1/3✗ Branch 7 → 8 not taken.
✓ Branch 7 → 10 taken 128 times.
✗ Branch 9 → 10 not taken.
|
128 | if (m_document) disconnect(m_document, nullptr, this, nullptr); |
| 59 |
1/1✓ Branch 10 → 11 taken 128 times.
|
128 | m_document = current; |
| 60 |
1/2✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 128 times.
|
128 | if (!m_document) return; |
| 61 | // A highlighter's new formats re-lay the block out: the layout reports | ||
| 62 | // it, and every edit does too. | ||
| 63 |
1/1✓ Branch 15 → 16 taken 128 times.
|
128 | connect(m_document, &QTextDocument::contentsChanged, this, &TextMarksItem::schedule); |
| 64 |
2/3✓ Branch 18 → 19 taken 128 times.
✓ Branch 19 → 20 taken 128 times.
✗ Branch 19 → 23 not taken.
|
128 | if (auto *layout = m_document->documentLayout()) |
| 65 |
1/1✓ Branch 20 → 21 taken 128 times.
|
128 | connect(layout, &QAbstractTextDocumentLayout::update, this, &TextMarksItem::schedule); |
| 66 | } | ||
| 67 | |||
| 68 | 536 | QList<TextMarksItem::Mark> TextMarksItem::collect() const { | |
| 69 | 536 | QList<Mark> marks; | |
| 70 |
1/1✓ Branch 2 → 3 taken 536 times.
|
536 | QTextDocument *doc = document(); |
| 71 |
7/12✓ Branch 3 → 4 taken 536 times.
✗ Branch 3 → 10 not taken.
✓ Branch 5 → 6 taken 536 times.
✗ Branch 5 → 10 not taken.
✓ Branch 6 → 7 taken 536 times.
✓ Branch 7 → 8 taken 536 times.
✗ Branch 7 → 10 not taken.
✓ Branch 8 → 9 taken 536 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 536 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 536 times.
|
536 | if (!doc || !m_textItem || width() <= 0 || height() <= 0) return marks; |
| 72 |
1/1✓ Branch 14 → 15 taken 536 times.
|
536 | QAbstractTextDocumentLayout *layout = doc->documentLayout(); |
| 73 |
1/2✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 536 times.
|
536 | if (!layout) return marks; |
| 74 | // QQuickTextEdit draws the document at its padding. | ||
| 75 |
1/1✓ Branch 25 → 26 taken 536 times.
|
1072 | const QPointF origin = m_textItem->mapToItem( |
| 76 |
4/4✓ Branch 19 → 20 taken 536 times.
✓ Branch 20 → 21 taken 536 times.
✓ Branch 22 → 23 taken 536 times.
✓ Branch 23 → 24 taken 536 times.
|
1072 | this, QPointF(m_textItem->property("leftPadding").toReal(), m_textItem->property("topPadding").toReal())); |
| 77 | 536 | const qreal top = -origin.y(); | |
| 78 |
1/1✓ Branch 29 → 30 taken 536 times.
|
536 | const qreal bottom = top + height(); |
| 79 |
1/1✓ Branch 32 → 33 taken 536 times.
|
536 | const int first = layout->hitTest(QPointF(0, std::max<qreal>(0, top)), Qt::FuzzyHit); |
| 80 |
5/5✓ Branch 34 → 35 taken 536 times.
✓ Branch 109 → 110 taken 1891 times.
✓ Branch 112 → 113 taken 2427 times.
✓ Branch 113 → 36 taken 1935 times.
✓ Branch 113 → 114 taken 492 times.
|
2427 | for (QTextBlock block = doc->findBlock(std::max(0, first)); block.isValid(); block = block.next()) { |
| 81 |
1/1✓ Branch 36 → 37 taken 1935 times.
|
1935 | const QRectF blockRect = layout->blockBoundingRect(block); |
| 82 |
2/2✓ Branch 38 → 39 taken 44 times.
✓ Branch 38 → 40 taken 1891 times.
|
1935 | if (blockRect.top() > bottom) break; |
| 83 |
4/7✓ Branch 41 → 42 taken 1891 times.
✗ Branch 41 → 44 not taken.
✓ Branch 42 → 43 taken 1891 times.
✗ Branch 43 → 44 not taken.
✓ Branch 43 → 45 taken 1891 times.
✗ Branch 46 → 47 not taken.
✓ Branch 46 → 48 taken 1891 times.
|
1891 | if (blockRect.bottom() < top || !block.isVisible()) continue; |
| 84 |
1/1✓ Branch 48 → 49 taken 1891 times.
|
1891 | const QTextLayout *text = block.layout(); |
| 85 |
1/2✗ Branch 49 → 50 not taken.
✓ Branch 49 → 51 taken 1891 times.
|
1891 | if (!text) continue; |
| 86 |
5/5✓ Branch 51 → 52 taken 1891 times.
✓ Branch 52 → 53 taken 1891 times.
✓ Branch 53 → 54 taken 1891 times.
✓ Branch 101 → 55 taken 2223 times.
✓ Branch 101 → 102 taken 1891 times.
|
4114 | for (const QTextLayout::FormatRange &range : text->formats()) { |
| 87 |
6/7✓ Branch 56 → 57 taken 2223 times.
✓ Branch 57 → 58 taken 201 times.
✓ Branch 57 → 59 taken 2022 times.
✗ Branch 58 → 59 not taken.
✓ Branch 58 → 60 taken 201 times.
✓ Branch 61 → 62 taken 2022 times.
✓ Branch 61 → 63 taken 201 times.
|
2223 | if (range.format.underlineStyle() != QTextCharFormat::WaveUnderline || range.length <= 0) continue; |
| 88 |
3/4✓ Branch 63 → 64 taken 201 times.
✓ Branch 65 → 66 taken 201 times.
✗ Branch 65 → 67 not taken.
✓ Branch 66 → 68 taken 201 times.
|
201 | const QColor color = range.format.underlineColor().isValid() ? range.format.underlineColor() : QColor(Qt::red); |
| 89 | // A word may wrap: one mark per line it covers. | ||
| 90 | 201 | int position = range.start; | |
| 91 | 201 | const int end = range.start + range.length; | |
| 92 |
2/2✓ Branch 96 → 69 taken 201 times.
✓ Branch 96 → 97 taken 201 times.
|
402 | while (position < end) { |
| 93 |
1/1✓ Branch 69 → 70 taken 201 times.
|
201 | const QTextLine line = text->lineForTextPosition(position); |
| 94 |
1/2✗ Branch 71 → 72 not taken.
✓ Branch 71 → 73 taken 201 times.
|
201 | if (!line.isValid()) break; |
| 95 |
2/2✓ Branch 73 → 74 taken 201 times.
✓ Branch 74 → 75 taken 201 times.
|
201 | const int lineEnd = std::min(end, line.textStart() + line.textLength()); |
| 96 |
1/1✓ Branch 76 → 77 taken 201 times.
|
201 | const qreal x1 = line.cursorToX(position); |
| 97 |
1/1✓ Branch 77 → 78 taken 201 times.
|
201 | const qreal x2 = line.cursorToX(lineEnd); |
| 98 | 201 | Mark mark; | |
| 99 | 201 | mark.x = origin.x() + blockRect.left() + std::min(x1, x2); | |
| 100 |
2/2✓ Branch 83 → 84 taken 201 times.
✓ Branch 84 → 85 taken 201 times.
|
201 | mark.y = origin.y() + blockRect.top() + line.y() + line.ascent() + 2; |
| 101 | 201 | mark.width = std::abs(x2 - x1); | |
| 102 | 201 | mark.color = color; | |
| 103 |
2/3✓ Branch 86 → 87 taken 201 times.
✗ Branch 86 → 88 not taken.
✓ Branch 87 → 88 taken 201 times.
|
201 | if (mark.width > 0) marks.append(mark); |
| 104 |
1/2✗ Branch 89 → 90 not taken.
✓ Branch 89 → 91 taken 201 times.
|
201 | if (marks.size() >= MaxMarks) return marks; |
| 105 |
1/2✗ Branch 91 → 92 not taken.
✓ Branch 91 → 93 taken 201 times.
|
201 | if (lineEnd <= position) break; |
| 106 | 201 | position = lineEnd; | |
| 107 | } | ||
| 108 | 1891 | } | |
| 109 | } | ||
| 110 | 536 | return marks; | |
| 111 | ✗ | } | |
| 112 | |||
| 113 | 536 | void TextMarksItem::updatePolish() { | |
| 114 | // The document a reused TextEdit shows may have changed since. | ||
| 115 |
1/1✓ Branch 2 → 3 taken 536 times.
|
536 | watchDocument(); |
| 116 |
1/1✓ Branch 3 → 4 taken 536 times.
|
536 | m_marks = collect(); |
| 117 | 536 | QVariantList drawnList; | |
| 118 |
1/1✓ Branch 7 → 8 taken 536 times.
|
536 | drawnList.reserve(m_marks.size()); |
| 119 |
2/2✓ Branch 40 → 11 taken 201 times.
✓ Branch 40 → 41 taken 536 times.
|
737 | for (const Mark &mark : std::as_const(m_marks)) |
| 120 |
3/5✓ Branch 26 → 27 taken 201 times.
✓ Branch 30 → 31 taken 603 times.
✓ Branch 30 → 32 taken 201 times.
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 57 not taken.
|
1407 | drawnList.append(QVariantMap{{QStringLiteral("x"), mark.x}, {QStringLiteral("y"), mark.y}, {QStringLiteral("width"), mark.width}}); |
| 121 |
3/3✓ Branch 41 → 42 taken 536 times.
✓ Branch 42 → 43 taken 57 times.
✓ Branch 42 → 45 taken 479 times.
|
536 | if (drawnList != m_drawnList) { |
| 122 | 57 | m_drawnList = drawnList; | |
| 123 |
1/1✓ Branch 44 → 45 taken 57 times.
|
57 | emit drawnChanged(); |
| 124 | } | ||
| 125 |
1/1✓ Branch 46 → 47 taken 536 times.
|
536 | update(); |
| 126 | 1340 | } | |
| 127 | |||
| 128 | 365 | void TextMarksItem::paint(QPainter *painter) { | |
| 129 | // A wave of 4 px per period, 1.5 px high: the usual spelling mark. | ||
| 130 | 365 | constexpr qreal period = 4.0; | |
| 131 | 365 | constexpr qreal amplitude = 1.5; | |
| 132 |
2/2✓ Branch 25 → 5 taken 178 times.
✓ Branch 25 → 26 taken 365 times.
|
543 | for (const Mark &mark : std::as_const(m_marks)) { |
| 133 |
1/1✓ Branch 7 → 8 taken 178 times.
|
178 | QPainterPath path(QPointF(mark.x, mark.y)); |
| 134 |
2/2✓ Branch 15 → 9 taken 3188 times.
✓ Branch 15 → 16 taken 178 times.
|
3366 | for (qreal x = 0; x < mark.width; x += period / 2) { |
| 135 | 3188 | const qreal next = std::min(mark.width, x + period / 2); | |
| 136 | 3188 | const bool up = int(x / (period / 2)) % 2 == 0; | |
| 137 |
3/3✓ Branch 10 → 11 taken 1641 times.
✓ Branch 10 → 12 taken 1547 times.
✓ Branch 13 → 14 taken 3188 times.
|
3188 | path.lineTo(mark.x + next, mark.y + (up ? -amplitude : 0)); |
| 138 | } | ||
| 139 |
3/3✓ Branch 16 → 17 taken 178 times.
✓ Branch 17 → 18 taken 178 times.
✓ Branch 18 → 19 taken 178 times.
|
178 | painter->setPen(QPen(mark.color, 1.0)); |
| 140 |
1/1✓ Branch 21 → 22 taken 178 times.
|
178 | painter->drawPath(path); |
| 141 | 178 | } | |
| 142 | 365 | } | |
| 143 |