diff options
Diffstat (limited to 'src/quick/items/qquicktextedit.cpp')
-rw-r--r-- | src/quick/items/qquicktextedit.cpp | 487 |
1 files changed, 364 insertions, 123 deletions
diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index d543b6bd41..506b4caa52 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -46,6 +46,7 @@ #include "qquickevents_p_p.h" #include "qquickwindow.h" #include "qquicktextnode_p.h" +#include "qquicktextnodeengine_p.h" #include "qquicktextutil_p.h" #include <QtQuick/qsgsimplerectnode.h> @@ -55,12 +56,14 @@ #include <QtGui/qpainter.h> #include <QtGui/qtextobject.h> #include <QtCore/qmath.h> +#include <QtCore/qalgorithms.h> #include <private/qqmlglobal_p.h> #include <private/qqmlproperty_p.h> #include <private/qtextengine_p.h> #include <private/qsgadaptationlayer_p.h> +#include "qquicktextdocument.h" QT_BEGIN_NAMESPACE @@ -117,6 +120,11 @@ TextEdit { The link must be in rich text or HTML format and the \a link string provides access to the particular link. */ + +// This is a pretty arbitrary figure. The idea is that we don't want to break down the document +// into text nodes corresponding to a text block each so that the glyph node grouping doesn't become pointless. +static const int nodeBreakingSize = 300; + QQuickTextEdit::QQuickTextEdit(QQuickItem *parent) : QQuickImplicitSizeItem(*(new QQuickTextEditPrivate), parent) { @@ -360,6 +368,8 @@ void QQuickTextEdit::setTextFormat(TextFormat format) not require advanced features such as transformation of the text. Using such features in combination with the NativeRendering render type will lend poor and sometimes pixelated results. + + On HighDpi "retina" displays this property is ignored and QtRendering is always used. */ QQuickTextEdit::RenderType QQuickTextEdit::renderType() const { @@ -409,7 +419,7 @@ void QQuickTextEdit::setFont(const QFont &font) moveCursorDelegate(); } updateSize(); - updateDocument(); + updateWholeDocument(); #ifndef QT_NO_IM updateInputMethod(Qt::ImCursorRectangle | Qt::ImFont); #endif @@ -445,7 +455,7 @@ void QQuickTextEdit::setColor(const QColor &color) return; d->color = color; - updateDocument(); + updateWholeDocument(); emit colorChanged(d->color); } @@ -467,7 +477,7 @@ void QQuickTextEdit::setSelectionColor(const QColor &color) return; d->selectionColor = color; - updateDocument(); + updateWholeDocument(); emit selectionColorChanged(d->selectionColor); } @@ -489,7 +499,7 @@ void QQuickTextEdit::setSelectedTextColor(const QColor &color) return; d->selectedTextColor = color; - updateDocument(); + updateWholeDocument(); emit selectedTextColorChanged(d->selectedTextColor); } @@ -634,6 +644,13 @@ void QQuickTextEditPrivate::mirrorChange() } } +#ifndef QT_NO_IM +Qt::InputMethodHints QQuickTextEditPrivate::effectiveInputMethodHints() const +{ + return inputMethodHints | Qt::ImhMultiLine; +} +#endif + QQuickTextEdit::VAlignment QQuickTextEdit::vAlign() const { Q_D(const QQuickTextEdit); @@ -1089,7 +1106,7 @@ void QQuickTextEdit::setFocusOnPress(bool on) \qmlproperty bool QtQuick2::TextEdit::persistentSelection Whether the TextEdit should keep the selection visible when it loses active focus to another - item in the scene. By default this is set to true; + item in the scene. By default this is set to false. */ bool QQuickTextEdit::persistentSelection() const { @@ -1197,6 +1214,7 @@ void QQuickTextEdit::geometryChanged(const QRectF &newGeometry, Q_D(QQuickTextEdit); if (newGeometry.width() != oldGeometry.width() && widthValid() && !d->inLayout) { updateSize(); + updateWholeDocument(); moveCursorDelegate(); } QQuickImplicitSizeItem::geometryChanged(newGeometry, oldGeometry); @@ -1230,6 +1248,44 @@ void QQuickTextEdit::componentComplete() if (d->cursorComponent && isCursorVisible()) QQuickTextUtil::createCursor(d); } + +/*! + \qmlproperty bool QtQuick2::TextEdit::selectByKeyboard + \since QtQuick 2.1 + + Defaults to true when the editor is editable, and false + when read-only. + + If true, the user can use the keyboard to select text + even if the editor is read-only. If false, the user + cannot use the keyboard to select text even if the + editor is editable. + + \sa readOnly +*/ +bool QQuickTextEdit::selectByKeyboard() const +{ + Q_D(const QQuickTextEdit); + if (d->selectByKeyboardSet) + return d->selectByKeyboard; + return !isReadOnly(); +} + +void QQuickTextEdit::setSelectByKeyboard(bool on) +{ + Q_D(QQuickTextEdit); + bool was = selectByKeyboard(); + if (!d->selectByKeyboardSet || was != on) { + d->selectByKeyboardSet = true; + d->selectByKeyboard = on; + if (on) + d->control->setTextInteractionFlags(d->control->textInteractionFlags() | Qt::TextSelectableByKeyboard); + else + d->control->setTextInteractionFlags(d->control->textInteractionFlags() & ~Qt::TextSelectableByKeyboard); + emit selectByKeyboardChanged(on); + } +} + /*! \qmlproperty bool QtQuick2::TextEdit::selectByMouse @@ -1308,8 +1364,12 @@ void QQuickTextEdit::setReadOnly(bool r) Qt::TextInteractionFlags flags = Qt::LinksAccessibleByMouse; if (d->selectByMouse) flags = flags | Qt::TextSelectableByMouse; + if (d->selectByKeyboardSet && d->selectByKeyboard) + flags = flags | Qt::TextSelectableByKeyboard; + else if (!d->selectByKeyboardSet && !r) + flags = flags | Qt::TextSelectableByKeyboard; if (!r) - flags = flags | Qt::TextSelectableByKeyboard | Qt::TextEditable; + flags = flags | Qt::TextEditable; d->control->setTextInteractionFlags(flags); if (!r) d->control->moveCursor(QTextCursor::End); @@ -1319,6 +1379,8 @@ void QQuickTextEdit::setReadOnly(bool r) #endif q_canPasteChanged(); emit readOnlyChanged(r); + if (!d->selectByKeyboardSet) + emit selectByKeyboardChanged(!r); } bool QQuickTextEdit::isReadOnly() const @@ -1440,7 +1502,7 @@ void QQuickTextEdit::select(int start, int end) d->control->setTextCursor(cursor); // QTBUG-11100 - updateSelectionMarkers(); + updateSelection(); } /*! @@ -1531,7 +1593,7 @@ void QQuickTextEdit::mousePressEvent(QMouseEvent *event) d->control->processEvent(event, QPointF(-d->xoff, -d->yoff)); if (d->focusOnPress){ bool hadActiveFocus = hasActiveFocus(); - forceActiveFocus(); + forceActiveFocus(Qt::MouseFocusReason); // re-open input panel on press if already focused #ifndef QT_NO_IM if (hasActiveFocus() && hadActiveFocus && !isReadOnly()) @@ -1593,30 +1655,7 @@ void QQuickTextEdit::inputMethodEvent(QInputMethodEvent *event) if (wasComposing != isInputMethodComposing()) emit inputMethodComposingChanged(); } -#endif // QT_NO_IM -void QQuickTextEdit::itemChange(ItemChange change, const ItemChangeData &value) -{ - Q_D(QQuickTextEdit); - if (change == ItemActiveFocusHasChanged) { - setCursorVisible(value.boolValue); - QFocusEvent focusEvent(value.boolValue ? QEvent::FocusIn : QEvent::FocusOut); - d->control->processEvent(&focusEvent, QPointF(-d->xoff, -d->yoff)); - if (value.boolValue) { - q_updateAlignment(); -#ifndef QT_NO_IM - connect(qApp->inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)), - this, SLOT(q_updateAlignment())); - } else { - disconnect(qApp->inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)), - this, SLOT(q_updateAlignment())); -#endif - } - } - QQuickItem::itemChange(change, value); -} - -#ifndef QT_NO_IM /*! \overload Returns the value of the given \a property. @@ -1631,7 +1670,7 @@ QVariant QQuickTextEdit::inputMethodQuery(Qt::InputMethodQuery property) const v = (bool)(flags() & ItemAcceptsInputMethod); break; case Qt::ImHints: - v = (int)inputMethodHints(); + v = (int)d->effectiveInputMethodHints(); break; default: v = d->control->inputMethodQuery(property); @@ -1650,6 +1689,15 @@ void QQuickTextEdit::triggerPreprocess() update(); } +typedef QQuickTextEditPrivate::Node TextNode; +typedef QList<TextNode*>::iterator TextNodeIterator; + + +static bool comesBefore(TextNode* n1, TextNode* n2) +{ + return n1->startPos() < n2->startPos(); +} + QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) { Q_UNUSED(updatePaintNodeData); @@ -1663,45 +1711,144 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * d->updateType = QQuickTextEditPrivate::UpdateNone; - QSGNode *currentNode = oldNode; - if (oldNode == 0 || d->documentDirty) { - d->documentDirty = false; + QSGTransformNode *rootNode = static_cast<QSGTransformNode *>(oldNode); + TextNodeIterator nodeIterator = d->textNodeMap.begin(); + while (nodeIterator != d->textNodeMap.end() && !(*nodeIterator)->dirty()) + ++nodeIterator; - QQuickTextNode *node = 0; - if (oldNode == 0) { - node = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext(), this); - currentNode = node; - } else { - node = static_cast<QQuickTextNode *>(oldNode); + + if (!oldNode || nodeIterator < d->textNodeMap.end()) { + + if (!oldNode) + rootNode = new QSGTransformNode; + + int firstDirtyPos = 0; + if (nodeIterator != d->textNodeMap.end()) { + firstDirtyPos = (*nodeIterator)->startPos(); + do { + rootNode->removeChildNode((*nodeIterator)->textNode()); + delete (*nodeIterator)->textNode(); + delete *nodeIterator; + nodeIterator = d->textNodeMap.erase(nodeIterator); + } while (nodeIterator != d->textNodeMap.end() && (*nodeIterator)->dirty()); } - node->setUseNativeRenderer(d->renderType == NativeRendering); - node->deleteContent(); - node->setMatrix(QMatrix4x4()); + // FIXME: the text decorations could probably be handled separately (only updated for affected textFrames) + if (d->frameDecorationsNode) { + rootNode->removeChildNode(d->frameDecorationsNode); + delete d->frameDecorationsNode; + } + d->frameDecorationsNode = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext(), this); + d->frameDecorationsNode->initEngine(QColor(), QColor(), QColor()); + + + QQuickTextNode *node = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext(), this); + node->setUseNativeRenderer(d->renderType == NativeRendering && d->window->devicePixelRatio() <= 1); + node->initEngine(d->color, d->selectedTextColor, d->selectionColor); + + + int sizeCounter = 0; + int prevBlockStart = firstDirtyPos; + QPointF basePosition(d->xoff, d->yoff); + QPointF nodeOffset; + TextNode *firstCleanNode = (nodeIterator != d->textNodeMap.end()) ? *nodeIterator : 0; + + QList<QTextFrame *> frames; + frames.append(d->document->rootFrame()); + + while (!frames.isEmpty()) { + QTextFrame *textFrame = frames.takeFirst(); + frames.append(textFrame->childFrames()); + d->frameDecorationsNode->m_engine->addFrameDecorations(d->document, textFrame); + + if (textFrame->firstPosition() > textFrame->lastPosition() + && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) { + QRectF rect = d->document->documentLayout()->frameBoundingRect(textFrame); + + if (!node->m_engine->hasContents()) { + nodeOffset = rect.topLeft(); + QMatrix4x4 transformMatrix; + transformMatrix.translate(nodeOffset.x(), nodeOffset.y()); + node->setMatrix(transformMatrix); + } + const int pos = textFrame->firstPosition() - 1; + QTextBlock block = textFrame->firstCursorPosition().block(); + node->m_engine->setCurrentLine(block.layout()->lineForTextPosition(pos - block.position())); + node->m_engine->addTextObject(QPointF(0, 0), block.charFormat(), QQuickTextNodeEngine::Unselected, d->document, + pos, textFrame->frameFormat().position()); + } else { + + QTextFrame::iterator it = textFrame->begin(); + + while (!it.atEnd()) { + QTextBlock block = it.currentBlock(); + ++it; + if (block.position() < firstDirtyPos) + continue; + + if (!node->m_engine->hasContents()) { + nodeOffset = d->document->documentLayout()->blockBoundingRect(block).topLeft(); + QMatrix4x4 transformMatrix; + transformMatrix.translate(nodeOffset.x(), nodeOffset.y()); + node->setMatrix(transformMatrix); + } + + node->m_engine->addTextBlock(d->document, block, basePosition - nodeOffset, d->color, QColor(), selectionStart(), selectionEnd() - 1); + sizeCounter += block.length(); + + if ((it.atEnd() && frames.isEmpty()) || (firstCleanNode && block.next().position() >= firstCleanNode->startPos())) // last node that needed replacing or last block of the last frame + break; + + if (sizeCounter > nodeBreakingSize) { + sizeCounter = 0; + node->m_engine->addToSceneGraph(node, QQuickText::Normal, QColor()); + nodeIterator = d->textNodeMap.insert(nodeIterator, new TextNode(prevBlockStart, node)); + ++nodeIterator; + rootNode->appendChildNode(node); + prevBlockStart = block.next().position(); + node = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext(), this); + node->setUseNativeRenderer(d->renderType == NativeRendering && d->window->devicePixelRatio() <= 1); + node->initEngine(d->color, d->selectedTextColor, d->selectionColor); + } + } + } + } + node->m_engine->addToSceneGraph(node, QQuickText::Normal, QColor()); + nodeIterator = d->textNodeMap.insert(nodeIterator, new TextNode(prevBlockStart, node)); + ++nodeIterator; + rootNode->appendChildNode(node); + d->frameDecorationsNode->m_engine->addToSceneGraph(d->frameDecorationsNode, QQuickText::Normal, QColor()); + // Now prepend the frame decorations since we want them rendered first, with the text nodes and cursor in front. + rootNode->prependChildNode(d->frameDecorationsNode); + + Q_ASSERT(nodeIterator == d->textNodeMap.end() || (*nodeIterator) == firstCleanNode); + // Update the position of the subsequent text blocks. + if (firstCleanNode) { + QPointF oldOffset = firstCleanNode->textNode()->matrix().map(QPointF(0,0)); + QPointF currentOffset = d->document->documentLayout()->blockBoundingRect(d->document->findBlock(firstCleanNode->startPos())).topLeft(); + QPointF delta = currentOffset - oldOffset; + while (nodeIterator != d->textNodeMap.end()) { + QMatrix4x4 transformMatrix = (*nodeIterator)->textNode()->matrix(); + transformMatrix.translate(delta.x(), delta.y()); + (*nodeIterator)->textNode()->setMatrix(transformMatrix); + ++nodeIterator; + } - node->addTextDocument(QPointF(d->xoff, d->yoff), d->document, d->color, QQuickText::Normal, QColor(), - QColor(), d->selectionColor, d->selectedTextColor, selectionStart(), - selectionEnd() - 1); // selectionEnd() returns first char after - // selection + } } if (d->cursorComponent == 0 && !isReadOnly()) { - QQuickTextNode *node = static_cast<QQuickTextNode *>(currentNode); - QColor color = (!d->cursorVisible || !d->control->cursorOn()) ? QColor(0, 0, 0, 0) : d->color; - - if (node->cursorNode() == 0) { - node->setCursor(cursorRectangle(), color); - } else { - node->cursorNode()->setRect(cursorRectangle()); - node->cursorNode()->setColor(color); - } - + if (d->cursorNode) + rootNode->removeChildNode(d->cursorNode); + delete d->cursorNode; + d->cursorNode = new QSGSimpleRectNode(cursorRectangle(), color); + rootNode->appendChildNode(d->cursorNode); } - return currentNode; + return rootNode; } /*! @@ -1789,27 +1936,28 @@ void QQuickTextEditPrivate::init() control->setAcceptRichText(false); control->setCursorIsFocusIndicator(true); - qmlobject_connect(control, QQuickTextControl, SIGNAL(updateRequest()), q, QQuickTextEdit, SLOT(updateDocument())); qmlobject_connect(control, QQuickTextControl, SIGNAL(updateCursorRequest()), q, QQuickTextEdit, SLOT(updateCursor())); - qmlobject_connect(control, QQuickTextControl, SIGNAL(textChanged()), q, QQuickTextEdit, SLOT(q_textChanged())); qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SIGNAL(selectedTextChanged())); - qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SLOT(updateSelectionMarkers())); - qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SLOT(updateSelectionMarkers())); + qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SLOT(updateSelection())); + qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SLOT(updateSelection())); qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SIGNAL(cursorPositionChanged())); qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorRectangleChanged()), q, QQuickTextEdit, SLOT(moveCursorDelegate())); qmlobject_connect(control, QQuickTextControl, SIGNAL(linkActivated(QString)), q, QQuickTextEdit, SIGNAL(linkActivated(QString))); + qmlobject_connect(control, QQuickTextControl, SIGNAL(textChanged()), q, QQuickTextEdit, SLOT(q_textChanged())); #ifndef QT_NO_CLIPBOARD qmlobject_connect(QGuiApplication::clipboard(), QClipboard, SIGNAL(dataChanged()), q, QQuickTextEdit, SLOT(q_canPasteChanged())); #endif qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(undoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canUndoChanged())); qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(redoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canRedoChanged())); qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(imagesLoaded()), q, QQuickTextEdit, SLOT(updateSize())); + QObject::connect(document, &QQuickTextDocumentWithImageResources::contentsChange, q, &QQuickTextEdit::q_contentsChange); document->setDefaultFont(font); document->setDocumentMargin(textMargin); document->setUndoRedoEnabled(false); // flush undo buffer. document->setUndoRedoEnabled(true); updateDefaultTextOption(); + q->updateSize(); } void QQuickTextEdit::q_textChanged() @@ -1828,6 +1976,44 @@ void QQuickTextEdit::q_textChanged() emit textChanged(); } +void QQuickTextEdit::markDirtyNodesForRange(int start, int end, int charDelta) +{ + Q_D(QQuickTextEdit); + if (start == end) + return; + TextNode dummyNode(start, 0); + TextNodeIterator it = qLowerBound(d->textNodeMap.begin(), d->textNodeMap.end(), &dummyNode, &comesBefore); + // qLowerBound gives us the first node past the start of the affected portion, rewind by one if we can. + if (it != d->textNodeMap.begin()) + --it; + + // mark the affected nodes as dirty + while (it != d->textNodeMap.constEnd()) { + if ((*it)->startPos() <= end) + (*it)->setDirty(); + else if (charDelta) + (*it)->moveStartPos(charDelta); + else + return; + ++it; + } +} + +void QQuickTextEdit::q_contentsChange(int pos, int charsRemoved, int charsAdded) +{ + Q_D(QQuickTextEdit); + + const int editRange = pos + qMax(charsAdded, charsRemoved); + const int delta = charsAdded - charsRemoved; + + markDirtyNodesForRange(pos, editRange, delta); + + if (isComponentComplete()) { + d->updateType = QQuickTextEditPrivate::UpdatePaintNode; + update(); + } +} + void QQuickTextEdit::moveCursorDelegate() { Q_D(QQuickTextEdit); @@ -1842,9 +2028,21 @@ void QQuickTextEdit::moveCursorDelegate() d->cursorItem->setY(cursorRect.y()); } -void QQuickTextEdit::updateSelectionMarkers() +void QQuickTextEdit::updateSelection() { Q_D(QQuickTextEdit); + + // No need for node updates when we go from an empty selection to another empty selection + if (d->control->textCursor().hasSelection() || d->hadSelection) { + markDirtyNodesForRange(qMin(d->lastSelectionStart, d->control->textCursor().selectionStart()), qMax(d->control->textCursor().selectionEnd(), d->lastSelectionEnd), 0); + if (isComponentComplete()) { + d->updateType = QQuickTextEditPrivate::UpdatePaintNode; + update(); + } + } + + d->hadSelection = d->control->textCursor().hasSelection(); + if (d->lastSelectionStart != d->control->textCursor().selectionStart()) { d->lastSelectionStart = d->control->textCursor().selectionStart(); emit selectionStartChanged(); @@ -1909,70 +2107,70 @@ qreal QQuickTextEditPrivate::getImplicitWidth() const void QQuickTextEdit::updateSize() { Q_D(QQuickTextEdit); - if (isComponentComplete()) { - qreal naturalWidth = d->implicitWidth; - // ### assumes that if the width is set, the text will fill to edges - // ### (unless wrap is false, then clipping will occur) - if (widthValid()) { - if (!d->requireImplicitWidth) { - emit implicitWidthChanged(); - // if the implicitWidth is used, then updateSize() has already been called (recursively) - if (d->requireImplicitWidth) - return; - } - if (d->requireImplicitWidth) { - d->document->setTextWidth(-1); - naturalWidth = d->document->idealWidth(); - - const bool wasInLayout = d->inLayout; - d->inLayout = true; - setImplicitWidth(naturalWidth); - d->inLayout = wasInLayout; - if (d->inLayout) // probably the result of a binding loop, but by letting it - return; // get this far we'll get a warning to that effect. - } - if (d->document->textWidth() != width()) - d->document->setTextWidth(width()); - } else { + if (!isComponentComplete()) { + d->dirty = true; + return; + } + + qreal naturalWidth = d->implicitWidth; + + qreal newWidth = d->document->idealWidth(); + // ### assumes that if the width is set, the text will fill to edges + // ### (unless wrap is false, then clipping will occur) + if (widthValid()) { + if (!d->requireImplicitWidth) { + emit implicitWidthChanged(); + // if the implicitWidth is used, then updateSize() has already been called (recursively) + if (d->requireImplicitWidth) + return; + } + if (d->requireImplicitWidth) { d->document->setTextWidth(-1); + naturalWidth = d->document->idealWidth(); + + const bool wasInLayout = d->inLayout; + d->inLayout = true; + setImplicitWidth(naturalWidth); + d->inLayout = wasInLayout; + if (d->inLayout) // probably the result of a binding loop, but by letting it + return; // get this far we'll get a warning to that effect. } - + if (d->document->textWidth() != width()) + d->document->setTextWidth(width()); //### need to confirm cost of always setting these - qreal newWidth = d->document->idealWidth(); - if ((!widthValid() || d->wrapMode == NoWrap) && d->document->textWidth() != newWidth) - d->document->setTextWidth(newWidth); // ### Text does not align if width is not set or the idealWidth exceeds the textWidth (QTextDoc bug) - // ### Setting the implicitWidth triggers another updateSize(), and unless there are bindings nothing has changed. - qreal iWidth = -1; - if (!widthValid() && !d->requireImplicitWidth) - iWidth = newWidth; - - QFontMetricsF fm(d->font); - qreal newHeight = d->document->isEmpty() ? qCeil(fm.height()) : d->document->size().height(); - - if (iWidth > -1) - setImplicitSize(iWidth, newHeight); - else - setImplicitHeight(newHeight); + } else if (d->wrapMode == NoWrap && d->document->textWidth() != newWidth) { + d->document->setTextWidth(newWidth); // ### Text does not align if width is not set or the idealWidth exceeds the textWidth (QTextDoc bug) + } else { + d->document->setTextWidth(-1); + } - d->xoff = QQuickTextUtil::alignedX(d->document->size().width(), width(), effectiveHAlign()); - d->yoff = QQuickTextUtil::alignedY(d->document->size().height(), height(), d->vAlign); - setBaselineOffset(fm.ascent() + d->yoff + d->textMargin); + QFontMetricsF fm(d->font); + qreal newHeight = d->document->isEmpty() ? qCeil(fm.height()) : d->document->size().height(); - QSizeF size(newWidth, newHeight); - if (d->contentSize != size) { - d->contentSize = size; - emit contentSizeChanged(); - } - } else { - d->dirty = true; + // ### Setting the implicitWidth triggers another updateSize(), and unless there are bindings nothing has changed. + if (!widthValid() && !d->requireImplicitWidth) + setImplicitSize(newWidth, newHeight); + else + setImplicitHeight(newHeight); + + d->xoff = qMax(qreal(0), QQuickTextUtil::alignedX(d->document->size().width(), width(), effectiveHAlign())); + d->yoff = QQuickTextUtil::alignedY(d->document->size().height(), height(), d->vAlign); + setBaselineOffset(fm.ascent() + d->yoff + d->textMargin); + + QSizeF size(newWidth, newHeight); + if (d->contentSize != size) { + d->contentSize = size; + emit contentSizeChanged(); } - updateDocument(); } -void QQuickTextEdit::updateDocument() +void QQuickTextEdit::updateWholeDocument() { Q_D(QQuickTextEdit); - d->documentDirty = true; + if (!d->textNodeMap.isEmpty()) { + Q_FOREACH (TextNode* node, d->textNodeMap) + node->setDirty(); + } if (isComponentComplete()) { d->updateType = QQuickTextEditPrivate::UpdatePaintNode; @@ -1994,7 +2192,7 @@ void QQuickTextEdit::q_updateAlignment() Q_D(QQuickTextEdit); if (d->determineHorizontalAlignment()) { d->updateDefaultTextOption(); - d->xoff = QQuickTextUtil::alignedX(d->document->size().width(), width(), effectiveHAlign()); + d->xoff = qMax(qreal(0), QQuickTextUtil::alignedX(d->document->size().width(), width(), effectiveHAlign())); moveCursorDelegate(); } } @@ -2062,12 +2260,36 @@ void QQuickTextEditPrivate::updateDefaultTextOption() void QQuickTextEdit::focusInEvent(QFocusEvent *event) { - Q_D(const QQuickTextEdit); + Q_D(QQuickTextEdit); + d->handleFocusEvent(event); + QQuickImplicitSizeItem::focusInEvent(event); +} + +void QQuickTextEdit::focusOutEvent(QFocusEvent *event) +{ + Q_D(QQuickTextEdit); + d->handleFocusEvent(event); + QQuickImplicitSizeItem::focusOutEvent(event); +} + +void QQuickTextEditPrivate::handleFocusEvent(QFocusEvent *event) +{ + Q_Q(QQuickTextEdit); + bool focus = event->type() == QEvent::FocusIn; + q->setCursorVisible(focus); + control->processEvent(event, QPointF(-xoff, -yoff)); + if (focus) { + q->q_updateAlignment(); #ifndef QT_NO_IM - if (d->focusOnPress && !isReadOnly()) - qGuiApp->inputMethod()->show(); + if (focusOnPress && !q->isReadOnly()) + qGuiApp->inputMethod()->show(); + q->connect(qApp->inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)), + q, SLOT(q_updateAlignment())); + } else { + q->disconnect(qApp->inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)), + q, SLOT(q_updateAlignment())); #endif - QQuickImplicitSizeItem::focusInEvent(event); + } } void QQuickTextEdit::q_canPasteChanged() @@ -2179,4 +2401,23 @@ void QQuickTextEdit::remove(int start, int end) d->control->updateCursorRectangle(false); } +/*! + \qmlproperty TextDocument QtQuick2::TextEdit::textDocument + \since QtQuick 2.1 + + Returns the QQuickTextDocument of this TextEdit. + It can be used to implement syntax highlighting using + \l QSyntaxHighlighter. + + \sa QQuickTextDocument +*/ + +QQuickTextDocument *QQuickTextEdit::textDocument() +{ + Q_D(QQuickTextEdit); + if (!d->quickDocument) + d->quickDocument = new QQuickTextDocument(this); + return d->quickDocument; +} + QT_END_NAMESPACE |