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