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