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