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