xref: /haiku/src/apps/haikudepot/edits_generic/EditManager.cpp (revision 52f7c9389475e19fc21487b38064b4390eeb6fea)
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