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