diff options
Diffstat (limited to 'src/declarative/items/qsgtextedit.cpp')
-rw-r--r-- | src/declarative/items/qsgtextedit.cpp | 1232 |
1 files changed, 1232 insertions, 0 deletions
diff --git a/src/declarative/items/qsgtextedit.cpp b/src/declarative/items/qsgtextedit.cpp new file mode 100644 index 0000000000..1c199ecc28 --- /dev/null +++ b/src/declarative/items/qsgtextedit.cpp @@ -0,0 +1,1232 @@ +// Commit: ec40dd2bb51868bca10dbd0c9116f3224ff2b29b +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgtextedit_p.h" +#include "qsgtextedit_p_p.h" +#include "qsgevents_p_p.h" +#include "qsgcanvas.h" + +#include <QtDeclarative/qdeclarativeinfo.h> +#include <QtGui/qapplication.h> +#include <QtGui/qgraphicssceneevent.h> +#include <QtGui/qpainter.h> +#include <QtGui/qtextobject.h> +#include <QtCore/qmath.h> + +#include <private/qdeclarativeglobal_p.h> +#include <private/qtextcontrol_p.h> +#include <private/qtextengine_p.h> +#include <private/qwidget_p.h> + +QT_BEGIN_NAMESPACE + +QWidgetPrivate *qt_widget_private(QWidget *widget); + +QSGTextEdit::QSGTextEdit(QSGItem *parent) +: QSGImplicitSizePaintedItem(*(new QSGTextEditPrivate), parent) +{ + Q_D(QSGTextEdit); + d->init(); +} + +QString QSGTextEdit::text() const +{ + Q_D(const QSGTextEdit); + +#ifndef QT_NO_TEXTHTMLPARSER + if (d->richText) + return d->document->toHtml(); + else +#endif + return d->document->toPlainText(); +} + +void QSGTextEdit::setText(const QString &text) +{ + Q_D(QSGTextEdit); + if (QSGTextEdit::text() == text) + return; + + 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); + } + q_textChanged(); +} + +QSGTextEdit::TextFormat QSGTextEdit::textFormat() const +{ + Q_D(const QSGTextEdit); + return d->format; +} + +void QSGTextEdit::setTextFormat(TextFormat format) +{ + Q_D(QSGTextEdit); + if (format == d->format) + return; + bool wasRich = d->richText; + d->richText = format == RichText || (format == AutoText && Qt::mightBeRichText(d->text)); + + if (wasRich && !d->richText) { + 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; + d->control->setAcceptRichText(d->format != PlainText); + emit textFormatChanged(d->format); +} + +QFont QSGTextEdit::font() const +{ + Q_D(const QSGTextEdit); + return d->sourceFont; +} + +void QSGTextEdit::setFont(const QFont &font) +{ + Q_D(QSGTextEdit); + if (d->sourceFont == font) + return; + + d->sourceFont = font; + QFont oldFont = d->font; + d->font = font; + if (d->font.pointSizeF() != -1) { + // 0.5pt resolution + qreal size = qRound(d->font.pointSizeF()*2.0); + d->font.setPointSizeF(size/2.0); + } + + if (oldFont != d->font) { + d->document->setDefaultFont(d->font); + if(d->cursor){ + d->cursor->setHeight(QFontMetrics(d->font).height()); + moveCursorDelegate(); + } + updateSize(); + update(); + } + emit fontChanged(d->sourceFont); +} + +QColor QSGTextEdit::color() const +{ + Q_D(const QSGTextEdit); + return d->color; +} + +void QSGTextEdit::setColor(const QColor &color) +{ + Q_D(QSGTextEdit); + if (d->color == color) + return; + + d->color = color; + QPalette pal = d->control->palette(); + pal.setColor(QPalette::Text, color); + d->control->setPalette(pal); + update(); + emit colorChanged(d->color); +} + +QColor QSGTextEdit::selectionColor() const +{ + Q_D(const QSGTextEdit); + return d->selectionColor; +} + +void QSGTextEdit::setSelectionColor(const QColor &color) +{ + Q_D(QSGTextEdit); + if (d->selectionColor == color) + return; + + d->selectionColor = color; + QPalette pal = d->control->palette(); + pal.setColor(QPalette::Highlight, color); + d->control->setPalette(pal); + update(); + emit selectionColorChanged(d->selectionColor); +} + +QColor QSGTextEdit::selectedTextColor() const +{ + Q_D(const QSGTextEdit); + return d->selectedTextColor; +} + +void QSGTextEdit::setSelectedTextColor(const QColor &color) +{ + Q_D(QSGTextEdit); + if (d->selectedTextColor == color) + return; + + d->selectedTextColor = color; + QPalette pal = d->control->palette(); + pal.setColor(QPalette::HighlightedText, color); + d->control->setPalette(pal); + update(); + emit selectedTextColorChanged(d->selectedTextColor); +} + +QSGTextEdit::HAlignment QSGTextEdit::hAlign() const +{ + Q_D(const QSGTextEdit); + return d->hAlign; +} + +void QSGTextEdit::setHAlign(HAlignment align) +{ + Q_D(QSGTextEdit); + bool forceAlign = d->hAlignImplicit && d->effectiveLayoutMirror; + d->hAlignImplicit = false; + if (d->setHAlign(align, forceAlign) && isComponentComplete()) { + d->updateDefaultTextOption(); + updateSize(); + } +} + +void QSGTextEdit::resetHAlign() +{ + Q_D(QSGTextEdit); + d->hAlignImplicit = true; + if (d->determineHorizontalAlignment() && isComponentComplete()) { + d->updateDefaultTextOption(); + updateSize(); + } +} + +QSGTextEdit::HAlignment QSGTextEdit::effectiveHAlign() const +{ + Q_D(const QSGTextEdit); + QSGTextEdit::HAlignment effectiveAlignment = d->hAlign; + if (!d->hAlignImplicit && d->effectiveLayoutMirror) { + switch (d->hAlign) { + case QSGTextEdit::AlignLeft: + effectiveAlignment = QSGTextEdit::AlignRight; + break; + case QSGTextEdit::AlignRight: + effectiveAlignment = QSGTextEdit::AlignLeft; + break; + default: + break; + } + } + return effectiveAlignment; +} + +bool QSGTextEditPrivate::setHAlign(QSGTextEdit::HAlignment alignment, bool forceAlign) +{ + Q_Q(QSGTextEdit); + if (hAlign != alignment || forceAlign) { + QSGTextEdit::HAlignment oldEffectiveHAlign = q->effectiveHAlign(); + hAlign = alignment; + emit q->horizontalAlignmentChanged(alignment); + if (oldEffectiveHAlign != q->effectiveHAlign()) + emit q->effectiveHorizontalAlignmentChanged(); + return true; + } + return false; +} + +bool QSGTextEditPrivate::determineHorizontalAlignment() +{ + Q_Q(QSGTextEdit); + if (hAlignImplicit && q->isComponentComplete()) { + bool alignToRight = text.isEmpty() ? QApplication::keyboardInputDirection() == Qt::RightToLeft : rightToLeftText; + return setHAlign(alignToRight ? QSGTextEdit::AlignRight : QSGTextEdit::AlignLeft); + } + return false; +} + +void QSGTextEditPrivate::mirrorChange() +{ + Q_Q(QSGTextEdit); + if (q->isComponentComplete()) { + if (!hAlignImplicit && (hAlign == QSGTextEdit::AlignRight || hAlign == QSGTextEdit::AlignLeft)) { + updateDefaultTextOption(); + q->updateSize(); + emit q->effectiveHorizontalAlignmentChanged(); + } + } +} + +QSGTextEdit::VAlignment QSGTextEdit::vAlign() const +{ + Q_D(const QSGTextEdit); + return d->vAlign; +} + +void QSGTextEdit::setVAlign(QSGTextEdit::VAlignment alignment) +{ + Q_D(QSGTextEdit); + if (alignment == d->vAlign) + return; + d->vAlign = alignment; + d->updateDefaultTextOption(); + updateSize(); + moveCursorDelegate(); + emit verticalAlignmentChanged(d->vAlign); +} + +QSGTextEdit::WrapMode QSGTextEdit::wrapMode() const +{ + Q_D(const QSGTextEdit); + return d->wrapMode; +} + +void QSGTextEdit::setWrapMode(WrapMode mode) +{ + Q_D(QSGTextEdit); + if (mode == d->wrapMode) + return; + d->wrapMode = mode; + d->updateDefaultTextOption(); + updateSize(); + emit wrapModeChanged(); +} + +int QSGTextEdit::lineCount() const +{ + Q_D(const QSGTextEdit); + return d->lineCount; +} + +qreal QSGTextEdit::paintedWidth() const +{ + Q_D(const QSGTextEdit); + return d->paintedSize.width(); +} + +qreal QSGTextEdit::paintedHeight() const +{ + Q_D(const QSGTextEdit); + return d->paintedSize.height(); +} + +QRectF QSGTextEdit::positionToRectangle(int pos) const +{ + Q_D(const QSGTextEdit); + QTextCursor c(d->document); + c.setPosition(pos); + return d->control->cursorRect(c); + +} + +int QSGTextEdit::positionAt(int x, int y) const +{ + Q_D(const QSGTextEdit); + int r = d->document->documentLayout()->hitTest(QPoint(x,y-d->yoff), Qt::FuzzyHit); + QTextCursor cursor = d->control->textCursor(); + if (r > cursor.position()) { + // The cursor position includes positions within the preedit text, but only positions in the + // same text block are offset so it is possible to get a position that is either part of the + // preedit or the next text block. + QTextLayout *layout = cursor.block().layout(); + const int preeditLength = layout + ? layout->preeditAreaText().length() + : 0; + if (preeditLength > 0 + && d->document->documentLayout()->blockBoundingRect(cursor.block()).contains(x,y-d->yoff)) { + r = r > cursor.position() + preeditLength + ? r - preeditLength + : cursor.position(); + } + } + return r; +} + +void QSGTextEdit::moveCursorSelection(int pos) +{ + //Note that this is the same as setCursorPosition but with the KeepAnchor flag set + Q_D(QSGTextEdit); + QTextCursor cursor = d->control->textCursor(); + if (cursor.position() == pos) + return; + cursor.setPosition(pos, QTextCursor::KeepAnchor); + d->control->setTextCursor(cursor); +} + +void QSGTextEdit::moveCursorSelection(int pos, SelectionMode mode) +{ + Q_D(QSGTextEdit); + QTextCursor cursor = d->control->textCursor(); + if (cursor.position() == pos) + return; + if (mode == SelectCharacters) { + cursor.setPosition(pos, QTextCursor::KeepAnchor); + } else if (cursor.anchor() < pos || (cursor.anchor() == pos && cursor.position() < pos)) { + if (cursor.anchor() > cursor.position()) { + cursor.setPosition(cursor.anchor(), QTextCursor::MoveAnchor); + cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor); + if (cursor.position() == cursor.anchor()) + cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor); + else + cursor.setPosition(cursor.position(), QTextCursor::MoveAnchor); + } else { + cursor.setPosition(cursor.anchor(), QTextCursor::MoveAnchor); + cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor); + } + + cursor.setPosition(pos, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor); + if (cursor.position() != pos) + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + } else if (cursor.anchor() > pos || (cursor.anchor() == pos && cursor.position() > pos)) { + if (cursor.anchor() < cursor.position()) { + cursor.setPosition(cursor.anchor(), QTextCursor::MoveAnchor); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::MoveAnchor); + } else { + cursor.setPosition(cursor.anchor(), QTextCursor::MoveAnchor); + cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + if (cursor.position() != cursor.anchor()) { + cursor.setPosition(cursor.anchor(), QTextCursor::MoveAnchor); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::MoveAnchor); + } + } + + cursor.setPosition(pos, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + if (cursor.position() != pos) { + cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor); + } + } + d->control->setTextCursor(cursor); +} + +bool QSGTextEdit::isCursorVisible() const +{ + Q_D(const QSGTextEdit); + return d->cursorVisible; +} + +void QSGTextEdit::setCursorVisible(bool on) +{ + Q_D(QSGTextEdit); + if (d->cursorVisible == on) + return; + d->cursorVisible = on; + QFocusEvent focusEvent(on ? QEvent::FocusIn : QEvent::FocusOut); + if (!on && !d->persistentSelection) + d->control->setCursorIsFocusIndicator(true); + d->control->processEvent(&focusEvent, QPointF(0, -d->yoff)); + emit cursorVisibleChanged(d->cursorVisible); +} + +int QSGTextEdit::cursorPosition() const +{ + Q_D(const QSGTextEdit); + return d->control->textCursor().position(); +} + +void QSGTextEdit::setCursorPosition(int pos) +{ + Q_D(QSGTextEdit); + if (pos < 0 || pos > d->text.length()) + return; + QTextCursor cursor = d->control->textCursor(); + if (cursor.position() == pos && cursor.anchor() == pos) + return; + cursor.setPosition(pos); + d->control->setTextCursor(cursor); +} + +QDeclarativeComponent* QSGTextEdit::cursorDelegate() const +{ + Q_D(const QSGTextEdit); + return d->cursorComponent; +} + +void QSGTextEdit::setCursorDelegate(QDeclarativeComponent* c) +{ + Q_D(QSGTextEdit); + if(d->cursorComponent){ + if(d->cursor){ + d->control->setCursorWidth(-1); + update(cursorRectangle()); + delete d->cursor; + d->cursor = 0; + } + } + d->cursorComponent = c; + if(c && c->isReady()){ + loadCursorDelegate(); + }else{ + if(c) + connect(c, SIGNAL(statusChanged()), + this, SLOT(loadCursorDelegate())); + } + + emit cursorDelegateChanged(); +} + +void QSGTextEdit::loadCursorDelegate() +{ + Q_D(QSGTextEdit); + if(d->cursorComponent->isLoading()) + return; + d->cursor = qobject_cast<QSGItem*>(d->cursorComponent->create(qmlContext(this))); + if(d->cursor){ + d->control->setCursorWidth(0); + update(cursorRectangle()); + QDeclarative_setParent_noEvent(d->cursor, this); + d->cursor->setParentItem(this); + d->cursor->setHeight(QFontMetrics(d->font).height()); + moveCursorDelegate(); + }else{ + qmlInfo(this) << "Error loading cursor delegate."; + } +} + +int QSGTextEdit::selectionStart() const +{ + Q_D(const QSGTextEdit); + return d->control->textCursor().selectionStart(); +} + +int QSGTextEdit::selectionEnd() const +{ + Q_D(const QSGTextEdit); + return d->control->textCursor().selectionEnd(); +} + +QString QSGTextEdit::selectedText() const +{ + Q_D(const QSGTextEdit); + return d->control->textCursor().selectedText(); +} + +bool QSGTextEdit::focusOnPress() const +{ + Q_D(const QSGTextEdit); + return d->focusOnPress; +} + +void QSGTextEdit::setFocusOnPress(bool on) +{ + Q_D(QSGTextEdit); + if (d->focusOnPress == on) + return; + d->focusOnPress = on; + emit activeFocusOnPressChanged(d->focusOnPress); +} + +bool QSGTextEdit::persistentSelection() const +{ + Q_D(const QSGTextEdit); + return d->persistentSelection; +} + +void QSGTextEdit::setPersistentSelection(bool on) +{ + Q_D(QSGTextEdit); + if (d->persistentSelection == on) + return; + d->persistentSelection = on; + emit persistentSelectionChanged(d->persistentSelection); +} + +qreal QSGTextEdit::textMargin() const +{ + Q_D(const QSGTextEdit); + return d->textMargin; +} + +void QSGTextEdit::setTextMargin(qreal margin) +{ + Q_D(QSGTextEdit); + if (d->textMargin == margin) + return; + d->textMargin = margin; + d->document->setDocumentMargin(d->textMargin); + emit textMarginChanged(d->textMargin); +} + +void QSGTextEdit::geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry) +{ + if (newGeometry.width() != oldGeometry.width()) + updateSize(); + QSGPaintedItem::geometryChanged(newGeometry, oldGeometry); +} + +void QSGTextEdit::componentComplete() +{ + Q_D(QSGTextEdit); + QSGPaintedItem::componentComplete(); + if (d->dirty) { + d->determineHorizontalAlignment(); + d->updateDefaultTextOption(); + updateSize(); + d->dirty = false; + } +} + +bool QSGTextEdit::selectByMouse() const +{ + Q_D(const QSGTextEdit); + return d->selectByMouse; +} + +void QSGTextEdit::setSelectByMouse(bool on) +{ + Q_D(QSGTextEdit); + if (d->selectByMouse != on) { + d->selectByMouse = on; + setKeepMouseGrab(on); + if (on) + setTextInteractionFlags(d->control->textInteractionFlags() | Qt::TextSelectableByMouse); + else + setTextInteractionFlags(d->control->textInteractionFlags() & ~Qt::TextSelectableByMouse); + emit selectByMouseChanged(on); + } +} + +QSGTextEdit::SelectionMode QSGTextEdit::mouseSelectionMode() const +{ + Q_D(const QSGTextEdit); + return d->mouseSelectionMode; +} + +void QSGTextEdit::setMouseSelectionMode(SelectionMode mode) +{ + Q_D(QSGTextEdit); + if (d->mouseSelectionMode != mode) { + d->mouseSelectionMode = mode; + d->control->setWordSelectionEnabled(mode == SelectWords); + emit mouseSelectionModeChanged(mode); + } +} + +void QSGTextEdit::setReadOnly(bool r) +{ + Q_D(QSGTextEdit); + if (r == isReadOnly()) + return; + + setFlag(QSGItem::ItemAcceptsInputMethod, !r); + Qt::TextInteractionFlags flags = Qt::LinksAccessibleByMouse; + if (d->selectByMouse) + flags = flags | Qt::TextSelectableByMouse; + if (!r) + flags = flags | Qt::TextSelectableByKeyboard | Qt::TextEditable; + d->control->setTextInteractionFlags(flags); + if (!r) + d->control->moveCursor(QTextCursor::End); + + emit readOnlyChanged(r); +} + +bool QSGTextEdit::isReadOnly() const +{ + Q_D(const QSGTextEdit); + return !(d->control->textInteractionFlags() & Qt::TextEditable); +} + +void QSGTextEdit::setTextInteractionFlags(Qt::TextInteractionFlags flags) +{ + Q_D(QSGTextEdit); + d->control->setTextInteractionFlags(flags); +} + +Qt::TextInteractionFlags QSGTextEdit::textInteractionFlags() const +{ + Q_D(const QSGTextEdit); + return d->control->textInteractionFlags(); +} + +QRect QSGTextEdit::cursorRectangle() const +{ + Q_D(const QSGTextEdit); + return d->control->cursorRect().toRect().translated(0,d->yoff); +} + +bool QSGTextEdit::event(QEvent *event) +{ + Q_D(QSGTextEdit); + if (event->type() == QEvent::ShortcutOverride) { + d->control->processEvent(event, QPointF(0, -d->yoff)); + return event->isAccepted(); + } + return QSGPaintedItem::event(event); +} + +/*! +\overload +Handles the given key \a event. +*/ +void QSGTextEdit::keyPressEvent(QKeyEvent *event) +{ + Q_D(QSGTextEdit); + d->control->processEvent(event, QPointF(0, -d->yoff)); + if (!event->isAccepted()) + QSGPaintedItem::keyPressEvent(event); +} + +/*! +\overload +Handles the given key \a event. +*/ +void QSGTextEdit::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QSGTextEdit); + d->control->processEvent(event, QPointF(0, -d->yoff)); + if (!event->isAccepted()) + QSGPaintedItem::keyReleaseEvent(event); +} + +void QSGTextEdit::deselect() +{ + Q_D(QSGTextEdit); + QTextCursor c = d->control->textCursor(); + c.clearSelection(); + d->control->setTextCursor(c); +} + +void QSGTextEdit::selectAll() +{ + Q_D(QSGTextEdit); + d->control->selectAll(); +} + +void QSGTextEdit::selectWord() +{ + Q_D(QSGTextEdit); + QTextCursor c = d->control->textCursor(); + c.select(QTextCursor::WordUnderCursor); + d->control->setTextCursor(c); +} + +void QSGTextEdit::select(int start, int end) +{ + Q_D(QSGTextEdit); + 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(); +} + +bool QSGTextEdit::isRightToLeft(int start, int end) +{ + Q_D(QSGTextEdit); + if (start > end) { + qmlInfo(this) << "isRightToLeft(start, end) called with the end property being smaller than the start."; + return false; + } else { + return d->text.mid(start, end - start).isRightToLeft(); + } +} + +#ifndef QT_NO_CLIPBOARD +void QSGTextEdit::cut() +{ + Q_D(QSGTextEdit); + d->control->cut(); +} + +void QSGTextEdit::copy() +{ + Q_D(QSGTextEdit); + d->control->copy(); +} + +void QSGTextEdit::paste() +{ + Q_D(QSGTextEdit); + d->control->paste(); +} +#endif // QT_NO_CLIPBOARD + +/*! +\overload +Handles the given mouse \a event. +*/ +void QSGTextEdit::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGTextEdit); + if (d->focusOnPress){ + 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; + } + } + } + d->control->processEvent(event, QPointF(0, -d->yoff)); + if (!event->isAccepted()) + QSGPaintedItem::mousePressEvent(event); +} + +/*! +\overload +Handles the given mouse \a event. +*/ +void QSGTextEdit::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGTextEdit); + d->control->processEvent(event, QPointF(0, -d->yoff)); + if (!d->showInputPanelOnFocus) { // input panel on click + if (d->focusOnPress && !isReadOnly() && boundingRect().contains(event->pos())) { + if (canvas() && canvas() == qApp->focusWidget()) { + qt_widget_private(canvas())->handleSoftwareInputPanel(event->button(), d->clickCausedFocus); + } + } + } + d->clickCausedFocus = false; + + if (!event->isAccepted()) + QSGPaintedItem::mouseReleaseEvent(event); +} + +/*! +\overload +Handles the given mouse \a event. +*/ +void QSGTextEdit::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGTextEdit); + d->control->processEvent(event, QPointF(0, -d->yoff)); + if (!event->isAccepted()) + QSGPaintedItem::mouseDoubleClickEvent(event); +} + +/*! +\overload +Handles the given mouse \a event. +*/ +void QSGTextEdit::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGTextEdit); + d->control->processEvent(event, QPointF(0, -d->yoff)); + if (!event->isAccepted()) + QSGPaintedItem::mouseMoveEvent(event); +} + +/*! +\overload +Handles the given input method \a event. +*/ +void QSGTextEdit::inputMethodEvent(QInputMethodEvent *event) +{ + Q_D(QSGTextEdit); + const bool wasComposing = isInputMethodComposing(); + d->control->processEvent(event, QPointF(0, -d->yoff)); + if (wasComposing != isInputMethodComposing()) + emit inputMethodComposingChanged(); +} + +void QSGTextEdit::itemChange(ItemChange change, const ItemChangeData &value) +{ + Q_D(QSGTextEdit); + if (change == ItemActiveFocusHasChanged) { + setCursorVisible(value.boolValue && d->canvas && d->canvas->hasFocus()); + } + QSGItem::itemChange(change, value); +} + +/*! +\overload +Returns the value of the given \a property. +*/ +QVariant QSGTextEdit::inputMethodQuery(Qt::InputMethodQuery property) const +{ + Q_D(const QSGTextEdit); + return d->control->inputMethodQuery(property); +} + +/*! +Draws the contents of the text edit using the given \a painter within +the given \a bounds. +*/ +void QSGTextEdit::paint(QPainter *painter) +{ + // XXX todo + QRect bounds(0, 0, width(), height()); + Q_D(QSGTextEdit); + + painter->setRenderHint(QPainter::TextAntialiasing, true); + painter->translate(0,d->yoff); + + d->control->drawContents(painter, bounds.translated(0,-d->yoff)); + + painter->translate(0,-d->yoff); +} + +void QSGTextEdit::updateImgCache(const QRectF &rf) +{ + Q_D(const QSGTextEdit); + 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); + } + } + update(r); +} + +bool QSGTextEdit::canPaste() const +{ + Q_D(const QSGTextEdit); + return d->canPaste; +} + +bool QSGTextEdit::isInputMethodComposing() const +{ + Q_D(const QSGTextEdit); + if (QTextLayout *layout = d->control->textCursor().block().layout()) + return layout->preeditAreaText().length() > 0; + return false; +} + +void QSGTextEditPrivate::init() +{ + Q_Q(QSGTextEdit); + + q->setSmooth(smooth); + q->setAcceptedMouseButtons(Qt::LeftButton); + q->setFlag(QSGItem::ItemAcceptsInputMethod); + + control = new QTextControl(q); + control->setIgnoreUnusedNavigationEvents(true); + control->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::TextSelectableByKeyboard | Qt::TextEditable); + control->setDragEnabled(false); + + // 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())); + QObject::connect(control, SIGNAL(selectionChanged()), q, SIGNAL(selectionChanged())); + 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(microFocusChanged()), q, SLOT(moveCursorDelegate())); + QObject::connect(control, SIGNAL(linkActivated(QString)), q, SIGNAL(linkActivated(QString))); +#ifndef QT_NO_CLIPBOARD + QObject::connect(q, SIGNAL(readOnlyChanged(bool)), q, SLOT(q_canPasteChanged())); + QObject::connect(QApplication::clipboard(), SIGNAL(dataChanged()), q, SLOT(q_canPasteChanged())); + canPaste = control->canPaste(); +#endif + + document = control->document(); + document->setDefaultFont(font); + document->setDocumentMargin(textMargin); + document->setUndoRedoEnabled(false); // flush undo buffer. + document->setUndoRedoEnabled(true); + updateDefaultTextOption(); +} + +void QSGTextEdit::q_textChanged() +{ + Q_D(QSGTextEdit); + d->text = text(); + d->rightToLeftText = d->document->begin().layout()->engine()->isRightToLeft(); + d->determineHorizontalAlignment(); + d->updateDefaultTextOption(); + updateSize(); + updateTotalLines(); + emit textChanged(d->text); +} + +void QSGTextEdit::moveCursorDelegate() +{ + Q_D(QSGTextEdit); + updateMicroFocus(); + emit cursorRectangleChanged(); + if(!d->cursor) + return; + QRectF cursorRect = cursorRectangle(); + d->cursor->setX(cursorRect.x()); + d->cursor->setY(cursorRect.y()); +} + +void QSGTextEditPrivate::updateSelection() +{ + Q_Q(QSGTextEdit); + QTextCursor cursor = control->textCursor(); + bool startChange = (lastSelectionStart != cursor.selectionStart()); + bool endChange = (lastSelectionEnd != cursor.selectionEnd()); + cursor.beginEditBlock(); + cursor.setPosition(lastSelectionStart, QTextCursor::MoveAnchor); + cursor.setPosition(lastSelectionEnd, QTextCursor::KeepAnchor); + cursor.endEditBlock(); + control->setTextCursor(cursor); + if(startChange) + q->selectionStartChanged(); + if(endChange) + q->selectionEndChanged(); +} + +void QSGTextEdit::updateSelectionMarkers() +{ + Q_D(QSGTextEdit); + if(d->lastSelectionStart != d->control->textCursor().selectionStart()){ + d->lastSelectionStart = d->control->textCursor().selectionStart(); + emit selectionStartChanged(); + } + if(d->lastSelectionEnd != d->control->textCursor().selectionEnd()){ + d->lastSelectionEnd = d->control->textCursor().selectionEnd(); + emit selectionEndChanged(); + } +} + +QRectF QSGTextEdit::boundingRect() const +{ + Q_D(const QSGTextEdit); + QRectF r = QSGPaintedItem::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); +} + +qreal QSGTextEditPrivate::getImplicitWidth() const +{ + Q_Q(const QSGTextEdit); + if (!requireImplicitWidth) { + // We don't calculate implicitWidth unless it is required. + // We need to force a size update now to ensure implicitWidth is calculated + const_cast<QSGTextEditPrivate*>(this)->requireImplicitWidth = true; + const_cast<QSGTextEdit*>(q)->updateSize(); + } + return implicitWidth; +} + +//### 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 QSGTextEdit::updateSize() +{ + Q_D(QSGTextEdit); + 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(); + } + if (d->document->textWidth() != width()) + d->document->setTextWidth(width()); + } else { + d->document->setTextWidth(-1); + } + QFontMetrics fm = QFontMetrics(d->font); + int dy = height(); + dy -= (int)d->document->size().height(); + + int nyoff; + if (heightValid()) { + if (d->vAlign == AlignBottom) + nyoff = dy; + else if (d->vAlign == AlignVCenter) + nyoff = dy/2; + else + nyoff = 0; + } else { + nyoff = 0; + } + if (nyoff != d->yoff) + 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() && d->document->textWidth() != newWidth) + d->document->setTextWidth(newWidth); // ### Text does not align if width is not set (QTextDoc bug) + // ### Setting the implicitWidth triggers another updateSize(), and unless there are bindings nothing has changed. + if (!widthValid()) + setImplicitWidth(newWidth); + else if (d->requireImplicitWidth) + setImplicitWidth(naturalWidth); + qreal newHeight = d->document->isEmpty() ? fm.height() : (int)d->document->size().height(); + setImplicitHeight(newHeight); + + d->paintedSize = QSize(newWidth, newHeight); + setContentsSize(d->paintedSize); + emit paintedSizeChanged(); + } else { + d->dirty = true; + } + update(); +} + +void QSGTextEdit::updateTotalLines() +{ + Q_D(QSGTextEdit); + + int subLines = 0; + + for (QTextBlock it = d->document->begin(); it != d->document->end(); it = it.next()) { + QTextLayout *layout = it.layout(); + if (!layout) + continue; + subLines += layout->lineCount()-1; + } + + int newTotalLines = d->document->lineCount() + subLines; + if (d->lineCount != newTotalLines) { + d->lineCount = newTotalLines; + emit lineCountChanged(); + } +} + +void QSGTextEditPrivate::updateDefaultTextOption() +{ + Q_Q(QSGTextEdit); + QTextOption opt = document->defaultTextOption(); + int oldAlignment = opt.alignment(); + + QSGTextEdit::HAlignment horizontalAlignment = q->effectiveHAlign(); + if (rightToLeftText) { + if (horizontalAlignment == QSGTextEdit::AlignLeft) + horizontalAlignment = QSGTextEdit::AlignRight; + else if (horizontalAlignment == QSGTextEdit::AlignRight) + horizontalAlignment = QSGTextEdit::AlignLeft; + } + opt.setAlignment((Qt::Alignment)(int)(horizontalAlignment | vAlign)); + + QTextOption::WrapMode oldWrapMode = opt.wrapMode(); + opt.setWrapMode(QTextOption::WrapMode(wrapMode)); + + if (oldWrapMode == opt.wrapMode() && oldAlignment == opt.alignment()) + return; + document->setDefaultTextOption(opt); +} + + +void QSGTextEdit::openSoftwareInputPanel() +{ + if (qApp) { + if (canvas() && canvas() == qApp->focusWidget()) { + QEvent event(QEvent::RequestSoftwareInputPanel); + QApplication::sendEvent(canvas(), &event); + } + } +} + +void QSGTextEdit::closeSoftwareInputPanel() +{ + if (qApp) { + if (canvas() && canvas() == qApp->focusWidget()) { + QEvent event(QEvent::CloseSoftwareInputPanel); + QApplication::sendEvent(canvas(), &event); + } + } +} + +void QSGTextEdit::focusInEvent(QFocusEvent *event) +{ + Q_D(const QSGTextEdit); + if (d->showInputPanelOnFocus) { + if (d->focusOnPress && !isReadOnly()) { + openSoftwareInputPanel(); + } + } + QSGPaintedItem::focusInEvent(event); +} + +void QSGTextEdit::q_canPasteChanged() +{ + Q_D(QSGTextEdit); + bool old = d->canPaste; + d->canPaste = d->control->canPaste(); + if(old!=d->canPaste) + emit canPasteChanged(); +} + +QT_END_NAMESPACE |