1 /*
2  *  Copyright (c) 2012 C. Boemann <cbo@boemann.dk>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #include "ChangeStylesCommand.h"
21 
22 #include "KoList.h"
23 
24 #include <KoStyleManager.h>
25 #include <KoCharacterStyle.h>
26 #include <KoParagraphStyle.h>
27 #include <KoTextDocument.h>
28 #include <KoTextEditor.h>
29 
30 #include <QTextDocument>
31 #include <QTextCursor>
32 #include <QTextBlock>
33 
ChangeStylesCommand(QTextDocument * qDoc,const QList<KoCharacterStyle * > & origCharacterStyles,const QList<KoParagraphStyle * > & origParagraphStyles,const QSet<int> & changedStyles,KUndo2Command * parent)34 ChangeStylesCommand::ChangeStylesCommand(QTextDocument *qDoc
35         , const QList<KoCharacterStyle *> &origCharacterStyles
36         , const QList<KoParagraphStyle *> &origParagraphStyles
37         , const QSet<int> &changedStyles
38         , KUndo2Command *parent)
39     : KUndo2Command(kundo2_noi18n("stylechangecommand"),parent)
40     , m_origCharacterStyles(origCharacterStyles)
41     , m_origParagraphStyles(origParagraphStyles)
42     , m_changedStyles(changedStyles)
43     , m_document(qDoc)
44     , m_first(true)
45 {
46     // TODO optimization strategy;  store the formatid of the formats we checked into
47     // a qset for 'hits' and 'ignores' and avoid the copying of the format
48     // (fragment.charFormat() / block.blockFormat()) when the formatId is
49     // already checked previously
50 
51     KoStyleManager *sm = KoTextDocument(m_document).styleManager();
52     QTextCursor cursor(m_document);
53     QTextBlock block = cursor.block();
54     Memento *memento = new Memento;
55 
56     while (block.isValid()) {
57         memento->blockPosition = block.position();
58         memento->blockParentCharFormat = block.charFormat();
59         memento->blockParentFormat = KoTextDocument(m_document).frameBlockFormat();
60         memento->paragraphStyleId = 0;
61 
62         if (!memento->blockParentCharFormat.isTableCellFormat()) {
63             memento->blockParentCharFormat = KoTextDocument(m_document).frameCharFormat();
64         }
65 
66         bool blockChanged = false;
67         int id =  block.blockFormat().intProperty(KoParagraphStyle::StyleId);
68         if (id > 0 && changedStyles.contains(id)) {
69             KoParagraphStyle *style = sm->paragraphStyle(id);
70             Q_ASSERT(style);
71 
72             // Calculate block format of direct formatting.
73             memento->blockDirectFormat = block.blockFormat(); // frame + style + direct
74             style->applyStyle(memento->blockParentFormat);
75             clearCommonProperties(&memento->blockDirectFormat, memento->blockParentFormat);
76 
77             // Calculate char format of direct formatting.
78             memento->blockDirectCharFormat = block.charFormat(); // frame + style + direct
79             style->KoCharacterStyle::applyStyle(memento->blockParentCharFormat);
80             style->KoCharacterStyle::ensureMinimalProperties(memento->blockParentCharFormat);
81             clearCommonProperties(&memento->blockDirectCharFormat, memento->blockParentCharFormat);
82 
83             memento->paragraphStyleId = id;
84             blockChanged = true;
85         }
86 
87         QTextBlock::iterator iter = block.begin();
88         while (!iter.atEnd()) {
89             QTextFragment fragment = iter.fragment();
90             QTextCharFormat cf(fragment.charFormat());
91             id = cf.intProperty(KoCharacterStyle::StyleId);
92             if (blockChanged || (id > 0 && changedStyles.contains(id))) {
93                 // create selection
94                 cursor.setPosition(fragment.position());
95                 cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor);
96                 QTextCharFormat blockCharFormat = block.charFormat(); // with old parstyle applied
97 
98                 KoCharacterStyle *style = sm->characterStyle(id);
99                 if (style) {
100                     style->applyStyle(blockCharFormat);
101                     style->ensureMinimalProperties(blockCharFormat);
102                 }
103 
104                 clearCommonProperties(&cf, blockCharFormat);
105 
106                 memento->fragmentStyleId.append(id);
107                 memento->fragmentDirectFormats.append(cf);
108                 memento->fragmentCursors.append(cursor);
109             }
110             ++iter;
111         }
112         if (blockChanged || memento->fragmentCursors.length()) {
113             m_mementos.append(memento);
114             memento = new Memento;
115         }
116         block = block.next();
117     }
118 
119     delete memento; // we always have one that is unused
120 }
121 
~ChangeStylesCommand()122 ChangeStylesCommand::~ChangeStylesCommand()
123 {
124 }
125 
redo()126 void ChangeStylesCommand::redo()
127 {
128     KUndo2Command::redo();
129 
130     if (m_first) {
131         m_first = false;
132         KoStyleManager *sm = KoTextDocument(m_document).styleManager();
133 
134         QTextCursor cursor(m_document);
135         foreach (Memento *memento, m_mementos) {
136 
137             cursor.setPosition(memento->blockPosition);
138             QTextBlock block = cursor.block();
139 
140             if (memento->paragraphStyleId > 0) {
141                 KoParagraphStyle *style = sm->paragraphStyle(memento->paragraphStyleId);
142                 Q_ASSERT(style);
143 
144                 // apply paragraph style with direct formatting on top.
145                 style->applyStyle(memento->blockParentFormat);
146                 memento->blockParentFormat.merge(memento->blockDirectFormat);
147                 cursor.setBlockFormat(memento->blockParentFormat);
148 
149                 // apply list style formatting
150                 if (KoTextDocument(m_document).list(block.textList())) {
151                     if (style->list() == KoTextDocument(m_document).list(block.textList())) {
152                         style->applyParagraphListStyle(block, memento->blockParentFormat);
153                     }
154                 } else {
155                     style->applyParagraphListStyle(block, memento->blockParentFormat);
156                 }
157 
158                 // apply character style with direct formatting on top.
159                 style->KoCharacterStyle::applyStyle(memento->blockParentCharFormat);
160                 style->KoCharacterStyle::ensureMinimalProperties(memento->blockParentCharFormat);
161                 memento->blockParentCharFormat.merge(memento->blockDirectCharFormat);
162 
163                 cursor.setBlockCharFormat(memento->blockParentCharFormat);
164             }
165 
166             QList<QTextCharFormat>::Iterator fmtIt = memento->fragmentDirectFormats.begin();
167             QList<int>::Iterator idIt = memento->fragmentStyleId.begin();
168             Q_FOREACH (QTextCursor fragCursor, memento->fragmentCursors) {
169                 QTextCharFormat cf(block.charFormat()); // start with block formatting
170 
171                 if (*idIt > 0) {
172                     KoCharacterStyle *style = sm->characterStyle(*idIt);
173                     if (style) {
174                         style->applyStyle(cf); // possibly apply charstyle formatting
175                     }
176                 }
177 
178                 cf.merge(*fmtIt); //apply direct formatting
179 
180                 fragCursor.setCharFormat(cf);
181 
182                 ++idIt;
183                 ++fmtIt;
184             }
185         }
186         qDeleteAll(m_mementos);
187         m_mementos.clear();
188     }
189 }
190 
undo()191 void ChangeStylesCommand::undo()
192 {
193     KUndo2Command::undo();
194 }
195 
196 
clearCommonProperties(QTextFormat * firstFormat,const QTextFormat & secondFormat)197 void ChangeStylesCommand::clearCommonProperties(QTextFormat *firstFormat, const QTextFormat &secondFormat)
198 {
199     Q_ASSERT(firstFormat);
200     Q_FOREACH (int key, secondFormat.properties().keys()) {
201         if (firstFormat->property(key) == secondFormat.property(key)) {
202             firstFormat->clearProperty(key);
203         }
204     }
205 }
206 
207