lbajardsilogic@0: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ lbajardsilogic@0: lbajardsilogic@0: /* lbajardsilogic@0: Sonic Visualiser lbajardsilogic@0: An audio file viewer and annotation editor. lbajardsilogic@0: Centre for Digital Music, Queen Mary, University of London. lbajardsilogic@0: lbajardsilogic@0: This program is free software; you can redistribute it and/or lbajardsilogic@0: modify it under the terms of the GNU General Public License as lbajardsilogic@0: published by the Free Software Foundation; either version 2 of the lbajardsilogic@0: License, or (at your option) any later version. See the file lbajardsilogic@0: COPYING included with this distribution for more information. lbajardsilogic@0: */ lbajardsilogic@0: lbajardsilogic@0: /* lbajardsilogic@0: This is a modified version of a source file from the Rosegarden lbajardsilogic@0: MIDI and audio sequencer and notation editor, copyright 2000-2006 lbajardsilogic@0: Chris Cannam, distributed under the GNU General Public License. lbajardsilogic@0: lbajardsilogic@0: This file contains traces of the KCommandHistory class from the KDE lbajardsilogic@0: project, copyright 2000 Werner Trobin and David Faure and lbajardsilogic@0: distributed under the GNU Lesser General Public License. lbajardsilogic@0: */ lbajardsilogic@0: lbajardsilogic@0: #include "CommandHistory.h" lbajardsilogic@0: lbajardsilogic@0: #include "Command.h" lbajardsilogic@0: lbajardsilogic@0: #include lbajardsilogic@0: #include lbajardsilogic@0: #include lbajardsilogic@0: #include lbajardsilogic@0: #include lbajardsilogic@0: lbajardsilogic@0: #include lbajardsilogic@0: lbajardsilogic@0: CommandHistory *CommandHistory::m_instance = 0; lbajardsilogic@0: lbajardsilogic@0: CommandHistory::CommandHistory() : lbajardsilogic@0: m_undoLimit(50), lbajardsilogic@0: m_redoLimit(50), lbajardsilogic@0: m_menuLimit(15), lbajardsilogic@0: m_savedAt(0), lbajardsilogic@0: m_currentCompound(0), lbajardsilogic@0: m_executeCompound(false), lbajardsilogic@0: m_currentBundle(0), lbajardsilogic@0: m_bundleTimer(0), lbajardsilogic@0: m_bundleTimeout(5000) lbajardsilogic@0: { lbajardsilogic@0: m_undoAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this); lbajardsilogic@0: m_undoAction->setShortcut(tr("Ctrl+Z")); lbajardsilogic@0: m_undoAction->setStatusTip(tr("Undo the last editing operation")); lbajardsilogic@0: connect(m_undoAction, SIGNAL(triggered()), this, SLOT(undo())); lbajardsilogic@0: lbajardsilogic@0: m_undoMenuAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this); lbajardsilogic@0: connect(m_undoMenuAction, SIGNAL(triggered()), this, SLOT(undo())); lbajardsilogic@0: lbajardsilogic@0: m_undoMenu = new QMenu(tr("&Undo")); lbajardsilogic@0: m_undoMenuAction->setMenu(m_undoMenu); lbajardsilogic@0: connect(m_undoMenu, SIGNAL(triggered(QAction *)), lbajardsilogic@0: this, SLOT(undoActivated(QAction*))); lbajardsilogic@0: lbajardsilogic@0: m_redoAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this); lbajardsilogic@0: m_redoAction->setShortcut(tr("Ctrl+Shift+Z")); lbajardsilogic@0: m_redoAction->setStatusTip(tr("Redo the last operation that was undone")); lbajardsilogic@0: connect(m_redoAction, SIGNAL(triggered()), this, SLOT(redo())); lbajardsilogic@0: lbajardsilogic@0: m_redoMenuAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this); lbajardsilogic@0: connect(m_redoMenuAction, SIGNAL(triggered()), this, SLOT(redo())); lbajardsilogic@0: lbajardsilogic@0: m_redoMenu = new QMenu(tr("Re&do")); lbajardsilogic@0: m_redoMenuAction->setMenu(m_redoMenu); lbajardsilogic@0: connect(m_redoMenu, SIGNAL(triggered(QAction *)), lbajardsilogic@0: this, SLOT(redoActivated(QAction*))); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: CommandHistory::~CommandHistory() lbajardsilogic@0: { lbajardsilogic@0: m_savedAt = -1; lbajardsilogic@0: clearStack(m_undoStack); lbajardsilogic@0: clearStack(m_redoStack); lbajardsilogic@0: lbajardsilogic@0: delete m_undoMenu; lbajardsilogic@0: delete m_redoMenu; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: CommandHistory * lbajardsilogic@0: CommandHistory::getInstance() lbajardsilogic@0: { lbajardsilogic@0: if (!m_instance) m_instance = new CommandHistory(); lbajardsilogic@0: return m_instance; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::clear() lbajardsilogic@0: { lbajardsilogic@0: // std::cerr << "CommandHistory::clear()" << std::endl; lbajardsilogic@0: closeBundle(); lbajardsilogic@0: m_savedAt = -1; lbajardsilogic@0: clearStack(m_undoStack); lbajardsilogic@0: clearStack(m_redoStack); lbajardsilogic@0: updateActions(); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::registerMenu(QMenu *menu) lbajardsilogic@0: { lbajardsilogic@0: menu->addAction(m_undoAction); lbajardsilogic@0: menu->addAction(m_redoAction); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::registerToolbar(QToolBar *toolbar) lbajardsilogic@0: { lbajardsilogic@0: toolbar->addAction(m_undoMenuAction); lbajardsilogic@0: toolbar->addAction(m_redoMenuAction); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::addCommand(Command *command) lbajardsilogic@0: { lbajardsilogic@0: if (!command) return; lbajardsilogic@0: lbajardsilogic@0: if (m_currentCompound) { lbajardsilogic@0: addToCompound(command, m_executeCompound); lbajardsilogic@0: return; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: addCommand(command, true); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::addCommand(Command *command, bool execute, bool bundle) lbajardsilogic@0: { lbajardsilogic@0: if (!command) return; lbajardsilogic@0: lbajardsilogic@0: if (m_currentCompound) { lbajardsilogic@0: addToCompound(command, execute); lbajardsilogic@0: return; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: if (bundle) { lbajardsilogic@0: addToBundle(command, execute); lbajardsilogic@0: return; lbajardsilogic@0: } else if (m_currentBundle) { lbajardsilogic@0: closeBundle(); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: // std::cerr << "CommandHistory::addCommand: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl; lbajardsilogic@0: lbajardsilogic@0: // We can't redo after adding a command lbajardsilogic@0: // std::cerr << "CommandHistory::clearing redo stack" << std::endl; lbajardsilogic@0: clearStack(m_redoStack); lbajardsilogic@0: lbajardsilogic@0: // can we reach savedAt? lbajardsilogic@0: if ((int)m_undoStack.size() < m_savedAt) m_savedAt = -1; // nope lbajardsilogic@0: lbajardsilogic@0: m_undoStack.push(command); lbajardsilogic@0: clipCommands(); lbajardsilogic@0: lbajardsilogic@0: if (execute) { lbajardsilogic@0: command->execute(); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: // Emit even if we aren't executing the command, because lbajardsilogic@0: // someone must have executed it for this to make any sense lbajardsilogic@0: emit commandExecuted(); lbajardsilogic@0: emit commandExecuted(command); lbajardsilogic@0: lbajardsilogic@0: updateActions(); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::addToBundle(Command *command, bool execute) lbajardsilogic@0: { lbajardsilogic@0: if (m_currentBundle) { lbajardsilogic@0: if (!command || (command->getName() != m_currentBundleName)) { lbajardsilogic@0: closeBundle(); lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: if (!command) return; lbajardsilogic@0: lbajardsilogic@0: if (!m_currentBundle) { lbajardsilogic@0: // need to addCommand before setting m_currentBundle, as addCommand lbajardsilogic@0: // with bundle false will reset m_currentBundle to 0 lbajardsilogic@0: MacroCommand *mc = new MacroCommand(command->getName()); lbajardsilogic@0: addCommand(mc, false); lbajardsilogic@0: m_currentBundle = mc; lbajardsilogic@0: m_currentBundleName = command->getName(); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: if (execute) command->execute(); lbajardsilogic@0: m_currentBundle->addCommand(command); lbajardsilogic@0: lbajardsilogic@0: delete m_bundleTimer; lbajardsilogic@0: m_bundleTimer = new QTimer(this); lbajardsilogic@0: connect(m_bundleTimer, SIGNAL(timeout()), this, SLOT(bundleTimerTimeout())); lbajardsilogic@0: m_bundleTimer->start(m_bundleTimeout); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::closeBundle() lbajardsilogic@0: { lbajardsilogic@0: m_currentBundle = 0; lbajardsilogic@0: m_currentBundleName = ""; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::bundleTimerTimeout() lbajardsilogic@0: { lbajardsilogic@0: closeBundle(); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::addToCompound(Command *command, bool execute) lbajardsilogic@0: { lbajardsilogic@0: // std::cerr << "CommandHistory::addToCompound: " << command->getName().toLocal8Bit().data() << std::endl; lbajardsilogic@0: lbajardsilogic@0: if (execute) command->execute(); lbajardsilogic@0: m_currentCompound->addCommand(command); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::startCompoundOperation(QString name, bool execute) lbajardsilogic@0: { lbajardsilogic@0: if (m_currentCompound) { lbajardsilogic@0: std::cerr << "CommandHistory::startCompoundOperation: ERROR: compound operation already in progress!" << std::endl; lbajardsilogic@0: std::cerr << "(name is " << m_currentCompound->getName().toLocal8Bit().data() << ")" << std::endl; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: closeBundle(); lbajardsilogic@0: lbajardsilogic@0: m_currentCompound = new MacroCommand(name); lbajardsilogic@0: m_executeCompound = execute; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::endCompoundOperation() lbajardsilogic@0: { lbajardsilogic@0: if (!m_currentCompound) { lbajardsilogic@0: std::cerr << "CommandHistory::endCompoundOperation: ERROR: no compound operation in progress!" << std::endl; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: MacroCommand *toAdd = m_currentCompound; lbajardsilogic@0: m_currentCompound = 0; lbajardsilogic@0: lbajardsilogic@0: if (toAdd->haveCommands()) { lbajardsilogic@0: lbajardsilogic@0: // We don't execute the macro command here, because we have lbajardsilogic@0: // been executing the individual commands as we went along if lbajardsilogic@0: // m_executeCompound was true. lbajardsilogic@0: addCommand(toAdd, false); lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::addExecutedCommand(Command *command) lbajardsilogic@0: { lbajardsilogic@0: addCommand(command, false); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::addCommandAndExecute(Command *command) lbajardsilogic@0: { lbajardsilogic@0: addCommand(command, true); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::undo() lbajardsilogic@0: { lbajardsilogic@0: if (m_undoStack.empty()) return; lbajardsilogic@0: lbajardsilogic@0: closeBundle(); lbajardsilogic@0: lbajardsilogic@0: Command *command = m_undoStack.top(); lbajardsilogic@0: command->unexecute(); lbajardsilogic@0: emit commandExecuted(); lbajardsilogic@0: emit commandUnexecuted(command); lbajardsilogic@0: lbajardsilogic@0: m_redoStack.push(command); lbajardsilogic@0: m_undoStack.pop(); lbajardsilogic@0: lbajardsilogic@0: clipCommands(); lbajardsilogic@0: updateActions(); lbajardsilogic@0: lbajardsilogic@0: if ((int)m_undoStack.size() == m_savedAt) emit documentRestored(); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::redo() lbajardsilogic@0: { lbajardsilogic@0: if (m_redoStack.empty()) return; lbajardsilogic@0: lbajardsilogic@0: closeBundle(); lbajardsilogic@0: lbajardsilogic@0: Command *command = m_redoStack.top(); lbajardsilogic@0: command->execute(); lbajardsilogic@0: emit commandExecuted(); lbajardsilogic@0: emit commandExecuted(command); lbajardsilogic@0: lbajardsilogic@0: m_undoStack.push(command); lbajardsilogic@0: m_redoStack.pop(); lbajardsilogic@0: // no need to clip lbajardsilogic@0: lbajardsilogic@0: updateActions(); lbajardsilogic@0: lbajardsilogic@0: if ((int)m_undoStack.size() == m_savedAt) emit documentRestored(); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::setUndoLimit(int limit) lbajardsilogic@0: { lbajardsilogic@0: if (limit > 0 && limit != m_undoLimit) { lbajardsilogic@0: m_undoLimit = limit; lbajardsilogic@0: clipCommands(); lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::setRedoLimit(int limit) lbajardsilogic@0: { lbajardsilogic@0: if (limit > 0 && limit != m_redoLimit) { lbajardsilogic@0: m_redoLimit = limit; lbajardsilogic@0: clipCommands(); lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::setMenuLimit(int limit) lbajardsilogic@0: { lbajardsilogic@0: m_menuLimit = limit; lbajardsilogic@0: updateActions(); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::setBundleTimeout(int ms) lbajardsilogic@0: { lbajardsilogic@0: m_bundleTimeout = ms; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::documentSaved() lbajardsilogic@0: { lbajardsilogic@0: closeBundle(); lbajardsilogic@0: m_savedAt = m_undoStack.size(); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::clipCommands() lbajardsilogic@0: { lbajardsilogic@0: if ((int)m_undoStack.size() > m_undoLimit) { lbajardsilogic@0: m_savedAt -= (m_undoStack.size() - m_undoLimit); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: clipStack(m_undoStack, m_undoLimit); lbajardsilogic@0: clipStack(m_redoStack, m_redoLimit); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::clipStack(CommandStack &stack, int limit) lbajardsilogic@0: { lbajardsilogic@0: int i; lbajardsilogic@0: lbajardsilogic@0: if ((int)stack.size() > limit) { lbajardsilogic@0: lbajardsilogic@0: CommandStack tempStack; lbajardsilogic@0: lbajardsilogic@0: for (i = 0; i < limit; ++i) { lbajardsilogic@0: // Command *command = stack.top(); lbajardsilogic@0: // std::cerr << "CommandHistory::clipStack: Saving recent command: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl; lbajardsilogic@0: tempStack.push(stack.top()); lbajardsilogic@0: stack.pop(); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: clearStack(stack); lbajardsilogic@0: lbajardsilogic@0: for (i = 0; i < m_undoLimit; ++i) { lbajardsilogic@0: stack.push(tempStack.top()); lbajardsilogic@0: tempStack.pop(); lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::clearStack(CommandStack &stack) lbajardsilogic@0: { lbajardsilogic@0: while (!stack.empty()) { lbajardsilogic@0: Command *command = stack.top(); lbajardsilogic@0: // Not safe to call getName() on a command about to be deleted lbajardsilogic@0: // std::cerr << "CommandHistory::clearStack: About to delete command " << command << std::endl; lbajardsilogic@0: delete command; lbajardsilogic@0: stack.pop(); lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::undoActivated(QAction *action) lbajardsilogic@0: { lbajardsilogic@0: int pos = m_actionCounts[action]; lbajardsilogic@0: for (int i = 0; i <= pos; ++i) { lbajardsilogic@0: undo(); lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::redoActivated(QAction *action) lbajardsilogic@0: { lbajardsilogic@0: int pos = m_actionCounts[action]; lbajardsilogic@0: for (int i = 0; i <= pos; ++i) { lbajardsilogic@0: redo(); lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: CommandHistory::updateActions() lbajardsilogic@0: { lbajardsilogic@0: m_actionCounts.clear(); lbajardsilogic@0: lbajardsilogic@0: for (int undo = 0; undo <= 1; ++undo) { lbajardsilogic@0: lbajardsilogic@0: QAction *action(undo ? m_undoAction : m_redoAction); lbajardsilogic@0: QAction *menuAction(undo ? m_undoMenuAction : m_redoMenuAction); lbajardsilogic@0: QMenu *menu(undo ? m_undoMenu : m_redoMenu); lbajardsilogic@0: CommandStack &stack(undo ? m_undoStack : m_redoStack); lbajardsilogic@0: lbajardsilogic@0: if (stack.empty()) { lbajardsilogic@0: lbajardsilogic@0: QString text(undo ? tr("Nothing to undo") : tr("Nothing to redo")); lbajardsilogic@0: lbajardsilogic@0: action->setEnabled(false); lbajardsilogic@0: action->setText(text); lbajardsilogic@0: lbajardsilogic@0: menuAction->setEnabled(false); lbajardsilogic@0: menuAction->setText(text); lbajardsilogic@0: lbajardsilogic@0: } else { lbajardsilogic@0: lbajardsilogic@0: action->setEnabled(true); lbajardsilogic@0: menuAction->setEnabled(true); lbajardsilogic@0: lbajardsilogic@0: QString commandName = stack.top()->getName(); lbajardsilogic@0: commandName.replace(QRegExp("&"), ""); lbajardsilogic@0: lbajardsilogic@0: QString text = (undo ? tr("&Undo %1") : tr("Re&do %1")) lbajardsilogic@0: .arg(commandName); lbajardsilogic@0: lbajardsilogic@0: action->setText(text); lbajardsilogic@0: menuAction->setText(text); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: menu->clear(); lbajardsilogic@0: lbajardsilogic@0: CommandStack tempStack; lbajardsilogic@0: int j = 0; lbajardsilogic@0: lbajardsilogic@0: while (j < m_menuLimit && !stack.empty()) { lbajardsilogic@0: lbajardsilogic@0: Command *command = stack.top(); lbajardsilogic@0: tempStack.push(command); lbajardsilogic@0: stack.pop(); lbajardsilogic@0: lbajardsilogic@0: QString commandName = command->getName(); lbajardsilogic@0: commandName.replace(QRegExp("&"), ""); lbajardsilogic@0: lbajardsilogic@0: QString text; lbajardsilogic@0: if (undo) text = tr("&Undo %1").arg(commandName); lbajardsilogic@0: else text = tr("Re&do %1").arg(commandName); lbajardsilogic@0: lbajardsilogic@0: QAction *action = menu->addAction(text); lbajardsilogic@0: m_actionCounts[action] = j++; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: while (!tempStack.empty()) { lbajardsilogic@0: stack.push(tempStack.top()); lbajardsilogic@0: tempStack.pop(); lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: