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

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