1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWidgets module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qplaintextedit_p.h"
41
42
43#include <qfont.h>
44#include <qpainter.h>
45#include <qevent.h>
46#include <qdebug.h>
47#if QT_CONFIG(draganddrop)
48#include <qdrag.h>
49#endif
50#include <qclipboard.h>
51#include <qmath.h>
52#if QT_CONFIG(menu)
53#include <qmenu.h>
54#endif
55#include <qstyle.h>
56#include <qtimer.h>
57#include "private/qapplication_p.h"
58#include "private/qtextdocumentlayout_p.h"
59#include "private/qabstracttextdocumentlayout_p.h"
60#include "qtextdocument.h"
61#include "private/qtextdocument_p.h"
62#include "qtextlist.h"
63#include "qaccessible.h"
64
65#include <qtextformat.h>
66#include <qdatetime.h>
67#include <qapplication.h>
68#include <limits.h>
69#include <qtexttable.h>
70#include <qvariant.h>
71
72QT_BEGIN_NAMESPACE
73
74static inline bool shouldEnableInputMethod(QPlainTextEdit *plaintextedit)
75{
76 return !plaintextedit->isReadOnly();
77}
78
79class QPlainTextDocumentLayoutPrivate : public QAbstractTextDocumentLayoutPrivate
80{
81 Q_DECLARE_PUBLIC(QPlainTextDocumentLayout)
82public:
83 QPlainTextDocumentLayoutPrivate() {
84 mainViewPrivate = nullptr;
85 width = 0;
86 maximumWidth = 0;
87 maximumWidthBlockNumber = 0;
88 blockCount = 1;
89 blockUpdate = blockDocumentSizeChanged = false;
90 cursorWidth = 1;
91 textLayoutFlags = 0;
92 }
93
94 qreal width;
95 qreal maximumWidth;
96 int maximumWidthBlockNumber;
97 int blockCount;
98 QPlainTextEditPrivate *mainViewPrivate;
99 bool blockUpdate;
100 bool blockDocumentSizeChanged;
101 int cursorWidth;
102 int textLayoutFlags;
103
104 void layoutBlock(const QTextBlock &block);
105 qreal blockWidth(const QTextBlock &block);
106
107 void relayout();
108};
109
110
111
112/*! \class QPlainTextDocumentLayout
113 \since 4.4
114 \brief The QPlainTextDocumentLayout class implements a plain text layout for QTextDocument.
115
116 \ingroup richtext-processing
117 \inmodule QtWidgets
118
119 A QPlainTextDocumentLayout is required for text documents that can
120 be display or edited in a QPlainTextEdit. See
121 QTextDocument::setDocumentLayout().
122
123 QPlainTextDocumentLayout uses the QAbstractTextDocumentLayout API
124 that QTextDocument requires, but redefines it partially in order to
125 support plain text better. For instances, it does not operate on
126 vertical pixels, but on paragraphs (called blocks) instead. The
127 height of a document is identical to the number of paragraphs it
128 contains. The layout also doesn't support tables or nested frames,
129 or any sort of advanced text layout that goes beyond a list of
130 paragraphs with syntax highlighting.
131
132*/
133
134
135
136/*!
137 Constructs a plain text document layout for the text \a document.
138 */
139QPlainTextDocumentLayout::QPlainTextDocumentLayout(QTextDocument *document)
140 :QAbstractTextDocumentLayout(* new QPlainTextDocumentLayoutPrivate, document) {
141}
142/*!
143 Destructs a plain text document layout.
144 */
145QPlainTextDocumentLayout::~QPlainTextDocumentLayout() {}
146
147
148/*!
149 \reimp
150 */
151void QPlainTextDocumentLayout::draw(QPainter *, const PaintContext &)
152{
153}
154
155/*!
156 \reimp
157 */
158int QPlainTextDocumentLayout::hitTest(const QPointF &, Qt::HitTestAccuracy ) const
159{
160// this function is used from
161// QAbstractTextDocumentLayout::anchorAt(), but is not
162// implementable in a plain text document layout, because the
163// layout depends on the top block and top line which depends on
164// the view
165 return -1;
166}
167
168/*!
169 \reimp
170 */
171int QPlainTextDocumentLayout::pageCount() const
172{ return 1; }
173
174/*!
175 \reimp
176 */
177QSizeF QPlainTextDocumentLayout::documentSize() const
178{
179 Q_D(const QPlainTextDocumentLayout);
180 return QSizeF(d->maximumWidth, document()->lineCount());
181}
182
183/*!
184 \reimp
185 */
186QRectF QPlainTextDocumentLayout::frameBoundingRect(QTextFrame *) const
187{
188 Q_D(const QPlainTextDocumentLayout);
189 return QRectF(0, 0, qMax(a: d->width, b: d->maximumWidth), qreal(INT_MAX));
190}
191
192/*!
193 \reimp
194 */
195QRectF QPlainTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const
196{
197 if (!block.isValid()) { return QRectF(); }
198 QTextLayout *tl = block.layout();
199 if (!tl->lineCount())
200 const_cast<QPlainTextDocumentLayout*>(this)->layoutBlock(block);
201 QRectF br;
202 if (block.isVisible()) {
203 br = QRectF(QPointF(0, 0), tl->boundingRect().bottomRight());
204 if (tl->lineCount() == 1)
205 br.setWidth(qMax(a: br.width(), b: tl->lineAt(i: 0).naturalTextWidth()));
206 qreal margin = document()->documentMargin();
207 br.adjust(xp1: 0, yp1: 0, xp2: margin, yp2: 0);
208 if (!block.next().isValid())
209 br.adjust(xp1: 0, yp1: 0, xp2: 0, yp2: margin);
210 }
211 return br;
212
213}
214
215/*!
216 Ensures that \a block has a valid layout
217 */
218void QPlainTextDocumentLayout::ensureBlockLayout(const QTextBlock &block) const
219{
220 if (!block.isValid())
221 return;
222 QTextLayout *tl = block.layout();
223 if (!tl->lineCount())
224 const_cast<QPlainTextDocumentLayout*>(this)->layoutBlock(block);
225}
226
227
228/*! \property QPlainTextDocumentLayout::cursorWidth
229
230 This property specifies the width of the cursor in pixels. The default value is 1.
231*/
232void QPlainTextDocumentLayout::setCursorWidth(int width)
233{
234 Q_D(QPlainTextDocumentLayout);
235 d->cursorWidth = width;
236}
237
238int QPlainTextDocumentLayout::cursorWidth() const
239{
240 Q_D(const QPlainTextDocumentLayout);
241 return d->cursorWidth;
242}
243
244QPlainTextDocumentLayoutPrivate *QPlainTextDocumentLayout::priv() const
245{
246 Q_D(const QPlainTextDocumentLayout);
247 return const_cast<QPlainTextDocumentLayoutPrivate*>(d);
248}
249
250
251/*!
252
253 Requests a complete update on all views.
254 */
255void QPlainTextDocumentLayout::requestUpdate()
256{
257 emit update(QRectF(0., -document()->documentMargin(), 1000000000., 1000000000.));
258}
259
260
261void QPlainTextDocumentLayout::setTextWidth(qreal newWidth)
262{
263 Q_D(QPlainTextDocumentLayout);
264 d->width = d->maximumWidth = newWidth;
265 d->relayout();
266}
267
268qreal QPlainTextDocumentLayout::textWidth() const
269{
270 Q_D(const QPlainTextDocumentLayout);
271 return d->width;
272}
273
274void QPlainTextDocumentLayoutPrivate::relayout()
275{
276 Q_Q(QPlainTextDocumentLayout);
277 QTextBlock block = q->document()->firstBlock();
278 while (block.isValid()) {
279 block.layout()->clearLayout();
280 block.setLineCount(block.isVisible() ? 1 : 0);
281 block = block.next();
282 }
283 emit q->update();
284}
285
286
287/*! \reimp
288 */
289void QPlainTextDocumentLayout::documentChanged(int from, int charsRemoved, int charsAdded)
290{
291 Q_D(QPlainTextDocumentLayout);
292 QTextDocument *doc = document();
293 int newBlockCount = doc->blockCount();
294 int charsChanged = charsRemoved + charsAdded;
295
296 QTextBlock changeStartBlock = doc->findBlock(pos: from);
297 QTextBlock changeEndBlock = doc->findBlock(pos: qMax(a: 0, b: from + charsChanged - 1));
298 bool blockVisibilityChanged = false;
299
300 if (changeStartBlock == changeEndBlock && newBlockCount == d->blockCount) {
301 QTextBlock block = changeStartBlock;
302 if (block.isValid() && block.length()) {
303 QRectF oldBr = blockBoundingRect(block);
304 layoutBlock(block);
305 QRectF newBr = blockBoundingRect(block);
306 if (newBr.height() == oldBr.height()) {
307 if (!d->blockUpdate)
308 emit updateBlock(block);
309 return;
310 }
311 }
312 } else {
313 QTextBlock block = changeStartBlock;
314 do {
315 block.clearLayout();
316 if (block.isVisible()
317 ? (block.lineCount() == 0)
318 : (block.lineCount() > 0)) {
319 blockVisibilityChanged = true;
320 block.setLineCount(block.isVisible() ? 1 : 0);
321 }
322 if (block == changeEndBlock)
323 break;
324 block = block.next();
325 } while(block.isValid());
326 }
327
328 if (newBlockCount != d->blockCount || blockVisibilityChanged) {
329 int changeEnd = changeEndBlock.blockNumber();
330 int blockDiff = newBlockCount - d->blockCount;
331 int oldChangeEnd = changeEnd - blockDiff;
332
333 if (d->maximumWidthBlockNumber > oldChangeEnd)
334 d->maximumWidthBlockNumber += blockDiff;
335
336 d->blockCount = newBlockCount;
337 if (d->blockCount == 1)
338 d->maximumWidth = blockWidth(block: doc->firstBlock());
339
340 if (!d->blockDocumentSizeChanged)
341 emit documentSizeChanged(newSize: documentSize());
342
343 if (blockDiff == 1 && changeEnd == newBlockCount -1 ) {
344 if (!d->blockUpdate) {
345 QTextBlock b = changeStartBlock;
346 for(;;) {
347 emit updateBlock(block: b);
348 if (b == changeEndBlock)
349 break;
350 b = b.next();
351 }
352 }
353 return;
354 }
355 }
356
357 if (!d->blockUpdate)
358 emit update(QRectF(0., -doc->documentMargin(), 1000000000., 1000000000.)); // optimization potential
359}
360
361
362void QPlainTextDocumentLayout::layoutBlock(const QTextBlock &block)
363{
364 Q_D(QPlainTextDocumentLayout);
365 QTextDocument *doc = document();
366 qreal margin = doc->documentMargin();
367 qreal blockMaximumWidth = 0;
368
369 qreal height = 0;
370 QTextLayout *tl = block.layout();
371 QTextOption option = doc->defaultTextOption();
372 tl->setTextOption(option);
373
374 int extraMargin = 0;
375 if (option.flags() & QTextOption::AddSpaceForLineAndParagraphSeparators) {
376 QFontMetrics fm(block.charFormat().font());
377 extraMargin += fm.horizontalAdvance(QChar(0x21B5));
378 }
379 tl->beginLayout();
380 qreal availableWidth = d->width;
381 if (availableWidth <= 0) {
382 availableWidth = qreal(INT_MAX); // similar to text edit with pageSize.width == 0
383 }
384 availableWidth -= 2*margin + extraMargin;
385 while (1) {
386 QTextLine line = tl->createLine();
387 if (!line.isValid())
388 break;
389 line.setLeadingIncluded(true);
390 line.setLineWidth(availableWidth);
391 line.setPosition(QPointF(margin, height));
392 height += line.height();
393 if (line.leading() < 0)
394 height += qCeil(v: line.leading());
395 blockMaximumWidth = qMax(a: blockMaximumWidth, b: line.naturalTextWidth() + 2*margin);
396 }
397 tl->endLayout();
398
399 int previousLineCount = doc->lineCount();
400 const_cast<QTextBlock&>(block).setLineCount(block.isVisible() ? tl->lineCount() : 0);
401 int lineCount = doc->lineCount();
402
403 bool emitDocumentSizeChanged = previousLineCount != lineCount;
404 if (blockMaximumWidth > d->maximumWidth) {
405 // new longest line
406 d->maximumWidth = blockMaximumWidth;
407 d->maximumWidthBlockNumber = block.blockNumber();
408 emitDocumentSizeChanged = true;
409 } else if (block.blockNumber() == d->maximumWidthBlockNumber && blockMaximumWidth < d->maximumWidth) {
410 // longest line shrinking
411 QTextBlock b = doc->firstBlock();
412 d->maximumWidth = 0;
413 QTextBlock maximumBlock;
414 while (b.isValid()) {
415 qreal blockMaximumWidth = blockWidth(block: b);
416 if (blockMaximumWidth > d->maximumWidth) {
417 d->maximumWidth = blockMaximumWidth;
418 maximumBlock = b;
419 }
420 b = b.next();
421 }
422 if (maximumBlock.isValid()) {
423 d->maximumWidthBlockNumber = maximumBlock.blockNumber();
424 emitDocumentSizeChanged = true;
425 }
426 }
427 if (emitDocumentSizeChanged && !d->blockDocumentSizeChanged)
428 emit documentSizeChanged(newSize: documentSize());
429}
430
431qreal QPlainTextDocumentLayout::blockWidth(const QTextBlock &block)
432{
433 QTextLayout *layout = block.layout();
434 if (!layout->lineCount())
435 return 0; // only for layouted blocks
436 qreal blockWidth = 0;
437 for (int i = 0; i < layout->lineCount(); ++i) {
438 QTextLine line = layout->lineAt(i);
439 blockWidth = qMax(a: line.naturalTextWidth() + 8, b: blockWidth);
440 }
441 return blockWidth;
442}
443
444
445QPlainTextEditControl::QPlainTextEditControl(QPlainTextEdit *parent)
446 : QWidgetTextControl(parent), textEdit(parent),
447 topBlock(0)
448{
449 setAcceptRichText(false);
450}
451
452void QPlainTextEditPrivate::_q_cursorPositionChanged()
453{
454 pageUpDownLastCursorYIsValid = false;
455 Q_Q(QPlainTextEdit);
456#ifndef QT_NO_ACCESSIBILITY
457 QAccessibleTextCursorEvent ev(q, q->textCursor().position());
458 QAccessible::updateAccessibility(event: &ev);
459#endif
460 emit q->cursorPositionChanged();
461}
462
463void QPlainTextEditPrivate::_q_verticalScrollbarActionTriggered(int action) {
464 if (action == QAbstractSlider::SliderPageStepAdd) {
465 pageUpDown(op: QTextCursor::Down, moveMode: QTextCursor::MoveAnchor, moveCursor: false);
466 } else if (action == QAbstractSlider::SliderPageStepSub) {
467 pageUpDown(op: QTextCursor::Up, moveMode: QTextCursor::MoveAnchor, moveCursor: false);
468 }
469}
470
471QMimeData *QPlainTextEditControl::createMimeDataFromSelection() const {
472 QPlainTextEdit *ed = qobject_cast<QPlainTextEdit *>(object: parent());
473 if (!ed)
474 return QWidgetTextControl::createMimeDataFromSelection();
475 return ed->createMimeDataFromSelection();
476 }
477bool QPlainTextEditControl::canInsertFromMimeData(const QMimeData *source) const {
478 QPlainTextEdit *ed = qobject_cast<QPlainTextEdit *>(object: parent());
479 if (!ed)
480 return QWidgetTextControl::canInsertFromMimeData(source);
481 return ed->canInsertFromMimeData(source);
482}
483void QPlainTextEditControl::insertFromMimeData(const QMimeData *source) {
484 QPlainTextEdit *ed = qobject_cast<QPlainTextEdit *>(object: parent());
485 if (!ed)
486 QWidgetTextControl::insertFromMimeData(source);
487 else
488 ed->insertFromMimeData(source);
489}
490
491qreal QPlainTextEditPrivate::verticalOffset(int topBlock, int topLine) const
492{
493 qreal offset = 0;
494 QTextDocument *doc = control->document();
495
496 if (topLine) {
497 QTextBlock currentBlock = doc->findBlockByNumber(blockNumber: topBlock);
498 QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(object: doc->documentLayout());
499 Q_ASSERT(documentLayout);
500 QRectF r = documentLayout->blockBoundingRect(block: currentBlock);
501 Q_UNUSED(r);
502 QTextLayout *layout = currentBlock.layout();
503 if (layout && topLine <= layout->lineCount()) {
504 QTextLine line = layout->lineAt(i: topLine - 1);
505 const QRectF lr = line.naturalTextRect();
506 offset = lr.bottom();
507 }
508 }
509 if (topBlock == 0 && topLine == 0)
510 offset -= doc->documentMargin(); // top margin
511 return offset;
512}
513
514
515qreal QPlainTextEditPrivate::verticalOffset() const {
516 return verticalOffset(topBlock: control->topBlock, topLine) + topLineFracture;
517}
518
519
520QTextBlock QPlainTextEditControl::firstVisibleBlock() const
521{
522 return document()->findBlockByNumber(blockNumber: topBlock);
523}
524
525
526
527int QPlainTextEditControl::hitTest(const QPointF &point, Qt::HitTestAccuracy ) const {
528 int currentBlockNumber = topBlock;
529 QTextBlock currentBlock = document()->findBlockByNumber(blockNumber: currentBlockNumber);
530 if (!currentBlock.isValid())
531 return -1;
532
533 QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(object: document()->documentLayout());
534 Q_ASSERT(documentLayout);
535
536 QPointF offset;
537 QRectF r = documentLayout->blockBoundingRect(block: currentBlock);
538 while (currentBlock.next().isValid() && r.bottom() + offset.y() <= point.y()) {
539 offset.ry() += r.height();
540 currentBlock = currentBlock.next();
541 ++currentBlockNumber;
542 r = documentLayout->blockBoundingRect(block: currentBlock);
543 }
544 while (currentBlock.previous().isValid() && r.top() + offset.y() > point.y()) {
545 offset.ry() -= r.height();
546 currentBlock = currentBlock.previous();
547 --currentBlockNumber;
548 r = documentLayout->blockBoundingRect(block: currentBlock);
549 }
550
551
552 if (!currentBlock.isValid())
553 return -1;
554 QTextLayout *layout = currentBlock.layout();
555 int off = 0;
556 QPointF pos = point - offset;
557 for (int i = 0; i < layout->lineCount(); ++i) {
558 QTextLine line = layout->lineAt(i);
559 const QRectF lr = line.naturalTextRect();
560 if (lr.top() > pos.y()) {
561 off = qMin(a: off, b: line.textStart());
562 } else if (lr.bottom() <= pos.y()) {
563 off = qMax(a: off, b: line.textStart() + line.textLength());
564 } else {
565 off = line.xToCursor(x: pos.x(), overwriteMode() ?
566 QTextLine::CursorOnCharacter : QTextLine::CursorBetweenCharacters);
567 break;
568 }
569 }
570
571 return currentBlock.position() + off;
572}
573
574QRectF QPlainTextEditControl::blockBoundingRect(const QTextBlock &block) const {
575 int currentBlockNumber = topBlock;
576 int blockNumber = block.blockNumber();
577 QTextBlock currentBlock = document()->findBlockByNumber(blockNumber: currentBlockNumber);
578 if (!currentBlock.isValid())
579 return QRectF();
580 Q_ASSERT(currentBlock.blockNumber() == currentBlockNumber);
581 QTextDocument *doc = document();
582 QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(object: doc->documentLayout());
583 Q_ASSERT(documentLayout);
584
585 QPointF offset;
586 if (!block.isValid())
587 return QRectF();
588 QRectF r = documentLayout->blockBoundingRect(block: currentBlock);
589 int maxVerticalOffset = r.height();
590 while (currentBlockNumber < blockNumber && offset.y() - maxVerticalOffset <= 2* textEdit->viewport()->height()) {
591 offset.ry() += r.height();
592 currentBlock = currentBlock.next();
593 ++currentBlockNumber;
594 if (!currentBlock.isVisible()) {
595 currentBlock = doc->findBlockByLineNumber(blockNumber: currentBlock.firstLineNumber());
596 currentBlockNumber = currentBlock.blockNumber();
597 }
598 r = documentLayout->blockBoundingRect(block: currentBlock);
599 }
600 while (currentBlockNumber > blockNumber && offset.y() + maxVerticalOffset >= -textEdit->viewport()->height()) {
601 currentBlock = currentBlock.previous();
602 --currentBlockNumber;
603 while (!currentBlock.isVisible()) {
604 currentBlock = currentBlock.previous();
605 --currentBlockNumber;
606 }
607 if (!currentBlock.isValid())
608 break;
609
610 r = documentLayout->blockBoundingRect(block: currentBlock);
611 offset.ry() -= r.height();
612 }
613
614 if (currentBlockNumber != blockNumber) {
615 // fallback for blocks out of reach. Give it some geometry at
616 // least, and ensure the layout is up to date.
617 r = documentLayout->blockBoundingRect(block);
618 if (currentBlockNumber > blockNumber)
619 offset.ry() -= r.height();
620 }
621 r.translate(p: offset);
622 return r;
623}
624
625QString QPlainTextEditControl::anchorAt(const QPointF &pos) const
626{
627 return textEdit->anchorAt(pos: pos.toPoint());
628}
629
630void QPlainTextEditPrivate::setTopLine(int visualTopLine, int dx)
631{
632 QTextDocument *doc = control->document();
633 QTextBlock block = doc->findBlockByLineNumber(blockNumber: visualTopLine);
634 int blockNumber = block.blockNumber();
635 int lineNumber = visualTopLine - block.firstLineNumber();
636 setTopBlock(newTopBlock: blockNumber, newTopLine: lineNumber, dx);
637}
638
639void QPlainTextEditPrivate::setTopBlock(int blockNumber, int lineNumber, int dx)
640{
641 Q_Q(QPlainTextEdit);
642 blockNumber = qMax(a: 0, b: blockNumber);
643 lineNumber = qMax(a: 0, b: lineNumber);
644 QTextDocument *doc = control->document();
645 QTextBlock block = doc->findBlockByNumber(blockNumber);
646
647 int newTopLine = block.firstLineNumber() + lineNumber;
648 int maxTopLine = vbar->maximum();
649
650 if (newTopLine > maxTopLine) {
651 block = doc->findBlockByLineNumber(blockNumber: maxTopLine);
652 blockNumber = block.blockNumber();
653 lineNumber = maxTopLine - block.firstLineNumber();
654 }
655
656 {
657 const QSignalBlocker blocker(vbar);
658 vbar->setValue(newTopLine);
659 }
660
661 if (!dx && blockNumber == control->topBlock && lineNumber == topLine)
662 return;
663
664 if (viewport->updatesEnabled() && viewport->isVisible()) {
665 int dy = 0;
666 if (doc->findBlockByNumber(blockNumber: control->topBlock).isValid()) {
667 qreal realdy = -q->blockBoundingGeometry(block).y()
668 + verticalOffset() - verticalOffset(topBlock: blockNumber, topLine: lineNumber);
669 dy = (int)realdy;
670 topLineFracture = realdy - dy;
671 }
672 control->topBlock = blockNumber;
673 topLine = lineNumber;
674
675 {
676 const QSignalBlocker blocker(vbar);
677 vbar->setValue(block.firstLineNumber() + lineNumber);
678 }
679
680 if (dx || dy) {
681 viewport->scroll(dx: q->isRightToLeft() ? -dx : dx, dy);
682 QGuiApplication::inputMethod()->update(queries: Qt::ImCursorRectangle | Qt::ImAnchorRectangle);
683 } else {
684 viewport->update();
685 topLineFracture = 0;
686 }
687 emit q->updateRequest(rect: viewport->rect(), dy);
688 } else {
689 control->topBlock = blockNumber;
690 topLine = lineNumber;
691 topLineFracture = 0;
692 }
693
694}
695
696
697
698void QPlainTextEditPrivate::ensureVisible(int position, bool center, bool forceCenter) {
699 Q_Q(QPlainTextEdit);
700 QRectF visible = QRectF(viewport->rect()).translated(p: -q->contentOffset());
701 QTextBlock block = control->document()->findBlock(pos: position);
702 if (!block.isValid())
703 return;
704 QRectF br = control->blockBoundingRect(block);
705 if (!br.isValid())
706 return;
707 QTextLine line = block.layout()->lineForTextPosition(pos: position - block.position());
708 Q_ASSERT(line.isValid());
709 QRectF lr = line.naturalTextRect().translated(p: br.topLeft());
710
711 if (lr.bottom() >= visible.bottom() || (center && lr.top() < visible.top()) || forceCenter){
712
713 qreal height = visible.height();
714 if (center)
715 height /= 2;
716
717 qreal h = center ? line.naturalTextRect().center().y() : line.naturalTextRect().bottom();
718
719 QTextBlock previousVisibleBlock = block;
720 while (h < height && block.previous().isValid()) {
721 previousVisibleBlock = block;
722 do {
723 block = block.previous();
724 } while (!block.isVisible() && block.previous().isValid());
725 h += q->blockBoundingRect(block).height();
726 }
727
728 int l = 0;
729 int lineCount = block.layout()->lineCount();
730 qreal voffset = verticalOffset(topBlock: block.blockNumber(), topLine: 0);
731 while (l < lineCount) {
732 QRectF lineRect = block.layout()->lineAt(i: l).naturalTextRect();
733 if (h - voffset - lineRect.top() <= height)
734 break;
735 ++l;
736 }
737
738 if (l >= lineCount) {
739 block = previousVisibleBlock;
740 l = 0;
741 }
742 setTopBlock(blockNumber: block.blockNumber(), lineNumber: l);
743 } else if (lr.top() < visible.top()) {
744 setTopBlock(blockNumber: block.blockNumber(), lineNumber: line.lineNumber());
745 }
746
747}
748
749
750void QPlainTextEditPrivate::updateViewport()
751{
752 Q_Q(QPlainTextEdit);
753 viewport->update();
754 emit q->updateRequest(rect: viewport->rect(), dy: 0);
755}
756
757QPlainTextEditPrivate::QPlainTextEditPrivate()
758 : control(nullptr),
759 tabChangesFocus(false),
760 lineWrap(QPlainTextEdit::WidgetWidth),
761 wordWrap(QTextOption::WrapAtWordBoundaryOrAnywhere),
762 clickCausedFocus(0), placeholderVisible(1),
763 topLine(0), topLineFracture(0),
764 pageUpDownLastCursorYIsValid(false)
765{
766 showCursorOnInitialShow = true;
767 backgroundVisible = false;
768 centerOnScroll = false;
769 inDrag = false;
770}
771
772
773void QPlainTextEditPrivate::init(const QString &txt)
774{
775 Q_Q(QPlainTextEdit);
776 control = new QPlainTextEditControl(q);
777
778 QTextDocument *doc = new QTextDocument(control);
779 QAbstractTextDocumentLayout *layout = new QPlainTextDocumentLayout(doc);
780 doc->setDocumentLayout(layout);
781 control->setDocument(doc);
782
783 control->setPalette(q->palette());
784
785 QObject::connect(sender: vbar, SIGNAL(actionTriggered(int)), receiver: q, SLOT(_q_verticalScrollbarActionTriggered(int)));
786
787 QObject::connect(sender: control, SIGNAL(microFocusChanged()), receiver: q, SLOT(updateMicroFocus()));
788 QObject::connect(sender: control, SIGNAL(documentSizeChanged(QSizeF)), receiver: q, SLOT(_q_adjustScrollbars()));
789 QObject::connect(sender: control, SIGNAL(blockCountChanged(int)), receiver: q, SIGNAL(blockCountChanged(int)));
790 QObject::connect(sender: control, SIGNAL(updateRequest(QRectF)), receiver: q, SLOT(_q_repaintContents(QRectF)));
791 QObject::connect(sender: control, SIGNAL(modificationChanged(bool)), receiver: q, SIGNAL(modificationChanged(bool)));
792
793 QObject::connect(sender: control, SIGNAL(textChanged()), receiver: q, SIGNAL(textChanged()));
794 QObject::connect(sender: control, SIGNAL(undoAvailable(bool)), receiver: q, SIGNAL(undoAvailable(bool)));
795 QObject::connect(sender: control, SIGNAL(redoAvailable(bool)), receiver: q, SIGNAL(redoAvailable(bool)));
796 QObject::connect(sender: control, SIGNAL(copyAvailable(bool)), receiver: q, SIGNAL(copyAvailable(bool)));
797 QObject::connect(sender: control, SIGNAL(selectionChanged()), receiver: q, SIGNAL(selectionChanged()));
798 QObject::connect(sender: control, SIGNAL(cursorPositionChanged()), receiver: q, SLOT(_q_cursorPositionChanged()));
799
800 QObject::connect(sender: control, SIGNAL(textChanged()), receiver: q, SLOT(_q_textChanged()));
801 QObject::connect(sender: control, SIGNAL(textChanged()), receiver: q, SLOT(updateMicroFocus()));
802
803 // set a null page size initially to avoid any relayouting until the textedit
804 // is shown. relayoutDocument() will take care of setting the page size to the
805 // viewport dimensions later.
806 doc->setTextWidth(-1);
807 doc->documentLayout()->setPaintDevice(viewport);
808 doc->setDefaultFont(q->font());
809
810
811 if (!txt.isEmpty())
812 control->setPlainText(txt);
813
814 hbar->setSingleStep(20);
815 vbar->setSingleStep(1);
816
817 viewport->setBackgroundRole(QPalette::Base);
818 q->setAcceptDrops(true);
819 q->setFocusPolicy(Qt::StrongFocus);
820 q->setAttribute(Qt::WA_KeyCompression);
821 q->setAttribute(Qt::WA_InputMethodEnabled);
822 q->setInputMethodHints(Qt::ImhMultiLine);
823
824#ifndef QT_NO_CURSOR
825 viewport->setCursor(Qt::IBeamCursor);
826#endif
827 originalOffsetY = 0;
828}
829
830void QPlainTextEditPrivate::_q_textChanged()
831{
832 Q_Q(QPlainTextEdit);
833
834 // We normally only repaint the part of view that contains text in the
835 // document that has changed (in _q_repaintContents). But the placeholder
836 // text is not a part of the document, but is drawn on separately. So whenever
837 // we either show or hide the placeholder text, we issue a full update.
838 bool placeholderCurrentyVisible = placeholderVisible;
839
840 placeholderVisible = !placeholderText.isEmpty()
841 && q->document()->isEmpty()
842 && (!q->firstVisibleBlock().isValid() ||
843 q->firstVisibleBlock().layout()->preeditAreaText().isEmpty());
844
845 if (placeholderCurrentyVisible != placeholderVisible)
846 viewport->update();
847}
848
849void QPlainTextEditPrivate::_q_repaintContents(const QRectF &contentsRect)
850{
851 Q_Q(QPlainTextEdit);
852 if (!contentsRect.isValid()) {
853 updateViewport();
854 return;
855 }
856 const int xOffset = horizontalOffset();
857 const int yOffset = (int)verticalOffset();
858 const QRect visibleRect(xOffset, yOffset, viewport->width(), viewport->height());
859
860 QRect r = contentsRect.adjusted(xp1: -1, yp1: -1, xp2: 1, yp2: 1).intersected(r: visibleRect).toAlignedRect();
861 if (r.isEmpty())
862 return;
863
864 r.translate(dx: -xOffset, dy: -yOffset);
865 viewport->update(r);
866 emit q->updateRequest(rect: r, dy: 0);
867}
868
869void QPlainTextEditPrivate::pageUpDown(QTextCursor::MoveOperation op, QTextCursor::MoveMode moveMode, bool moveCursor)
870{
871
872 Q_Q(QPlainTextEdit);
873
874 QTextCursor cursor = control->textCursor();
875 if (moveCursor) {
876 ensureCursorVisible();
877 if (!pageUpDownLastCursorYIsValid)
878 pageUpDownLastCursorY = control->cursorRect(cursor).top() - verticalOffset();
879 }
880
881 qreal lastY = pageUpDownLastCursorY;
882
883
884 if (op == QTextCursor::Down) {
885 QRectF visible = QRectF(viewport->rect()).translated(p: -q->contentOffset());
886 QTextBlock firstVisibleBlock = q->firstVisibleBlock();
887 QTextBlock block = firstVisibleBlock;
888 QRectF br = q->blockBoundingRect(block);
889 qreal h = 0;
890 int atEnd = false;
891 while (h + br.height() <= visible.bottom()) {
892 if (!block.next().isValid()) {
893 atEnd = true;
894 lastY = visible.bottom(); // set cursor to last line
895 break;
896 }
897 h += br.height();
898 block = block.next();
899 br = q->blockBoundingRect(block);
900 }
901
902 if (!atEnd) {
903 int line = 0;
904 qreal diff = visible.bottom() - h;
905 int lineCount = block.layout()->lineCount();
906 while (line < lineCount - 1) {
907 if (block.layout()->lineAt(i: line).naturalTextRect().bottom() > diff) {
908 // the first line that did not completely fit the screen
909 break;
910 }
911 ++line;
912 }
913 setTopBlock(blockNumber: block.blockNumber(), lineNumber: line);
914 }
915
916 if (moveCursor) {
917 // move using movePosition to keep the cursor's x
918 lastY += verticalOffset();
919 bool moved = false;
920 do {
921 moved = cursor.movePosition(op, moveMode);
922 } while (moved && control->cursorRect(cursor).top() < lastY);
923 }
924
925 } else if (op == QTextCursor::Up) {
926
927 QRectF visible = QRectF(viewport->rect()).translated(p: -q->contentOffset());
928 visible.translate(dx: 0, dy: -visible.height()); // previous page
929 QTextBlock block = q->firstVisibleBlock();
930 qreal h = 0;
931 while (h >= visible.top()) {
932 if (!block.previous().isValid()) {
933 if (control->topBlock == 0 && topLine == 0) {
934 lastY = 0; // set cursor to first line
935 }
936 break;
937 }
938 block = block.previous();
939 QRectF br = q->blockBoundingRect(block);
940 h -= br.height();
941 }
942
943 int line = 0;
944 if (block.isValid()) {
945 qreal diff = visible.top() - h;
946 int lineCount = block.layout()->lineCount();
947 while (line < lineCount) {
948 if (block.layout()->lineAt(i: line).naturalTextRect().top() >= diff)
949 break;
950 ++line;
951 }
952 if (line == lineCount) {
953 if (block.next().isValid() && block.next() != q->firstVisibleBlock()) {
954 block = block.next();
955 line = 0;
956 } else {
957 --line;
958 }
959 }
960 }
961 setTopBlock(blockNumber: block.blockNumber(), lineNumber: line);
962
963 if (moveCursor) {
964 cursor.setVisualNavigation(true);
965 // move using movePosition to keep the cursor's x
966 lastY += verticalOffset();
967 bool moved = false;
968 do {
969 moved = cursor.movePosition(op, moveMode);
970 } while (moved && control->cursorRect(cursor).top() > lastY);
971 }
972 }
973
974 if (moveCursor) {
975 control->setTextCursor(cursor, selectionClipboard: moveMode == QTextCursor::KeepAnchor);
976 pageUpDownLastCursorYIsValid = true;
977 }
978}
979
980#if QT_CONFIG(scrollbar)
981
982void QPlainTextEditPrivate::_q_adjustScrollbars()
983{
984 Q_Q(QPlainTextEdit);
985 QTextDocument *doc = control->document();
986 QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(object: doc->documentLayout());
987 Q_ASSERT(documentLayout);
988 bool documentSizeChangedBlocked = documentLayout->priv()->blockDocumentSizeChanged;
989 documentLayout->priv()->blockDocumentSizeChanged = true;
990 qreal margin = doc->documentMargin();
991
992 int vmax = 0;
993
994 int vSliderLength = 0;
995 if (!centerOnScroll && q->isVisible()) {
996 QTextBlock block = doc->lastBlock();
997 const qreal visible = viewport->rect().height() - margin - 1;
998 qreal y = 0;
999 int visibleFromBottom = 0;
1000
1001 while (block.isValid()) {
1002 if (!block.isVisible()) {
1003 block = block.previous();
1004 continue;
1005 }
1006 y += documentLayout->blockBoundingRect(block).height();
1007
1008 QTextLayout *layout = block.layout();
1009 int layoutLineCount = layout->lineCount();
1010 if (y > visible) {
1011 int lineNumber = 0;
1012 while (lineNumber < layoutLineCount) {
1013 QTextLine line = layout->lineAt(i: lineNumber);
1014 const QRectF lr = line.naturalTextRect();
1015 if (lr.top() >= y - visible)
1016 break;
1017 ++lineNumber;
1018 }
1019 if (lineNumber < layoutLineCount)
1020 visibleFromBottom += (layoutLineCount - lineNumber);
1021 break;
1022
1023 }
1024 visibleFromBottom += layoutLineCount;
1025 block = block.previous();
1026 }
1027 vmax = qMax(a: 0, b: doc->lineCount() - visibleFromBottom);
1028 vSliderLength = visibleFromBottom;
1029
1030 } else {
1031 vmax = qMax(a: 0, b: doc->lineCount() - 1);
1032 int lineSpacing = q->fontMetrics().lineSpacing();
1033 vSliderLength = lineSpacing != 0 ? viewport->height() / lineSpacing : 0;
1034 }
1035
1036
1037
1038 QSizeF documentSize = documentLayout->documentSize();
1039 vbar->setRange(min: 0, max: qMax(a: 0, b: vmax));
1040 vbar->setPageStep(vSliderLength);
1041 int visualTopLine = vmax;
1042 QTextBlock firstVisibleBlock = q->firstVisibleBlock();
1043 if (firstVisibleBlock.isValid())
1044 visualTopLine = firstVisibleBlock.firstLineNumber() + topLine;
1045
1046 {
1047 const QSignalBlocker blocker(vbar);
1048 vbar->setValue(visualTopLine);
1049 }
1050
1051 hbar->setRange(min: 0, max: (int)documentSize.width() - viewport->width());
1052 hbar->setPageStep(viewport->width());
1053 documentLayout->priv()->blockDocumentSizeChanged = documentSizeChangedBlocked;
1054 setTopLine(visualTopLine: vbar->value());
1055}
1056
1057#endif
1058
1059
1060void QPlainTextEditPrivate::ensureViewportLayouted()
1061{
1062}
1063
1064/*!
1065 \class QPlainTextEdit
1066 \since 4.4
1067 \brief The QPlainTextEdit class provides a widget that is used to edit and display
1068 plain text.
1069
1070 \ingroup richtext-processing
1071 \inmodule QtWidgets
1072
1073 \tableofcontents
1074
1075 \section1 Introduction and Concepts
1076
1077 QPlainTextEdit is an advanced viewer/editor supporting plain
1078 text. It is optimized to handle large documents and to respond
1079 quickly to user input.
1080
1081 QPlainText uses very much the same technology and concepts as
1082 QTextEdit, but is optimized for plain text handling.
1083
1084 QPlainTextEdit works on paragraphs and characters. A paragraph is
1085 a formatted string which is word-wrapped to fit into the width of
1086 the widget. By default when reading plain text, one newline
1087 signifies a paragraph. A document consists of zero or more
1088 paragraphs. Paragraphs are separated by hard line breaks. Each
1089 character within a paragraph has its own attributes, for example,
1090 font and color.
1091
1092 The shape of the mouse cursor on a QPlainTextEdit is
1093 Qt::IBeamCursor by default. It can be changed through the
1094 viewport()'s cursor property.
1095
1096 \section1 Using QPlainTextEdit as a Display Widget
1097
1098 The text is set or replaced using setPlainText() which deletes the
1099 existing text and replaces it with the text passed to setPlainText().
1100
1101 Text can be inserted using the QTextCursor class or using the
1102 convenience functions insertPlainText(), appendPlainText() or
1103 paste().
1104
1105 By default, the text edit wraps words at whitespace to fit within
1106 the text edit widget. The setLineWrapMode() function is used to
1107 specify the kind of line wrap you want, \l WidgetWidth or \l
1108 NoWrap if you don't want any wrapping. If you use word wrap to
1109 the widget's width \l WidgetWidth, you can specify whether to
1110 break on whitespace or anywhere with setWordWrapMode().
1111
1112 The find() function can be used to find and select a given string
1113 within the text.
1114
1115 If you want to limit the total number of paragraphs in a
1116 QPlainTextEdit, as it is for example useful in a log viewer, then
1117 you can use the maximumBlockCount property. The combination of
1118 setMaximumBlockCount() and appendPlainText() turns QPlainTextEdit
1119 into an efficient viewer for log text. The scrolling can be
1120 reduced with the centerOnScroll() property, making the log viewer
1121 even faster. Text can be formatted in a limited way, either using
1122 a syntax highlighter (see below), or by appending html-formatted
1123 text with appendHtml(). While QPlainTextEdit does not support
1124 complex rich text rendering with tables and floats, it does
1125 support limited paragraph-based formatting that you may need in a
1126 log viewer.
1127
1128 \section2 Read-only Key Bindings
1129
1130 When QPlainTextEdit is used read-only the key bindings are limited to
1131 navigation, and text may only be selected with the mouse:
1132 \table
1133 \header \li Keypresses \li Action
1134 \row \li Qt::UpArrow \li Moves one line up.
1135 \row \li Qt::DownArrow \li Moves one line down.
1136 \row \li Qt::LeftArrow \li Moves one character to the left.
1137 \row \li Qt::RightArrow \li Moves one character to the right.
1138 \row \li PageUp \li Moves one (viewport) page up.
1139 \row \li PageDown \li Moves one (viewport) page down.
1140 \row \li Home \li Moves to the beginning of the text.
1141 \row \li End \li Moves to the end of the text.
1142 \row \li Alt+Wheel
1143 \li Scrolls the page horizontally (the Wheel is the mouse wheel).
1144 \row \li Ctrl+Wheel \li Zooms the text.
1145 \row \li Ctrl+A \li Selects all text.
1146 \endtable
1147
1148
1149 \section1 Using QPlainTextEdit as an Editor
1150
1151 All the information about using QPlainTextEdit as a display widget also
1152 applies here.
1153
1154 Selection of text is handled by the QTextCursor class, which provides
1155 functionality for creating selections, retrieving the text contents or
1156 deleting selections. You can retrieve the object that corresponds with
1157 the user-visible cursor using the textCursor() method. If you want to set
1158 a selection in QPlainTextEdit just create one on a QTextCursor object and
1159 then make that cursor the visible cursor using setCursor(). The selection
1160 can be copied to the clipboard with copy(), or cut to the clipboard with
1161 cut(). The entire text can be selected using selectAll().
1162
1163 QPlainTextEdit holds a QTextDocument object which can be retrieved using the
1164 document() method. You can also set your own document object using setDocument().
1165 QTextDocument emits a textChanged() signal if the text changes and it also
1166 provides a isModified() function which will return true if the text has been
1167 modified since it was either loaded or since the last call to setModified
1168 with false as argument. In addition it provides methods for undo and redo.
1169
1170 \section2 Syntax Highlighting
1171
1172 Just like QTextEdit, QPlainTextEdit works together with
1173 QSyntaxHighlighter.
1174
1175 \section2 Editing Key Bindings
1176
1177 The list of key bindings which are implemented for editing:
1178 \table
1179 \header \li Keypresses \li Action
1180 \row \li Backspace \li Deletes the character to the left of the cursor.
1181 \row \li Delete \li Deletes the character to the right of the cursor.
1182 \row \li Ctrl+C \li Copy the selected text to the clipboard.
1183 \row \li Ctrl+Insert \li Copy the selected text to the clipboard.
1184 \row \li Ctrl+K \li Deletes to the end of the line.
1185 \row \li Ctrl+V \li Pastes the clipboard text into text edit.
1186 \row \li Shift+Insert \li Pastes the clipboard text into text edit.
1187 \row \li Ctrl+X \li Deletes the selected text and copies it to the clipboard.
1188 \row \li Shift+Delete \li Deletes the selected text and copies it to the clipboard.
1189 \row \li Ctrl+Z \li Undoes the last operation.
1190 \row \li Ctrl+Y \li Redoes the last operation.
1191 \row \li LeftArrow \li Moves the cursor one character to the left.
1192 \row \li Ctrl+LeftArrow \li Moves the cursor one word to the left.
1193 \row \li RightArrow \li Moves the cursor one character to the right.
1194 \row \li Ctrl+RightArrow \li Moves the cursor one word to the right.
1195 \row \li UpArrow \li Moves the cursor one line up.
1196 \row \li Ctrl+UpArrow \li Moves the cursor one word up.
1197 \row \li DownArrow \li Moves the cursor one line down.
1198 \row \li Ctrl+Down Arrow \li Moves the cursor one word down.
1199 \row \li PageUp \li Moves the cursor one page up.
1200 \row \li PageDown \li Moves the cursor one page down.
1201 \row \li Home \li Moves the cursor to the beginning of the line.
1202 \row \li Ctrl+Home \li Moves the cursor to the beginning of the text.
1203 \row \li End \li Moves the cursor to the end of the line.
1204 \row \li Ctrl+End \li Moves the cursor to the end of the text.
1205 \row \li Alt+Wheel \li Scrolls the page horizontally (the Wheel is the mouse wheel).
1206 \row \li Ctrl+Wheel \li Zooms the text.
1207 \endtable
1208
1209 To select (mark) text hold down the Shift key whilst pressing one
1210 of the movement keystrokes, for example, \e{Shift+Right Arrow}
1211 will select the character to the right, and \e{Shift+Ctrl+Right
1212 Arrow} will select the word to the right, etc.
1213
1214 \section1 Differences to QTextEdit
1215
1216 QPlainTextEdit is a thin class, implemented by using most of the
1217 technology that is behind QTextEdit and QTextDocument. Its
1218 performance benefits over QTextEdit stem mostly from using a
1219 different and simplified text layout called
1220 QPlainTextDocumentLayout on the text document (see
1221 QTextDocument::setDocumentLayout()). The plain text document layout
1222 does not support tables nor embedded frames, and \e{replaces a
1223 pixel-exact height calculation with a line-by-line respectively
1224 paragraph-by-paragraph scrolling approach}. This makes it possible
1225 to handle significantly larger documents, and still resize the
1226 editor with line wrap enabled in real time. It also makes for a
1227 fast log viewer (see setMaximumBlockCount()).
1228
1229
1230 \sa QTextDocument, QTextCursor, {Application Example},
1231 {Code Editor Example}, {Syntax Highlighter Example},
1232 {Rich Text Processing}
1233
1234*/
1235
1236/*!
1237 \property QPlainTextEdit::plainText
1238
1239 This property gets and sets the plain text editor's contents. The previous
1240 contents are removed and undo/redo history is reset when this property is set.
1241 currentCharFormat() is also reset, unless textCursor() is already at the
1242 beginning of the document.
1243
1244 By default, for an editor with no contents, this property contains an empty string.
1245*/
1246
1247/*!
1248 \property QPlainTextEdit::undoRedoEnabled
1249 \brief whether undo and redo are enabled
1250
1251 Users are only able to undo or redo actions if this property is
1252 true, and if there is an action that can be undone (or redone).
1253
1254 By default, this property is \c true.
1255*/
1256
1257/*!
1258 \enum QPlainTextEdit::LineWrapMode
1259
1260 \value NoWrap
1261 \value WidgetWidth
1262*/
1263
1264
1265/*!
1266 Constructs an empty QPlainTextEdit with parent \a
1267 parent.
1268*/
1269QPlainTextEdit::QPlainTextEdit(QWidget *parent)
1270 : QAbstractScrollArea(*new QPlainTextEditPrivate, parent)
1271{
1272 Q_D(QPlainTextEdit);
1273 d->init();
1274}
1275
1276/*!
1277 \internal
1278*/
1279QPlainTextEdit::QPlainTextEdit(QPlainTextEditPrivate &dd, QWidget *parent)
1280 : QAbstractScrollArea(dd, parent)
1281{
1282 Q_D(QPlainTextEdit);
1283 d->init();
1284}
1285
1286/*!
1287 Constructs a QPlainTextEdit with parent \a parent. The text edit will display
1288 the plain text \a text.
1289*/
1290QPlainTextEdit::QPlainTextEdit(const QString &text, QWidget *parent)
1291 : QAbstractScrollArea(*new QPlainTextEditPrivate, parent)
1292{
1293 Q_D(QPlainTextEdit);
1294 d->init(txt: text);
1295}
1296
1297
1298/*!
1299 Destructor.
1300*/
1301QPlainTextEdit::~QPlainTextEdit()
1302{
1303 Q_D(QPlainTextEdit);
1304 if (d->documentLayoutPtr) {
1305 if (d->documentLayoutPtr->priv()->mainViewPrivate == d)
1306 d->documentLayoutPtr->priv()->mainViewPrivate = nullptr;
1307 }
1308}
1309
1310/*!
1311 Makes \a document the new document of the text editor.
1312
1313 The parent QObject of the provided document remains the owner
1314 of the object. If the current document is a child of the text
1315 editor, then it is deleted.
1316
1317 The document must have a document layout that inherits
1318 QPlainTextDocumentLayout (see QTextDocument::setDocumentLayout()).
1319
1320 \sa document()
1321*/
1322void QPlainTextEdit::setDocument(QTextDocument *document)
1323{
1324 Q_D(QPlainTextEdit);
1325 QPlainTextDocumentLayout *documentLayout = nullptr;
1326
1327 if (!document) {
1328 document = new QTextDocument(d->control);
1329 documentLayout = new QPlainTextDocumentLayout(document);
1330 document->setDocumentLayout(documentLayout);
1331 } else {
1332 documentLayout = qobject_cast<QPlainTextDocumentLayout*>(object: document->documentLayout());
1333 if (Q_UNLIKELY(!documentLayout)) {
1334 qWarning(msg: "QPlainTextEdit::setDocument: Document set does not support QPlainTextDocumentLayout");
1335 return;
1336 }
1337 }
1338 d->control->setDocument(document);
1339 if (!documentLayout->priv()->mainViewPrivate)
1340 documentLayout->priv()->mainViewPrivate = d;
1341 d->documentLayoutPtr = documentLayout;
1342 d->updateDefaultTextOption();
1343 d->relayoutDocument();
1344 d->_q_adjustScrollbars();
1345}
1346
1347/*!
1348 Returns a pointer to the underlying document.
1349
1350 \sa setDocument()
1351*/
1352QTextDocument *QPlainTextEdit::document() const
1353{
1354 Q_D(const QPlainTextEdit);
1355 return d->control->document();
1356}
1357
1358/*!
1359 \since 5.3
1360
1361 \property QPlainTextEdit::placeholderText
1362 \brief the editor placeholder text
1363
1364 Setting this property makes the editor display a grayed-out
1365 placeholder text as long as the document() is empty.
1366
1367 By default, this property contains an empty string.
1368
1369 \sa document()
1370*/
1371void QPlainTextEdit::setPlaceholderText(const QString &placeholderText)
1372{
1373 Q_D(QPlainTextEdit);
1374 if (d->placeholderText != placeholderText) {
1375 d->placeholderText = placeholderText;
1376 if (d->control->document()->isEmpty())
1377 d->viewport->update();
1378 }
1379}
1380
1381QString QPlainTextEdit::placeholderText() const
1382{
1383 Q_D(const QPlainTextEdit);
1384 return d->placeholderText;
1385}
1386
1387/*!
1388 Sets the visible \a cursor.
1389*/
1390void QPlainTextEdit::setTextCursor(const QTextCursor &cursor)
1391{
1392 doSetTextCursor(cursor);
1393}
1394
1395/*!
1396 \internal
1397
1398 This provides a hook for subclasses to intercept cursor changes.
1399*/
1400
1401void QPlainTextEdit::doSetTextCursor(const QTextCursor &cursor)
1402{
1403 Q_D(QPlainTextEdit);
1404 d->control->setTextCursor(cursor);
1405}
1406
1407/*!
1408 Returns a copy of the QTextCursor that represents the currently visible cursor.
1409 Note that changes on the returned cursor do not affect QPlainTextEdit's cursor; use
1410 setTextCursor() to update the visible cursor.
1411 */
1412QTextCursor QPlainTextEdit::textCursor() const
1413{
1414 Q_D(const QPlainTextEdit);
1415 return d->control->textCursor();
1416}
1417
1418/*!
1419 Returns the reference of the anchor at position \a pos, or an
1420 empty string if no anchor exists at that point.
1421
1422 \since 4.7
1423 */
1424QString QPlainTextEdit::anchorAt(const QPoint &pos) const
1425{
1426 Q_D(const QPlainTextEdit);
1427 int cursorPos = d->control->hitTest(point: pos + QPointF(d->horizontalOffset(),
1428 d->verticalOffset()),
1429 Qt::ExactHit);
1430 if (cursorPos < 0)
1431 return QString();
1432
1433 QTextDocumentPrivate *pieceTable = document()->docHandle();
1434 QTextDocumentPrivate::FragmentIterator it = pieceTable->find(pos: cursorPos);
1435 QTextCharFormat fmt = pieceTable->formatCollection()->charFormat(index: it->format);
1436 return fmt.anchorHref();
1437}
1438
1439/*!
1440 Undoes the last operation.
1441
1442 If there is no operation to undo, i.e. there is no undo step in
1443 the undo/redo history, nothing happens.
1444
1445 \sa redo()
1446*/
1447void QPlainTextEdit::undo()
1448{
1449 Q_D(QPlainTextEdit);
1450 d->control->undo();
1451}
1452
1453void QPlainTextEdit::redo()
1454{
1455 Q_D(QPlainTextEdit);
1456 d->control->redo();
1457}
1458
1459/*!
1460 \fn void QPlainTextEdit::redo()
1461
1462 Redoes the last operation.
1463
1464 If there is no operation to redo, i.e. there is no redo step in
1465 the undo/redo history, nothing happens.
1466
1467 \sa undo()
1468*/
1469
1470#ifndef QT_NO_CLIPBOARD
1471/*!
1472 Copies the selected text to the clipboard and deletes it from
1473 the text edit.
1474
1475 If there is no selected text nothing happens.
1476
1477 \sa copy(), paste()
1478*/
1479
1480void QPlainTextEdit::cut()
1481{
1482 Q_D(QPlainTextEdit);
1483 d->control->cut();
1484}
1485
1486/*!
1487 Copies any selected text to the clipboard.
1488
1489 \sa copyAvailable()
1490*/
1491
1492void QPlainTextEdit::copy()
1493{
1494 Q_D(QPlainTextEdit);
1495 d->control->copy();
1496}
1497
1498/*!
1499 Pastes the text from the clipboard into the text edit at the
1500 current cursor position.
1501
1502 If there is no text in the clipboard nothing happens.
1503
1504 To change the behavior of this function, i.e. to modify what
1505 QPlainTextEdit can paste and how it is being pasted, reimplement the
1506 virtual canInsertFromMimeData() and insertFromMimeData()
1507 functions.
1508
1509 \sa cut(), copy()
1510*/
1511
1512void QPlainTextEdit::paste()
1513{
1514 Q_D(QPlainTextEdit);
1515 d->control->paste();
1516}
1517#endif
1518
1519/*!
1520 Deletes all the text in the text edit.
1521
1522 Notes:
1523 \list
1524 \li The undo/redo history is also cleared.
1525 \li currentCharFormat() is reset, unless textCursor()
1526 is already at the beginning of the document.
1527 \endlist
1528
1529 \sa cut(), setPlainText()
1530*/
1531void QPlainTextEdit::clear()
1532{
1533 Q_D(QPlainTextEdit);
1534 // clears and sets empty content
1535 d->control->topBlock = d->topLine = d->topLineFracture = 0;
1536 d->control->clear();
1537}
1538
1539
1540/*!
1541 Selects all text.
1542
1543 \sa copy(), cut(), textCursor()
1544 */
1545void QPlainTextEdit::selectAll()
1546{
1547 Q_D(QPlainTextEdit);
1548 d->control->selectAll();
1549}
1550
1551/*! \internal
1552*/
1553bool QPlainTextEdit::event(QEvent *e)
1554{
1555 Q_D(QPlainTextEdit);
1556
1557#ifndef QT_NO_CONTEXTMENU
1558 if (e->type() == QEvent::ContextMenu
1559 && static_cast<QContextMenuEvent *>(e)->reason() == QContextMenuEvent::Keyboard) {
1560 ensureCursorVisible();
1561 const QPoint cursorPos = cursorRect().center();
1562 QContextMenuEvent ce(QContextMenuEvent::Keyboard, cursorPos, d->viewport->mapToGlobal(cursorPos));
1563 ce.setAccepted(e->isAccepted());
1564 const bool result = QAbstractScrollArea::event(&ce);
1565 e->setAccepted(ce.isAccepted());
1566 return result;
1567 }
1568#endif // QT_NO_CONTEXTMENU
1569 if (e->type() == QEvent::ShortcutOverride
1570 || e->type() == QEvent::ToolTip) {
1571 d->sendControlEvent(e);
1572 }
1573#ifdef QT_KEYPAD_NAVIGATION
1574 else if (e->type() == QEvent::EnterEditFocus || e->type() == QEvent::LeaveEditFocus) {
1575 if (QApplicationPrivate::keypadNavigationEnabled())
1576 d->sendControlEvent(e);
1577 }
1578#endif
1579#ifndef QT_NO_GESTURES
1580 else if (e->type() == QEvent::Gesture) {
1581 QGestureEvent *ge = static_cast<QGestureEvent *>(e);
1582 QPanGesture *g = static_cast<QPanGesture *>(ge->gesture(type: Qt::PanGesture));
1583 if (g) {
1584 QScrollBar *hBar = horizontalScrollBar();
1585 QScrollBar *vBar = verticalScrollBar();
1586 if (g->state() == Qt::GestureStarted)
1587 d->originalOffsetY = vBar->value();
1588 QPointF offset = g->offset();
1589 if (!offset.isNull()) {
1590 if (QGuiApplication::isRightToLeft())
1591 offset.rx() *= -1;
1592 // QPlainTextEdit scrolls by lines only in vertical direction
1593 QFontMetrics fm(document()->defaultFont());
1594 int lineHeight = fm.height();
1595 int newX = hBar->value() - g->delta().x();
1596 int newY = d->originalOffsetY - offset.y()/lineHeight;
1597 hBar->setValue(newX);
1598 vBar->setValue(newY);
1599 }
1600 }
1601 return true;
1602 }
1603#endif // QT_NO_GESTURES
1604 return QAbstractScrollArea::event(e);
1605}
1606
1607/*! \internal
1608*/
1609
1610void QPlainTextEdit::timerEvent(QTimerEvent *e)
1611{
1612 Q_D(QPlainTextEdit);
1613 if (e->timerId() == d->autoScrollTimer.timerId()) {
1614 QRect visible = d->viewport->rect();
1615 QPoint pos;
1616 if (d->inDrag) {
1617 pos = d->autoScrollDragPos;
1618 visible.adjust(dx1: qMin(a: visible.width()/3,b: 20), dy1: qMin(a: visible.height()/3,b: 20),
1619 dx2: -qMin(a: visible.width()/3,b: 20), dy2: -qMin(a: visible.height()/3,b: 20));
1620 } else {
1621 const QPoint globalPos = QCursor::pos();
1622 pos = d->viewport->mapFromGlobal(globalPos);
1623 QMouseEvent ev(QEvent::MouseMove, pos, d->viewport->mapTo(d->viewport->topLevelWidget(), pos), globalPos,
1624 Qt::LeftButton, Qt::LeftButton, QGuiApplication::keyboardModifiers());
1625 mouseMoveEvent(e: &ev);
1626 }
1627 int deltaY = qMax(a: pos.y() - visible.top(), b: visible.bottom() - pos.y()) - visible.height();
1628 int deltaX = qMax(a: pos.x() - visible.left(), b: visible.right() - pos.x()) - visible.width();
1629 int delta = qMax(a: deltaX, b: deltaY);
1630 if (delta >= 0) {
1631 if (delta < 7)
1632 delta = 7;
1633 int timeout = 4900 / (delta * delta);
1634 d->autoScrollTimer.start(msec: timeout, obj: this);
1635
1636 if (deltaY > 0)
1637 d->vbar->triggerAction(action: pos.y() < visible.center().y() ?
1638 QAbstractSlider::SliderSingleStepSub
1639 : QAbstractSlider::SliderSingleStepAdd);
1640 if (deltaX > 0)
1641 d->hbar->triggerAction(action: pos.x() < visible.center().x() ?
1642 QAbstractSlider::SliderSingleStepSub
1643 : QAbstractSlider::SliderSingleStepAdd);
1644 }
1645 }
1646#ifdef QT_KEYPAD_NAVIGATION
1647 else if (e->timerId() == d->deleteAllTimer.timerId()) {
1648 d->deleteAllTimer.stop();
1649 clear();
1650 }
1651#endif
1652}
1653
1654/*!
1655 Changes the text of the text edit to the string \a text.
1656 Any previous text is removed.
1657
1658 \a text is interpreted as plain text.
1659
1660 Notes:
1661 \list
1662 \li The undo/redo history is also cleared.
1663 \li currentCharFormat() is reset, unless textCursor()
1664 is already at the beginning of the document.
1665 \endlist
1666
1667 \sa toPlainText()
1668*/
1669
1670void QPlainTextEdit::setPlainText(const QString &text)
1671{
1672 Q_D(QPlainTextEdit);
1673 d->control->setPlainText(text);
1674}
1675
1676/*!
1677 \fn QString QPlainTextEdit::toPlainText() const
1678
1679 Returns the text of the text edit as plain text.
1680
1681 \sa QPlainTextEdit::setPlainText()
1682 */
1683
1684/*! \reimp
1685*/
1686void QPlainTextEdit::keyPressEvent(QKeyEvent *e)
1687{
1688 Q_D(QPlainTextEdit);
1689
1690#ifdef QT_KEYPAD_NAVIGATION
1691 switch (e->key()) {
1692 case Qt::Key_Select:
1693 if (QApplicationPrivate::keypadNavigationEnabled()) {
1694 if (!(d->control->textInteractionFlags() & Qt::LinksAccessibleByKeyboard))
1695 setEditFocus(!hasEditFocus());
1696 else {
1697 if (!hasEditFocus())
1698 setEditFocus(true);
1699 else {
1700 QTextCursor cursor = d->control->textCursor();
1701 QTextCharFormat charFmt = cursor.charFormat();
1702 if (!cursor.hasSelection() || charFmt.anchorHref().isEmpty()) {
1703 setEditFocus(false);
1704 }
1705 }
1706 }
1707 }
1708 break;
1709 case Qt::Key_Back:
1710 case Qt::Key_No:
1711 if (!QApplicationPrivate::keypadNavigationEnabled()
1712 || (QApplicationPrivate::keypadNavigationEnabled() && !hasEditFocus())) {
1713 e->ignore();
1714 return;
1715 }
1716 break;
1717 default:
1718 if (QApplicationPrivate::keypadNavigationEnabled()) {
1719 if (!hasEditFocus() && !(e->modifiers() & Qt::ControlModifier)) {
1720 if (e->text()[0].isPrint()) {
1721 setEditFocus(true);
1722 clear();
1723 } else {
1724 e->ignore();
1725 return;
1726 }
1727 }
1728 }
1729 break;
1730 }
1731#endif
1732
1733#ifndef QT_NO_SHORTCUT
1734
1735 Qt::TextInteractionFlags tif = d->control->textInteractionFlags();
1736
1737 if (tif & Qt::TextSelectableByKeyboard){
1738 if (e == QKeySequence::SelectPreviousPage) {
1739 e->accept();
1740 d->pageUpDown(op: QTextCursor::Up, moveMode: QTextCursor::KeepAnchor);
1741 return;
1742 } else if (e ==QKeySequence::SelectNextPage) {
1743 e->accept();
1744 d->pageUpDown(op: QTextCursor::Down, moveMode: QTextCursor::KeepAnchor);
1745 return;
1746 }
1747 }
1748 if (tif & (Qt::TextSelectableByKeyboard | Qt::TextEditable)) {
1749 if (e == QKeySequence::MoveToPreviousPage) {
1750 e->accept();
1751 d->pageUpDown(op: QTextCursor::Up, moveMode: QTextCursor::MoveAnchor);
1752 return;
1753 } else if (e == QKeySequence::MoveToNextPage) {
1754 e->accept();
1755 d->pageUpDown(op: QTextCursor::Down, moveMode: QTextCursor::MoveAnchor);
1756 return;
1757 }
1758 }
1759
1760 if (!(tif & Qt::TextEditable)) {
1761 switch (e->key()) {
1762 case Qt::Key_Space:
1763 e->accept();
1764 if (e->modifiers() & Qt::ShiftModifier)
1765 d->vbar->triggerAction(action: QAbstractSlider::SliderPageStepSub);
1766 else
1767 d->vbar->triggerAction(action: QAbstractSlider::SliderPageStepAdd);
1768 break;
1769 default:
1770 d->sendControlEvent(e);
1771 if (!e->isAccepted() && e->modifiers() == Qt::NoModifier) {
1772 if (e->key() == Qt::Key_Home) {
1773 d->vbar->triggerAction(action: QAbstractSlider::SliderToMinimum);
1774 e->accept();
1775 } else if (e->key() == Qt::Key_End) {
1776 d->vbar->triggerAction(action: QAbstractSlider::SliderToMaximum);
1777 e->accept();
1778 }
1779 }
1780 if (!e->isAccepted()) {
1781 QAbstractScrollArea::keyPressEvent(e);
1782 }
1783 }
1784 return;
1785 }
1786#endif // QT_NO_SHORTCUT
1787
1788 d->sendControlEvent(e);
1789#ifdef QT_KEYPAD_NAVIGATION
1790 if (!e->isAccepted()) {
1791 switch (e->key()) {
1792 case Qt::Key_Up:
1793 case Qt::Key_Down:
1794 if (QApplicationPrivate::keypadNavigationEnabled()) {
1795 // Cursor position didn't change, so we want to leave
1796 // these keys to change focus.
1797 e->ignore();
1798 return;
1799 }
1800 break;
1801 case Qt::Key_Left:
1802 case Qt::Key_Right:
1803 if (QApplicationPrivate::keypadNavigationEnabled()
1804 && QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional) {
1805 // Same as for Key_Up and Key_Down.
1806 e->ignore();
1807 return;
1808 }
1809 break;
1810 case Qt::Key_Back:
1811 if (!e->isAutoRepeat()) {
1812 if (QApplicationPrivate::keypadNavigationEnabled()) {
1813 if (document()->isEmpty()) {
1814 setEditFocus(false);
1815 e->accept();
1816 } else if (!d->deleteAllTimer.isActive()) {
1817 e->accept();
1818 d->deleteAllTimer.start(750, this);
1819 }
1820 } else {
1821 e->ignore();
1822 return;
1823 }
1824 }
1825 break;
1826 default: break;
1827 }
1828 }
1829#endif
1830}
1831
1832/*! \reimp
1833*/
1834void QPlainTextEdit::keyReleaseEvent(QKeyEvent *e)
1835{
1836#ifdef QT_KEYPAD_NAVIGATION
1837 Q_D(QPlainTextEdit);
1838 if (QApplicationPrivate::keypadNavigationEnabled()) {
1839 if (!e->isAutoRepeat() && e->key() == Qt::Key_Back
1840 && d->deleteAllTimer.isActive()) {
1841 d->deleteAllTimer.stop();
1842 QTextCursor cursor = d->control->textCursor();
1843 QTextBlockFormat blockFmt = cursor.blockFormat();
1844
1845 QTextList *list = cursor.currentList();
1846 if (list && cursor.atBlockStart()) {
1847 list->remove(cursor.block());
1848 } else if (cursor.atBlockStart() && blockFmt.indent() > 0) {
1849 blockFmt.setIndent(blockFmt.indent() - 1);
1850 cursor.setBlockFormat(blockFmt);
1851 } else {
1852 cursor.deletePreviousChar();
1853 }
1854 setTextCursor(cursor);
1855 }
1856 }
1857#else
1858 QWidget::keyReleaseEvent(event: e);
1859#endif
1860}
1861
1862/*!
1863 Loads the resource specified by the given \a type and \a name.
1864
1865 This function is an extension of QTextDocument::loadResource().
1866
1867 \sa QTextDocument::loadResource()
1868*/
1869QVariant QPlainTextEdit::loadResource(int type, const QUrl &name)
1870{
1871 Q_UNUSED(type);
1872 Q_UNUSED(name);
1873 return QVariant();
1874}
1875
1876/*! \reimp
1877*/
1878void QPlainTextEdit::resizeEvent(QResizeEvent *e)
1879{
1880 Q_D(QPlainTextEdit);
1881 if (e->oldSize().width() != e->size().width())
1882 d->relayoutDocument();
1883 d->_q_adjustScrollbars();
1884}
1885
1886void QPlainTextEditPrivate::relayoutDocument()
1887{
1888 QTextDocument *doc = control->document();
1889 QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(object: doc->documentLayout());
1890 Q_ASSERT(documentLayout);
1891 documentLayoutPtr = documentLayout;
1892
1893 int width = viewport->width();
1894
1895 if (documentLayout->priv()->mainViewPrivate == nullptr
1896 || documentLayout->priv()->mainViewPrivate == this
1897 || width > documentLayout->textWidth()) {
1898 documentLayout->priv()->mainViewPrivate = this;
1899 documentLayout->setTextWidth(width);
1900 }
1901}
1902
1903static void fillBackground(QPainter *p, const QRectF &rect, QBrush brush, const QRectF &gradientRect = QRectF())
1904{
1905 p->save();
1906 if (brush.style() >= Qt::LinearGradientPattern && brush.style() <= Qt::ConicalGradientPattern) {
1907 if (!gradientRect.isNull()) {
1908 QTransform m = QTransform::fromTranslate(dx: gradientRect.left(), dy: gradientRect.top());
1909 m.scale(sx: gradientRect.width(), sy: gradientRect.height());
1910 brush.setTransform(m);
1911 const_cast<QGradient *>(brush.gradient())->setCoordinateMode(QGradient::LogicalMode);
1912 }
1913 } else {
1914 p->setBrushOrigin(rect.topLeft());
1915 }
1916 p->fillRect(rect, brush);
1917 p->restore();
1918}
1919
1920
1921
1922/*! \reimp
1923*/
1924void QPlainTextEdit::paintEvent(QPaintEvent *e)
1925{
1926 Q_D(QPlainTextEdit);
1927 QPainter painter(viewport());
1928 Q_ASSERT(qobject_cast<QPlainTextDocumentLayout*>(document()->documentLayout()));
1929
1930 QPointF offset(contentOffset());
1931
1932 QRect er = e->rect();
1933 QRect viewportRect = viewport()->rect();
1934
1935 bool editable = !isReadOnly();
1936
1937 QTextBlock block = firstVisibleBlock();
1938 qreal maximumWidth = document()->documentLayout()->documentSize().width();
1939
1940 // Set a brush origin so that the WaveUnderline knows where the wave started
1941 painter.setBrushOrigin(offset);
1942
1943 // keep right margin clean from full-width selection
1944 int maxX = offset.x() + qMax(a: (qreal)viewportRect.width(), b: maximumWidth)
1945 - document()->documentMargin() + cursorWidth();
1946 er.setRight(qMin(a: er.right(), b: maxX));
1947 painter.setClipRect(er);
1948
1949 if (d->placeholderVisible) {
1950 const QColor col = d->control->palette().placeholderText().color();
1951 painter.setPen(col);
1952 painter.setClipRect(e->rect());
1953 const int margin = int(document()->documentMargin());
1954 QRectF textRect = viewportRect.adjusted(xp1: margin, yp1: margin, xp2: 0, yp2: 0);
1955 painter.drawText(r: textRect, flags: Qt::AlignTop | Qt::TextWordWrap, text: placeholderText());
1956 }
1957
1958 QAbstractTextDocumentLayout::PaintContext context = getPaintContext();
1959 painter.setPen(context.palette.text().color());
1960
1961 while (block.isValid()) {
1962
1963 QRectF r = blockBoundingRect(block).translated(p: offset);
1964 QTextLayout *layout = block.layout();
1965
1966 if (!block.isVisible()) {
1967 offset.ry() += r.height();
1968 block = block.next();
1969 continue;
1970 }
1971
1972 if (r.bottom() >= er.top() && r.top() <= er.bottom()) {
1973
1974 QTextBlockFormat blockFormat = block.blockFormat();
1975
1976 QBrush bg = blockFormat.background();
1977 if (bg != Qt::NoBrush) {
1978 QRectF contentsRect = r;
1979 contentsRect.setWidth(qMax(a: r.width(), b: maximumWidth));
1980 fillBackground(p: &painter, rect: contentsRect, brush: bg);
1981 }
1982
1983
1984 QVector<QTextLayout::FormatRange> selections;
1985 int blpos = block.position();
1986 int bllen = block.length();
1987 for (int i = 0; i < context.selections.size(); ++i) {
1988 const QAbstractTextDocumentLayout::Selection &range = context.selections.at(i);
1989 const int selStart = range.cursor.selectionStart() - blpos;
1990 const int selEnd = range.cursor.selectionEnd() - blpos;
1991 if (selStart < bllen && selEnd > 0
1992 && selEnd > selStart) {
1993 QTextLayout::FormatRange o;
1994 o.start = selStart;
1995 o.length = selEnd - selStart;
1996 o.format = range.format;
1997 selections.append(t: o);
1998 } else if (!range.cursor.hasSelection() && range.format.hasProperty(propertyId: QTextFormat::FullWidthSelection)
1999 && block.contains(position: range.cursor.position())) {
2000 // for full width selections we don't require an actual selection, just
2001 // a position to specify the line. that's more convenience in usage.
2002 QTextLayout::FormatRange o;
2003 QTextLine l = layout->lineForTextPosition(pos: range.cursor.position() - blpos);
2004 o.start = l.textStart();
2005 o.length = l.textLength();
2006 if (o.start + o.length == bllen - 1)
2007 ++o.length; // include newline
2008 o.format = range.format;
2009 selections.append(t: o);
2010 }
2011 }
2012
2013 bool drawCursor = ((editable || (textInteractionFlags() & Qt::TextSelectableByKeyboard))
2014 && context.cursorPosition >= blpos
2015 && context.cursorPosition < blpos + bllen);
2016
2017 bool drawCursorAsBlock = drawCursor && overwriteMode() ;
2018
2019 if (drawCursorAsBlock) {
2020 if (context.cursorPosition == blpos + bllen - 1) {
2021 drawCursorAsBlock = false;
2022 } else {
2023 QTextLayout::FormatRange o;
2024 o.start = context.cursorPosition - blpos;
2025 o.length = 1;
2026 o.format.setForeground(palette().base());
2027 o.format.setBackground(palette().text());
2028 selections.append(t: o);
2029 }
2030 }
2031
2032 layout->draw(p: &painter, pos: offset, selections, clip: er);
2033
2034 if ((drawCursor && !drawCursorAsBlock)
2035 || (editable && context.cursorPosition < -1
2036 && !layout->preeditAreaText().isEmpty())) {
2037 int cpos = context.cursorPosition;
2038 if (cpos < -1)
2039 cpos = layout->preeditAreaPosition() - (cpos + 2);
2040 else
2041 cpos -= blpos;
2042 layout->drawCursor(p: &painter, pos: offset, cursorPosition: cpos, width: cursorWidth());
2043 }
2044 }
2045
2046 offset.ry() += r.height();
2047 if (offset.y() > viewportRect.height())
2048 break;
2049 block = block.next();
2050 }
2051
2052 if (backgroundVisible() && !block.isValid() && offset.y() <= er.bottom()
2053 && (centerOnScroll() || verticalScrollBar()->maximum() == verticalScrollBar()->minimum())) {
2054 painter.fillRect(QRect(QPoint((int)er.left(), (int)offset.y()), er.bottomRight()), palette().window());
2055 }
2056}
2057
2058
2059void QPlainTextEditPrivate::updateDefaultTextOption()
2060{
2061 QTextDocument *doc = control->document();
2062
2063 QTextOption opt = doc->defaultTextOption();
2064 QTextOption::WrapMode oldWrapMode = opt.wrapMode();
2065
2066 if (lineWrap == QPlainTextEdit::NoWrap)
2067 opt.setWrapMode(QTextOption::NoWrap);
2068 else
2069 opt.setWrapMode(wordWrap);
2070
2071 if (opt.wrapMode() != oldWrapMode)
2072 doc->setDefaultTextOption(opt);
2073}
2074
2075
2076/*! \reimp
2077*/
2078void QPlainTextEdit::mousePressEvent(QMouseEvent *e)
2079{
2080 Q_D(QPlainTextEdit);
2081#ifdef QT_KEYPAD_NAVIGATION
2082 if (QApplicationPrivate::keypadNavigationEnabled() && !hasEditFocus())
2083 setEditFocus(true);
2084#endif
2085 d->sendControlEvent(e);
2086}
2087
2088/*! \reimp
2089*/
2090void QPlainTextEdit::mouseMoveEvent(QMouseEvent *e)
2091{
2092 Q_D(QPlainTextEdit);
2093 d->inDrag = false; // paranoia
2094 const QPoint pos = e->pos();
2095 d->sendControlEvent(e);
2096 if (!(e->buttons() & Qt::LeftButton))
2097 return;
2098 if (e->source() == Qt::MouseEventNotSynthesized) {
2099 const QRect visible = d->viewport->rect();
2100 if (visible.contains(p: pos))
2101 d->autoScrollTimer.stop();
2102 else if (!d->autoScrollTimer.isActive())
2103 d->autoScrollTimer.start(msec: 100, obj: this);
2104 }
2105}
2106
2107/*! \reimp
2108*/
2109void QPlainTextEdit::mouseReleaseEvent(QMouseEvent *e)
2110{
2111 Q_D(QPlainTextEdit);
2112 d->sendControlEvent(e);
2113 if (e->source() == Qt::MouseEventNotSynthesized && d->autoScrollTimer.isActive()) {
2114 d->autoScrollTimer.stop();
2115 d->ensureCursorVisible();
2116 }
2117
2118 if (!isReadOnly() && rect().contains(p: e->pos()))
2119 d->handleSoftwareInputPanel(button: e->button(), clickCausedFocus: d->clickCausedFocus);
2120 d->clickCausedFocus = 0;
2121}
2122
2123/*! \reimp
2124*/
2125void QPlainTextEdit::mouseDoubleClickEvent(QMouseEvent *e)
2126{
2127 Q_D(QPlainTextEdit);
2128 d->sendControlEvent(e);
2129}
2130
2131/*! \reimp
2132*/
2133bool QPlainTextEdit::focusNextPrevChild(bool next)
2134{
2135 Q_D(const QPlainTextEdit);
2136 if (!d->tabChangesFocus && d->control->textInteractionFlags() & Qt::TextEditable)
2137 return false;
2138 return QAbstractScrollArea::focusNextPrevChild(next);
2139}
2140
2141#ifndef QT_NO_CONTEXTMENU
2142/*!
2143 \fn void QPlainTextEdit::contextMenuEvent(QContextMenuEvent *event)
2144
2145 Shows the standard context menu created with createStandardContextMenu().
2146
2147 If you do not want the text edit to have a context menu, you can set
2148 its \l contextMenuPolicy to Qt::NoContextMenu. If you want to
2149 customize the context menu, reimplement this function. If you want
2150 to extend the standard context menu, reimplement this function, call
2151 createStandardContextMenu() and extend the menu returned.
2152
2153 Information about the event is passed in the \a event object.
2154
2155 \snippet code/src_gui_widgets_qplaintextedit.cpp 0
2156*/
2157void QPlainTextEdit::contextMenuEvent(QContextMenuEvent *e)
2158{
2159 Q_D(QPlainTextEdit);
2160 d->sendControlEvent(e);
2161}
2162#endif // QT_NO_CONTEXTMENU
2163
2164#if QT_CONFIG(draganddrop)
2165/*! \reimp
2166*/
2167void QPlainTextEdit::dragEnterEvent(QDragEnterEvent *e)
2168{
2169 Q_D(QPlainTextEdit);
2170 d->inDrag = true;
2171 d->sendControlEvent(e);
2172}
2173
2174/*! \reimp
2175*/
2176void QPlainTextEdit::dragLeaveEvent(QDragLeaveEvent *e)
2177{
2178 Q_D(QPlainTextEdit);
2179 d->inDrag = false;
2180 d->autoScrollTimer.stop();
2181 d->sendControlEvent(e);
2182}
2183
2184/*! \reimp
2185*/
2186void QPlainTextEdit::dragMoveEvent(QDragMoveEvent *e)
2187{
2188 Q_D(QPlainTextEdit);
2189 d->autoScrollDragPos = e->pos();
2190 if (!d->autoScrollTimer.isActive())
2191 d->autoScrollTimer.start(msec: 100, obj: this);
2192 d->sendControlEvent(e);
2193}
2194
2195/*! \reimp
2196*/
2197void QPlainTextEdit::dropEvent(QDropEvent *e)
2198{
2199 Q_D(QPlainTextEdit);
2200 d->inDrag = false;
2201 d->autoScrollTimer.stop();
2202 d->sendControlEvent(e);
2203}
2204
2205#endif // QT_CONFIG(draganddrop)
2206
2207/*! \reimp
2208 */
2209void QPlainTextEdit::inputMethodEvent(QInputMethodEvent *e)
2210{
2211 Q_D(QPlainTextEdit);
2212#ifdef QT_KEYPAD_NAVIGATION
2213 if (d->control->textInteractionFlags() & Qt::TextEditable
2214 && QApplicationPrivate::keypadNavigationEnabled()
2215 && !hasEditFocus()) {
2216 setEditFocus(true);
2217 selectAll(); // so text is replaced rather than appended to
2218 }
2219#endif
2220 d->sendControlEvent(e);
2221 ensureCursorVisible();
2222}
2223
2224/*!\reimp
2225*/
2226void QPlainTextEdit::scrollContentsBy(int dx, int /*dy*/)
2227{
2228 Q_D(QPlainTextEdit);
2229 d->setTopLine(visualTopLine: d->vbar->value(), dx);
2230}
2231
2232/*!\reimp
2233*/
2234QVariant QPlainTextEdit::inputMethodQuery(Qt::InputMethodQuery property) const
2235{
2236 return inputMethodQuery(query: property, argument: QVariant());
2237}
2238
2239/*!\internal
2240 */
2241QVariant QPlainTextEdit::inputMethodQuery(Qt::InputMethodQuery query, QVariant argument) const
2242{
2243 Q_D(const QPlainTextEdit);
2244 switch (query) {
2245 case Qt::ImHints:
2246 case Qt::ImInputItemClipRectangle:
2247 return QWidget::inputMethodQuery(query);
2248 default:
2249 break;
2250 }
2251
2252 const QPointF offset = contentOffset();
2253 switch (argument.userType()) {
2254 case QMetaType::QRectF:
2255 argument = argument.toRectF().translated(p: -offset);
2256 break;
2257 case QMetaType::QPointF:
2258 argument = argument.toPointF() - offset;
2259 break;
2260 case QMetaType::QRect:
2261 argument = argument.toRect().translated(p: -offset.toPoint());
2262 break;
2263 case QMetaType::QPoint:
2264 argument = argument.toPoint() - offset;
2265 break;
2266 default:
2267 break;
2268 }
2269
2270 const QVariant v = d->control->inputMethodQuery(property: query, argument);
2271 switch (v.userType()) {
2272 case QMetaType::QRectF:
2273 return v.toRectF().translated(p: offset);
2274 case QMetaType::QPointF:
2275 return v.toPointF() + offset;
2276 case QMetaType::QRect:
2277 return v.toRect().translated(p: offset.toPoint());
2278 case QMetaType::QPoint:
2279 return v.toPoint() + offset.toPoint();
2280 default:
2281 break;
2282 }
2283 return v;
2284}
2285
2286/*! \reimp
2287*/
2288void QPlainTextEdit::focusInEvent(QFocusEvent *e)
2289{
2290 Q_D(QPlainTextEdit);
2291 if (e->reason() == Qt::MouseFocusReason) {
2292 d->clickCausedFocus = 1;
2293 }
2294 QAbstractScrollArea::focusInEvent(event: e);
2295 d->sendControlEvent(e);
2296}
2297
2298/*! \reimp
2299*/
2300void QPlainTextEdit::focusOutEvent(QFocusEvent *e)
2301{
2302 Q_D(QPlainTextEdit);
2303 QAbstractScrollArea::focusOutEvent(event: e);
2304 d->sendControlEvent(e);
2305}
2306
2307/*! \reimp
2308*/
2309void QPlainTextEdit::showEvent(QShowEvent *)
2310{
2311 Q_D(QPlainTextEdit);
2312 if (d->showCursorOnInitialShow) {
2313 d->showCursorOnInitialShow = false;
2314 ensureCursorVisible();
2315 }
2316}
2317
2318/*! \reimp
2319*/
2320void QPlainTextEdit::changeEvent(QEvent *e)
2321{
2322 Q_D(QPlainTextEdit);
2323 QAbstractScrollArea::changeEvent(e);
2324 if (e->type() == QEvent::ApplicationFontChange
2325 || e->type() == QEvent::FontChange) {
2326 d->control->document()->setDefaultFont(font());
2327 } else if(e->type() == QEvent::ActivationChange) {
2328 if (!isActiveWindow())
2329 d->autoScrollTimer.stop();
2330 } else if (e->type() == QEvent::EnabledChange) {
2331 e->setAccepted(isEnabled());
2332 d->control->setPalette(palette());
2333 d->sendControlEvent(e);
2334 } else if (e->type() == QEvent::PaletteChange) {
2335 d->control->setPalette(palette());
2336 } else if (e->type() == QEvent::LayoutDirectionChange) {
2337 d->sendControlEvent(e);
2338 }
2339}
2340
2341/*! \reimp
2342*/
2343#if QT_CONFIG(wheelevent)
2344void QPlainTextEdit::wheelEvent(QWheelEvent *e)
2345{
2346 Q_D(QPlainTextEdit);
2347 if (!(d->control->textInteractionFlags() & Qt::TextEditable)) {
2348 if (e->modifiers() & Qt::ControlModifier) {
2349 float delta = e->angleDelta().y() / 120.f;
2350 zoomInF(range: delta);
2351 return;
2352 }
2353 }
2354 QAbstractScrollArea::wheelEvent(e);
2355 updateMicroFocus();
2356}
2357#endif
2358
2359/*!
2360 Zooms in on the text by making the base font size \a range
2361 points larger and recalculating all font sizes to be the new size.
2362 This does not change the size of any images.
2363
2364 \sa zoomOut()
2365*/
2366void QPlainTextEdit::zoomIn(int range)
2367{
2368 zoomInF(range);
2369}
2370
2371/*!
2372 Zooms out on the text by making the base font size \a range points
2373 smaller and recalculating all font sizes to be the new size. This
2374 does not change the size of any images.
2375
2376 \sa zoomIn()
2377*/
2378void QPlainTextEdit::zoomOut(int range)
2379{
2380 zoomInF(range: -range);
2381}
2382
2383/*!
2384 \internal
2385*/
2386void QPlainTextEdit::zoomInF(float range)
2387{
2388 if (range == 0.f)
2389 return;
2390 QFont f = font();
2391 const float newSize = f.pointSizeF() + range;
2392 if (newSize <= 0)
2393 return;
2394 f.setPointSizeF(newSize);
2395 setFont(f);
2396}
2397
2398#ifndef QT_NO_CONTEXTMENU
2399/*! This function creates the standard context menu which is shown
2400 when the user clicks on the text edit with the right mouse
2401 button. It is called from the default contextMenuEvent() handler.
2402 The popup menu's ownership is transferred to the caller.
2403
2404 We recommend that you use the createStandardContextMenu(QPoint) version instead
2405 which will enable the actions that are sensitive to where the user clicked.
2406*/
2407
2408QMenu *QPlainTextEdit::createStandardContextMenu()
2409{
2410 Q_D(QPlainTextEdit);
2411 return d->control->createStandardContextMenu(pos: QPointF(), parent: this);
2412}
2413
2414/*!
2415 \since 5.5
2416 This function creates the standard context menu which is shown
2417 when the user clicks on the text edit with the right mouse
2418 button. It is called from the default contextMenuEvent() handler
2419 and it takes the \a position in document coordinates where the mouse click was.
2420 This can enable actions that are sensitive to the position where the user clicked.
2421 The popup menu's ownership is transferred to the caller.
2422*/
2423
2424QMenu *QPlainTextEdit::createStandardContextMenu(const QPoint &position)
2425{
2426 Q_D(QPlainTextEdit);
2427 return d->control->createStandardContextMenu(pos: position, parent: this);
2428}
2429#endif // QT_NO_CONTEXTMENU
2430
2431/*!
2432 returns a QTextCursor at position \a pos (in viewport coordinates).
2433*/
2434QTextCursor QPlainTextEdit::cursorForPosition(const QPoint &pos) const
2435{
2436 Q_D(const QPlainTextEdit);
2437 return d->control->cursorForPosition(pos: d->mapToContents(point: pos));
2438}
2439
2440/*!
2441 returns a rectangle (in viewport coordinates) that includes the
2442 \a cursor.
2443 */
2444QRect QPlainTextEdit::cursorRect(const QTextCursor &cursor) const
2445{
2446 Q_D(const QPlainTextEdit);
2447 if (cursor.isNull())
2448 return QRect();
2449
2450 QRect r = d->control->cursorRect(cursor).toRect();
2451 r.translate(dx: -d->horizontalOffset(),dy: -(int)d->verticalOffset());
2452 return r;
2453}
2454
2455/*!
2456 returns a rectangle (in viewport coordinates) that includes the
2457 cursor of the text edit.
2458 */
2459QRect QPlainTextEdit::cursorRect() const
2460{
2461 Q_D(const QPlainTextEdit);
2462 QRect r = d->control->cursorRect().toRect();
2463 r.translate(dx: -d->horizontalOffset(),dy: -(int)d->verticalOffset());
2464 return r;
2465}
2466
2467
2468/*!
2469 \property QPlainTextEdit::overwriteMode
2470 \brief whether text entered by the user will overwrite existing text
2471
2472 As with many text editors, the plain text editor widget can be configured
2473 to insert or overwrite existing text with new text entered by the user.
2474
2475 If this property is \c true, existing text is overwritten, character-for-character
2476 by new text; otherwise, text is inserted at the cursor position, displacing
2477 existing text.
2478
2479 By default, this property is \c false (new text does not overwrite existing text).
2480*/
2481
2482bool QPlainTextEdit::overwriteMode() const
2483{
2484 Q_D(const QPlainTextEdit);
2485 return d->control->overwriteMode();
2486}
2487
2488void QPlainTextEdit::setOverwriteMode(bool overwrite)
2489{
2490 Q_D(QPlainTextEdit);
2491 d->control->setOverwriteMode(overwrite);
2492}
2493
2494#if QT_DEPRECATED_SINCE(5, 10)
2495/*!
2496 \property QPlainTextEdit::tabStopWidth
2497 \brief the tab stop width in pixels
2498 \deprecated in Qt 5.10. Use tabStopDistance instead.
2499
2500 By default, this property contains a value of 80.
2501*/
2502
2503int QPlainTextEdit::tabStopWidth() const
2504{
2505 return qRound(d: tabStopDistance());
2506}
2507
2508void QPlainTextEdit::setTabStopWidth(int width)
2509{
2510 setTabStopDistance(width);
2511}
2512#endif
2513
2514/*!
2515 \property QPlainTextEdit::tabStopDistance
2516 \brief the tab stop distance in pixels
2517 \since 5.10
2518
2519 By default, this property contains a value of 80.
2520*/
2521
2522qreal QPlainTextEdit::tabStopDistance() const
2523{
2524 Q_D(const QPlainTextEdit);
2525 return d->control->document()->defaultTextOption().tabStopDistance();
2526}
2527
2528void QPlainTextEdit::setTabStopDistance(qreal distance)
2529{
2530 Q_D(QPlainTextEdit);
2531 QTextOption opt = d->control->document()->defaultTextOption();
2532 if (opt.tabStopDistance() == distance || distance < 0)
2533 return;
2534 opt.setTabStopDistance(distance);
2535 d->control->document()->setDefaultTextOption(opt);
2536}
2537
2538
2539/*!
2540 \property QPlainTextEdit::cursorWidth
2541
2542 This property specifies the width of the cursor in pixels. The default value is 1.
2543*/
2544int QPlainTextEdit::cursorWidth() const
2545{
2546 Q_D(const QPlainTextEdit);
2547 return d->control->cursorWidth();
2548}
2549
2550void QPlainTextEdit::setCursorWidth(int width)
2551{
2552 Q_D(QPlainTextEdit);
2553 d->control->setCursorWidth(width);
2554}
2555
2556
2557
2558/*!
2559 This function allows temporarily marking certain regions in the document
2560 with a given color, specified as \a selections. This can be useful for
2561 example in a programming editor to mark a whole line of text with a given
2562 background color to indicate the existence of a breakpoint.
2563
2564 \sa QTextEdit::ExtraSelection, extraSelections()
2565*/
2566void QPlainTextEdit::setExtraSelections(const QList<QTextEdit::ExtraSelection> &selections)
2567{
2568 Q_D(QPlainTextEdit);
2569 d->control->setExtraSelections(selections);
2570}
2571
2572/*!
2573 Returns previously set extra selections.
2574
2575 \sa setExtraSelections()
2576*/
2577QList<QTextEdit::ExtraSelection> QPlainTextEdit::extraSelections() const
2578{
2579 Q_D(const QPlainTextEdit);
2580 return d->control->extraSelections();
2581}
2582
2583/*!
2584 This function returns a new MIME data object to represent the contents
2585 of the text edit's current selection. It is called when the selection needs
2586 to be encapsulated into a new QMimeData object; for example, when a drag
2587 and drop operation is started, or when data is copied to the clipboard.
2588
2589 If you reimplement this function, note that the ownership of the returned
2590 QMimeData object is passed to the caller. The selection can be retrieved
2591 by using the textCursor() function.
2592*/
2593QMimeData *QPlainTextEdit::createMimeDataFromSelection() const
2594{
2595 Q_D(const QPlainTextEdit);
2596 return d->control->QWidgetTextControl::createMimeDataFromSelection();
2597}
2598
2599/*!
2600 This function returns \c true if the contents of the MIME data object, specified
2601 by \a source, can be decoded and inserted into the document. It is called
2602 for example when during a drag operation the mouse enters this widget and it
2603 is necessary to determine whether it is possible to accept the drag.
2604 */
2605bool QPlainTextEdit::canInsertFromMimeData(const QMimeData *source) const
2606{
2607 Q_D(const QPlainTextEdit);
2608 return d->control->QWidgetTextControl::canInsertFromMimeData(source);
2609}
2610
2611/*!
2612 This function inserts the contents of the MIME data object, specified
2613 by \a source, into the text edit at the current cursor position. It is
2614 called whenever text is inserted as the result of a clipboard paste
2615 operation, or when the text edit accepts data from a drag and drop
2616 operation.
2617*/
2618void QPlainTextEdit::insertFromMimeData(const QMimeData *source)
2619{
2620 Q_D(QPlainTextEdit);
2621 d->control->QWidgetTextControl::insertFromMimeData(source);
2622}
2623
2624/*!
2625 \property QPlainTextEdit::readOnly
2626 \brief whether the text edit is read-only
2627
2628 In a read-only text edit the user can only navigate through the
2629 text and select text; modifying the text is not possible.
2630
2631 This property's default is false.
2632*/
2633
2634bool QPlainTextEdit::isReadOnly() const
2635{
2636 Q_D(const QPlainTextEdit);
2637 return !(d->control->textInteractionFlags() & Qt::TextEditable);
2638}
2639
2640void QPlainTextEdit::setReadOnly(bool ro)
2641{
2642 Q_D(QPlainTextEdit);
2643 Qt::TextInteractionFlags flags = Qt::NoTextInteraction;
2644 if (ro) {
2645 flags = Qt::TextSelectableByMouse;
2646 } else {
2647 flags = Qt::TextEditorInteraction;
2648 }
2649 d->control->setTextInteractionFlags(flags);
2650 setAttribute(Qt::WA_InputMethodEnabled, on: shouldEnableInputMethod(plaintextedit: this));
2651 QEvent event(QEvent::ReadOnlyChange);
2652 QCoreApplication::sendEvent(receiver: this, event: &event);
2653}
2654
2655/*!
2656 \property QPlainTextEdit::textInteractionFlags
2657
2658 Specifies how the label should interact with user input if it displays text.
2659
2660 If the flags contain either Qt::LinksAccessibleByKeyboard or Qt::TextSelectableByKeyboard
2661 then the focus policy is also automatically set to Qt::ClickFocus.
2662
2663 The default value depends on whether the QPlainTextEdit is read-only
2664 or editable.
2665*/
2666
2667void QPlainTextEdit::setTextInteractionFlags(Qt::TextInteractionFlags flags)
2668{
2669 Q_D(QPlainTextEdit);
2670 d->control->setTextInteractionFlags(flags);
2671}
2672
2673Qt::TextInteractionFlags QPlainTextEdit::textInteractionFlags() const
2674{
2675 Q_D(const QPlainTextEdit);
2676 return d->control->textInteractionFlags();
2677}
2678
2679/*!
2680 Merges the properties specified in \a modifier into the current character
2681 format by calling QTextCursor::mergeCharFormat on the editor's cursor.
2682 If the editor has a selection then the properties of \a modifier are
2683 directly applied to the selection.
2684
2685 \sa QTextCursor::mergeCharFormat()
2686 */
2687void QPlainTextEdit::mergeCurrentCharFormat(const QTextCharFormat &modifier)
2688{
2689 Q_D(QPlainTextEdit);
2690 d->control->mergeCurrentCharFormat(modifier);
2691}
2692
2693/*!
2694 Sets the char format that is be used when inserting new text to \a
2695 format by calling QTextCursor::setCharFormat() on the editor's
2696 cursor. If the editor has a selection then the char format is
2697 directly applied to the selection.
2698 */
2699void QPlainTextEdit::setCurrentCharFormat(const QTextCharFormat &format)
2700{
2701 Q_D(QPlainTextEdit);
2702 d->control->setCurrentCharFormat(format);
2703}
2704
2705/*!
2706 Returns the char format that is used when inserting new text.
2707 */
2708QTextCharFormat QPlainTextEdit::currentCharFormat() const
2709{
2710 Q_D(const QPlainTextEdit);
2711 return d->control->currentCharFormat();
2712}
2713
2714
2715
2716/*!
2717 Convenience slot that inserts \a text at the current
2718 cursor position.
2719
2720 It is equivalent to
2721
2722 \snippet code/src_gui_widgets_qplaintextedit.cpp 1
2723 */
2724void QPlainTextEdit::insertPlainText(const QString &text)
2725{
2726 Q_D(QPlainTextEdit);
2727 d->control->insertPlainText(text);
2728}
2729
2730
2731/*!
2732 Moves the cursor by performing the given \a operation.
2733
2734 If \a mode is QTextCursor::KeepAnchor, the cursor selects the text it moves over.
2735 This is the same effect that the user achieves when they hold down the Shift key
2736 and move the cursor with the cursor keys.
2737
2738 \sa QTextCursor::movePosition()
2739*/
2740void QPlainTextEdit::moveCursor(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode)
2741{
2742 Q_D(QPlainTextEdit);
2743 d->control->moveCursor(op: operation, mode);
2744}
2745
2746/*!
2747 Returns whether text can be pasted from the clipboard into the textedit.
2748*/
2749bool QPlainTextEdit::canPaste() const
2750{
2751 Q_D(const QPlainTextEdit);
2752 return d->control->canPaste();
2753}
2754
2755/*!
2756 Convenience function to print the text edit's document to the given \a printer. This
2757 is equivalent to calling the print method on the document directly except that this
2758 function also supports QPrinter::Selection as print range.
2759
2760 \sa QTextDocument::print()
2761*/
2762#ifndef QT_NO_PRINTER
2763void QPlainTextEdit::print(QPagedPaintDevice *printer) const
2764{
2765 Q_D(const QPlainTextEdit);
2766 d->control->print(printer);
2767}
2768#endif
2769
2770/*! \property QPlainTextEdit::tabChangesFocus
2771 \brief whether \uicontrol Tab changes focus or is accepted as input
2772
2773 In some occasions text edits should not allow the user to input
2774 tabulators or change indentation using the \uicontrol Tab key, as this breaks
2775 the focus chain. The default is false.
2776
2777*/
2778
2779bool QPlainTextEdit::tabChangesFocus() const
2780{
2781 Q_D(const QPlainTextEdit);
2782 return d->tabChangesFocus;
2783}
2784
2785void QPlainTextEdit::setTabChangesFocus(bool b)
2786{
2787 Q_D(QPlainTextEdit);
2788 d->tabChangesFocus = b;
2789}
2790
2791/*!
2792 \property QPlainTextEdit::documentTitle
2793 \brief the title of the document parsed from the text.
2794
2795 By default, this property contains an empty string.
2796*/
2797
2798/*!
2799 \property QPlainTextEdit::lineWrapMode
2800 \brief the line wrap mode
2801
2802 The default mode is WidgetWidth which causes words to be
2803 wrapped at the right edge of the text edit. Wrapping occurs at
2804 whitespace, keeping whole words intact. If you want wrapping to
2805 occur within words use setWordWrapMode().
2806*/
2807
2808QPlainTextEdit::LineWrapMode QPlainTextEdit::lineWrapMode() const
2809{
2810 Q_D(const QPlainTextEdit);
2811 return d->lineWrap;
2812}
2813
2814void QPlainTextEdit::setLineWrapMode(LineWrapMode wrap)
2815{
2816 Q_D(QPlainTextEdit);
2817 if (d->lineWrap == wrap)
2818 return;
2819 d->lineWrap = wrap;
2820 d->updateDefaultTextOption();
2821 d->relayoutDocument();
2822 d->_q_adjustScrollbars();
2823 ensureCursorVisible();
2824}
2825
2826/*!
2827 \property QPlainTextEdit::wordWrapMode
2828 \brief the mode QPlainTextEdit will use when wrapping text by words
2829
2830 By default, this property is set to QTextOption::WrapAtWordBoundaryOrAnywhere.
2831
2832 \sa QTextOption::WrapMode
2833*/
2834
2835QTextOption::WrapMode QPlainTextEdit::wordWrapMode() const
2836{
2837 Q_D(const QPlainTextEdit);
2838 return d->wordWrap;
2839}
2840
2841void QPlainTextEdit::setWordWrapMode(QTextOption::WrapMode mode)
2842{
2843 Q_D(QPlainTextEdit);
2844 if (mode == d->wordWrap)
2845 return;
2846 d->wordWrap = mode;
2847 d->updateDefaultTextOption();
2848}
2849
2850/*!
2851 \property QPlainTextEdit::backgroundVisible
2852 \brief whether the palette background is visible outside the document area
2853
2854 If set to true, the plain text edit paints the palette background
2855 on the viewport area not covered by the text document. Otherwise,
2856 if set to false, it won't. The feature makes it possible for
2857 the user to visually distinguish between the area of the document,
2858 painted with the base color of the palette, and the empty
2859 area not covered by any document.
2860
2861 The default is false.
2862*/
2863
2864bool QPlainTextEdit::backgroundVisible() const
2865{
2866 Q_D(const QPlainTextEdit);
2867 return d->backgroundVisible;
2868}
2869
2870void QPlainTextEdit::setBackgroundVisible(bool visible)
2871{
2872 Q_D(QPlainTextEdit);
2873 if (visible == d->backgroundVisible)
2874 return;
2875 d->backgroundVisible = visible;
2876 d->updateViewport();
2877}
2878
2879/*!
2880 \property QPlainTextEdit::centerOnScroll
2881 \brief whether the cursor should be centered on screen
2882
2883 If set to true, the plain text edit scrolls the document
2884 vertically to make the cursor visible at the center of the
2885 viewport. This also allows the text edit to scroll below the end
2886 of the document. Otherwise, if set to false, the plain text edit
2887 scrolls the smallest amount possible to ensure the cursor is
2888 visible. The same algorithm is applied to any new line appended
2889 through appendPlainText().
2890
2891 The default is false.
2892
2893 \sa centerCursor(), ensureCursorVisible()
2894*/
2895
2896bool QPlainTextEdit::centerOnScroll() const
2897{
2898 Q_D(const QPlainTextEdit);
2899 return d->centerOnScroll;
2900}
2901
2902void QPlainTextEdit::setCenterOnScroll(bool enabled)
2903{
2904 Q_D(QPlainTextEdit);
2905 if (enabled == d->centerOnScroll)
2906 return;
2907 d->centerOnScroll = enabled;
2908 d->_q_adjustScrollbars();
2909}
2910
2911
2912
2913/*!
2914 Finds the next occurrence of the string, \a exp, using the given
2915 \a options. Returns \c true if \a exp was found and changes the
2916 cursor to select the match; otherwise returns \c false.
2917*/
2918bool QPlainTextEdit::find(const QString &exp, QTextDocument::FindFlags options)
2919{
2920 Q_D(QPlainTextEdit);
2921 return d->control->find(exp, options);
2922}
2923
2924/*!
2925 \fn bool QPlainTextEdit::find(const QRegExp &exp, QTextDocument::FindFlags options)
2926
2927 \since 5.3
2928 \overload
2929
2930 Finds the next occurrence, matching the regular expression, \a exp, using the given
2931 \a options. The QTextDocument::FindCaseSensitively option is ignored for this overload,
2932 use QRegExp::caseSensitivity instead.
2933
2934 Returns \c true if a match was found and changes the cursor to select the match;
2935 otherwise returns \c false.
2936*/
2937#ifndef QT_NO_REGEXP
2938bool QPlainTextEdit::find(const QRegExp &exp, QTextDocument::FindFlags options)
2939{
2940 Q_D(QPlainTextEdit);
2941 return d->control->find(exp, options);
2942}
2943#endif
2944
2945/*!
2946 \fn bool QPlainTextEdit::find(const QRegularExpression &exp, QTextDocument::FindFlags options)
2947
2948 \since 5.13
2949 \overload
2950
2951 Finds the next occurrence, matching the regular expression, \a exp, using the given
2952 \a options. The QTextDocument::FindCaseSensitively option is ignored for this overload,
2953 use QRegularExpression::CaseInsensitiveOption instead.
2954
2955 Returns \c true if a match was found and changes the cursor to select the match;
2956 otherwise returns \c false.
2957*/
2958#if QT_CONFIG(regularexpression)
2959bool QPlainTextEdit::find(const QRegularExpression &exp, QTextDocument::FindFlags options)
2960{
2961 Q_D(QPlainTextEdit);
2962 return d->control->find(exp, options);
2963}
2964#endif
2965
2966/*!
2967 \fn void QPlainTextEdit::copyAvailable(bool yes)
2968
2969 This signal is emitted when text is selected or de-selected in the
2970 text edit.
2971
2972 When text is selected this signal will be emitted with \a yes set
2973 to true. If no text has been selected or if the selected text is
2974 de-selected this signal is emitted with \a yes set to false.
2975
2976 If \a yes is true then copy() can be used to copy the selection to
2977 the clipboard. If \a yes is false then copy() does nothing.
2978
2979 \sa selectionChanged()
2980*/
2981
2982
2983/*!
2984 \fn void QPlainTextEdit::selectionChanged()
2985
2986 This signal is emitted whenever the selection changes.
2987
2988 \sa copyAvailable()
2989*/
2990
2991/*!
2992 \fn void QPlainTextEdit::cursorPositionChanged()
2993
2994 This signal is emitted whenever the position of the
2995 cursor changed.
2996*/
2997
2998
2999
3000/*!
3001 \fn void QPlainTextEdit::updateRequest(const QRect &rect, int dy)
3002
3003 This signal is emitted when the text document needs an update of
3004 the specified \a rect. If the text is scrolled, \a rect will cover
3005 the entire viewport area. If the text is scrolled vertically, \a
3006 dy carries the amount of pixels the viewport was scrolled.
3007
3008 The purpose of the signal is to support extra widgets in plain
3009 text edit subclasses that e.g. show line numbers, breakpoints, or
3010 other extra information.
3011*/
3012
3013/*! \fn void QPlainTextEdit::blockCountChanged(int newBlockCount);
3014
3015 This signal is emitted whenever the block count changes. The new
3016 block count is passed in \a newBlockCount.
3017*/
3018
3019/*! \fn void QPlainTextEdit::modificationChanged(bool changed);
3020
3021 This signal is emitted whenever the content of the document
3022 changes in a way that affects the modification state. If \a
3023 changed is true, the document has been modified; otherwise it is
3024 false.
3025
3026 For example, calling setModified(false) on a document and then
3027 inserting text causes the signal to get emitted. If you undo that
3028 operation, causing the document to return to its original
3029 unmodified state, the signal will get emitted again.
3030*/
3031
3032
3033
3034
3035void QPlainTextEditPrivate::append(const QString &text, Qt::TextFormat format)
3036{
3037 Q_Q(QPlainTextEdit);
3038
3039 QTextDocument *document = control->document();
3040 QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(object: document->documentLayout());
3041 Q_ASSERT(documentLayout);
3042
3043 int maximumBlockCount = document->maximumBlockCount();
3044 if (maximumBlockCount)
3045 document->setMaximumBlockCount(0);
3046
3047 const bool atBottom = q->isVisible()
3048 && (control->blockBoundingRect(block: document->lastBlock()).bottom() - verticalOffset()
3049 <= viewport->rect().bottom());
3050
3051 if (!q->isVisible())
3052 showCursorOnInitialShow = true;
3053
3054 bool documentSizeChangedBlocked = documentLayout->priv()->blockDocumentSizeChanged;
3055 documentLayout->priv()->blockDocumentSizeChanged = true;
3056
3057 if (format == Qt::RichText)
3058 control->appendHtml(html: text);
3059 else if (format == Qt::PlainText)
3060 control->appendPlainText(text);
3061 else
3062 control->append(text);
3063
3064 if (maximumBlockCount > 0) {
3065 if (document->blockCount() > maximumBlockCount) {
3066 bool blockUpdate = false;
3067 if (control->topBlock) {
3068 control->topBlock--;
3069 blockUpdate = true;
3070 emit q->updateRequest(rect: viewport->rect(), dy: 0);
3071 }
3072
3073 bool updatesBlocked = documentLayout->priv()->blockUpdate;
3074 documentLayout->priv()->blockUpdate = blockUpdate;
3075 QTextCursor cursor(document);
3076 cursor.movePosition(op: QTextCursor::NextBlock, QTextCursor::KeepAnchor);
3077 cursor.removeSelectedText();
3078 documentLayout->priv()->blockUpdate = updatesBlocked;
3079 }
3080 document->setMaximumBlockCount(maximumBlockCount);
3081 }
3082
3083 documentLayout->priv()->blockDocumentSizeChanged = documentSizeChangedBlocked;
3084 _q_adjustScrollbars();
3085
3086
3087 if (atBottom) {
3088 const bool needScroll = !centerOnScroll
3089 || control->blockBoundingRect(block: document->lastBlock()).bottom() - verticalOffset()
3090 > viewport->rect().bottom();
3091 if (needScroll)
3092 vbar->setValue(vbar->maximum());
3093 }
3094}
3095
3096
3097/*!
3098 Appends a new paragraph with \a text to the end of the text edit.
3099
3100 \sa appendHtml()
3101*/
3102
3103void QPlainTextEdit::appendPlainText(const QString &text)
3104{
3105 Q_D(QPlainTextEdit);
3106 d->append(text, format: Qt::PlainText);
3107}
3108
3109/*!
3110 Appends a new paragraph with \a html to the end of the text edit.
3111
3112 appendPlainText()
3113*/
3114
3115void QPlainTextEdit::appendHtml(const QString &html)
3116{
3117 Q_D(QPlainTextEdit);
3118 d->append(text: html, format: Qt::RichText);
3119}
3120
3121void QPlainTextEditPrivate::ensureCursorVisible(bool center)
3122{
3123 Q_Q(QPlainTextEdit);
3124 QRect visible = viewport->rect();
3125 QRect cr = q->cursorRect();
3126 if (cr.top() < visible.top() || cr.bottom() > visible.bottom()) {
3127 ensureVisible(position: control->textCursor().position(), center);
3128 }
3129
3130 const bool rtl = q->isRightToLeft();
3131 if (cr.left() < visible.left() || cr.right() > visible.right()) {
3132 int x = cr.center().x() + horizontalOffset() - visible.width()/2;
3133 hbar->setValue(rtl ? hbar->maximum() - x : x);
3134 }
3135}
3136
3137/*!
3138 Ensures that the cursor is visible by scrolling the text edit if
3139 necessary.
3140
3141 \sa centerCursor(), centerOnScroll
3142*/
3143void QPlainTextEdit::ensureCursorVisible()
3144{
3145 Q_D(QPlainTextEdit);
3146 d->ensureCursorVisible(center: d->centerOnScroll);
3147}
3148
3149
3150/*! Scrolls the document in order to center the cursor vertically.
3151
3152\sa ensureCursorVisible(), centerOnScroll
3153 */
3154void QPlainTextEdit::centerCursor()
3155{
3156 Q_D(QPlainTextEdit);
3157 d->ensureVisible(position: textCursor().position(), center: true, forceCenter: true);
3158}
3159
3160/*!
3161 Returns the first visible block.
3162
3163 \sa blockBoundingRect()
3164 */
3165QTextBlock QPlainTextEdit::firstVisibleBlock() const
3166{
3167 Q_D(const QPlainTextEdit);
3168 return d->control->firstVisibleBlock();
3169}
3170
3171/*! Returns the content's origin in viewport coordinates.
3172
3173 The origin of the content of a plain text edit is always the top
3174 left corner of the first visible text block. The content offset
3175 is different from (0,0) when the text has been scrolled
3176 horizontally, or when the first visible block has been scrolled
3177 partially off the screen, i.e. the visible text does not start
3178 with the first line of the first visible block, or when the first
3179 visible block is the very first block and the editor displays a
3180 margin.
3181
3182 \sa firstVisibleBlock(), horizontalScrollBar(), verticalScrollBar()
3183 */
3184QPointF QPlainTextEdit::contentOffset() const
3185{
3186 Q_D(const QPlainTextEdit);
3187 return QPointF(-d->horizontalOffset(), -d->verticalOffset());
3188}
3189
3190
3191/*! Returns the bounding rectangle of the text \a block in content
3192 coordinates. Translate the rectangle with the contentOffset() to get
3193 visual coordinates on the viewport.
3194
3195 \sa firstVisibleBlock(), blockBoundingRect()
3196 */
3197QRectF QPlainTextEdit::blockBoundingGeometry(const QTextBlock &block) const
3198{
3199 Q_D(const QPlainTextEdit);
3200 return d->control->blockBoundingRect(block);
3201}
3202
3203/*!
3204 Returns the bounding rectangle of the text \a block in the block's own coordinates.
3205
3206 \sa blockBoundingGeometry()
3207 */
3208QRectF QPlainTextEdit::blockBoundingRect(const QTextBlock &block) const
3209{
3210 QPlainTextDocumentLayout *documentLayout = qobject_cast<QPlainTextDocumentLayout*>(object: document()->documentLayout());
3211 Q_ASSERT(documentLayout);
3212 return documentLayout->blockBoundingRect(block);
3213}
3214
3215/*!
3216 \property QPlainTextEdit::blockCount
3217 \brief the number of text blocks in the document.
3218
3219 By default, in an empty document, this property contains a value of 1.
3220*/
3221int QPlainTextEdit::blockCount() const
3222{
3223 return document()->blockCount();
3224}
3225
3226/*! Returns the paint context for the viewport(), useful only when
3227 reimplementing paintEvent().
3228 */
3229QAbstractTextDocumentLayout::PaintContext QPlainTextEdit::getPaintContext() const
3230{
3231 Q_D(const QPlainTextEdit);
3232 return d->control->getPaintContext(widget: d->viewport);
3233}
3234
3235/*!
3236 \property QPlainTextEdit::maximumBlockCount
3237 \brief the limit for blocks in the document.
3238
3239 Specifies the maximum number of blocks the document may have. If there are
3240 more blocks in the document that specified with this property blocks are removed
3241 from the beginning of the document.
3242
3243 A negative or zero value specifies that the document may contain an unlimited
3244 amount of blocks.
3245
3246 The default value is 0.
3247
3248 Note that setting this property will apply the limit immediately to the document
3249 contents. Setting this property also disables the undo redo history.
3250
3251*/
3252
3253
3254/*!
3255 \fn void QPlainTextEdit::textChanged()
3256
3257 This signal is emitted whenever the document's content changes; for
3258 example, when text is inserted or deleted, or when formatting is applied.
3259*/
3260
3261/*!
3262 \fn void QPlainTextEdit::undoAvailable(bool available)
3263
3264 This signal is emitted whenever undo operations become available
3265 (\a available is true) or unavailable (\a available is false).
3266*/
3267
3268/*!
3269 \fn void QPlainTextEdit::redoAvailable(bool available)
3270
3271 This signal is emitted whenever redo operations become available
3272 (\a available is true) or unavailable (\a available is false).
3273*/
3274
3275QT_END_NAMESPACE
3276
3277#include "moc_qplaintextedit.cpp"
3278#include "moc_qplaintextedit_p.cpp"
3279

source code of qtbase/src/widgets/widgets/qplaintextedit.cpp