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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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