KTextWidgets

krichtextedit.cpp
1/*
2 krichtextedit
3 SPDX-FileCopyrightText: 2007 Laurent Montel <montel@kde.org>
4 SPDX-FileCopyrightText: 2008 Thomas McGuire <thomas.mcguire@gmx.net>
5 SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmail.com>
6
7 SPDX-License-Identifier: LGPL-2.1-or-later
8*/
9
10#include "krichtextedit.h"
11#include "krichtextedit_p.h"
12
13// Own includes
14#include "klinkdialog_p.h"
15
16// kdelibs includes
17#include <KCursor>
18
19// Qt includes
20#include <QRegularExpression>
21
22void KRichTextEditPrivate::activateRichText()
23{
24 Q_Q(KRichTextEdit);
25
26 if (mMode == KRichTextEdit::Plain) {
27 q->setAcceptRichText(true);
28 mMode = KRichTextEdit::Rich;
29 Q_EMIT q->textModeChanged(mMode);
30 }
31}
32
33void KRichTextEditPrivate::setTextCursor(QTextCursor &cursor)
34{
35 Q_Q(KRichTextEdit);
36
37 q->setTextCursor(cursor);
38}
39
40void KRichTextEditPrivate::mergeFormatOnWordOrSelection(const QTextCharFormat &format)
41{
42 Q_Q(KRichTextEdit);
43
44 QTextCursor cursor = q->textCursor();
45 QTextCursor wordStart(cursor);
46 QTextCursor wordEnd(cursor);
47
48 wordStart.movePosition(QTextCursor::StartOfWord);
49 wordEnd.movePosition(QTextCursor::EndOfWord);
50
51 cursor.beginEditBlock();
52 if (!cursor.hasSelection() && cursor.position() != wordStart.position() && cursor.position() != wordEnd.position()) {
54 }
55 cursor.mergeCharFormat(format);
56 q->mergeCurrentCharFormat(format);
57 cursor.endEditBlock();
58}
59
61 : KRichTextEdit(*new KRichTextEditPrivate(this), text, parent)
62{
63}
64
65KRichTextEdit::KRichTextEdit(KRichTextEditPrivate &dd, const QString &text, QWidget *parent)
66 : KTextEdit(dd, text, parent)
67{
69
70 d->init();
71}
72
74 : KRichTextEdit(*new KRichTextEditPrivate(this), parent)
75{
76}
77
78KRichTextEdit::KRichTextEdit(KRichTextEditPrivate &dd, QWidget *parent)
79 : KTextEdit(dd, parent)
80{
82
83 d->init();
84}
85
87
88//@cond PRIVATE
89void KRichTextEditPrivate::init()
90{
91 Q_Q(KRichTextEdit);
92
93 q->setAcceptRichText(false);
94 KCursor::setAutoHideCursor(q, true, true);
95}
96//@endcond
97
98void KRichTextEdit::setListStyle(int _styleIndex)
99{
101
102 d->nestedListHelper->handleOnBulletType(-_styleIndex);
103 setFocus();
104 d->activateRichText();
105}
106
108{
110
111 d->nestedListHelper->changeIndent(+1);
112 d->activateRichText();
113}
114
116{
118
119 d->nestedListHelper->changeIndent(-1);
120}
121
123{
125
127 QTextBlockFormat bf = cursor.blockFormat();
128 QTextCharFormat cf = cursor.charFormat();
129
130 cursor.beginEditBlock();
131 cursor.insertHtml(QStringLiteral("<hr>"));
132 cursor.insertBlock(bf, cf);
133 cursor.endEditBlock();
135 d->activateRichText();
136}
137
139{
141
143 setFocus();
144 d->activateRichText();
145}
146
148{
150
152 setFocus();
153 d->activateRichText();
154}
155
157{
159
161 setFocus();
162 d->activateRichText();
163}
164
166{
168
170 setFocus();
171 d->activateRichText();
172}
173
175{
177
178 QTextBlockFormat format;
181 cursor.mergeBlockFormat(format);
183 setFocus();
184 d->activateRichText();
185}
186
188{
190
191 QTextBlockFormat format;
194 cursor.mergeBlockFormat(format);
196 setFocus();
197 d->activateRichText();
198}
199
201{
203
204 QTextCharFormat fmt;
206 d->mergeFormatOnWordOrSelection(fmt);
207 setFocus();
208 d->activateRichText();
209}
210
212{
214
215 QTextCharFormat fmt;
216 fmt.setFontItalic(italic);
217 d->mergeFormatOnWordOrSelection(fmt);
218 setFocus();
219 d->activateRichText();
220}
221
223{
225
226 QTextCharFormat fmt;
227 fmt.setFontUnderline(underline);
228 d->mergeFormatOnWordOrSelection(fmt);
229 setFocus();
230 d->activateRichText();
231}
232
234{
236
237 QTextCharFormat fmt;
238 fmt.setFontStrikeOut(strikeOut);
239 d->mergeFormatOnWordOrSelection(fmt);
240 setFocus();
241 d->activateRichText();
242}
243
245{
247
248 QTextCharFormat fmt;
249 fmt.setForeground(color);
250 d->mergeFormatOnWordOrSelection(fmt);
251 setFocus();
252 d->activateRichText();
253}
254
256{
258
259 QTextCharFormat fmt;
260 fmt.setBackground(color);
261 d->mergeFormatOnWordOrSelection(fmt);
262 setFocus();
263 d->activateRichText();
264}
265
267{
269
270 QTextCharFormat fmt;
272 d->mergeFormatOnWordOrSelection(fmt);
273 setFocus();
274 d->activateRichText();
275}
276
278{
280
281 QTextCharFormat fmt;
283 d->mergeFormatOnWordOrSelection(fmt);
284 setFocus();
285 d->activateRichText();
286}
287
289{
291
292 QTextCharFormat fmt;
293 fmt.setFont(font);
294 d->mergeFormatOnWordOrSelection(fmt);
295 setFocus();
296 d->activateRichText();
297}
298
300{
302
303 if (d->mMode == Rich) {
304 d->mMode = Plain;
305 // TODO: Warn the user about this?
306 auto insertPlainFunc = [this]() {
308 };
309 QMetaObject::invokeMethod(this, insertPlainFunc);
310 setAcceptRichText(false);
311 Q_EMIT textModeChanged(d->mMode);
312 }
313}
314
319
321{
323
324 QTextCharFormat fmt;
326 d->mergeFormatOnWordOrSelection(fmt);
327 setFocus();
328 d->activateRichText();
329}
330
332{
334
335 QTextCharFormat fmt;
337 d->mergeFormatOnWordOrSelection(fmt);
338 setFocus();
339 d->activateRichText();
340}
341
343{
345
346 const int boundedLevel = qBound(0, 6, level);
347 // Apparently, 5 is maximum for FontSizeAdjustment; otherwise level=1 and
348 // level=2 look the same
349 const int sizeAdjustment = boundedLevel > 0 ? 5 - boundedLevel : 0;
350
352 cursor.beginEditBlock();
353
354 QTextBlockFormat blkfmt;
355 blkfmt.setHeadingLevel(boundedLevel);
356 cursor.mergeBlockFormat(blkfmt);
357
358 QTextCharFormat chrfmt;
359 chrfmt.setFontWeight(boundedLevel > 0 ? QFont::Bold : QFont::Normal);
360 chrfmt.setProperty(QTextFormat::FontSizeAdjustment, sizeAdjustment);
361 // Applying style to the current line or selection
362 QTextCursor selectCursor = cursor;
363 if (selectCursor.hasSelection()) {
364 QTextCursor top = selectCursor;
365 top.setPosition(qMin(top.anchor(), top.position()));
367
368 QTextCursor bottom = selectCursor;
369 bottom.setPosition(qMax(bottom.anchor(), bottom.position()));
371
372 selectCursor.setPosition(top.position(), QTextCursor::MoveAnchor);
373 selectCursor.setPosition(bottom.position(), QTextCursor::KeepAnchor);
374 } else {
376 }
377 selectCursor.mergeCharFormat(chrfmt);
378
379 cursor.mergeBlockCharFormat(chrfmt);
380 cursor.endEditBlock();
382 setFocus();
383 d->activateRichText();
384}
385
387{
389
390 d->activateRichText();
391}
392
394{
395 Q_D(const KRichTextEdit);
396
397 return d->mMode;
398}
399
401{
402 if (textMode() == Rich) {
403 return toCleanHtml();
404 } else {
405 return toPlainText();
406 }
407}
408
410{
412
413 // might be rich text
414 if (Qt::mightBeRichText(text)) {
415 if (d->mMode == KRichTextEdit::Plain) {
416 d->activateRichText();
417 }
418 setHtml(text);
419 } else {
420 setPlainText(text);
421 }
422}
423
424// KF6 TODO: remove constness
426{
429 return cursor.selectedText();
430}
431
432// KF6 TODO: remove constness
434{
435 Q_D(const KRichTextEdit);
436
439 // KF6 TODO: remove const_cast
440 const_cast<KRichTextEditPrivate *>(d)->setTextCursor(cursor);
441}
442
444{
445 // If the cursor is on a link, select the text of the link.
446 if (cursor->charFormat().isAnchor()) {
447 QString aHref = cursor->charFormat().anchorHref();
448
449 // Move cursor to start of link
450 while (cursor->charFormat().anchorHref() == aHref) {
451 if (cursor->atStart()) {
452 break;
453 }
454 cursor->setPosition(cursor->position() - 1);
455 }
456 if (cursor->charFormat().anchorHref() != aHref) {
457 cursor->setPosition(cursor->position() + 1, QTextCursor::KeepAnchor);
458 }
459
460 // Move selection to the end of the link
461 while (cursor->charFormat().anchorHref() == aHref) {
462 if (cursor->atEnd()) {
463 break;
464 }
465 cursor->setPosition(cursor->position() + 1, QTextCursor::KeepAnchor);
466 }
467 if (cursor->charFormat().anchorHref() != aHref) {
468 cursor->setPosition(cursor->position() - 1, QTextCursor::KeepAnchor);
469 }
470 } else if (cursor->hasSelection()) {
471 // Nothing to to. Using the currently selected text as the link text.
472 } else {
473 // Select current word
474 cursor->movePosition(QTextCursor::StartOfWord);
476 }
477}
478
483
484void KRichTextEdit::updateLink(const QString &linkUrl, const QString &linkText)
485{
487
489
491 cursor.beginEditBlock();
492
493 if (!cursor.hasSelection()) {
495 }
496
497 QTextCharFormat format = cursor.charFormat();
498 // Save original format to create an extra space with the existing char
499 // format for the block
500 const QTextCharFormat originalFormat = format;
501 if (!linkUrl.isEmpty()) {
502 // Add link details
503 format.setAnchor(true);
504 format.setAnchorHref(linkUrl);
505 // Workaround for QTBUG-1814:
506 // Link formatting does not get applied immediately when setAnchor(true)
507 // is called. So the formatting needs to be applied manually.
509 format.setUnderlineColor(palette().link().color());
510 format.setForeground(palette().link());
511 d->activateRichText();
512 } else {
513 // Remove link details
514 format.setAnchor(false);
515 format.setAnchorHref(QString());
516 // Workaround for QTBUG-1814:
517 // Link formatting does not get removed immediately when setAnchor(false)
518 // is called. So the formatting needs to be applied manually.
519 QTextDocument defaultTextDocument;
520 QTextCharFormat defaultCharFormat = defaultTextDocument.begin().charFormat();
521
522 format.setUnderlineStyle(defaultCharFormat.underlineStyle());
523 format.setUnderlineColor(defaultCharFormat.underlineColor());
524 format.setForeground(defaultCharFormat.foreground());
525 }
526
527 // Insert link text specified in dialog, otherwise write out url.
528 QString _linkText;
529 if (!linkText.isEmpty()) {
530 _linkText = linkText;
531 } else {
532 _linkText = linkUrl;
533 }
534 cursor.insertText(_linkText, format);
535
536 // Insert a space after the link if at the end of the block so that
537 // typing some text after the link does not carry link formatting
538 if (!linkUrl.isEmpty() && cursor.atBlockEnd()) {
539 cursor.setPosition(cursor.selectionEnd());
540 cursor.setCharFormat(originalFormat);
541 cursor.insertText(QStringLiteral(" "));
542 }
543
544 cursor.endEditBlock();
545}
546
548{
550
551 bool handled = false;
552 if (textCursor().currentList()) {
553 handled = d->nestedListHelper->handleKeyPressEvent(event);
554 }
555
556 // If a line was merged with previous (next) one, with different heading level,
557 // the style should also be adjusted accordingly (i.e. merged)
558 if ((event->key() == Qt::Key_Backspace && textCursor().atBlockStart()
559 && (textCursor().blockFormat().headingLevel() != textCursor().block().previous().blockFormat().headingLevel()))
560 || (event->key() == Qt::Key_Delete && textCursor().atBlockEnd()
561 && (textCursor().blockFormat().headingLevel() != textCursor().block().next().blockFormat().headingLevel()))) {
563 cursor.beginEditBlock();
564 if (event->key() == Qt::Key_Delete) {
565 cursor.deleteChar();
566 } else {
567 cursor.deletePreviousChar();
568 }
569 setHeadingLevel(cursor.blockFormat().headingLevel());
570 cursor.endEditBlock();
571 handled = true;
572 }
573
574 const auto prevHeadingLevel = textCursor().blockFormat().headingLevel();
575 if (!handled) {
577 }
578
579 // Match the behavior of office suites: newline after header switches to normal text
580 if (event->key() == Qt::Key_Return //
581 && prevHeadingLevel > 0) {
582 // it should be undoable together with actual "return" keypress
584 if (textCursor().atBlockEnd()) {
586 } else {
587 setHeadingLevel(prevHeadingLevel);
588 }
590 }
591
593}
594
595// void KRichTextEdit::dropEvent(QDropEvent *event)
596// {
597// int dropSize = event->mimeData()->text().size();
598//
599// dropEvent( event );
600// QTextCursor cursor = textCursor();
601// int cursorPosition = cursor.position();
602// cursor.setPosition( cursorPosition - dropSize );
603// cursor.setPosition( cursorPosition, QTextCursor::KeepAnchor );
604// setTextCursor( cursor );
605// d->nestedListHelper->handleAfterDropEvent( event );
606// }
607
609{
610 Q_D(const KRichTextEdit);
611
612 return d->nestedListHelper->canIndent();
613}
614
616{
617 Q_D(const KRichTextEdit);
618
619 return d->nestedListHelper->canDedent();
620}
621
623{
624 QString result = toHtml();
625
626 static const QString EMPTYLINEHTML = QLatin1String(
627 "<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; "
628 "margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; \">&nbsp;</p>");
629
630 // Qt inserts various style properties based on the current mode of the editor (underline,
631 // bold, etc), but only empty paragraphs *also* have qt-paragraph-type set to 'empty'.
632 static const QRegularExpression EMPTYLINEREGEX(QStringLiteral("<p style=\"-qt-paragraph-type:empty;(.*?)</p>"));
633
634 static const QString OLLISTPATTERNQT = QStringLiteral("<ol style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px;");
635
636 static const QString ULLISTPATTERNQT = QStringLiteral("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px;");
637
638 static const QString ORDEREDLISTHTML = QStringLiteral("<ol style=\"margin-top: 0px; margin-bottom: 0px;");
639
640 static const QString UNORDEREDLISTHTML = QStringLiteral("<ul style=\"margin-top: 0px; margin-bottom: 0px;");
641
642 // fix 1 - empty lines should show as empty lines - MS Outlook treats margin-top:0px; as
643 // a non-existing line.
644 // Although we can simply remove the margin-top style property, we still get unwanted results
645 // if you have three or more empty lines. It's best to replace empty <p> elements with <p>&nbsp;</p>.
646 // replace all occurrences with the new line text
647 result.replace(EMPTYLINEREGEX, EMPTYLINEHTML);
648
649 // fix 2a - ordered lists - MS Outlook treats margin-left:0px; as
650 // a non-existing number; e.g: "1. First item" turns into "First Item"
651 result.replace(OLLISTPATTERNQT, ORDEREDLISTHTML);
652
653 // fix 2b - unordered lists - MS Outlook treats margin-left:0px; as
654 // a non-existing bullet; e.g: "* First bullet" turns into "First Bullet"
655 result.replace(ULLISTPATTERNQT, UNORDEREDLISTHTML);
656
657 return result;
658}
659
660#include "moc_krichtextedit.cpp"
static void setAutoHideCursor(QWidget *w, bool enable, bool customEventFilter=false)
The KRichTextEdit class provides a widget to edit and display rich text.
void insertPlainTextImplementation()
void setTextStrikeOut(bool strikeOut)
Toggles the strikeout formatting of the current word or selection at the current cursor position.
void setTextSubScript(bool subscript)
Toggles the subscript formatting of the current word or selection at the current cursor position.
void alignRight()
Sets the alignment of the current block to Right Aligned.
void setTextForegroundColor(const QColor &color)
Sets the foreground color of the current word or selection to color.
void setTextBackgroundColor(const QColor &color)
Sets the background color of the current word or selection to color.
void insertHorizontalRule()
Inserts a horizontal rule below the current block.
void enableRichTextMode()
This enables rich text mode.
bool canDedentList() const
Returns true if the list item at the current position can be dedented.
QString currentLinkText() const
Returns the text of the link at the current position or an empty string if the cursor is not on a lin...
void setFont(const QFont &font)
Sets the current word or selection to the font font.
void alignJustify()
Sets the alignment of the current block to Justified.
void switchToPlainText()
This will switch the editor to plain text mode.
void setTextOrHtml(const QString &text)
Replaces all the content of the text edit with the given string.
void setTextUnderline(bool underline)
Toggles the underline formatting of the current word or selection at the current cursor position.
void textModeChanged(KRichTextEdit::Mode mode)
Emitted whenever the text mode is changed.
void setHeadingLevel(int level)
Sets the heading level of a current block or selection.
QString toCleanHtml() const
This will clean some of the bad html produced by the underlying QTextEdit It walks over all lines and...
QString textOrHtml() const
void selectLinkText() const
Convenience function to select the link text using the active cursor.
Mode
The mode the edit widget is in.
@ Plain
Plain text mode.
@ Rich
Rich text mode.
void setFontSize(int size)
Sets the current word or selection to the font size size.
void indentListLess()
Decreases the nesting level of the current block or selected blocks.
void setFontFamily(const QString &fontFamily)
Sets the current word or selection to the font family fontFamily.
void makeLeftToRight()
Sets the direction of the current block to Left-To-Right.
KRichTextEdit(const QString &text, QWidget *parent=nullptr)
Constructs a KRichTextEdit object.
~KRichTextEdit() override
Destructor.
void keyPressEvent(QKeyEvent *event) override
Reimplemented.
QString currentLinkUrl() const
Returns the URL target (href) of the link at the current position or an empty string if the cursor is...
void setListStyle(int _styleIndex)
Sets the list style of the current list, or creates a new list using the current block.
void indentListMore()
Increases the nesting level of the current block or selected blocks.
void alignLeft()
Sets the alignment of the current block to Left Aligned.
void setTextSuperScript(bool superscript)
Toggles the superscript formatting of the current word or selection at the current cursor position.
void setTextBold(bool bold)
Toggles the bold formatting of the current word or selection at the current cursor position.
void setTextItalic(bool italic)
Toggles the italic formatting of the current word or selection at the current cursor position.
void makeRightToLeft()
Sets the direction of the current block to Right-To-Left.
Mode textMode() const
bool canIndentList() const
Returns true if the list item at the current position can be indented.
void alignCenter()
Sets the alignment of the current block to Centered.
void updateLink(const QString &linkUrl, const QString &linkText)
Replaces the current selection with a hyperlink with the link URL linkUrl and the link text linkText.
A KDE'ified QTextEdit.
Definition ktextedit.h:46
bool event(QEvent *) override
Reimplemented to catch "delete word" shortcut events.
void keyPressEvent(QKeyEvent *) override
Reimplemented for internal reasons.
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
Q_EMITQ_EMIT
QObject * parent() const const
bool isEmpty() const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
bool mightBeRichText(const QString &text)
AlignLeft
Key_Backspace
RightToLeft
QTextCharFormat charFormat() const const
int headingLevel() const const
void setHeadingLevel(int level)
QString anchorHref() const const
void setAnchor(bool anchor)
void setAnchorHref(const QString &value)
void setFont(const QFont &font, FontPropertiesInheritanceBehavior behavior)
void setFontFamilies(const QStringList &families)
void setFontItalic(bool italic)
void setFontPointSize(qreal size)
void setFontStrikeOut(bool strikeOut)
void setFontUnderline(bool underline)
void setFontWeight(int weight)
void setUnderlineColor(const QColor &color)
void setUnderlineStyle(UnderlineStyle style)
void setVerticalAlignment(VerticalAlignment alignment)
QColor underlineColor() const const
UnderlineStyle underlineStyle() const const
int anchor() const const
void beginEditBlock()
QTextBlockFormat blockFormat() const const
QTextCharFormat charFormat() const const
void endEditBlock()
bool hasSelection() const const
void joinPreviousEditBlock()
void mergeCharFormat(const QTextCharFormat &modifier)
bool movePosition(MoveOperation operation, MoveMode mode, int n)
int position() const const
void select(SelectionType selection)
void setPosition(int pos, MoveMode m)
QTextBlock begin() const const
void setAcceptRichText(bool accept)
void cursorPositionChanged()
QString fontFamily() const const
void setHtml(const QString &text)
void setAlignment(Qt::Alignment a)
void setPlainText(const QString &text)
void setTextCursor(const QTextCursor &cursor)
QTextCursor textCursor() const const
QString toPlainText() const const
QBrush foreground() const const
void setBackground(const QBrush &brush)
void setForeground(const QBrush &brush)
void setLayoutDirection(Qt::LayoutDirection direction)
void setProperty(int propertyId, const QList< QTextLength > &value)
void setFocus()
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri May 2 2025 11:58:19 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.