From e430f1336881850a73a54a291e0276160d568212 Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Mon, 9 May 2011 12:07:55 +0200 Subject: Make QSGTextNode back-end for QML's TextInput To get the benefit of different glyph node backends, the TextInput item has now been moved over on SceneGraph's TextNode instead of QPainter, and selections and decorations are painted using QSGSimpleRects. --- src/declarative/items/qsgtextinput.cpp | 143 ++++++++++++++---- src/declarative/items/qsgtextinput_p.h | 4 +- src/declarative/items/qsgtextinput_p_p.h | 9 +- src/declarative/items/qsgtextnode.cpp | 250 +++++++++++++++++++++++++++++-- src/declarative/items/qsgtextnode_p.h | 15 +- 5 files changed, 374 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/declarative/items/qsgtextinput.cpp b/src/declarative/items/qsgtextinput.cpp index 1db4474d4a..a0c3c3e0ef 100644 --- a/src/declarative/items/qsgtextinput.cpp +++ b/src/declarative/items/qsgtextinput.cpp @@ -52,13 +52,15 @@ #include #include #include +#include +#include QT_BEGIN_NAMESPACE QWidgetPrivate *qt_widget_private(QWidget *widget); QSGTextInput::QSGTextInput(QSGItem* parent) -: QSGImplicitSizePaintedItem(*(new QSGTextInputPrivate), parent) +: QSGImplicitSizeItem(*(new QSGTextInputPrivate), parent) { Q_D(QSGTextInput); d->init(); @@ -616,7 +618,7 @@ void QSGTextInput::keyPressEvent(QKeyEvent* ev) d->control->processKeyEvent(ev); } if (!ev->isAccepted()) - QSGPaintedItem::keyPressEvent(ev); + QSGImplicitSizeItem::keyPressEvent(ev); } void QSGTextInput::inputMethodEvent(QInputMethodEvent *ev) @@ -631,7 +633,7 @@ void QSGTextInput::inputMethodEvent(QInputMethodEvent *ev) d->updateHorizontalScroll(); } if (!ev->isAccepted()) - QSGPaintedItem::inputMethodEvent(ev); + QSGImplicitSizeItem::inputMethodEvent(ev); if (wasComposing != (d->control->preeditAreaText().length() > 0)) emit inputMethodComposingChanged(); @@ -647,7 +649,7 @@ void QSGTextInput::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) d->control->selectWordAtPos(cursor); event->setAccepted(true); } else { - QSGPaintedItem::mouseDoubleClickEvent(event); + QSGImplicitSizeItem::mouseDoubleClickEvent(event); } } @@ -692,7 +694,7 @@ void QSGTextInput::mouseMoveEvent(QGraphicsSceneMouseEvent *event) moveCursorSelection(d->xToPos(event->pos().x()), d->mouseSelectionMode); event->setAccepted(true); } else { - QSGPaintedItem::mouseMoveEvent(event); + QSGImplicitSizeItem::mouseMoveEvent(event); } } @@ -715,7 +717,7 @@ void QSGTextInput::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) d->clickCausedFocus = false; d->control->processEvent(event); if (!event->isAccepted()) - QSGPaintedItem::mouseReleaseEvent(event); + QSGImplicitSizeItem::mouseReleaseEvent(event); } bool QSGTextInputPrivate::sendMouseEventToInputContext( @@ -781,7 +783,7 @@ bool QSGTextInput::event(QEvent* ev) handled = d->control->processEvent(ev); } if(!handled) - handled = QSGPaintedItem::event(ev); + handled = QSGImplicitSizeItem::event(ev); return handled; } @@ -793,7 +795,7 @@ void QSGTextInput::geometryChanged(const QRectF &newGeometry, updateSize(); d->updateHorizontalScroll(); } - QSGPaintedItem::geometryChanged(newGeometry, oldGeometry); + QSGImplicitSizeItem::geometryChanged(newGeometry, oldGeometry); } int QSGTextInputPrivate::calculateTextWidth() @@ -860,20 +862,46 @@ void QSGTextInputPrivate::updateHorizontalScroll() } } -void QSGTextInput::paint(QPainter *p) -{ - // XXX todo - QRect r(0, 0, width(), height()); - +//void QSGTextInput::paint(QPainter *p) +//{ +// // XXX todo +// QRect r(0, 0, width(), height()); + +// Q_D(QSGTextInput); +// p->setRenderHint(QPainter::TextAntialiasing, true); +// p->save(); +// p->setPen(QPen(d->color)); +// int flags = QLineControl::DrawText; +// if(!isReadOnly() && d->cursorVisible && !d->cursorItem) +// flags |= QLineControl::DrawCursor; +// if (d->control->hasSelectedText()) +// flags |= QLineControl::DrawSelections; +// QPoint offset = QPoint(0,0); +// QFontMetrics fm = QFontMetrics(d->font); +// QRect br(boundingRect().toRect()); +// if (d->autoScroll) { +// // the y offset is there to keep the baseline constant in case we have script changes in the text. +// offset = br.topLeft() - QPoint(d->hscroll, d->control->ascent() - fm.ascent()); +// } else { +// offset = QPoint(d->hscroll, 0); +// } +// d->control->draw(p, offset, r, flags); +// p->restore(); +//} + +QSGNode *QSGTextInput::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) +{ + Q_UNUSED(data); Q_D(QSGTextInput); - p->setRenderHint(QPainter::TextAntialiasing, true); - p->save(); - p->setPen(QPen(d->color)); - int flags = QLineControl::DrawText; - if(!isReadOnly() && d->cursorVisible && !d->cursorItem) - flags |= QLineControl::DrawCursor; - if (d->control->hasSelectedText()) - flags |= QLineControl::DrawSelections; + + QSGTextNode *node = static_cast(oldNode); + if (node == 0) + node = new QSGTextNode(QSGItemPrivate::get(this)->sceneGraphContext()); + d->textNode = node; + + node->deleteContent(); + node->setMatrix(QMatrix4x4()); + QPoint offset = QPoint(0,0); QFontMetrics fm = QFontMetrics(d->font); QRect br(boundingRect().toRect()); @@ -883,8 +911,26 @@ void QSGTextInput::paint(QPainter *p) } else { offset = QPoint(d->hscroll, 0); } - d->control->draw(p, offset, r, flags); - p->restore(); + + // ### Thread of text layout :/ + QTextLayout *textLayout = d->control->textLayout(); + node->addTextLayout(offset, textLayout, d->color, + QSGText::Normal, QColor(), + d->selectionColor, d->selectedTextColor, + d->control->selectionStart(), + d->control->selectionEnd() - 1); // selectionEnd() returns first char after + // selection + + if (!isReadOnly() && d->cursorItem == 0) { + offset.rx() += d->control->cursorToX(); + node->setCursor(QRectF(offset, QSizeF(d->control->cursorWidth(), br.height())), d->color); + if (!d->cursorVisible + || (!d->control->cursorBlinkStatus() && d->control->cursorBlinkPeriod() > 0)) { + d->hideCursor(); + } + } + + return node; } QVariant QSGTextInput::inputMethodQuery(Qt::InputMethodQuery property) const @@ -1123,7 +1169,7 @@ void QSGTextInput::focusInEvent(QFocusEvent *event) openSoftwareInputPanel(); } } - QSGPaintedItem::focusInEvent(event); + QSGImplicitSizeItem::focusInEvent(event); } void QSGTextInput::itemChange(ItemChange change, const ItemChangeData &value) @@ -1156,6 +1202,7 @@ void QSGTextInputPrivate::init() q->setSmooth(smooth); q->setAcceptedMouseButtons(Qt::LeftButton); q->setFlag(QSGItem::ItemAcceptsInputMethod); + q->setFlag(QSGItem::ItemHasContents); q->connect(control, SIGNAL(cursorPositionChanged(int,int)), q, SLOT(cursorPosChanged())); q->connect(control, SIGNAL(selectionChanged()), @@ -1246,19 +1293,56 @@ void QSGTextInput::q_textChanged() } } +void QSGTextInputPrivate::showCursor() +{ + if (textNode != 0 && textNode->cursorNode() != 0) + textNode->cursorNode()->setColor(color); +} + +void QSGTextInputPrivate::hideCursor() +{ + if (textNode != 0 && textNode->cursorNode() != 0) + textNode->cursorNode()->setColor(QColor(0, 0, 0, 0)); +} + void QSGTextInput::updateRect(const QRect &r) { Q_D(QSGTextInput); - if(r == QRect()) + if (!isComponentComplete()) + return; + + if (r.isEmpty()) { update(); - else - update(QRect(r.x() - d->hscroll, r.y(), r.width(), r.height())); + } else { // Cursor update + QSGSimpleRectNode *cursorNode = d->textNode->cursorNode(); + if (cursorNode != 0) { + QPoint offset = QPoint(0,0); + QFontMetrics fm = QFontMetrics(d->font); + QRect br(boundingRect().toRect()); + if (d->autoScroll) { + // the y offset is there to keep the baseline constant in case we have script changes in the text. + offset = br.topLeft() - QPoint(d->hscroll, d->control->ascent() - fm.ascent()); + } else { + offset = QPoint(d->hscroll, 0); + } + offset.rx() += d->control->cursorToX(); + + cursorNode->setRect(QRectF(offset, QSizeF(d->control->cursorWidth(), br.height()))); + + if (!d->cursorVisible + || (!d->control->cursorBlinkStatus() && d->control->cursorBlinkPeriod() > 0)) { + d->hideCursor(); + } else { + d->showCursor(); + } + } + } } QRectF QSGTextInput::boundingRect() const { Q_D(const QSGTextInput); - QRectF r = QSGPaintedItem::boundingRect(); + QRectF r = QSGImplicitSizeItem::boundingRect(); int cursorWidth = d->cursorItem ? d->cursorItem->width() : d->control->cursorWidth(); @@ -1274,8 +1358,7 @@ void QSGTextInput::updateSize(bool needsRedraw) int w = width(); int h = height(); setImplicitHeight(d->control->height()-1); // -1 to counter QLineControl's +1 which is not consistent with Text. - setImplicitWidth(d->calculateTextWidth()); - setContentsSize(QSize(width(), height())); + setImplicitWidth(d->calculateTextWidth()); if(w==width() && h==height() && needsRedraw) update(); } diff --git a/src/declarative/items/qsgtextinput_p.h b/src/declarative/items/qsgtextinput_p.h index ccea485e99..d2bc9e3961 100644 --- a/src/declarative/items/qsgtextinput_p.h +++ b/src/declarative/items/qsgtextinput_p.h @@ -54,7 +54,7 @@ QT_MODULE(Declarative) class QSGTextInputPrivate; class QValidator; -class Q_AUTOTEST_EXPORT QSGTextInput : public QSGImplicitSizePaintedItem +class Q_AUTOTEST_EXPORT QSGTextInput : public QSGImplicitSizeItem { Q_OBJECT Q_ENUMS(HAlignment) @@ -204,7 +204,6 @@ public: bool hasAcceptableInput() const; - void paint(QPainter *p); QVariant inputMethodQuery(Qt::InputMethodQuery property) const; QRectF boundingRect() const; @@ -260,6 +259,7 @@ protected: bool event(QEvent *e); void focusInEvent(QFocusEvent *event); virtual void itemChange(ItemChange, const ItemChangeData &); + QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data); public Q_SLOTS: void selectAll(); diff --git a/src/declarative/items/qsgtextinput_p_p.h b/src/declarative/items/qsgtextinput_p_p.h index 6561d28a31..6f69be5d69 100644 --- a/src/declarative/items/qsgtextinput_p_p.h +++ b/src/declarative/items/qsgtextinput_p_p.h @@ -65,7 +65,9 @@ QT_BEGIN_NAMESPACE -class Q_AUTOTEST_EXPORT QSGTextInputPrivate : public QSGImplicitSizePaintedItemPrivate +class QSGTextNode; + +class Q_AUTOTEST_EXPORT QSGTextInputPrivate : public QSGImplicitSizeItemPrivate { Q_DECLARE_PUBLIC(QSGTextInput) public: @@ -76,7 +78,7 @@ public: hscroll(0), oldScroll(0), oldValidity(false), focused(false), focusOnPress(true), showInputPanelOnFocus(true), clickCausedFocus(false), cursorVisible(false), autoScroll(true), selectByMouse(false), canPaste(false), hAlignImplicit(true), - selectPressed(false) + selectPressed(false), textNode(0) { #ifdef Q_OS_SYMBIAN if (QSysInfo::symbianVersion() == QSysInfo::SV_SF_1 || QSysInfo::symbianVersion() == QSysInfo::SV_SF_3) { @@ -106,6 +108,8 @@ public: int calculateTextWidth(); bool sendMouseEventToInputContext(QGraphicsSceneMouseEvent *event, QEvent::Type eventType); void updateInputMethodHints(); + void hideCursor(); + void showCursor(); QLineControl* control; @@ -122,6 +126,7 @@ public: QPointer cursorComponent; QPointer cursorItem; QPointF pressPos; + QSGTextNode *textNode; int lastSelectionStart; int lastSelectionEnd; diff --git a/src/declarative/items/qsgtextnode.cpp b/src/declarative/items/qsgtextnode.cpp index 33325a14ab..f48b65f168 100644 --- a/src/declarative/items/qsgtextnode.cpp +++ b/src/declarative/items/qsgtextnode.cpp @@ -47,6 +47,7 @@ #include +#include #include #include #include @@ -64,7 +65,7 @@ QT_BEGIN_NAMESPACE Creates an empty QSGTextNode */ QSGTextNode::QSGTextNode(QSGContext *context) -: m_context(context) + : m_context(context), m_cursorNode(0) { #if defined(QML_RUNTIME_TESTING) description = QLatin1String("text"); @@ -184,21 +185,245 @@ void QSGTextNode::addTextDocument(const QPointF &position, QTextDocument *textDo } } -void QSGTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color, - QSGText::TextStyle style, const QColor &styleColor) +void QSGTextNode::setCursor(const QRectF &rect, const QColor &color) { - QList glyphsList(textLayout->glyphRuns()); - for (int i=0; i *binaryTree, + const QGlyphRun &glyphRun, + SelectionState selectionState) + { + int newIndex = binaryTree->size(); + QRectF searchRect = glyphRun.boundingRect(); + + binaryTree->append(BinaryTreeNode(glyphRun, selectionState, searchRect)); + if (newIndex == 0) + return; + + int searchIndex = 0; + forever { + BinaryTreeNode *node = binaryTree->data() + searchIndex; + if (searchRect.left() < node->boundingRect.left()) { + if (node->leftChildIndex < 0) { + node->leftChildIndex = newIndex; + break; + } else { + searchIndex = node->leftChildIndex; + } + } else { + if (node->rightChildIndex < 0) { + node->rightChildIndex = newIndex; + break; + } else { + searchIndex = node->rightChildIndex; + } + } + } + } + + static void inOrder(const QVarLengthArray &binaryTree, + QVarLengthArray *sortedIndexes, + int currentIndex = 0) + { + Q_ASSERT(currentIndex < binaryTree.size()); + + const BinaryTreeNode *node = binaryTree.data() + currentIndex; + if (node->leftChildIndex >= 0) + inOrder(binaryTree, sortedIndexes, node->leftChildIndex); + + sortedIndexes->append(currentIndex); + + if (node->rightChildIndex >= 0) + inOrder(binaryTree, sortedIndexes, node->rightChildIndex); + } + }; +} + +void QSGTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color, + QSGText::TextStyle style, const QColor &styleColor, + const QColor &selectionColor, const QColor &selectedTextColor, + int selectionStart, int selectionEnd) +{ QFont font = textLayout->font(); - QRawFont rawFont = QRawFont::fromFont(font); - if (font.strikeOut() || font.underline() || font.overline()) { - addTextDecorations(position, rawFont, color, textLayout->boundingRect().width(), - font.overline(), font.strikeOut(), font.underline()); + bool overline = font.overline(); + bool underline = font.underline(); + bool strikeOut = font.strikeOut(); + QRawFont decorationRawFont = QRawFont::fromFont(font); + + if (selectionStart < 0 || selectionEnd < 0) { + for (int i=0; ilineCount(); ++i) { + QTextLine line = textLayout->lineAt(i); + + QList glyphRuns = line.glyphRuns(); + for (int j=0; j binaryNodes; + for (int i=0; ilineCount(); ++i) { + QTextLine line = textLayout->lineAt(i); + + // Make a list of glyph runs sorted on left-most x coordinate to make it possible + // to find the correct selection rects + if (line.textStart() < selectionStart) { + QList glyphRuns = line.glyphRuns(line.textStart(), + qMin(selectionStart - line.textStart(), + line.textLength())); + for (int j=0; j= selectionStart && selectionStart >= line.textStart()) { + QList selectedGlyphRuns = line.glyphRuns(selectionStart, + selectionEnd - selectionStart + 1); + for (int j=0; j= line.textStart() && selectionEnd < lineEnd) { + QList glyphRuns = line.glyphRuns(selectionEnd + 1, lineEnd - selectionEnd); + for (int j=0; j sortedIndexes; + BinaryTreeNode::inOrder(binaryNodes, &sortedIndexes); + + Q_ASSERT(sortedIndexes.size() == binaryNodes.size()); + + BinaryTreeNode::SelectionState currentSelectionState = BinaryTreeNode::Unselected; + QRectF currentRect = QRectF(line.x(), line.y(), 1, 1); + for (int i=0; i<=sortedIndexes.size(); ++i) { + BinaryTreeNode *node = 0; + if (i < sortedIndexes.size()) { + int sortedIndex = sortedIndexes.at(i); + Q_ASSERT(sortedIndex < binaryNodes.size()); + + node = binaryNodes.data() + sortedIndex; + const QGlyphRun &glyphRun = node->glyphRun; + + QColor currentColor = node->selectionState == BinaryTreeNode::Unselected + ? color + : selectedTextColor; + + QPointF pos(position + QPointF(0, glyphRun.rawFont().ascent())); + addGlyphs(pos, glyphRun, currentColor, style, styleColor); + + if (i == 0) + currentSelectionState = node->selectionState; + } + + // If we've reached an unselected node from a selected node, we add the + // selection rect to the graph, and we add decoration every time the + // selection state changes, because that means the text color changes + if (node == 0 || node->selectionState != currentSelectionState) { + if (node != 0) + currentRect.setRight(node->boundingRect.left()); + else + currentRect.setWidth(line.naturalTextWidth() - (currentRect.x() - line.x())); + currentRect.setY(line.y()); + currentRect.setHeight(line.height()); + + // Draw selection all the way up to the left edge of the unselected item + if (currentSelectionState == BinaryTreeNode::Selected) + prependChildNode(new QSGSimpleRectNode(currentRect, selectionColor)); + + if (overline || underline || strikeOut) { + QColor currentColor = currentSelectionState == BinaryTreeNode::Unselected + ? color + : selectedTextColor; + addTextDecorations(currentRect.topLeft() + QPointF(0, decorationRawFont.ascent()), + decorationRawFont, currentColor, currentRect.width(), + overline, strikeOut, underline); + } + + if (node != 0) { + currentSelectionState = node->selectionState; + currentRect = node->boundingRect; + } + } else { + if (currentRect.isEmpty()) + currentRect = node->boundingRect; + else + currentRect = currentRect.united(node->boundingRect); + } + } + } } } @@ -378,6 +603,7 @@ void QSGTextNode::deleteContent() { while (childCount() > 0) delete childAtIndex(0); + m_cursorNode = 0; } #if 0 diff --git a/src/declarative/items/qsgtextnode_p.h b/src/declarative/items/qsgtextnode_p.h index 7a49f51dbe..ece80bdce0 100644 --- a/src/declarative/items/qsgtextnode_p.h +++ b/src/declarative/items/qsgtextnode_p.h @@ -54,6 +54,7 @@ class QColor; class QTextDocument; class QSGContext; class QRawFont; +class QSGSimpleRectNode; class QSGTextNode : public QSGTransformNode { @@ -65,18 +66,30 @@ public: void deleteContent(); void addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color = QColor(), - QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor()); + QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor(), + const QColor &selectionColor = QColor(), const QColor &selectedTextColor = QColor(), + int selectionStart = -1, int selectionEnd = -1); void addTextDocument(const QPointF &position, QTextDocument *textDocument, const QColor &color = QColor(), QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor()); + void setCursor(const QRectF &rect, const QColor &color); + QSGSimpleRectNode *cursorNode() const { return m_cursorNode; } + private: void addTextBlock(const QPointF &position, QTextDocument *textDocument, const QTextBlock &block, const QColor &overrideColor, QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor()); QSGGlyphNode *addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color, QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor()); + QSGGlyphNode *addGlyphsAndDecoration(const QPointF &position, const QGlyphRun &glyphs, + const QColor &color, + QSGText::TextStyle style = QSGText::Normal, + const QColor &styleColor = QColor(), + const QPointF &decorationPosition = QPointF()); + void addTextDecorations(const QPointF &position, const QRawFont &font, const QColor &color, qreal width, bool hasOverline, bool hasStrikeOut, bool hasUnderline); QSGContext *m_context; + QSGSimpleRectNode *m_cursorNode; }; QT_END_NAMESPACE -- cgit v1.2.3