1 /* This file is part of the KDE project
2 * Copyright (C) 2009 Pierre Stirnweiss <pstirnweiss@googlemail.com>
3 * Copyright (C) 2009 Thomas Zander <zander@kde.org>
4 * Copyright (C) 2015 Soma Schliszka <soma.schliszka@gmail.com>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB.  If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21 
22 #ifndef KOTEXTEDITOR_P_H
23 #define KOTEXTEDITOR_P_H
24 
25 #include "KoTextEditor.h"
26 
27 #include "KoTextDocument.h"
28 #include "styles/KoParagraphStyle.h"
29 #include "styles/KoStyleManager.h"
30 #include "changetracker/KoChangeTracker.h"
31 
32 #include <klocalizedstring.h>
33 #include <kundo2magicstring.h>
34 
35 #include <QStack>
36 #include <QTextBlock>
37 #include <QTextDocument>
38 #include <QTextTableCell>
39 #include <QTimer>
40 
41 class KUndo2Command;
42 
43 class Q_DECL_HIDDEN KoTextEditor::Private
44 {
45 public:
46     enum State {
47         NoOp,
48         KeyPress,
49         Delete,
50         Format,
51         Custom
52     };
53 
54     explicit Private(KoTextEditor *qq, QTextDocument *document);
55 
~Private()56     ~Private() {}
57 
58     void documentCommandAdded();
59     void updateState(State newState, const KUndo2MagicString &title = KUndo2MagicString());
60 
61     void newLine(KUndo2Command *parent);
62     void clearCharFormatProperty(int propertyId);
63 
64     void emitTextFormatChanged();
65 
66     KoTextEditor *q;
67     QTextCursor caret;
68     QTextDocument *document;
69     QStack<KUndo2Command*> commandStack;
70     bool addNewCommand;
71     bool dummyMacroAdded;
72     int customCommandCount;
73     KUndo2MagicString commandTitle;
74 
75     State editorState;
76 
77     bool editProtected;
78     bool editProtectionCached;
79 };
80 
81 class KoTextVisitor
82 {
83 public:
84     /// The ObjectVisitingMode enum marks how was the visited object selected.
85     enum ObjectVisitingMode {
86         Partly,     /// The visited object (table, cell, ...) is just @b partly selected. (Eg. just one cell is selected in the visited table)
87         Entirely,   /// The visited object (table, cell, ...) is @b entirely selected.
88     };
89 
KoTextVisitor(KoTextEditor * editor)90     explicit KoTextVisitor(KoTextEditor *editor)
91         : m_abortVisiting(false)
92         , m_editor(editor)
93     {
94     }
95 
~KoTextVisitor()96     virtual ~KoTextVisitor() {}
97     // called whenever a visit was prevented by editprotection
nonVisit()98     virtual void nonVisit() {}
99 
visitFragmentSelection(QTextCursor &)100     virtual void visitFragmentSelection(QTextCursor &)
101     {
102     }
103 
104     /**
105      * This method allows to perform custom operation when the visitor reaches a QTextTable
106      * @param visitedTable pointer to the currently visited table object
107      * @param visitingMode flag, marks if the table is just partly visited or entirely
108      */
visitTable(QTextTable * visitedTable,ObjectVisitingMode visitingMode)109     virtual void visitTable(QTextTable *visitedTable, ObjectVisitingMode visitingMode)
110     {
111         Q_UNUSED(visitedTable);
112         Q_UNUSED(visitingMode);
113     }
114 
115     /**
116      * This method allows to perform custom operation when the visitor reaches a QTextTableCell
117      * @param visitedCell pointer to the currently visited cell object
118      * @param visitingMode flag, marks if the cell is just partly visited or entirely
119      */
visitTableCell(QTextTableCell * visitedCell,ObjectVisitingMode visitingMode)120     virtual void visitTableCell(QTextTableCell *visitedCell, ObjectVisitingMode visitingMode)
121     {
122         Q_UNUSED(visitedCell);
123         Q_UNUSED(visitingMode);
124     }
125 
126     // The default implementation calls visitFragmentSelection on each fragment.intersect.selection
visitBlock(QTextBlock & block,const QTextCursor & caret)127     virtual void visitBlock(QTextBlock &block, const QTextCursor &caret)
128     {
129         for (QTextBlock::iterator it = block.begin(); it != block.end(); ++it) {
130             QTextCursor fragmentSelection(caret);
131             fragmentSelection.setPosition(qMax(caret.selectionStart(), it.fragment().position()));
132             fragmentSelection.setPosition(qMin(caret.selectionEnd(), it.fragment().position() + it.fragment().length()), QTextCursor::KeepAnchor);
133 
134             if (fragmentSelection.anchor() >= fragmentSelection.position()) {
135                 continue;
136             }
137 
138             visitFragmentSelection(fragmentSelection);
139         }
140     }
141 
abortVisiting()142     bool abortVisiting() { return m_abortVisiting;}
setAbortVisiting(bool abort)143     void setAbortVisiting(bool abort) {m_abortVisiting = abort;}
editor()144     KoTextEditor * editor() const {return m_editor;}
145 private:
146     bool m_abortVisiting;
147     KoTextEditor *m_editor;
148 };
149 
150 class BlockFormatVisitor
151 {
152 public:
BlockFormatVisitor()153     BlockFormatVisitor() {}
~BlockFormatVisitor()154     virtual ~BlockFormatVisitor() {}
155 
156     virtual void visit(QTextBlock &block) const = 0;
157 
158     static void visitSelection(KoTextEditor *editor, const BlockFormatVisitor &visitor, const KUndo2MagicString &title = kundo2_i18n("Format"), bool resetProperties = false, bool registerChange = true) {
159         int start = qMin(editor->position(), editor->anchor());
160         int end = qMax(editor->position(), editor->anchor());
161 
162         QTextBlock block = editor->block();
163         if (block.position() > start)
164             block = block.document()->findBlock(start);
165 
166         // now loop over all blocks that the selection contains and alter the text fragments where applicable.
167         while (block.isValid() && block.position() <= end) {
168             QTextBlockFormat prevFormat = block.blockFormat();
169             if (resetProperties) {
170                 if (KoTextDocument(editor->document()).styleManager()) {
171                     KoParagraphStyle *old = KoTextDocument(editor->document()).styleManager()->paragraphStyle(block.blockFormat().intProperty(KoParagraphStyle::StyleId));
172                     if (old)
173                         old->unapplyStyle(block);
174                 }
175             }
176             visitor.visit(block);
177             QTextCursor cursor(block);
178             QTextBlockFormat format = cursor.blockFormat();
179             if (registerChange)
180                 editor->registerTrackedChange(cursor, KoGenChange::FormatChange, title, format, prevFormat, true);
181             block = block.next();
182         }
183     }
184 };
185 
186 class CharFormatVisitor
187 {
188 public:
CharFormatVisitor()189     CharFormatVisitor() {}
~CharFormatVisitor()190     virtual ~CharFormatVisitor() {}
191 
192     virtual void visit(QTextCharFormat &format) const = 0;
193 
194     static void visitSelection(KoTextEditor *editor, const CharFormatVisitor &visitor, const KUndo2MagicString &title = kundo2_i18n("Format"), bool registerChange = true) {
195         int start = qMin(editor->position(), editor->anchor());
196         int end = qMax(editor->position(), editor->anchor());
197         if (start == end) { // just set a new one.
198             QTextCharFormat format = editor->charFormat();
199             visitor.visit(format);
200 
201             if (registerChange && KoTextDocument(editor->document()).changeTracker() && KoTextDocument(editor->document()).changeTracker()->recordChanges()) {
202                 QTextCharFormat prevFormat(editor->charFormat());
203 
204                 int changeId = KoTextDocument(editor->document()).changeTracker()->getFormatChangeId(title, format, prevFormat, editor->charFormat().property( KoCharacterStyle::ChangeTrackerId ).toInt());
205                 format.setProperty(KoCharacterStyle::ChangeTrackerId, changeId);
206             }
207 
208             editor->cursor()->setCharFormat(format);
209             return;
210         }
211 
212         QTextBlock block = editor->block();
213         if (block.position() > start)
214             block = block.document()->findBlock(start);
215 
216         QList<QTextCursor> cursors;
217         QList<QTextCharFormat> formats;
218         // now loop over all blocks that the selection contains and alter the text fragments where applicable.
219         while (block.isValid() && block.position() < end) {
220             QTextBlock::iterator iter = block.begin();
221             while (! iter.atEnd()) {
222                 QTextFragment fragment = iter.fragment();
223                 if (fragment.position() > end)
224                     break;
225                 if (fragment.position() + fragment.length() <= start) {
226                     ++iter;
227                     continue;
228                 }
229 
230                 QTextCursor cursor(block);
231                 cursor.setPosition(fragment.position() + 1);
232                 QTextCharFormat format = cursor.charFormat(); // this gets the format one char after the position.
233                 visitor.visit(format);
234 
235                 if (registerChange && KoTextDocument(editor->document()).changeTracker() && KoTextDocument(editor->document()).changeTracker()->recordChanges()) {
236                     QTextCharFormat prevFormat(cursor.charFormat());
237 
238                     int changeId = KoTextDocument(editor->document()).changeTracker()->getFormatChangeId(title, format, prevFormat, cursor.charFormat().property( KoCharacterStyle::ChangeTrackerId ).toInt());
239                     format.setProperty(KoCharacterStyle::ChangeTrackerId, changeId);
240                 }
241 
242                 cursor.setPosition(qMax(start, fragment.position()));
243                 int to = qMin(end, fragment.position() + fragment.length());
244                 cursor.setPosition(to, QTextCursor::KeepAnchor);
245                 cursors.append(cursor);
246                 formats.append(format);
247 
248                 QTextCharFormat prevFormat(cursor.charFormat());
249                 if (registerChange)
250                     editor->registerTrackedChange(cursor,KoGenChange::FormatChange,title, format, prevFormat, false); //this will lead to every fragment having a different change until the change merging in registerTrackedChange checks also for formatChange or not?
251 
252                 ++iter;
253             }
254             block = block.next();
255         }
256         QList<QTextCharFormat>::Iterator iter = formats.begin();
Q_FOREACH(QTextCursor cursor,cursors)257         Q_FOREACH (QTextCursor cursor, cursors) {
258             cursor.setCharFormat(*iter);
259             ++iter;
260         }
261     }
262 };
263 
264 #endif //KOTEXTEDITOR_P_H
265