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