src/declarative/graphicsitems/qdeclarativetextedit.cpp
changeset 33 3e2da88830cd
parent 30 5dc02b23752f
child 37 758a864f9613
--- a/src/declarative/graphicsitems/qdeclarativetextedit.cpp	Tue Jul 06 15:10:48 2010 +0300
+++ b/src/declarative/graphicsitems/qdeclarativetextedit.cpp	Wed Aug 18 10:37:55 2010 +0300
@@ -61,26 +61,45 @@
 
 /*!
     \qmlclass TextEdit QDeclarativeTextEdit
-  \since 4.7
-    \brief The TextEdit item allows you to add editable formatted text to a scene.
+    \since 4.7
+    \brief The TextEdit item displays multiple lines of editable formatted text.
+    \inherits Item
+
+    The TextEdit item displays a block of editable, formatted text.
 
     It can display both plain and rich text. For example:
 
     \qml
 TextEdit {
-    id: edit
+    width: 240
     text: "<b>Hello</b> <i>World!</i>"
-    focus: true
     font.family: "Helvetica"
     font.pointSize: 20
     color: "blue"
-    width: 240
+    focus: true
 }
     \endqml
 
     \image declarative-textedit.gif
 
-    \sa Text
+    Setting \l {Item::focus}{focus} to \c true enables the TextEdit item to receive keyboard focus.
+
+    Note that the TextEdit does not implement scrolling, following the cursor, or other behaviors specific
+    to a look-and-feel. For example, to add flickable scrolling that follows the cursor:
+
+    \snippet snippets/declarative/texteditor.qml 0
+
+    A particular look-and-feel might use smooth scrolling (eg. using SmoothedFollow), might have a visible
+    scrollbar, or a scrollbar that fades in to show location, etc.
+
+    Clipboard support is provided by the cut(), copy(), and paste() functions, and the selection can
+    be handled in a traditional "mouse" mechanism by setting selectByMouse, or handled completely
+    from QML by manipulating selectionStart and selectionEnd, or using selectAll() or selectWord().
+
+    You can translate between cursor positions (characters from the start of the document) and pixel
+    points using positionAt() and positionToRectangle().
+
+    \sa Text, TextInput, {declarative/text/textselection}{Text Selection example}
 */
 
 /*!
@@ -94,7 +113,7 @@
 
     \image declarative-textedit.png
 
-    A QDeclarativeTextEdit object can be instantiated in Qml using the tag \c &lt;TextEdit&gt;.
+    A QDeclarativeTextEdit object can be instantiated in QML using the tag \c &lt;TextEdit&gt;.
 */
 
 /*!
@@ -111,9 +130,11 @@
 {
     Q_D(const QDeclarativeTextEdit);
 
+#ifndef QT_NO_TEXTHTMLPARSER
     if (d->richText)
         return d->document->toHtml();
     else
+#endif
         return d->document->toPlainText();
 }
 
@@ -165,12 +186,6 @@
 */
 
 /*!
-    \qmlproperty bool TextEdit::font.outline
-
-    Sets whether the font has an outline style.
-*/
-
-/*!
     \qmlproperty bool TextEdit::font.strikeout
 
     Sets whether the font has a strikeout style.
@@ -187,8 +202,9 @@
 
     Sets the font size in pixels.
 
-    Using this function makes the font device dependent.
-    Use \c pointSize to set the size of the font in a device independent manner.
+    Using this function makes the font device dependent.  Use
+    \l{TextEdit::font.pointSize} to set the size of the font in a
+    device independent manner.
 */
 
 /*!
@@ -197,8 +213,7 @@
     Sets the letter spacing for the font.
 
     Letter spacing changes the default spacing between individual letters in the font.
-    A value of 100 will keep the spacing unchanged; a value of 200 will enlarge the spacing after a character by
-    the width of the character itself.
+    A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing.
 */
 
 /*!
@@ -241,10 +256,13 @@
     Q_D(QDeclarativeTextEdit);
     if (QDeclarativeTextEdit::text() == text)
         return;
-    d->text = text;
     d->richText = d->format == RichText || (d->format == AutoText && Qt::mightBeRichText(text));
     if (d->richText) {
+#ifndef QT_NO_TEXTHTMLPARSER
         d->control->setHtml(text);
+#else
+        d->control->setPlainText(text);
+#endif
     } else {
         d->control->setPlainText(text);
     }
@@ -309,7 +327,11 @@
         d->control->setPlainText(d->text);
         updateSize();
     } else if (!wasRich && d->richText) {
+#ifndef QT_NO_TEXTHTMLPARSER
         d->control->setHtml(d->text);
+#else
+        d->control->setPlainText(d->text);
+#endif
         updateSize();
     }
     d->format = format;
@@ -427,12 +449,22 @@
     \qmlproperty enumeration TextEdit::horizontalAlignment
     \qmlproperty enumeration TextEdit::verticalAlignment
 
-    Sets the horizontal and vertical alignment of the text within the TextEdit items
+    Sets the horizontal and vertical alignment of the text within the TextEdit item's
     width and height.  By default, the text is top-left aligned.
 
-    The valid values for \c horizontalAlignment are \c TextEdit.AlignLeft, \c TextEdit.AlignRight and
-    \c TextEdit.AlignHCenter.  The valid values for \c verticalAlignment are \c TextEdit.AlignTop, \c TextEdit.AlignBottom
-    and \c TextEdit.AlignVCenter.
+    Valid values for \c horizontalAlignment are:
+    \list
+    \o TextEdit.AlignLeft (default)
+    \o TextEdit.AlignRight 
+    \o TextEdit.AlignHCenter
+    \endlist
+    
+    Valid values for \c verticalAlignment are:
+    \list
+    \o TextEdit.AlignTop (default)
+    \o TextEdit.AlignBottom
+    \c TextEdit.AlignVCenter
+    \endlist
 */
 QDeclarativeTextEdit::HAlignment QDeclarativeTextEdit::hAlign() const
 {
@@ -475,14 +507,13 @@
     The text will only wrap if an explicit width has been set.
 
     \list
-    \o TextEdit.NoWrap - no wrapping will be performed.
-    \o TextEdit.WordWrap - wrapping is done on word boundaries.
-    \o TextEdit.WrapAnywhere - Text can be wrapped at any point on a line, even if it occurs in the middle of a word.
-    \o TextEdit.WrapAtWordBoundaryOrAnywhere - If possible, wrapping occurs at a word boundary; otherwise it
-       will occur at the appropriate point on the line, even in the middle of a word.
+    \o TextEdit.NoWrap - no wrapping will be performed. If the text contains insufficient newlines, then implicitWidth will exceed a set width.
+    \o TextEdit.WordWrap - wrapping is done on word boundaries only. If a word is too long, implicitWidth will exceed a set width.
+    \o TextEdit.WrapAnywhere - wrapping is done at any point on a line, even if it occurs in the middle of a word.
+    \o TextEdit.Wrap - if possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate point on the line, even in the middle of a word.
     \endlist
 
-    The default is TextEdit.NoWrap.
+    The default is TextEdit.NoWrap. If you set a width, consider using TextEdit.Wrap.
 */
 QDeclarativeTextEdit::WrapMode QDeclarativeTextEdit::wrapMode() const
 {
@@ -502,10 +533,97 @@
 }
 
 /*!
+    \qmlproperty real TextEdit::paintedWidth
+
+    Returns the width of the text, including the width past the width
+    which is covered due to insufficient wrapping if \l wrapMode is set.
+*/
+qreal QDeclarativeTextEdit::paintedWidth() const
+{
+    return implicitWidth();
+}
+
+/*!
+    \qmlproperty real TextEdit::paintedHeight
+
+    Returns the height of the text, including the height past the height
+    that is covered if the text does not fit within the set height.
+*/
+qreal QDeclarativeTextEdit::paintedHeight() const
+{
+    return implicitHeight();
+}
+
+/*!
+    \qmlmethod rectangle TextEdit::positionToRectangle(position)
+
+    Returns the rectangle at the given \a position in the text. The x, y,
+    and height properties correspond to the cursor that would describe
+    that position.
+*/
+QRectF QDeclarativeTextEdit::positionToRectangle(int pos) const
+{
+    Q_D(const QDeclarativeTextEdit);
+    QTextCursor c(d->document);
+    c.setPosition(pos);
+    return d->control->cursorRect(c);
+
+}
+
+/*!
+    \qmlmethod int TextEdit::positionAt(x,y)
+
+    Returns the text position closest to pixel position (\a x, \a y).
+
+    Position 0 is before the first character, position 1 is after the first character
+    but before the second, and so on until position \l {text}.length, which is after all characters.
+*/
+int QDeclarativeTextEdit::positionAt(int x, int y) const
+{
+    Q_D(const QDeclarativeTextEdit);
+    int r = d->document->documentLayout()->hitTest(QPoint(x,y-d->yoff), Qt::FuzzyHit);
+    return r;
+}
+
+/*!
+    \qmlmethod int TextEdit::moveCursorSelection(int pos)
+
+    Moves the cursor to \a position and updates the selection accordingly.
+    (To only move the cursor, set the \l cursorPosition property.)
+
+    When this method is called it additionally sets either the
+    selectionStart or the selectionEnd (whichever was at the previous cursor position)
+    to the specified position. This allows you to easily extend and contract the selected
+    text range.
+
+    For example, take this sequence of calls:
+
+    \code
+        cursorPosition = 5
+        moveCursorSelection(9)
+        moveCursorSelection(7)
+    \endcode
+
+    This moves the cursor to position 5, extend the selection end from 5 to 9
+    and then retract the selection end from 9 to 7, leaving the text from position 5 to 7
+    selected (the 6th and 7th characters).
+*/
+void QDeclarativeTextEdit::moveCursorSelection(int pos)
+{
+    //Note that this is the same as setCursorPosition but with the KeepAnchor flag set
+    Q_D(QDeclarativeTextEdit);
+    QTextCursor cursor = d->control->textCursor();
+    if (cursor.position() == pos)
+        return;
+    cursor.setPosition(pos, QTextCursor::KeepAnchor);
+    d->control->setTextCursor(cursor);
+}
+
+/*!
     \qmlproperty bool TextEdit::cursorVisible
     If true the text edit shows a cursor.
 
-    This property is set and unset when the text edit gets focus, but it can also
+    This property is set and unset when the text edit gets active focus, but it can also
     be set directly (useful, for example, if a KeyProxy might forward keys to it).
 */
 bool QDeclarativeTextEdit::isCursorVisible() const
@@ -574,7 +692,7 @@
             disconnect(d->control, SIGNAL(cursorPositionChanged()),
                     this, SLOT(moveCursorDelegate()));
             d->control->setCursorWidth(-1);
-            dirtyCache(cursorRect());
+            dirtyCache(cursorRectangle());
             delete d->cursor;
             d->cursor = 0;
         }
@@ -601,7 +719,7 @@
         connect(d->control, SIGNAL(cursorPositionChanged()),
                 this, SLOT(moveCursorDelegate()));
         d->control->setCursorWidth(0);
-        dirtyCache(cursorRect());
+        dirtyCache(cursorRectangle());
         QDeclarative_setParent_noEvent(d->cursor, this);
         d->cursor->setParentItem(this);
         d->cursor->setHeight(QFontMetrics(d->font).height());
@@ -615,12 +733,9 @@
     \qmlproperty int TextEdit::selectionStart
 
     The cursor position before the first character in the current selection.
-    Setting this and selectionEnd allows you to specify a selection in the
-    text edit.
 
-    Note that if selectionStart == selectionEnd then there is no current
-    selection. If you attempt to set selectionStart to a value outside of
-    the current text, selectionStart will not be changed.
+    This property is read-only. To change the selection, use select(start,end),
+    selectAll(), or selectWord().
 
     \sa selectionEnd, cursorPosition, selectedText
 */
@@ -630,25 +745,13 @@
     return d->control->textCursor().selectionStart();
 }
 
-void QDeclarativeTextEdit::setSelectionStart(int s)
-{
-    Q_D(QDeclarativeTextEdit);
-    if(d->lastSelectionStart == s || s < 0 || s > text().length())
-        return;
-    d->lastSelectionStart = s;
-    d->updateSelection();// Will emit the relevant signals
-}
-
 /*!
     \qmlproperty int TextEdit::selectionEnd
 
     The cursor position after the last character in the current selection.
-    Setting this and selectionStart allows you to specify a selection in the
-    text edit.
 
-    Note that if selectionStart == selectionEnd then there is no current
-    selection. If you attempt to set selectionEnd to a value outside of
-    the current text, selectionEnd will not be changed.
+    This property is read-only. To change the selection, use select(start,end),
+    selectAll(), or selectWord().
 
     \sa selectionStart, cursorPosition, selectedText
 */
@@ -658,15 +761,6 @@
     return d->control->textCursor().selectionEnd();
 }
 
-void QDeclarativeTextEdit::setSelectionEnd(int s)
-{
-    Q_D(QDeclarativeTextEdit);
-    if(d->lastSelectionEnd == s || s < 0 || s > text().length())
-        return;
-    d->lastSelectionEnd = s;
-    d->updateSelection();// Will emit the relevant signals
-}
-
 /*!
     \qmlproperty string TextEdit::selectedText
 
@@ -688,9 +782,9 @@
 }
 
 /*!
-    \qmlproperty bool TextEdit::focusOnPress
+    \qmlproperty bool TextEdit::activeFocusOnPress
 
-    Whether the TextEdit should gain focus on a mouse press. By default this is
+    Whether the TextEdit should gain active focus on a mouse press. By default this is
     set to true.
 */
 bool QDeclarativeTextEdit::focusOnPress() const
@@ -705,13 +799,13 @@
     if (d->focusOnPress == on)
         return;
     d->focusOnPress = on;
-    emit focusOnPressChanged(d->focusOnPress);
+    emit activeFocusOnPressChanged(d->focusOnPress);
 }
 
 /*!
     \qmlproperty bool TextEdit::persistentSelection
 
-    Whether the TextEdit should keep the selection visible when it loses focus to another
+    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;
 */
 bool QDeclarativeTextEdit::persistentSelection() const
@@ -773,7 +867,7 @@
 }
 
 /*!
-    \qmlproperty string TextEdit::selectByMouse
+    \qmlproperty bool TextEdit::selectByMouse
 
     Defaults to false.
 
@@ -854,10 +948,12 @@
 }
 
 /*!
-    Returns the rectangle where the text cursor is rendered
-    within the text edit.
+    \qmlproperty rectangle TextEdit::cursorRectangle
+
+    The rectangle where the text cursor is rendered
+    within the text edit. Read-only.
 */
-QRect QDeclarativeTextEdit::cursorRect() const
+QRect QDeclarativeTextEdit::cursorRectangle() const
 {
     Q_D(const QDeclarativeTextEdit);
     return d->control->cursorRect().toRect().translated(0,-d->yoff);
@@ -914,6 +1010,8 @@
 }
 
 /*!
+    \qmlmethod void TextEdit::selectAll()
+
     Causes all text to be selected.
 */
 void QDeclarativeTextEdit::selectAll()
@@ -923,24 +1021,103 @@
 }
 
 /*!
+    \qmlmethod void TextEdit::selectWord()
+
+    Causes the word closest to the current cursor position to be selected.
+*/
+void QDeclarativeTextEdit::selectWord()
+{
+    Q_D(QDeclarativeTextEdit);
+    QTextCursor c = d->control->textCursor();
+    c.select(QTextCursor::WordUnderCursor);
+    d->control->setTextCursor(c);
+}
+
+/*!
+    \qmlmethod void TextEdit::select(start,end)
+
+    Causes the text from \a start to \a end to be selected.
+
+    If either start or end is out of range, the selection is not changed.
+
+    After calling this, selectionStart will become the lesser
+    and selectionEnd will become the greater (regardless of the order passed
+    to this method).
+
+    \sa selectionStart, selectionEnd
+*/
+void QDeclarativeTextEdit::select(int start, int end)
+{
+    Q_D(QDeclarativeTextEdit);
+    if (start < 0 || end < 0 || start > d->text.length() || end > d->text.length())
+        return;
+    QTextCursor cursor = d->control->textCursor();
+    cursor.beginEditBlock();
+    cursor.setPosition(start, QTextCursor::MoveAnchor);
+    cursor.setPosition(end, QTextCursor::KeepAnchor);
+    cursor.endEditBlock();
+    d->control->setTextCursor(cursor);
+
+    // QTBUG-11100
+    updateSelectionMarkers();
+}
+
+#ifndef QT_NO_CLIPBOARD
+/*!
+    \qmlmethod TextEdit::cut()
+
+    Moves the currently selected text to the system clipboard.
+*/
+void QDeclarativeTextEdit::cut()
+{
+    Q_D(QDeclarativeTextEdit);
+    d->control->cut();
+}
+
+/*!
+    \qmlmethod TextEdit::copy()
+
+    Copies the currently selected text to the system clipboard.
+*/
+void QDeclarativeTextEdit::copy()
+{
+    Q_D(QDeclarativeTextEdit);
+    d->control->copy();
+}
+
+/*!
+    \qmlmethod TextEdit::paste()
+
+    Replaces the currently selected text by the contents of the system clipboard.
+*/
+void QDeclarativeTextEdit::paste()
+{
+    Q_D(QDeclarativeTextEdit);
+    d->control->paste();
+}
+#endif // QT_NO_CLIPBOARD
+
+/*!
 \overload
 Handles the given mouse \a event.
 */
 void QDeclarativeTextEdit::mousePressEvent(QGraphicsSceneMouseEvent *event)
 {
     Q_D(QDeclarativeTextEdit);
-    bool hadFocus = hasFocus();
     if (d->focusOnPress){
-        QGraphicsItem *p = parentItem();//###Is there a better way to find my focus scope?
-        while(p) {
-            if (p->flags() & QGraphicsItem::ItemIsFocusScope)
-                p->setFocus();
-            p = p->parentItem();
+        bool hadActiveFocus = hasActiveFocus();
+        forceActiveFocus();
+        if (d->showInputPanelOnFocus) {
+            if (hasActiveFocus() && hadActiveFocus && !isReadOnly()) {
+                // re-open input panel on press if already focused
+                openSoftwareInputPanel();
+            }
+        } else { // show input panel on click
+            if (hasActiveFocus() && !hadActiveFocus) {
+                d->clickCausedFocus = true;
+            }
         }
-        setFocus(true);
     }
-    if (!hadFocus && hasFocus())
-        d->clickCausedFocus = true;
     if (event->type() != QEvent::GraphicsSceneMouseDoubleClick || d->selectByMouse)
         d->control->processEvent(event, QPointF(0, -d->yoff));
     if (!event->isAccepted())
@@ -954,12 +1131,18 @@
 void QDeclarativeTextEdit::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
 {
     Q_D(QDeclarativeTextEdit);
-    QWidget *widget = event->widget();
-    if (widget && (d->control->textInteractionFlags() & Qt::TextEditable) && boundingRect().contains(event->pos()))
-        qt_widget_private(widget)->handleSoftwareInputPanel(event->button(), d->clickCausedFocus);
+    d->control->processEvent(event, QPointF(0, -d->yoff));
+    if (!d->showInputPanelOnFocus) { // input panel on click
+        if (d->focusOnPress && !isReadOnly() && boundingRect().contains(event->pos())) {
+            if (QGraphicsView * view = qobject_cast<QGraphicsView*>(qApp->focusWidget())) {
+                if (view->scene() && view->scene() == scene()) {
+                    qt_widget_private(view)->handleSoftwareInputPanel(event->button(), d->clickCausedFocus);
+                }
+            }
+        }
+    }
     d->clickCausedFocus = false;
 
-    d->control->processEvent(event, QPointF(0, -d->yoff));
     if (!event->isAccepted())
         QDeclarativePaintedItem::mouseReleaseEvent(event);
 }
@@ -1036,9 +1219,19 @@
 void QDeclarativeTextEdit::updateImgCache(const QRectF &rf)
 {
     Q_D(const QDeclarativeTextEdit);
-    QRect r = rf.toRect();
-    if (r != QRect(0,0,INT_MAX,INT_MAX)) // Don't translate "everything"
-        r = r.translated(0,d->yoff);
+    QRect r;
+    if (!rf.isValid()) {
+        r = QRect(0,0,INT_MAX,INT_MAX);
+    } else {
+        r = rf.toRect();
+        if (r.height() > INT_MAX/2) {
+            // Take care of overflow when translating "everything"
+            r.setTop(r.y() + d->yoff);
+            r.setBottom(INT_MAX/2);
+        } else {
+            r = r.translated(0,d->yoff);
+        }
+    }
     dirtyCache(r);
     emit update();
 }
@@ -1069,6 +1262,15 @@
     control = new QTextControl(q);
     control->setIgnoreUnusedNavigationEvents(true);
 
+    // QTextControl follows the default text color
+    // defined by the platform, declarative text
+    // should be black by default
+    QPalette pal = control->palette();
+    if (pal.color(QPalette::Text) != color) {
+        pal.setColor(QPalette::Text, color);
+        control->setPalette(pal);
+    }
+
     QObject::connect(control, SIGNAL(updateRequest(QRectF)), q, SLOT(updateImgCache(QRectF)));
 
     QObject::connect(control, SIGNAL(textChanged()), q, SLOT(q_textChanged()));
@@ -1076,6 +1278,7 @@
     QObject::connect(control, SIGNAL(selectionChanged()), q, SLOT(updateSelectionMarkers()));
     QObject::connect(control, SIGNAL(cursorPositionChanged()), q, SLOT(updateSelectionMarkers()));
     QObject::connect(control, SIGNAL(cursorPositionChanged()), q, SIGNAL(cursorPositionChanged()));
+    QObject::connect(control, SIGNAL(cursorPositionChanged()), q, SIGNAL(cursorRectangleChanged()));
 
     document = control->document();
     document->setDefaultFont(font);
@@ -1087,8 +1290,11 @@
 
 void QDeclarativeTextEdit::q_textChanged()
 {
+    Q_D(QDeclarativeTextEdit);
+    d->text = text();
     updateSize();
-    emit textChanged(text());
+    updateMicroFocus();
+    emit textChanged(d->text);
 }
 
 void QDeclarativeTextEdit::moveCursorDelegate()
@@ -1107,7 +1313,6 @@
     QTextCursor cursor = control->textCursor();
     bool startChange = (lastSelectionStart != cursor.selectionStart());
     bool endChange = (lastSelectionEnd != cursor.selectionEnd());
-    //### Is it worth calculating a more minimal set of movements?
     cursor.beginEditBlock();
     cursor.setPosition(lastSelectionStart, QTextCursor::MoveAnchor);
     cursor.setPosition(lastSelectionEnd, QTextCursor::KeepAnchor);
@@ -1117,8 +1322,6 @@
         q->selectionStartChanged();
     if(endChange)
         q->selectionEndChanged();
-    startChange = (lastSelectionStart != control->textCursor().selectionStart());
-    endChange = (lastSelectionEnd != control->textCursor().selectionEnd());
 }
 
 void QDeclarativeTextEdit::updateSelectionMarkers()
@@ -1132,8 +1335,26 @@
         d->lastSelectionEnd = d->control->textCursor().selectionEnd();
         emit selectionEndChanged();
     }
+    updateMicroFocus();
 }
 
+QRectF QDeclarativeTextEdit::boundingRect() const
+{
+    Q_D(const QDeclarativeTextEdit);
+    QRectF r = QDeclarativePaintedItem::boundingRect();
+    int cursorWidth = 1;
+    if(d->cursor)
+        cursorWidth = d->cursor->width();
+    if(!d->document->isEmpty())
+        cursorWidth += 3;// ### Need a better way of accounting for space between char and cursor
+
+    // Could include font max left/right bearings to either side of rectangle.
+
+    r.setRight(r.right() + cursorWidth);
+    return r.translated(0,d->yoff);
+}
+
+
 //### we should perhaps be a bit smarter here -- depending on what has changed, we shouldn't
 //    need to do all the calculations each time
 void QDeclarativeTextEdit::updateSize()
@@ -1144,35 +1365,39 @@
         int dy = height();
         // ### assumes that if the width is set, the text will fill to edges
         // ### (unless wrap is false, then clipping will occur)
-        if (widthValid())
+        if (widthValid() && d->document->textWidth() != width())
             d->document->setTextWidth(width());
         dy -= (int)d->document->size().height();
 
+        int nyoff;
         if (heightValid()) {
             if (d->vAlign == AlignBottom)
-                d->yoff = dy;
+                nyoff = dy;
             else if (d->vAlign == AlignVCenter)
-                d->yoff = dy/2;
+                nyoff = dy/2;
+            else
+                nyoff = 0;
         } else {
-            d->yoff = 0;
+            nyoff = 0;
+        }
+        if (nyoff != d->yoff) {
+            prepareGeometryChange();
+            d->yoff = nyoff;
         }
         setBaselineOffset(fm.ascent() + d->yoff + d->textMargin);
 
         //### need to comfirm cost of always setting these
         int newWidth = qCeil(d->document->idealWidth());
-        if (!widthValid())
+        if (!widthValid() && d->document->textWidth() != newWidth)
             d->document->setTextWidth(newWidth); // ### Text does not align if width is not set (QTextDoc bug)
-        int cursorWidth = 1;
-        if(d->cursor)
-            cursorWidth = d->cursor->width();
-        newWidth += cursorWidth;
-        if(!d->document->isEmpty())
-            newWidth += 3;// ### Need a better way of accounting for space between char and cursor
         // ### Setting the implicitWidth triggers another updateSize(), and unless there are bindings nothing has changed.
         setImplicitWidth(newWidth);
-        setImplicitHeight(d->text.isEmpty() ? fm.height() : (int)d->document->size().height());
+        qreal newHeight = d->document->isEmpty() ? fm.height() : (int)d->document->size().height();
+        setImplicitHeight(newHeight);
 
-        setContentsSize(QSize(width(), height()));
+        setContentsSize(QSize(newWidth, newHeight));
+
+        emit paintedSizeChanged();
     } else {
         d->dirty = true;
     }
@@ -1193,4 +1418,118 @@
     document->setDefaultTextOption(opt);
 }
 
+
+/*!
+    \qmlmethod void TextEdit::openSoftwareInputPanel()
+
+    Opens software input panels like virtual keyboards for typing, useful for
+    customizing when you want the input keyboard to be shown and hidden in
+    your application.
+
+    By default the opening of input panels follows the platform style. On Symbian^1 and
+    Symbian^3 -based devices the panels are opened by clicking TextEdit. On other platforms
+    the panels are automatically opened when TextEdit element gains active focus. Input panels are
+    always closed if no editor has active focus.
+
+    You can disable the automatic behavior by setting the property \c activeFocusOnPress to false
+    and use functions openSoftwareInputPanel() and closeSoftwareInputPanel() to implement
+    the behavior you want.
+
+    Only relevant on platforms, which provide virtual keyboards.
+
+    \code
+        import Qt 4.7
+        TextEdit {
+            id: textEdit
+            text: "Hello world!"
+            activeFocusOnPress: false
+            MouseArea {
+                anchors.fill: parent
+                onClicked: {
+                    if (!textEdit.activeFocus) {
+                        textEdit.forceActiveFocus();
+                        textEdit.openSoftwareInputPanel();
+                    } else {
+                        textEdit.focus = false;
+                    }
+                }
+                onPressAndHold: textEdit.closeSoftwareInputPanel();
+            }
+        }
+    \endcode
+*/
+void QDeclarativeTextEdit::openSoftwareInputPanel()
+{
+    QEvent event(QEvent::RequestSoftwareInputPanel);
+    if (qApp) {
+        if (QGraphicsView * view = qobject_cast<QGraphicsView*>(qApp->focusWidget())) {
+            if (view->scene() && view->scene() == scene()) {
+                QApplication::sendEvent(view, &event);
+            }
+        }
+    }
+}
+
+/*!
+    \qmlmethod void TextEdit::closeSoftwareInputPanel()
+
+    Closes a software input panel like a virtual keyboard shown on the screen, useful
+    for customizing when you want the input keyboard to be shown and hidden in
+    your application.
+
+    By default the opening of input panels follows the platform style. On Symbian^1 and
+    Symbian^3 -based devices the panels are opened by clicking TextEdit. On other platforms
+    the panels are automatically opened when TextEdit element gains active focus. Input panels are
+    always closed if no editor has active focus.
+
+    You can disable the automatic behavior by setting the property \c activeFocusOnPress to false
+    and use functions openSoftwareInputPanel() and closeSoftwareInputPanel() to implement
+    the behavior you want.
+
+    Only relevant on platforms, which provide virtual keyboards.
+
+    \code
+        import Qt 4.7
+        TextEdit {
+            id: textEdit
+            text: "Hello world!"
+            activeFocusOnPress: false
+            MouseArea {
+                anchors.fill: parent
+                onClicked: {
+                    if (!textEdit.activeFocus) {
+                        textEdit.forceActiveFocus();
+                        textEdit.openSoftwareInputPanel();
+                    } else {
+                        textEdit.focus = false;
+                    }
+                }
+                onPressAndHold: textEdit.closeSoftwareInputPanel();
+            }
+        }
+    \endcode
+*/
+void QDeclarativeTextEdit::closeSoftwareInputPanel()
+{
+    QEvent event(QEvent::CloseSoftwareInputPanel);
+    if (qApp) {
+        if (QGraphicsView * view = qobject_cast<QGraphicsView*>(qApp->focusWidget())) {
+            if (view->scene() && view->scene() == scene()) {
+                QApplication::sendEvent(view, &event);
+            }
+        }
+    }
+}
+
+void QDeclarativeTextEdit::focusInEvent(QFocusEvent *event)
+{
+    Q_D(const QDeclarativeTextEdit);
+    if (d->showInputPanelOnFocus) {
+        if (d->focusOnPress && !isReadOnly()) {
+            openSoftwareInputPanel();
+        }
+    }
+    QDeclarativePaintedItem::focusInEvent(event);
+}
+
 QT_END_NAMESPACE