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