aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/items/qquicktextedit.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/items/qquicktextedit.cpp')
-rw-r--r--src/quick/items/qquicktextedit.cpp487
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