diff options
author | Lars Knoll <lars.knoll@nokia.com> | 2009-03-23 09:18:55 (GMT) |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2009-03-23 09:18:55 (GMT) |
commit | e5fcad302d86d316390c6b0f62759a067313e8a9 (patch) | |
tree | c2afbf6f1066b6ce261f14341cf6d310e5595bc1 /src/gui/widgets/qplaintextedit.cpp | |
download | Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.zip Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.gz Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.bz2 |
Long live Qt 4.5!
Diffstat (limited to 'src/gui/widgets/qplaintextedit.cpp')
-rw-r--r-- | src/gui/widgets/qplaintextedit.cpp | 2893 |
1 files changed, 2893 insertions, 0 deletions
diff --git a/src/gui/widgets/qplaintextedit.cpp b/src/gui/widgets/qplaintextedit.cpp new file mode 100644 index 0000000..2e9201d --- /dev/null +++ b/src/gui/widgets/qplaintextedit.cpp @@ -0,0 +1,2893 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplaintextedit_p.h" + + +#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 "qtextdocument.h" +#include "private/qtextdocument_p.h" +#include "qtextlist.h" +#include "private/qtextcontrol_p.h" + +#include <qtextformat.h> +#include <qdatetime.h> +#include <qapplication.h> +#include <limits.h> +#include <qtexttable.h> +#include <qvariant.h> + +#include <qinputcontext.h> + +#ifndef QT_NO_TEXTEDIT + +QT_BEGIN_NAMESPACE + +class QPlainTextDocumentLayoutPrivate : public QAbstractTextDocumentLayoutPrivate +{ + Q_DECLARE_PUBLIC(QPlainTextDocumentLayout) +public: + QPlainTextDocumentLayoutPrivate() { + mainViewPrivate = 0; + width = 0; + maximumWidth = 0; + maximumWidthBlockNumber = 0; + blockCount = 1; + blockUpdate = blockDocumentSizeChanged = false; + cursorWidth = 1; + textLayoutFlags = 0; + } + + qreal width; + qreal maximumWidth; + int maximumWidthBlockNumber; + int blockCount; + QPlainTextEditPrivate *mainViewPrivate; + bool blockUpdate; + bool blockDocumentSizeChanged; + int cursorWidth; + int textLayoutFlags; + + void layoutBlock(const QTextBlock &block); + qreal blockWidth(const QTextBlock &block); + + void relayout(); +}; + + + +/*! \class QPlainTextDocumentLayout + \since 4.4 + \brief The QPlainTextDocumentLayout class implements a plain text layout for QTextDocument + + \ingroup text + + + A QPlainTextDocumentLayout is required for text documents that can + be display or edited in a QPlainTextEdit. See + QTextDocument::setDocumentLayout(). + + QPlainTextDocumentLayout uses the QAbstractTextDocumentLayout API + that QTextDocument requires, but redefines it partially in order to + support plain text better. For instances, it does not operate on + vertical pixels, but on paragraphs (called blocks) instead. The + height of a document is identical to the number of paragraphs it + contains. The layout also doesn't support tables or nested frames, + or any sort of advanced text layout that goes beyond a list of + paragraphs with syntax highlighting. + +*/ + + + +/*! + Constructs a plain text document layout for the text \a document. + */ +QPlainTextDocumentLayout::QPlainTextDocumentLayout(QTextDocument *document) + :QAbstractTextDocumentLayout(* new QPlainTextDocumentLayoutPrivate, document) { +} +/*! + Destructs a plain text document layout. + */ +QPlainTextDocumentLayout::~QPlainTextDocumentLayout() {} + + +/*! + \reimp + */ +void QPlainTextDocumentLayout::draw(QPainter *, const PaintContext &) +{ +} + +/*! + \reimp + */ +int QPlainTextDocumentLayout::hitTest(const QPointF &, Qt::HitTestAccuracy ) const +{ +// this function is used from +// QAbstractTextDocumentLayout::anchorAt(), but is not +// implementable in a plain text document layout, because the +// layout depends on the top block and top line which depends on +// the view + return -1; +} + +/*! + \reimp + */ +int QPlainTextDocumentLayout::pageCount() const +{ return 1; } + +/*! + \reimp + */ +QSizeF QPlainTextDocumentLayout::documentSize() const +{ + Q_D(const QPlainTextDocumentLayout); + return QSizeF(d->maximumWidth, document()->lineCount()); +} + +/*! + \reimp + */ +QRectF QPlainTextDocumentLayout::frameBoundingRect(QTextFrame *) const +{ + Q_D(const QPlainTextDocumentLayout); + return QRectF(0, 0, qMax(d->width, d->maximumWidth), qreal(INT_MAX)); +} + +/*! + \reimp + */ +QRectF QPlainTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const +{ + if (!block.isValid()) { return QRectF(); } + QTextLayout *tl = block.layout(); + if (!tl->lineCount()) + const_cast<QPlainTextDocumentLayout*>(this)->layoutBlock(block); + QRectF br; + if (block.isVisible()) { + br = QRectF(QPointF(0, 0), tl->boundingRect().bottomRight()); + if (tl->lineCount() == 1) + br.setWidth(qMax(br.width(), tl->lineAt(0).naturalTextWidth())); + qreal margin = document()->documentMargin(); + br.adjust(0, 0, margin, 0); + if (!block.next().isValid()) + br.adjust(0, 0, 0, margin); + } + return br; + +} + +/*! + Ensures that \a block has a valid layout + */ +void QPlainTextDocumentLayout::ensureBlockLayout(const QTextBlock &block) const +{ + if (!block.isValid()) + return; + QTextLayout *tl = block.layout(); + if (!tl->lineCount()) + const_cast<QPlainTextDocumentLayout*>(this)->layoutBlock(block); +} + + +/*! \property QPlainTextDocumentLayout::cursorWidth + + This property specifies the width of the cursor in pixels. The default value is 1. +*/ +void QPlainTextDocumentLayout::setCursorWidth(int width) +{ + Q_D(QPlainTextDocumentLayout); + d->cursorWidth = width; +} + +int QPlainTextDocumentLayout::cursorWidth() const +{ + Q_D(const QPlainTextDocumentLayout); + return d->cursorWidth; +} + +QPlainTextDocumentLayoutPrivate *QPlainTextDocumentLayout::priv() const +{ + Q_D(const QPlainTextDocumentLayout); + return const_cast<QPlainTextDocumentLayoutPrivate*>(d); +} + + +/*! + + Requests a complete update on all views. + */ +void QPlainTextDocumentLayout::requestUpdate() +{ + emit update(QRectF(0., -4., 1000000000., 1000000000.)); +} + + +void QPlainTextDocumentLayout::setTextWidth(qreal newWidth) +{ + Q_D(QPlainTextDocumentLayout); + d->width = d->maximumWidth = newWidth; + d->relayout(); +} + +qreal QPlainTextDocumentLayout::textWidth() const +{ + Q_D(const QPlainTextDocumentLayout); + return d->width; +} + +void QPlainTextDocumentLayoutPrivate::relayout() +{ + Q_Q(QPlainTextDocumentLayout); + QTextBlock block = q->document()->firstBlock(); + while (block.isValid()) { + block.layout()->clearLayout(); + block.setLineCount(block.isVisible() ? 1 : 0); + block = block.next(); + } + emit q->update(); +} + + +/*! \reimp + */ +void QPlainTextDocumentLayout::documentChanged(int from, int /*charsRemoved*/, int charsAdded) +{ + Q_D(QPlainTextDocumentLayout); + QTextDocument *doc = document(); + int newBlockCount = doc->blockCount(); + + QTextBlock changeStartBlock = doc->findBlock(from); + QTextBlock changeEndBlock = doc->findBlock(qMax(0, from + charsAdded - 1)); + + if (changeStartBlock == changeEndBlock && newBlockCount == d->blockCount) { + QTextBlock block = changeStartBlock; + int blockLineCount = block.layout()->lineCount(); + if (block.isValid() && blockLineCount) { + QRectF oldBr = blockBoundingRect(block); + layoutBlock(block); + QRectF newBr = blockBoundingRect(block); + if (newBr.height() == oldBr.height()) { + if (!d->blockUpdate) + emit updateBlock(block); + return; + } + } + } else { + QTextBlock block = changeStartBlock; + do { + block.clearLayout(); + if (block == changeEndBlock) + break; + block = block.next(); + } while(block.isValid()); + } + + if (newBlockCount != d->blockCount) { + + int changeEnd = changeEndBlock.blockNumber(); + int blockDiff = newBlockCount - d->blockCount; + int oldChangeEnd = changeEnd - blockDiff; + + if (d->maximumWidthBlockNumber > oldChangeEnd) + d->maximumWidthBlockNumber += blockDiff; + + d->blockCount = newBlockCount; + if (d->blockCount == 1) + d->maximumWidth = blockWidth(doc->firstBlock()); + + if (!d->blockDocumentSizeChanged) + emit documentSizeChanged(documentSize()); + + if (blockDiff == 1 && changeEnd == newBlockCount -1 ) { + if (!d->blockUpdate) { + QTextBlock b = changeStartBlock; + for(;;) { + emit updateBlock(b); + if (b == changeEndBlock) + break; + b = b.next(); + } + } + return; + } + } + + if (!d->blockUpdate) + emit update(); // optimization potential + +} + + +void QPlainTextDocumentLayout::layoutBlock(const QTextBlock &block) +{ + Q_D(QPlainTextDocumentLayout); + QTextDocument *doc = document(); + qreal margin = doc->documentMargin(); + QFontMetrics fm(doc->defaultFont()); + qreal blockMaximumWidth = 0; + + int leading = qMax(0, fm.leading()); + qreal height = 0; + QTextLayout *tl = block.layout(); + QTextOption option = doc->defaultTextOption(); + tl->setTextOption(option); + + int extraMargin = 0; + if (option.flags() & QTextOption::AddSpaceForLineAndParagraphSeparators) { + QFontMetrics fm(block.charFormat().font()); + extraMargin += fm.width(QChar(0x21B5)); + } + tl->beginLayout(); + while (1) { + QTextLine line = tl->createLine(); + if (!line.isValid()) + break; + line.setLineWidth(d->width - 2*margin - extraMargin); + + height += leading; + line.setPosition(QPointF(margin, height)); + height += line.height(); + blockMaximumWidth = qMax(blockMaximumWidth, line.naturalTextWidth() + 2*margin); + } + tl->endLayout(); + + int previousLineCount = doc->lineCount(); + const_cast<QTextBlock&>(block).setLineCount(block.isVisible() ? tl->lineCount() : 0); + int lineCount = doc->lineCount(); + + bool emitDocumentSizeChanged = previousLineCount != lineCount; + if (blockMaximumWidth > d->maximumWidth) { + // new longest line + d->maximumWidth = blockMaximumWidth; + d->maximumWidthBlockNumber = block.blockNumber(); + emitDocumentSizeChanged = true; + } else if (block.blockNumber() == d->maximumWidthBlockNumber && blockMaximumWidth < d->maximumWidth) { + // longest line shrinking + QTextBlock b = doc->firstBlock(); + d->maximumWidth = 0; + QTextBlock maximumBlock; + while (b.isValid()) { + qreal blockMaximumWidth = blockWidth(b); + if (blockMaximumWidth > d->maximumWidth) { + d->maximumWidth = blockMaximumWidth; + maximumBlock = b; + } + b = b.next(); + } + if (maximumBlock.isValid()) { + d->maximumWidthBlockNumber = maximumBlock.blockNumber(); + emitDocumentSizeChanged = true; + } + } + if (emitDocumentSizeChanged && !d->blockDocumentSizeChanged) + emit documentSizeChanged(documentSize()); +} + +qreal QPlainTextDocumentLayout::blockWidth(const QTextBlock &block) +{ + QTextLayout *layout = block.layout(); + if (!layout->lineCount()) + return 0; // only for layouted blocks + qreal blockWidth = 0; + for (int i = 0; i < layout->lineCount(); ++i) { + QTextLine line = layout->lineAt(i); + blockWidth = qMax(line.naturalTextWidth() + 8, blockWidth); + } + return blockWidth; +} + + +QPlainTextEditControl::QPlainTextEditControl(QPlainTextEdit *parent) + : QTextControl(parent), textEdit(parent), + topBlock(0) +{ + setAcceptRichText(false); +} + +void QPlainTextEditPrivate::_q_cursorPositionChanged() +{ + pageUpDownLastCursorYIsValid = false; +}; + +void QPlainTextEditPrivate::_q_verticalScrollbarActionTriggered(int action) { + if (action == QAbstractSlider::SliderPageStepAdd) { + pageUpDown(QTextCursor::Down, QTextCursor::MoveAnchor, false); + } else if (action == QAbstractSlider::SliderPageStepSub) { + pageUpDown(QTextCursor::Up, QTextCursor::MoveAnchor, false); + } +} + +QMimeData *QPlainTextEditControl::createMimeDataFromSelection() const { + QPlainTextEdit *ed = qobject_cast<QPlainTextEdit *>(parent()); + if (!ed) + return QTextControl::createMimeDataFromSelection(); + return ed->createMimeDataFromSelection(); + } +bool QPlainTextEditControl::canInsertFromMimeData(const QMimeData *source) const { + QPlainTextEdit *ed = qobject_cast<QPlainTextEdit *>(parent()); + if (!ed) + return QTextControl::canInsertFromMimeData(source); + return ed->canInsertFromMimeData(source); +} +void QPlainTextEditControl::insertFromMimeData(const QMimeData *source) { + QPlainTextEdit *ed = qobject_cast<QPlainTextEdit *>(parent()); + if (!ed) + QTextControl::insertFromMimeData(source); + else + ed->insertFromMimeData(source); +} + +int QPlainTextEditPrivate::verticalOffset(int topBlock, int topLine) const +{ + qreal offset = 0; + QTextDocument *doc = control->document(); + + if (topLine) { + QTextBlock currentBlock = doc->findBlockByNumber(topBlock); + QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(doc->documentLayout()); + Q_ASSERT(documentLayout); + QRectF r = documentLayout->blockBoundingRect(currentBlock); + QTextLayout *layout = currentBlock.layout(); + if (layout && topLine <= layout->lineCount()) { + QTextLine line = layout->lineAt(topLine - 1); + const QRectF lr = line.naturalTextRect(); + offset = lr.bottom(); + } + } + if (topBlock == 0 && topLine == 0) + offset -= doc->documentMargin(); // top margin + return (int)offset; +} + + +int QPlainTextEditPrivate::verticalOffset() const { + return verticalOffset(control->topBlock, topLine); +} + + +QTextBlock QPlainTextEditControl::firstVisibleBlock() const +{ + return document()->findBlockByNumber(topBlock); +} + + + +int QPlainTextEditControl::hitTest(const QPointF &point, Qt::HitTestAccuracy ) const { + int currentBlockNumber = topBlock; + QTextBlock currentBlock = document()->findBlockByNumber(currentBlockNumber); + QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(document()->documentLayout()); + Q_ASSERT(documentLayout); + + QPointF offset; + QRectF r = documentLayout->blockBoundingRect(currentBlock); + while (currentBlock.next().isValid() && r.bottom() + offset.y() <= point.y()) { + offset.ry() += r.height(); + currentBlock = currentBlock.next(); + ++currentBlockNumber; + r = documentLayout->blockBoundingRect(currentBlock); + } + while (currentBlock.previous().isValid() && r.top() + offset.y() > point.y()) { + offset.ry() -= r.height(); + currentBlock = currentBlock.previous(); + --currentBlockNumber; + r = documentLayout->blockBoundingRect(currentBlock); + } + + + if (!currentBlock.isValid()) + return -1; + QTextLayout *layout = currentBlock.layout(); + int off = 0; + QPointF pos = point - offset; + for (int i = 0; i < layout->lineCount(); ++i) { + QTextLine line = layout->lineAt(i); + const QRectF lr = line.naturalTextRect(); + if (lr.top() > pos.y()) { + off = qMin(off, line.textStart()); + } else if (lr.bottom() <= pos.y()) { + off = qMax(off, line.textStart() + line.textLength()); + } else { + off = line.xToCursor(pos.x(), overwriteMode() ? + QTextLine::CursorOnCharacter : QTextLine::CursorBetweenCharacters); + break; + } + } + + return currentBlock.position() + off; +} + +QRectF QPlainTextEditControl::blockBoundingRect(const QTextBlock &block) const { + int currentBlockNumber = topBlock; + int blockNumber = block.blockNumber(); + QTextBlock currentBlock = document()->findBlockByNumber(currentBlockNumber); + if (!currentBlock.isValid()) + return QRectF(); + Q_ASSERT(currentBlock.blockNumber() == currentBlockNumber); + QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(document()->documentLayout()); + Q_ASSERT(documentLayout); + + QPointF offset; + if (!block.isValid()) + return QRectF(); + QRectF r = documentLayout->blockBoundingRect(currentBlock); + while (currentBlockNumber < blockNumber && offset.y() <= 2* textEdit->viewport()->height()) { + offset.ry() += r.height(); + currentBlock = currentBlock.next(); + ++currentBlockNumber; + r = documentLayout->blockBoundingRect(currentBlock); + } + while (currentBlockNumber > blockNumber && offset.y() >= -textEdit->viewport()->height()) { + currentBlock = currentBlock.previous(); + if (!currentBlock.isValid()) + break; + --currentBlockNumber; + r = documentLayout->blockBoundingRect(currentBlock); + offset.ry() -= r.height(); + } + + if (currentBlockNumber != blockNumber) { + // fallback for blocks out of reach. Give it some geometry at + // least, and ensure the layout is up to date. + r = documentLayout->blockBoundingRect(block); + if (currentBlockNumber > blockNumber) + offset.ry() -= r.height(); + } + r.translate(offset); + return r; +} + + +void QPlainTextEditPrivate::setTopLine(int visualTopLine, int dx) +{ + QTextDocument *doc = control->document(); + QTextBlock block = doc->findBlockByLineNumber(visualTopLine); + int blockNumber = block.blockNumber(); + int lineNumber = visualTopLine - block.firstLineNumber(); + setTopBlock(blockNumber, lineNumber, dx); +} + +void QPlainTextEditPrivate::setTopBlock(int blockNumber, int lineNumber, int dx) +{ + Q_Q(QPlainTextEdit); + blockNumber = qMax(0, blockNumber); + lineNumber = qMax(0, lineNumber); + QTextDocument *doc = control->document(); + QTextBlock block = doc->findBlockByNumber(blockNumber); + + int newTopLine = block.firstLineNumber() + lineNumber; + int maxTopLine = vbar->maximum(); + + if (newTopLine > maxTopLine) { + block = doc->findBlockByLineNumber(maxTopLine); + blockNumber = block.blockNumber(); + lineNumber = maxTopLine - block.firstLineNumber(); + } + + bool vbarSignalsBlocked = vbar->blockSignals(true); + vbar->setValue(newTopLine); + vbar->blockSignals(vbarSignalsBlocked); + + if (!dx && blockNumber == control->topBlock && lineNumber == topLine) + return; + + if (viewport->updatesEnabled() && viewport->isVisible()) { + int dy = 0; + if (doc->findBlockByLineNumber(control->topBlock).isValid()) { + dy = (int)(-q->blockBoundingGeometry(block).y()) + + verticalOffset() - verticalOffset(blockNumber, lineNumber); + } + control->topBlock = blockNumber; + topLine = lineNumber; + if (dx || dy) + viewport->scroll(q->isRightToLeft() ? -dx : dx, dy); + else + viewport->update(); + emit q->updateRequest(viewport->rect(), dy); + } else { + control->topBlock = blockNumber; + topLine = lineNumber; + } + +} + + + +void QPlainTextEditPrivate::ensureVisible(int position, bool center, bool forceCenter) { + Q_Q(QPlainTextEdit); + QRectF visible = QRectF(viewport->rect()).translated(-q->contentOffset()); + QTextBlock block = control->document()->findBlock(position); + if (!block.isValid()) + return; + QRectF br = control->blockBoundingRect(block); + if (!br.isValid()) + return; + QRectF lr = br; + QTextLine line = block.layout()->lineForTextPosition(position - block.position()); + Q_ASSERT(line.isValid()); + lr = line.naturalTextRect().translated(br.topLeft()); + + if (lr.bottom() >= visible.bottom() || (center && lr.top() < visible.top()) || forceCenter){ + + qreal height = visible.height(); + if (center) + height /= 2; + + qreal h = center ? line.naturalTextRect().center().y() : line.naturalTextRect().bottom(); + + while (h < height && block.previous().isValid()) { + block = block.previous(); + h += q->blockBoundingRect(block).height(); + } + + int l = 0; + int lineCount = block.layout()->lineCount(); + int voffset = verticalOffset(block.blockNumber(), 0); + while (l < lineCount) { + QRectF lineRect = block.layout()->lineAt(l).naturalTextRect(); + if (h - voffset - lineRect.top() <= height) + break; + ++l; + } + + if (block.next().isValid() && l >= lineCount) { + block = block.next(); + l = 0; + } + setTopBlock(block.blockNumber(), l); + } else if (lr.top() < visible.top()) { + setTopBlock(block.blockNumber(), line.lineNumber()); + } + +} + + +void QPlainTextEditPrivate::updateViewport() +{ + Q_Q(QPlainTextEdit); + viewport->update(); + emit q->updateRequest(viewport->rect(), 0); +} + +QPlainTextEditPrivate::QPlainTextEditPrivate() + : control(0), + tabChangesFocus(false), + lineWrap(QPlainTextEdit::WidgetWidth), + wordWrap(QTextOption::WrapAtWordBoundaryOrAnywhere), + topLine(0), pageUpDownLastCursorYIsValid(false) +{ + showCursorOnInitialShow = true; + backgroundVisible = false; + centerOnScroll = false; + inDrag = false; +} + + +void QPlainTextEditPrivate::init(const QString &txt) +{ + Q_Q(QPlainTextEdit); + control = new QPlainTextEditControl(q); + + QTextDocument *doc = new QTextDocument(control); + QAbstractTextDocumentLayout *layout = new QPlainTextDocumentLayout(doc); + doc->setDocumentLayout(layout); + control->setDocument(doc); + + control->setPalette(q->palette()); + + QObject::connect(vbar, SIGNAL(actionTriggered(int)), q, SLOT(_q_verticalScrollbarActionTriggered(int))); + + QObject::connect(control, SIGNAL(microFocusChanged()), q, SLOT(updateMicroFocus())); + QObject::connect(control, SIGNAL(documentSizeChanged(QSizeF)), q, SLOT(_q_adjustScrollbars())); + QObject::connect(control, SIGNAL(blockCountChanged(int)), q, SIGNAL(blockCountChanged(int))); + QObject::connect(control, SIGNAL(updateRequest(QRectF)), q, SLOT(_q_repaintContents(QRectF))); + QObject::connect(control, SIGNAL(modificationChanged(bool)), q, SIGNAL(modificationChanged(bool))); + + QObject::connect(control, SIGNAL(textChanged()), q, SIGNAL(textChanged())); + QObject::connect(control, SIGNAL(undoAvailable(bool)), q, SIGNAL(undoAvailable(bool))); + QObject::connect(control, SIGNAL(redoAvailable(bool)), q, SIGNAL(redoAvailable(bool))); + QObject::connect(control, SIGNAL(copyAvailable(bool)), q, SIGNAL(copyAvailable(bool))); + QObject::connect(control, SIGNAL(selectionChanged()), q, SIGNAL(selectionChanged())); + QObject::connect(control, SIGNAL(cursorPositionChanged()), q, SLOT(_q_cursorPositionChanged())); + QObject::connect(control, SIGNAL(cursorPositionChanged()), q, SIGNAL(cursorPositionChanged())); + + + // set a null page size initially to avoid any relayouting until the textedit + // is shown. relayoutDocument() will take care of setting the page size to the + // viewport dimensions later. + doc->setTextWidth(0); + doc->documentLayout()->setPaintDevice(viewport); + doc->setDefaultFont(q->font()); + + + if (!txt.isEmpty()) + control->setPlainText(txt); + + hbar->setSingleStep(20); + vbar->setSingleStep(1); + + viewport->setBackgroundRole(QPalette::Base); + q->setAcceptDrops(true); + q->setFocusPolicy(Qt::WheelFocus); + q->setAttribute(Qt::WA_KeyCompression); + q->setAttribute(Qt::WA_InputMethodEnabled); + +#ifndef QT_NO_CURSOR + viewport->setCursor(Qt::IBeamCursor); +#endif +} + +void QPlainTextEditPrivate::_q_repaintContents(const QRectF &contentsRect) +{ + Q_Q(QPlainTextEdit); + if (!contentsRect.isValid()) { + updateViewport(); + return; + } + const int xOffset = horizontalOffset(); + const int yOffset = verticalOffset(); + const QRect visibleRect(xOffset, yOffset, viewport->width(), viewport->height()); + + QRect r = contentsRect.adjusted(-1, -1, 1, 1).intersected(visibleRect).toAlignedRect(); + if (r.isEmpty()) + return; + + r.translate(-xOffset, -yOffset); + viewport->update(r); + emit q->updateRequest(r, 0); +} + +void QPlainTextEditPrivate::pageUpDown(QTextCursor::MoveOperation op, QTextCursor::MoveMode moveMode, bool moveCursor) +{ + + Q_Q(QPlainTextEdit); + + QTextCursor cursor = control->textCursor(); + if (moveCursor) { + ensureCursorVisible(); + if (!pageUpDownLastCursorYIsValid) + pageUpDownLastCursorY = control->cursorRect(cursor).top() - verticalOffset(); + } + + qreal lastY = pageUpDownLastCursorY; + + + if (op == QTextCursor::Down) { + QRectF visible = QRectF(viewport->rect()).translated(-q->contentOffset()); + QTextBlock firstVisibleBlock = q->firstVisibleBlock(); + QTextBlock block = firstVisibleBlock; + QRectF br = q->blockBoundingRect(block); + qreal h = 0; + int atEnd = false; + while (h + br.height() <= visible.bottom()) { + if (!block.next().isValid()) { + atEnd = true; + lastY = visible.bottom(); // set cursor to last line + break; + } + h += br.height(); + block = block.next(); + br = q->blockBoundingRect(block); + } + + if (!atEnd) { + int line = 0; + qreal diff = visible.bottom() - h; + int lineCount = block.layout()->lineCount(); + while (line < lineCount - 1) { + if (block.layout()->lineAt(line).naturalTextRect().bottom() > diff) { + // the first line that did not completely fit the screen + break; + } + ++line; + } + setTopBlock(block.blockNumber(), line); + } + + if (moveCursor) { + // move using movePosition to keep the cursor's x + lastY += verticalOffset(); + bool moved = false; + do { + moved = cursor.movePosition(op, moveMode); + } while (moved && control->cursorRect(cursor).top() < lastY); + } + + } else if (op == QTextCursor::Up) { + + QRectF visible = QRectF(viewport->rect()).translated(-q->contentOffset()); + visible.translate(0, -visible.height()); // previous page + QTextBlock block = q->firstVisibleBlock(); + qreal h = 0; + while (h >= visible.top()) { + if (!block.previous().isValid()) { + if (control->topBlock == 0 && topLine == 0) { + lastY = 0; // set cursor to first line + } + break; + } + block = block.previous(); + QRectF br = q->blockBoundingRect(block); + h -= br.height(); + } + + int line = 0; + if (block.isValid()) { + qreal diff = visible.top() - h; + int lineCount = block.layout()->lineCount(); + while (line < lineCount) { + if (block.layout()->lineAt(line).naturalTextRect().top() >= diff) + break; + ++line; + } + if (line == lineCount) { + if (block.next().isValid() && block.next() != q->firstVisibleBlock()) { + block = block.next(); + line = 0; + } else { + --line; + } + } + } + setTopBlock(block.blockNumber(), line); + + if (moveCursor) { + // move using movePosition to keep the cursor's x + lastY += verticalOffset(); + bool moved = false; + do { + moved = cursor.movePosition(op, moveMode); + } while (moved && control->cursorRect(cursor).top() > lastY); + } + } + + if (moveCursor) { + control->setTextCursor(cursor); + pageUpDownLastCursorYIsValid = true; + } +} + +#ifndef QT_NO_SCROLLBAR + +void QPlainTextEditPrivate::_q_adjustScrollbars() +{ + Q_Q(QPlainTextEdit); + QTextDocument *doc = control->document(); + QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(doc->documentLayout()); + Q_ASSERT(documentLayout); + bool documentSizeChangedBlocked = documentLayout->priv()->blockDocumentSizeChanged; + documentLayout->priv()->blockDocumentSizeChanged = true; + qreal margin = doc->documentMargin(); + + int vmax = 0; + + int vSliderLength = 0; + if (!centerOnScroll && q->isVisible()) { + QTextBlock block = doc->lastBlock(); + const int visible = static_cast<int>(viewport->rect().height() - margin - 1); + int y = 0; + int visibleFromBottom = 0; + + while (block.isValid()) { + if (!block.isVisible()) { + block = block.previous(); + continue; + } + y += int(documentLayout->blockBoundingRect(block).height()); + + QTextLayout *layout = block.layout(); + int layoutLineCount = layout->lineCount(); + if (y > visible) { + int lineNumber = 0; + while (lineNumber < layoutLineCount) { + QTextLine line = layout->lineAt(lineNumber); + const QRectF lr = line.naturalTextRect(); + if (int(lr.top()) >= y - visible) + break; + ++lineNumber; + } + if (lineNumber < layoutLineCount) + visibleFromBottom += (layoutLineCount - lineNumber - 1); + break; + + } + visibleFromBottom += layoutLineCount; + block = block.previous(); + } + vmax = qMax(0, doc->lineCount() - visibleFromBottom); + vSliderLength = visibleFromBottom; + + } else { + vmax = qMax(0, doc->lineCount() - 1); + vSliderLength = viewport->height() / q->fontMetrics().lineSpacing(); + } + + + + QSizeF documentSize = documentLayout->documentSize(); + vbar->setRange(0, qMax(0, vmax)); + vbar->setPageStep(vSliderLength); + int visualTopLine = vmax; + QTextBlock firstVisibleBlock = q->firstVisibleBlock(); + if (firstVisibleBlock.isValid()) + visualTopLine = firstVisibleBlock.firstLineNumber() + topLine; + bool vbarSignalsBlocked = vbar->blockSignals(true); + vbar->setValue(visualTopLine); + vbar->blockSignals(vbarSignalsBlocked); + + hbar->setRange(0, (int)documentSize.width() - viewport->width()); + hbar->setPageStep(viewport->width()); + documentLayout->priv()->blockDocumentSizeChanged = documentSizeChangedBlocked; + setTopLine(vbar->value()); +} + +#endif + + +void QPlainTextEditPrivate::ensureViewportLayouted() +{ +} + +/*! + \class QPlainTextEdit + \since 4.4 + \brief The QPlainTextEdit class provides a widget that is used to edit and display + plain text. + + \ingroup text + \mainclass + + \tableofcontents + + \section1 Introduction and Concepts + + QPlainTextEdit is an advanced viewer/editor supporting plain + text. It is optimized to handle large documents and to respond + quickly to user input. + + QPlainText uses very much the same technology and concepts as + QTextEdit, but is optimized for plain text handling. + + QPlainTextEdit works on paragraphs and characters. A paragraph is a + formatted string which is word-wrapped to fit into the width of + the widget. By default when reading plain text, one newline + signifies a paragraph. A document consists of zero or more + paragraphs. The words in the paragraph are aligned in accordance + with the paragraph's alignment. Paragraphs are separated by hard + line breaks. Each character within a paragraph has its own + attributes, for example, font and color. + + The shape of the mouse cursor on a QPlainTextEdit is + Qt::IBeamCursor by default. It can be changed through the + viewport()'s cursor property. + + \section1 Using QPlainTextEdit as a Display Widget + + The text is set or replaced using setPlainText() which deletes any + existing text and replaces it with the text passed in the + setPlainText() call. + + Text itself can be inserted using the QTextCursor class or using + the convenience functins insertPlainText(), appendPlainText() or + paste(). + + By default the text edit wraps words at whitespace to fit within + the text edit widget. The setLineWrapMode() function is used to + specify the kind of line wrap you want, \l WidgetWidth or \l + NoWrap if you don't want any wrapping. If you use word wrap to + the widget's width \l WidgetWidth, you can specify whether to + break on whitespace or anywhere with setWordWrapMode(). + + The find() function can be used to find and select a given string + within the text. + + If you want to limit the total number of paragraphs in a + QPlainTextEdit, as it is for example useful in a log viewer, then + you can use the maximumBlockCount property. The combination of + setMaximumBlockCount() and appendPlainText() turns QPlainTextEdit + into an efficient viewer for log text. The scrolling can be + reduced with the centerOnScroll() property, making the log viewer + even faster. Text can be formatted in a limited way, either using + a syntax highlighter (see below), or by appending html-formatted + text with appendHtml(). While QPlainTextEdit does not support + complex rich text rendering with tables and floats, it does + support limited paragraph-based formatting that you may need in a + log viewer. + + \section2 Read-only Key Bindings + + When QPlainTextEdit is used read-only the key bindings are limited to + navigation, and text may only be selected with the mouse: + \table + \header \i Keypresses \i Action + \row \i Qt::UpArrow \i Moves one line up. + \row \i Qt::DownArrow \i Moves one line down. + \row \i Qt::LeftArrow \i Moves one character to the left. + \row \i Qt::RightArrow \i Moves one character to the right. + \row \i PageUp \i Moves one (viewport) page up. + \row \i PageDown \i Moves one (viewport) page down. + \row \i Home \i Moves to the beginning of the text. + \row \i End \i Moves to the end of the text. + \row \i Alt+Wheel + \i Scrolls the page horizontally (the Wheel is the mouse wheel). + \row \i Ctrl+Wheel \i Zooms the text. + \row \i Ctrl+A \i Selects all text. + \endtable + + + \section1 Using QPlainTextEdit as an Editor + + All the information about using QPlainTextEdit as a display widget also + applies here. + + Selection of text is handled by the QTextCursor class, which provides + functionality for creating selections, retrieving the text contents or + deleting selections. You can retrieve the object that corresponds with + the user-visible cursor using the textCursor() method. If you want to set + a selection in QPlainTextEdit just create one on a QTextCursor object and + then make that cursor the visible cursor using setCursor(). The selection + can be copied to the clipboard with copy(), or cut to the clipboard with + cut(). The entire text can be selected using selectAll(). + + QPlainTextEdit holds a QTextDocument object which can be retrieved using the + document() method. You can also set your own document object using setDocument(). + QTextDocument emits a textChanged() signal if the text changes and it also + provides a isModified() function which will return true if the text has been + modified since it was either loaded or since the last call to setModified + with false as argument. In addition it provides methods for undo and redo. + + \section2 Syntax Highlighting + + Just like QTextEdit, QPlainTextEdit works together with + QSyntaxHighlighter. + + \section2 Editing Key Bindings + + The list of key bindings which are implemented for editing: + \table + \header \i Keypresses \i Action + \row \i Backspace \i Deletes the character to the left of the cursor. + \row \i Delete \i Deletes the character to the right of the cursor. + \row \i Ctrl+C \i Copy the selected text to the clipboard. + \row \i Ctrl+Insert \i Copy the selected text to the clipboard. + \row \i Ctrl+K \i Deletes to the end of the line. + \row \i Ctrl+V \i Pastes the clipboard text into text edit. + \row \i Shift+Insert \i Pastes the clipboard text into text edit. + \row \i Ctrl+X \i Deletes the selected text and copies it to the clipboard. + \row \i Shift+Delete \i Deletes the selected text and copies it to the clipboard. + \row \i Ctrl+Z \i Undoes the last operation. + \row \i Ctrl+Y \i Redoes the last operation. + \row \i LeftArrow \i Moves the cursor one character to the left. + \row \i Ctrl+LeftArrow \i Moves the cursor one word to the left. + \row \i RightArrow \i Moves the cursor one character to the right. + \row \i Ctrl+RightArrow \i Moves the cursor one word to the right. + \row \i UpArrow \i Moves the cursor one line up. + \row \i Ctrl+UpArrow \i Moves the cursor one word up. + \row \i DownArrow \i Moves the cursor one line down. + \row \i Ctrl+Down Arrow \i Moves the cursor one word down. + \row \i PageUp \i Moves the cursor one page up. + \row \i PageDown \i Moves the cursor one page down. + \row \i Home \i Moves the cursor to the beginning of the line. + \row \i Ctrl+Home \i Moves the cursor to the beginning of the text. + \row \i End \i Moves the cursor to the end of the line. + \row \i Ctrl+End \i Moves the cursor to the end of the text. + \row \i Alt+Wheel \i Scrolls the page horizontally (the Wheel is the mouse wheel). + \row \i Ctrl+Wheel \i Zooms the text. + \endtable + + To select (mark) text hold down the Shift key whilst pressing one + of the movement keystrokes, for example, \e{Shift+Right Arrow} + will select the character to the right, and \e{Shift+Ctrl+Right + Arrow} will select the word to the right, etc. + + \section1 Differences to QTextEdit + + QPlainTextEdit is a thin class, implemented by using most of the + technology that is behind QTextEdit and QTextDocument. Its + performance benefits over QTextEdit stem mostly from using a + different and simplified text layout called + QPlainTextDocumentLayout on the text document (see + QTextDocument::setDocumentLayout()). The plain text document layout + does not support tables nor embedded frames, and \e{replaces a + pixel-exact height calculation with a line-by-line respectively + paragraph-by-paragraph scrolling approach}. This makes it possible + to handle significantly larger documents, and still resize the + editor with line wrap enabled in real time. It also makes for a + fast log viewer (see setMaximumBlockCount()). + + + \sa QTextDocument, QTextCursor, {Application Example}, + {Syntax Highlighter Example}, {Rich Text Processing} + +*/ + +/*! + \property QPlainTextEdit::plainText + + This property gets and sets the plain text editor's contents. The previous + contents are removed and undo/redo history is reset when this property is set. + + By default, for an editor with no contents, this property contains an empty string. +*/ + +/*! + \property QPlainTextEdit::undoRedoEnabled + \brief whether undo and redo are enabled + + Users are only able to undo or redo actions if this property is + true, and if there is an action that can be undone (or redone). + + By default, this property is true. +*/ + +/*! + \enum QPlainTextEdit::LineWrapMode + + \value NoWrap + \value WidgetWidth +*/ + + +/*! + Constructs an empty QPlainTextEdit with parent \a + parent. +*/ +QPlainTextEdit::QPlainTextEdit(QWidget *parent) + : QAbstractScrollArea(*new QPlainTextEditPrivate, parent) +{ + Q_D(QPlainTextEdit); + d->init(); +} + +/*! + \internal +*/ +QPlainTextEdit::QPlainTextEdit(QPlainTextEditPrivate &dd, QWidget *parent) + : QAbstractScrollArea(dd, parent) +{ + Q_D(QPlainTextEdit); + d->init(); +} + +/*! + Constructs a QPlainTextEdit with parent \a parent. The text edit will display + the plain text \a text. +*/ +QPlainTextEdit::QPlainTextEdit(const QString &text, QWidget *parent) + : QAbstractScrollArea(*new QPlainTextEditPrivate, parent) +{ + Q_D(QPlainTextEdit); + d->init(text); +} + + +/*! + Destructor. +*/ +QPlainTextEdit::~QPlainTextEdit() +{ + Q_D(QPlainTextEdit); + if (d->documentLayoutPtr) { + if (d->documentLayoutPtr->priv()->mainViewPrivate == d) + d->documentLayoutPtr->priv()->mainViewPrivate = 0; + } +} + +/*! + Makes \a document the new document of the text editor. + + The parent QObject of the provided document remains the owner + of the object. If the current document is a child of the text + editor, then it is deleted. + + The document must have a document layout that inherits + QPlainTextDocumentLayout (see QTextDocument::setDocumentLayout()). + + \sa document() +*/ +void QPlainTextEdit::setDocument(QTextDocument *document) +{ + Q_D(QPlainTextEdit); + QPlainTextDocumentLayout *documentLayout = 0; + + if (!document) { + document = new QTextDocument(d->control); + documentLayout = new QPlainTextDocumentLayout(document); + document->setDocumentLayout(documentLayout); + } else { + documentLayout = qobject_cast<QPlainTextDocumentLayout*>(document->documentLayout()); + if (!documentLayout) { + qWarning("QPlainTextEdit::setDocument: Document set does not support QPlainTextDocumentLayout"); + return; + } + } + d->control->setDocument(document); + if (!documentLayout->priv()->mainViewPrivate) + documentLayout->priv()->mainViewPrivate = d; + d->documentLayoutPtr = documentLayout; + d->updateDefaultTextOption(); + d->relayoutDocument(); + d->_q_adjustScrollbars(); +} + +/*! + Returns a pointer to the underlying document. + + \sa setDocument() +*/ +QTextDocument *QPlainTextEdit::document() const +{ + Q_D(const QPlainTextEdit); + return d->control->document(); +} + +/*! + Sets the visible \a cursor. +*/ +void QPlainTextEdit::setTextCursor(const QTextCursor &cursor) +{ + Q_D(QPlainTextEdit); + d->control->setTextCursor(cursor); +} + +/*! + Returns a copy of the QTextCursor that represents the currently visible cursor. + Note that changes on the returned cursor do not affect QPlainTextEdit's cursor; use + setTextCursor() to update the visible cursor. + */ +QTextCursor QPlainTextEdit::textCursor() const +{ + Q_D(const QPlainTextEdit); + return d->control->textCursor(); +} + + +/*! + Undoes the last operation. + + If there is no operation to undo, i.e. there is no undo step in + the undo/redo history, nothing happens. + + \sa redo() +*/ +void QPlainTextEdit::undo() +{ + Q_D(QPlainTextEdit); + d->control->undo(); +} + +void QPlainTextEdit::redo() +{ + Q_D(QPlainTextEdit); + d->control->redo(); +} + +/*! + \fn void QPlainTextEdit::redo() + + Redoes the last operation. + + If there is no operation to redo, i.e. there is no redo step in + the undo/redo history, nothing happens. + + \sa undo() +*/ + +#ifndef QT_NO_CLIPBOARD +/*! + Copies the selected text to the clipboard and deletes it from + the text edit. + + If there is no selected text nothing happens. + + \sa copy() paste() +*/ + +void QPlainTextEdit::cut() +{ + Q_D(QPlainTextEdit); + d->control->cut(); +} + +/*! + Copies any selected text to the clipboard. + + \sa copyAvailable() +*/ + +void QPlainTextEdit::copy() +{ + Q_D(QPlainTextEdit); + d->control->copy(); +} + +/*! + Pastes the text from the clipboard into the text edit at the + current cursor position. + + If there is no text in the clipboard nothing happens. + + To change the behavior of this function, i.e. to modify what + QPlainTextEdit can paste and how it is being pasted, reimplement the + virtual canInsertFromMimeData() and insertFromMimeData() + functions. + + \sa cut() copy() +*/ + +void QPlainTextEdit::paste() +{ + Q_D(QPlainTextEdit); + d->control->paste(); +} +#endif + +/*! + Deletes all the text in the text edit. + + Note that the undo/redo history is cleared by this function. + + \sa cut() setPlainText() +*/ +void QPlainTextEdit::clear() +{ + Q_D(QPlainTextEdit); + // clears and sets empty content + d->control->topBlock = d->topLine = 0; + d->control->clear(); +} + + +/*! + Selects all text. + + \sa copy() cut() textCursor() + */ +void QPlainTextEdit::selectAll() +{ + Q_D(QPlainTextEdit); + d->control->selectAll(); +} + +/*! \internal +*/ +bool QPlainTextEdit::event(QEvent *e) +{ + Q_D(QPlainTextEdit); + +#ifndef QT_NO_CONTEXTMENU + if (e->type() == QEvent::ContextMenu + && static_cast<QContextMenuEvent *>(e)->reason() == QContextMenuEvent::Keyboard) { + ensureCursorVisible(); + const QPoint cursorPos = cursorRect().center(); + QContextMenuEvent ce(QContextMenuEvent::Keyboard, cursorPos, d->viewport->mapToGlobal(cursorPos)); + ce.setAccepted(e->isAccepted()); + const bool result = QAbstractScrollArea::event(&ce); + e->setAccepted(ce.isAccepted()); + return result; + } +#endif // QT_NO_CONTEXTMENU + if (e->type() == QEvent::ShortcutOverride + || e->type() == QEvent::ToolTip) { + d->sendControlEvent(e); + } +#ifdef QT_KEYPAD_NAVIGATION + else if (e->type() == QEvent::EnterEditFocus || e->type() == QEvent::LeaveEditFocus) { + if (QApplication::keypadNavigationEnabled()) + d->sendControlEvent(e); + } +#endif + return QAbstractScrollArea::event(e); +} + +/*! \internal +*/ + +void QPlainTextEdit::timerEvent(QTimerEvent *e) +{ + Q_D(QPlainTextEdit); + if (e->timerId() == d->autoScrollTimer.timerId()) { + QRect visible = d->viewport->rect(); + QPoint pos; + if (d->inDrag) { + pos = d->autoScrollDragPos; + visible.adjust(qMin(visible.width()/3,20), qMin(visible.height()/3,20), + -qMin(visible.width()/3,20), -qMin(visible.height()/3,20)); + } else { + const QPoint globalPos = QCursor::pos(); + pos = d->viewport->mapFromGlobal(globalPos); + QMouseEvent ev(QEvent::MouseMove, pos, globalPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + mouseMoveEvent(&ev); + } + int deltaY = qMax(pos.y() - visible.top(), visible.bottom() - pos.y()) - visible.height(); + int deltaX = qMax(pos.x() - visible.left(), visible.right() - pos.x()) - visible.width(); + int delta = qMax(deltaX, deltaY); + if (delta >= 0) { + if (delta < 7) + delta = 7; + int timeout = 4900 / (delta * delta); + d->autoScrollTimer.start(timeout, this); + + if (deltaY > 0) + d->vbar->triggerAction(pos.y() < visible.center().y() ? + QAbstractSlider::SliderSingleStepSub + : QAbstractSlider::SliderSingleStepAdd); + if (deltaX > 0) + d->hbar->triggerAction(pos.x() < visible.center().x() ? + QAbstractSlider::SliderSingleStepSub + : QAbstractSlider::SliderSingleStepAdd); + } + } +#ifdef QT_KEYPAD_NAVIGATION + else if (e->timerId() == d->deleteAllTimer.timerId()) { + d->deleteAllTimer.stop(); + clear(); + } +#endif +} + +/*! + Changes the text of the text edit to the string \a text. + Any previous text is removed. + + \a text is interpreted as plain text. + + Note that the undo/redo history is cleared by this function. + + \sa toText() +*/ + +void QPlainTextEdit::setPlainText(const QString &text) +{ + Q_D(QPlainTextEdit); + d->control->setPlainText(text); +} + +/*! + \fn QString QPlainTextEdit::toPlainText() const + + Returns the text of the text edit as plain text. + + \sa QPlainTextEdit::setPlainText() + */ + +/*! \reimp +*/ +void QPlainTextEdit::keyPressEvent(QKeyEvent *e) +{ + Q_D(QPlainTextEdit); + +#ifdef QT_KEYPAD_NAVIGATION + switch (e->key()) { + case Qt::Key_Select: + if (QApplication::keypadNavigationEnabled()) { + if (!(d->control->textInteractionFlags() & Qt::LinksAccessibleByKeyboard)) + setEditFocus(!hasEditFocus()); + else { + if (!hasEditFocus()) + setEditFocus(true); + else { + QTextCursor cursor = d->control->textCursor(); + QTextCharFormat charFmt = cursor.charFormat(); + if (!cursor.hasSelection() || charFmt.anchorHref().isEmpty()) { + setEditFocus(false); + } + } + } + } + break; + case Qt::Key_Back: + case Qt::Key_No: + if (!QApplication::keypadNavigationEnabled() + || (QApplication::keypadNavigationEnabled() && !hasEditFocus())) { + e->ignore(); + return; + } + break; + default: + if (QApplication::keypadNavigationEnabled()) { + if (!hasEditFocus() && !(e->modifiers() & Qt::ControlModifier)) { + if (e->text()[0].isPrint()) { + setEditFocus(true); + clear(); + } else { + e->ignore(); + return; + } + } + } + break; + } +#endif + + if (!(d->control->textInteractionFlags() & Qt::TextEditable)) { + switch (e->key()) { + case Qt::Key_Space: + e->accept(); + if (e->modifiers() & Qt::ShiftModifier) + d->vbar->triggerAction(QAbstractSlider::SliderPageStepSub); + else + d->vbar->triggerAction(QAbstractSlider::SliderPageStepAdd); + break; + default: + d->sendControlEvent(e); + if (!e->isAccepted() && e->modifiers() == Qt::NoModifier) { + if (e->key() == Qt::Key_Home) { + d->vbar->triggerAction(QAbstractSlider::SliderToMinimum); + e->accept(); + } else if (e->key() == Qt::Key_End) { + d->vbar->triggerAction(QAbstractSlider::SliderToMaximum); + e->accept(); + } + } + if (!e->isAccepted()) { + QAbstractScrollArea::keyPressEvent(e); + } + } + return; + } + +#ifndef QT_NO_SHORTCUT + if (e == QKeySequence::MoveToPreviousPage) { + e->accept(); + d->pageUpDown(QTextCursor::Up, QTextCursor::MoveAnchor); + return; + } else if (e == QKeySequence::MoveToNextPage) { + e->accept(); + d->pageUpDown(QTextCursor::Down, QTextCursor::MoveAnchor); + return; + } else if (e == QKeySequence::SelectPreviousPage) { + e->accept(); + d->pageUpDown(QTextCursor::Up, QTextCursor::KeepAnchor); + return; + } else if (e ==QKeySequence::SelectNextPage) { + e->accept(); + d->pageUpDown(QTextCursor::Down, QTextCursor::KeepAnchor); + return; + } +#endif // QT_NO_SHORTCUT + + + d->sendControlEvent(e); +#ifdef QT_KEYPAD_NAVIGATION + if (!e->isAccepted()) { + switch (e->key()) { + case Qt::Key_Up: + case Qt::Key_Down: + if (QApplication::keypadNavigationEnabled()) { + // Cursor position didn't change, so we want to leave + // these keys to change focus. + e->ignore(); + return; + } + break; + case Qt::Key_Back: + if (!e->isAutoRepeat()) { + if (QApplication::keypadNavigationEnabled()) { + if (document()->isEmpty()) { + setEditFocus(false); + e->accept(); + } else if (!d->deleteAllTimer.isActive()) { + e->accept(); + d->deleteAllTimer.start(750, this); + } + } else { + e->ignore(); + return; + } + } + break; + default: break; + } + } +#endif +} + +/*! \reimp +*/ +void QPlainTextEdit::keyReleaseEvent(QKeyEvent *e) +{ +#ifdef QT_KEYPAD_NAVIGATION + Q_D(QPlainTextEdit); + if (QApplication::keypadNavigationEnabled()) { + if (!e->isAutoRepeat() && e->key() == Qt::Key_Back + && d->deleteAllTimer.isActive()) { + d->deleteAllTimer.stop(); + QTextCursor cursor = d->control->textCursor(); + QTextBlockFormat blockFmt = cursor.blockFormat(); + + QTextList *list = cursor.currentList(); + if (list && cursor.atBlockStart()) { + list->remove(cursor.block()); + } else if (cursor.atBlockStart() && blockFmt.indent() > 0) { + blockFmt.setIndent(blockFmt.indent() - 1); + cursor.setBlockFormat(blockFmt); + } else { + cursor.deletePreviousChar(); + } + setTextCursor(cursor); + } + } +#else + Q_UNUSED(e); +#endif +} + +/*! + Loads the resource specified by the given \a type and \a name. + + This function is an extension of QTextDocument::loadResource(). + + \sa QTextDocument::loadResource() +*/ +QVariant QPlainTextEdit::loadResource(int type, const QUrl &name) +{ + Q_UNUSED(type); + Q_UNUSED(name); + return QVariant(); +} + +/*! \reimp +*/ +void QPlainTextEdit::resizeEvent(QResizeEvent *e) +{ + Q_D(QPlainTextEdit); + if (e->oldSize().width() != e->size().width()) + d->relayoutDocument(); + d->_q_adjustScrollbars(); +} + +void QPlainTextEditPrivate::relayoutDocument() +{ + QTextDocument *doc = control->document(); + QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(doc->documentLayout()); + Q_ASSERT(documentLayout); + documentLayoutPtr = documentLayout; + + int width = viewport->width(); + + if (documentLayout->priv()->mainViewPrivate == 0 + || documentLayout->priv()->mainViewPrivate == this + || width > documentLayout->textWidth()) { + documentLayout->priv()->mainViewPrivate = this; + documentLayout->setTextWidth(width); + } +} + +static void fillBackground(QPainter *p, const QRectF &rect, QBrush brush, QRectF gradientRect = QRectF()) +{ + p->save(); + if (brush.style() >= Qt::LinearGradientPattern && brush.style() <= Qt::ConicalGradientPattern) { + if (!gradientRect.isNull()) { + QTransform m; + m.translate(gradientRect.left(), gradientRect.top()); + m.scale(gradientRect.width(), gradientRect.height()); + brush.setTransform(m); + const_cast<QGradient *>(brush.gradient())->setCoordinateMode(QGradient::LogicalMode); + } + } else { + p->setBrushOrigin(rect.topLeft()); + } + p->fillRect(rect, brush); + p->restore(); +} + + + +/*! \reimp +*/ +void QPlainTextEdit::paintEvent(QPaintEvent *e) +{ + QPainter painter(viewport()); + Q_ASSERT(qobject_cast<QPlainTextDocumentLayout*>(document()->documentLayout())); + + QPointF offset(contentOffset()); + + QRect er = e->rect(); + QRect viewportRect = viewport()->rect(); + + bool editable = !isReadOnly(); + + QTextBlock block = firstVisibleBlock(); + qreal maximumWidth = document()->documentLayout()->documentSize().width(); + + // keep right margin clean from full-width selection + int maxX = offset.x() + qMax((qreal)viewportRect.width(), maximumWidth) + - document()->documentMargin(); + er.setRight(qMin(er.right(), maxX)); + painter.setClipRect(er); + + + QAbstractTextDocumentLayout::PaintContext context = getPaintContext(); + + while (block.isValid()) { + + QRectF r = blockBoundingRect(block).translated(offset); + QTextLayout *layout = block.layout(); + + if (!block.isVisible()) { + offset.ry() += r.height(); + block = block.next(); + continue; + } + + if (r.bottom() >= er.top() && r.top() <= er.bottom()) { + + QTextBlockFormat blockFormat = block.blockFormat(); + + QBrush bg = blockFormat.background(); + if (bg != Qt::NoBrush) { + QRectF contentsRect = r; + contentsRect.setWidth(qMax(r.width(), maximumWidth)); + fillBackground(&painter, contentsRect, bg); + } + + + QVector<QTextLayout::FormatRange> selections; + int blpos = block.position(); + int bllen = block.length(); + for (int i = 0; i < context.selections.size(); ++i) { + const QAbstractTextDocumentLayout::Selection &range = context.selections.at(i); + const int selStart = range.cursor.selectionStart() - blpos; + const int selEnd = range.cursor.selectionEnd() - blpos; + if (selStart < bllen && selEnd > 0 + && selEnd > selStart) { + QTextLayout::FormatRange o; + o.start = selStart; + o.length = selEnd - selStart; + o.format = range.format; + selections.append(o); + } else if (!range.cursor.hasSelection() && range.format.hasProperty(QTextFormat::FullWidthSelection) + && block.contains(range.cursor.position())) { + // for full width selections we don't require an actual selection, just + // a position to specify the line. that's more convenience in usage. + QTextLayout::FormatRange o; + QTextLine l = layout->lineForTextPosition(range.cursor.position() - blpos); + o.start = l.textStart(); + o.length = l.textLength(); + if (o.start + o.length == bllen - 1) + ++o.length; // include newline + o.format = range.format; + selections.append(o); + } + } + + bool drawCursor = (editable + && context.cursorPosition >= blpos + && context.cursorPosition < blpos + bllen); + + bool drawCursorAsBlock = drawCursor && overwriteMode() ; + + if (drawCursorAsBlock) { + if (context.cursorPosition == blpos + bllen - 1) { + drawCursorAsBlock = false; + } else { + QTextLayout::FormatRange o; + o.start = context.cursorPosition - blpos; + o.length = 1; + o.format.setForeground(palette().base()); + o.format.setBackground(palette().text()); + selections.append(o); + } + } + + + layout->draw(&painter, offset, selections, er); + if ((drawCursor && !drawCursorAsBlock) + || (editable && context.cursorPosition < -1 + && !layout->preeditAreaText().isEmpty())) { + int cpos = context.cursorPosition; + if (cpos < -1) + cpos = layout->preeditAreaPosition() - (cpos + 2); + else + cpos -= blpos; + layout->drawCursor(&painter, offset, cpos, cursorWidth()); + } + } + + offset.ry() += r.height(); + if (offset.y() > viewportRect.height()) + break; + block = block.next(); + } + + if (backgroundVisible() && !block.isValid() && offset.y() <= er.bottom() + && (centerOnScroll() || verticalScrollBar()->maximum() == verticalScrollBar()->minimum())) { + painter.fillRect(QRect(QPoint((int)er.left(), (int)offset.y()), er.bottomRight()), palette().background()); + } +} + + +void QPlainTextEditPrivate::updateDefaultTextOption() +{ + QTextDocument *doc = control->document(); + + QTextOption opt = doc->defaultTextOption(); + QTextOption::WrapMode oldWrapMode = opt.wrapMode(); + + if (lineWrap == QPlainTextEdit::NoWrap) + opt.setWrapMode(QTextOption::NoWrap); + else + opt.setWrapMode(wordWrap); + + if (opt.wrapMode() != oldWrapMode) + doc->setDefaultTextOption(opt); +} + + +/*! \reimp +*/ +void QPlainTextEdit::mousePressEvent(QMouseEvent *e) +{ + Q_D(QPlainTextEdit); +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) + setEditFocus(true); +#endif + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QPlainTextEdit::mouseMoveEvent(QMouseEvent *e) +{ + Q_D(QPlainTextEdit); + d->inDrag = false; // paranoia + const QPoint pos = e->pos(); + d->sendControlEvent(e); + if (!(e->buttons() & Qt::LeftButton)) + return; + QRect visible = d->viewport->rect(); + if (visible.contains(pos)) + d->autoScrollTimer.stop(); + else if (!d->autoScrollTimer.isActive()) + d->autoScrollTimer.start(100, this); +} + +/*! \reimp +*/ +void QPlainTextEdit::mouseReleaseEvent(QMouseEvent *e) +{ + Q_D(QPlainTextEdit); + d->sendControlEvent(e); + if (d->autoScrollTimer.isActive()) { + d->autoScrollTimer.stop(); + d->ensureCursorVisible(); + } +} + +/*! \reimp +*/ +void QPlainTextEdit::mouseDoubleClickEvent(QMouseEvent *e) +{ + Q_D(QPlainTextEdit); + d->sendControlEvent(e); +} + +/*! \reimp +*/ +bool QPlainTextEdit::focusNextPrevChild(bool next) +{ + Q_D(const QPlainTextEdit); + if (!d->tabChangesFocus && d->control->textInteractionFlags() & Qt::TextEditable) + return false; + return QAbstractScrollArea::focusNextPrevChild(next); +} + +#ifndef QT_NO_CONTEXTMENU +/*! + \fn void QPlainTextEdit::contextMenuEvent(QContextMenuEvent *event) + + Shows the standard context menu created with createStandardContextMenu(). + + If you do not want the text edit to have a context menu, you can set + its \l contextMenuPolicy to Qt::NoContextMenu. If you want to + customize the context menu, reimplement this function. If you want + to extend the standard context menu, reimplement this function, call + createStandardContextMenu() and extend the menu returned. + + Information about the event is passed in the \a event object. + + \snippet doc/src/snippets/code/src_gui_widgets_qplaintextedit.cpp 0 +*/ +void QPlainTextEdit::contextMenuEvent(QContextMenuEvent *e) +{ + Q_D(QPlainTextEdit); + d->sendControlEvent(e); +} +#endif // QT_NO_CONTEXTMENU + +#ifndef QT_NO_DRAGANDDROP +/*! \reimp +*/ +void QPlainTextEdit::dragEnterEvent(QDragEnterEvent *e) +{ + Q_D(QPlainTextEdit); + d->inDrag = true; + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QPlainTextEdit::dragLeaveEvent(QDragLeaveEvent *e) +{ + Q_D(QPlainTextEdit); + d->inDrag = false; + d->autoScrollTimer.stop(); + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QPlainTextEdit::dragMoveEvent(QDragMoveEvent *e) +{ + Q_D(QPlainTextEdit); + d->autoScrollDragPos = e->pos(); + if (!d->autoScrollTimer.isActive()) + d->autoScrollTimer.start(100, this); + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QPlainTextEdit::dropEvent(QDropEvent *e) +{ + Q_D(QPlainTextEdit); + d->inDrag = false; + d->autoScrollTimer.stop(); + d->sendControlEvent(e); +} + +#endif // QT_NO_DRAGANDDROP + +/*! \reimp + */ +void QPlainTextEdit::inputMethodEvent(QInputMethodEvent *e) +{ + Q_D(QPlainTextEdit); +#ifdef QT_KEYPAD_NAVIGATION + if (d->control->textInteractionFlags() & Qt::TextEditable + && QApplication::keypadNavigationEnabled() + && !hasEditFocus()) { + setEditFocus(true); + selectAll(); // so text is replaced rather than appended to + } +#endif + d->sendControlEvent(e); +} + +/*!\reimp +*/ +void QPlainTextEdit::scrollContentsBy(int dx, int /*dy*/) +{ + Q_D(QPlainTextEdit); + d->setTopLine(d->vbar->value(), dx); +} + +/*!\reimp +*/ +QVariant QPlainTextEdit::inputMethodQuery(Qt::InputMethodQuery property) const +{ + Q_D(const QPlainTextEdit); + QVariant v = d->control->inputMethodQuery(property); + const QPoint offset(-d->horizontalOffset(), -0); + if (v.type() == QVariant::RectF) + v = v.toRectF().toRect().translated(offset); + else if (v.type() == QVariant::PointF) + v = v.toPointF().toPoint() + offset; + else if (v.type() == QVariant::Rect) + v = v.toRect().translated(offset); + else if (v.type() == QVariant::Point) + v = v.toPoint() + offset; + return v; +} + +/*! \reimp +*/ +void QPlainTextEdit::focusInEvent(QFocusEvent *e) +{ + Q_D(QPlainTextEdit); + QAbstractScrollArea::focusInEvent(e); + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QPlainTextEdit::focusOutEvent(QFocusEvent *e) +{ + Q_D(QPlainTextEdit); + QAbstractScrollArea::focusOutEvent(e); + d->sendControlEvent(e); +} + +/*! \reimp +*/ +void QPlainTextEdit::showEvent(QShowEvent *) +{ + Q_D(QPlainTextEdit); + if (d->showCursorOnInitialShow) { + d->showCursorOnInitialShow = false; + ensureCursorVisible(); + } +} + +/*! \reimp +*/ +void QPlainTextEdit::changeEvent(QEvent *e) +{ + Q_D(QPlainTextEdit); + QAbstractScrollArea::changeEvent(e); + if (e->type() == QEvent::ApplicationFontChange + || e->type() == QEvent::FontChange) { + d->control->document()->setDefaultFont(font()); + } else if(e->type() == QEvent::ActivationChange) { + if (!isActiveWindow()) + d->autoScrollTimer.stop(); + } else if (e->type() == QEvent::EnabledChange) { + e->setAccepted(isEnabled()); + d->sendControlEvent(e); + } else if (e->type() == QEvent::PaletteChange) { + d->control->setPalette(palette()); + } else if (e->type() == QEvent::LayoutDirectionChange) { + d->sendControlEvent(e); + } +} + +/*! \reimp +*/ +#ifndef QT_NO_WHEELEVENT +void QPlainTextEdit::wheelEvent(QWheelEvent *e) +{ + QAbstractScrollArea::wheelEvent(e); + updateMicroFocus(); +} +#endif + +#ifndef QT_NO_CONTEXTMENU +/*! This function creates the standard context menu which is shown + when the user clicks on the line edit with the right mouse + button. It is called from the default contextMenuEvent() handler. + The popup menu's ownership is transferred to the caller. +*/ + +QMenu *QPlainTextEdit::createStandardContextMenu() +{ + Q_D(QPlainTextEdit); + return d->control->createStandardContextMenu(QPointF(), this); +} +#endif // QT_NO_CONTEXTMENU + +/*! + returns a QTextCursor at position \a pos (in viewport coordinates). +*/ +QTextCursor QPlainTextEdit::cursorForPosition(const QPoint &pos) const +{ + Q_D(const QPlainTextEdit); + return d->control->cursorForPosition(d->mapToContents(pos)); +} + +/*! + returns a rectangle (in viewport coordinates) that includes the + \a cursor. + */ +QRect QPlainTextEdit::cursorRect(const QTextCursor &cursor) const +{ + Q_D(const QPlainTextEdit); + if (cursor.isNull()) + return QRect(); + + QRect r = d->control->cursorRect(cursor).toRect(); + r.translate(-d->horizontalOffset(),-d->verticalOffset()); + return r; +} + +/*! + returns a rectangle (in viewport coordinates) that includes the + cursor of the text edit. + */ +QRect QPlainTextEdit::cursorRect() const +{ + Q_D(const QPlainTextEdit); + QRect r = d->control->cursorRect().toRect(); + r.translate(-d->horizontalOffset(),-d->verticalOffset()); + return r; +} + + +/*! + \property QPlainTextEdit::overwriteMode + \brief whether text entered by the user will overwrite existing text + + As with many text editors, the plain text editor widget can be configured + to insert or overwrite existing text with new text entered by the user. + + If this property is true, existing text is overwritten, character-for-character + by new text; otherwise, text is inserted at the cursor position, displacing + existing text. + + By default, this property is false (new text does not overwrite existing text). +*/ + +bool QPlainTextEdit::overwriteMode() const +{ + Q_D(const QPlainTextEdit); + return d->control->overwriteMode(); +} + +void QPlainTextEdit::setOverwriteMode(bool overwrite) +{ + Q_D(QPlainTextEdit); + d->control->setOverwriteMode(overwrite); +} + +/*! + \property QPlainTextEdit::tabStopWidth + \brief the tab stop width in pixels + + By default, this property contains a value of 80. +*/ + +int QPlainTextEdit::tabStopWidth() const +{ + Q_D(const QPlainTextEdit); + return qRound(d->control->document()->defaultTextOption().tabStop()); +} + +void QPlainTextEdit::setTabStopWidth(int width) +{ + Q_D(QPlainTextEdit); + QTextOption opt = d->control->document()->defaultTextOption(); + if (opt.tabStop() == width || width < 0) + return; + opt.setTabStop(width); + d->control->document()->setDefaultTextOption(opt); +} + +/*! + \property QPlainTextEdit::cursorWidth + + This property specifies the width of the cursor in pixels. The default value is 1. +*/ +int QPlainTextEdit::cursorWidth() const +{ + Q_D(const QPlainTextEdit); + return d->control->cursorWidth(); +} + +void QPlainTextEdit::setCursorWidth(int width) +{ + Q_D(QPlainTextEdit); + d->control->setCursorWidth(width); +} + + + +/*! + This function allows temporarily marking certain regions in the document + with a given color, specified as \a selections. This can be useful for + example in a programming editor to mark a whole line of text with a given + background color to indicate the existence of a breakpoint. + + \sa QTextEdit::ExtraSelection, extraSelections() +*/ +void QPlainTextEdit::setExtraSelections(const QList<QTextEdit::ExtraSelection> &selections) +{ + Q_D(QPlainTextEdit); + d->control->setExtraSelections(selections); +} + +/*! + Returns previously set extra selections. + + \sa setExtraSelections() +*/ +QList<QTextEdit::ExtraSelection> QPlainTextEdit::extraSelections() const +{ + Q_D(const QPlainTextEdit); + return d->control->extraSelections(); +} + +/*! + This function returns a new MIME data object to represent the contents + of the text edit's current selection. It is called when the selection needs + to be encapsulated into a new QMimeData object; for example, when a drag + and drop operation is started, or when data is copied to the clipboard. + + If you reimplement this function, note that the ownership of the returned + QMimeData object is passed to the caller. The selection can be retrieved + by using the textCursor() function. +*/ +QMimeData *QPlainTextEdit::createMimeDataFromSelection() const +{ + Q_D(const QPlainTextEdit); + return d->control->QTextControl::createMimeDataFromSelection(); +} + +/*! + This function returns true if the contents of the MIME data object, specified + by \a source, can be decoded and inserted into the document. It is called + for example when during a drag operation the mouse enters this widget and it + is necessary to determine whether it is possible to accept the drag. + */ +bool QPlainTextEdit::canInsertFromMimeData(const QMimeData *source) const +{ + Q_D(const QPlainTextEdit); + return d->control->QTextControl::canInsertFromMimeData(source); +} + +/*! + This function inserts the contents of the MIME data object, specified + by \a source, into the text edit at the current cursor position. It is + called whenever text is inserted as the result of a clipboard paste + operation, or when the text edit accepts data from a drag and drop + operation. +*/ +void QPlainTextEdit::insertFromMimeData(const QMimeData *source) +{ + Q_D(QPlainTextEdit); + d->control->QTextControl::insertFromMimeData(source); +} + +/*! + \property QPlainTextEdit::readOnly + \brief whether the text edit is read-only + + In a read-only text edit the user can only navigate through the + text and select text; modifying the text is not possible. + + This property's default is false. +*/ + +bool QPlainTextEdit::isReadOnly() const +{ + Q_D(const QPlainTextEdit); + return !(d->control->textInteractionFlags() & Qt::TextEditable); +} + +void QPlainTextEdit::setReadOnly(bool ro) +{ + Q_D(QPlainTextEdit); + Qt::TextInteractionFlags flags = Qt::NoTextInteraction; + if (ro) { + flags = Qt::TextSelectableByMouse; + } else { + flags = Qt::TextEditorInteraction; + } + setAttribute(Qt::WA_InputMethodEnabled, !ro); + d->control->setTextInteractionFlags(flags); +} + +/*! + \property QPlainTextEdit::textInteractionFlags + + Specifies how the label should interact with user input if it displays text. + + If the flags contain either Qt::LinksAccessibleByKeyboard or Qt::TextSelectableByKeyboard + then the focus policy is also automatically set to Qt::ClickFocus. + + The default value depends on whether the QPlainTextEdit is read-only + or editable, and whether it is a QTextBrowser or not. +*/ + +void QPlainTextEdit::setTextInteractionFlags(Qt::TextInteractionFlags flags) +{ + Q_D(QPlainTextEdit); + d->control->setTextInteractionFlags(flags); +} + +Qt::TextInteractionFlags QPlainTextEdit::textInteractionFlags() const +{ + Q_D(const QPlainTextEdit); + return d->control->textInteractionFlags(); +} + +/*! + Merges the properties specified in \a modifier into the current character + format by calling QTextCursor::mergeCharFormat on the editor's cursor. + If the editor has a selection then the properties of \a modifier are + directly applied to the selection. + + \sa QTextCursor::mergeCharFormat() + */ +void QPlainTextEdit::mergeCurrentCharFormat(const QTextCharFormat &modifier) +{ + Q_D(QPlainTextEdit); + d->control->mergeCurrentCharFormat(modifier); +} + +/*! + Sets the char format that is be used when inserting new text to \a + format by calling QTextCursor::setCharFormat() on the editor's + cursor. If the editor has a selection then the char format is + directly applied to the selection. + */ +void QPlainTextEdit::setCurrentCharFormat(const QTextCharFormat &format) +{ + Q_D(QPlainTextEdit); + d->control->setCurrentCharFormat(format); +} + +/*! + Returns the char format that is used when inserting new text. + */ +QTextCharFormat QPlainTextEdit::currentCharFormat() const +{ + Q_D(const QPlainTextEdit); + return d->control->currentCharFormat(); +} + + + +/*! + Convenience slot that inserts \a text at the current + cursor position. + + It is equivalent to + + \snippet doc/src/snippets/code/src_gui_widgets_qplaintextedit.cpp 1 + */ +void QPlainTextEdit::insertPlainText(const QString &text) +{ + Q_D(QPlainTextEdit); + d->control->insertPlainText(text); +} + + +/*! + Moves the cursor by performing the given \a operation. + + If \a mode is QTextCursor::KeepAnchor, the cursor selects the text it moves over. + This is the same effect that the user achieves when they hold down the Shift key + and move the cursor with the cursor keys. + + \sa QTextCursor::movePosition() +*/ +void QPlainTextEdit::moveCursor(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode) +{ + Q_D(QPlainTextEdit); + d->control->moveCursor(operation, mode); +} + +/*! + Returns whether text can be pasted from the clipboard into the textedit. +*/ +bool QPlainTextEdit::canPaste() const +{ + Q_D(const QPlainTextEdit); + return d->control->canPaste(); +} + +#ifndef QT_NO_PRINTER +/*! + Convenience function to print the text edit's document to the given \a printer. This + is equivalent to calling the print method on the document directly except that this + function also supports QPrinter::Selection as print range. + + \sa QTextDocument::print() +*/ +void QPlainTextEdit::print(QPrinter *printer) const +{ + Q_D(const QPlainTextEdit); + d->control->print(printer); +} +#endif // QT _NO_PRINTER + +/*! \property QPlainTextEdit::tabChangesFocus + \brief whether \gui Tab changes focus or is accepted as input + + In some occasions text edits should not allow the user to input + tabulators or change indentation using the \gui Tab key, as this breaks + the focus chain. The default is false. + +*/ + +bool QPlainTextEdit::tabChangesFocus() const +{ + Q_D(const QPlainTextEdit); + return d->tabChangesFocus; +} + +void QPlainTextEdit::setTabChangesFocus(bool b) +{ + Q_D(QPlainTextEdit); + d->tabChangesFocus = b; +} + +/*! + \property QPlainTextEdit::documentTitle + \brief the title of the document parsed from the text. + + By default, this property contains an empty string. +*/ + +/*! + \property QPlainTextEdit::lineWrapMode + \brief the line wrap mode + + The default mode is WidgetWidth which causes words to be + wrapped at the right edge of the text edit. Wrapping occurs at + whitespace, keeping whole words intact. If you want wrapping to + occur within words use setWordWrapMode(). +*/ + +QPlainTextEdit::LineWrapMode QPlainTextEdit::lineWrapMode() const +{ + Q_D(const QPlainTextEdit); + return d->lineWrap; +} + +void QPlainTextEdit::setLineWrapMode(LineWrapMode wrap) +{ + Q_D(QPlainTextEdit); + if (d->lineWrap == wrap) + return; + d->lineWrap = wrap; + d->updateDefaultTextOption(); + d->relayoutDocument(); + d->_q_adjustScrollbars(); + ensureCursorVisible(); +} + +/*! + \property QPlainTextEdit::wordWrapMode + \brief the mode QPlainTextEdit will use when wrapping text by words + + By default, this property is set to QTextOption::WrapAtWordBoundaryOrAnywhere. + + \sa QTextOption::WrapMode +*/ + +QTextOption::WrapMode QPlainTextEdit::wordWrapMode() const +{ + Q_D(const QPlainTextEdit); + return d->wordWrap; +} + +void QPlainTextEdit::setWordWrapMode(QTextOption::WrapMode mode) +{ + Q_D(QPlainTextEdit); + if (mode == d->wordWrap) + return; + d->wordWrap = mode; + d->updateDefaultTextOption(); +} + +/*! + \property QPlainTextEdit::backgroundVisible + \brief whether the palette background is visible outside the document area + + If set to true, the plain text edit paints the palette background + on the viewport area not covered by the text document. Otherwise, + if set to false, it won't. The feature makes it possible for + the user to visually distinguish between the area of the document, + painted with the base color of the palette, and the empty + area not covered by any document. + + The default is false. +*/ + +bool QPlainTextEdit::backgroundVisible() const +{ + Q_D(const QPlainTextEdit); + return d->backgroundVisible; +} + +void QPlainTextEdit::setBackgroundVisible(bool visible) +{ + Q_D(QPlainTextEdit); + if (visible == d->backgroundVisible) + return; + d->backgroundVisible = visible; + d->updateViewport(); +} + +/*! + \property QPlainTextEdit::centerOnScroll + \brief whether the cursor should be centered on screen + + If set to true, the plain text edit scrolls the document + vertically to make the cursor visible at the center of the + viewport. This also allows the text edit to scroll below the end + of the document. Otherwise, if set to false, the plain text edit + scrolls the smallest amount possible to ensure the cursor is + visible. The same algorithm is applied to any new line appended + through appendPlainText(). + + The default is false. + + \sa centerCursor(), ensureCursorVisible() +*/ + +bool QPlainTextEdit::centerOnScroll() const +{ + Q_D(const QPlainTextEdit); + return d->centerOnScroll; +} + +void QPlainTextEdit::setCenterOnScroll(bool enabled) +{ + Q_D(QPlainTextEdit); + if (enabled == d->centerOnScroll) + return; + d->centerOnScroll = enabled; +} + + + +/*! + Finds the next occurrence of the string, \a exp, using the given + \a options. Returns true if \a exp was found and changes the + cursor to select the match; otherwise returns false. +*/ +bool QPlainTextEdit::find(const QString &exp, QTextDocument::FindFlags options) +{ + Q_D(QPlainTextEdit); + return d->control->find(exp, options); +} + +/*! + \fn void QPlainTextEdit::copyAvailable(bool yes) + + This signal is emitted when text is selected or de-selected in the + text edit. + + When text is selected this signal will be emitted with \a yes set + to true. If no text has been selected or if the selected text is + de-selected this signal is emitted with \a yes set to false. + + If \a yes is true then copy() can be used to copy the selection to + the clipboard. If \a yes is false then copy() does nothing. + + \sa selectionChanged() +*/ + + +/*! + \fn void QPlainTextEdit::selectionChanged() + + This signal is emitted whenever the selection changes. + + \sa copyAvailable() +*/ + +/*! + \fn void QPlainTextEdit::cursorPositionChanged() + + This signal is emitted whenever the position of the + cursor changed. +*/ + + + +/*! + \fn void QPlainTextEdit::updateRequest(const QRect &rect, int dy) + + This signal is emitted when the text document needs an update of + the specified \a rect. If the text is scrolled, \a rect will cover + the entire viewport area. If the text is scrolled vertically, \a + dy carries the amount of pixels the viewport was scrolled. + + The purpose of the signal is to support extra widgets in plain + text edit subclasses that e.g. show line numbers, breakpoints, or + other extra information. +*/ + +/*! \fn void QPlainTextEdit::blockCountChanged(int newBlockCount); + + This signal is emitted whenever the block count changes. The new + block count is passed in \a newBlockCount. +*/ + +/*! \fn void QPlainTextEdit::modificationChanged(bool changed); + + This signal is emitted whenever the content of the document + changes in a way that affects the modification state. If \a + changed is true, the document has been modified; otherwise it is + false. + + For example, calling setModified(false) on a document and then + inserting text causes the signal to get emitted. If you undo that + operation, causing the document to return to its original + unmodified state, the signal will get emitted again. +*/ + + + + +void QPlainTextEditPrivate::append(const QString &text, Qt::TextFormat format) +{ + Q_Q(QPlainTextEdit); + + QTextDocument *document = control->document(); + QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(document->documentLayout()); + Q_ASSERT(documentLayout); + + int maximumBlockCount = document->maximumBlockCount(); + if (maximumBlockCount) + document->setMaximumBlockCount(0); + + const bool atBottom = q->isVisible() + && (control->blockBoundingRect(document->lastBlock()).bottom() - verticalOffset() + <= viewport->rect().bottom()); + + if (!q->isVisible()) + showCursorOnInitialShow = true; + + bool documentSizeChangedBlocked = documentLayout->priv()->blockDocumentSizeChanged; + documentLayout->priv()->blockDocumentSizeChanged = true; + + if (format == Qt::RichText) + control->appendHtml(text); + else if (format == Qt::PlainText) + control->appendPlainText(text); + else + control->append(text); + + if (maximumBlockCount > 0) { + if (document->blockCount() > maximumBlockCount) { + bool blockUpdate = false; + if (control->topBlock) { + control->topBlock--; + blockUpdate = true; + emit q->updateRequest(viewport->rect(), 0); + } + + bool updatesBlocked = documentLayout->priv()->blockUpdate; + documentLayout->priv()->blockUpdate = blockUpdate; + QTextCursor cursor(document); + cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + documentLayout->priv()->blockUpdate = updatesBlocked; + } + document->setMaximumBlockCount(maximumBlockCount); + } + + documentLayout->priv()->blockDocumentSizeChanged = documentSizeChangedBlocked; + _q_adjustScrollbars(); + + + if (atBottom) { + const bool needScroll = !centerOnScroll + || control->blockBoundingRect(document->lastBlock()).bottom() - verticalOffset() + > viewport->rect().bottom(); + if (needScroll) + vbar->setValue(vbar->maximum()); + } +} + + +/*! + Appends a new paragraph with \a text to the end of the text edit. + + \sa appendHtml() +*/ + +void QPlainTextEdit::appendPlainText(const QString &text) +{ + Q_D(QPlainTextEdit); + d->append(text, Qt::PlainText); +} + +/*! + Appends a new paragraph with \a html to the end of the text edit. + + appendPlainText() +*/ + +void QPlainTextEdit::appendHtml(const QString &html) +{ + Q_D(QPlainTextEdit); + d->append(html, Qt::RichText); +} + +void QPlainTextEditPrivate::ensureCursorVisible(bool center) +{ + Q_Q(QPlainTextEdit); + QRect visible = viewport->rect(); + QRect cr = q->cursorRect(); + if (cr.top() < visible.top() || cr.bottom() > visible.bottom()) { + ensureVisible(control->textCursor().position(), center); + } + + const bool rtl = q->isRightToLeft(); + if (cr.left() < visible.left() || cr.right() > visible.right()) { + int x = cr.center().x() + horizontalOffset() - visible.width()/2; + hbar->setValue(rtl ? hbar->maximum() - x : x); + } +} + +/*! + Ensures that the cursor is visible by scrolling the text edit if + necessary. + + \sa centerCursor(), centerOnScroll +*/ +void QPlainTextEdit::ensureCursorVisible() +{ + Q_D(QPlainTextEdit); + d->ensureCursorVisible(d->centerOnScroll); +} + + +/*! Scrolls the document in order to center the cursor vertically. + +\sa ensureCursorVisible(), centerOnScroll + */ +void QPlainTextEdit::centerCursor() +{ + Q_D(QPlainTextEdit); + d->ensureVisible(textCursor().position(), true, true); +} + +/*! + Returns the first visible block. + + \sa blockBoundingRect() + */ +QTextBlock QPlainTextEdit::firstVisibleBlock() const +{ + Q_D(const QPlainTextEdit); + return d->control->firstVisibleBlock(); +} + +/*! Returns the content's origin in viewport coordinates. + + The origin of the content of a plain text edit is always the top + left corner of the first visible text block. The content offset + is different from (0,0) when the text has been scrolled + horizontally, or when the first visible block has been scrolled + partially off the screen, i.e. the visible text does not start + with the first line of the first visible block, or when the first + visible block is the very first block and the editor displays a + margin. + + \sa firstVisibleBlock(), horizontalScrollBar(), verticalScrollBar() + */ +QPointF QPlainTextEdit::contentOffset() const +{ + Q_D(const QPlainTextEdit); + return QPointF(-d->horizontalOffset(), -d->verticalOffset()); +} + + +/*! Returns the bounding rectangle of the text \a block in content + coordinates. Translate the rectangle with the contentOffset() to get + visual coordinates on the viewport. + + \sa firstVisibleBlock(), blockBoundingRect() + */ +QRectF QPlainTextEdit::blockBoundingGeometry(const QTextBlock &block) const +{ + Q_D(const QPlainTextEdit); + return d->control->blockBoundingRect(block); +} + +/*! + Returns the bounding rectangle of the text \a block in the block's own coordinates. + + \sa blockBoundingGeometry() + */ +QRectF QPlainTextEdit::blockBoundingRect(const QTextBlock &block) const +{ + QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(document()->documentLayout()); + Q_ASSERT(documentLayout); + return documentLayout->blockBoundingRect(block); +} + +/*! + \property QPlainTextEdit::blockCount + \brief the number of text blocks in the document. + + By default, in an empty document, this property contains a value of 1. +*/ +int QPlainTextEdit::blockCount() const +{ + return document()->blockCount(); +} + +/*! Returns the paint context for the viewport(), useful only when + reimplementing paintEvent(). + */ +QAbstractTextDocumentLayout::PaintContext QPlainTextEdit::getPaintContext() const +{ + Q_D(const QPlainTextEdit); + return d->control->getPaintContext(d->viewport); +} + +/*! + \property QPlainTextEdit::maximumBlockCount + \brief the limit for blocks in the document. + + Specifies the maximum number of blocks the document may have. If there are + more blocks in the document that specified with this property blocks are removed + from the beginning of the document. + + A negative or zero value specifies that the document may contain an unlimited + amount of blocks. + + The default value is 0. + + Note that setting this property will apply the limit immediately to the document + contents. Setting this property also disables the undo redo history. + +*/ + + +/*! + \fn void QPlainTextEdit::textChanged() + + This signal is emitted whenever the document's content changes; for + example, when text is inserted or deleted, or when formatting is applied. +*/ + +/*! + \fn void QPlainTextEdit::undoAvailable(bool available) + + This signal is emitted whenever undo operations become available + (\a available is true) or unavailable (\a available is false). +*/ + +/*! + \fn void QPlainTextEdit::redoAvailable(bool available) + + This signal is emitted whenever redo operations become available + (\a available is true) or unavailable (\a available is false). +*/ + +QT_END_NAMESPACE + +#include "moc_qplaintextedit.cpp" +#include "moc_qplaintextedit_p.cpp" + +#endif // QT_NO_TEXTEDIT |