xref: /haiku/src/apps/haikudepot/edits_generic/EditManager.cpp (revision 7323d0a21daaded71c6231c5b7bcba9db4024a40)
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(
178 		fListeners.begin(),
179 		fListeners.end(),
180 		listener), fListeners.end());
181 }
182 
183 
184 // #pragma mark -
185 
186 
187 status_t
188 EditManager::_AddEdit(const UndoableEditRef& edit)
189 {
190 	status_t status = B_OK;
191 
192 	bool add = true;
193 	if (!fUndoHistory.empty()) {
194 		// Try to collapse edits to a single edit
195 		// or remove this and the previous edit if
196 		// they reverse each other
197 		const UndoableEditRef& top = fUndoHistory.top();
198 		if (edit->UndoesPrevious(top.Get())) {
199 			add = false;
200 			fUndoHistory.pop();
201 		} else if (top->CombineWithNext(edit.Get())) {
202 			add = false;
203 			// After collapsing, the edit might
204 			// have changed it's mind about InitCheck()
205 			// (the commands reversed each other)
206 			if (top->InitCheck() != B_OK) {
207 				fUndoHistory.pop();
208 			}
209 		} else if (edit->CombineWithPrevious(top.Get())) {
210 			fUndoHistory.pop();
211 			// After collapsing, the edit might
212 			// have changed it's mind about InitCheck()
213 			// (the commands reversed each other)
214 			if (edit->InitCheck() != B_OK) {
215 				add = false;
216 			}
217 		}
218 	}
219 	if (add)
220 		fUndoHistory.push(edit);
221 
222 	if (status == B_OK) {
223 		// The redo stack needs to be empty
224 		// as soon as an edit was added (also in case of collapsing)
225 		while (!fRedoHistory.empty()) {
226 			fRedoHistory.pop();
227 		}
228 	}
229 
230 	return status;
231 }
232 
233 
234 void
235 EditManager::_NotifyListeners()
236 {
237 	// Iterate a copy of the list, so we don't crash if listeners
238 	// detach themselves while being notified.
239 	std::vector<Listener*> listeners(fListeners);
240 
241 	std::vector<Listener*>::const_iterator it;
242 	for (it = listeners.begin(); it != listeners.end(); it++) {
243 		Listener* listener = *it;
244 		listener->EditManagerChanged(this);
245 	}
246 }
247 
248