KDEUI
krichtextedit.cpp
Go to the documentation of this file.
00001 /* 00002 * krichtextedit 00003 * 00004 * Copyright 2007 Laurent Montel <montel@kde.org> 00005 * Copyright 2008 Thomas McGuire <thomas.mcguire@gmx.net> 00006 * Copyright 2008 Stephen Kelly <steveire@gmail.com> 00007 * 00008 * This library is free software; you can redistribute it and/or 00009 * modify it under the terms of the GNU Lesser General Public 00010 * License as published by the Free Software Foundation; either 00011 * version 2.1 of the License, or (at your option) any later version. 00012 * 00013 * This library is distributed in the hope that it will be useful, 00014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 * Lesser General Public License for more details. 00017 * 00018 * You should have received a copy of the GNU Lesser General Public 00019 * License along with this library; if not, write to the Free Software 00020 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 00021 * 02110-1301 USA 00022 */ 00023 00024 #include "krichtextedit.h" 00025 00026 // Own includes 00027 #include "nestedlisthelper.h" 00028 #include "klinkdialog.h" 00029 00030 // kdelibs includes 00031 #include <kcursor.h> 00032 #include <kcolorscheme.h> 00033 00034 // Qt includes 00035 #include <QtGui/QTextDocumentFragment> 00036 #include <QtGui/QMouseEvent> 00037 00042 //@cond PRIVATE 00043 class KRichTextEditPrivate : public QObject 00044 { 00045 public: 00046 KRichTextEditPrivate(KRichTextEdit *parent) 00047 : q(parent), 00048 mMode(KRichTextEdit::Plain) { 00049 nestedListHelper = new NestedListHelper(q); 00050 } 00051 00052 ~KRichTextEditPrivate() { 00053 delete nestedListHelper; 00054 } 00055 00056 // 00057 // Normal functions 00058 // 00059 00060 // If the text under the cursor is a link, the cursor's selection is set to 00061 // the complete link text. Otherwise selects the current word if there is no 00062 // selection. 00063 void selectLinkText() const; 00064 00065 void init(); 00066 00067 // Switches to rich text mode and emits the mode changed signal if the 00068 // mode really changed. 00069 void activateRichText(); 00070 00071 // Applies formatting to the current word if there is no selection. 00072 void mergeFormatOnWordOrSelection(const QTextCharFormat &format); 00073 00074 void setTextCursor(QTextCursor &cursor); 00075 00076 00077 // Data members 00078 00079 KRichTextEdit *q; 00080 KRichTextEdit::Mode mMode; 00081 00082 NestedListHelper *nestedListHelper; 00083 00084 }; 00085 00086 void KRichTextEditPrivate::activateRichText() 00087 { 00088 if (mMode == KRichTextEdit::Plain) { 00089 q->setAcceptRichText(true); 00090 mMode = KRichTextEdit::Rich; 00091 emit q->textModeChanged(mMode); 00092 } 00093 } 00094 00095 void KRichTextEditPrivate::setTextCursor(QTextCursor &cursor) 00096 { 00097 q->setTextCursor(cursor); 00098 } 00099 00100 void KRichTextEditPrivate::mergeFormatOnWordOrSelection(const QTextCharFormat &format) 00101 { 00102 QTextCursor cursor = q->textCursor(); 00103 QTextCursor wordStart(cursor); 00104 QTextCursor wordEnd(cursor); 00105 00106 wordStart.movePosition(QTextCursor::StartOfWord); 00107 wordEnd.movePosition(QTextCursor::EndOfWord); 00108 00109 cursor.beginEditBlock(); 00110 if (!cursor.hasSelection() && cursor.position() != wordStart.position() && cursor.position() != wordEnd.position()) 00111 cursor.select(QTextCursor::WordUnderCursor); 00112 cursor.mergeCharFormat(format); 00113 q->mergeCurrentCharFormat(format); 00114 cursor.endEditBlock(); 00115 } 00116 //@endcond 00117 00118 KRichTextEdit::KRichTextEdit(const QString& text, QWidget *parent) 00119 : KTextEdit(text, parent), d(new KRichTextEditPrivate(this)) 00120 { 00121 d->init(); 00122 } 00123 00124 KRichTextEdit::KRichTextEdit(QWidget *parent) 00125 : KTextEdit(parent), d(new KRichTextEditPrivate(this)) 00126 { 00127 d->init(); 00128 } 00129 00130 KRichTextEdit::~KRichTextEdit() 00131 { 00132 delete d; 00133 } 00134 00135 //@cond PRIVATE 00136 void KRichTextEditPrivate::init() 00137 { 00138 q->setAcceptRichText(false); 00139 KCursor::setAutoHideCursor(q, true, true); 00140 } 00141 //@endcond 00142 00143 void KRichTextEdit::setListStyle(int _styleIndex) 00144 { 00145 d->nestedListHelper->handleOnBulletType(-_styleIndex); 00146 setFocus(); 00147 d->activateRichText(); 00148 } 00149 00150 void KRichTextEdit::indentListMore() 00151 { 00152 d->nestedListHelper->handleOnIndentMore(); 00153 d->activateRichText(); 00154 } 00155 00156 void KRichTextEdit::indentListLess() 00157 { 00158 d->nestedListHelper->handleOnIndentLess(); 00159 } 00160 00161 void KRichTextEdit::insertHorizontalRule() 00162 { 00163 QTextCursor cursor = textCursor(); 00164 QTextBlockFormat bf = cursor.blockFormat(); 00165 QTextCharFormat cf = cursor.charFormat(); 00166 00167 cursor.beginEditBlock(); 00168 cursor.insertHtml("<hr>"); 00169 cursor.insertBlock(bf, cf); 00170 setTextCursor(cursor); 00171 d->activateRichText(); 00172 cursor.endEditBlock(); 00173 } 00174 00175 void KRichTextEdit::alignLeft() 00176 { 00177 setAlignment(Qt::AlignLeft); 00178 setFocus(); 00179 d->activateRichText(); 00180 } 00181 00182 void KRichTextEdit::alignCenter() 00183 { 00184 setAlignment(Qt::AlignHCenter); 00185 setFocus(); 00186 d->activateRichText(); 00187 } 00188 00189 void KRichTextEdit::alignRight() 00190 { 00191 setAlignment(Qt::AlignRight); 00192 setFocus(); 00193 d->activateRichText(); 00194 } 00195 00196 void KRichTextEdit::alignJustify() 00197 { 00198 setAlignment(Qt::AlignJustify); 00199 setFocus(); 00200 d->activateRichText(); 00201 } 00202 00203 void KRichTextEdit::makeRightToLeft() 00204 { 00205 QTextBlockFormat format; 00206 format.setLayoutDirection(Qt::RightToLeft); 00207 QTextCursor cursor = textCursor(); 00208 cursor.mergeBlockFormat(format); 00209 setTextCursor(cursor); 00210 setFocus(); 00211 d->activateRichText(); 00212 } 00213 00214 void KRichTextEdit::makeLeftToRight() 00215 { 00216 QTextBlockFormat format; 00217 format.setLayoutDirection(Qt::LeftToRight); 00218 QTextCursor cursor = textCursor(); 00219 cursor.mergeBlockFormat(format); 00220 setTextCursor(cursor); 00221 setFocus(); 00222 d->activateRichText(); 00223 } 00224 00225 void KRichTextEdit::setTextBold(bool bold) 00226 { 00227 QTextCharFormat fmt; 00228 fmt.setFontWeight(bold ? QFont::Bold : QFont::Normal); 00229 d->mergeFormatOnWordOrSelection(fmt); 00230 setFocus(); 00231 d->activateRichText(); 00232 } 00233 00234 void KRichTextEdit::setTextItalic(bool italic) 00235 { 00236 QTextCharFormat fmt; 00237 fmt.setFontItalic(italic); 00238 d->mergeFormatOnWordOrSelection(fmt); 00239 setFocus(); 00240 d->activateRichText(); 00241 } 00242 00243 void KRichTextEdit::setTextUnderline(bool underline) 00244 { 00245 QTextCharFormat fmt; 00246 fmt.setFontUnderline(underline); 00247 d->mergeFormatOnWordOrSelection(fmt); 00248 setFocus(); 00249 d->activateRichText(); 00250 } 00251 00252 void KRichTextEdit::setTextStrikeOut(bool strikeOut) 00253 { 00254 QTextCharFormat fmt; 00255 fmt.setFontStrikeOut(strikeOut); 00256 d->mergeFormatOnWordOrSelection(fmt); 00257 setFocus(); 00258 d->activateRichText(); 00259 } 00260 00261 void KRichTextEdit::setTextForegroundColor(const QColor &color) 00262 { 00263 QTextCharFormat fmt; 00264 fmt.setForeground(color); 00265 d->mergeFormatOnWordOrSelection(fmt); 00266 setFocus(); 00267 d->activateRichText(); 00268 } 00269 00270 void KRichTextEdit::setTextBackgroundColor(const QColor &color) 00271 { 00272 QTextCharFormat fmt; 00273 fmt.setBackground(color); 00274 d->mergeFormatOnWordOrSelection(fmt); 00275 setFocus(); 00276 d->activateRichText(); 00277 } 00278 00279 void KRichTextEdit::setFontFamily(const QString &fontFamily) 00280 { 00281 QTextCharFormat fmt; 00282 fmt.setFontFamily(fontFamily); 00283 d->mergeFormatOnWordOrSelection(fmt); 00284 setFocus(); 00285 d->activateRichText(); 00286 } 00287 00288 void KRichTextEdit::setFontSize(int size) 00289 { 00290 QTextCharFormat fmt; 00291 fmt.setFontPointSize(size); 00292 d->mergeFormatOnWordOrSelection(fmt); 00293 setFocus(); 00294 d->activateRichText(); 00295 } 00296 00297 void KRichTextEdit::setFont(const QFont &font) 00298 { 00299 QTextCharFormat fmt; 00300 fmt.setFont(font); 00301 d->mergeFormatOnWordOrSelection(fmt); 00302 setFocus(); 00303 d->activateRichText(); 00304 } 00305 00306 void KRichTextEdit::switchToPlainText() 00307 { 00308 if (d->mMode == Rich) { 00309 d->mMode = Plain; 00310 // TODO: Warn the user about this? 00311 document()->setPlainText(document()->toPlainText()); 00312 setAcceptRichText(false); 00313 emit textModeChanged(d->mMode); 00314 } 00315 } 00316 00317 void KRichTextEdit::setTextSuperScript(bool superscript) 00318 { 00319 QTextCharFormat fmt; 00320 fmt.setVerticalAlignment(superscript ? QTextCharFormat::AlignSuperScript : QTextCharFormat::AlignNormal); 00321 d->mergeFormatOnWordOrSelection(fmt); 00322 setFocus(); 00323 d->activateRichText(); 00324 } 00325 00326 void KRichTextEdit::setTextSubScript(bool subscript) 00327 { 00328 QTextCharFormat fmt; 00329 fmt.setVerticalAlignment(subscript ? QTextCharFormat::AlignSubScript : QTextCharFormat::AlignNormal); 00330 d->mergeFormatOnWordOrSelection(fmt); 00331 setFocus(); 00332 d->activateRichText(); 00333 } 00334 00335 void KRichTextEdit::enableRichTextMode() 00336 { 00337 d->activateRichText(); 00338 } 00339 00340 KRichTextEdit::Mode KRichTextEdit::textMode() const 00341 { 00342 return d->mMode; 00343 } 00344 00345 QString KRichTextEdit::textOrHtml() const 00346 { 00347 if (textMode() == Rich) 00348 return toCleanHtml(); 00349 else 00350 return toPlainText(); 00351 } 00352 00353 void KRichTextEdit::setTextOrHtml(const QString &text) 00354 { 00355 // might be rich text 00356 if (Qt::mightBeRichText(text)) { 00357 if (d->mMode == KRichTextEdit::Plain) { 00358 d->activateRichText(); 00359 } 00360 setHtml(text); 00361 } else { 00362 setPlainText(text); 00363 } 00364 } 00365 00366 QString KRichTextEdit::currentLinkText() const 00367 { 00368 QTextCursor cursor = textCursor(); 00369 selectLinkText(&cursor); 00370 return cursor.selectedText(); 00371 } 00372 00373 void KRichTextEdit::selectLinkText() const 00374 { 00375 QTextCursor cursor = textCursor(); 00376 selectLinkText(&cursor); 00377 d->setTextCursor(cursor); 00378 } 00379 00380 void KRichTextEdit::selectLinkText(QTextCursor *cursor) const 00381 { 00382 // If the cursor is on a link, select the text of the link. 00383 if (cursor->charFormat().isAnchor()) { 00384 QString aHref = cursor->charFormat().anchorHref(); 00385 00386 // Move cursor to start of link 00387 while (cursor->charFormat().anchorHref() == aHref) { 00388 if (cursor->atStart()) 00389 break; 00390 cursor->setPosition(cursor->position() - 1); 00391 } 00392 if (cursor->charFormat().anchorHref() != aHref) 00393 cursor->setPosition(cursor->position() + 1, QTextCursor::KeepAnchor); 00394 00395 // Move selection to the end of the link 00396 while (cursor->charFormat().anchorHref() == aHref) { 00397 if (cursor->atEnd()) 00398 break; 00399 cursor->setPosition(cursor->position() + 1, QTextCursor::KeepAnchor); 00400 } 00401 if (cursor->charFormat().anchorHref() != aHref) 00402 cursor->setPosition(cursor->position() - 1, QTextCursor::KeepAnchor); 00403 } else if (cursor->hasSelection()) { 00404 // Nothing to to. Using the currently selected text as the link text. 00405 } else { 00406 00407 // Select current word 00408 cursor->movePosition(QTextCursor::StartOfWord); 00409 cursor->movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); 00410 } 00411 } 00412 00413 QString KRichTextEdit::currentLinkUrl() const 00414 { 00415 return textCursor().charFormat().anchorHref(); 00416 } 00417 00418 void KRichTextEdit::updateLink(const QString &linkUrl, const QString &linkText) 00419 { 00420 selectLinkText(); 00421 00422 QTextCursor cursor = textCursor(); 00423 cursor.beginEditBlock(); 00424 00425 if (!cursor.hasSelection()) { 00426 cursor.select(QTextCursor::WordUnderCursor); 00427 } 00428 00429 QTextCharFormat format = cursor.charFormat(); 00430 // Save original format to create an extra space with the existing char 00431 // format for the block 00432 const QTextCharFormat originalFormat = format; 00433 if (!linkUrl.isEmpty()) { 00434 // Add link details 00435 format.setAnchor(true); 00436 format.setAnchorHref(linkUrl); 00437 // Workaround for QTBUG-1814: 00438 // Link formatting does not get applied immediately when setAnchor(true) 00439 // is called. So the formatting needs to be applied manually. 00440 format.setUnderlineStyle(QTextCharFormat::SingleUnderline); 00441 format.setUnderlineColor(KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::LinkText).color()); 00442 format.setForeground(KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::LinkText).color()); 00443 d->activateRichText(); 00444 } else { 00445 // Remove link details 00446 format.setAnchor(false); 00447 format.setAnchorHref(QString()); 00448 // Workaround for QTBUG-1814: 00449 // Link formatting does not get removed immediately when setAnchor(false) 00450 // is called. So the formatting needs to be applied manually. 00451 QTextDocument defaultTextDocument; 00452 QTextCharFormat defaultCharFormat = defaultTextDocument.begin().charFormat(); 00453 00454 format.setUnderlineStyle( defaultCharFormat.underlineStyle() ); 00455 format.setUnderlineColor( defaultCharFormat.underlineColor() ); 00456 format.setForeground( defaultCharFormat.foreground() ); 00457 } 00458 00459 // Insert link text specified in dialog, otherwise write out url. 00460 QString _linkText; 00461 if (!linkText.isEmpty()) { 00462 _linkText = linkText; 00463 } else { 00464 _linkText = linkUrl; 00465 } 00466 cursor.insertText(_linkText, format); 00467 00468 00469 // Insert a space after the link if at the end of the block so that 00470 // typing some text after the link does not carry link formatting 00471 if (!linkUrl.isEmpty() && cursor.atBlockEnd()) { 00472 cursor.setPosition(cursor.selectionEnd()); 00473 cursor.setCharFormat(originalFormat); 00474 cursor.insertText(QString(" ")); 00475 } 00476 00477 cursor.endEditBlock(); 00478 } 00479 00480 void KRichTextEdit::keyPressEvent(QKeyEvent *event) 00481 { 00482 bool handled = false; 00483 if (textCursor().currentList()) { 00484 // handled is False if the key press event was not handled or not completely 00485 // handled by the Helper class. 00486 handled = d->nestedListHelper->handleBeforeKeyPressEvent(event); 00487 } 00488 00489 if (!handled) { 00490 KTextEdit::keyPressEvent(event); 00491 } 00492 00493 if (textCursor().currentList()) { 00494 d->nestedListHelper->handleAfterKeyPressEvent(event); 00495 } 00496 emit cursorPositionChanged(); 00497 } 00498 00499 // void KRichTextEdit::dropEvent(QDropEvent *event) 00500 // { 00501 // int dropSize = event->mimeData()->text().size(); 00502 // 00503 // dropEvent( event ); 00504 // QTextCursor cursor = textCursor(); 00505 // int cursorPosition = cursor.position(); 00506 // cursor.setPosition( cursorPosition - dropSize ); 00507 // cursor.setPosition( cursorPosition, QTextCursor::KeepAnchor ); 00508 // setTextCursor( cursor ); 00509 // d->nestedListHelper->handleAfterDropEvent( event ); 00510 // } 00511 00512 00513 bool KRichTextEdit::canIndentList() const 00514 { 00515 return d->nestedListHelper->canIndent(); 00516 } 00517 00518 bool KRichTextEdit::canDedentList() const 00519 { 00520 return d->nestedListHelper->canDedent(); 00521 } 00522 00523 QString KRichTextEdit::toCleanHtml() const 00524 { 00525 QString result = toHtml(); 00526 00527 static const QString EMPTYLINEFROMQT = QLatin1String( 00528 "<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; " 00529 "margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; " 00530 "-qt-user-state:0;\"></p>" ); 00531 00532 static const QString EMPTYLINEHTML = QLatin1String( 00533 "<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; " 00534 "margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; " 00535 "-qt-user-state:0;\"><br /></p>" ); 00536 00537 static const QString OLLISTPATTERNQT = QLatin1String( 00538 "<ol style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px;" ); 00539 00540 static const QString ULLISTPATTERNQT = QLatin1String( 00541 "<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px;" ); 00542 00543 static const QString ORDEREDLISTHTML = QLatin1String( 00544 "<ol style=\"margin-top: 0px; margin-bottom: 0px;" ); 00545 00546 static const QString UNORDEREDLISTHTML = QLatin1String( 00547 "<ul style=\"margin-top: 0px; margin-bottom: 0px;" ); 00548 00549 // fix 1 - empty lines should show as empty lines - MS Outlook treats margin-top:0px; as 00550 // a non-existing line. 00551 // Although we can simply remove the margin-top style property, we still get unwanted results 00552 // if you have three or more empty lines. It's best to replace empty <p> elements with <p><br /></p>. 00553 // As per http://www.w3.org/TR/xhtml1/dtds.html#a_dtd_XHTML-1.0-Strict, <br> elements are still proper 00554 // HTML. 00555 result.replace(EMPTYLINEFROMQT, EMPTYLINEHTML); 00556 00557 // fix 2a - ordered lists - MS Outlook treats margin-left:0px; as 00558 // a non-existing number; e.g: "1. First item" turns into "First Item" 00559 result.replace(OLLISTPATTERNQT, ORDEREDLISTHTML); 00560 00561 // fix 2b - unordered lists - MS Outlook treats margin-left:0px; as 00562 // a non-existing bullet; e.g: "* First bullet" turns into "First Bullet" 00563 result.replace(ULLISTPATTERNQT, UNORDEREDLISTHTML); 00564 00565 return result; 00566 } 00567 00568 #include "krichtextedit.moc"
KDE 4.6 API Reference