/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui 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 "qtextcontrol_p.h" #include "qtextcontrol_p_p.h" #ifndef QT_NO_TEXTCONTROL #include #include #include #include #include #include #include #include #include #include #include "private/qtextdocumentlayout_p.h" #include "private/qtextedit_p.h" #include "qtextdocument.h" #include "private/qtextdocument_p.h" #include "qtextlist.h" #include "private/qtextcontrol_p.h" #include "qgraphicssceneevent.h" #include "qprinter.h" #include "qtextdocumentwriter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef QT_NO_SHORTCUT #include "private/qapplication_p.h" #include "private/qshortcutmap_p.h" #include #define ACCEL_KEY(k) (!qApp->d_func()->shortcutMap.hasShortcutForKeySequence(k) ? QLatin1Char('\t') + QString(QKeySequence(k)) : QString()) #else #define ACCEL_KEY(k) QString() #endif QT_BEGIN_NAMESPACE #ifndef QT_NO_CONTEXTMENU #if defined(Q_WS_WIN) extern bool qt_use_rtl_extensions; #endif #endif // 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); } QTextControlPrivate::QTextControlPrivate() : doc(0), cursorOn(false), cursorIsFocusIndicator(false), interactionFlags(Qt::TextEditorInteraction), #ifndef QT_NO_DRAGANDDROP mousePressed(false), mightStartDrag(false), #endif lastSelectionState(false), ignoreAutomaticScrollbarAdjustement(false), overwriteMode(false), acceptRichText(true), preeditCursor(0), hideCursor(false), hasFocus(false), #ifdef QT_KEYPAD_NAVIGATION hasEditFocus(false), #endif isEnabled(true), hadSelectionOnMousePress(false), openExternalLinks(false) {} bool QTextControlPrivate::cursorMoveKeyEvent(QKeyEvent *e) { #ifdef QT_NO_SHORTCUT Q_UNUSED(e); #endif Q_Q(QTextControl); 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) { } #ifndef QT_NO_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::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 // QT_NO_SHORTCUT else { return false; } // Except for pageup and pagedown, Mac 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); q->ensureCursorVisible(); if (moved) { if (cursor.position() != oldCursorPos) emit q->cursorPositionChanged(); emit q->microFocusChanged(); } #ifdef QT_KEYPAD_NAVIGATION else if (QApplication::keypadNavigationEnabled() && (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down)) { return false; } #endif selectionChanged(/*forceEmitSelectionChanged =*/(mode == QTextCursor::KeepAnchor)); repaintOldAndNewSelection(oldSelection); return true; } void QTextControlPrivate::updateCurrentCharFormat() { Q_Q(QTextControl); QTextCharFormat fmt = cursor.charFormat(); if (fmt == lastCharFormat) return; lastCharFormat = fmt; emit q->currentCharFormatChanged(fmt); emit q->microFocusChanged(); } void QTextControlPrivate::indent() { QTextBlockFormat blockFmt = cursor.blockFormat(); QTextList *list = cursor.currentList(); if (!list) { QTextBlockFormat modifier; modifier.setIndent(blockFmt.indent() + 1); cursor.mergeBlockFormat(modifier); } else { QTextListFormat format = list->format(); format.setIndent(format.indent() + 1); if (list->itemNumber(cursor.block()) == 1) list->setFormat(format); else cursor.createList(format); } } void QTextControlPrivate::outdent() { QTextBlockFormat blockFmt = cursor.blockFormat(); QTextList *list = cursor.currentList(); if (!list) { QTextBlockFormat modifier; modifier.setIndent(blockFmt.indent() - 1); cursor.mergeBlockFormat(modifier); } else { QTextListFormat listFmt = list->format(); listFmt.setIndent(listFmt.indent() - 1); list->setFormat(listFmt); } } void QTextControlPrivate::gotoNextTableCell() { QTextTable *table = cursor.currentTable(); QTextTableCell cell = table->cellAt(cursor); int newColumn = cell.column() + cell.columnSpan(); int newRow = cell.row(); if (newColumn >= table->columns()) { newColumn = 0; ++newRow; if (newRow >= table->rows()) table->insertRows(table->rows(), 1); } cell = table->cellAt(newRow, newColumn); cursor = cell.firstCursorPosition(); } void QTextControlPrivate::gotoPreviousTableCell() { QTextTable *table = cursor.currentTable(); QTextTableCell cell = table->cellAt(cursor); int newColumn = cell.column() - 1; int newRow = cell.row(); if (newColumn < 0) { newColumn = table->columns() - 1; --newRow; if (newRow < 0) return; } cell = table->cellAt(newRow, newColumn); cursor = cell.firstCursorPosition(); } void QTextControlPrivate::createAutoBulletList() { cursor.beginEditBlock(); QTextBlockFormat blockFmt = cursor.blockFormat(); QTextListFormat listFmt; listFmt.setStyle(QTextListFormat::ListDisc); listFmt.setIndent(blockFmt.indent() + 1); blockFmt.setIndent(0); cursor.setBlockFormat(blockFmt); cursor.createList(listFmt); cursor.endEditBlock(); } void QTextControlPrivate::init(Qt::TextFormat format, const QString &text, QTextDocument *document) { Q_Q(QTextControl); setContent(format, text, document); QWidget *parentWidget = qobject_cast(parent); if (parentWidget) { QTextOption opt = doc->defaultTextOption(); opt.setTextDirection(parentWidget->layoutDirection()); doc->setDefaultTextOption(opt); } doc->setUndoRedoEnabled(interactionFlags & Qt::TextEditable); q->setCursorWidth(-1); } void QTextControlPrivate::setContent(Qt::TextFormat format, const QString &text, QTextDocument *document) { Q_Q(QTextControl); // for use when called from setPlainText. we may want to re-use the currently // set char format then. const QTextCharFormat charFormatForInsertion = cursor.charFormat(); bool clearDocument = true; if (!doc) { if (document) { doc = document; clearDocument = false; } else { palette = QApplication::palette("QTextControl"); doc = new QTextDocument(q); } _q_documentLayoutChanged(); cursor = QTextCursor(doc); // #### doc->documentLayout()->setPaintDevice(viewport); QObject::connect(doc, SIGNAL(contentsChanged()), q, SLOT(_q_updateCurrentCharFormatAndSelection())); QObject::connect(doc, SIGNAL(cursorPositionChanged(QTextCursor)), q, SLOT(_q_emitCursorPosChanged(QTextCursor))); QObject::connect(doc, SIGNAL(documentLayoutChanged()), q, SLOT(_q_documentLayoutChanged())); // convenience signal forwards QObject::connect(doc, SIGNAL(contentsChanged()), q, SIGNAL(textChanged())); QObject::connect(doc, SIGNAL(undoAvailable(bool)), q, SIGNAL(undoAvailable(bool))); QObject::connect(doc, SIGNAL(redoAvailable(bool)), q, SIGNAL(redoAvailable(bool))); QObject::connect(doc, SIGNAL(modificationChanged(bool)), q, SIGNAL(modificationChanged(bool))); QObject::connect(doc, SIGNAL(blockCountChanged(int)), q, SIGNAL(blockCountChanged(int))); } bool previousUndoRedoState = doc->isUndoRedoEnabled(); if (!document) doc->setUndoRedoEnabled(false); // avoid multiple textChanged() signals being emitted QObject::disconnect(doc, SIGNAL(contentsChanged()), q, 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 { #ifndef QT_NO_TEXTHTMLPARSER doc->setHtml(text); #else doc->setPlainText(text); #endif doc->setUndoRedoEnabled(false); } cursor = QTextCursor(doc); } else if (clearDocument) { doc->clear(); } cursor.setCharFormat(charFormatForInsertion); QObject::connect(doc, SIGNAL(contentsChanged()), q, SIGNAL(textChanged())); emit q->textChanged(); if (!document) doc->setUndoRedoEnabled(previousUndoRedoState); _q_updateCurrentCharFormatAndSelection(); if (!document) doc->setModified(false); q->ensureCursorVisible(); emit q->cursorPositionChanged(); } void QTextControlPrivate::startDrag() { #ifndef QT_NO_DRAGANDDROP Q_Q(QTextControl); mousePressed = false; if (!contextWidget) return; QMimeData *data = q->createMimeDataFromSelection(); QDrag *drag = new QDrag(contextWidget); drag->setMimeData(data); Qt::DropActions actions = Qt::CopyAction; if (interactionFlags & Qt::TextEditable) actions |= Qt::MoveAction; Qt::DropAction action = drag->exec(actions, Qt::MoveAction); if (action == Qt::MoveAction && drag->target() != contextWidget) cursor.removeSelectedText(); #endif } void QTextControlPrivate::setCursorPosition(const QPointF &pos) { Q_Q(QTextControl); const int cursorPos = q->hitTest(pos, Qt::FuzzyHit); if (cursorPos == -1) return; cursor.setPosition(cursorPos); } void QTextControlPrivate::setCursorPosition(int pos, QTextCursor::MoveMode mode) { cursor.setPosition(pos, mode); if (mode != QTextCursor::KeepAnchor) { selectedWordOnDoubleClick = QTextCursor(); selectedBlockOnTrippleClick = QTextCursor(); } } void QTextControlPrivate::repaintCursor() { Q_Q(QTextControl); emit q->updateRequest(cursorRectPlusUnicodeDirectionMarkers(cursor)); } void QTextControlPrivate::repaintOldAndNewSelection(const QTextCursor &oldSelection) { Q_Q(QTextControl); 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(q->selectionRect(differenceSelection)); } else { if (!oldSelection.isNull()) emit q->updateRequest(q->selectionRect(oldSelection) | cursorRectPlusUnicodeDirectionMarkers(oldSelection)); emit q->updateRequest(q->selectionRect() | cursorRectPlusUnicodeDirectionMarkers(cursor)); } } void QTextControlPrivate::selectionChanged(bool forceEmitSelectionChanged /*=false*/) { Q_Q(QTextControl); if (forceEmitSelectionChanged) emit q->selectionChanged(); bool current = cursor.hasSelection(); if (current == lastSelectionState) return; lastSelectionState = current; emit q->copyAvailable(current); if (!forceEmitSelectionChanged) emit q->selectionChanged(); emit q->microFocusChanged(); } void QTextControlPrivate::_q_updateCurrentCharFormatAndSelection() { updateCurrentCharFormat(); selectionChanged(); } #ifndef QT_NO_CLIPBOARD void QTextControlPrivate::setClipboardSelection() { QClipboard *clipboard = QApplication::clipboard(); if (!cursor.hasSelection() || !clipboard->supportsSelection()) return; Q_Q(QTextControl); QMimeData *data = q->createMimeDataFromSelection(); clipboard->setMimeData(data, QClipboard::Selection); } #endif void QTextControlPrivate::_q_emitCursorPosChanged(const QTextCursor &someCursor) { Q_Q(QTextControl); if (someCursor.isCopyOf(cursor)) { emit q->cursorPositionChanged(); emit q->microFocusChanged(); } } void QTextControlPrivate::_q_documentLayoutChanged() { Q_Q(QTextControl); QAbstractTextDocumentLayout *layout = doc->documentLayout(); QObject::connect(layout, SIGNAL(update(QRectF)), q, SIGNAL(updateRequest(QRectF))); QObject::connect(layout, SIGNAL(updateBlock(QTextBlock)), q, SLOT(_q_updateBlock(QTextBlock))); QObject::connect(layout, SIGNAL(documentSizeChanged(QSizeF)), q, SIGNAL(documentSizeChanged(QSizeF))); } void QTextControlPrivate::setBlinkingCursorEnabled(bool enable) { Q_Q(QTextControl); if (enable && QApplication::cursorFlashTime() > 0) cursorBlinkTimer.start(QApplication::cursorFlashTime() / 2, q); else cursorBlinkTimer.stop(); cursorOn = enable; repaintCursor(); } void QTextControlPrivate::extendWordwiseSelection(int suggestedNewPosition, qreal mouseXPosition) { Q_Q(QTextControl); // 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 (mouseXPosition < wordStartX || mouseXPosition > wordEndX) return; // keep the already selected word even when moving to the left // (#39164) if (suggestedNewPosition < selectedWordOnDoubleClick.position()) cursor.setPosition(selectedWordOnDoubleClick.selectionEnd()); else cursor.setPosition(selectedWordOnDoubleClick.selectionStart()); const qreal differenceToStart = mouseXPosition - wordStartX; const qreal differenceToEnd = wordEndX - mouseXPosition; if (differenceToStart < differenceToEnd) setCursorPosition(wordStartPos, QTextCursor::KeepAnchor); else setCursorPosition(wordEndPos, QTextCursor::KeepAnchor); if (interactionFlags & Qt::TextSelectableByMouse) { #ifndef QT_NO_CLIPBOARD setClipboardSelection(); #endif selectionChanged(true); } } void QTextControlPrivate::extendBlockwiseSelection(int suggestedNewPosition) { Q_Q(QTextControl); // if inside the initial selected line keep that if (suggestedNewPosition >= selectedBlockOnTrippleClick.selectionStart() && suggestedNewPosition <= selectedBlockOnTrippleClick.selectionEnd()) { q->setTextCursor(selectedBlockOnTrippleClick); return; } if (suggestedNewPosition < selectedBlockOnTrippleClick.position()) { cursor.setPosition(selectedBlockOnTrippleClick.selectionEnd()); cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor); cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); } else { cursor.setPosition(selectedBlockOnTrippleClick.selectionStart()); cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor); cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); } if (interactionFlags & Qt::TextSelectableByMouse) { #ifndef QT_NO_CLIPBOARD setClipboardSelection(); #endif selectionChanged(true); } } void QTextControlPrivate::_q_deleteSelected() { if (!(interactionFlags & Qt::TextEditable) || !cursor.hasSelection()) return; cursor.removeSelectedText(); } void QTextControl::undo() { Q_D(QTextControl); d->repaintSelection(); d->doc->undo(&d->cursor); ensureCursorVisible(); } void QTextControl::redo() { Q_D(QTextControl); d->repaintSelection(); d->doc->redo(&d->cursor); ensureCursorVisible(); } QTextControl::QTextControl(QObject *parent) : QObject(*new QTextControlPrivate, parent) { Q_D(QTextControl); d->init(); } QTextControl::QTextControl(const QString &text, QObject *parent) : QObject(*new QTextControlPrivate, parent) { Q_D(QTextControl); d->init(Qt::RichText, text); } QTextControl::QTextControl(QTextDocument *doc, QObject *parent) : QObject(*new QTextControlPrivate, parent) { Q_D(QTextControl); d->init(Qt::RichText, QString(), doc); } QTextControl::~QTextControl() { } void QTextControl::setDocument(QTextDocument *document) { Q_D(QTextControl); if (d->doc == document) return; d->doc->disconnect(this); d->doc->documentLayout()->disconnect(this); d->doc->documentLayout()->setPaintDevice(0); if (d->doc->parent() == this) delete d->doc; d->doc = 0; d->setContent(Qt::RichText, QString(), document); } QTextDocument *QTextControl::document() const { Q_D(const QTextControl); return d->doc; } void QTextControl::setTextCursor(const QTextCursor &cursor) { Q_D(QTextControl); 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(); ensureCursorVisible(); d->repaintOldAndNewSelection(oldSelection); if (posChanged) emit cursorPositionChanged(); } QTextCursor QTextControl::textCursor() const { Q_D(const QTextControl); return d->cursor; } #ifndef QT_NO_CLIPBOARD void QTextControl::cut() { Q_D(QTextControl); if (!(d->interactionFlags & Qt::TextEditable) || !d->cursor.hasSelection()) return; copy(); d->cursor.removeSelectedText(); } void QTextControl::copy() { Q_D(QTextControl); if (!d->cursor.hasSelection()) return; QMimeData *data = createMimeDataFromSelection(); QApplication::clipboard()->setMimeData(data); } void QTextControl::paste() { const QMimeData *md = QApplication::clipboard()->mimeData(); if (md) insertFromMimeData(md); } #endif void QTextControl::clear() { Q_D(QTextControl); // clears and sets empty content d->extraSelections.clear(); d->setContent(); } void QTextControl::selectAll() { Q_D(QTextControl); 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 QTextControl::processEvent(QEvent *e, const QPointF &coordinateOffset, QWidget *contextWidget) { QMatrix m; m.translate(coordinateOffset.x(), coordinateOffset.y()); processEvent(e, m, contextWidget); } void QTextControl::processEvent(QEvent *e, const QMatrix &matrix, QWidget *contextWidget) { Q_D(QTextControl); if (d->interactionFlags & Qt::NoTextInteraction) return; d->contextWidget = contextWidget; if (!d->contextWidget) { switch (e->type()) { #ifndef QT_NO_GRAPHICSVIEW case QEvent::GraphicsSceneMouseMove: case QEvent::GraphicsSceneMousePress: case QEvent::GraphicsSceneMouseRelease: case QEvent::GraphicsSceneMouseDoubleClick: case QEvent::GraphicsSceneContextMenu: case QEvent::GraphicsSceneHoverEnter: case QEvent::GraphicsSceneHoverMove: case QEvent::GraphicsSceneHoverLeave: case QEvent::GraphicsSceneHelp: case QEvent::GraphicsSceneDragEnter: case QEvent::GraphicsSceneDragMove: case QEvent::GraphicsSceneDragLeave: case QEvent::GraphicsSceneDrop: { QGraphicsSceneEvent *ev = static_cast(e); d->contextWidget = ev->widget(); break; } #endif // QT_NO_GRAPHICSVIEW default: break; }; } switch (e->type()) { case QEvent::KeyPress: d->keyPressEvent(static_cast(e)); break; case QEvent::MouseButtonPress: { QMouseEvent *ev = static_cast(e); d->mousePressEvent(ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), ev->globalPos()); break; } case QEvent::MouseMove: { QMouseEvent *ev = static_cast(e); d->mouseMoveEvent(ev->buttons(), matrix.map(ev->pos())); break; } case QEvent::MouseButtonRelease: { QMouseEvent *ev = static_cast(e); d->mouseReleaseEvent(ev->button(), matrix.map(ev->pos())); break; } case QEvent::MouseButtonDblClick: { QMouseEvent *ev = static_cast(e); d->mouseDoubleClickEvent(e, ev->button(), matrix.map(ev->pos())); break; } case QEvent::InputMethod: d->inputMethodEvent(static_cast(e)); break; #ifndef QT_NO_CONTEXTMENU case QEvent::ContextMenu: { QContextMenuEvent *ev = static_cast(e); d->contextMenuEvent(ev->globalPos(), matrix.map(ev->pos()), contextWidget); break; } #endif // QT_NO_CONTEXTMENU case QEvent::FocusIn: case QEvent::FocusOut: d->focusEvent(static_cast(e)); break; case QEvent::EnabledChange: d->isEnabled = e->isAccepted(); break; #ifndef QT_NO_TOOLTIP case QEvent::ToolTip: { QHelpEvent *ev = static_cast(e); d->showToolTip(ev->globalPos(), matrix.map(ev->pos()), contextWidget); break; } #endif // QT_NO_TOOLTIP #ifndef QT_NO_DRAGANDDROP case QEvent::DragEnter: { QDragEnterEvent *ev = static_cast(e); if (d->dragEnterEvent(e, ev->mimeData())) ev->acceptProposedAction(); break; } case QEvent::DragLeave: d->dragLeaveEvent(); break; case QEvent::DragMove: { QDragMoveEvent *ev = static_cast(e); if (d->dragMoveEvent(e, ev->mimeData(), matrix.map(ev->pos()))) ev->acceptProposedAction(); break; } case QEvent::Drop: { QDropEvent *ev = static_cast(e); if (d->dropEvent(ev->mimeData(), matrix.map(ev->pos()), ev->dropAction(), ev->source())) ev->acceptProposedAction(); break; } #endif #ifndef QT_NO_GRAPHICSVIEW case QEvent::GraphicsSceneMousePress: { QGraphicsSceneMouseEvent *ev = static_cast(e); d->mousePressEvent(ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), ev->screenPos()); break; } case QEvent::GraphicsSceneMouseMove: { QGraphicsSceneMouseEvent *ev = static_cast(e); d->mouseMoveEvent(ev->buttons(), matrix.map(ev->pos())); break; } case QEvent::GraphicsSceneMouseRelease: { QGraphicsSceneMouseEvent *ev = static_cast(e); d->mouseReleaseEvent(ev->button(), matrix.map(ev->pos())); break; } case QEvent::GraphicsSceneMouseDoubleClick: { QGraphicsSceneMouseEvent *ev = static_cast(e); d->mouseDoubleClickEvent(e, ev->button(), matrix.map(ev->pos())); break; } case QEvent::GraphicsSceneContextMenu: { QGraphicsSceneContextMenuEvent *ev = static_cast(e); d->contextMenuEvent(ev->screenPos(), matrix.map(ev->pos()), contextWidget); break; } case QEvent::GraphicsSceneHoverMove: { QGraphicsSceneHoverEvent *ev = static_cast(e); d->mouseMoveEvent(Qt::NoButton, matrix.map(ev->pos())); break; } case QEvent::GraphicsSceneDragEnter: { QGraphicsSceneDragDropEvent *ev = static_cast(e); if (d->dragEnterEvent(e, ev->mimeData())) ev->acceptProposedAction(); break; } case QEvent::GraphicsSceneDragLeave: d->dragLeaveEvent(); break; case QEvent::GraphicsSceneDragMove: { QGraphicsSceneDragDropEvent *ev = static_cast(e); if (d->dragMoveEvent(e, ev->mimeData(), matrix.map(ev->pos()))) ev->acceptProposedAction(); break; } case QEvent::GraphicsSceneDrop: { QGraphicsSceneDragDropEvent *ev = static_cast(e); if (d->dropEvent(ev->mimeData(), matrix.map(ev->pos()), ev->dropAction(), ev->source())) ev->accept(); break; } #endif // QT_NO_GRAPHICSVIEW #ifdef QT_KEYPAD_NAVIGATION case QEvent::EnterEditFocus: case QEvent::LeaveEditFocus: if (QApplication::keypadNavigationEnabled()) d->editFocusEvent(e); break; #endif case QEvent::ShortcutOverride: if (d->interactionFlags & Qt::TextEditable) { QKeyEvent* ke = static_cast(e); if (ke->modifiers() == Qt::NoModifier || ke->modifiers() == Qt::ShiftModifier || ke->modifiers() == Qt::KeypadModifier) { if (ke->key() < Qt::Key_Escape) { ke->accept(); } else { switch (ke->key()) { case Qt::Key_Return: case Qt::Key_Enter: case Qt::Key_Delete: case Qt::Key_Home: case Qt::Key_End: case Qt::Key_Backspace: case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_Tab: ke->accept(); default: break; } } #ifndef QT_NO_SHORTCUT } else if (ke == QKeySequence::Copy || ke == QKeySequence::Paste || ke == QKeySequence::Cut || ke == QKeySequence::Redo || ke == QKeySequence::Undo || ke == QKeySequence::MoveToNextWord || ke == QKeySequence::MoveToPreviousWord || ke == QKeySequence::MoveToStartOfDocument || ke == QKeySequence::MoveToEndOfDocument || ke == QKeySequence::SelectNextWord || ke == QKeySequence::SelectPreviousWord || ke == QKeySequence::SelectStartOfLine || ke == QKeySequence::SelectEndOfLine || ke == QKeySequence::SelectStartOfBlock || ke == QKeySequence::SelectEndOfBlock || ke == QKeySequence::SelectStartOfDocument || ke == QKeySequence::SelectEndOfDocument || ke == QKeySequence::SelectAll ) { ke->accept(); #endif } } break; case QEvent::LayoutDirectionChange: { if (contextWidget) { QTextOption opt = document()->defaultTextOption(); opt.setTextDirection(contextWidget->layoutDirection()); document()->setDefaultTextOption(opt); } } // FALL THROUGH default: break; } } bool QTextControl::event(QEvent *e) { return QObject::event(e); } void QTextControl::timerEvent(QTimerEvent *e) { Q_D(QTextControl); if (e->timerId() == d->cursorBlinkTimer.timerId()) { d->cursorOn = !d->cursorOn; if (d->cursor.hasSelection()) d->cursorOn &= (QApplication::style()->styleHint(QStyle::SH_BlinkCursorWhenTextSelected) != 0); d->repaintCursor(); } else if (e->timerId() == d->trippleClickTimer.timerId()) { d->trippleClickTimer.stop(); } } void QTextControl::setPlainText(const QString &text) { Q_D(QTextControl); d->setContent(Qt::PlainText, text); } void QTextControl::setHtml(const QString &text) { Q_D(QTextControl); d->setContent(Qt::RichText, text); } void QTextControlPrivate::keyPressEvent(QKeyEvent *e) { Q_Q(QTextControl); #ifndef QT_NO_SHORTCUT if (e == QKeySequence::SelectAll) { e->accept(); q->selectAll(); return; } #ifndef QT_NO_CLIPBOARD else if (e == QKeySequence::Copy) { e->accept(); q->copy(); return; } #endif #endif // QT_NO_SHORTCUT if (interactionFlags & Qt::TextSelectableByKeyboard && cursorMoveKeyEvent(e)) goto accept; if (interactionFlags & Qt::LinksAccessibleByKeyboard) { if ((e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter #ifdef QT_KEYPAD_NAVIGATION || e->key() == Qt::Key_Select #endif ) && cursor.hasSelection()) { e->accept(); activateLinkUnderCursor(); return; } } 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 { cursor.deletePreviousChar(); } goto accept; } #ifndef QT_NO_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) { } #ifndef QT_NO_SHORTCUT else if (e == QKeySequence::Undo) { q->undo(); } else if (e == QKeySequence::Redo) { q->redo(); } #ifndef QT_NO_CLIPBOARD else if (e == QKeySequence::Cut) { q->cut(); } else if (e == QKeySequence::Paste) { q->paste(); } #endif else if (e == QKeySequence::Delete) { cursor.deleteChar(); } else if (e == QKeySequence::DeleteEndOfWord) { cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor); cursor.removeSelectedText(); } else if (e == QKeySequence::DeleteStartOfWord) { 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 // QT_NO_SHORTCUT else { goto process; } goto accept; process: { QString text = e->text(); if (!text.isEmpty() && (text.at(0).isPrint() || text.at(0) == QLatin1Char('\t'))) { if (overwriteMode // no need to call deleteChar() if we have a selection, insertText // does it already && !cursor.hasSelection() && !cursor.atBlockEnd()) cursor.deleteChar(); cursor.insertText(text); selectionChanged(); } else { e->ignore(); return; } } accept: e->accept(); cursorOn = true; q->ensureCursorVisible(); updateCurrentCharFormat(); } QVariant QTextControl::loadResource(int type, const QUrl &name) { #ifdef QT_NO_TEXTEDIT Q_UNUSED(type); Q_UNUSED(name); #else if (QTextEdit *textEdit = qobject_cast(parent())) { QUrl resolvedName = textEdit->d_func()->resolveUrl(name); return textEdit->loadResource(type, resolvedName); } #endif return QVariant(); } void QTextControlPrivate::_q_updateBlock(const QTextBlock &block) { Q_Q(QTextControl); emit q->updateRequest(q->blockBoundingRect(block)); } QRectF QTextControlPrivate::rectForPosition(int position) const { Q_Q(const QTextControl); const QTextBlock block = doc->findBlock(position); if (!block.isValid()) return QRectF(); const QAbstractTextDocumentLayout *docLayout = doc->documentLayout(); const QTextLayout *layout = block.layout(); const QPointF layoutPos = q->blockBoundingRect(block).topLeft(); int relativePos = position - block.position(); if (preeditCursor != 0) { int preeditPos = layout->preeditAreaPosition(); if (relativePos == preeditPos) relativePos += preeditCursor; else if (relativePos > preeditPos) relativePos += layout->preeditAreaText().length(); } QTextLine line = layout->lineForTextPosition(relativePos); int cursorWidth; { bool ok = false; #ifndef QT_NO_PROPERTIES cursorWidth = docLayout->property("cursorWidth").toInt(&ok); #endif if (!ok) cursorWidth = 1; } 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()).width(QLatin1Char(' ')); // in sync with QTextLine::draw() } r = QRectF(layoutPos.x() + x, layoutPos.y() + line.y(), cursorWidth + w, line.height()); } else { r = QRectF(layoutPos.x(), layoutPos.y(), cursorWidth, 10); // #### correct height } return r; } static inline bool firstFramePosLessThanCursorPos(QTextFrame *frame, int position) { return frame->firstPosition() < position; } static inline bool cursorPosLessThanLastFramePos(int position, QTextFrame *frame) { return position < frame->lastPosition(); } static QRectF boundingRectOfFloatsInSelection(const QTextCursor &cursor) { QRectF r; QTextFrame *frame = cursor.currentFrame(); const QList children = frame->childFrames(); const QList::ConstIterator firstFrame = qLowerBound(children.constBegin(), children.constEnd(), cursor.selectionStart(), firstFramePosLessThanCursorPos); const QList::ConstIterator lastFrame = qUpperBound(children.constBegin(), children.constEnd(), cursor.selectionEnd(), cursorPosLessThanLastFramePos); for (QList::ConstIterator it = firstFrame; it != lastFrame; ++it) { if ((*it)->frameFormat().position() != QTextFrameFormat::InFlow) r |= frame->document()->documentLayout()->frameBoundingRect(*it); } return r; } QRectF QTextControl::selectionRect(const QTextCursor &cursor) const { Q_D(const QTextControl); QRectF r = d->rectForPosition(cursor.selectionStart()); if (cursor.hasComplexSelection() && cursor.currentTable()) { QTextTable *table = cursor.currentTable(); r = d->doc->documentLayout()->frameBoundingRect(table); /* int firstRow, numRows, firstColumn, numColumns; cursor.selectedTableCells(&firstRow, &numRows, &firstColumn, &numColumns); const QTextTableCell firstCell = table->cellAt(firstRow, firstColumn); const QTextTableCell lastCell = table->cellAt(firstRow + numRows - 1, firstColumn + numColumns - 1); const QAbstractTextDocumentLayout * const layout = doc->documentLayout(); QRectF tableSelRect = layout->blockBoundingRect(firstCell.firstCursorPosition().block()); for (int col = firstColumn; col < firstColumn + numColumns; ++col) { const QTextTableCell cell = table->cellAt(firstRow, col); const qreal y = layout->blockBoundingRect(cell.firstCursorPosition().block()).top(); tableSelRect.setTop(qMin(tableSelRect.top(), y)); } for (int row = firstRow; row < firstRow + numRows; ++row) { const QTextTableCell cell = table->cellAt(row, firstColumn); const qreal x = layout->blockBoundingRect(cell.firstCursorPosition().block()).left(); tableSelRect.setLeft(qMin(tableSelRect.left(), x)); } for (int col = firstColumn; col < firstColumn + numColumns; ++col) { const QTextTableCell cell = table->cellAt(firstRow + numRows - 1, col); const qreal y = layout->blockBoundingRect(cell.lastCursorPosition().block()).bottom(); tableSelRect.setBottom(qMax(tableSelRect.bottom(), y)); } for (int row = firstRow; row < firstRow + numRows; ++row) { const QTextTableCell cell = table->cellAt(row, firstColumn + numColumns - 1); const qreal x = layout->blockBoundingRect(cell.lastCursorPosition().block()).right(); tableSelRect.setRight(qMax(tableSelRect.right(), x)); } r = tableSelRect.toRect(); */ } else if (cursor.hasSelection()) { const int position = cursor.selectionStart(); const int anchor = cursor.selectionEnd(); const QTextBlock posBlock = d->doc->findBlock(position); const QTextBlock anchorBlock = d->doc->findBlock(anchor); if (posBlock == anchorBlock && posBlock.isValid() && posBlock.layout()->lineCount()) { const QTextLine posLine = posBlock.layout()->lineForTextPosition(position - posBlock.position()); const QTextLine anchorLine = anchorBlock.layout()->lineForTextPosition(anchor - anchorBlock.position()); const int firstLine = qMin(posLine.lineNumber(), anchorLine.lineNumber()); const int lastLine = qMax(posLine.lineNumber(), anchorLine.lineNumber()); const QTextLayout *layout = posBlock.layout(); r = QRectF(); for (int i = firstLine; i <= lastLine; ++i) { r |= layout->lineAt(i).rect(); r |= layout->lineAt(i).naturalTextRect(); // might be bigger in the case of wrap not enabled } r.translate(blockBoundingRect(posBlock).topLeft()); } else { QRectF anchorRect = d->rectForPosition(cursor.selectionEnd()); r |= anchorRect; r |= boundingRectOfFloatsInSelection(cursor); QRectF frameRect(d->doc->documentLayout()->frameBoundingRect(cursor.currentFrame())); r.setLeft(frameRect.left()); r.setRight(frameRect.right()); } if (r.isValid()) r.adjust(-1, -1, 1, 1); } return r; } QRectF QTextControl::selectionRect() const { Q_D(const QTextControl); return selectionRect(d->cursor); } void QTextControlPrivate::mousePressEvent(Qt::MouseButton button, const QPointF &pos, Qt::KeyboardModifiers modifiers, Qt::MouseButtons buttons, const QPoint &globalPos) { Q_Q(QTextControl); if (interactionFlags & Qt::LinksAccessibleByMouse) { anchorOnMousePress = q->anchorAt(pos); if (cursorIsFocusIndicator) { cursorIsFocusIndicator = false; repaintSelection(); cursor.clearSelection(); } } if (!(button & Qt::LeftButton)) return; if (!((interactionFlags & Qt::TextSelectableByMouse) || (interactionFlags & Qt::TextEditable))) return; cursorIsFocusIndicator = false; const QTextCursor oldSelection = cursor; const int oldCursorPos = cursor.position(); mousePressed = true; #ifndef QT_NO_DRAGANDDROP mightStartDrag = false; #endif if (trippleClickTimer.isActive() && ((pos - trippleClickPoint).toPoint().manhattanLength() < QApplication::startDragDistance())) { cursor.movePosition(QTextCursor::StartOfBlock); cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); selectedBlockOnTrippleClick = cursor; anchorOnMousePress = QString(); trippleClickTimer.stop(); } else { int cursorPos = q->hitTest(pos, Qt::FuzzyHit); if (cursorPos == -1) return; #if !defined(QT_NO_IM) QTextLayout *layout = cursor.block().layout(); if (contextWidget && layout && !layout->preeditAreaText().isEmpty()) { QInputContext *ctx = inputContext(); if (ctx) { QMouseEvent ev(QEvent::MouseButtonPress, contextWidget->mapFromGlobal(globalPos), globalPos, button, buttons, modifiers); ctx->mouseHandler(cursorPos - cursor.position(), &ev); } if (!layout->preeditAreaText().isEmpty()) return; } #endif if (modifiers == Qt::ShiftModifier) { if (selectedBlockOnTrippleClick.hasSelection()) extendBlockwiseSelection(cursorPos); else if (selectedWordOnDoubleClick.hasSelection()) extendWordwiseSelection(cursorPos, pos.x()); else setCursorPosition(cursorPos, QTextCursor::KeepAnchor); } else { if (cursor.hasSelection() && !cursorIsFocusIndicator && cursorPos >= cursor.selectionStart() && cursorPos <= cursor.selectionEnd() && q->hitTest(pos, Qt::ExactHit) != -1) { #ifndef QT_NO_DRAGANDDROP mightStartDrag = true; dragStartPos = pos.toPoint(); #endif return; } setCursorPosition(cursorPos); } } if (interactionFlags & Qt::TextEditable) { q->ensureCursorVisible(); if (cursor.position() != oldCursorPos) emit q->cursorPositionChanged(); _q_updateCurrentCharFormatAndSelection(); } else { if (cursor.position() != oldCursorPos) emit q->cursorPositionChanged(); selectionChanged(); } repaintOldAndNewSelection(oldSelection); hadSelectionOnMousePress = cursor.hasSelection(); } void QTextControlPrivate::mouseMoveEvent(Qt::MouseButtons buttons, const QPointF &mousePos) { Q_Q(QTextControl); if (interactionFlags & Qt::LinksAccessibleByMouse) { QString anchor = q->anchorAt(mousePos); if (anchor != highlightedAnchor) { highlightedAnchor = anchor; emit q->linkHovered(anchor); } } if (!(buttons & Qt::LeftButton)) return; if (!((interactionFlags & Qt::TextSelectableByMouse) || (interactionFlags & Qt::TextEditable))) return; if (!(mousePressed || selectedWordOnDoubleClick.hasSelection() || selectedBlockOnTrippleClick.hasSelection())) return; const QTextCursor oldSelection = cursor; const int oldCursorPos = cursor.position(); if (mightStartDrag) { if ((mousePos.toPoint() - dragStartPos).manhattanLength() > QApplication::startDragDistance()) startDrag(); return; } const qreal mouseX = qreal(mousePos.x()); #if !defined(QT_NO_IM) QTextLayout *layout = cursor.block().layout(); if (layout && !layout->preeditAreaText().isEmpty()) return; #endif int newCursorPos = q->hitTest(mousePos, Qt::FuzzyHit); if (newCursorPos == -1) return; if (selectedBlockOnTrippleClick.hasSelection()) extendBlockwiseSelection(newCursorPos); else if (selectedWordOnDoubleClick.hasSelection()) extendWordwiseSelection(newCursorPos, mouseX); else setCursorPosition(newCursorPos, QTextCursor::KeepAnchor); if (interactionFlags & Qt::TextEditable) { // don't call ensureVisible for the visible cursor to avoid jumping // scrollbars. the autoscrolling ensures smooth scrolling if necessary. //q->ensureCursorVisible(); if (cursor.position() != oldCursorPos) emit q->cursorPositionChanged(); _q_updateCurrentCharFormatAndSelection(); if (QInputContext *ic = inputContext()) { ic->update(); } } else { //emit q->visibilityRequest(QRectF(mousePos, QSizeF(1, 1))); if (cursor.position() != oldCursorPos) emit q->cursorPositionChanged(); } selectionChanged(true); repaintOldAndNewSelection(oldSelection); } void QTextControlPrivate::mouseReleaseEvent(Qt::MouseButton button, const QPointF &pos) { Q_Q(QTextControl); const QTextCursor oldSelection = cursor; const int oldCursorPos = cursor.position(); #ifndef QT_NO_DRAGANDDROP if (mightStartDrag && (button & Qt::LeftButton)) { mousePressed = false; setCursorPosition(pos); cursor.clearSelection(); selectionChanged(); } #endif if (mousePressed) { mousePressed = false; #ifndef QT_NO_CLIPBOARD if (interactionFlags & Qt::TextSelectableByMouse) { setClipboardSelection(); selectionChanged(true); } } else if (button == Qt::MidButton && (interactionFlags & Qt::TextEditable) && QApplication::clipboard()->supportsSelection()) { setCursorPosition(pos); const QMimeData *md = QApplication::clipboard()->mimeData(QClipboard::Selection); if (md) q->insertFromMimeData(md); #endif } repaintOldAndNewSelection(oldSelection); if (cursor.position() != oldCursorPos) emit q->cursorPositionChanged(); if (interactionFlags & Qt::LinksAccessibleByMouse) { if (!(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 QTextControlPrivate::mouseDoubleClickEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos) { Q_Q(QTextControl); if (button != Qt::LeftButton || !(interactionFlags & Qt::TextSelectableByMouse)) { e->ignore(); return; } #if !defined(QT_NO_IM) QTextLayout *layout = cursor.block().layout(); if (layout && !layout->preeditAreaText().isEmpty()) return; #endif #ifndef QT_NO_DRAGANDDROP mightStartDrag = false; #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; trippleClickPoint = pos; trippleClickTimer.start(QApplication::doubleClickInterval(), q); if (doEmit) { selectionChanged(); #ifndef QT_NO_CLIPBOARD setClipboardSelection(); #endif emit q->cursorPositionChanged(); } } void QTextControlPrivate::contextMenuEvent(const QPoint &screenPos, const QPointF &docPos, QWidget *contextWidget) { #ifdef QT_NO_CONTEXTMENU Q_UNUSED(screenPos); Q_UNUSED(docPos); Q_UNUSED(contextWidget); #else Q_Q(QTextControl); if (!hasFocus) return; QMenu *menu = q->createStandardContextMenu(docPos, contextWidget); if (!menu) return; menu->exec(screenPos); delete menu; #endif } bool QTextControlPrivate::dragEnterEvent(QEvent *e, const QMimeData *mimeData) { Q_Q(QTextControl); if (!(interactionFlags & Qt::TextEditable) || !q->canInsertFromMimeData(mimeData)) { e->ignore(); return false; } dndFeedbackCursor = QTextCursor(); return true; // accept proposed action } void QTextControlPrivate::dragLeaveEvent() { Q_Q(QTextControl); const QRectF crect = q->cursorRect(dndFeedbackCursor); dndFeedbackCursor = QTextCursor(); if (crect.isValid()) emit q->updateRequest(crect); } bool QTextControlPrivate::dragMoveEvent(QEvent *e, const QMimeData *mimeData, const QPointF &pos) { Q_Q(QTextControl); if (!(interactionFlags & Qt::TextEditable) || !q->canInsertFromMimeData(mimeData)) { e->ignore(); return false; } const int cursorPos = q->hitTest(pos, Qt::FuzzyHit); if (cursorPos != -1) { QRectF crect = q->cursorRect(dndFeedbackCursor); if (crect.isValid()) emit q->updateRequest(crect); dndFeedbackCursor = cursor; dndFeedbackCursor.setPosition(cursorPos); crect = q->cursorRect(dndFeedbackCursor); emit q->updateRequest(crect); } return true; // accept proposed action } bool QTextControlPrivate::dropEvent(const QMimeData *mimeData, const QPointF &pos, Qt::DropAction dropAction, QWidget *source) { Q_Q(QTextControl); dndFeedbackCursor = QTextCursor(); if (!(interactionFlags & Qt::TextEditable) || !q->canInsertFromMimeData(mimeData)) return false; repaintSelection(); QTextCursor insertionCursor = q->cursorForPosition(pos); insertionCursor.beginEditBlock(); if (dropAction == Qt::MoveAction && source == contextWidget) cursor.removeSelectedText(); cursor = insertionCursor; q->insertFromMimeData(mimeData); insertionCursor.endEditBlock(); q->ensureCursorVisible(); return true; // accept proposed action } void QTextControlPrivate::inputMethodEvent(QInputMethodEvent *e) { Q_Q(QTextControl); if (!(interactionFlags & Qt::TextEditable) || cursor.isNull()) { e->ignore(); return; } bool isGettingInput = !e->commitString().isEmpty() || !e->preeditString().isEmpty() || e->replacementLength() > 0; if (isGettingInput) { cursor.beginEditBlock(); cursor.removeSelectedText(); } // insert commit string if (!e->commitString().isEmpty() || e->replacementLength()) { 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); q->ensureCursorVisible(); repaintOldAndNewSelection(oldCursor); } } QTextBlock block = cursor.block(); QTextLayout *layout = block.layout(); layout->setPreeditArea(cursor.position() - block.position(), e->preeditString()); QList overrides; preeditCursor = e->preeditString().length(); hideCursor = false; for (int i = 0; i < e->attributes().size(); ++i) { const QInputMethodEvent::Attribute &a = e->attributes().at(i); if (a.type == QInputMethodEvent::Cursor) { preeditCursor = a.start; hideCursor = !a.length; } else if (a.type == QInputMethodEvent::TextFormat) { QTextCharFormat f = qvariant_cast(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->setAdditionalFormats(overrides); if (isGettingInput) cursor.endEditBlock(); } QVariant QTextControl::inputMethodQuery(Qt::InputMethodQuery property) const { Q_D(const QTextControl); QTextBlock block = d->cursor.block(); switch(property) { case Qt::ImMicroFocus: return cursorRect(); case Qt::ImFont: return QVariant(d->cursor.charFormat().font()); case Qt::ImCursorPosition: 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(qBound(0, d->cursor.anchor() - block.position(), block.length())); default: return QVariant(); } } void QTextControl::setFocus(bool focus, Qt::FocusReason reason) { QFocusEvent ev(focus ? QEvent::FocusIn : QEvent::FocusOut, reason); processEvent(&ev); } void QTextControlPrivate::focusEvent(QFocusEvent *e) { Q_Q(QTextControl); emit q->updateRequest(q->selectionRect()); if (e->gotFocus()) { #ifdef QT_KEYPAD_NAVIGATION if (!QApplication::keypadNavigationEnabled() || (hasEditFocus && e->reason() == Qt::PopupFocusReason)) { #endif cursorOn = (interactionFlags & Qt::TextSelectableByKeyboard); if (interactionFlags & Qt::TextEditable) { setBlinkingCursorEnabled(true); } #ifdef QT_KEYPAD_NAVIGATION } #endif } else { setBlinkingCursorEnabled(false); if (cursorIsFocusIndicator && e->reason() != Qt::ActiveWindowFocusReason && e->reason() != Qt::PopupFocusReason && cursor.hasSelection()) { cursor.clearSelection(); } } hasFocus = e->gotFocus(); } QString QTextControlPrivate::anchorForCursor(const QTextCursor &anchorCursor) const { if (anchorCursor.hasSelection()) { QTextCursor cursor = anchorCursor; if (cursor.selectionStart() != cursor.position()) cursor.setPosition(cursor.selectionStart()); cursor.movePosition(QTextCursor::NextCharacter); QTextCharFormat fmt = cursor.charFormat(); if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) return fmt.stringProperty(QTextFormat::AnchorHref); } return QString(); } #ifdef QT_KEYPAD_NAVIGATION void QTextControlPrivate::editFocusEvent(QEvent *e) { Q_Q(QTextControl); if (QApplication::keypadNavigationEnabled()) { if (e->type() == QEvent::EnterEditFocus && interactionFlags & Qt::TextEditable) { const QTextCursor oldSelection = cursor; const int oldCursorPos = cursor.position(); const bool moved = cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); q->ensureCursorVisible(); if (moved) { if (cursor.position() != oldCursorPos) emit q->cursorPositionChanged(); emit q->microFocusChanged(); } selectionChanged(); repaintOldAndNewSelection(oldSelection); setBlinkingCursorEnabled(true); } else setBlinkingCursorEnabled(false); } hasEditFocus = e->type() == QEvent::EnterEditFocus ? true : false; } #endif #ifndef QT_NO_CONTEXTMENU QMenu *QTextControl::createStandardContextMenu(const QPointF &pos, QWidget *parent) { Q_D(QTextControl); const bool showTextSelectionActions = d->interactionFlags & (Qt::TextEditable | Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse); d->linkToCopy = QString(); if (!pos.isNull()) d->linkToCopy = anchorAt(pos); if (d->linkToCopy.isEmpty() && !showTextSelectionActions) return 0; QMenu *menu = new QMenu(parent); QAction *a; if (d->interactionFlags & Qt::TextEditable) { a = menu->addAction(tr("&Undo") + ACCEL_KEY(QKeySequence::Undo), this, SLOT(undo())); a->setEnabled(d->doc->isUndoAvailable()); a = menu->addAction(tr("&Redo") + ACCEL_KEY(QKeySequence::Redo), this, SLOT(redo())); a->setEnabled(d->doc->isRedoAvailable()); menu->addSeparator(); a = menu->addAction(tr("Cu&t") + ACCEL_KEY(QKeySequence::Cut), this, SLOT(cut())); a->setEnabled(d->cursor.hasSelection()); } if (showTextSelectionActions) { a = menu->addAction(tr("&Copy") + ACCEL_KEY(QKeySequence::Copy), this, SLOT(copy())); a->setEnabled(d->cursor.hasSelection()); } if ((d->interactionFlags & Qt::LinksAccessibleByKeyboard) || (d->interactionFlags & Qt::LinksAccessibleByMouse)) { a = menu->addAction(tr("Copy &Link Location"), this, SLOT(_q_copyLink())); a->setEnabled(!d->linkToCopy.isEmpty()); } if (d->interactionFlags & Qt::TextEditable) { #if !defined(QT_NO_CLIPBOARD) a = menu->addAction(tr("&Paste") + ACCEL_KEY(QKeySequence::Paste), this, SLOT(paste())); a->setEnabled(canPaste()); #endif a = menu->addAction(tr("Delete"), this, SLOT(_q_deleteSelected())); a->setEnabled(d->cursor.hasSelection()); } if (showTextSelectionActions) { menu->addSeparator(); a = menu->addAction(tr("Select All") + ACCEL_KEY(QKeySequence::SelectAll), this, SLOT(selectAll())); a->setEnabled(!d->doc->isEmpty()); } #if !defined(QT_NO_IM) if (d->contextWidget) { QInputContext *qic = d->inputContext(); if (qic) { QList imActions = qic->actions(); for (int i = 0; i < imActions.size(); ++i) menu->addAction(imActions.at(i)); } } #endif #if defined(Q_WS_WIN) if ((d->interactionFlags & Qt::TextEditable) && qt_use_rtl_extensions) { #else if (d->interactionFlags & Qt::TextEditable) { #endif menu->addSeparator(); QUnicodeControlCharacterMenu *ctrlCharacterMenu = new QUnicodeControlCharacterMenu(this, menu); menu->addMenu(ctrlCharacterMenu); } return menu; } #endif // QT_NO_CONTEXTMENU QTextCursor QTextControl::cursorForPosition(const QPointF &pos) const { Q_D(const QTextControl); int cursorPos = hitTest(pos, Qt::FuzzyHit); if (cursorPos == -1) cursorPos = 0; QTextCursor c(d->doc); c.setPosition(cursorPos); return c; } QRectF QTextControl::cursorRect(const QTextCursor &cursor) const { Q_D(const QTextControl); if (cursor.isNull()) return QRectF(); return d->rectForPosition(cursor.position()); } QRectF QTextControl::cursorRect() const { Q_D(const QTextControl); return cursorRect(d->cursor); } QRectF QTextControlPrivate::cursorRectPlusUnicodeDirectionMarkers(const QTextCursor &cursor) const { if (cursor.isNull()) return QRectF(); return rectForPosition(cursor.position()).adjusted(-4, 0, 4, 0); } QString QTextControl::anchorAt(const QPointF &pos) const { Q_D(const QTextControl); return d->doc->documentLayout()->anchorAt(pos); } QString QTextControl::anchorAtCursor() const { Q_D(const QTextControl); return d->anchorForCursor(d->cursor); } bool QTextControl::overwriteMode() const { Q_D(const QTextControl); return d->overwriteMode; } void QTextControl::setOverwriteMode(bool overwrite) { Q_D(QTextControl); d->overwriteMode = overwrite; } int QTextControl::cursorWidth() const { #ifndef QT_NO_PROPERTIES Q_D(const QTextControl); return d->doc->documentLayout()->property("cursorWidth").toInt(); #else return 1; #endif } void QTextControl::setCursorWidth(int width) { Q_D(QTextControl); #ifdef QT_NO_PROPERTIES Q_UNUSED(width); #else if (width == -1) width = QApplication::style()->pixelMetric(QStyle::PM_TextCursorWidth); d->doc->documentLayout()->setProperty("cursorWidth", width); #endif d->repaintCursor(); } bool QTextControl::acceptRichText() const { Q_D(const QTextControl); return d->acceptRichText; } void QTextControl::setAcceptRichText(bool accept) { Q_D(QTextControl); d->acceptRichText = accept; } #ifndef QT_NO_TEXTEDIT void QTextControl::setExtraSelections(const QList &selections) { Q_D(QTextControl); QHash hash; for (int i = 0; i < d->extraSelections.count(); ++i) { const QAbstractTextDocumentLayout::Selection &esel = d->extraSelections.at(i); hash.insertMulti(esel.cursor.anchor(), i); } for (int i = 0; i < selections.count(); ++i) { const QTextEdit::ExtraSelection &sel = selections.at(i); QHash::iterator it = hash.find(sel.cursor.anchor()); if (it != hash.end()) { const QAbstractTextDocumentLayout::Selection &esel = d->extraSelections.at(it.value()); if (esel.cursor.position() == sel.cursor.position() && esel.format == sel.format) { hash.erase(it); continue; } } QRectF r = selectionRect(sel.cursor); if (sel.format.boolProperty(QTextFormat::FullWidthSelection)) { r.setLeft(0); r.setWidth(qreal(INT_MAX)); } emit updateRequest(r); } for (QHash::iterator it = hash.begin(); it != hash.end(); ++it) { const QAbstractTextDocumentLayout::Selection &esel = d->extraSelections.at(it.value()); QRectF r = selectionRect(esel.cursor); if (esel.format.boolProperty(QTextFormat::FullWidthSelection)) { r.setLeft(0); r.setWidth(qreal(INT_MAX)); } emit updateRequest(r); } d->extraSelections.resize(selections.count()); for (int i = 0; i < selections.count(); ++i) { d->extraSelections[i].cursor = selections.at(i).cursor; d->extraSelections[i].format = selections.at(i).format; } } QList QTextControl::extraSelections() const { Q_D(const QTextControl); QList selections; for (int i = 0; i < d->extraSelections.count(); ++i) { QTextEdit::ExtraSelection sel; sel.cursor = d->extraSelections.at(i).cursor; sel.format = d->extraSelections.at(i).format; selections.append(sel); } return selections; } #endif // QT_NO_TEXTEDIT void QTextControl::setTextWidth(qreal width) { Q_D(QTextControl); d->doc->setTextWidth(width); } qreal QTextControl::textWidth() const { Q_D(const QTextControl); return d->doc->textWidth(); } QSizeF QTextControl::size() const { Q_D(const QTextControl); return d->doc->size(); } void QTextControl::setOpenExternalLinks(bool open) { Q_D(QTextControl); d->openExternalLinks = open; } bool QTextControl::openExternalLinks() const { Q_D(const QTextControl); return d->openExternalLinks; } void QTextControl::moveCursor(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode) { Q_D(QTextControl); const QTextCursor oldSelection = d->cursor; const bool moved = d->cursor.movePosition(op, mode); d->_q_updateCurrentCharFormatAndSelection(); ensureCursorVisible(); d->repaintOldAndNewSelection(oldSelection); if (moved) emit cursorPositionChanged(); } bool QTextControl::canPaste() const { #ifndef QT_NO_CLIPBOARD Q_D(const QTextControl); if (d->interactionFlags & Qt::TextEditable) { const QMimeData *md = QApplication::clipboard()->mimeData(); return md && canInsertFromMimeData(md); } #endif return false; } void QTextControl::setCursorIsFocusIndicator(bool b) { Q_D(QTextControl); d->cursorIsFocusIndicator = b; d->repaintCursor(); } bool QTextControl::cursorIsFocusIndicator() const { Q_D(const QTextControl); return d->cursorIsFocusIndicator; } #ifndef QT_NO_PRINTER void QTextControl::print(QPrinter *printer) const { #ifndef QT_NO_PRINTER Q_D(const QTextControl); if (!printer || !printer->isValid()) return; QTextDocument *tempDoc = 0; const QTextDocument *doc = d->doc; if (printer->printRange() == QPrinter::Selection) { if (!d->cursor.hasSelection()) return; tempDoc = new QTextDocument(const_cast(doc)); tempDoc->setMetaInformation(QTextDocument::DocumentTitle, doc->metaInformation(QTextDocument::DocumentTitle)); tempDoc->setPageSize(doc->pageSize()); tempDoc->setDefaultFont(doc->defaultFont()); tempDoc->setUseDesignMetrics(doc->useDesignMetrics()); QTextCursor(tempDoc).insertFragment(d->cursor.selection()); doc = tempDoc; } doc->print(printer); delete tempDoc; #endif } #endif // QT_NO_PRINTER QMimeData *QTextControl::createMimeDataFromSelection() const { Q_D(const QTextControl); const QTextDocumentFragment fragment(d->cursor); return new QTextEditMimeData(fragment); } bool QTextControl::canInsertFromMimeData(const QMimeData *source) const { Q_D(const QTextControl); if (d->acceptRichText) return (source->hasText() && !source->text().isEmpty()) || source->hasHtml() || source->hasFormat(QLatin1String("application/x-qrichtext")) || source->hasFormat(QLatin1String("application/x-qt-richtext")); else return source->hasText() && !source->text().isEmpty(); } void QTextControl::insertFromMimeData(const QMimeData *source) { Q_D(QTextControl); if (!(d->interactionFlags & Qt::TextEditable) || !source) return; bool hasData = false; QTextDocumentFragment fragment; #ifndef QT_NO_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). QString richtext = QString::fromUtf8(source->data(QLatin1String("application/x-qrichtext"))); richtext.prepend(QLatin1String("")); 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 // QT_NO_TEXTHTMLPARSER if (hasData) d->cursor.insertFragment(fragment); ensureCursorVisible(); } bool QTextControl::findNextPrevAnchor(const QTextCursor &startCursor, bool next, QTextCursor &newAnchor) { Q_D(QTextControl); int anchorStart = -1; QString anchorHref; int anchorEnd = -1; if (next) { const int startPos = startCursor.selectionEnd(); QTextBlock block = d->doc->findBlock(startPos); QTextBlock::Iterator it = block.begin(); while (!it.atEnd() && it.fragment().position() < startPos) ++it; while (block.isValid()) { anchorStart = -1; // find next anchor for (; !it.atEnd(); ++it) { const QTextFragment fragment = it.fragment(); const QTextCharFormat fmt = fragment.charFormat(); if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) { anchorStart = fragment.position(); anchorHref = fmt.anchorHref(); break; } } if (anchorStart != -1) { anchorEnd = -1; // find next non-anchor fragment for (; !it.atEnd(); ++it) { const QTextFragment fragment = it.fragment(); const QTextCharFormat fmt = fragment.charFormat(); if (!fmt.isAnchor() || fmt.anchorHref() != anchorHref) { anchorEnd = fragment.position(); break; } } if (anchorEnd == -1) anchorEnd = block.position() + block.length() - 1; // make found selection break; } block = block.next(); it = block.begin(); } } else { int startPos = startCursor.selectionStart(); if (startPos > 0) --startPos; QTextBlock block = d->doc->findBlock(startPos); QTextBlock::Iterator blockStart = block.begin(); QTextBlock::Iterator it = block.end(); if (startPos == block.position()) { it = block.begin(); } else { do { if (it == blockStart) { it = QTextBlock::Iterator(); block = QTextBlock(); } else { --it; } } while (!it.atEnd() && it.fragment().position() + it.fragment().length() - 1 > startPos); } while (block.isValid()) { anchorStart = -1; if (!it.atEnd()) { do { const QTextFragment fragment = it.fragment(); const QTextCharFormat fmt = fragment.charFormat(); if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) { anchorStart = fragment.position() + fragment.length(); anchorHref = fmt.anchorHref(); break; } if (it == blockStart) it = QTextBlock::Iterator(); else --it; } while (!it.atEnd()); } if (anchorStart != -1 && !it.atEnd()) { anchorEnd = -1; do { const QTextFragment fragment = it.fragment(); const QTextCharFormat fmt = fragment.charFormat(); if (!fmt.isAnchor() || fmt.anchorHref() != anchorHref) { anchorEnd = fragment.position() + fragment.length(); break; } if (it == blockStart) it = QTextBlock::Iterator(); else --it; } while (!it.atEnd()); if (anchorEnd == -1) anchorEnd = qMax(0, block.position()); break; } block = block.previous(); it = block.end(); if (it != block.begin()) --it; blockStart = block.begin(); } } if (anchorStart != -1 && anchorEnd != -1) { newAnchor = d->cursor; newAnchor.setPosition(anchorStart); newAnchor.setPosition(anchorEnd, QTextCursor::KeepAnchor); return true; } return false; } void QTextControlPrivate::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); #ifndef QT_NO_DESKTOPSERVICES if (openExternalLinks) QDesktopServices::openUrl(href); else #endif emit q_func()->linkActivated(href); } #ifndef QT_NO_TOOLTIP void QTextControlPrivate::showToolTip(const QPoint &globalPos, const QPointF &pos, QWidget *contextWidget) { const QString toolTip = q_func()->cursorForPosition(pos).charFormat().toolTip(); if (toolTip.isEmpty()) return; QToolTip::showText(globalPos, toolTip, contextWidget); } #endif // QT_NO_TOOLTIP bool QTextControl::setFocusToNextOrPreviousAnchor(bool next) { Q_D(QTextControl); if (!(d->interactionFlags & Qt::LinksAccessibleByKeyboard)) return false; QRectF crect = selectionRect(); emit updateRequest(crect); // If we don't have a current anchor, we start from the start/end if (!d->cursor.hasSelection()) { d->cursor = QTextCursor(d->doc); if (next) d->cursor.movePosition(QTextCursor::Start); else d->cursor.movePosition(QTextCursor::End); } QTextCursor newAnchor; if (findNextPrevAnchor(d->cursor, next, newAnchor)) { d->cursor = newAnchor; d->cursorIsFocusIndicator = true; } else { d->cursor.clearSelection(); } if (d->cursor.hasSelection()) { crect = selectionRect(); emit updateRequest(crect); emit visibilityRequest(crect); return true; } else { return false; } } bool QTextControl::setFocusToAnchor(const QTextCursor &newCursor) { Q_D(QTextControl); if (!(d->interactionFlags & Qt::LinksAccessibleByKeyboard)) return false; // Verify that this is an anchor. const QString anchorHref = d->anchorForCursor(newCursor); if (anchorHref.isEmpty()) return false; // and process it QRectF crect = selectionRect(); emit updateRequest(crect); d->cursor.setPosition(newCursor.selectionStart()); d->cursor.setPosition(newCursor.selectionEnd(), QTextCursor::KeepAnchor); d->cursorIsFocusIndicator = true; crect = selectionRect(); emit updateRequest(crect); emit visibilityRequest(crect); return true; } void QTextControl::setTextInteractionFlags(Qt::TextInteractionFlags flags) { Q_D(QTextControl); if (flags == d->interactionFlags) return; d->interactionFlags = flags; if (d->hasFocus) d->setBlinkingCursorEnabled(flags & Qt::TextEditable); } Qt::TextInteractionFlags QTextControl::textInteractionFlags() const { Q_D(const QTextControl); return d->interactionFlags; } void QTextControl::mergeCurrentCharFormat(const QTextCharFormat &modifier) { Q_D(QTextControl); d->cursor.mergeCharFormat(modifier); d->updateCurrentCharFormat(); } void QTextControl::setCurrentCharFormat(const QTextCharFormat &format) { Q_D(QTextControl); d->cursor.setCharFormat(format); d->updateCurrentCharFormat(); } QTextCharFormat QTextControl::currentCharFormat() const { Q_D(const QTextControl); return d->cursor.charFormat(); } void QTextControl::insertPlainText(const QString &text) { Q_D(QTextControl); d->cursor.insertText(text); } #ifndef QT_NO_TEXTHTMLPARSER void QTextControl::insertHtml(const QString &text) { Q_D(QTextControl); d->cursor.insertHtml(text); } #endif // QT_NO_TEXTHTMLPARSER QPointF QTextControl::anchorPosition(const QString &name) const { Q_D(const QTextControl); if (name.isEmpty()) return QPointF(); QRectF r; for (QTextBlock block = d->doc->begin(); block.isValid(); block = block.next()) { QTextCharFormat format = block.charFormat(); if (format.isAnchor() && format.anchorNames().contains(name)) { r = d->rectForPosition(block.position()); break; } for (QTextBlock::Iterator it = block.begin(); !it.atEnd(); ++it) { QTextFragment fragment = it.fragment(); format = fragment.charFormat(); if (format.isAnchor() && format.anchorNames().contains(name)) { r = d->rectForPosition(fragment.position()); block = QTextBlock(); break; } } } if (!r.isValid()) return QPointF(); return QPointF(0, r.top()); } void QTextControl::adjustSize() { Q_D(QTextControl); d->doc->adjustSize(); } bool QTextControl::find(const QString &exp, QTextDocument::FindFlags options) { Q_D(QTextControl); QTextCursor search = d->doc->find(exp, d->cursor, options); if (search.isNull()) return false; setTextCursor(search); return true; } void QTextControlPrivate::append(const QString &text, Qt::TextFormat format) { QTextCursor tmp(doc); tmp.beginEditBlock(); tmp.movePosition(QTextCursor::End); if (!doc->isEmpty()) tmp.insertBlock(cursor.blockFormat(), cursor.charFormat()); else tmp.setCharFormat(cursor.charFormat()); // preserve the char format QTextCharFormat oldCharFormat = cursor.charFormat(); #ifndef QT_NO_TEXTHTMLPARSER if (format == Qt::RichText || (format == Qt::AutoText && Qt::mightBeRichText(text))) { tmp.insertHtml(text); } else { tmp.insertText(text); } #else tmp.insertText(text); #endif // QT_NO_TEXTHTMLPARSER if (!cursor.hasSelection()) cursor.setCharFormat(oldCharFormat); tmp.endEditBlock(); } void QTextControl::append(const QString &text) { Q_D(QTextControl); d->append(text, Qt::AutoText); } void QTextControl::appendHtml(const QString &html) { Q_D(QTextControl); d->append(html, Qt::RichText); } void QTextControl::appendPlainText(const QString &text) { Q_D(QTextControl); d->append(text, Qt::PlainText); } void QTextControl::ensureCursorVisible() { Q_D(QTextControl); QRectF crect = d->rectForPosition(d->cursor.position()).adjusted(-5, 0, 5, 0); emit visibilityRequest(crect); emit microFocusChanged(); } QPalette QTextControl::palette() const { Q_D(const QTextControl); return d->palette; } void QTextControl::setPalette(const QPalette &pal) { Q_D(QTextControl); d->palette = pal; } QAbstractTextDocumentLayout::PaintContext QTextControl::getPaintContext(QWidget *widget) const { Q_D(const QTextControl); QAbstractTextDocumentLayout::PaintContext ctx; ctx.selections = d->extraSelections; ctx.palette = d->palette; if (d->cursorOn && d->isEnabled) { if (d->hideCursor) ctx.cursorPosition = -1; else if (d->preeditCursor != 0) ctx.cursorPosition = - (d->preeditCursor + 2); else ctx.cursorPosition = d->cursor.position(); } if (!d->dndFeedbackCursor.isNull()) ctx.cursorPosition = d->dndFeedbackCursor.position(); #ifdef QT_KEYPAD_NAVIGATION if (!QApplication::keypadNavigationEnabled() || d->hasEditFocus) #endif if (d->cursor.hasSelection()) { QAbstractTextDocumentLayout::Selection selection; selection.cursor = d->cursor; if (d->cursorIsFocusIndicator) { QStyleOption opt; opt.palette = ctx.palette; QStyleHintReturnVariant ret; QStyle *style = QApplication::style(); if (widget) style = widget->style(); style->styleHint(QStyle::SH_TextControl_FocusIndicatorTextCharFormat, &opt, widget, &ret); selection.format = qVariantValue(ret.variant).toCharFormat(); } else { QPalette::ColorGroup cg = d->hasFocus ? QPalette::Active : QPalette::Inactive; selection.format.setBackground(ctx.palette.brush(cg, QPalette::Highlight)); selection.format.setForeground(ctx.palette.brush(cg, QPalette::HighlightedText)); QStyleOption opt; QStyle *style = QApplication::style(); if (widget) { opt.initFrom(widget); style = widget->style(); } if (style->styleHint(QStyle::SH_RichText_FullWidthSelection, &opt, widget)) selection.format.setProperty(QTextFormat::FullWidthSelection, true); } ctx.selections.append(selection); } return ctx; } void QTextControl::drawContents(QPainter *p, const QRectF &rect, QWidget *widget) { Q_D(QTextControl); p->save(); QAbstractTextDocumentLayout::PaintContext ctx = getPaintContext(widget); if (rect.isValid()) p->setClipRect(rect, Qt::IntersectClip); ctx.clip = rect; d->doc->documentLayout()->draw(p, ctx); p->restore(); } void QTextControlPrivate::_q_copyLink() { #ifndef QT_NO_CLIPBOARD QMimeData *md = new QMimeData; md->setText(linkToCopy); QApplication::clipboard()->setMimeData(md); #endif } QInputContext *QTextControlPrivate::inputContext() { QInputContext *ctx = contextWidget->inputContext(); if (!ctx && contextWidget->parentWidget()) ctx = contextWidget->parentWidget()->inputContext(); return ctx; } int QTextControl::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const { Q_D(const QTextControl); return d->doc->documentLayout()->hitTest(point, accuracy); } QRectF QTextControl::blockBoundingRect(const QTextBlock &block) const { Q_D(const QTextControl); return d->doc->documentLayout()->blockBoundingRect(block); } #ifndef QT_NO_CONTEXTMENU #define NUM_CONTROL_CHARACTERS 10 const struct QUnicodeControlCharacter { const char *text; ushort character; } qt_controlCharacters[NUM_CONTROL_CHARACTERS] = { { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRM Left-to-right mark"), 0x200e }, { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLM Right-to-left mark"), 0x200f }, { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "ZWJ Zero width joiner"), 0x200d }, { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "ZWNJ Zero width non-joiner"), 0x200c }, { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "ZWSP Zero width space"), 0x200b }, { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRE Start of left-to-right embedding"), 0x202a }, { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLE Start of right-to-left embedding"), 0x202b }, { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRO Start of left-to-right override"), 0x202d }, { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLO Start of right-to-left override"), 0x202e }, { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "PDF Pop directional formatting"), 0x202c }, }; QUnicodeControlCharacterMenu::QUnicodeControlCharacterMenu(QObject *_editWidget, QWidget *parent) : QMenu(parent), editWidget(_editWidget) { setTitle(tr("Insert Unicode control character")); for (int i = 0; i < NUM_CONTROL_CHARACTERS; ++i) { addAction(tr(qt_controlCharacters[i].text), this, SLOT(menuActionTriggered())); } } void QUnicodeControlCharacterMenu::menuActionTriggered() { QAction *a = qobject_cast(sender()); int idx = actions().indexOf(a); if (idx < 0 || idx >= NUM_CONTROL_CHARACTERS) return; QChar c(qt_controlCharacters[idx].character); QString str(c); #ifndef QT_NO_TEXTEDIT if (QTextEdit *edit = qobject_cast(editWidget)) { edit->insertPlainText(str); return; } #endif if (QTextControl *control = qobject_cast(editWidget)) { control->insertPlainText(str); } #ifndef QT_NO_LINEEDIT if (QLineEdit *edit = qobject_cast(editWidget)) { edit->insert(str); return; } #endif } #endif // QT_NO_CONTEXTMENU QStringList QTextEditMimeData::formats() const { if (!fragment.isEmpty()) return QStringList() << QString::fromLatin1("text/plain") << QString::fromLatin1("text/html") #ifndef QT_NO_TEXTODFWRITER << QString::fromLatin1("application/vnd.oasis.opendocument.text") #endif ; else return QMimeData::formats(); } QVariant QTextEditMimeData::retrieveData(const QString &mimeType, QVariant::Type type) const { if (!fragment.isEmpty()) setup(); return QMimeData::retrieveData(mimeType, type); } void QTextEditMimeData::setup() const { QTextEditMimeData *that = const_cast(this); #ifndef QT_NO_TEXTHTMLPARSER that->setData(QLatin1String("text/html"), fragment.toHtml("utf-8").toUtf8()); #endif #ifndef QT_NO_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_qtextcontrol_p.cpp" #endif // QT_NO_TEXTCONTROL