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 21 CommandStack::CommandStack(RWLocker* locker) 22 : Notifier() 23 , fLocker(locker) 24 , fSavedCommand(NULL) 25 { 26 } 27 28 // destructor 29 CommandStack::~CommandStack() 30 { 31 Clear(); 32 } 33 34 // Perform 35 status_t 36 CommandStack::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 63 status_t 64 CommandStack::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 87 status_t 88 CommandStack::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 111 bool 112 CommandStack::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 127 bool 128 CommandStack::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 143 void 144 CommandStack::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 162 void 163 CommandStack::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 175 bool 176 CommandStack::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 193 status_t 194 CommandStack::_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