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 }