Mercurial > hg > easaier-soundaccess
comparison base/CommandHistory.cpp @ 0:fc9323a41f5a
start base : Sonic Visualiser sv1-1.0rc1
author | lbajardsilogic |
---|---|
date | Fri, 11 May 2007 09:08:14 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:fc9323a41f5a |
---|---|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
2 | |
3 /* | |
4 Sonic Visualiser | |
5 An audio file viewer and annotation editor. | |
6 Centre for Digital Music, Queen Mary, University of London. | |
7 | |
8 This program is free software; you can redistribute it and/or | |
9 modify it under the terms of the GNU General Public License as | |
10 published by the Free Software Foundation; either version 2 of the | |
11 License, or (at your option) any later version. See the file | |
12 COPYING included with this distribution for more information. | |
13 */ | |
14 | |
15 /* | |
16 This is a modified version of a source file from the Rosegarden | |
17 MIDI and audio sequencer and notation editor, copyright 2000-2006 | |
18 Chris Cannam, distributed under the GNU General Public License. | |
19 | |
20 This file contains traces of the KCommandHistory class from the KDE | |
21 project, copyright 2000 Werner Trobin and David Faure and | |
22 distributed under the GNU Lesser General Public License. | |
23 */ | |
24 | |
25 #include "CommandHistory.h" | |
26 | |
27 #include "Command.h" | |
28 | |
29 #include <QRegExp> | |
30 #include <QMenu> | |
31 #include <QToolBar> | |
32 #include <QString> | |
33 #include <QTimer> | |
34 | |
35 #include <iostream> | |
36 | |
37 CommandHistory *CommandHistory::m_instance = 0; | |
38 | |
39 CommandHistory::CommandHistory() : | |
40 m_undoLimit(50), | |
41 m_redoLimit(50), | |
42 m_menuLimit(15), | |
43 m_savedAt(0), | |
44 m_currentCompound(0), | |
45 m_executeCompound(false), | |
46 m_currentBundle(0), | |
47 m_bundleTimer(0), | |
48 m_bundleTimeout(5000) | |
49 { | |
50 m_undoAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this); | |
51 m_undoAction->setShortcut(tr("Ctrl+Z")); | |
52 m_undoAction->setStatusTip(tr("Undo the last editing operation")); | |
53 connect(m_undoAction, SIGNAL(triggered()), this, SLOT(undo())); | |
54 | |
55 m_undoMenuAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this); | |
56 connect(m_undoMenuAction, SIGNAL(triggered()), this, SLOT(undo())); | |
57 | |
58 m_undoMenu = new QMenu(tr("&Undo")); | |
59 m_undoMenuAction->setMenu(m_undoMenu); | |
60 connect(m_undoMenu, SIGNAL(triggered(QAction *)), | |
61 this, SLOT(undoActivated(QAction*))); | |
62 | |
63 m_redoAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this); | |
64 m_redoAction->setShortcut(tr("Ctrl+Shift+Z")); | |
65 m_redoAction->setStatusTip(tr("Redo the last operation that was undone")); | |
66 connect(m_redoAction, SIGNAL(triggered()), this, SLOT(redo())); | |
67 | |
68 m_redoMenuAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this); | |
69 connect(m_redoMenuAction, SIGNAL(triggered()), this, SLOT(redo())); | |
70 | |
71 m_redoMenu = new QMenu(tr("Re&do")); | |
72 m_redoMenuAction->setMenu(m_redoMenu); | |
73 connect(m_redoMenu, SIGNAL(triggered(QAction *)), | |
74 this, SLOT(redoActivated(QAction*))); | |
75 } | |
76 | |
77 CommandHistory::~CommandHistory() | |
78 { | |
79 m_savedAt = -1; | |
80 clearStack(m_undoStack); | |
81 clearStack(m_redoStack); | |
82 | |
83 delete m_undoMenu; | |
84 delete m_redoMenu; | |
85 } | |
86 | |
87 CommandHistory * | |
88 CommandHistory::getInstance() | |
89 { | |
90 if (!m_instance) m_instance = new CommandHistory(); | |
91 return m_instance; | |
92 } | |
93 | |
94 void | |
95 CommandHistory::clear() | |
96 { | |
97 // std::cerr << "CommandHistory::clear()" << std::endl; | |
98 closeBundle(); | |
99 m_savedAt = -1; | |
100 clearStack(m_undoStack); | |
101 clearStack(m_redoStack); | |
102 updateActions(); | |
103 } | |
104 | |
105 void | |
106 CommandHistory::registerMenu(QMenu *menu) | |
107 { | |
108 menu->addAction(m_undoAction); | |
109 menu->addAction(m_redoAction); | |
110 } | |
111 | |
112 void | |
113 CommandHistory::registerToolbar(QToolBar *toolbar) | |
114 { | |
115 toolbar->addAction(m_undoMenuAction); | |
116 toolbar->addAction(m_redoMenuAction); | |
117 } | |
118 | |
119 void | |
120 CommandHistory::addCommand(Command *command) | |
121 { | |
122 if (!command) return; | |
123 | |
124 if (m_currentCompound) { | |
125 addToCompound(command, m_executeCompound); | |
126 return; | |
127 } | |
128 | |
129 addCommand(command, true); | |
130 } | |
131 | |
132 void | |
133 CommandHistory::addCommand(Command *command, bool execute, bool bundle) | |
134 { | |
135 if (!command) return; | |
136 | |
137 if (m_currentCompound) { | |
138 addToCompound(command, execute); | |
139 return; | |
140 } | |
141 | |
142 if (bundle) { | |
143 addToBundle(command, execute); | |
144 return; | |
145 } else if (m_currentBundle) { | |
146 closeBundle(); | |
147 } | |
148 | |
149 // std::cerr << "CommandHistory::addCommand: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl; | |
150 | |
151 // We can't redo after adding a command | |
152 // std::cerr << "CommandHistory::clearing redo stack" << std::endl; | |
153 clearStack(m_redoStack); | |
154 | |
155 // can we reach savedAt? | |
156 if ((int)m_undoStack.size() < m_savedAt) m_savedAt = -1; // nope | |
157 | |
158 m_undoStack.push(command); | |
159 clipCommands(); | |
160 | |
161 if (execute) { | |
162 command->execute(); | |
163 } | |
164 | |
165 // Emit even if we aren't executing the command, because | |
166 // someone must have executed it for this to make any sense | |
167 emit commandExecuted(); | |
168 emit commandExecuted(command); | |
169 | |
170 updateActions(); | |
171 } | |
172 | |
173 void | |
174 CommandHistory::addToBundle(Command *command, bool execute) | |
175 { | |
176 if (m_currentBundle) { | |
177 if (!command || (command->getName() != m_currentBundleName)) { | |
178 closeBundle(); | |
179 } | |
180 } | |
181 | |
182 if (!command) return; | |
183 | |
184 if (!m_currentBundle) { | |
185 // need to addCommand before setting m_currentBundle, as addCommand | |
186 // with bundle false will reset m_currentBundle to 0 | |
187 MacroCommand *mc = new MacroCommand(command->getName()); | |
188 addCommand(mc, false); | |
189 m_currentBundle = mc; | |
190 m_currentBundleName = command->getName(); | |
191 } | |
192 | |
193 if (execute) command->execute(); | |
194 m_currentBundle->addCommand(command); | |
195 | |
196 delete m_bundleTimer; | |
197 m_bundleTimer = new QTimer(this); | |
198 connect(m_bundleTimer, SIGNAL(timeout()), this, SLOT(bundleTimerTimeout())); | |
199 m_bundleTimer->start(m_bundleTimeout); | |
200 } | |
201 | |
202 void | |
203 CommandHistory::closeBundle() | |
204 { | |
205 m_currentBundle = 0; | |
206 m_currentBundleName = ""; | |
207 } | |
208 | |
209 void | |
210 CommandHistory::bundleTimerTimeout() | |
211 { | |
212 closeBundle(); | |
213 } | |
214 | |
215 void | |
216 CommandHistory::addToCompound(Command *command, bool execute) | |
217 { | |
218 // std::cerr << "CommandHistory::addToCompound: " << command->getName().toLocal8Bit().data() << std::endl; | |
219 | |
220 if (execute) command->execute(); | |
221 m_currentCompound->addCommand(command); | |
222 } | |
223 | |
224 void | |
225 CommandHistory::startCompoundOperation(QString name, bool execute) | |
226 { | |
227 if (m_currentCompound) { | |
228 std::cerr << "CommandHistory::startCompoundOperation: ERROR: compound operation already in progress!" << std::endl; | |
229 std::cerr << "(name is " << m_currentCompound->getName().toLocal8Bit().data() << ")" << std::endl; | |
230 } | |
231 | |
232 closeBundle(); | |
233 | |
234 m_currentCompound = new MacroCommand(name); | |
235 m_executeCompound = execute; | |
236 } | |
237 | |
238 void | |
239 CommandHistory::endCompoundOperation() | |
240 { | |
241 if (!m_currentCompound) { | |
242 std::cerr << "CommandHistory::endCompoundOperation: ERROR: no compound operation in progress!" << std::endl; | |
243 } | |
244 | |
245 MacroCommand *toAdd = m_currentCompound; | |
246 m_currentCompound = 0; | |
247 | |
248 if (toAdd->haveCommands()) { | |
249 | |
250 // We don't execute the macro command here, because we have | |
251 // been executing the individual commands as we went along if | |
252 // m_executeCompound was true. | |
253 addCommand(toAdd, false); | |
254 } | |
255 } | |
256 | |
257 void | |
258 CommandHistory::addExecutedCommand(Command *command) | |
259 { | |
260 addCommand(command, false); | |
261 } | |
262 | |
263 void | |
264 CommandHistory::addCommandAndExecute(Command *command) | |
265 { | |
266 addCommand(command, true); | |
267 } | |
268 | |
269 void | |
270 CommandHistory::undo() | |
271 { | |
272 if (m_undoStack.empty()) return; | |
273 | |
274 closeBundle(); | |
275 | |
276 Command *command = m_undoStack.top(); | |
277 command->unexecute(); | |
278 emit commandExecuted(); | |
279 emit commandUnexecuted(command); | |
280 | |
281 m_redoStack.push(command); | |
282 m_undoStack.pop(); | |
283 | |
284 clipCommands(); | |
285 updateActions(); | |
286 | |
287 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored(); | |
288 } | |
289 | |
290 void | |
291 CommandHistory::redo() | |
292 { | |
293 if (m_redoStack.empty()) return; | |
294 | |
295 closeBundle(); | |
296 | |
297 Command *command = m_redoStack.top(); | |
298 command->execute(); | |
299 emit commandExecuted(); | |
300 emit commandExecuted(command); | |
301 | |
302 m_undoStack.push(command); | |
303 m_redoStack.pop(); | |
304 // no need to clip | |
305 | |
306 updateActions(); | |
307 | |
308 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored(); | |
309 } | |
310 | |
311 void | |
312 CommandHistory::setUndoLimit(int limit) | |
313 { | |
314 if (limit > 0 && limit != m_undoLimit) { | |
315 m_undoLimit = limit; | |
316 clipCommands(); | |
317 } | |
318 } | |
319 | |
320 void | |
321 CommandHistory::setRedoLimit(int limit) | |
322 { | |
323 if (limit > 0 && limit != m_redoLimit) { | |
324 m_redoLimit = limit; | |
325 clipCommands(); | |
326 } | |
327 } | |
328 | |
329 void | |
330 CommandHistory::setMenuLimit(int limit) | |
331 { | |
332 m_menuLimit = limit; | |
333 updateActions(); | |
334 } | |
335 | |
336 void | |
337 CommandHistory::setBundleTimeout(int ms) | |
338 { | |
339 m_bundleTimeout = ms; | |
340 } | |
341 | |
342 void | |
343 CommandHistory::documentSaved() | |
344 { | |
345 closeBundle(); | |
346 m_savedAt = m_undoStack.size(); | |
347 } | |
348 | |
349 void | |
350 CommandHistory::clipCommands() | |
351 { | |
352 if ((int)m_undoStack.size() > m_undoLimit) { | |
353 m_savedAt -= (m_undoStack.size() - m_undoLimit); | |
354 } | |
355 | |
356 clipStack(m_undoStack, m_undoLimit); | |
357 clipStack(m_redoStack, m_redoLimit); | |
358 } | |
359 | |
360 void | |
361 CommandHistory::clipStack(CommandStack &stack, int limit) | |
362 { | |
363 int i; | |
364 | |
365 if ((int)stack.size() > limit) { | |
366 | |
367 CommandStack tempStack; | |
368 | |
369 for (i = 0; i < limit; ++i) { | |
370 // Command *command = stack.top(); | |
371 // std::cerr << "CommandHistory::clipStack: Saving recent command: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl; | |
372 tempStack.push(stack.top()); | |
373 stack.pop(); | |
374 } | |
375 | |
376 clearStack(stack); | |
377 | |
378 for (i = 0; i < m_undoLimit; ++i) { | |
379 stack.push(tempStack.top()); | |
380 tempStack.pop(); | |
381 } | |
382 } | |
383 } | |
384 | |
385 void | |
386 CommandHistory::clearStack(CommandStack &stack) | |
387 { | |
388 while (!stack.empty()) { | |
389 Command *command = stack.top(); | |
390 // Not safe to call getName() on a command about to be deleted | |
391 // std::cerr << "CommandHistory::clearStack: About to delete command " << command << std::endl; | |
392 delete command; | |
393 stack.pop(); | |
394 } | |
395 } | |
396 | |
397 void | |
398 CommandHistory::undoActivated(QAction *action) | |
399 { | |
400 int pos = m_actionCounts[action]; | |
401 for (int i = 0; i <= pos; ++i) { | |
402 undo(); | |
403 } | |
404 } | |
405 | |
406 void | |
407 CommandHistory::redoActivated(QAction *action) | |
408 { | |
409 int pos = m_actionCounts[action]; | |
410 for (int i = 0; i <= pos; ++i) { | |
411 redo(); | |
412 } | |
413 } | |
414 | |
415 void | |
416 CommandHistory::updateActions() | |
417 { | |
418 m_actionCounts.clear(); | |
419 | |
420 for (int undo = 0; undo <= 1; ++undo) { | |
421 | |
422 QAction *action(undo ? m_undoAction : m_redoAction); | |
423 QAction *menuAction(undo ? m_undoMenuAction : m_redoMenuAction); | |
424 QMenu *menu(undo ? m_undoMenu : m_redoMenu); | |
425 CommandStack &stack(undo ? m_undoStack : m_redoStack); | |
426 | |
427 if (stack.empty()) { | |
428 | |
429 QString text(undo ? tr("Nothing to undo") : tr("Nothing to redo")); | |
430 | |
431 action->setEnabled(false); | |
432 action->setText(text); | |
433 | |
434 menuAction->setEnabled(false); | |
435 menuAction->setText(text); | |
436 | |
437 } else { | |
438 | |
439 action->setEnabled(true); | |
440 menuAction->setEnabled(true); | |
441 | |
442 QString commandName = stack.top()->getName(); | |
443 commandName.replace(QRegExp("&"), ""); | |
444 | |
445 QString text = (undo ? tr("&Undo %1") : tr("Re&do %1")) | |
446 .arg(commandName); | |
447 | |
448 action->setText(text); | |
449 menuAction->setText(text); | |
450 } | |
451 | |
452 menu->clear(); | |
453 | |
454 CommandStack tempStack; | |
455 int j = 0; | |
456 | |
457 while (j < m_menuLimit && !stack.empty()) { | |
458 | |
459 Command *command = stack.top(); | |
460 tempStack.push(command); | |
461 stack.pop(); | |
462 | |
463 QString commandName = command->getName(); | |
464 commandName.replace(QRegExp("&"), ""); | |
465 | |
466 QString text; | |
467 if (undo) text = tr("&Undo %1").arg(commandName); | |
468 else text = tr("Re&do %1").arg(commandName); | |
469 | |
470 QAction *action = menu->addAction(text); | |
471 m_actionCounts[action] = j++; | |
472 } | |
473 | |
474 while (!tempStack.empty()) { | |
475 stack.push(tempStack.top()); | |
476 tempStack.pop(); | |
477 } | |
478 } | |
479 } | |
480 |