blob: 8db2a38b1199a4fbb72b8ff51b20b9417ebe87ff [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qquicktextcontrol_p.h"
#include "qquicktextcontrol_p_p.h"
#ifndef QT_NO_TEXTCONTROL
#include <qcoreapplication.h>
#include <qfont.h>
#include <qfontmetrics.h>
#include <qevent.h>
#include <qdebug.h>
#include <qclipboard.h>
#include <qtimer.h>
#include <qinputmethod.h>
#include "private/qtextdocumentlayout_p.h"
#include "private/qabstracttextdocumentlayout_p.h"
#include "qtextdocument.h"
#include "private/qtextdocument_p.h"
#include "qtextlist.h"
#include "qtextdocumentwriter.h"
#include "private/qtextcursor_p.h"
#include <QtCore/qloggingcategory.h>
#include <qtextformat.h>
#include <qtransform.h>
#include <qdatetime.h>
#include <qbuffer.h>
#include <qguiapplication.h>
#include <limits.h>
#include <qtexttable.h>
#include <qvariant.h>
#include <qurl.h>
#include <qstylehints.h>
#include <qmetaobject.h>
#include <private/qqmlglobal_p.h>
// ### these should come from QStyleHints
const int textCursorWidth = 1;
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(DBG_HOVER_TRACE)
// could go into QTextCursor...
static QTextLine currentTextLine(const QTextCursor &cursor)
{
const QTextBlock block = cursor.block();
if (!block.isValid())
return QTextLine();
const QTextLayout *layout = block.layout();
if (!layout)
return QTextLine();
const int relativePos = cursor.position() - block.position();
return layout->lineForTextPosition(relativePos);
}
QQuickTextControlPrivate::QQuickTextControlPrivate()
: doc(nullptr),
#if QT_CONFIG(im)
preeditCursor(0),
#endif
interactionFlags(Qt::TextEditorInteraction),
cursorOn(false),
cursorIsFocusIndicator(false),
mousePressed(false),
lastSelectionState(false),
ignoreAutomaticScrollbarAdjustement(false),
overwriteMode(false),
acceptRichText(true),
cursorVisible(false),
cursorBlinkingEnabled(false),
hasFocus(false),
hadSelectionOnMousePress(false),
wordSelectionEnabled(false),
hasImState(false),
cursorRectangleChanged(false),
hoveredMarker(false),
lastSelectionStart(-1),
lastSelectionEnd(-1)
{}
bool QQuickTextControlPrivate::cursorMoveKeyEvent(QKeyEvent *e)
{
#if !QT_CONFIG(shortcut)
Q_UNUSED(e);
#endif
Q_Q(QQuickTextControl);
if (cursor.isNull())
return false;
const QTextCursor oldSelection = cursor;
const int oldCursorPos = cursor.position();
QTextCursor::MoveMode mode = QTextCursor::MoveAnchor;
QTextCursor::MoveOperation op = QTextCursor::NoMove;
if (false) {
}
#if QT_CONFIG(shortcut)
if (e == QKeySequence::MoveToNextChar) {
op = QTextCursor::Right;
}
else if (e == QKeySequence::MoveToPreviousChar) {
op = QTextCursor::Left;
}
else if (e == QKeySequence::SelectNextChar) {
op = QTextCursor::Right;
mode = QTextCursor::KeepAnchor;
}
else if (e == QKeySequence::SelectPreviousChar) {
op = QTextCursor::Left;
mode = QTextCursor::KeepAnchor;
}
else if (e == QKeySequence::SelectNextWord) {
op = QTextCursor::WordRight;
mode = QTextCursor::KeepAnchor;
}
else if (e == QKeySequence::SelectPreviousWord) {
op = QTextCursor::WordLeft;
mode = QTextCursor::KeepAnchor;
}
else if (e == QKeySequence::SelectStartOfLine) {
op = QTextCursor::StartOfLine;
mode = QTextCursor::KeepAnchor;
}
else if (e == QKeySequence::SelectEndOfLine) {
op = QTextCursor::EndOfLine;
mode = QTextCursor::KeepAnchor;
}
else if (e == QKeySequence::SelectStartOfBlock) {
op = QTextCursor::StartOfBlock;
mode = QTextCursor::KeepAnchor;
}
else if (e == QKeySequence::SelectEndOfBlock) {
op = QTextCursor::EndOfBlock;
mode = QTextCursor::KeepAnchor;
}
else if (e == QKeySequence::SelectStartOfDocument) {
op = QTextCursor::Start;
mode = QTextCursor::KeepAnchor;
}
else if (e == QKeySequence::SelectEndOfDocument) {
op = QTextCursor::End;
mode = QTextCursor::KeepAnchor;
}
else if (e == QKeySequence::SelectPreviousLine) {
op = QTextCursor::Up;
mode = QTextCursor::KeepAnchor;
}
else if (e == QKeySequence::SelectNextLine) {
op = QTextCursor::Down;
mode = QTextCursor::KeepAnchor;
{
QTextBlock block = cursor.block();
QTextLine line = currentTextLine(cursor);
if (!block.next().isValid()
&& line.isValid()
&& line.lineNumber() == block.layout()->lineCount() - 1)
op = QTextCursor::End;
}
}
else if (e == QKeySequence::MoveToNextWord) {
op = QTextCursor::WordRight;
}
else if (e == QKeySequence::MoveToPreviousWord) {
op = QTextCursor::WordLeft;
}
else if (e == QKeySequence::MoveToEndOfBlock) {
op = QTextCursor::EndOfBlock;
}
else if (e == QKeySequence::MoveToStartOfBlock) {
op = QTextCursor::StartOfBlock;
}
else if (e == QKeySequence::MoveToNextLine) {
op = QTextCursor::Down;
}
else if (e == QKeySequence::MoveToPreviousLine) {
op = QTextCursor::Up;
}
else if (e == QKeySequence::MoveToStartOfLine) {
op = QTextCursor::StartOfLine;
}
else if (e == QKeySequence::MoveToEndOfLine) {
op = QTextCursor::EndOfLine;
}
else if (e == QKeySequence::MoveToStartOfDocument) {
op = QTextCursor::Start;
}
else if (e == QKeySequence::MoveToEndOfDocument) {
op = QTextCursor::End;
}
#endif // shortcut
else {
return false;
}
// Except for pageup and pagedown, OS X has very different behavior, we don't do it all, but
// here's the breakdown:
// Shift still works as an anchor, but only one of the other keys can be down Ctrl (Command),
// Alt (Option), or Meta (Control).
// Command/Control + Left/Right -- Move to left or right of the line
// + Up/Down -- Move to top bottom of the file. (Control doesn't move the cursor)
// Option + Left/Right -- Move one word Left/right.
// + Up/Down -- Begin/End of Paragraph.
// Home/End Top/Bottom of file. (usually don't move the cursor, but will select)
bool visualNavigation = cursor.visualNavigation();
cursor.setVisualNavigation(true);
const bool moved = cursor.movePosition(op, mode);
cursor.setVisualNavigation(visualNavigation);
bool isNavigationEvent
= e->key() == Qt::Key_Up
|| e->key() == Qt::Key_Down
|| e->key() == Qt::Key_Left
|| e->key() == Qt::Key_Right;
if (moved) {
if (cursor.position() != oldCursorPos)
emit q->cursorPositionChanged();
q->updateCursorRectangle(true);
} else if (isNavigationEvent && oldSelection.anchor() == cursor.anchor()) {
return false;
}
selectionChanged(/*forceEmitSelectionChanged =*/(mode == QTextCursor::KeepAnchor));
repaintOldAndNewSelection(oldSelection);
return true;
}
void QQuickTextControlPrivate::updateCurrentCharFormat()
{
Q_Q(QQuickTextControl);
QTextCharFormat fmt = cursor.charFormat();
if (fmt == lastCharFormat)
return;
lastCharFormat = fmt;
emit q->currentCharFormatChanged(fmt);
cursorRectangleChanged = true;
}
void QQuickTextControlPrivate::setContent(Qt::TextFormat format, const QString &text)
{
Q_Q(QQuickTextControl);
#if QT_CONFIG(im)
cancelPreedit();
#endif
// for use when called from setPlainText. we may want to re-use the currently
// set char format then.
const QTextCharFormat charFormatForInsertion = cursor.charFormat();
bool previousUndoRedoState = doc->isUndoRedoEnabled();
doc->setUndoRedoEnabled(false);
const int oldCursorPos = cursor.position();
// avoid multiple textChanged() signals being emitted
qmlobject_disconnect(doc, QTextDocument, SIGNAL(contentsChanged()), q, QQuickTextControl, SIGNAL(textChanged()));
if (!text.isEmpty()) {
// clear 'our' cursor for insertion to prevent
// the emission of the cursorPositionChanged() signal.
// instead we emit it only once at the end instead of
// at the end of the document after loading and when
// positioning the cursor again to the start of the
// document.
cursor = QTextCursor();
if (format == Qt::PlainText) {
QTextCursor formatCursor(doc);
// put the setPlainText and the setCharFormat into one edit block,
// so that the syntax highlight triggers only /once/ for the entire
// document, not twice.
formatCursor.beginEditBlock();
doc->setPlainText(text);
doc->setUndoRedoEnabled(false);
formatCursor.select(QTextCursor::Document);
formatCursor.setCharFormat(charFormatForInsertion);
formatCursor.endEditBlock();
} else if (format == Qt::MarkdownText) {
doc->setBaseUrl(doc->baseUrl().adjusted(QUrl::RemoveFilename));
doc->setMarkdown(text);
} else {
#if QT_CONFIG(texthtmlparser)
doc->setHtml(text);
#else
doc->setPlainText(text);
#endif
doc->setUndoRedoEnabled(false);
}
cursor = QTextCursor(doc);
} else {
doc->clear();
}
cursor.setCharFormat(charFormatForInsertion);
qmlobject_connect(doc, QTextDocument, SIGNAL(contentsChanged()), q, QQuickTextControl, SIGNAL(textChanged()));
emit q->textChanged();
doc->setUndoRedoEnabled(previousUndoRedoState);
_q_updateCurrentCharFormatAndSelection();
doc->setModified(false);
q->updateCursorRectangle(true);
if (cursor.position() != oldCursorPos)
emit q->cursorPositionChanged();
}
void QQuickTextControlPrivate::setCursorPosition(const QPointF &pos)
{
Q_Q(QQuickTextControl);
const int cursorPos = q->hitTest(pos, Qt::FuzzyHit);
if (cursorPos == -1)
return;
cursor.setPosition(cursorPos);
}
void QQuickTextControlPrivate::setCursorPosition(int pos, QTextCursor::MoveMode mode)
{
cursor.setPosition(pos, mode);
if (mode != QTextCursor::KeepAnchor) {
selectedWordOnDoubleClick = QTextCursor();
selectedBlockOnTripleClick = QTextCursor();
}
}
void QQuickTextControlPrivate::repaintCursor()
{
Q_Q(QQuickTextControl);
emit q->updateCursorRequest();
}
void QQuickTextControlPrivate::repaintOldAndNewSelection(const QTextCursor &oldSelection)
{
Q_Q(QQuickTextControl);
if (cursor.hasSelection()
&& oldSelection.hasSelection()
&& cursor.currentFrame() == oldSelection.currentFrame()
&& !cursor.hasComplexSelection()
&& !oldSelection.hasComplexSelection()
&& cursor.anchor() == oldSelection.anchor()
) {
QTextCursor differenceSelection(doc);
differenceSelection.setPosition(oldSelection.position());
differenceSelection.setPosition(cursor.position(), QTextCursor::KeepAnchor);
emit q->updateRequest();
} else {
if (!oldSelection.hasSelection() && !cursor.hasSelection()) {
if (!oldSelection.isNull())
emit q->updateCursorRequest();
emit q->updateCursorRequest();
} else {
if (!oldSelection.isNull())
emit q->updateRequest();
emit q->updateRequest();
}
}
}
void QQuickTextControlPrivate::selectionChanged(bool forceEmitSelectionChanged /*=false*/)
{
Q_Q(QQuickTextControl);
if (forceEmitSelectionChanged) {
#if QT_CONFIG(im)
if (hasFocus)
qGuiApp->inputMethod()->update(Qt::ImCurrentSelection);
#endif
emit q->selectionChanged();
}
bool current = cursor.hasSelection();
int selectionStart = cursor.selectionStart();
int selectionEnd = cursor.selectionEnd();
if (current == lastSelectionState && (!current || (selectionStart == lastSelectionStart && selectionEnd == lastSelectionEnd)))
return;
if (lastSelectionState != current) {
lastSelectionState = current;
emit q->copyAvailable(current);
}
lastSelectionStart = selectionStart;
lastSelectionEnd = selectionEnd;
if (!forceEmitSelectionChanged) {
#if QT_CONFIG(im)
if (hasFocus)
qGuiApp->inputMethod()->update(Qt::ImCurrentSelection);
#endif
emit q->selectionChanged();
}
q->updateCursorRectangle(true);
}
void QQuickTextControlPrivate::_q_updateCurrentCharFormatAndSelection()
{
updateCurrentCharFormat();
selectionChanged();
}
#if QT_CONFIG(clipboard)
void QQuickTextControlPrivate::setClipboardSelection()
{
QClipboard *clipboard = QGuiApplication::clipboard();
if (!cursor.hasSelection() || !clipboard->supportsSelection())
return;
Q_Q(QQuickTextControl);
QMimeData *data = q->createMimeDataFromSelection();
clipboard->setMimeData(data, QClipboard::Selection);
}
#endif
void QQuickTextControlPrivate::_q_updateCursorPosChanged(const QTextCursor &someCursor)
{
Q_Q(QQuickTextControl);
if (someCursor.isCopyOf(cursor)) {
emit q->cursorPositionChanged();
q->updateCursorRectangle(true);
}
}
void QQuickTextControlPrivate::setBlinkingCursorEnabled(bool enable)
{
if (cursorBlinkingEnabled == enable)
return;
cursorBlinkingEnabled = enable;
updateCursorFlashTime();
if (enable)
connect(qApp->styleHints(), &QStyleHints::cursorFlashTimeChanged, this, &QQuickTextControlPrivate::updateCursorFlashTime);
else
disconnect(qApp->styleHints(), &QStyleHints::cursorFlashTimeChanged, this, &QQuickTextControlPrivate::updateCursorFlashTime);
}
void QQuickTextControlPrivate::updateCursorFlashTime()
{
// Note: cursorOn represents the current blinking state controlled by a timer, and
// should not be confused with cursorVisible or cursorBlinkingEnabled. However, we
// interpretate a cursorFlashTime of 0 to mean "always on, never blink".
cursorOn = true;
int flashTime = QGuiApplication::styleHints()->cursorFlashTime();
if (cursorBlinkingEnabled && flashTime >= 2)
cursorBlinkTimer.start(flashTime / 2, q_func());
else
cursorBlinkTimer.stop();
repaintCursor();
}
void QQuickTextControlPrivate::extendWordwiseSelection(int suggestedNewPosition, qreal mouseXPosition)
{
Q_Q(QQuickTextControl);
// if inside the initial selected word keep that
if (suggestedNewPosition >= selectedWordOnDoubleClick.selectionStart()
&& suggestedNewPosition <= selectedWordOnDoubleClick.selectionEnd()) {
q->setTextCursor(selectedWordOnDoubleClick);
return;
}
QTextCursor curs = selectedWordOnDoubleClick;
curs.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor);
if (!curs.movePosition(QTextCursor::StartOfWord))
return;
const int wordStartPos = curs.position();
const int blockPos = curs.block().position();
const QPointF blockCoordinates = q->blockBoundingRect(curs.block()).topLeft();
QTextLine line = currentTextLine(curs);
if (!line.isValid())
return;
const qreal wordStartX = line.cursorToX(curs.position() - blockPos) + blockCoordinates.x();
if (!curs.movePosition(QTextCursor::EndOfWord))
return;
const int wordEndPos = curs.position();
const QTextLine otherLine = currentTextLine(curs);
if (otherLine.textStart() != line.textStart()
|| wordEndPos == wordStartPos)
return;
const qreal wordEndX = line.cursorToX(curs.position() - blockPos) + blockCoordinates.x();
if (!wordSelectionEnabled && (mouseXPosition < wordStartX || mouseXPosition > wordEndX))
return;
if (suggestedNewPosition < selectedWordOnDoubleClick.position()) {
cursor.setPosition(selectedWordOnDoubleClick.selectionEnd());
setCursorPosition(wordStartPos, QTextCursor::KeepAnchor);
} else {
cursor.setPosition(selectedWordOnDoubleClick.selectionStart());
setCursorPosition(wordEndPos, QTextCursor::KeepAnchor);
}
if (interactionFlags & Qt::TextSelectableByMouse) {
#if QT_CONFIG(clipboard)
setClipboardSelection();
#endif
selectionChanged(true);
}
}
void QQuickTextControlPrivate::extendBlockwiseSelection(int suggestedNewPosition)
{
Q_Q(QQuickTextControl);
// if inside the initial selected line keep that
if (suggestedNewPosition >= selectedBlockOnTripleClick.selectionStart()
&& suggestedNewPosition <= selectedBlockOnTripleClick.selectionEnd()) {
q->setTextCursor(selectedBlockOnTripleClick);
return;
}
if (suggestedNewPosition < selectedBlockOnTripleClick.position()) {
cursor.setPosition(selectedBlockOnTripleClick.selectionEnd());
cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor);
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
} else {
cursor.setPosition(selectedBlockOnTripleClick.selectionStart());
cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor);
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
}
if (interactionFlags & Qt::TextSelectableByMouse) {
#if QT_CONFIG(clipboard)
setClipboardSelection();
#endif
selectionChanged(true);
}
}
void QQuickTextControl::undo()
{
Q_D(QQuickTextControl);
d->repaintSelection();
const int oldCursorPos = d->cursor.position();
d->doc->undo(&d->cursor);
if (d->cursor.position() != oldCursorPos)
emit cursorPositionChanged();
updateCursorRectangle(true);
}
void QQuickTextControl::redo()
{
Q_D(QQuickTextControl);
d->repaintSelection();
const int oldCursorPos = d->cursor.position();
d->doc->redo(&d->cursor);
if (d->cursor.position() != oldCursorPos)
emit cursorPositionChanged();
updateCursorRectangle(true);
}
void QQuickTextControl::clear()
{
Q_D(QQuickTextControl);
d->cursor.select(QTextCursor::Document);
d->cursor.removeSelectedText();
}
QQuickTextControl::QQuickTextControl(QTextDocument *doc, QObject *parent)
: QInputControl(TextEdit, *new QQuickTextControlPrivate, parent)
{
Q_D(QQuickTextControl);
Q_ASSERT(doc);
QAbstractTextDocumentLayout *layout = doc->documentLayout();
qmlobject_connect(layout, QAbstractTextDocumentLayout, SIGNAL(update(QRectF)), this, QQuickTextControl, SIGNAL(updateRequest()));
qmlobject_connect(layout, QAbstractTextDocumentLayout, SIGNAL(updateBlock(QTextBlock)), this, QQuickTextControl, SIGNAL(updateRequest()));
qmlobject_connect(doc, QTextDocument, SIGNAL(contentsChanged()), this, QQuickTextControl, SIGNAL(textChanged()));
qmlobject_connect(doc, QTextDocument, SIGNAL(contentsChanged()), this, QQuickTextControl, SLOT(_q_updateCurrentCharFormatAndSelection()));
qmlobject_connect(doc, QTextDocument, SIGNAL(cursorPositionChanged(QTextCursor)), this, QQuickTextControl, SLOT(_q_updateCursorPosChanged(QTextCursor)));
connect(doc, &QTextDocument::contentsChange, this, &QQuickTextControl::contentsChange);
layout->setProperty("cursorWidth", textCursorWidth);
d->doc = doc;
d->cursor = QTextCursor(doc);
d->lastCharFormat = d->cursor.charFormat();
doc->setPageSize(QSizeF(0, 0));
doc->setModified(false);
doc->setUndoRedoEnabled(true);
}
QQuickTextControl::~QQuickTextControl()
{
}
QTextDocument *QQuickTextControl::document() const
{
Q_D(const QQuickTextControl);
return d->doc;
}
void QQuickTextControl::updateCursorRectangle(bool force)
{
Q_D(QQuickTextControl);
const bool update = d->cursorRectangleChanged || force;
d->cursorRectangleChanged = false;
if (update)
emit cursorRectangleChanged();
}
void QQuickTextControl::setTextCursor(const QTextCursor &cursor)
{
Q_D(QQuickTextControl);
#if QT_CONFIG(im)
d->commitPreedit();
#endif
d->cursorIsFocusIndicator = false;
const bool posChanged = cursor.position() != d->cursor.position();
const QTextCursor oldSelection = d->cursor;
d->cursor = cursor;
d->cursorOn = d->hasFocus && (d->interactionFlags & Qt::TextEditable);
d->_q_updateCurrentCharFormatAndSelection();
updateCursorRectangle(true);
d->repaintOldAndNewSelection(oldSelection);
if (posChanged)
emit cursorPositionChanged();
}
QTextCursor QQuickTextControl::textCursor() const
{
Q_D(const QQuickTextControl);
return d->cursor;
}
#if QT_CONFIG(clipboard)
void QQuickTextControl::cut()
{
Q_D(QQuickTextControl);
if (!(d->interactionFlags & Qt::TextEditable) || !d->cursor.hasSelection())
return;
copy();
d->cursor.removeSelectedText();
}
void QQuickTextControl::copy()
{
Q_D(QQuickTextControl);
if (!d->cursor.hasSelection())
return;
QMimeData *data = createMimeDataFromSelection();
QGuiApplication::clipboard()->setMimeData(data);
}
void QQuickTextControl::paste(QClipboard::Mode mode)
{
const QMimeData *md = QGuiApplication::clipboard()->mimeData(mode);
if (md)
insertFromMimeData(md);
}
#endif
void QQuickTextControl::selectAll()
{
Q_D(QQuickTextControl);
const int selectionLength = qAbs(d->cursor.position() - d->cursor.anchor());
d->cursor.select(QTextCursor::Document);
d->selectionChanged(selectionLength != qAbs(d->cursor.position() - d->cursor.anchor()));
d->cursorIsFocusIndicator = false;
emit updateRequest();
}
void QQuickTextControl::processEvent(QEvent *e, const QPointF &coordinateOffset)
{
QTransform t;
t.translate(coordinateOffset.x(), coordinateOffset.y());
processEvent(e, t);
}
void QQuickTextControl::processEvent(QEvent *e, const QTransform &transform)
{
Q_D(QQuickTextControl);
if (d->interactionFlags == Qt::NoTextInteraction) {
e->ignore();
return;
}
switch (e->type()) {
case QEvent::KeyPress:
d->keyPressEvent(static_cast<QKeyEvent *>(e));
break;
case QEvent::KeyRelease:
d->keyReleaseEvent(static_cast<QKeyEvent *>(e));
break;
case QEvent::MouseButtonPress: {
QMouseEvent *ev = static_cast<QMouseEvent *>(e);
d->mousePressEvent(ev, transform.map(ev->localPos()));
break; }
case QEvent::MouseMove: {
QMouseEvent *ev = static_cast<QMouseEvent *>(e);
d->mouseMoveEvent(ev, transform.map(ev->localPos()));
break; }
case QEvent::MouseButtonRelease: {
QMouseEvent *ev = static_cast<QMouseEvent *>(e);
d->mouseReleaseEvent(ev, transform.map(ev->localPos()));
break; }
case QEvent::MouseButtonDblClick: {
QMouseEvent *ev = static_cast<QMouseEvent *>(e);
d->mouseDoubleClickEvent(ev, transform.map(ev->localPos()));
break; }
case QEvent::HoverEnter:
case QEvent::HoverMove:
case QEvent::HoverLeave: {
QHoverEvent *ev = static_cast<QHoverEvent *>(e);
d->hoverEvent(ev, transform.map(ev->posF()));
break; }
#if QT_CONFIG(im)
case QEvent::InputMethod:
d->inputMethodEvent(static_cast<QInputMethodEvent *>(e));
break;
#endif
case QEvent::FocusIn:
case QEvent::FocusOut:
d->focusEvent(static_cast<QFocusEvent *>(e));
break;
case QEvent::ShortcutOverride:
if (d->interactionFlags & Qt::TextEditable) {
QKeyEvent* ke = static_cast<QKeyEvent *>(e);
if (isCommonTextEditShortcut(ke))
ke->accept();
}
break;
default:
break;
}
}
bool QQuickTextControl::event(QEvent *e)
{
return QObject::event(e);
}
void QQuickTextControl::timerEvent(QTimerEvent *e)
{
Q_D(QQuickTextControl);
if (e->timerId() == d->cursorBlinkTimer.timerId()) {
d->cursorOn = !d->cursorOn;
d->repaintCursor();
}
}
void QQuickTextControl::setPlainText(const QString &text)
{
Q_D(QQuickTextControl);
d->setContent(Qt::PlainText, text);
}
void QQuickTextControl::setMarkdownText(const QString &text)
{
Q_D(QQuickTextControl);
d->setContent(Qt::MarkdownText, text);
}
void QQuickTextControl::setHtml(const QString &text)
{
Q_D(QQuickTextControl);
d->setContent(Qt::RichText, text);
}
void QQuickTextControlPrivate::keyReleaseEvent(QKeyEvent *e)
{
if (e->key() == Qt::Key_Back) {
e->ignore();
return;
}
return;
}
void QQuickTextControlPrivate::keyPressEvent(QKeyEvent *e)
{
Q_Q(QQuickTextControl);
if (e->key() == Qt::Key_Back) {
e->ignore();
return;
}
#if QT_CONFIG(shortcut)
if (e == QKeySequence::SelectAll) {
e->accept();
q->selectAll();
#if QT_CONFIG(clipboard)
setClipboardSelection();
#endif
return;
}
#if QT_CONFIG(clipboard)
else if (e == QKeySequence::Copy) {
e->accept();
q->copy();
return;
}
#endif
#endif // shortcut
if (interactionFlags & Qt::TextSelectableByKeyboard
&& cursorMoveKeyEvent(e))
goto accept;
if (!(interactionFlags & Qt::TextEditable)) {
e->ignore();
return;
}
if (e->key() == Qt::Key_Direction_L || e->key() == Qt::Key_Direction_R) {
QTextBlockFormat fmt;
fmt.setLayoutDirection((e->key() == Qt::Key_Direction_L) ? Qt::LeftToRight : Qt::RightToLeft);
cursor.mergeBlockFormat(fmt);
goto accept;
}
// schedule a repaint of the region of the cursor, as when we move it we
// want to make sure the old cursor disappears (not noticeable when moving
// only a few pixels but noticeable when jumping between cells in tables for
// example)
repaintSelection();
if (e->key() == Qt::Key_Backspace && !(e->modifiers() & ~Qt::ShiftModifier)) {
QTextBlockFormat blockFmt = cursor.blockFormat();
QTextList *list = cursor.currentList();
if (list && cursor.atBlockStart() && !cursor.hasSelection()) {
list->remove(cursor.block());
} else if (cursor.atBlockStart() && blockFmt.indent() > 0) {
blockFmt.setIndent(blockFmt.indent() - 1);
cursor.setBlockFormat(blockFmt);
} else {
QTextCursor localCursor = cursor;
localCursor.deletePreviousChar();
}
goto accept;
}
#if QT_CONFIG(shortcut)
else if (e == QKeySequence::InsertParagraphSeparator) {
cursor.insertBlock();
e->accept();
goto accept;
} else if (e == QKeySequence::InsertLineSeparator) {
cursor.insertText(QString(QChar::LineSeparator));
e->accept();
goto accept;
}
#endif
if (false) {
}
#if QT_CONFIG(shortcut)
else if (e == QKeySequence::Undo) {
q->undo();
}
else if (e == QKeySequence::Redo) {
q->redo();
}
#if QT_CONFIG(clipboard)
else if (e == QKeySequence::Cut) {
q->cut();
}
else if (e == QKeySequence::Paste) {
QClipboard::Mode mode = QClipboard::Clipboard;
q->paste(mode);
}
#endif
else if (e == QKeySequence::Delete) {
QTextCursor localCursor = cursor;
localCursor.deleteChar();
}
else if (e == QKeySequence::DeleteEndOfWord) {
if (!cursor.hasSelection())
cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
}
else if (e == QKeySequence::DeleteStartOfWord) {
if (!cursor.hasSelection())
cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
}
else if (e == QKeySequence::DeleteEndOfLine) {
QTextBlock block = cursor.block();
if (cursor.position() == block.position() + block.length() - 2)
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
else
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
}
#endif // shortcut
else {
goto process;
}
goto accept;
process:
{
if (q->isAcceptableInput(e)) {
if (overwriteMode
// no need to call deleteChar() if we have a selection, insertText
// does it already
&& !cursor.hasSelection()
&& !cursor.atBlockEnd()) {
cursor.deleteChar();
}
cursor.insertText(e->text());
selectionChanged();
} else {
e->ignore();
return;
}
}
accept:
#if QT_CONFIG(clipboard)
setClipboardSelection();
#endif
e->accept();
cursorOn = true;
q->updateCursorRectangle(true);
updateCurrentCharFormat();
}
QRectF QQuickTextControlPrivate::rectForPosition(int position) const
{
Q_Q(const QQuickTextControl);
const QTextBlock block = doc->findBlock(position);
if (!block.isValid())
return QRectF();
const QTextLayout *layout = block.layout();
const QPointF layoutPos = q->blockBoundingRect(block).topLeft();
int relativePos = position - block.position();
#if QT_CONFIG(im)
if (preeditCursor != 0) {
int preeditPos = layout->preeditAreaPosition();
if (relativePos == preeditPos)
relativePos += preeditCursor;
else if (relativePos > preeditPos)
relativePos += layout->preeditAreaText().length();
}
#endif
QTextLine line = layout->lineForTextPosition(relativePos);
QRectF r;
if (line.isValid()) {
qreal x = line.cursorToX(relativePos);
qreal w = 0;
if (overwriteMode) {
if (relativePos < line.textLength() - line.textStart())
w = line.cursorToX(relativePos + 1) - x;
else
w = QFontMetrics(block.layout()->font()).horizontalAdvance(QLatin1Char(' ')); // in sync with QTextLine::draw()
}
r = QRectF(layoutPos.x() + x, layoutPos.y() + line.y(), textCursorWidth + w, line.height());
} else {
r = QRectF(layoutPos.x(), layoutPos.y(), textCursorWidth, 10); // #### correct height
}
return r;
}
void QQuickTextControlPrivate::mousePressEvent(QMouseEvent *e, const QPointF &pos)
{
Q_Q(QQuickTextControl);
mousePressed = (interactionFlags & Qt::TextSelectableByMouse) && (e->button() & Qt::LeftButton);
mousePressPos = pos.toPoint();
if (sendMouseEventToInputContext(e, pos))
return;
if (interactionFlags & Qt::LinksAccessibleByMouse) {
anchorOnMousePress = q->anchorAt(pos);
if (cursorIsFocusIndicator) {
cursorIsFocusIndicator = false;
repaintSelection();
cursor.clearSelection();
}
}
if (interactionFlags & Qt::TextEditable)
blockWithMarkerUnderMousePress = q->blockWithMarkerAt(pos);
if (e->button() & Qt::MiddleButton) {
return;
} else if (!(e->button() & Qt::LeftButton)) {
e->ignore();
return;
} else if (!(interactionFlags & (Qt::TextSelectableByMouse | Qt::TextEditable))) {
if (!(interactionFlags & Qt::LinksAccessibleByMouse))
e->ignore();
return;
}
cursorIsFocusIndicator = false;
const QTextCursor oldSelection = cursor;
const int oldCursorPos = cursor.position();
#if QT_CONFIG(im)
commitPreedit();
#endif
if ((e->timestamp() < (timestampAtLastDoubleClick + QGuiApplication::styleHints()->mouseDoubleClickInterval()))
&& ((pos - tripleClickPoint).toPoint().manhattanLength() < QGuiApplication::styleHints()->startDragDistance())) {
cursor.movePosition(QTextCursor::StartOfBlock);
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
selectedBlockOnTripleClick = cursor;
anchorOnMousePress = QString();
timestampAtLastDoubleClick = 0; // do not enter this condition in case of 4(!) rapid clicks
} else {
int cursorPos = q->hitTest(pos, Qt::FuzzyHit);
if (cursorPos == -1) {
e->ignore();
return;
}
if (e->modifiers() == Qt::ShiftModifier && (interactionFlags & Qt::TextSelectableByMouse)) {
if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) {
selectedWordOnDoubleClick = cursor;
selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor);
}
if (selectedBlockOnTripleClick.hasSelection())
extendBlockwiseSelection(cursorPos);
else if (selectedWordOnDoubleClick.hasSelection())
extendWordwiseSelection(cursorPos, pos.x());
else if (!wordSelectionEnabled)
setCursorPosition(cursorPos, QTextCursor::KeepAnchor);
} else {
setCursorPosition(cursorPos);
}
}
if (cursor.position() != oldCursorPos) {
q->updateCursorRectangle(true);
emit q->cursorPositionChanged();
}
if (interactionFlags & Qt::TextEditable)
_q_updateCurrentCharFormatAndSelection();
else
selectionChanged();
repaintOldAndNewSelection(oldSelection);
hadSelectionOnMousePress = cursor.hasSelection();
}
void QQuickTextControlPrivate::mouseMoveEvent(QMouseEvent *e, const QPointF &mousePos)
{
Q_Q(QQuickTextControl);
if ((e->buttons() & Qt::LeftButton)) {
const bool editable = interactionFlags & Qt::TextEditable;
if (!(mousePressed
|| editable
|| selectedWordOnDoubleClick.hasSelection()
|| selectedBlockOnTripleClick.hasSelection()))
return;
const QTextCursor oldSelection = cursor;
const int oldCursorPos = cursor.position();
if (!mousePressed)
return;
const qreal mouseX = qreal(mousePos.x());
int newCursorPos = q->hitTest(mousePos, Qt::FuzzyHit);
#if QT_CONFIG(im)
if (isPreediting()) {
// note: oldCursorPos not including preedit
int selectionStartPos = q->hitTest(mousePressPos, Qt::FuzzyHit);
if (newCursorPos != selectionStartPos) {
commitPreedit();
// commit invalidates positions
newCursorPos = q->hitTest(mousePos, Qt::FuzzyHit);
selectionStartPos = q->hitTest(mousePressPos, Qt::FuzzyHit);
setCursorPosition(selectionStartPos);
}
}
#endif
if (newCursorPos == -1)
return;
if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) {
selectedWordOnDoubleClick = cursor;
selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor);
}
if (selectedBlockOnTripleClick.hasSelection())
extendBlockwiseSelection(newCursorPos);
else if (selectedWordOnDoubleClick.hasSelection())
extendWordwiseSelection(newCursorPos, mouseX);
#if QT_CONFIG(im)
else if (!isPreediting())
setCursorPosition(newCursorPos, QTextCursor::KeepAnchor);
#endif
if (interactionFlags & Qt::TextEditable) {
if (cursor.position() != oldCursorPos) {
emit q->cursorPositionChanged();
}
_q_updateCurrentCharFormatAndSelection();
#if QT_CONFIG(im)
if (qGuiApp)
qGuiApp->inputMethod()->update(Qt::ImQueryInput);
#endif
} else if (cursor.position() != oldCursorPos) {
emit q->cursorPositionChanged();
}
selectionChanged(true);
repaintOldAndNewSelection(oldSelection);
}
sendMouseEventToInputContext(e, mousePos);
}
void QQuickTextControlPrivate::mouseReleaseEvent(QMouseEvent *e, const QPointF &pos)
{
Q_Q(QQuickTextControl);
if (sendMouseEventToInputContext(e, pos))
return;
const QTextCursor oldSelection = cursor;
const int oldCursorPos = cursor.position();
if (mousePressed) {
mousePressed = false;
#if QT_CONFIG(clipboard)
setClipboardSelection();
selectionChanged(true);
} else if (e->button() == Qt::MiddleButton
&& (interactionFlags & Qt::TextEditable)
&& QGuiApplication::clipboard()->supportsSelection()) {
setCursorPosition(pos);
const QMimeData *md = QGuiApplication::clipboard()->mimeData(QClipboard::Selection);
if (md)
q->insertFromMimeData(md);
#endif
}
repaintOldAndNewSelection(oldSelection);
if (cursor.position() != oldCursorPos) {
emit q->cursorPositionChanged();
q->updateCursorRectangle(true);
}
if ((interactionFlags & Qt::TextEditable) && (e->button() & Qt::LeftButton) && blockWithMarkerUnderMousePress.isValid()) {
QTextBlock block = q->blockWithMarkerAt(pos);
if (block == blockWithMarkerUnderMousePress) {
auto fmt = block.blockFormat();
fmt.setMarker(fmt.marker() == QTextBlockFormat::MarkerType::Unchecked ?
QTextBlockFormat::MarkerType::Checked : QTextBlockFormat::MarkerType::Unchecked);
cursor.setBlockFormat(fmt);
}
}
if (interactionFlags & Qt::LinksAccessibleByMouse) {
if (!(e->button() & Qt::LeftButton))
return;
const QString anchor = q->anchorAt(pos);
if (anchor.isEmpty())
return;
if (!cursor.hasSelection()
|| (anchor == anchorOnMousePress && hadSelectionOnMousePress)) {
const int anchorPos = q->hitTest(pos, Qt::ExactHit);
if (anchorPos != -1) {
cursor.setPosition(anchorPos);
QString anchor = anchorOnMousePress;
anchorOnMousePress = QString();
activateLinkUnderCursor(anchor);
}
}
}
}
void QQuickTextControlPrivate::mouseDoubleClickEvent(QMouseEvent *e, const QPointF &pos)
{
Q_Q(QQuickTextControl);
if (e->button() == Qt::LeftButton && (interactionFlags & Qt::TextSelectableByMouse)) {
#if QT_CONFIG(im)
commitPreedit();
#endif
const QTextCursor oldSelection = cursor;
setCursorPosition(pos);
QTextLine line = currentTextLine(cursor);
bool doEmit = false;
if (line.isValid() && line.textLength()) {
cursor.select(QTextCursor::WordUnderCursor);
doEmit = true;
}
repaintOldAndNewSelection(oldSelection);
cursorIsFocusIndicator = false;
selectedWordOnDoubleClick = cursor;
tripleClickPoint = pos;
timestampAtLastDoubleClick = e->timestamp();
if (doEmit) {
selectionChanged();
#if QT_CONFIG(clipboard)
setClipboardSelection();
#endif
emit q->cursorPositionChanged();
q->updateCursorRectangle(true);
}
} else if (!sendMouseEventToInputContext(e, pos)) {
e->ignore();
}
}
bool QQuickTextControlPrivate::sendMouseEventToInputContext(QMouseEvent *e, const QPointF &pos)
{
#if QT_CONFIG(im)
Q_Q(QQuickTextControl);
Q_UNUSED(e);
if (isPreediting()) {
QTextLayout *layout = cursor.block().layout();
int cursorPos = q->hitTest(pos, Qt::FuzzyHit) - cursor.position();
if (cursorPos >= 0 && cursorPos <= layout->preeditAreaText().length()) {
if (e->type() == QEvent::MouseButtonRelease) {
QGuiApplication::inputMethod()->invokeAction(QInputMethod::Click, cursorPos);
}
return true;
}
}
#else
Q_UNUSED(e);
Q_UNUSED(pos);
#endif
return false;
}
#if QT_CONFIG(im)
void QQuickTextControlPrivate::inputMethodEvent(QInputMethodEvent *e)
{
Q_Q(QQuickTextControl);
if (!(interactionFlags & Qt::TextEditable) || cursor.isNull()) {
e->ignore();
return;
}
bool isGettingInput = !e->commitString().isEmpty()
|| e->preeditString() != cursor.block().layout()->preeditAreaText()
|| e->replacementLength() > 0;
bool forceSelectionChanged = false;
int oldCursorPos = cursor.position();
cursor.beginEditBlock();
if (isGettingInput) {
cursor.removeSelectedText();
}
QTextBlock block;
// insert commit string
if (!e->commitString().isEmpty() || e->replacementLength()) {
if (e->commitString().endsWith(QChar::LineFeed))
block = cursor.block(); // Remember the block where the preedit text is
QTextCursor c = cursor;
c.setPosition(c.position() + e->replacementStart());
c.setPosition(c.position() + e->replacementLength(), QTextCursor::KeepAnchor);
c.insertText(e->commitString());
}
for (int i = 0; i < e->attributes().size(); ++i) {
const QInputMethodEvent::Attribute &a = e->attributes().at(i);
if (a.type == QInputMethodEvent::Selection) {
QTextCursor oldCursor = cursor;
int blockStart = a.start + cursor.block().position();
cursor.setPosition(blockStart, QTextCursor::MoveAnchor);
cursor.setPosition(blockStart + a.length, QTextCursor::KeepAnchor);
repaintOldAndNewSelection(oldCursor);
forceSelectionChanged = true;
}
}
if (!block.isValid())
block = cursor.block();
QTextLayout *layout = block.layout();
if (isGettingInput) {
layout->setPreeditArea(cursor.position() - block.position(), e->preeditString());
emit q->preeditTextChanged();
}
QVector<QTextLayout::FormatRange> overrides;
const int oldPreeditCursor = preeditCursor;
preeditCursor = e->preeditString().length();
hasImState = !e->preeditString().isEmpty();
cursorVisible = true;
for (int i = 0; i < e->attributes().size(); ++i) {
const QInputMethodEvent::Attribute &a = e->attributes().at(i);
if (a.type == QInputMethodEvent::Cursor) {
hasImState = true;
preeditCursor = a.start;
cursorVisible = a.length != 0;
} else if (a.type == QInputMethodEvent::TextFormat) {
hasImState = true;
QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat();
if (f.isValid()) {
QTextLayout::FormatRange o;
o.start = a.start + cursor.position() - block.position();
o.length = a.length;
o.format = f;
overrides.append(o);
}
}
}
layout->setFormats(overrides);
cursor.endEditBlock();
QTextCursorPrivate *cursor_d = QTextCursorPrivate::getPrivate(&cursor);
if (cursor_d)
cursor_d->setX();
if (cursor.position() != oldCursorPos)
emit q->cursorPositionChanged();
q->updateCursorRectangle(oldPreeditCursor != preeditCursor || forceSelectionChanged || isGettingInput);
selectionChanged(forceSelectionChanged);
}
QVariant QQuickTextControl::inputMethodQuery(Qt::InputMethodQuery property) const
{
return inputMethodQuery(property, QVariant());
}
QVariant QQuickTextControl::inputMethodQuery(Qt::InputMethodQuery property, const QVariant &argument) const
{
Q_D(const QQuickTextControl);
QTextBlock block = d->cursor.block();
switch (property) {
case Qt::ImCursorRectangle:
return cursorRect();
case Qt::ImAnchorRectangle:
return anchorRect();
case Qt::ImFont:
return QVariant(d->cursor.charFormat().font());
case Qt::ImCursorPosition: {
const QPointF pt = argument.toPointF();
if (!pt.isNull())
return QVariant(d->doc->documentLayout()->hitTest(pt, Qt::FuzzyHit) - block.position());
return QVariant(d->cursor.position() - block.position());
}
case Qt::ImSurroundingText:
return QVariant(block.text());
case Qt::ImCurrentSelection:
return QVariant(d->cursor.selectedText());
case Qt::ImMaximumTextLength:
return QVariant(); // No limit.
case Qt::ImAnchorPosition:
return QVariant(d->cursor.anchor() - block.position());
case Qt::ImAbsolutePosition:
return QVariant(d->cursor.position());
case Qt::ImTextAfterCursor:
{
int maxLength = argument.isValid() ? argument.toInt() : 1024;
QTextCursor tmpCursor = d->cursor;
int localPos = d->cursor.position() - block.position();
QString result = block.text().mid(localPos);
while (result.length() < maxLength) {
int currentBlock = tmpCursor.blockNumber();
tmpCursor.movePosition(QTextCursor::NextBlock);
if (tmpCursor.blockNumber() == currentBlock)
break;
result += QLatin1Char('\n') + tmpCursor.block().text();
}
return QVariant(result);
}
case Qt::ImTextBeforeCursor:
{
int maxLength = argument.isValid() ? argument.toInt() : 1024;
QTextCursor tmpCursor = d->cursor;
int localPos = d->cursor.position() - block.position();
int numBlocks = 0;
int resultLen = localPos;
while (resultLen < maxLength) {
int currentBlock = tmpCursor.blockNumber();
tmpCursor.movePosition(QTextCursor::PreviousBlock);
if (tmpCursor.blockNumber() == currentBlock)
break;
numBlocks++;
resultLen += tmpCursor.block().length();
}
QString result;
while (numBlocks) {
result += tmpCursor.block().text() + QLatin1Char('\n');
tmpCursor.movePosition(QTextCursor::NextBlock);
--numBlocks;
}
result += block.text().midRef(0,localPos);
return QVariant(result);
}
default:
return QVariant();
}
}
#endif // im
void QQuickTextControlPrivate::focusEvent(QFocusEvent *e)
{
Q_Q(QQuickTextControl);
emit q->updateRequest();
hasFocus = e->gotFocus();
if (e->gotFocus()) {
setBlinkingCursorEnabled(interactionFlags & (Qt::TextEditable | Qt::TextSelectableByKeyboard));
} else {
setBlinkingCursorEnabled(false);
if (cursorIsFocusIndicator
&& e->reason() != Qt::ActiveWindowFocusReason
&& e->reason() != Qt::PopupFocusReason
&& cursor.hasSelection()) {
cursor.clearSelection();
emit q->selectionChanged();
}
}
}
void QQuickTextControlPrivate::hoverEvent(QHoverEvent *e, const QPointF &pos)
{
Q_Q(QQuickTextControl);
QString link;
if (e->type() != QEvent::HoverLeave)
link = q->anchorAt(pos);
if (hoveredLink != link) {
hoveredLink = link;
emit q->linkHovered(link);
qCDebug(DBG_HOVER_TRACE) << q << e->type() << pos << "hoveredLink" << hoveredLink;
} else {
QTextBlock block = q->blockWithMarkerAt(pos);
if (block.isValid() != hoveredMarker)
emit q->markerHovered(block.isValid());
hoveredMarker = block.isValid();
if (hoveredMarker)
qCDebug(DBG_HOVER_TRACE) << q << e->type() << pos << "hovered marker" << int(block.blockFormat().marker()) << block.text();
}
}
bool QQuickTextControl::hasImState() const
{
Q_D(const QQuickTextControl);
return d->hasImState;
}
bool QQuickTextControl::overwriteMode() const
{
Q_D(const QQuickTextControl);
return d->overwriteMode;
}
void QQuickTextControl::setOverwriteMode(bool overwrite)
{
Q_D(QQuickTextControl);
if (d->overwriteMode == overwrite)
return;
d->overwriteMode = overwrite;
emit overwriteModeChanged(overwrite);
}
bool QQuickTextControl::cursorVisible() const
{
Q_D(const QQuickTextControl);
return d->cursorVisible;
}
void QQuickTextControl::setCursorVisible(bool visible)
{
Q_D(QQuickTextControl);
d->cursorVisible = visible;
d->setBlinkingCursorEnabled(d->cursorVisible
&& (d->interactionFlags & (Qt::TextEditable | Qt::TextSelectableByKeyboard)));
}
QRectF QQuickTextControl::anchorRect() const
{
Q_D(const QQuickTextControl);
QRectF rect;
QTextCursor cursor = d->cursor;
if (!cursor.isNull()) {
rect = d->rectForPosition(cursor.anchor());
}
return rect;
}
QRectF QQuickTextControl::cursorRect(const QTextCursor &cursor) const
{
Q_D(const QQuickTextControl);
if (cursor.isNull())
return QRectF();
return d->rectForPosition(cursor.position());
}
QRectF QQuickTextControl::cursorRect() const
{
Q_D(const QQuickTextControl);
return cursorRect(d->cursor);
}
QString QQuickTextControl::hoveredLink() const
{
Q_D(const QQuickTextControl);
return d->hoveredLink;
}
QString QQuickTextControl::anchorAt(const QPointF &pos) const
{
Q_D(const QQuickTextControl);
return d->doc->documentLayout()->anchorAt(pos);
}
QTextBlock QQuickTextControl::blockWithMarkerAt(const QPointF &pos) const
{
Q_D(const QQuickTextControl);
return d->doc->documentLayout()->blockWithMarkerAt(pos);
}
void QQuickTextControl::setAcceptRichText(bool accept)
{
Q_D(QQuickTextControl);
d->acceptRichText = accept;
}
void QQuickTextControl::moveCursor(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode)
{
Q_D(QQuickTextControl);
const QTextCursor oldSelection = d->cursor;
const bool moved = d->cursor.movePosition(op, mode);
d->_q_updateCurrentCharFormatAndSelection();
updateCursorRectangle(true);
d->repaintOldAndNewSelection(oldSelection);
if (moved)
emit cursorPositionChanged();
}
bool QQuickTextControl::canPaste() const
{
#if QT_CONFIG(clipboard)
Q_D(const QQuickTextControl);
if (d->interactionFlags & Qt::TextEditable) {
const QMimeData *md = QGuiApplication::clipboard()->mimeData();
return md && canInsertFromMimeData(md);
}
#endif
return false;
}
void QQuickTextControl::setCursorIsFocusIndicator(bool b)
{
Q_D(QQuickTextControl);
d->cursorIsFocusIndicator = b;
d->repaintCursor();
}
void QQuickTextControl::setWordSelectionEnabled(bool enabled)
{
Q_D(QQuickTextControl);
d->wordSelectionEnabled = enabled;
}
QMimeData *QQuickTextControl::createMimeDataFromSelection() const
{
Q_D(const QQuickTextControl);
const QTextDocumentFragment fragment(d->cursor);
return new QQuickTextEditMimeData(fragment);
}
bool QQuickTextControl::canInsertFromMimeData(const QMimeData *source) const
{
Q_D(const QQuickTextControl);
if (d->acceptRichText)
return source->hasText()
|| source->hasHtml()
|| source->hasFormat(QLatin1String("application/x-qrichtext"))
|| source->hasFormat(QLatin1String("application/x-qt-richtext"));
else
return source->hasText();
}
void QQuickTextControl::insertFromMimeData(const QMimeData *source)
{
Q_D(QQuickTextControl);
if (!(d->interactionFlags & Qt::TextEditable) || !source)
return;
bool hasData = false;
QTextDocumentFragment fragment;
#if QT_CONFIG(texthtmlparser)
if (source->hasFormat(QLatin1String("application/x-qrichtext")) && d->acceptRichText) {
// x-qrichtext is always UTF-8 (taken from Qt3 since we don't use it anymore).
const QString richtext = QLatin1String("<meta name=\"qrichtext\" content=\"1\" />")
+ QString::fromUtf8(source->data(QLatin1String("application/x-qrichtext")));
fragment = QTextDocumentFragment::fromHtml(richtext, d->doc);
hasData = true;
} else if (source->hasHtml() && d->acceptRichText) {
fragment = QTextDocumentFragment::fromHtml(source->html(), d->doc);
hasData = true;
} else {
QString text = source->text();
if (!text.isNull()) {
fragment = QTextDocumentFragment::fromPlainText(text);
hasData = true;
}
}
#else
fragment = QTextDocumentFragment::fromPlainText(source->text());
#endif // texthtmlparser
if (hasData)
d->cursor.insertFragment(fragment);
updateCursorRectangle(true);
}
void QQuickTextControlPrivate::activateLinkUnderCursor(QString href)
{
QTextCursor oldCursor = cursor;
if (href.isEmpty()) {
QTextCursor tmp = cursor;
if (tmp.selectionStart() != tmp.position())
tmp.setPosition(tmp.selectionStart());
tmp.movePosition(QTextCursor::NextCharacter);
href = tmp.charFormat().anchorHref();
}
if (href.isEmpty())
return;
if (!cursor.hasSelection()) {
QTextBlock block = cursor.block();
const int cursorPos = cursor.position();
QTextBlock::Iterator it = block.begin();
QTextBlock::Iterator linkFragment;
for (; !it.atEnd(); ++it) {
QTextFragment fragment = it.fragment();
const int fragmentPos = fragment.position();
if (fragmentPos <= cursorPos &&
fragmentPos + fragment.length() > cursorPos) {
linkFragment = it;
break;
}
}
if (!linkFragment.atEnd()) {
it = linkFragment;
cursor.setPosition(it.fragment().position());
if (it != block.begin()) {
do {
--it;
QTextFragment fragment = it.fragment();
if (fragment.charFormat().anchorHref() != href)
break;
cursor.setPosition(fragment.position());
} while (it != block.begin());
}
for (it = linkFragment; !it.atEnd(); ++it) {
QTextFragment fragment = it.fragment();
if (fragment.charFormat().anchorHref() != href)
break;
cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor);
}
}
}
if (hasFocus) {
cursorIsFocusIndicator = true;
} else {
cursorIsFocusIndicator = false;
cursor.clearSelection();
}
repaintOldAndNewSelection(oldCursor);
emit q_func()->linkActivated(href);
}
#if QT_CONFIG(im)
bool QQuickTextControlPrivate::isPreediting() const
{
QTextLayout *layout = cursor.block().layout();
if (layout && !layout->preeditAreaText().isEmpty())
return true;
return false;
}
void QQuickTextControlPrivate::commitPreedit()
{
Q_Q(QQuickTextControl);
if (!hasImState)
return;
QGuiApplication::inputMethod()->commit();
if (!hasImState)
return;
QInputMethodEvent event;
QCoreApplication::sendEvent(q->parent(), &event);
}
void QQuickTextControlPrivate::cancelPreedit()
{
Q_Q(QQuickTextControl);
if (!hasImState)
return;
QGuiApplication::inputMethod()->reset();
QInputMethodEvent event;
QCoreApplication::sendEvent(q->parent(), &event);
}
#endif // im
void QQuickTextControl::setTextInteractionFlags(Qt::TextInteractionFlags flags)
{
Q_D(QQuickTextControl);
if (flags == d->interactionFlags)
return;
d->interactionFlags = flags;
if (d->hasFocus)
d->setBlinkingCursorEnabled(flags & (Qt::TextEditable | Qt::TextSelectableByKeyboard));
}
Qt::TextInteractionFlags QQuickTextControl::textInteractionFlags() const
{
Q_D(const QQuickTextControl);
return d->interactionFlags;
}
QString QQuickTextControl::toPlainText() const
{
return document()->toPlainText();
}
#if QT_CONFIG(texthtmlparser)
QString QQuickTextControl::toHtml() const
{
return document()->toHtml();
}
#endif
#if QT_CONFIG(textmarkdownwriter)
QString QQuickTextControl::toMarkdown() const
{
return document()->toMarkdown();
}
#endif
bool QQuickTextControl::cursorOn() const
{
Q_D(const QQuickTextControl);
return d->cursorOn;
}
int QQuickTextControl::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const
{
Q_D(const QQuickTextControl);
return d->doc->documentLayout()->hitTest(point, accuracy);
}
QRectF QQuickTextControl::blockBoundingRect(const QTextBlock &block) const
{
Q_D(const QQuickTextControl);
return d->doc->documentLayout()->blockBoundingRect(block);
}
QString QQuickTextControl::preeditText() const
{
#if QT_CONFIG(im)
Q_D(const QQuickTextControl);
QTextLayout *layout = d->cursor.block().layout();
if (!layout)
return QString();
return layout->preeditAreaText();
#else
return QString();
#endif
}
QStringList QQuickTextEditMimeData::formats() const
{
if (!fragment.isEmpty())
return QStringList() << QString::fromLatin1("text/plain") << QString::fromLatin1("text/html")
#if QT_CONFIG(textodfwriter)
<< QString::fromLatin1("application/vnd.oasis.opendocument.text")
#endif
;
else
return QMimeData::formats();
}
QVariant QQuickTextEditMimeData::retrieveData(const QString &mimeType, QVariant::Type type) const
{
if (!fragment.isEmpty())
setup();
return QMimeData::retrieveData(mimeType, type);
}
void QQuickTextEditMimeData::setup() const
{
QQuickTextEditMimeData *that = const_cast<QQuickTextEditMimeData *>(this);
#if QT_CONFIG(texthtmlparser)
that->setData(QLatin1String("text/html"), fragment.toHtml("utf-8").toUtf8());
#endif
#if QT_CONFIG(textodfwriter)
{
QBuffer buffer;
QTextDocumentWriter writer(&buffer, "ODF");
writer.write(fragment);
buffer.close();
that->setData(QLatin1String("application/vnd.oasis.opendocument.text"), buffer.data());
}
#endif
that->setText(fragment.toPlainText());
fragment = QTextDocumentFragment();
}
QT_END_NAMESPACE
#include "moc_qquicktextcontrol_p.cpp"
#endif // QT_NO_TEXTCONTROL