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