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

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