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 20CommandStack::CommandStack() 21 : BLocker("history"), 22 Observable(), 23 fSavedCommand(NULL) 24{ 25} 26 27// destructor 28CommandStack::~CommandStack() 29{ 30 Clear(); 31} 32 33// Perform 34status_t 35CommandStack::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 60status_t 61CommandStack::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 84status_t 85CommandStack::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 108bool 109CommandStack::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 124bool 125CommandStack::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 140void 141CommandStack::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 159void 160CommandStack::Save() 161{ 162 if (Lock()) { 163 if (!fUndoHistory.empty()) 164 fSavedCommand = fUndoHistory.top(); 165 Unlock(); 166 } 167 168 Notify(); 169} 170 171// IsSaved 172bool 173CommandStack::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 190status_t 191CommandStack::_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