Warning, file /office/calligra/libs/text/KoTextEditor_undo.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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 * 0007 * This library is free software; you can redistribute it and/or 0008 * modify it under the terms of the GNU Library General Public 0009 * License as published by the Free Software Foundation; either 0010 * version 2 of the License, or (at your option) any later version. 0011 * 0012 * This library is distributed in the hope that it will be useful, 0013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0015 * Library General Public License for more details. 0016 * 0017 * You should have received a copy of the GNU Library General Public License 0018 * along with this library; see the file COPYING.LIB. If not, write to 0019 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0020 * Boston, MA 02110-1301, USA. 0021 */ 0022 0023 #include "KoTextEditor.h" 0024 #include "KoTextEditor_p.h" 0025 0026 #include "KoTextDocument.h" 0027 0028 #include <kundo2command.h> 0029 0030 #include <klocalizedstring.h> 0031 0032 #include <QTextDocument> 0033 #include <QPointer> 0034 0035 #include "TextDebug.h" 0036 0037 /** Calligra's undo/redo framework. 0038 The @c KoTextEditor undo/redo framework sits between the @c QTextDocument and the application's undo/redo stack. 0039 0040 When the @c QTextDocument is changed by an editing action, it internally creates an undo/redo command. When doing so a signal (undoCommandAdded()) is emitted by the @c QTextDocument in order for applications to update their undo/redo stack accordingly. 0041 Each @c QTextDocument used in Calligra is handled by a specific @c KoTextEditor. It is responsible for on the one hand edit the @c QTextDocument, and on the other hand to listen for the QTextDocument's signal. 0042 0043 Calligra uses a @c KUndo2Stack as its application undo/redo stack. This stack is populated by @c KUndo2Command or sub-classes of it. 0044 0045 In order to limit the number of command sub-classes, KoTextEditor provides a framework which uses a generic command. 0046 0047 The framework is based on a sort of state machine. The KoTextEditor can be in several different states (see KoTextEditor::Private::State). 0048 These are: 0049 NoOp: this states indicates that the KoTextEditor is not editing the QTextDocument. 0050 KeyPress: this state indicates that the user is typing text. All text typed in succession should correspond to one undo command. To be used when entering text outside of an insertTextCommand. 0051 Delete: this state indicates that the user is deleting characters. All deletions done in succession should correspond to one undo command. To be used for deleting outside a deleteCommand. Currently not in use, our deletion is done through a command because of inline objects. 0052 Format: this state indicates that we are formatting text. To be used when formatting outside of a command. 0053 Custom: this state indicates that the QTextDocument is changed through a KUndo2Command. 0054 0055 KoTextEditor reacts differently when receiving the QTextDocument's signal, depending on its state. 0056 0057 In addition the framework allows to encapsulate modifications in a on-the-fly created custom command (\sa beginEditBlock() endEditBlock()). 0058 Furthermore the framework allows to push complete KUndo2Commands. 0059 0060 See the documentation file for how to use this framework. 0061 */ 0062 0063 /* 0064 Important members: 0065 0066 commandStack: This stack holds the headCommands. These parent the generated UndoTextCommands. When undo or redo is called, they will in turn call UndoTextCommand::undo/redo. 0067 0068 editorState: Holds the state of the KoTextEditor. see above 0069 0070 commandTitle: Holds the title which is to be used when creating a headCommand. 0071 0072 addNewCommand: bool used to tell the framework to create a new headCommand and push it on the commandStack, when receiving an undoCommandAdded signal from QTextDocument. 0073 0074 customCommandCount: counter used to keep track of nested KUndo2Commands that are pushed on the KoTextEditor. 0075 */ 0076 0077 0078 // This slot is called when the KoTextEditor receives the signal undoCommandAdded() from QTextDocument. A generic UndoTextCommand is used to match the QTextDocument's internal undo command. This command calls QTextDocument::undo() or QTextDocument::redo() respectively, which triggers the undo redo actions on the document. 0079 //In order to allow nesting commands, we maintain a stack of commands. The top command will be the parent of our auto generated UndoTextCommands. 0080 //Depending on the case, we might create a command which will serve as head command. This is pushed to our commandStack and eventually to the application's stack. 0081 void KoTextEditor::Private::documentCommandAdded() 0082 { 0083 class UndoTextCommand : public KUndo2Command 0084 { 0085 public: 0086 UndoTextCommand(QTextDocument *document, KoTextEditor::Private *p, KUndo2Command *parent = 0) 0087 : KUndo2Command(kundo2_i18n("Text"), parent), 0088 m_document(document) 0089 , m_p(p) 0090 {} 0091 0092 void undo() override { 0093 QTextDocument *doc = m_document.data(); 0094 if (doc == 0) 0095 return; 0096 doc->undo(KoTextDocument(doc).textEditor()->cursor()); 0097 m_p->emitTextFormatChanged(); 0098 } 0099 0100 void redo() override { 0101 QTextDocument *doc = m_document.data(); 0102 if (doc == 0) 0103 return; 0104 doc->redo(KoTextDocument(doc).textEditor()->cursor()); 0105 m_p->emitTextFormatChanged(); 0106 } 0107 0108 QPointer<QTextDocument> m_document; 0109 KoTextEditor::Private *m_p; 0110 }; 0111 0112 debugText << "received a QTextDocument undoCommand signal"; 0113 debugText << "commandStack count: " << commandStack.count(); 0114 debugText << "addCommand: " << addNewCommand; 0115 debugText << "editorState: " << editorState; 0116 if (commandStack.isEmpty()) { 0117 //We have an empty stack. We need a head command which is to be pushed onto our commandStack and on the application stack if there is one. 0118 //This command will serve as a parent for the auto-generated UndoTextCommands. 0119 debugText << "empty stack, will push a new headCommand on both commandStack and application's stack. title: " << commandTitle; 0120 commandStack.push(new KUndo2Command(commandTitle)); 0121 if (KoTextDocument(document).undoStack()) { 0122 KoTextDocument(document).undoStack()->push(commandStack.top()); 0123 } 0124 addNewCommand = false; 0125 debugText << "commandStack is now: " << commandStack.count(); 0126 } 0127 else if (addNewCommand) { 0128 //We have already a headCommand on the commandStack. However we want a new child headCommand (nested commands) on the commandStack for parenting further UndoTextCommands. This shouldn't be pushed on the application's stack because it is a child of the current commandStack's top. 0129 debugText << "we have a headCommand on the commandStack but need a new child command. we will push it only on the commandStack: " << commandTitle; 0130 commandStack.push(new KUndo2Command(commandTitle, commandStack.top())); 0131 addNewCommand = false; 0132 debugText << "commandStack count is now: " << commandStack.count(); 0133 } 0134 else if ((editorState == KeyPress || editorState == Delete) && !commandStack.isEmpty() && commandStack.top()->childCount()) { 0135 //QTextDocument emits a signal on the first key press (or delete) and "merges" the subsequent ones, until an incompatible one is done. In which case it re-emit a signal. 0136 //Here we are in KeyPress (or Delete) state. The fact that the commandStack isn't empty and its top command has children means that we just received such a signal. We therefore need to pop the previous headCommand (which were either key press or delete) and create a new one to parent the UndoTextCommands. This command also need to be pushed on the application's stack. 0137 debugText << "we are in subsequent keyPress/delete state and still received a signal. we need to create a new headCommand: " << commandTitle; 0138 debugText << "so we pop the current one and push the new one on both the commandStack and the application's stack"; 0139 commandStack.pop(); 0140 commandStack.push(new KUndo2Command(commandTitle, !commandStack.isEmpty()?commandStack.top():0)); 0141 if (KoTextDocument(document).undoStack()) { 0142 KoTextDocument(document).undoStack()->push(commandStack.top()); 0143 } 0144 debugText << "commandStack count: " << commandStack.count(); 0145 } 0146 0147 //Now we can create our UndoTextCommand which is parented to the commandStack't top headCommand. 0148 new UndoTextCommand(document, this, commandStack.top()); 0149 debugText << "done creating the dummy UndoTextCommand"; 0150 } 0151 0152 //This method is used to update the KoTextEditor state, which will condition how the QTextDocument::undoCommandAdded signal will get handled. 0153 void KoTextEditor::Private::updateState(KoTextEditor::Private::State newState, const KUndo2MagicString &title) 0154 { 0155 debugText << "updateState from: " << editorState << " to: " << newState << " with: " << title; 0156 debugText << "commandStack count: " << commandStack.count(); 0157 if (editorState == Custom && newState != NoOp) { 0158 //We already are in a custom state (meaning that either a KUndo2Command was pushed on us, an on-the-fly macro command was started or we are executing a complex editing from within the KoTextEditor. 0159 //In that state any update of the state different from NoOp is part of that "macro". However, updating the state means that we are now wanting to have a new command for parenting the UndoTextCommand generated after the signal 0160 //from QTextDocument. This is to ensure that undo/redo actions are done in the proper order. Setting addNewCommand will ensure that we create such a child headCommand on the commandStack. This command will not be pushed on the application's stack. 0161 debugText << "we are already in a custom state. a new state, which is not NoOp is part of the macro we are doing. we need however to create a new command on the commandStack to parent a signal induced UndoTextCommand"; 0162 addNewCommand = true; 0163 if (!title.isEmpty()) 0164 commandTitle = title; 0165 else 0166 commandTitle = kundo2_i18n("Text"); 0167 debugText << "returning now. commandStack is not modified at this stage"; 0168 return; 0169 } 0170 if (newState == NoOp && !commandStack.isEmpty()) { 0171 //Calling updateState to NoOp when the commandStack isn't empty means that the current headCommand on the commandStack is finished. Further UndoTextCommands do not belong to it. So we pop it. 0172 //If after poping the headCommand we still have some commands on the commandStack means we have not finished with the highest "macro". In that case we need to stay in the "Custom" state. 0173 //On the contrary, an empty commandStack means we have finished with the "macro". In that case, we set the editor to NoOp state. A signal from the QTextDocument should also generate a new headCommand. 0174 debugText << "we are in a macro and update the state to NoOp. this means that the command on top of the commandStack is finished. we should pop it"; 0175 debugText << "commandStack count before: " << commandStack.count(); 0176 commandStack.pop(); 0177 debugText << "commandStack count after: " << commandStack.count(); 0178 if (commandStack.isEmpty()) { 0179 debugText << "we have no more commands on the commandStack. the macro is complete. next signal induced command will need to be parented to a new headCommand. Also the editor should go to NoOp"; 0180 addNewCommand = true; 0181 editorState = NoOp; 0182 } 0183 debugText << "returning now. commandStack count: " << commandStack.count(); 0184 return; 0185 } 0186 if (editorState != newState || commandTitle != title) { 0187 //We are not in "Custom" state but either are moving to a new state (from editing to format,...) or the command type is the same, but not the command itself (like format:bold, format:italic). The later case is caracterised by a different command title. 0188 //When we change command, we need to pop the current commandStack's top and ask for a new headCommand to be created. 0189 debugText << "we are not in a custom state but change the command"; 0190 debugText << "commandStack count: " << commandStack.count(); 0191 if (!commandStack.isEmpty()) { 0192 debugText << "the commandStack is not empty. however the command on it is not a macro. so we pop it and ask to recreate a new one: " << title; 0193 commandStack.pop(); 0194 addNewCommand = true; 0195 } 0196 } 0197 editorState = newState; 0198 if (!title.isEmpty()) 0199 commandTitle = title; 0200 else 0201 commandTitle = kundo2_i18n("Text"); 0202 debugText << "returning now. commandStack count: " << commandStack.count(); 0203 } 0204 0205 /// This method is used to push a complete KUndo2Command on the KoTextEditor. This command will be pushed on the application's stack if needed. The logic allows to push several commands which are then going to be nested, provided these children are pushed from within the redo method of their parent. 0206 void KoTextEditor::addCommand(KUndo2Command *command) 0207 { 0208 debugText << "we receive a command to add on the stack."; 0209 debugText << "commandStack count: " << d->commandStack.count(); 0210 debugText << "customCommandCount counter: " << d->customCommandCount << " will increase"; 0211 0212 //We increase the customCommandCount counter to inform the framework that we are having a further KUndo2Command and update the KoTextEditor's state to Custom. 0213 //However, this update will request a new headCommand to be pushed on the commandStack. This is what we want for internal complex editions but not in this case. Indeed, it must be the KUndo2Command which will parent the UndoTextCommands. Therefore we set the addNewCommand back to false. 0214 //If the commandStack is empty, we are the highest "macro" command and we should therefore push the KUndo2Command on the application's stack. 0215 //On the contrary, if the commandStack is not empty, or the pushed command has a parent, it means that we are adding a nested KUndo2Command. In which case we just want to put it on the commandStack to parent UndoTextCommands. We need to call the redo method manually though. 0216 ++d->customCommandCount; 0217 debugText << "we will now go to custom state"; 0218 d->updateState(KoTextEditor::Private::Custom, (!command->text().isEmpty())?command->text():kundo2_i18n("Text")); 0219 debugText << "but will set the addCommand to false. we don't want a new headCommand"; 0220 d->addNewCommand = false; 0221 debugText << "commandStack count is: " << d->commandStack.count(); 0222 if (d->commandStack.isEmpty()) { 0223 debugText << "the commandStack is empty. this means we are the top most command"; 0224 d->commandStack.push(command); 0225 debugText << "command pushed on the commandStack. count: " << d->commandStack.count(); 0226 KUndo2QStack *stack = KoTextDocument(d->document).undoStack(); 0227 if (stack && !command->hasParent()) { 0228 debugText << "we have an application stack and the command is not a sub command of a non text command (which have been pushed outside kotext"; 0229 stack->push(command); 0230 debugText << "so we pushed it on the application's' stack"; 0231 } else { 0232 debugText << "we either have no application's stack, or our command is actually the child of a non kotext command"; 0233 command->redo(); 0234 debugText << "still called redo on it"; 0235 } 0236 } 0237 else { 0238 debugText << "the commandStack is not empty, our command is actually nested in another kotext command. we don't push on the application stack but only on the commandStack"; 0239 d->commandStack.push(command); 0240 debugText << "commandStack count after push: " << d->commandStack.count(); 0241 command->redo(); 0242 debugText << "called redo still"; 0243 } 0244 0245 //When we reach that point, the command has been executed. We first need to clean up all the automatically generated headCommand on our commandStack, which could potentially have been created during the editing. When we reach our pushed command, the commandStack is clean. We can then call a state update to NoOp and decrease the customCommandCount counter. 0246 debugText << "the command has been executed. we need to clean up the commandStack of the auto generated headCommands"; 0247 debugText << "before cleaning. commandStack count: " << d->commandStack.count(); 0248 while (d->commandStack.top() != command) { 0249 d->commandStack.pop(); 0250 } 0251 debugText << "after cleaning. commandStack count: " << d->commandStack.count() << " will set NoOp"; 0252 d->updateState(KoTextEditor::Private::NoOp); 0253 debugText << "after NoOp set. inCustomCounter: " << d->customCommandCount << " will decrease and return"; 0254 --d->customCommandCount; 0255 } 0256 0257 /// DO NOT USE THIS. It stays here for compiling reasons. But it will severely break everything. Again: DO NOT USE THIS. 0258 void KoTextEditor::instantlyExecuteCommand(KUndo2Command *command) 0259 { 0260 d->updateState(KoTextEditor::Private::Custom, (!command->text().isEmpty())?command->text():kundo2_i18n("Text")); 0261 command->redo(); 0262 // instant replay done let's not keep it dangling 0263 if (!command->hasParent()) { 0264 d->updateState(KoTextEditor::Private::NoOp); 0265 } 0266 } 0267 0268 /// This method is used to start an on-the-fly macro command. Use KoTextEditor::endEditBlock to stop it. 0269 /// *** 0270 /// Important note: 0271 /// *** 0272 /// The framework does not allow to push a complete KUndo2Command (through KoTextEditor::addCommand) from within an EditBlock. Doing so will lead in the best case to several undo/redo commands on the application's stack instead of one, in the worst case to an out of sync application's stack. 0273 /// *** 0274 KUndo2Command *KoTextEditor::beginEditBlock(const KUndo2MagicString &title) 0275 { 0276 debugText << "beginEditBlock"; 0277 debugText << "commandStack count: " << d->commandStack.count(); 0278 debugText << "customCommandCount counter: " << d->customCommandCount; 0279 if (!d->customCommandCount) { 0280 // We are not in a custom macro command. So we first need to update the KoTextEditor's state to Custom. Additionally, if the commandStack is empty, we need to create a master headCommand for our macro and push it on the stack. 0281 debugText << "we are not in a custom command. will update state to custom"; 0282 d->updateState(KoTextEditor::Private::Custom, title); 0283 debugText << "commandStack count: " << d->commandStack.count(); 0284 if (d->commandStack.isEmpty()) { 0285 debugText << "the commandStack is empty. we need a dummy headCommand both on the commandStack and on the application's stack"; 0286 KUndo2Command *command = new KUndo2Command(title); 0287 d->commandStack.push(command); 0288 ++d->customCommandCount; 0289 d->dummyMacroAdded = true; //This bool is used to tell endEditBlock that we have created a master headCommand. 0290 KUndo2QStack *stack = KoTextDocument(d->document).undoStack(); 0291 if (stack) { 0292 stack->push(command); 0293 } else { 0294 command->redo(); 0295 } 0296 debugText << "done adding the headCommand. commandStack count: " << d->commandStack.count() << " inCommand counter: " << d->customCommandCount; 0297 } 0298 } 0299 //QTextDocument sends the undoCommandAdded signal at the end of the QTextCursor edit block. Since we want our master headCommand to parent the signal induced UndoTextCommands, we should not call QTextCursor::beginEditBlock for the headCommand. 0300 if (!(d->dummyMacroAdded && d->customCommandCount == 1)) { 0301 debugText << "we did not add a dummy command, or we are further down nesting. call beginEditBlock on the caret to nest the QTextDoc changes"; 0302 //we don't call beginEditBlock for the first headCommand because we want the signals to be sent before we finished our command. 0303 d->caret.beginEditBlock(); 0304 } 0305 debugText << "will return top od commandStack"; 0306 return (d->commandStack.isEmpty())?0:d->commandStack.top(); 0307 } 0308 0309 void KoTextEditor::endEditBlock() 0310 { 0311 debugText << "endEditBlock"; 0312 //Only the self created master headCommand (see beginEditBlock) is left on the commandStack, we need to decrease the customCommandCount counter that we increased on creation. 0313 //If we are not yet at this master headCommand, we can call QTextCursor::endEditBlock 0314 if (d->dummyMacroAdded && d->customCommandCount == 1) { 0315 debugText << "only the created dummy headCommand from beginEditBlock is left. we need to decrease further the nesting counter"; 0316 //we don't call caret.endEditBlock because we did not begin a block for the first headCommand 0317 --d->customCommandCount; 0318 d->dummyMacroAdded = false; 0319 } else { 0320 debugText << "we are not at our top dummy headCommand. call caret.endEditBlock"; 0321 d->caret.endEditBlock(); 0322 } 0323 if (!d->customCommandCount) { 0324 //We have now finished completely the macro, set the editor state to NoOp then. 0325 debugText << "we have finished completely the macro, set the state to NoOp now. commandStack count: " << d->commandStack.count(); 0326 d->updateState(KoTextEditor::Private::NoOp); 0327 debugText << "done setting the state. editorState: " << d->editorState << " commandStack count: " << d->commandStack.count(); 0328 } 0329 }