| /**************************************************************************** |
| ** |
| ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). |
| ** All rights reserved. |
| ** Contact: Nokia Corporation (qt-info@nokia.com) |
| ** |
| ** This file is part of the QtGui module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** GNU Lesser General Public License Usage |
| ** 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. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU General |
| ** Public License version 3.0 as published by the Free Software Foundation |
| ** and appearing in the file LICENSE.GPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU General |
| ** Public License version 3.0 requirements will be met: |
| ** http://www.gnu.org/copyleft/gpl.html. |
| ** |
| ** Other Usage |
| ** Alternatively, this file may be used in accordance with the terms and |
| ** conditions contained in a signed written agreement between you and Nokia. |
| ** |
| ** |
| ** |
| ** |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qtextcontrol_p.h" |
| #include "qtextcontrol_p_p.h" |
| |
| #ifndef QT_NO_TEXTCONTROL |
| |
| #include <qfont.h> |
| #include <qpainter.h> |
| #include <qevent.h> |
| #include <qdebug.h> |
| #include <qmime.h> |
| #include <qdrag.h> |
| #include <qclipboard.h> |
| #include <qmenu.h> |
| #include <qstyle.h> |
| #include <qtimer.h> |
| #include "private/qtextdocumentlayout_p.h" |
| #include "private/qabstracttextdocumentlayout_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 <qtextformat.h> |
| #include <qdatetime.h> |
| #include <qbuffer.h> |
| #include <qapplication.h> |
| #include <limits.h> |
| #include <qtexttable.h> |
| #include <qvariant.h> |
| #include <qurl.h> |
| #include <qdesktopservices.h> |
| #include <qinputcontext.h> |
| #include <qtooltip.h> |
| #include <qstyleoption.h> |
| #include <QtGui/qlineedit.h> |
| |
| #ifndef QT_NO_SHORTCUT |
| #include "private/qapplication_p.h" |
| #include "private/qshortcutmap_p.h" |
| #include <qkeysequence.h> |
| #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) || defined(Q_WS_X11) |
| 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), |
| dragEnabled(true), |
| #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), |
| ignoreUnusedNavigationEvents(false), |
| openExternalLinks(false), |
| wordSelectionEnabled(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(); |
| |
| bool ignoreNavigationEvents = ignoreUnusedNavigationEvents; |
| bool isNavigationEvent = e->key() == Qt::Key_Up || e->key() == Qt::Key_Down; |
| |
| #ifdef QT_KEYPAD_NAVIGATION |
| ignoreNavigationEvents = ignoreNavigationEvents || QApplication::keypadNavigationEnabled(); |
| isNavigationEvent = isNavigationEvent || |
| (QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional |
| && (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right)); |
| #else |
| isNavigationEvent = isNavigationEvent || e->key() == Qt::Key_Left || e->key() == Qt::Key_Right; |
| #endif |
| |
| if (moved) { |
| if (cursor.position() != oldCursorPos) |
| emit q->cursorPositionChanged(); |
| emit q->microFocusChanged(); |
| } else if (ignoreNavigationEvents && isNavigationEvent && oldSelection.anchor() == cursor.anchor()) { |
| return false; |
| } |
| |
| 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); |
| |
| 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(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); |
| |
| //Saving the index save some time. |
| static int contentsChangedIndex = QTextDocument::staticMetaObject.indexOfSignal("contentsChanged()"); |
| static int textChangedIndex = QTextControl::staticMetaObject.indexOfSignal("textChanged()"); |
| // avoid multiple textChanged() signals being emitted |
| QMetaObject::disconnect(doc, contentsChangedIndex, q, textChangedIndex); |
| |
| 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); |
| |
| QMetaObject::connect(doc, contentsChangedIndex, q, textChangedIndex); |
| 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; |
| Qt::DropAction action; |
| if (interactionFlags & Qt::TextEditable) { |
| actions |= Qt::MoveAction; |
| action = drag->exec(actions, Qt::MoveAction); |
| } else { |
| action = drag->exec(actions, Qt::CopyAction); |
| } |
| |
| 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 (!wordSelectionEnabled && (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(); |
| const int oldCursorPos = d->cursor.position(); |
| d->doc->undo(&d->cursor); |
| if (d->cursor.position() != oldCursorPos) |
| emit cursorPositionChanged(); |
| emit microFocusChanged(); |
| ensureCursorVisible(); |
| } |
| |
| void QTextControl::redo() |
| { |
| Q_D(QTextControl); |
| d->repaintSelection(); |
| const int oldCursorPos = d->cursor.position(); |
| d->doc->redo(&d->cursor); |
| if (d->cursor.position() != oldCursorPos) |
| emit cursorPositionChanged(); |
| emit microFocusChanged(); |
| 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(QClipboard::Mode mode) |
| { |
| const QMimeData *md = QApplication::clipboard()->mimeData(mode); |
| 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) { |
| e->ignore(); |
| 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<QGraphicsSceneEvent *>(e); |
| d->contextWidget = ev->widget(); |
| break; |
| } |
| #endif // QT_NO_GRAPHICSVIEW |
| default: break; |
| }; |
| } |
| |
| switch (e->type()) { |
| case QEvent::KeyPress: |
| d->keyPressEvent(static_cast<QKeyEvent *>(e)); |
| break; |
| case QEvent::MouseButtonPress: { |
| QMouseEvent *ev = static_cast<QMouseEvent *>(e); |
| d->mousePressEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), |
| ev->buttons(), ev->globalPos()); |
| break; } |
| case QEvent::MouseMove: { |
| QMouseEvent *ev = static_cast<QMouseEvent *>(e); |
| d->mouseMoveEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), |
| ev->buttons(), ev->globalPos()); |
| break; } |
| case QEvent::MouseButtonRelease: { |
| QMouseEvent *ev = static_cast<QMouseEvent *>(e); |
| d->mouseReleaseEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), |
| ev->buttons(), ev->globalPos()); |
| break; } |
| case QEvent::MouseButtonDblClick: { |
| QMouseEvent *ev = static_cast<QMouseEvent *>(e); |
| d->mouseDoubleClickEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), |
| ev->buttons(), ev->globalPos()); |
| break; } |
| case QEvent::InputMethod: |
| d->inputMethodEvent(static_cast<QInputMethodEvent *>(e)); |
| break; |
| #ifndef QT_NO_CONTEXTMENU |
| case QEvent::ContextMenu: { |
| QContextMenuEvent *ev = static_cast<QContextMenuEvent *>(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<QFocusEvent *>(e)); |
| break; |
| |
| case QEvent::EnabledChange: |
| d->isEnabled = e->isAccepted(); |
| break; |
| |
| #ifndef QT_NO_TOOLTIP |
| case QEvent::ToolTip: { |
| QHelpEvent *ev = static_cast<QHelpEvent *>(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<QDragEnterEvent *>(e); |
| if (d->dragEnterEvent(e, ev->mimeData())) |
| ev->acceptProposedAction(); |
| break; |
| } |
| case QEvent::DragLeave: |
| d->dragLeaveEvent(); |
| break; |
| case QEvent::DragMove: { |
| QDragMoveEvent *ev = static_cast<QDragMoveEvent *>(e); |
| if (d->dragMoveEvent(e, ev->mimeData(), matrix.map(ev->pos()))) |
| ev->acceptProposedAction(); |
| break; |
| } |
| case QEvent::Drop: { |
| QDropEvent *ev = static_cast<QDropEvent *>(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<QGraphicsSceneMouseEvent *>(e); |
| d->mousePressEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), |
| ev->screenPos()); |
| break; } |
| case QEvent::GraphicsSceneMouseMove: { |
| QGraphicsSceneMouseEvent *ev = static_cast<QGraphicsSceneMouseEvent *>(e); |
| d->mouseMoveEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), |
| ev->screenPos()); |
| break; } |
| case QEvent::GraphicsSceneMouseRelease: { |
| QGraphicsSceneMouseEvent *ev = static_cast<QGraphicsSceneMouseEvent *>(e); |
| d->mouseReleaseEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), |
| ev->screenPos()); |
| break; } |
| case QEvent::GraphicsSceneMouseDoubleClick: { |
| QGraphicsSceneMouseEvent *ev = static_cast<QGraphicsSceneMouseEvent *>(e); |
| d->mouseDoubleClickEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), |
| ev->screenPos()); |
| break; } |
| case QEvent::GraphicsSceneContextMenu: { |
| QGraphicsSceneContextMenuEvent *ev = static_cast<QGraphicsSceneContextMenuEvent *>(e); |
| d->contextMenuEvent(ev->screenPos(), matrix.map(ev->pos()), contextWidget); |
| break; } |
| |
| case QEvent::GraphicsSceneHoverMove: { |
| QGraphicsSceneHoverEvent *ev = static_cast<QGraphicsSceneHoverEvent *>(e); |
| d->mouseMoveEvent(ev, Qt::NoButton, matrix.map(ev->pos()), ev->modifiers(),Qt::NoButton, |
| ev->screenPos()); |
| break; } |
| |
| case QEvent::GraphicsSceneDragEnter: { |
| QGraphicsSceneDragDropEvent *ev = static_cast<QGraphicsSceneDragDropEvent *>(e); |
| if (d->dragEnterEvent(e, ev->mimeData())) |
| ev->acceptProposedAction(); |
| break; } |
| case QEvent::GraphicsSceneDragLeave: |
| d->dragLeaveEvent(); |
| break; |
| case QEvent::GraphicsSceneDragMove: { |
| QGraphicsSceneDragDropEvent *ev = static_cast<QGraphicsSceneDragDropEvent *>(e); |
| if (d->dragMoveEvent(e, ev->mimeData(), matrix.map(ev->pos()))) |
| ev->acceptProposedAction(); |
| break; } |
| case QEvent::GraphicsSceneDrop: { |
| QGraphicsSceneDragDropEvent *ev = static_cast<QGraphicsSceneDragDropEvent *>(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<QKeyEvent *>(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; |
| 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 { |
| QTextCursor localCursor = cursor; |
| localCursor.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) { |
| QClipboard::Mode mode = QClipboard::Clipboard; |
| #ifdef Q_WS_X11 |
| if (e->modifiers() == (Qt::CTRL | Qt::SHIFT) && e->key() == Qt::Key_Insert) |
| mode = QClipboard::Selection; |
| #endif |
| 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 // 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<QTextEdit *>(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); |
| QRectF br = q->blockBoundingRect(block); |
| br.setRight(qreal(INT_MAX)); // the block might have shrunk |
| emit q->updateRequest(br); |
| } |
| |
| 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<QTextFrame *> children = frame->childFrames(); |
| |
| const QList<QTextFrame *>::ConstIterator firstFrame = qLowerBound(children.constBegin(), children.constEnd(), |
| cursor.selectionStart(), firstFramePosLessThanCursorPos); |
| const QList<QTextFrame *>::ConstIterator lastFrame = qUpperBound(children.constBegin(), children.constEnd(), |
| cursor.selectionEnd(), cursorPosLessThanLastFramePos); |
| for (QList<QTextFrame *>::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(QEvent *e, Qt::MouseButton button, const QPointF &pos, Qt::KeyboardModifiers modifiers, |
| Qt::MouseButtons buttons, const QPoint &globalPos) |
| { |
| Q_Q(QTextControl); |
| |
| if (sendMouseEventToInputContext( |
| e, QEvent::MouseButtonPress, button, pos, modifiers, buttons, globalPos)) { |
| return; |
| } |
| |
| if (interactionFlags & Qt::LinksAccessibleByMouse) { |
| anchorOnMousePress = q->anchorAt(pos); |
| |
| if (cursorIsFocusIndicator) { |
| cursorIsFocusIndicator = false; |
| repaintSelection(); |
| cursor.clearSelection(); |
| } |
| } |
| if (!(button & Qt::LeftButton) || |
| !((interactionFlags & Qt::TextSelectableByMouse) || (interactionFlags & Qt::TextEditable))) { |
| e->ignore(); |
| return; |
| } |
| |
| cursorIsFocusIndicator = false; |
| const QTextCursor oldSelection = cursor; |
| const int oldCursorPos = cursor.position(); |
| |
| mousePressed = (interactionFlags & Qt::TextSelectableByMouse); |
| #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) { |
| e->ignore(); |
| return; |
| } |
| |
| if (modifiers == Qt::ShiftModifier && (interactionFlags & Qt::TextSelectableByMouse)) { |
| if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) { |
| selectedWordOnDoubleClick = cursor; |
| selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor); |
| } |
| |
| if (selectedBlockOnTrippleClick.hasSelection()) |
| extendBlockwiseSelection(cursorPos); |
| else if (selectedWordOnDoubleClick.hasSelection()) |
| extendWordwiseSelection(cursorPos, pos.x()); |
| else if (!wordSelectionEnabled) |
| setCursorPosition(cursorPos, QTextCursor::KeepAnchor); |
| } else { |
| |
| if (dragEnabled |
| && 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(); |
| emit q->microFocusChanged(); |
| } |
| selectionChanged(); |
| } |
| repaintOldAndNewSelection(oldSelection); |
| hadSelectionOnMousePress = cursor.hasSelection(); |
| } |
| |
| void QTextControlPrivate::mouseMoveEvent(QEvent *e, Qt::MouseButton button, const QPointF &mousePos, Qt::KeyboardModifiers modifiers, |
| Qt::MouseButtons buttons, const QPoint &globalPos) |
| { |
| Q_Q(QTextControl); |
| |
| if (sendMouseEventToInputContext( |
| e, QEvent::MouseMove, button, mousePos, modifiers, buttons, globalPos)) { |
| return; |
| } |
| |
| if (interactionFlags & Qt::LinksAccessibleByMouse) { |
| QString anchor = q->anchorAt(mousePos); |
| if (anchor != highlightedAnchor) { |
| highlightedAnchor = anchor; |
| emit q->linkHovered(anchor); |
| } |
| } |
| |
| if (!(buttons & Qt::LeftButton)) |
| return; |
| |
| const bool editable = interactionFlags & Qt::TextEditable; |
| |
| if (!(mousePressed |
| || editable |
| || mightStartDrag |
| || 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; |
| } |
| |
| if (!mousePressed) |
| return; |
| |
| const qreal mouseX = qreal(mousePos.x()); |
| |
| int newCursorPos = q->hitTest(mousePos, Qt::FuzzyHit); |
| if (newCursorPos == -1) |
| return; |
| |
| if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) { |
| selectedWordOnDoubleClick = cursor; |
| selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor); |
| } |
| |
| 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(); |
| #ifndef QT_NO_IM |
| if (contextWidget) { |
| if (QInputContext *ic = inputContext()) { |
| ic->update(); |
| } |
| } |
| #endif //QT_NO_IM |
| } else { |
| //emit q->visibilityRequest(QRectF(mousePos, QSizeF(1, 1))); |
| if (cursor.position() != oldCursorPos) { |
| emit q->cursorPositionChanged(); |
| emit q->microFocusChanged(); |
| } |
| } |
| selectionChanged(true); |
| repaintOldAndNewSelection(oldSelection); |
| } |
| |
| void QTextControlPrivate::mouseReleaseEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, Qt::KeyboardModifiers modifiers, |
| Qt::MouseButtons buttons, const QPoint &globalPos) |
| { |
| Q_Q(QTextControl); |
| |
| if (sendMouseEventToInputContext( |
| e, QEvent::MouseButtonRelease, button, pos, modifiers, buttons, globalPos)) { |
| return; |
| } |
| |
| 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 |
| 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(); |
| emit q->microFocusChanged(); |
| } |
| |
| 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, Qt::KeyboardModifiers modifiers, |
| Qt::MouseButtons buttons, const QPoint &globalPos) |
| { |
| Q_Q(QTextControl); |
| |
| if (sendMouseEventToInputContext( |
| e, QEvent::MouseButtonDblClick, button, pos, modifiers, buttons, globalPos)) { |
| return; |
| } |
| |
| if (button != Qt::LeftButton |
| || !(interactionFlags & Qt::TextSelectableByMouse)) { |
| e->ignore(); |
| return; |
| } |
| |
| #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(); |
| } |
| } |
| |
| bool QTextControlPrivate::sendMouseEventToInputContext( |
| QEvent *e, QEvent::Type eventType, Qt::MouseButton button, const QPointF &pos, |
| Qt::KeyboardModifiers modifiers, Qt::MouseButtons buttons, const QPoint &globalPos) |
| { |
| #if !defined(QT_NO_IM) |
| Q_Q(QTextControl); |
| |
| QTextLayout *layout = cursor.block().layout(); |
| if (contextWidget && layout && !layout->preeditAreaText().isEmpty()) { |
| QInputContext *ctx = inputContext(); |
| int cursorPos = q->hitTest(pos, Qt::FuzzyHit) - cursor.position(); |
| |
| if (cursorPos < 0 || cursorPos > layout->preeditAreaText().length()) { |
| cursorPos = -1; |
| // don't send move events outside the preedit area |
| if (eventType == QEvent::MouseMove) |
| return true; |
| } |
| if (ctx) { |
| QMouseEvent ev(eventType, contextWidget->mapFromGlobal(globalPos), globalPos, |
| button, buttons, modifiers); |
| ctx->mouseHandler(cursorPos, &ev); |
| e->setAccepted(ev.isAccepted()); |
| } |
| if (!layout->preeditAreaText().isEmpty()) |
| return true; |
| } |
| #else |
| Q_UNUSED(e); |
| Q_UNUSED(eventType); |
| Q_UNUSED(button); |
| Q_UNUSED(pos); |
| Q_UNUSED(modifiers); |
| Q_UNUSED(buttons); |
| Q_UNUSED(globalPos); |
| #endif |
| return false; |
| } |
| |
| 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->setAttribute(Qt::WA_DeleteOnClose); |
| menu->popup(screenPos); |
| #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() != cursor.block().layout()->preeditAreaText() |
| || e->replacementLength() > 0; |
| |
| cursor.beginEditBlock(); |
| if (isGettingInput) { |
| 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(); |
| if (isGettingInput) |
| layout->setPreeditArea(cursor.position() - block.position(), e->preeditString()); |
| QList<QTextLayout::FormatRange> overrides; |
| const int oldPreeditCursor = preeditCursor; |
| 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<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->setAdditionalFormats(overrides); |
| cursor.endEditBlock(); |
| if (oldPreeditCursor != preeditCursor) |
| emit q->microFocusChanged(); |
| } |
| |
| 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 |
| #ifdef Q_OS_SYMBIAN |
| || e->reason() == Qt::ActiveWindowFocusReason |
| #endif |
| ))) { |
| #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<QAction *> imActions = qic->actions(); |
| for (int i = 0; i < imActions.size(); ++i) |
| menu->addAction(imActions.at(i)); |
| } |
| } |
| #endif |
| |
| #if defined(Q_WS_WIN) || defined(Q_WS_X11) |
| 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<QTextEdit::ExtraSelection> &selections) |
| { |
| Q_D(QTextControl); |
| |
| QHash<int, int> 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<int, int>::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<int, int>::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<QTextEdit::ExtraSelection> QTextControl::extraSelections() const |
| { |
| Q_D(const QTextControl); |
| QList<QTextEdit::ExtraSelection> 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; |
| } |
| |
| bool QTextControl::ignoreUnusedNavigationEvents() const |
| { |
| Q_D(const QTextControl); |
| return d->ignoreUnusedNavigationEvents; |
| } |
| |
| void QTextControl::setIgnoreUnusedNavigationEvents(bool ignore) |
| { |
| Q_D(QTextControl); |
| d->ignoreUnusedNavigationEvents = ignore; |
| } |
| |
| 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; |
| } |
| |
| |
| void QTextControl::setDragEnabled(bool enabled) |
| { |
| Q_D(QTextControl); |
| d->dragEnabled = enabled; |
| } |
| |
| bool QTextControl::isDragEnabled() const |
| { |
| Q_D(const QTextControl); |
| return d->dragEnabled; |
| } |
| |
| void QTextControl::setWordSelectionEnabled(bool enabled) |
| { |
| Q_D(QTextControl); |
| d->wordSelectionEnabled = enabled; |
| } |
| |
| bool QTextControl::isWordSelectionEnabled() const |
| { |
| Q_D(const QTextControl); |
| return d->wordSelectionEnabled; |
| } |
| |
| #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<QTextDocument *>(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; |
| |
| // copy the custom object handlers |
| doc->documentLayout()->d_func()->handlers = d->doc->documentLayout()->d_func()->handlers; |
| } |
| 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("<meta name=\"qrichtext\" content=\"1\" />")); |
| 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<QTextFormat>(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<QAction *>(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<QTextEdit *>(editWidget)) { |
| edit->insertPlainText(str); |
| return; |
| } |
| #endif |
| if (QTextControl *control = qobject_cast<QTextControl *>(editWidget)) { |
| control->insertPlainText(str); |
| } |
| #ifndef QT_NO_LINEEDIT |
| if (QLineEdit *edit = qobject_cast<QLineEdit *>(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<QTextEditMimeData *>(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 |