comparison widgets/CommandHistory.cpp @ 376:e1a9e478b7f2

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