File indexing completed on 2024-05-26 16:15:56
0001 /* This file is part of the KDE project 0002 * Copyright (C) 2009-2012 Pierre Stirnweiss <pstirnweiss@googlemail.com> 0003 * Copyright (C) 2006-2010 Thomas Zander <zander@kde.org> 0004 * Copyright (c) 2011 Boudewijn Rempt <boud@kogmbh.com> 0005 * Copyright (C) 2011-2012 C. Boemann <cbo@boemann.dk> 0006 * Copyright (C) 2014 Denis Kuplyakov <dener.kup@gmail.com> 0007 * 0008 * This library is free software; you can redistribute it and/or 0009 * modify it under the terms of the GNU Library General Public 0010 * License as published by the Free Software Foundation; either 0011 * version 2 of the License, or (at your option) any later version. 0012 * 0013 * This library is distributed in the hope that it will be useful, 0014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0016 * Library General Public License for more details. 0017 * 0018 * You should have received a copy of the GNU Library General Public License 0019 * along with this library; see the file COPYING.LIB. If not, write to 0020 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0021 * Boston, MA 02110-1301, USA. 0022 */ 0023 0024 #include "KoTextEditor.h" 0025 #include "KoTextEditor_p.h" 0026 0027 #include "styles/KoCharacterStyle.h" 0028 #include "styles/KoParagraphStyle.h" 0029 #include "styles/KoStyleManager.h" 0030 #include "commands/ParagraphFormattingCommand.h" 0031 0032 #include <klocalizedstring.h> 0033 0034 #include <QFontDatabase> 0035 #include <QTextBlock> 0036 #include <QTextBlockFormat> 0037 #include <QTextCharFormat> 0038 #include <QTextFormat> 0039 #include <QTextList> 0040 0041 #include "TextDebug.h" 0042 #include "KoTextDebug.h" 0043 0044 0045 void KoTextEditor::Private::clearCharFormatProperty(int property) 0046 { 0047 class PropertyWiper : public CharFormatVisitor 0048 { 0049 public: 0050 PropertyWiper(int propertyId) : propertyId(propertyId) {} 0051 void visit(QTextCharFormat &format) const override { 0052 format.clearProperty(propertyId); 0053 } 0054 0055 int propertyId; 0056 }; 0057 PropertyWiper wiper(property); 0058 CharFormatVisitor::visitSelection(q, wiper, KUndo2MagicString(), false); 0059 } 0060 0061 void KoTextEditor::bold(bool bold) 0062 { 0063 if (isEditProtected()) { 0064 return; 0065 } 0066 0067 d->updateState(KoTextEditor::Private::Format, kundo2_i18n("Bold")); 0068 QTextCharFormat format; 0069 format.setFontWeight(bold ? QFont::Bold : QFont::Normal); 0070 mergeAutoStyle(format); 0071 d->updateState(KoTextEditor::Private::NoOp); 0072 } 0073 0074 void KoTextEditor::italic(bool italic) 0075 { 0076 if (isEditProtected()) { 0077 return; 0078 } 0079 0080 d->updateState(KoTextEditor::Private::Format, kundo2_i18n("Italic")); 0081 QTextCharFormat format; 0082 format.setFontItalic(italic); 0083 mergeAutoStyle(format); 0084 d->updateState(KoTextEditor::Private::NoOp); 0085 } 0086 0087 void KoTextEditor::underline(bool underline) 0088 { 0089 if (isEditProtected()) { 0090 return; 0091 } 0092 0093 d->updateState(KoTextEditor::Private::Format, kundo2_i18n("Underline")); 0094 QTextCharFormat format; 0095 if (underline) { 0096 format.setProperty(KoCharacterStyle::UnderlineType, KoCharacterStyle::SingleLine); 0097 format.setProperty(KoCharacterStyle::UnderlineStyle, KoCharacterStyle::SolidLine); 0098 } else { 0099 format.setProperty(KoCharacterStyle::UnderlineType, KoCharacterStyle::NoLineType); 0100 format.setProperty(KoCharacterStyle::UnderlineStyle, KoCharacterStyle::NoLineStyle); 0101 } 0102 mergeAutoStyle(format); 0103 d->updateState(KoTextEditor::Private::NoOp); 0104 } 0105 0106 void KoTextEditor::strikeOut(bool strikeout) 0107 { 0108 if (isEditProtected()) { 0109 return; 0110 } 0111 0112 d->updateState(KoTextEditor::Private::Format, kundo2_i18n("Strike Out")); 0113 QTextCharFormat format; 0114 if (strikeout) { 0115 format.setProperty(KoCharacterStyle::StrikeOutType, KoCharacterStyle::SingleLine); 0116 format.setProperty(KoCharacterStyle::StrikeOutStyle, KoCharacterStyle::SolidLine); 0117 } else { 0118 format.setProperty(KoCharacterStyle::StrikeOutType, KoCharacterStyle::NoLineType); 0119 format.setProperty(KoCharacterStyle::StrikeOutStyle, KoCharacterStyle::NoLineStyle); 0120 } 0121 mergeAutoStyle(format); 0122 d->updateState(KoTextEditor::Private::NoOp); 0123 } 0124 0125 void KoTextEditor::setHorizontalTextAlignment(Qt::Alignment align) 0126 { 0127 if (isEditProtected()) { 0128 return; 0129 } 0130 0131 class Aligner : public BlockFormatVisitor 0132 { 0133 public: 0134 Aligner(Qt::Alignment align) : alignment(align) {} 0135 void visit(QTextBlock &block) const override { 0136 QTextBlockFormat format = block.blockFormat(); 0137 format.setAlignment(alignment); 0138 QTextCursor cursor(block); 0139 cursor.setBlockFormat(format); 0140 } 0141 Qt::Alignment alignment; 0142 }; 0143 0144 Aligner aligner(align); 0145 d->updateState(KoTextEditor::Private::Format, kundo2_i18n("Change Alignment")); 0146 BlockFormatVisitor::visitSelection(this, aligner, kundo2_i18n("Change Alignment")); 0147 d->updateState(KoTextEditor::Private::NoOp); 0148 emit textFormatChanged(); 0149 } 0150 0151 void KoTextEditor::setVerticalTextAlignment(Qt::Alignment align) 0152 { 0153 if (isEditProtected()) { 0154 return; 0155 } 0156 0157 QTextCharFormat::VerticalAlignment charAlign = QTextCharFormat::AlignNormal; 0158 if (align == Qt::AlignTop) 0159 charAlign = QTextCharFormat::AlignSuperScript; 0160 else if (align == Qt::AlignBottom) 0161 charAlign = QTextCharFormat::AlignSubScript; 0162 0163 d->updateState(KoTextEditor::Private::Format, kundo2_i18n("Set Vertical Alignment")); 0164 QTextCharFormat format; 0165 format.setVerticalAlignment(charAlign); 0166 mergeAutoStyle(format); 0167 d->updateState(KoTextEditor::Private::NoOp); 0168 } 0169 0170 void KoTextEditor::decreaseIndent() 0171 { 0172 if (isEditProtected()) { 0173 return; 0174 } 0175 0176 class Indenter : public BlockFormatVisitor 0177 { 0178 public: 0179 void visit(QTextBlock &block) const override { 0180 QTextBlockFormat format = block.blockFormat(); 0181 // TODO make the 10 configurable. 0182 format.setLeftMargin(qMax(qreal(0.0), format.leftMargin() - 10)); 0183 0184 if (block.textList()) { 0185 const QTextListFormat listFormat = block.textList()->format(); 0186 if (format.leftMargin() < listFormat.doubleProperty(KoListStyle::Margin)) { 0187 format.setLeftMargin(listFormat.doubleProperty(KoListStyle::Margin)); 0188 } 0189 } 0190 QTextCursor cursor(block); 0191 cursor.setBlockFormat(format); 0192 } 0193 Qt::Alignment alignment; 0194 }; 0195 0196 Indenter indenter; 0197 d->updateState(KoTextEditor::Private::Format, kundo2_i18n("Decrease Indent")); 0198 BlockFormatVisitor::visitSelection(this, indenter, kundo2_i18n("Decrease Indent")); 0199 d->updateState(KoTextEditor::Private::NoOp); 0200 emit textFormatChanged(); 0201 } 0202 0203 void KoTextEditor::increaseIndent() 0204 { 0205 if (isEditProtected()) { 0206 return; 0207 } 0208 0209 class Indenter : public BlockFormatVisitor 0210 { 0211 public: 0212 void visit(QTextBlock &block) const override { 0213 QTextBlockFormat format = block.blockFormat(); 0214 // TODO make the 10 configurable. 0215 0216 if (!block.textList()) { 0217 format.setLeftMargin(format.leftMargin() + 10); 0218 } else { 0219 const QTextListFormat listFormat = block.textList()->format(); 0220 if (format.leftMargin() == 0) { 0221 format.setLeftMargin(listFormat.doubleProperty(KoListStyle::Margin) + 10); 0222 } else { 0223 format.setLeftMargin(format.leftMargin() + 10); 0224 } 0225 } 0226 QTextCursor cursor(block); 0227 cursor.setBlockFormat(format); 0228 } 0229 Qt::Alignment alignment; 0230 }; 0231 0232 Indenter indenter; 0233 d->updateState(KoTextEditor::Private::Format, kundo2_i18n("Increase Indent")); 0234 BlockFormatVisitor::visitSelection(this, indenter, kundo2_i18n("Increase Indent")); 0235 d->updateState(KoTextEditor::Private::NoOp); 0236 emit textFormatChanged(); 0237 } 0238 0239 class FontResizer : public CharFormatVisitor 0240 { 0241 public: 0242 enum Type { Grow, Shrink }; 0243 FontResizer(Type type_) : type(type_) { 0244 QFontDatabase fontDB; 0245 defaultSizes = fontDB.standardSizes(); 0246 } 0247 void visit(QTextCharFormat &format) const override { 0248 const qreal current = format.fontPointSize(); 0249 int prev = 1; 0250 foreach(int pt, defaultSizes) { 0251 if ((type == Grow && pt > current) || (type == Shrink && pt >= current)) { 0252 format.setFontPointSize(type == Grow ? pt : prev); 0253 return; 0254 } 0255 prev = pt; 0256 } 0257 } 0258 0259 QList<int> defaultSizes; 0260 const Type type; 0261 }; 0262 0263 void KoTextEditor::decreaseFontSize() 0264 { 0265 if (isEditProtected()) { 0266 return; 0267 } 0268 0269 d->updateState(KoTextEditor::Private::Format, kundo2_i18n("Decrease font size")); 0270 FontResizer sizer(FontResizer::Shrink); 0271 CharFormatVisitor::visitSelection(this, sizer, kundo2_i18n("Decrease font size")); 0272 d->updateState(KoTextEditor::Private::NoOp); 0273 emit textFormatChanged(); 0274 } 0275 0276 void KoTextEditor::increaseFontSize() 0277 { 0278 if (isEditProtected()) { 0279 return; 0280 } 0281 0282 d->updateState(KoTextEditor::Private::Format, kundo2_i18n("Increase font size")); 0283 FontResizer sizer(FontResizer::Grow); 0284 CharFormatVisitor::visitSelection(this, sizer, kundo2_i18n("Increase font size")); 0285 d->updateState(KoTextEditor::Private::NoOp); 0286 emit textFormatChanged(); 0287 } 0288 0289 void KoTextEditor::setFontFamily(const QString &font) 0290 { 0291 if (isEditProtected()) { 0292 return; 0293 } 0294 0295 d->updateState(KoTextEditor::Private::Format, kundo2_i18n("Set Font")); 0296 QTextCharFormat format; 0297 format.setFontFamily(font); 0298 mergeAutoStyle(format); 0299 d->updateState(KoTextEditor::Private::NoOp); 0300 } 0301 0302 void KoTextEditor::setFontSize(qreal size) 0303 { 0304 if (isEditProtected()) { 0305 return; 0306 } 0307 0308 d->updateState(KoTextEditor::Private::Format, kundo2_i18n("Set Font Size")); 0309 QTextCharFormat format; 0310 format.setFontPointSize(size); 0311 mergeAutoStyle(format); 0312 d->updateState(KoTextEditor::Private::NoOp); 0313 } 0314 0315 void KoTextEditor::setTextBackgroundColor(const QColor &color) 0316 { 0317 if (isEditProtected()) { 0318 return; 0319 } 0320 0321 d->updateState(KoTextEditor::Private::Format, kundo2_i18n("Set Background Color")); 0322 QTextCharFormat format; 0323 format.setBackground(QBrush(color)); 0324 mergeAutoStyle(format); 0325 d->updateState(KoTextEditor::Private::NoOp); 0326 } 0327 0328 void KoTextEditor::setTextColor(const QColor &color) 0329 { 0330 if (isEditProtected()) { 0331 return; 0332 } 0333 0334 d->updateState(KoTextEditor::Private::Format, kundo2_i18n("Set Text Color")); 0335 QTextCharFormat format; 0336 format.setForeground(QBrush(color)); 0337 mergeAutoStyle(format); 0338 d->updateState(KoTextEditor::Private::NoOp); 0339 } 0340 0341 class SetCharacterStyleVisitor : public KoTextVisitor 0342 { 0343 public: 0344 SetCharacterStyleVisitor(KoTextEditor *editor, KoCharacterStyle *style) 0345 : KoTextVisitor(editor) 0346 , m_style(style) 0347 { 0348 } 0349 0350 void visitBlock(QTextBlock &block, const QTextCursor &caret) override 0351 { 0352 m_newFormat = block.charFormat(); 0353 m_style->applyStyle(m_newFormat); 0354 m_style->ensureMinimalProperties(m_newFormat); 0355 0356 KoTextVisitor::visitBlock(block, caret); 0357 0358 QList<QTextCharFormat>::Iterator it = m_formats.begin(); 0359 foreach(QTextCursor cursor, m_cursors) { 0360 QTextFormat prevFormat(cursor.charFormat()); 0361 cursor.setCharFormat(*it); 0362 editor()->registerTrackedChange(cursor, KoGenChange::FormatChange, kundo2_i18n("Set Character Style"), *it, prevFormat, false); 0363 ++it; 0364 } 0365 } 0366 0367 void visitFragmentSelection(QTextCursor &fragmentSelection) override 0368 { 0369 QTextCharFormat format = m_newFormat; 0370 0371 QVariant v; 0372 v = fragmentSelection.charFormat().property(KoCharacterStyle::InlineInstanceId); 0373 if (!v.isNull()) { 0374 format.setProperty(KoCharacterStyle::InlineInstanceId, v); 0375 } 0376 0377 v = fragmentSelection.charFormat().property(KoCharacterStyle::ChangeTrackerId); 0378 if (!v.isNull()) { 0379 format.setProperty(KoCharacterStyle::ChangeTrackerId, v); 0380 } 0381 0382 if (fragmentSelection.charFormat().isAnchor()) { 0383 format.setAnchor(true); 0384 format.setProperty(KoCharacterStyle::AnchorType, fragmentSelection.charFormat().intProperty(KoCharacterStyle::AnchorType)); 0385 format.setAnchorHref(fragmentSelection.charFormat().anchorHref()); 0386 } 0387 0388 m_formats.append(format); 0389 m_cursors.append(fragmentSelection); 0390 } 0391 0392 KoCharacterStyle *m_style; 0393 QTextCharFormat m_newFormat; 0394 QList<QTextCharFormat> m_formats; 0395 QList<QTextCursor> m_cursors; 0396 }; 0397 0398 void KoTextEditor::setStyle(KoCharacterStyle *style) 0399 { 0400 Q_ASSERT(style); 0401 d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Set Character Style")); 0402 0403 int caretAnchor = d->caret.anchor(); 0404 int caretPosition = d->caret.position(); 0405 0406 SetCharacterStyleVisitor visitor(this, style); 0407 0408 recursivelyVisitSelection(d->document->rootFrame()->begin(), visitor); 0409 0410 if (!isEditProtected() && caretAnchor == caretPosition) { //if there is no selection, it can happen that the caret does not get the proper style applied (beginning of a block). We need to force it. 0411 //applying a style is absolute, so first initialise the caret with the frame's style, then apply the paragraph's. Finally apply the character style 0412 QTextCharFormat charFormat = KoTextDocument(d->document).frameCharFormat(); 0413 KoStyleManager *styleManager = KoTextDocument(d->document).styleManager(); 0414 KoParagraphStyle *paragraphStyle = styleManager->paragraphStyle(d->caret.charFormat().intProperty(KoParagraphStyle::StyleId)); 0415 if (paragraphStyle) { 0416 paragraphStyle->KoCharacterStyle::applyStyle(charFormat); 0417 } 0418 d->caret.setCharFormat(charFormat); 0419 style->applyStyle(&(d->caret)); 0420 } 0421 else { //if the caret has a selection, the visitor has already applied the style, reset the caret's position so it picks the proper style. 0422 d->caret.setPosition(caretAnchor); 0423 d->caret.setPosition(caretPosition, QTextCursor::KeepAnchor); 0424 } 0425 0426 d->updateState(KoTextEditor::Private::NoOp); 0427 emit textFormatChanged(); 0428 emit characterStyleApplied(style); 0429 } 0430 0431 0432 class SetParagraphStyleVisitor : public KoTextVisitor 0433 { 0434 public: 0435 SetParagraphStyleVisitor(KoTextEditor *editor, KoStyleManager *styleManager, KoParagraphStyle *style) 0436 : KoTextVisitor(editor) 0437 , m_styleManager(styleManager) 0438 , m_style(style) 0439 { 0440 } 0441 0442 void visitBlock(QTextBlock &block, const QTextCursor &) override 0443 { 0444 if (m_styleManager) { 0445 QTextBlockFormat bf = block.blockFormat(); 0446 KoParagraphStyle *old = m_styleManager->paragraphStyle(bf.intProperty(KoParagraphStyle::StyleId)); 0447 if (old) 0448 old->unapplyStyle(block); 0449 } 0450 // The above should unapply the style and it's lists part, but we want to clear everything 0451 // except section info. 0452 QTextCursor cursor(block); 0453 QVariant sectionStartings = cursor.blockFormat().property(KoParagraphStyle::SectionStartings); 0454 QVariant sectionEndings = cursor.blockFormat().property(KoParagraphStyle::SectionEndings); 0455 QTextBlockFormat fmt; 0456 fmt.setProperty(KoParagraphStyle::SectionStartings, sectionStartings); 0457 fmt.setProperty(KoParagraphStyle::SectionEndings, sectionEndings); 0458 cursor.setBlockFormat(fmt); 0459 m_style->applyStyle(block); 0460 } 0461 0462 KoStyleManager *m_styleManager; 0463 KoParagraphStyle *m_style; 0464 }; 0465 0466 void KoTextEditor::setStyle(KoParagraphStyle *style) 0467 { 0468 d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Set Paragraph Style")); 0469 0470 int caretAnchor = d->caret.anchor(); 0471 int caretPosition = d->caret.position(); 0472 KoStyleManager *styleManager = KoTextDocument(d->document).styleManager(); 0473 SetParagraphStyleVisitor visitor(this, styleManager, style); 0474 0475 recursivelyVisitSelection(d->document->rootFrame()->begin(), visitor); 0476 0477 if (!isEditProtected() && caretAnchor == caretPosition) { //if there is no selection, it can happen that the caret does not get the proper style applied (beginning of a block). We need to force it. 0478 //applying a style is absolute, so first initialise the caret with the frame's style, then apply the paragraph style 0479 QTextCharFormat charFormat = KoTextDocument(d->document).frameCharFormat(); 0480 d->caret.setCharFormat(charFormat); 0481 style->KoCharacterStyle::applyStyle(&(d->caret)); 0482 } 0483 else { 0484 d->caret.setPosition(caretAnchor); 0485 d->caret.setPosition(caretPosition, QTextCursor::KeepAnchor); 0486 } 0487 0488 d->updateState(KoTextEditor::Private::NoOp); 0489 emit paragraphStyleApplied(style); 0490 emit textFormatChanged(); 0491 } 0492 0493 class MergeAutoCharacterStyleVisitor : public KoTextVisitor 0494 { 0495 public: 0496 MergeAutoCharacterStyleVisitor(KoTextEditor *editor, QTextCharFormat deltaCharFormat) 0497 : KoTextVisitor(editor) 0498 , m_deltaCharFormat(deltaCharFormat) 0499 { 0500 } 0501 0502 void visitBlock(QTextBlock &block, const QTextCursor &caret) override 0503 { 0504 KoTextVisitor::visitBlock(block, caret); 0505 0506 QList<QTextCharFormat>::Iterator it = m_formats.begin(); 0507 foreach(QTextCursor cursor, m_cursors) { 0508 QTextFormat prevFormat(cursor.charFormat()); 0509 cursor.setCharFormat(*it); 0510 ++it; 0511 } 0512 } 0513 0514 void visitFragmentSelection(QTextCursor &fragmentSelection) override 0515 { 0516 QTextCharFormat format = fragmentSelection.charFormat(); 0517 format.merge(m_deltaCharFormat); 0518 0519 m_formats.append(format); 0520 m_cursors.append(fragmentSelection); 0521 } 0522 0523 QTextCharFormat m_deltaCharFormat; 0524 QList<QTextCharFormat> m_formats; 0525 QList<QTextCursor> m_cursors; 0526 }; 0527 0528 void KoTextEditor::mergeAutoStyle(const QTextCharFormat &deltaCharFormat) 0529 { 0530 d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Formatting")); 0531 0532 int caretAnchor = d->caret.anchor(); 0533 int caretPosition = d->caret.position(); 0534 MergeAutoCharacterStyleVisitor visitor(this, deltaCharFormat); 0535 0536 recursivelyVisitSelection(d->document->rootFrame()->begin(), visitor); 0537 0538 if (!isEditProtected() && caretAnchor == caretPosition) { //if there is no selection, it can happen that the caret does not get the proper style applied (beginning of a block). We need to force it. 0539 d->caret.mergeCharFormat(deltaCharFormat); 0540 } 0541 else { 0542 d->caret.setPosition(caretAnchor); 0543 d->caret.setPosition(caretPosition, QTextCursor::KeepAnchor); 0544 } 0545 0546 d->updateState(KoTextEditor::Private::NoOp); 0547 emit textFormatChanged(); 0548 } 0549 0550 0551 void KoTextEditor::applyDirectFormatting(const QTextCharFormat &deltaCharFormat, 0552 const QTextBlockFormat &deltaBlockFormat, 0553 const KoListLevelProperties &llp) 0554 { 0555 addCommand(new ParagraphFormattingCommand(this, deltaCharFormat, deltaBlockFormat, llp)); 0556 emit textFormatChanged(); 0557 } 0558 0559 QTextCharFormat KoTextEditor::blockCharFormat() const 0560 { 0561 return d->caret.blockCharFormat(); 0562 } 0563 0564 QTextBlockFormat KoTextEditor::blockFormat() const 0565 { 0566 return d->caret.blockFormat(); 0567 } 0568 0569 QTextCharFormat KoTextEditor::charFormat() const 0570 { 0571 return d->caret.charFormat(); 0572 } 0573 0574 0575 void KoTextEditor::mergeBlockFormat(const QTextBlockFormat &modifier) 0576 { 0577 if (isEditProtected()) { 0578 return; 0579 } 0580 d->caret.mergeBlockFormat(modifier); 0581 emit textFormatChanged(); 0582 } 0583 0584 0585 void KoTextEditor::setBlockFormat(const QTextBlockFormat &format) 0586 { 0587 if (isEditProtected()) { 0588 return; 0589 } 0590 0591 Q_UNUSED(format) 0592 d->caret.setBlockFormat(format); 0593 emit textFormatChanged(); 0594 } 0595 0596 void KoTextEditor::setCharFormat(const QTextCharFormat &format) 0597 { 0598 if (isEditProtected()) { 0599 return; 0600 } 0601 0602 d->caret.setCharFormat(format); 0603 emit textFormatChanged(); 0604 }