xref: /haiku/src/apps/icon-o-matic/generic/command/CommandStack.cpp (revision ed24eb5ff12640d052171c6a7feba37fab8a75d1)
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
20 CommandStack::CommandStack()
21 	: BLocker("history"),
22 	  Observable(),
23 	  fSavedCommand(NULL)
24 {
25 }
26 
27 // destructor
28 CommandStack::~CommandStack()
29 {
30 	Clear();
31 }
32 
33 // Perform
34 status_t
35 CommandStack::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
60 status_t
61 CommandStack::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
84 status_t
85 CommandStack::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
108 bool
109 CommandStack::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
124 bool
125 CommandStack::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
140 void
141 CommandStack::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
159 void
160 CommandStack::Save()
161 {
162 	if (Lock()) {
163 		if (!fUndoHistory.empty())
164 			fSavedCommand = fUndoHistory.top();
165 		Unlock();
166 	}
167 
168 	Notify();
169 }
170 
171 // IsSaved
172 bool
173 CommandStack::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
190 status_t
191 CommandStack::_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