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 | |
22 | void 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(mode: mMode); |
30 | } |
31 | } |
32 | |
33 | void KRichTextEditPrivate::setTextCursor(QTextCursor &cursor) |
34 | { |
35 | Q_Q(KRichTextEdit); |
36 | |
37 | q->setTextCursor(cursor); |
38 | } |
39 | |
40 | void 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(op: QTextCursor::StartOfWord); |
49 | wordEnd.movePosition(op: QTextCursor::EndOfWord); |
50 | |
51 | cursor.beginEditBlock(); |
52 | if (!cursor.hasSelection() && cursor.position() != wordStart.position() && cursor.position() != wordEnd.position()) { |
53 | cursor.select(selection: QTextCursor::WordUnderCursor); |
54 | } |
55 | cursor.mergeCharFormat(modifier: format); |
56 | q->mergeCurrentCharFormat(modifier: format); |
57 | cursor.endEditBlock(); |
58 | } |
59 | |
60 | KRichTextEdit::KRichTextEdit(const QString &text, QWidget *parent) |
61 | : KRichTextEdit(*new KRichTextEditPrivate(this), text, parent) |
62 | { |
63 | } |
64 | |
65 | KRichTextEdit::KRichTextEdit(KRichTextEditPrivate &dd, const QString &text, QWidget *parent) |
66 | : KTextEdit(dd, text, parent) |
67 | { |
68 | Q_D(KRichTextEdit); |
69 | |
70 | d->init(); |
71 | } |
72 | |
73 | KRichTextEdit::KRichTextEdit(QWidget *parent) |
74 | : KRichTextEdit(*new KRichTextEditPrivate(this), parent) |
75 | { |
76 | } |
77 | |
78 | KRichTextEdit::KRichTextEdit(KRichTextEditPrivate &dd, QWidget *parent) |
79 | : KTextEdit(dd, parent) |
80 | { |
81 | Q_D(KRichTextEdit); |
82 | |
83 | d->init(); |
84 | } |
85 | |
86 | KRichTextEdit::~KRichTextEdit() = default; |
87 | |
88 | //@cond PRIVATE |
89 | void KRichTextEditPrivate::init() |
90 | { |
91 | Q_Q(KRichTextEdit); |
92 | |
93 | q->setAcceptRichText(false); |
94 | KCursor::setAutoHideCursor(w: q, enable: true, customEventFilter: true); |
95 | } |
96 | //@endcond |
97 | |
98 | void KRichTextEdit::setListStyle(int _styleIndex) |
99 | { |
100 | Q_D(KRichTextEdit); |
101 | |
102 | d->nestedListHelper->handleOnBulletType(styleIndex: -_styleIndex); |
103 | setFocus(); |
104 | d->activateRichText(); |
105 | } |
106 | |
107 | void KRichTextEdit::indentListMore() |
108 | { |
109 | Q_D(KRichTextEdit); |
110 | |
111 | d->nestedListHelper->changeIndent(delta: +1); |
112 | d->activateRichText(); |
113 | } |
114 | |
115 | void KRichTextEdit::indentListLess() |
116 | { |
117 | Q_D(KRichTextEdit); |
118 | |
119 | d->nestedListHelper->changeIndent(delta: -1); |
120 | } |
121 | |
122 | void KRichTextEdit::insertHorizontalRule() |
123 | { |
124 | Q_D(KRichTextEdit); |
125 | |
126 | QTextCursor cursor = textCursor(); |
127 | QTextBlockFormat bf = cursor.blockFormat(); |
128 | QTextCharFormat cf = cursor.charFormat(); |
129 | |
130 | cursor.beginEditBlock(); |
131 | cursor.insertHtml(QStringLiteral("<hr>" )); |
132 | cursor.insertBlock(format: bf, charFormat: cf); |
133 | cursor.endEditBlock(); |
134 | setTextCursor(cursor); |
135 | d->activateRichText(); |
136 | } |
137 | |
138 | void KRichTextEdit::alignLeft() |
139 | { |
140 | Q_D(KRichTextEdit); |
141 | |
142 | setAlignment(Qt::AlignLeft); |
143 | setFocus(); |
144 | d->activateRichText(); |
145 | } |
146 | |
147 | void KRichTextEdit::alignCenter() |
148 | { |
149 | Q_D(KRichTextEdit); |
150 | |
151 | setAlignment(Qt::AlignHCenter); |
152 | setFocus(); |
153 | d->activateRichText(); |
154 | } |
155 | |
156 | void KRichTextEdit::alignRight() |
157 | { |
158 | Q_D(KRichTextEdit); |
159 | |
160 | setAlignment(Qt::AlignRight); |
161 | setFocus(); |
162 | d->activateRichText(); |
163 | } |
164 | |
165 | void KRichTextEdit::alignJustify() |
166 | { |
167 | Q_D(KRichTextEdit); |
168 | |
169 | setAlignment(Qt::AlignJustify); |
170 | setFocus(); |
171 | d->activateRichText(); |
172 | } |
173 | |
174 | void KRichTextEdit::makeRightToLeft() |
175 | { |
176 | Q_D(KRichTextEdit); |
177 | |
178 | QTextBlockFormat format; |
179 | format.setLayoutDirection(Qt::RightToLeft); |
180 | QTextCursor cursor = textCursor(); |
181 | cursor.mergeBlockFormat(modifier: format); |
182 | setTextCursor(cursor); |
183 | setFocus(); |
184 | d->activateRichText(); |
185 | } |
186 | |
187 | void KRichTextEdit::makeLeftToRight() |
188 | { |
189 | Q_D(KRichTextEdit); |
190 | |
191 | QTextBlockFormat format; |
192 | format.setLayoutDirection(Qt::LeftToRight); |
193 | QTextCursor cursor = textCursor(); |
194 | cursor.mergeBlockFormat(modifier: format); |
195 | setTextCursor(cursor); |
196 | setFocus(); |
197 | d->activateRichText(); |
198 | } |
199 | |
200 | void KRichTextEdit::setTextBold(bool bold) |
201 | { |
202 | Q_D(KRichTextEdit); |
203 | |
204 | QTextCharFormat fmt; |
205 | fmt.setFontWeight(bold ? QFont::Bold : QFont::Normal); |
206 | d->mergeFormatOnWordOrSelection(format: fmt); |
207 | setFocus(); |
208 | d->activateRichText(); |
209 | } |
210 | |
211 | void KRichTextEdit::setTextItalic(bool italic) |
212 | { |
213 | Q_D(KRichTextEdit); |
214 | |
215 | QTextCharFormat fmt; |
216 | fmt.setFontItalic(italic); |
217 | d->mergeFormatOnWordOrSelection(format: fmt); |
218 | setFocus(); |
219 | d->activateRichText(); |
220 | } |
221 | |
222 | void KRichTextEdit::setTextUnderline(bool underline) |
223 | { |
224 | Q_D(KRichTextEdit); |
225 | |
226 | QTextCharFormat fmt; |
227 | fmt.setFontUnderline(underline); |
228 | d->mergeFormatOnWordOrSelection(format: fmt); |
229 | setFocus(); |
230 | d->activateRichText(); |
231 | } |
232 | |
233 | void KRichTextEdit::setTextStrikeOut(bool strikeOut) |
234 | { |
235 | Q_D(KRichTextEdit); |
236 | |
237 | QTextCharFormat fmt; |
238 | fmt.setFontStrikeOut(strikeOut); |
239 | d->mergeFormatOnWordOrSelection(format: fmt); |
240 | setFocus(); |
241 | d->activateRichText(); |
242 | } |
243 | |
244 | void KRichTextEdit::setTextForegroundColor(const QColor &color) |
245 | { |
246 | Q_D(KRichTextEdit); |
247 | |
248 | QTextCharFormat fmt; |
249 | fmt.setForeground(color); |
250 | d->mergeFormatOnWordOrSelection(format: fmt); |
251 | setFocus(); |
252 | d->activateRichText(); |
253 | } |
254 | |
255 | void KRichTextEdit::setTextBackgroundColor(const QColor &color) |
256 | { |
257 | Q_D(KRichTextEdit); |
258 | |
259 | QTextCharFormat fmt; |
260 | fmt.setBackground(color); |
261 | d->mergeFormatOnWordOrSelection(format: fmt); |
262 | setFocus(); |
263 | d->activateRichText(); |
264 | } |
265 | |
266 | void KRichTextEdit::setFontFamily(const QString &fontFamily) |
267 | { |
268 | Q_D(KRichTextEdit); |
269 | |
270 | QTextCharFormat fmt; |
271 | fmt.setFontFamilies({fontFamily}); |
272 | d->mergeFormatOnWordOrSelection(format: fmt); |
273 | setFocus(); |
274 | d->activateRichText(); |
275 | } |
276 | |
277 | void KRichTextEdit::setFontSize(int size) |
278 | { |
279 | Q_D(KRichTextEdit); |
280 | |
281 | QTextCharFormat fmt; |
282 | fmt.setFontPointSize(size); |
283 | d->mergeFormatOnWordOrSelection(format: fmt); |
284 | setFocus(); |
285 | d->activateRichText(); |
286 | } |
287 | |
288 | void KRichTextEdit::setFont(const QFont &font) |
289 | { |
290 | Q_D(KRichTextEdit); |
291 | |
292 | QTextCharFormat fmt; |
293 | fmt.setFont(font); |
294 | d->mergeFormatOnWordOrSelection(format: fmt); |
295 | setFocus(); |
296 | d->activateRichText(); |
297 | } |
298 | |
299 | void KRichTextEdit::switchToPlainText() |
300 | { |
301 | Q_D(KRichTextEdit); |
302 | |
303 | if (d->mMode == Rich) { |
304 | d->mMode = Plain; |
305 | // TODO: Warn the user about this? |
306 | auto insertPlainFunc = [this]() { |
307 | insertPlainTextImplementation(); |
308 | }; |
309 | QMetaObject::invokeMethod(object: this, function&: insertPlainFunc); |
310 | setAcceptRichText(false); |
311 | Q_EMIT textModeChanged(mode: d->mMode); |
312 | } |
313 | } |
314 | |
315 | void KRichTextEdit::insertPlainTextImplementation() |
316 | { |
317 | document()->setPlainText(document()->toPlainText()); |
318 | } |
319 | |
320 | void KRichTextEdit::setTextSuperScript(bool superscript) |
321 | { |
322 | Q_D(KRichTextEdit); |
323 | |
324 | QTextCharFormat fmt; |
325 | fmt.setVerticalAlignment(superscript ? QTextCharFormat::AlignSuperScript : QTextCharFormat::AlignNormal); |
326 | d->mergeFormatOnWordOrSelection(format: fmt); |
327 | setFocus(); |
328 | d->activateRichText(); |
329 | } |
330 | |
331 | void KRichTextEdit::setTextSubScript(bool subscript) |
332 | { |
333 | Q_D(KRichTextEdit); |
334 | |
335 | QTextCharFormat fmt; |
336 | fmt.setVerticalAlignment(subscript ? QTextCharFormat::AlignSubScript : QTextCharFormat::AlignNormal); |
337 | d->mergeFormatOnWordOrSelection(format: fmt); |
338 | setFocus(); |
339 | d->activateRichText(); |
340 | } |
341 | |
342 | void KRichTextEdit::setHeadingLevel(int level) |
343 | { |
344 | Q_D(KRichTextEdit); |
345 | |
346 | const int boundedLevel = qBound(min: 0, val: 6, max: 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 | |
351 | QTextCursor cursor = textCursor(); |
352 | cursor.beginEditBlock(); |
353 | |
354 | QTextBlockFormat blkfmt; |
355 | blkfmt.setHeadingLevel(boundedLevel); |
356 | cursor.mergeBlockFormat(modifier: blkfmt); |
357 | |
358 | QTextCharFormat chrfmt; |
359 | chrfmt.setFontWeight(boundedLevel > 0 ? QFont::Bold : QFont::Normal); |
360 | chrfmt.setProperty(propertyId: QTextFormat::FontSizeAdjustment, value: 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(pos: qMin(a: top.anchor(), b: top.position())); |
366 | top.movePosition(op: QTextCursor::StartOfBlock); |
367 | |
368 | QTextCursor bottom = selectCursor; |
369 | bottom.setPosition(pos: qMax(a: bottom.anchor(), b: bottom.position())); |
370 | bottom.movePosition(op: QTextCursor::EndOfBlock); |
371 | |
372 | selectCursor.setPosition(pos: top.position(), mode: QTextCursor::MoveAnchor); |
373 | selectCursor.setPosition(pos: bottom.position(), mode: QTextCursor::KeepAnchor); |
374 | } else { |
375 | selectCursor.select(selection: QTextCursor::BlockUnderCursor); |
376 | } |
377 | selectCursor.mergeCharFormat(modifier: chrfmt); |
378 | |
379 | cursor.mergeBlockCharFormat(modifier: chrfmt); |
380 | cursor.endEditBlock(); |
381 | setTextCursor(cursor); |
382 | setFocus(); |
383 | d->activateRichText(); |
384 | } |
385 | |
386 | void KRichTextEdit::enableRichTextMode() |
387 | { |
388 | Q_D(KRichTextEdit); |
389 | |
390 | d->activateRichText(); |
391 | } |
392 | |
393 | KRichTextEdit::Mode KRichTextEdit::textMode() const |
394 | { |
395 | Q_D(const KRichTextEdit); |
396 | |
397 | return d->mMode; |
398 | } |
399 | |
400 | QString KRichTextEdit::textOrHtml() const |
401 | { |
402 | if (textMode() == Rich) { |
403 | return toCleanHtml(); |
404 | } else { |
405 | return toPlainText(); |
406 | } |
407 | } |
408 | |
409 | void KRichTextEdit::setTextOrHtml(const QString &text) |
410 | { |
411 | Q_D(KRichTextEdit); |
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 |
425 | QString KRichTextEdit::currentLinkText() const |
426 | { |
427 | QTextCursor cursor = textCursor(); |
428 | selectLinkText(cursor: &cursor); |
429 | return cursor.selectedText(); |
430 | } |
431 | |
432 | // KF6 TODO: remove constness |
433 | void KRichTextEdit::selectLinkText() const |
434 | { |
435 | Q_D(const KRichTextEdit); |
436 | |
437 | QTextCursor cursor = textCursor(); |
438 | selectLinkText(cursor: &cursor); |
439 | // KF6 TODO: remove const_cast |
440 | const_cast<KRichTextEditPrivate *>(d)->setTextCursor(cursor); |
441 | } |
442 | |
443 | void KRichTextEdit::selectLinkText(QTextCursor *cursor) const |
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(pos: cursor->position() - 1); |
455 | } |
456 | if (cursor->charFormat().anchorHref() != aHref) { |
457 | cursor->setPosition(pos: cursor->position() + 1, mode: 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(pos: cursor->position() + 1, mode: QTextCursor::KeepAnchor); |
466 | } |
467 | if (cursor->charFormat().anchorHref() != aHref) { |
468 | cursor->setPosition(pos: cursor->position() - 1, mode: 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(op: QTextCursor::StartOfWord); |
475 | cursor->movePosition(op: QTextCursor::EndOfWord, QTextCursor::KeepAnchor); |
476 | } |
477 | } |
478 | |
479 | QString KRichTextEdit::currentLinkUrl() const |
480 | { |
481 | return textCursor().charFormat().anchorHref(); |
482 | } |
483 | |
484 | void KRichTextEdit::updateLink(const QString &linkUrl, const QString &linkText) |
485 | { |
486 | Q_D(KRichTextEdit); |
487 | |
488 | selectLinkText(); |
489 | |
490 | QTextCursor cursor = textCursor(); |
491 | cursor.beginEditBlock(); |
492 | |
493 | if (!cursor.hasSelection()) { |
494 | cursor.select(selection: QTextCursor::WordUnderCursor); |
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. |
508 | format.setUnderlineStyle(QTextCharFormat::SingleUnderline); |
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(text: _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(pos: cursor.selectionEnd()); |
540 | cursor.setCharFormat(originalFormat); |
541 | cursor.insertText(QStringLiteral(" " )); |
542 | } |
543 | |
544 | cursor.endEditBlock(); |
545 | } |
546 | |
547 | void KRichTextEdit::keyPressEvent(QKeyEvent *event) |
548 | { |
549 | Q_D(KRichTextEdit); |
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()))) { |
562 | QTextCursor cursor = textCursor(); |
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) { |
576 | KTextEdit::keyPressEvent(event); |
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 |
583 | textCursor().joinPreviousEditBlock(); |
584 | if (textCursor().atBlockEnd()) { |
585 | setHeadingLevel(0); |
586 | } else { |
587 | setHeadingLevel(prevHeadingLevel); |
588 | } |
589 | textCursor().endEditBlock(); |
590 | } |
591 | |
592 | Q_EMIT cursorPositionChanged(); |
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 | |
608 | bool KRichTextEdit::canIndentList() const |
609 | { |
610 | Q_D(const KRichTextEdit); |
611 | |
612 | return d->nestedListHelper->canIndent(); |
613 | } |
614 | |
615 | bool KRichTextEdit::canDedentList() const |
616 | { |
617 | Q_D(const KRichTextEdit); |
618 | |
619 | return d->nestedListHelper->canDedent(); |
620 | } |
621 | |
622 | QString KRichTextEdit::toCleanHtml() const |
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; \"> </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> </p>. |
646 | // replace all occurrences with the new line text |
647 | result.replace(re: EMPTYLINEREGEX, after: 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(before: OLLISTPATTERNQT, after: 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(before: ULLISTPATTERNQT, after: UNORDEREDLISTHTML); |
656 | |
657 | return result; |
658 | } |
659 | |
660 | #include "moc_krichtextedit.cpp" |
661 | |