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
18EditManager::Listener::~Listener()
19{
20}
21
22
23EditManager::EditManager()
24{
25}
26
27
28EditManager::~EditManager()
29{
30	Clear();
31}
32
33
34status_t
35EditManager::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
44status_t
45EditManager::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
66status_t
67EditManager::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
86status_t
87EditManager::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
106bool
107EditManager::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
118bool
119EditManager::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
130void
131EditManager::Clear()
132{
133	while (!fUndoHistory.empty())
134		fUndoHistory.pop();
135	while (!fRedoHistory.empty())
136		fRedoHistory.pop();
137
138	_NotifyListeners();
139}
140
141
142void
143EditManager::Save()
144{
145	if (!fUndoHistory.empty())
146		fEditAtSave = fUndoHistory.top();
147
148	_NotifyListeners();
149}
150
151
152bool
153EditManager::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
167void
168EditManager::AddListener(Listener* listener)
169{
170	return fListeners.push_back(listener);
171}
172
173
174void
175EditManager::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
185status_t
186EditManager::_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
232void
233EditManager::_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