annotate widgets/CommandHistory.cpp @ 377:0bcb449d15f4

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