xref: /haiku/src/apps/mediaplayer/support/CommandStack.cpp (revision 1214ef1b2100f2b3299fc9d8d6142e46f70a4c3f)
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
21 CommandStack::CommandStack(RWLocker* locker)
22 	: Notifier()
23 	, fLocker(locker)
24 	, fSavedCommand(NULL)
25 {
26 }
27 
28 // destructor
29 CommandStack::~CommandStack()
30 {
31 	Clear();
32 }
33 
34 // Perform
35 status_t
36 CommandStack::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
63 status_t
64 CommandStack::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
87 status_t
88 CommandStack::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
111 bool
112 CommandStack::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
127 bool
128 CommandStack::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
143 void
144 CommandStack::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
162 void
163 CommandStack::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
175 bool
176 CommandStack::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
193 status_t
194 CommandStack::_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