| 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 | |