1/* 2 * Copyright 2006-2007, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus <superstippi@gmx.de> 7 */ 8 9#include "CommandStack.h" 10 11#include <stdio.h> 12#include <string.h> 13 14#include <Locker.h> 15#include <String.h> 16 17#include "Command.h" 18#include "RWLocker.h" 19 20// constructor 21CommandStack::CommandStack(RWLocker* locker) 22 : Notifier() 23 , fLocker(locker) 24 , fSavedCommand(NULL) 25{ 26} 27 28// destructor 29CommandStack::~CommandStack() 30{ 31 Clear(); 32} 33 34// Perform 35status_t 36CommandStack::Perform(Command* command) 37{ 38 if (!fLocker->WriteLock()) 39 return B_ERROR; 40 41 status_t ret = command ? B_OK : B_BAD_VALUE; 42 if (ret == B_OK) 43 ret = command->InitCheck(); 44 45 if (ret == B_OK) 46 ret = command->Perform(); 47 48 if (ret == B_OK) 49 ret = _AddCommand(command); 50 51 if (ret != B_OK) { 52 // no one else feels responsible... 53 delete command; 54 } 55 fLocker->WriteUnlock(); 56 57 Notify(); 58 59 return ret; 60} 61 62// Undo 63status_t 64CommandStack::Undo() 65{ 66 if (!fLocker->WriteLock()) 67 return B_ERROR; 68 69 status_t status = B_ERROR; 70 if (!fUndoHistory.empty()) { 71 Command* command = fUndoHistory.top(); 72 fUndoHistory.pop(); 73 status = command->Undo(); 74 if (status == B_OK) 75 fRedoHistory.push(command); 76 else 77 fUndoHistory.push(command); 78 } 79 fLocker->WriteUnlock(); 80 81 Notify(); 82 83 return status; 84} 85 86// Redo 87status_t 88CommandStack::Redo() 89{ 90 if (!fLocker->WriteLock()) 91 return B_ERROR; 92 93 status_t status = B_ERROR; 94 if (!fRedoHistory.empty()) { 95 Command* command = fRedoHistory.top(); 96 fRedoHistory.pop(); 97 status = command->Redo(); 98 if (status == B_OK) 99 fUndoHistory.push(command); 100 else 101 fRedoHistory.push(command); 102 } 103 fLocker->WriteUnlock(); 104 105 Notify(); 106 107 return status; 108} 109 110// UndoName 111bool 112CommandStack::GetUndoName(BString& name) 113{ 114 bool success = false; 115 if (fLocker->ReadLock()) { 116 if (!fUndoHistory.empty()) { 117 name << " "; 118 fUndoHistory.top()->GetName(name); 119 success = true; 120 } 121 fLocker->ReadUnlock(); 122 } 123 return success; 124} 125 126// RedoName 127bool 128CommandStack::GetRedoName(BString& name) 129{ 130 bool success = false; 131 if (fLocker->ReadLock()) { 132 if (!fRedoHistory.empty()) { 133 name << " "; 134 fRedoHistory.top()->GetName(name); 135 success = true; 136 } 137 fLocker->ReadUnlock(); 138 } 139 return success; 140} 141 142// Clear 143void 144CommandStack::Clear() 145{ 146 if (fLocker->WriteLock()) { 147 while (!fUndoHistory.empty()) { 148 delete fUndoHistory.top(); 149 fUndoHistory.pop(); 150 } 151 while (!fRedoHistory.empty()) { 152 delete fRedoHistory.top(); 153 fRedoHistory.pop(); 154 } 155 fLocker->WriteUnlock(); 156 } 157 158 Notify(); 159} 160 161// Save 162void 163CommandStack::Save() 164{ 165 if (fLocker->WriteLock()) { 166 if (!fUndoHistory.empty()) 167 fSavedCommand = fUndoHistory.top(); 168 fLocker->WriteUnlock(); 169 } 170 171 Notify(); 172} 173 174// IsSaved 175bool 176CommandStack::IsSaved() 177{ 178 bool saved = false; 179 if (fLocker->ReadLock()) { 180 saved = fUndoHistory.empty(); 181 if (fSavedCommand && !saved) { 182 if (fSavedCommand == fUndoHistory.top()) 183 saved = true; 184 } 185 fLocker->ReadUnlock(); 186 } 187 return saved; 188} 189 190// #pragma mark - 191 192// _AddCommand 193status_t 194CommandStack::_AddCommand(Command* command) 195{ 196 status_t status = B_OK; 197 198 bool add = true; 199 if (!fUndoHistory.empty()) { 200 // try to collapse commands to a single command 201 // or remove this and the previous command if 202 // they reverse each other 203 if (Command* top = fUndoHistory.top()) { 204 if (command->UndoesPrevious(top)) { 205 add = false; 206 fUndoHistory.pop(); 207 delete top; 208 delete command; 209 } else if (top->CombineWithNext(command)) { 210 add = false; 211 delete command; 212 // after collapsing, the command might 213 // have changed it's mind about InitCheck() 214 // (the commands reversed each other) 215 if (top->InitCheck() < B_OK) { 216 fUndoHistory.pop(); 217 delete top; 218 } 219 } else if (command->CombineWithPrevious(top)) { 220 fUndoHistory.pop(); 221 delete top; 222 // after collapsing, the command might 223 // have changed it's mind about InitCheck() 224 // (the commands reversed each other) 225 if (command->InitCheck() < B_OK) { 226 delete command; 227 add = false; 228 } 229 } 230 } 231 } 232 if (add) { 233 try { 234 fUndoHistory.push(command); 235 } catch (...) { 236 status = B_ERROR; 237 } 238 } 239 240 if (status == B_OK) { 241 // the redo stack needs to be empty 242 // as soon as a command was added (also in case of collapsing) 243 while (!fRedoHistory.empty()) { 244 delete fRedoHistory.top(); 245 fRedoHistory.pop(); 246 } 247 } 248 249 return status; 250} 251 252 253