annotate base/CommandHistory.cpp @ 267:fa612dc181af

* Fix failure to play right to the end of files loaded from mp3 or ogg
author Chris Cannam
date Thu, 14 Jun 2007 13:49:30 +0000
parents dc46851837d6
children 9867f99e0bb7
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@47 219
Chris@115 220 if (execute) command->execute();
Chris@47 221 m_currentCompound->addCommand(command);
Chris@44 222 }
Chris@44 223
Chris@44 224 void
Chris@44 225 CommandHistory::startCompoundOperation(QString name, bool execute)
Chris@44 226 {
Chris@47 227 if (m_currentCompound) {
Chris@47 228 std::cerr << "CommandHistory::startCompoundOperation: ERROR: compound operation already in progress!" << std::endl;
Chris@47 229 std::cerr << "(name is " << m_currentCompound->getName().toLocal8Bit().data() << ")" << std::endl;
Chris@44 230 }
Chris@47 231
Chris@47 232 closeBundle();
Chris@47 233
Chris@47 234 m_currentCompound = new MacroCommand(name);
Chris@47 235 m_executeCompound = execute;
Chris@44 236 }
Chris@44 237
Chris@44 238 void
Chris@44 239 CommandHistory::endCompoundOperation()
Chris@44 240 {
Chris@47 241 if (!m_currentCompound) {
Chris@47 242 std::cerr << "CommandHistory::endCompoundOperation: ERROR: no compound operation in progress!" << std::endl;
Chris@44 243 }
Chris@44 244
Chris@53 245 MacroCommand *toAdd = m_currentCompound;
Chris@47 246 m_currentCompound = 0;
Chris@44 247
Chris@53 248 if (toAdd->haveCommands()) {
Chris@53 249
Chris@53 250 // We don't execute the macro command here, because we have
Chris@53 251 // been executing the individual commands as we went along if
Chris@53 252 // m_executeCompound was true.
Chris@53 253 addCommand(toAdd, false);
Chris@53 254 }
Chris@44 255 }
Chris@44 256
Chris@44 257 void
Chris@17 258 CommandHistory::addExecutedCommand(Command *command)
Chris@17 259 {
Chris@17 260 addCommand(command, false);
Chris@17 261 }
Chris@17 262
Chris@17 263 void
Chris@17 264 CommandHistory::addCommandAndExecute(Command *command)
Chris@17 265 {
Chris@17 266 addCommand(command, true);
Chris@17 267 }
Chris@17 268
Chris@17 269 void
Chris@17 270 CommandHistory::undo()
Chris@16 271 {
Chris@16 272 if (m_undoStack.empty()) return;
Chris@16 273
Chris@47 274 closeBundle();
Chris@47 275
Chris@16 276 Command *command = m_undoStack.top();
Chris@16 277 command->unexecute();
Chris@16 278 emit commandExecuted();
Chris@17 279 emit commandUnexecuted(command);
Chris@16 280
Chris@16 281 m_redoStack.push(command);
Chris@16 282 m_undoStack.pop();
Chris@16 283
Chris@16 284 clipCommands();
Chris@16 285 updateActions();
Chris@16 286
Chris@16 287 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored();
Chris@16 288 }
Chris@16 289
Chris@16 290 void
Chris@17 291 CommandHistory::redo()
Chris@16 292 {
Chris@16 293 if (m_redoStack.empty()) return;
Chris@16 294
Chris@47 295 closeBundle();
Chris@47 296
Chris@16 297 Command *command = m_redoStack.top();
Chris@16 298 command->execute();
Chris@16 299 emit commandExecuted();
Chris@16 300 emit commandExecuted(command);
Chris@16 301
Chris@16 302 m_undoStack.push(command);
Chris@16 303 m_redoStack.pop();
Chris@16 304 // no need to clip
Chris@16 305
Chris@16 306 updateActions();
Chris@41 307
Chris@41 308 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored();
Chris@16 309 }
Chris@16 310
Chris@16 311 void
Chris@17 312 CommandHistory::setUndoLimit(int limit)
Chris@16 313 {
Chris@16 314 if (limit > 0 && limit != m_undoLimit) {
Chris@16 315 m_undoLimit = limit;
Chris@16 316 clipCommands();
Chris@16 317 }
Chris@16 318 }
Chris@16 319
Chris@16 320 void
Chris@17 321 CommandHistory::setRedoLimit(int limit)
Chris@16 322 {
Chris@16 323 if (limit > 0 && limit != m_redoLimit) {
Chris@16 324 m_redoLimit = limit;
Chris@16 325 clipCommands();
Chris@16 326 }
Chris@16 327 }
Chris@16 328
Chris@16 329 void
Chris@46 330 CommandHistory::setMenuLimit(int limit)
Chris@46 331 {
Chris@46 332 m_menuLimit = limit;
Chris@46 333 updateActions();
Chris@46 334 }
Chris@46 335
Chris@46 336 void
Chris@47 337 CommandHistory::setBundleTimeout(int ms)
Chris@47 338 {
Chris@47 339 m_bundleTimeout = ms;
Chris@47 340 }
Chris@47 341
Chris@47 342 void
Chris@17 343 CommandHistory::documentSaved()
Chris@16 344 {
Chris@47 345 closeBundle();
Chris@16 346 m_savedAt = m_undoStack.size();
Chris@16 347 }
Chris@16 348
Chris@16 349 void
Chris@17 350 CommandHistory::clipCommands()
Chris@16 351 {
Chris@16 352 if ((int)m_undoStack.size() > m_undoLimit) {
Chris@16 353 m_savedAt -= (m_undoStack.size() - m_undoLimit);
Chris@16 354 }
Chris@16 355
Chris@16 356 clipStack(m_undoStack, m_undoLimit);
Chris@16 357 clipStack(m_redoStack, m_redoLimit);
Chris@16 358 }
Chris@16 359
Chris@16 360 void
Chris@17 361 CommandHistory::clipStack(CommandStack &stack, int limit)
Chris@16 362 {
Chris@16 363 int i;
Chris@16 364
Chris@16 365 if ((int)stack.size() > limit) {
Chris@16 366
Chris@16 367 CommandStack tempStack;
Chris@16 368
Chris@16 369 for (i = 0; i < limit; ++i) {
Chris@259 370 // Command *command = stack.top();
Chris@78 371 // std::cerr << "CommandHistory::clipStack: Saving recent command: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl;
Chris@16 372 tempStack.push(stack.top());
Chris@16 373 stack.pop();
Chris@16 374 }
Chris@16 375
Chris@16 376 clearStack(stack);
Chris@16 377
Chris@16 378 for (i = 0; i < m_undoLimit; ++i) {
Chris@16 379 stack.push(tempStack.top());
Chris@16 380 tempStack.pop();
Chris@16 381 }
Chris@16 382 }
Chris@16 383 }
Chris@16 384
Chris@16 385 void
Chris@17 386 CommandHistory::clearStack(CommandStack &stack)
Chris@16 387 {
Chris@16 388 while (!stack.empty()) {
Chris@16 389 Command *command = stack.top();
Chris@46 390 // Not safe to call getName() on a command about to be deleted
Chris@117 391 // std::cerr << "CommandHistory::clearStack: About to delete command " << command << std::endl;
Chris@16 392 delete command;
Chris@16 393 stack.pop();
Chris@16 394 }
Chris@16 395 }
Chris@16 396
Chris@16 397 void
Chris@17 398 CommandHistory::undoActivated(QAction *action)
Chris@16 399 {
Chris@16 400 int pos = m_actionCounts[action];
Chris@16 401 for (int i = 0; i <= pos; ++i) {
Chris@16 402 undo();
Chris@16 403 }
Chris@16 404 }
Chris@16 405
Chris@16 406 void
Chris@17 407 CommandHistory::redoActivated(QAction *action)
Chris@16 408 {
Chris@16 409 int pos = m_actionCounts[action];
Chris@16 410 for (int i = 0; i <= pos; ++i) {
Chris@16 411 redo();
Chris@16 412 }
Chris@16 413 }
Chris@16 414
Chris@16 415 void
Chris@17 416 CommandHistory::updateActions()
Chris@16 417 {
Chris@16 418 m_actionCounts.clear();
Chris@16 419
Chris@16 420 for (int undo = 0; undo <= 1; ++undo) {
Chris@16 421
Chris@17 422 QAction *action(undo ? m_undoAction : m_redoAction);
Chris@17 423 QAction *menuAction(undo ? m_undoMenuAction : m_redoMenuAction);
Chris@16 424 QMenu *menu(undo ? m_undoMenu : m_redoMenu);
Chris@16 425 CommandStack &stack(undo ? m_undoStack : m_redoStack);
Chris@16 426
Chris@17 427 if (stack.empty()) {
Chris@17 428
Chris@17 429 QString text(undo ? tr("Nothing to undo") : tr("Nothing to redo"));
Chris@17 430
Chris@17 431 action->setEnabled(false);
Chris@17 432 action->setText(text);
Chris@17 433
Chris@17 434 menuAction->setEnabled(false);
Chris@17 435 menuAction->setText(text);
Chris@17 436
Chris@17 437 } else {
Chris@17 438
Chris@17 439 action->setEnabled(true);
Chris@17 440 menuAction->setEnabled(true);
Chris@17 441
Chris@17 442 QString commandName = stack.top()->getName();
Chris@17 443 commandName.replace(QRegExp("&"), "");
Chris@17 444
Chris@17 445 QString text = (undo ? tr("&Undo %1") : tr("Re&do %1"))
Chris@17 446 .arg(commandName);
Chris@17 447
Chris@17 448 action->setText(text);
Chris@17 449 menuAction->setText(text);
Chris@17 450 }
Chris@17 451
Chris@16 452 menu->clear();
Chris@16 453
Chris@16 454 CommandStack tempStack;
Chris@16 455 int j = 0;
Chris@16 456
Chris@46 457 while (j < m_menuLimit && !stack.empty()) {
Chris@16 458
Chris@16 459 Command *command = stack.top();
Chris@16 460 tempStack.push(command);
Chris@16 461 stack.pop();
Chris@16 462
Chris@17 463 QString commandName = command->getName();
Chris@16 464 commandName.replace(QRegExp("&"), "");
Chris@16 465
Chris@16 466 QString text;
Chris@16 467 if (undo) text = tr("&Undo %1").arg(commandName);
Chris@16 468 else text = tr("Re&do %1").arg(commandName);
Chris@16 469
Chris@16 470 QAction *action = menu->addAction(text);
Chris@16 471 m_actionCounts[action] = j++;
Chris@16 472 }
Chris@16 473
Chris@16 474 while (!tempStack.empty()) {
Chris@16 475 stack.push(tempStack.top());
Chris@16 476 tempStack.pop();
Chris@16 477 }
Chris@16 478 }
Chris@16 479 }
Chris@16 480