annotate base/CommandHistory.cpp @ 61:749b5521e082

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