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