1 /*
2  Copyright (C) 2010-2014 Kristian Duske
3 
4  This file is part of TrenchBroom.
5 
6  TrenchBroom is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  TrenchBroom 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
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with TrenchBroom. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "CommandProcessor.h"
21 
22 #include "Exceptions.h"
23 #include "SetAny.h"
24 #include "View/MapDocumentCommandFacade.h"
25 
26 #include <wx/time.h>
27 
28 #include <algorithm>
29 
30 namespace TrenchBroom {
31     namespace View {
32         const Command::CommandType CommandGroup::Type = Command::freeType();
33 
CommandGroup(const String & name,const CommandList & commands,Notifier1<Command::Ptr> & commandDoNotifier,Notifier1<Command::Ptr> & commandDoneNotifier,Notifier1<UndoableCommand::Ptr> & commandUndoNotifier,Notifier1<UndoableCommand::Ptr> & commandUndoneNotifier)34         CommandGroup::CommandGroup(const String& name, const CommandList& commands,
35                                    Notifier1<Command::Ptr>& commandDoNotifier,
36                                    Notifier1<Command::Ptr>& commandDoneNotifier,
37                                    Notifier1<UndoableCommand::Ptr>& commandUndoNotifier,
38                                    Notifier1<UndoableCommand::Ptr>& commandUndoneNotifier) :
39         UndoableCommand(Type, name),
40         m_commands(commands),
41         m_commandDoNotifier(commandDoNotifier),
42         m_commandDoneNotifier(commandDoneNotifier),
43         m_commandUndoNotifier(commandUndoNotifier),
44         m_commandUndoneNotifier(commandUndoneNotifier) {}
45 
doPerformDo(MapDocumentCommandFacade * document)46         bool CommandGroup::doPerformDo(MapDocumentCommandFacade* document) {
47             CommandList::iterator it, end;
48             for (it = m_commands.begin(), end = m_commands.end(); it != end; ++it) {
49                 UndoableCommand::Ptr command = *it;
50                 m_commandDoNotifier(command);
51                 if (!command->performDo(document))
52                     throw CommandProcessorException("Partial failure while executing command group");
53                 m_commandDoneNotifier(command);
54             }
55             return true;
56         }
57 
doPerformUndo(MapDocumentCommandFacade * document)58         bool CommandGroup::doPerformUndo(MapDocumentCommandFacade* document) {
59             CommandList::reverse_iterator it, end;
60             for (it = m_commands.rbegin(), end = m_commands.rend(); it != end; ++it) {
61                 UndoableCommand::Ptr command = *it;
62                 m_commandUndoNotifier(command);
63                 if (!command->performUndo(document))
64                     throw CommandProcessorException("Partial failure while undoing command group");
65                 m_commandUndoneNotifier(command);
66             }
67             return true;
68         }
69 
doIsRepeatDelimiter() const70         bool CommandGroup::doIsRepeatDelimiter() const {
71             CommandList::const_iterator it, end;
72             for (it = m_commands.begin(), end = m_commands.end(); it != end; ++it) {
73                 UndoableCommand::Ptr command = *it;
74                 if (command->isRepeatDelimiter())
75                     return true;
76             }
77             return false;
78         }
79 
doIsRepeatable(MapDocumentCommandFacade * document) const80         bool CommandGroup::doIsRepeatable(MapDocumentCommandFacade* document) const {
81             CommandList::const_iterator it, end;
82             for (it = m_commands.begin(), end = m_commands.end(); it != end; ++it) {
83                 UndoableCommand::Ptr command = *it;
84                 if (!command->isRepeatable(document))
85                     return false;
86             }
87             return true;
88         }
89 
doRepeat(MapDocumentCommandFacade * document) const90         UndoableCommand::Ptr CommandGroup::doRepeat(MapDocumentCommandFacade* document) const {
91             CommandList clones;
92             CommandList::const_iterator it, end;
93             for (it = m_commands.begin(), end = m_commands.end(); it != end; ++it) {
94                 UndoableCommand::Ptr command = *it;
95                 assert(command->isRepeatable(document));
96                 UndoableCommand::Ptr clone = command->repeat(document);
97                 clones.push_back(clone);
98             }
99             return UndoableCommand::Ptr(new CommandGroup(name(), clones, m_commandDoNotifier, m_commandDoneNotifier, m_commandUndoNotifier, m_commandUndoneNotifier));
100         }
101 
doCollateWith(UndoableCommand::Ptr command)102         bool CommandGroup::doCollateWith(UndoableCommand::Ptr command) {
103             return false;
104         }
105 
106         const wxLongLong CommandProcessor::CollationInterval(1000);
107 
108         struct CommandProcessor::SubmitAndStoreResult {
109             bool submitted;
110             bool stored;
111 
SubmitAndStoreResultTrenchBroom::View::CommandProcessor::SubmitAndStoreResult112             SubmitAndStoreResult() :
113             submitted(false),
114             stored(false) {}
115         };
116 
CommandProcessor(MapDocumentCommandFacade * document)117         CommandProcessor::CommandProcessor(MapDocumentCommandFacade* document) :
118         m_document(document),
119         m_clearRepeatableCommandStack(false),
120         m_lastCommandTimestamp(0),
121         m_groupLevel(0) {
122             assert(m_document != NULL);
123         }
124 
hasLastCommand() const125         bool CommandProcessor::hasLastCommand() const {
126             return !m_lastCommandStack.empty();
127         }
128 
hasNextCommand() const129         bool CommandProcessor::hasNextCommand() const {
130             return !m_nextCommandStack.empty();
131         }
132 
lastCommandName() const133         const String& CommandProcessor::lastCommandName() const {
134             if (!hasLastCommand())
135                 throw CommandProcessorException("Command stack is empty");
136             return m_lastCommandStack.back()->name();
137         }
138 
nextCommandName() const139         const String& CommandProcessor::nextCommandName() const {
140             if (!hasNextCommand())
141                 throw CommandProcessorException("Undo stack is empty");
142             return m_nextCommandStack.back()->name();
143         }
144 
beginGroup(const String & name)145         void CommandProcessor::beginGroup(const String& name) {
146             if (m_groupLevel == 0)
147                 m_groupName = name;
148             ++m_groupLevel;
149         }
150 
endGroup()151         void CommandProcessor::endGroup() {
152             if (m_groupLevel == 0)
153                 throw CommandProcessorException("Group stack is empty");
154             --m_groupLevel;
155             if (m_groupLevel == 0)
156                 createAndStoreCommandGroup();
157         }
158 
rollbackGroup()159         void CommandProcessor::rollbackGroup() {
160             while (!m_groupedCommands.empty())
161                 popGroupedCommand()->performUndo(m_document);
162         }
163 
submitCommand(Command::Ptr command)164         bool CommandProcessor::submitCommand(Command::Ptr command) {
165             const bool success = doCommand(command);
166             if (!success)
167                 return false;
168 
169             m_lastCommandStack.clear();
170             m_nextCommandStack.clear();
171             return true;
172         }
173 
submitAndStoreCommand(UndoableCommand::Ptr command)174         bool CommandProcessor::submitAndStoreCommand(UndoableCommand::Ptr command) {
175             const SubmitAndStoreResult result = submitAndStoreCommand(command, true);
176             if (result.submitted) {
177                 if (result.stored && m_groupLevel == 0)
178                     pushRepeatableCommand(command);
179                 return true;
180             }
181             return false;
182         }
183 
undoLastCommand()184         bool CommandProcessor::undoLastCommand() {
185             if (m_groupLevel > 0)
186                 throw CommandProcessorException("Cannot undo individual commands of a command group");
187 
188             UndoableCommand::Ptr command = popLastCommand();
189             if (undoCommand(command)) {
190                 pushNextCommand(command);
191                 popLastRepeatableCommand(command);
192                 return true;
193             }
194             return false;
195         }
196 
redoNextCommand()197         bool CommandProcessor::redoNextCommand() {
198             if (m_groupLevel > 0)
199                 throw CommandProcessorException("Cannot redo while in a command group");
200 
201             UndoableCommand::Ptr command = popNextCommand();
202             if (doCommand(command)) {
203                 if (pushLastCommand(command, false) && m_groupLevel == 0)
204                     pushRepeatableCommand(command);
205                 return true;
206             }
207             return false;
208         }
209 
repeatLastCommands()210         bool CommandProcessor::repeatLastCommands() {
211             CommandList commands;
212             CommandStack::iterator it, end;
213             for (it = m_repeatableCommandStack.begin(), end = m_repeatableCommandStack.end(); it != end; ++it) {
214                 UndoableCommand::Ptr command = *it;
215                 if (command->isRepeatable(m_document))
216                     commands.push_back(UndoableCommand::Ptr(command->repeat(m_document)));
217             }
218 
219             if (commands.empty())
220                 return false;
221 
222             StringStream name;
223             name << "Repeat " << commands.size() << " Commands";
224 
225             UndoableCommand::Ptr repeatableCommand = UndoableCommand::Ptr(createCommandGroup(name.str(), commands));
226             return submitAndStoreCommand(repeatableCommand, false).submitted;
227         }
228 
clearRepeatableCommands()229         void CommandProcessor::clearRepeatableCommands() {
230             m_repeatableCommandStack.clear();
231             m_clearRepeatableCommandStack = false;
232         }
233 
clear()234         void CommandProcessor::clear() {
235             assert(m_groupLevel == 0);
236 
237             clearRepeatableCommands();
238             m_lastCommandStack.clear();
239             m_nextCommandStack.clear();
240             m_lastCommandTimestamp = 0;
241         }
242 
submitAndStoreCommand(UndoableCommand::Ptr command,const bool collate)243         CommandProcessor::SubmitAndStoreResult CommandProcessor::submitAndStoreCommand(UndoableCommand::Ptr command, const bool collate) {
244             SubmitAndStoreResult result;
245             result.submitted = doCommand(command);
246             if (!result.submitted)
247                 return result;
248 
249             result.stored = storeCommand(command, collate);
250             if (!m_nextCommandStack.empty())
251                 m_nextCommandStack.clear();
252             return result;
253         }
254 
doCommand(Command::Ptr command)255         bool CommandProcessor::doCommand(Command::Ptr command) {
256             try {
257                 if (command->type() != CommandGroup::Type)
258                     commandDoNotifier(command);
259                 if (command->performDo(m_document)) {
260                     if (command->type() != CommandGroup::Type)
261                         commandDoneNotifier(command);
262                     return true;
263                 }
264                 if (command->type() != CommandGroup::Type)
265                     commandDoFailedNotifier(command);
266             } catch (const Exception& e) {
267                 m_document->error(e.what());
268             }
269             return false;
270         }
271 
undoCommand(UndoableCommand::Ptr command)272         bool CommandProcessor::undoCommand(UndoableCommand::Ptr command) {
273             try {
274                 if (command->type() != CommandGroup::Type)
275                     commandUndoNotifier(command);
276                 if (command->performUndo(m_document)) {
277                     if (command->type() != CommandGroup::Type)
278                         commandUndoneNotifier(command);
279                     return true;
280                 }
281                 if (command->type() != CommandGroup::Type)
282                     commandUndoFailedNotifier(command);
283             } catch (const Exception& e) {
284                 m_document->error(e.what());
285             }
286             return false;
287         }
288 
storeCommand(UndoableCommand::Ptr command,const bool collate)289         bool CommandProcessor::storeCommand(UndoableCommand::Ptr command, const bool collate) {
290             if (m_groupLevel == 0)
291                 return pushLastCommand(command, collate);
292             return pushGroupedCommand(command, collate);
293         }
294 
pushGroupedCommand(UndoableCommand::Ptr command,const bool collate)295         bool CommandProcessor::pushGroupedCommand(UndoableCommand::Ptr command, const bool collate) {
296             assert(m_groupLevel > 0);
297             if (!m_groupedCommands.empty()) {
298                 UndoableCommand::Ptr lastCommand = m_groupedCommands.back();
299                 if (collate && !lastCommand->collateWith(command)) {
300                     m_groupedCommands.push_back(command);
301                     return false;
302                 }
303             } else {
304                 m_groupedCommands.push_back(command);
305             }
306             return true;
307         }
308 
popGroupedCommand()309         UndoableCommand::Ptr CommandProcessor::popGroupedCommand() {
310             assert(m_groupLevel > 0);
311             if (m_groupedCommands.empty())
312                 throw CommandProcessorException("Group command stack is empty");
313             UndoableCommand::Ptr groupedCommand = m_groupedCommands.back();
314             m_groupedCommands.pop_back();
315             return groupedCommand;
316         }
317 
createAndStoreCommandGroup()318         void CommandProcessor::createAndStoreCommandGroup() {
319             if (!m_groupedCommands.empty()) {
320                 if (m_groupName.empty())
321                     m_groupName = m_groupedCommands.front()->name();
322                 UndoableCommand::Ptr group(createCommandGroup(m_groupName, m_groupedCommands));
323                 m_groupedCommands.clear();
324                 pushLastCommand(group, false);
325                 pushRepeatableCommand(group);
326             }
327             m_groupName = "";
328         }
329 
createCommandGroup(const String & name,const CommandList & commands)330         UndoableCommand::Ptr CommandProcessor::createCommandGroup(const String& name, const CommandList& commands) {
331             return UndoableCommand::Ptr(new CommandGroup(name, commands,
332                                                commandDoNotifier,
333                                                commandDoneNotifier,
334                                                commandUndoNotifier,
335                                                commandUndoneNotifier));
336         }
337 
pushLastCommand(UndoableCommand::Ptr command,const bool collate)338         bool CommandProcessor::pushLastCommand(UndoableCommand::Ptr command, const bool collate) {
339             assert(m_groupLevel == 0);
340 
341             const wxLongLong timestamp = ::wxGetLocalTimeMillis();
342             const SetLate<wxLongLong> setLastCommandTimestamp(m_lastCommandTimestamp, timestamp);
343 
344             if (collatable(collate, timestamp)) {
345                 UndoableCommand::Ptr lastCommand = m_lastCommandStack.back();
346                 if (lastCommand->collateWith(command))
347                     return false;
348             }
349             m_lastCommandStack.push_back(command);
350             return true;
351         }
352 
collatable(const bool collate,const wxLongLong timestamp) const353         bool CommandProcessor::collatable(const bool collate, const wxLongLong timestamp) const {
354             return collate && !m_lastCommandStack.empty() && timestamp - m_lastCommandTimestamp <= CollationInterval;
355         }
356 
pushNextCommand(UndoableCommand::Ptr command)357         void CommandProcessor::pushNextCommand(UndoableCommand::Ptr command) {
358             assert(m_groupLevel == 0);
359             m_nextCommandStack.push_back(command);
360         }
361 
pushRepeatableCommand(UndoableCommand::Ptr command)362         void CommandProcessor::pushRepeatableCommand(UndoableCommand::Ptr command) {
363             if (command->isRepeatDelimiter()) {
364                 m_clearRepeatableCommandStack = true;
365             } else {
366                 if (m_clearRepeatableCommandStack) {
367                     m_repeatableCommandStack.clear();
368                     m_clearRepeatableCommandStack = false;
369                 }
370                 m_repeatableCommandStack.push_back(command);
371             }
372         }
373 
popLastCommand()374         UndoableCommand::Ptr CommandProcessor::popLastCommand() {
375             assert(m_groupLevel == 0);
376             if (m_lastCommandStack.empty())
377                 throw CommandProcessorException("Command stack is empty");
378             UndoableCommand::Ptr lastCommand = m_lastCommandStack.back();
379             m_lastCommandStack.pop_back();
380             return lastCommand;
381         }
382 
popNextCommand()383         UndoableCommand::Ptr CommandProcessor::popNextCommand() {
384             assert(m_groupLevel == 0);
385             if (m_nextCommandStack.empty())
386                 throw CommandProcessorException("Command stack is empty");
387             UndoableCommand::Ptr nextCommand = m_nextCommandStack.back();
388             m_nextCommandStack.pop_back();
389             return nextCommand;
390         }
391 
popLastRepeatableCommand(UndoableCommand::Ptr command)392         void CommandProcessor::popLastRepeatableCommand(UndoableCommand::Ptr command) {
393             if (!m_repeatableCommandStack.empty() && m_repeatableCommandStack.back() == command)
394                 m_repeatableCommandStack.pop_back();
395         }
396     }
397 }
398