annotate base/CommandHistory.cpp @ 386:e6d11871e4c9

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