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 }