Mercurial > hg > svcore
view base/CommandHistory.cpp @ 115:90ade4fa63be
* Fix serious failure to reload "imported" (i.e. all non-derived non-main)
models from .sv file
* Give a short playback duration to notes with formal duration of 0 or 1
* Show crosshairs on spectrogram even when there is another layer on top
(if it isn't opaque)
* Always paste to the same time in the layer as the cut/copy was from, rather
than to the playback pointer -- less flexible, but more predictable and
less annoying. We probably need a way to get the old behaviour if pasting
from somewhere else in the future (e.g. from a text file), but we can't do
that yet anyway
* Use a compound operation for dragging and resizing selections, so as to
ensure a single undo operation works
* Use a note model as the target for feature extraction plugins that output
variable samplerate data with more than one value per feature
* Avoid possible crashes in cut/paste if a layer proves to have no model
author | Chris Cannam |
---|---|
date | Thu, 11 May 2006 11:35:46 +0000 |
parents | c983dda79f72 |
children | c30728d5625c |
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See the file COPYING included with this distribution for more information. */ /* This is a modified version of a source file from the Rosegarden MIDI and audio sequencer and notation editor, copyright 2000-2006 Chris Cannam, distributed under the GNU General Public License. This file contains traces of the KCommandHistory class from the KDE project, copyright 2000 Werner Trobin and David Faure and distributed under the GNU Lesser General Public License. */ #include "CommandHistory.h" #include "Command.h" #include <QRegExp> #include <QMenu> #include <QToolBar> #include <QString> #include <QTimer> #include <iostream> CommandHistory *CommandHistory::m_instance = 0; CommandHistory::CommandHistory() : m_undoLimit(50), m_redoLimit(50), m_menuLimit(15), m_savedAt(0), m_currentCompound(0), m_executeCompound(false), m_currentBundle(0), m_bundleTimer(0), m_bundleTimeout(5000) { m_undoAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this); m_undoAction->setShortcut(tr("Ctrl+Z")); connect(m_undoAction, SIGNAL(triggered()), this, SLOT(undo())); m_undoMenuAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this); connect(m_undoMenuAction, SIGNAL(triggered()), this, SLOT(undo())); m_undoMenu = new QMenu(tr("&Undo")); m_undoMenuAction->setMenu(m_undoMenu); connect(m_undoMenu, SIGNAL(triggered(QAction *)), this, SLOT(undoActivated(QAction*))); m_redoAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this); m_redoAction->setShortcut(tr("Ctrl+Shift+Z")); connect(m_redoAction, SIGNAL(triggered()), this, SLOT(redo())); m_redoMenuAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this); connect(m_redoMenuAction, SIGNAL(triggered()), this, SLOT(redo())); m_redoMenu = new QMenu(tr("Re&do")); m_redoMenuAction->setMenu(m_redoMenu); connect(m_redoMenu, SIGNAL(triggered(QAction *)), this, SLOT(redoActivated(QAction*))); } CommandHistory::~CommandHistory() { m_savedAt = -1; clearStack(m_undoStack); clearStack(m_redoStack); delete m_undoMenu; delete m_redoMenu; } CommandHistory * CommandHistory::getInstance() { if (!m_instance) m_instance = new CommandHistory(); return m_instance; } void CommandHistory::clear() { // std::cerr << "CommandHistory::clear()" << std::endl; closeBundle(); m_savedAt = -1; clearStack(m_undoStack); clearStack(m_redoStack); updateActions(); } void CommandHistory::registerMenu(QMenu *menu) { menu->addAction(m_undoAction); menu->addAction(m_redoAction); } void CommandHistory::registerToolbar(QToolBar *toolbar) { toolbar->addAction(m_undoMenuAction); toolbar->addAction(m_redoMenuAction); } void CommandHistory::addCommand(Command *command) { if (!command) return; if (m_currentCompound) { addToCompound(command, m_executeCompound); return; } addCommand(command, true); } void CommandHistory::addCommand(Command *command, bool execute, bool bundle) { if (!command) return; if (m_currentCompound) { addToCompound(command, execute); return; } if (bundle) { addToBundle(command, execute); return; } else if (m_currentBundle) { closeBundle(); } // std::cerr << "CommandHistory::addCommand: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl; // We can't redo after adding a command // std::cerr << "CommandHistory::clearing redo stack" << std::endl; clearStack(m_redoStack); // can we reach savedAt? if ((int)m_undoStack.size() < m_savedAt) m_savedAt = -1; // nope m_undoStack.push(command); clipCommands(); if (execute) { command->execute(); } // Emit even if we aren't executing the command, because // someone must have executed it for this to make any sense emit commandExecuted(); emit commandExecuted(command); updateActions(); } void CommandHistory::addToBundle(Command *command, bool execute) { if (m_currentBundle) { if (!command || (command->getName() != m_currentBundleName)) { closeBundle(); } } if (!command) return; if (!m_currentBundle) { // need to addCommand before setting m_currentBundle, as addCommand // with bundle false will reset m_currentBundle to 0 MacroCommand *mc = new MacroCommand(command->getName()); addCommand(mc, false); m_currentBundle = mc; m_currentBundleName = command->getName(); } if (execute) command->execute(); m_currentBundle->addCommand(command); delete m_bundleTimer; m_bundleTimer = new QTimer(this); connect(m_bundleTimer, SIGNAL(timeout()), this, SLOT(bundleTimerTimeout())); m_bundleTimer->start(m_bundleTimeout); } void CommandHistory::closeBundle() { m_currentBundle = 0; m_currentBundleName = ""; } void CommandHistory::bundleTimerTimeout() { closeBundle(); } void CommandHistory::addToCompound(Command *command, bool execute) { // std::cerr << "CommandHistory::addToCompound: " << command->getName().toLocal8Bit().data() << std::endl; if (execute) command->execute(); m_currentCompound->addCommand(command); } void CommandHistory::startCompoundOperation(QString name, bool execute) { if (m_currentCompound) { std::cerr << "CommandHistory::startCompoundOperation: ERROR: compound operation already in progress!" << std::endl; std::cerr << "(name is " << m_currentCompound->getName().toLocal8Bit().data() << ")" << std::endl; } closeBundle(); m_currentCompound = new MacroCommand(name); m_executeCompound = execute; } void CommandHistory::endCompoundOperation() { if (!m_currentCompound) { std::cerr << "CommandHistory::endCompoundOperation: ERROR: no compound operation in progress!" << std::endl; } MacroCommand *toAdd = m_currentCompound; m_currentCompound = 0; if (toAdd->haveCommands()) { // We don't execute the macro command here, because we have // been executing the individual commands as we went along if // m_executeCompound was true. addCommand(toAdd, false); } } void CommandHistory::addExecutedCommand(Command *command) { addCommand(command, false); } void CommandHistory::addCommandAndExecute(Command *command) { addCommand(command, true); } void CommandHistory::undo() { if (m_undoStack.empty()) return; closeBundle(); Command *command = m_undoStack.top(); command->unexecute(); emit commandExecuted(); emit commandUnexecuted(command); m_redoStack.push(command); m_undoStack.pop(); clipCommands(); updateActions(); if ((int)m_undoStack.size() == m_savedAt) emit documentRestored(); } void CommandHistory::redo() { if (m_redoStack.empty()) return; closeBundle(); Command *command = m_redoStack.top(); command->execute(); emit commandExecuted(); emit commandExecuted(command); m_undoStack.push(command); m_redoStack.pop(); // no need to clip updateActions(); if ((int)m_undoStack.size() == m_savedAt) emit documentRestored(); } void CommandHistory::setUndoLimit(int limit) { if (limit > 0 && limit != m_undoLimit) { m_undoLimit = limit; clipCommands(); } } void CommandHistory::setRedoLimit(int limit) { if (limit > 0 && limit != m_redoLimit) { m_redoLimit = limit; clipCommands(); } } void CommandHistory::setMenuLimit(int limit) { m_menuLimit = limit; updateActions(); } void CommandHistory::setBundleTimeout(int ms) { m_bundleTimeout = ms; } void CommandHistory::documentSaved() { closeBundle(); m_savedAt = m_undoStack.size(); } void CommandHistory::clipCommands() { if ((int)m_undoStack.size() > m_undoLimit) { m_savedAt -= (m_undoStack.size() - m_undoLimit); } clipStack(m_undoStack, m_undoLimit); clipStack(m_redoStack, m_redoLimit); } void CommandHistory::clipStack(CommandStack &stack, int limit) { int i; if ((int)stack.size() > limit) { CommandStack tempStack; for (i = 0; i < limit; ++i) { Command *command = stack.top(); // std::cerr << "CommandHistory::clipStack: Saving recent command: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl; tempStack.push(stack.top()); stack.pop(); } clearStack(stack); for (i = 0; i < m_undoLimit; ++i) { stack.push(tempStack.top()); tempStack.pop(); } } } void CommandHistory::clearStack(CommandStack &stack) { while (!stack.empty()) { Command *command = stack.top(); // Not safe to call getName() on a command about to be deleted std::cerr << "CommandHistory::clearStack: About to delete command " << command << std::endl; delete command; stack.pop(); } } void CommandHistory::undoActivated(QAction *action) { int pos = m_actionCounts[action]; for (int i = 0; i <= pos; ++i) { undo(); } } void CommandHistory::redoActivated(QAction *action) { int pos = m_actionCounts[action]; for (int i = 0; i <= pos; ++i) { redo(); } } void CommandHistory::updateActions() { m_actionCounts.clear(); for (int undo = 0; undo <= 1; ++undo) { QAction *action(undo ? m_undoAction : m_redoAction); QAction *menuAction(undo ? m_undoMenuAction : m_redoMenuAction); QMenu *menu(undo ? m_undoMenu : m_redoMenu); CommandStack &stack(undo ? m_undoStack : m_redoStack); if (stack.empty()) { QString text(undo ? tr("Nothing to undo") : tr("Nothing to redo")); action->setEnabled(false); action->setText(text); menuAction->setEnabled(false); menuAction->setText(text); } else { action->setEnabled(true); menuAction->setEnabled(true); QString commandName = stack.top()->getName(); commandName.replace(QRegExp("&"), ""); QString text = (undo ? tr("&Undo %1") : tr("Re&do %1")) .arg(commandName); action->setText(text); menuAction->setText(text); } menu->clear(); CommandStack tempStack; int j = 0; while (j < m_menuLimit && !stack.empty()) { Command *command = stack.top(); tempStack.push(command); stack.pop(); QString commandName = command->getName(); commandName.replace(QRegExp("&"), ""); QString text; if (undo) text = tr("&Undo %1").arg(commandName); else text = tr("Re&do %1").arg(commandName); QAction *action = menu->addAction(text); m_actionCounts[action] = j++; } while (!tempStack.empty()) { stack.push(tempStack.top()); tempStack.pop(); } } }