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 <KColorScheme>
18#include <KCursor>
19
20// Qt includes
21#include <QRegularExpression>
22
23void KRichTextEditPrivate::activateRichText()
24{
25 Q_Q(KRichTextEdit);
26
27 if (mMode == KRichTextEdit::Plain) {
28 q->setAcceptRichText(true);
29 mMode = KRichTextEdit::Rich;
30 Q_EMIT q->textModeChanged(mMode);
31 }
32}
33
34void KRichTextEditPrivate::setTextCursor(QTextCursor &cursor)
35{
36 Q_Q(KRichTextEdit);
37
38 q->setTextCursor(cursor);
39}
40
41void KRichTextEditPrivate::mergeFormatOnWordOrSelection(const QTextCharFormat &format)
42{
43 Q_Q(KRichTextEdit);
44
45 QTextCursor cursor = q->textCursor();
46 QTextCursor wordStart(cursor);
47 QTextCursor wordEnd(cursor);
48
49 wordStart.movePosition(QTextCursor::StartOfWord);
50 wordEnd.movePosition(QTextCursor::EndOfWord);
51
52 cursor.beginEditBlock();
53 if (!cursor.hasSelection() && cursor.position() != wordStart.position() && cursor.position() != wordEnd.position()) {
55 }
56 cursor.mergeCharFormat(format);
57 q->mergeCurrentCharFormat(format);
58 cursor.endEditBlock();
59}
60
62 : KRichTextEdit(*new KRichTextEditPrivate(this), text, parent)
63{
64}
65
66KRichTextEdit::KRichTextEdit(KRichTextEditPrivate &dd, const QString &text, QWidget *parent)
67 : KTextEdit(dd, text, parent)
68{
70
71 d->init();
72}
73
75 : KRichTextEdit(*new KRichTextEditPrivate(this), parent)
76{
77}
78
79KRichTextEdit::KRichTextEdit(KRichTextEditPrivate &dd, QWidget *parent)
80 : KTextEdit(dd, parent)
81{
83
84 d->init();
85}
86
88
89//@cond PRIVATE
90void KRichTextEditPrivate::init()
91{
92 Q_Q(KRichTextEdit);
93
94 q->setAcceptRichText(false);
95 KCursor::setAutoHideCursor(q, true, true);
96}
97//@endcond
98
99void KRichTextEdit::setListStyle(int _styleIndex)
100{
102
103 d->nestedListHelper->handleOnBulletType(-_styleIndex);
104 setFocus();
105 d->activateRichText();
106}
107
109{
111
112 d->nestedListHelper->changeIndent(+1);
113 d->activateRichText();
114}
115
117{
119
120 d->nestedListHelper->changeIndent(-1);
121}
122
124{
126
128 QTextBlockFormat bf = cursor.blockFormat();
129 QTextCharFormat cf = cursor.charFormat();
130
131 cursor.beginEditBlock();
132 cursor.insertHtml(QStringLiteral("<hr>"));
133 cursor.insertBlock(bf, cf);
134 cursor.endEditBlock();
136 d->activateRichText();
137}
138
140{
142
144 setFocus();
145 d->activateRichText();
146}
147
149{
151
153 setFocus();
154 d->activateRichText();
155}
156
158{
160
162 setFocus();
163 d->activateRichText();
164}
165
167{
169
171 setFocus();
172 d->activateRichText();
173}
174
176{
178
179 QTextBlockFormat format;
182 cursor.mergeBlockFormat(format);
184 setFocus();
185 d->activateRichText();
186}
187
189{
191
192 QTextBlockFormat format;
195 cursor.mergeBlockFormat(format);
197 setFocus();
198 d->activateRichText();
199}
200
202{
204
205 QTextCharFormat fmt;
207 d->mergeFormatOnWordOrSelection(fmt);
208 setFocus();
209 d->activateRichText();
210}
211
213{
215
216 QTextCharFormat fmt;
217 fmt.setFontItalic(italic);
218 d->mergeFormatOnWordOrSelection(fmt);
219 setFocus();
220 d->activateRichText();
221}
222
224{
226
227 QTextCharFormat fmt;
228 fmt.setFontUnderline(underline);
229 d->mergeFormatOnWordOrSelection(fmt);
230 setFocus();
231 d->activateRichText();
232}
233
235{
237
238 QTextCharFormat fmt;
239 fmt.setFontStrikeOut(strikeOut);
240 d->mergeFormatOnWordOrSelection(fmt);
241 setFocus();
242 d->activateRichText();
243}
244
246{
248
249 QTextCharFormat fmt;
250 fmt.setForeground(color);
251 d->mergeFormatOnWordOrSelection(fmt);
252 setFocus();
253 d->activateRichText();
254}
255
257{
259
260 QTextCharFormat fmt;
261 fmt.setBackground(color);
262 d->mergeFormatOnWordOrSelection(fmt);
263 setFocus();
264 d->activateRichText();
265}
266
268{
270
271 QTextCharFormat fmt;
273 d->mergeFormatOnWordOrSelection(fmt);
274 setFocus();
275 d->activateRichText();
276}
277
279{
281
282 QTextCharFormat fmt;
284 d->mergeFormatOnWordOrSelection(fmt);
285 setFocus();
286 d->activateRichText();
287}
288
290{
292
293 QTextCharFormat fmt;
294 fmt.setFont(font);
295 d->mergeFormatOnWordOrSelection(fmt);
296 setFocus();
297 d->activateRichText();
298}
299
301{
303
304 if (d->mMode == Rich) {
305 d->mMode = Plain;
306 // TODO: Warn the user about this?
307 auto insertPlainFunc = [this]() {
309 };
310 QMetaObject::invokeMethod(this, insertPlainFunc);
311 setAcceptRichText(false);
312 Q_EMIT textModeChanged(d->mMode);
313 }
314}
315
320
322{
324
325 QTextCharFormat fmt;
327 d->mergeFormatOnWordOrSelection(fmt);
328 setFocus();
329 d->activateRichText();
330}
331
333{
335
336 QTextCharFormat fmt;
338 d->mergeFormatOnWordOrSelection(fmt);
339 setFocus();
340 d->activateRichText();
341}
342
344{
346
347 const int boundedLevel = qBound(0, 6, level);
348 // Apparently, 5 is maximum for FontSizeAdjustment; otherwise level=1 and
349 // level=2 look the same
350 const int sizeAdjustment = boundedLevel > 0 ? 5 - boundedLevel : 0;
351
353 cursor.beginEditBlock();
354
355 QTextBlockFormat blkfmt;
356 blkfmt.setHeadingLevel(boundedLevel);
357 cursor.mergeBlockFormat(blkfmt);
358
359 QTextCharFormat chrfmt;
360 chrfmt.setFontWeight(boundedLevel > 0 ? QFont::Bold : QFont::Normal);
361 chrfmt.setProperty(QTextFormat::FontSizeAdjustment, sizeAdjustment);
362 // Applying style to the current line or selection
363 QTextCursor selectCursor = cursor;
364 if (selectCursor.hasSelection()) {
365 QTextCursor top = selectCursor;
366 top.setPosition(qMin(top.anchor(), top.position()));
368
369 QTextCursor bottom = selectCursor;
370 bottom.setPosition(qMax(bottom.anchor(), bottom.position()));
372
373 selectCursor.setPosition(top.position(), QTextCursor::MoveAnchor);
374 selectCursor.setPosition(bottom.position(), QTextCursor::KeepAnchor);
375 } else {
377 }
378 selectCursor.mergeCharFormat(chrfmt);
379
380 cursor.mergeBlockCharFormat(chrfmt);
381 cursor.endEditBlock();
383 setFocus();
384 d->activateRichText();
385}
386
388{
390
391 d->activateRichText();
392}
393
395{
396 Q_D(const KRichTextEdit);
397
398 return d->mMode;
399}
400
402{
403 if (textMode() == Rich) {
404 return toCleanHtml();
405 } else {
406 return toPlainText();
407 }
408}
409
411{
413
414 // might be rich text
415 if (Qt::mightBeRichText(text)) {
416 if (d->mMode == KRichTextEdit::Plain) {
417 d->activateRichText();
418 }
419 setHtml(text);
420 } else {
421 setPlainText(text);
422 }
423}
424
425// KF6 TODO: remove constness
427{
430 return cursor.selectedText();
431}
432
433// KF6 TODO: remove constness
435{
436 Q_D(const KRichTextEdit);
437
440 // KF6 TODO: remove const_cast
441 const_cast<KRichTextEditPrivate *>(d)->setTextCursor(cursor);
442}
443
445{
446 // If the cursor is on a link, select the text of the link.
447 if (cursor->charFormat().isAnchor()) {
448 QString aHref = cursor->charFormat().anchorHref();
449
450 // Move cursor to start of link
451 while (cursor->charFormat().anchorHref() == aHref) {
452 if (cursor->atStart()) {
453 break;
454 }
455 cursor->setPosition(cursor->position() - 1);
456 }
457 if (cursor->charFormat().anchorHref() != aHref) {
458 cursor->setPosition(cursor->position() + 1, QTextCursor::KeepAnchor);
459 }
460
461 // Move selection to the end of the link
462 while (cursor->charFormat().anchorHref() == aHref) {
463 if (cursor->atEnd()) {
464 break;
465 }
466 cursor->setPosition(cursor->position() + 1, QTextCursor::KeepAnchor);
467 }
468 if (cursor->charFormat().anchorHref() != aHref) {
469 cursor->setPosition(cursor->position() - 1, QTextCursor::KeepAnchor);
470 }
471 } else if (cursor->hasSelection()) {
472 // Nothing to to. Using the currently selected text as the link text.
473 } else {
474 // Select current word
475 cursor->movePosition(QTextCursor::StartOfWord);
477 }
478}
479
484
485void KRichTextEdit::updateLink(const QString &linkUrl, const QString &linkText)
486{
488
490
492 cursor.beginEditBlock();
493
494 if (!cursor.hasSelection()) {
496 }
497
498 QTextCharFormat format = cursor.charFormat();
499 // Save original format to create an extra space with the existing char
500 // format for the block
501 const QTextCharFormat originalFormat = format;
502 if (!linkUrl.isEmpty()) {
503 // Add link details
504 format.setAnchor(true);
505 format.setAnchorHref(linkUrl);
506 // Workaround for QTBUG-1814:
507 // Link formatting does not get applied immediately when setAnchor(true)
508 // is called. So the formatting needs to be applied manually.
512 d->activateRichText();
513 } else {
514 // Remove link details
515 format.setAnchor(false);
516 format.setAnchorHref(QString());
517 // Workaround for QTBUG-1814:
518 // Link formatting does not get removed immediately when setAnchor(false)
519 // is called. So the formatting needs to be applied manually.
520 QTextDocument defaultTextDocument;
521 QTextCharFormat defaultCharFormat = defaultTextDocument.begin().charFormat();
522
523 format.setUnderlineStyle(defaultCharFormat.underlineStyle());
524 format.setUnderlineColor(defaultCharFormat.underlineColor());
525 format.setForeground(defaultCharFormat.foreground());
526 }
527
528 // Insert link text specified in dialog, otherwise write out url.
529 QString _linkText;
530 if (!linkText.isEmpty()) {
531 _linkText = linkText;
532 } else {
533 _linkText = linkUrl;
534 }
535 cursor.insertText(_linkText, format);
536
537 // Insert a space after the link if at the end of the block so that
538 // typing some text after the link does not carry link formatting
539 if (!linkUrl.isEmpty() && cursor.atBlockEnd()) {
540 cursor.setPosition(cursor.selectionEnd());
541 cursor.setCharFormat(originalFormat);
542 cursor.insertText(QStringLiteral(" "));
543 }
544
545 cursor.endEditBlock();
546}
547
549{
551
552 bool handled = false;
553 if (textCursor().currentList()) {
554 handled = d->nestedListHelper->handleKeyPressEvent(event);
555 }
556
557 // If a line was merged with previous (next) one, with different heading level,
558 // the style should also be adjusted accordingly (i.e. merged)
559 if ((event->key() == Qt::Key_Backspace && textCursor().atBlockStart()
560 && (textCursor().blockFormat().headingLevel() != textCursor().block().previous().blockFormat().headingLevel()))
561 || (event->key() == Qt::Key_Delete && textCursor().atBlockEnd()
562 && (textCursor().blockFormat().headingLevel() != textCursor().block().next().blockFormat().headingLevel()))) {
564 cursor.beginEditBlock();
565 if (event->key() == Qt::Key_Delete) {
566 cursor.deleteChar();
567 } else {
568 cursor.deletePreviousChar();
569 }
570 setHeadingLevel(cursor.blockFormat().headingLevel());
571 cursor.endEditBlock();
572 handled = true;
573 }
574
575 const auto prevHeadingLevel = textCursor().blockFormat().headingLevel();
576 if (!handled) {
578 }
579
580 // Match the behavior of office suites: newline after header switches to normal text
581 if (event->key() == Qt::Key_Return //
582 && prevHeadingLevel > 0) {
583 // it should be undoable together with actual "return" keypress
585 if (textCursor().atBlockEnd()) {
587 } else {
588 setHeadingLevel(prevHeadingLevel);
589 }
591 }
592
594}
595
596// void KRichTextEdit::dropEvent(QDropEvent *event)
597// {
598// int dropSize = event->mimeData()->text().size();
599//
600// dropEvent( event );
601// QTextCursor cursor = textCursor();
602// int cursorPosition = cursor.position();
603// cursor.setPosition( cursorPosition - dropSize );
604// cursor.setPosition( cursorPosition, QTextCursor::KeepAnchor );
605// setTextCursor( cursor );
606// d->nestedListHelper->handleAfterDropEvent( event );
607// }
608
610{
611 Q_D(const KRichTextEdit);
612
613 return d->nestedListHelper->canIndent();
614}
615
617{
618 Q_D(const KRichTextEdit);
619
620 return d->nestedListHelper->canDedent();
621}
622
624{
625 QString result = toHtml();
626
627 static const QString EMPTYLINEHTML = QLatin1String(
628 "<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; "
629 "margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; \">&nbsp;</p>");
630
631 // Qt inserts various style properties based on the current mode of the editor (underline,
632 // bold, etc), but only empty paragraphs *also* have qt-paragraph-type set to 'empty'.
633 static const QRegularExpression EMPTYLINEREGEX(QStringLiteral("<p style=\"-qt-paragraph-type:empty;(.*?)</p>"));
634
635 static const QString OLLISTPATTERNQT = QStringLiteral("<ol style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px;");
636
637 static const QString ULLISTPATTERNQT = QStringLiteral("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px;");
638
639 static const QString ORDEREDLISTHTML = QStringLiteral("<ol style=\"margin-top: 0px; margin-bottom: 0px;");
640
641 static const QString UNORDEREDLISTHTML = QStringLiteral("<ul style=\"margin-top: 0px; margin-bottom: 0px;");
642
643 // fix 1 - empty lines should show as empty lines - MS Outlook treats margin-top:0px; as
644 // a non-existing line.
645 // Although we can simply remove the margin-top style property, we still get unwanted results
646 // if you have three or more empty lines. It's best to replace empty <p> elements with <p>&nbsp;</p>.
647 // replace all occurrences with the new line text
648 result.replace(EMPTYLINEREGEX, EMPTYLINEHTML);
649
650 // fix 2a - ordered lists - MS Outlook treats margin-left:0px; as
651 // a non-existing number; e.g: "1. First item" turns into "First Item"
652 result.replace(OLLISTPATTERNQT, ORDEREDLISTHTML);
653
654 // fix 2b - unordered lists - MS Outlook treats margin-left:0px; as
655 // a non-existing bullet; e.g: "* First bullet" turns into "First Bullet"
656 result.replace(ULLISTPATTERNQT, UNORDEREDLISTHTML);
657
658 return result;
659}
660
661#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
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-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:14:15 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.