Chris@16: /* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ Chris@16: Chris@16: /* Chris@16: A waveform viewer and audio annotation editor. Chris@16: Chris Cannam, Queen Mary University of London, 2005-2006 Chris@16: Chris@16: This is experimental software. Not for distribution. Chris@16: */ Chris@16: Chris@16: /* Chris@16: This is a modified version of a source file from the Rosegarden Chris@16: MIDI and audio sequencer and notation editor, copyright 2000-2006 Chris@16: Chris Cannam, distributed under the GNU General Public License. Chris@16: Chris@16: This file contains traces of the KCommandHistory class from the KDE Chris@16: project, copyright 2000 Werner Trobin and David Faure and Chris@16: distributed under the GNU Lesser General Public License. Chris@16: */ Chris@16: Chris@17: #include "CommandHistory.h" Chris@16: Chris@16: #include "Command.h" Chris@16: Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: Chris@16: #include Chris@16: Chris@17: CommandHistory *CommandHistory::m_instance = 0; Chris@17: Chris@17: CommandHistory::CommandHistory() : Chris@16: m_undoLimit(50), Chris@16: m_redoLimit(50), Chris@16: m_savedAt(0) Chris@16: { Chris@16: m_undoAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this); Chris@16: m_undoAction->setShortcut(tr("Ctrl+Z")); Chris@16: connect(m_undoAction, SIGNAL(triggered()), this, SLOT(undo())); Chris@16: Chris@17: m_undoMenuAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this); Chris@17: connect(m_undoMenuAction, SIGNAL(triggered()), this, SLOT(undo())); Chris@17: Chris@16: m_undoMenu = new QMenu(tr("&Undo")); Chris@17: m_undoMenuAction->setMenu(m_undoMenu); Chris@16: connect(m_undoMenu, SIGNAL(triggered(QAction *)), Chris@16: this, SLOT(undoActivated(QAction*))); Chris@16: Chris@17: m_redoAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this); Chris@16: m_redoAction->setShortcut(tr("Ctrl+Shift+Z")); Chris@16: connect(m_redoAction, SIGNAL(triggered()), this, SLOT(redo())); Chris@17: Chris@17: m_redoMenuAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this); Chris@17: connect(m_redoMenuAction, SIGNAL(triggered()), this, SLOT(redo())); Chris@16: Chris@16: m_redoMenu = new QMenu(tr("Re&do")); Chris@17: m_redoMenuAction->setMenu(m_redoMenu); Chris@16: connect(m_redoMenu, SIGNAL(triggered(QAction *)), Chris@16: this, SLOT(redoActivated(QAction*))); Chris@16: } Chris@16: Chris@17: CommandHistory::~CommandHistory() Chris@16: { Chris@16: m_savedAt = -1; Chris@16: clearStack(m_undoStack); Chris@16: clearStack(m_redoStack); Chris@16: Chris@16: delete m_undoMenu; Chris@16: delete m_redoMenu; Chris@16: } Chris@16: Chris@17: CommandHistory * Chris@17: CommandHistory::getInstance() Chris@17: { Chris@17: if (!m_instance) m_instance = new CommandHistory(); Chris@17: return m_instance; Chris@17: } Chris@17: Chris@16: void Chris@17: CommandHistory::clear() Chris@16: { Chris@16: m_savedAt = -1; Chris@16: clearStack(m_undoStack); Chris@16: clearStack(m_redoStack); Chris@16: updateActions(); Chris@16: } Chris@16: Chris@16: void Chris@17: CommandHistory::registerMenu(QMenu *menu) Chris@16: { Chris@16: menu->addAction(m_undoAction); Chris@16: menu->addAction(m_redoAction); Chris@16: } Chris@16: Chris@16: void Chris@17: CommandHistory::registerToolbar(QToolBar *toolbar) Chris@16: { Chris@17: toolbar->addAction(m_undoMenuAction); Chris@17: toolbar->addAction(m_redoMenuAction); Chris@16: } Chris@16: Chris@16: void Chris@17: CommandHistory::addCommand(Command *command, bool execute) Chris@16: { Chris@16: if (!command) return; Chris@16: Chris@17: std::cerr << "MVCH::addCommand: " << command->getName().toLocal8Bit().data() << std::endl; Chris@16: Chris@16: // We can't redo after adding a command Chris@16: clearStack(m_redoStack); Chris@16: Chris@16: // can we reach savedAt? Chris@16: if ((int)m_undoStack.size() < m_savedAt) m_savedAt = -1; // nope Chris@16: Chris@16: m_undoStack.push(command); Chris@16: clipCommands(); Chris@16: Chris@16: if (execute) { Chris@16: command->execute(); Chris@16: } Chris@16: Chris@17: // Emit even if we aren't executing the command, because Chris@17: // someone must have executed it for this to make any sense Chris@17: emit commandExecuted(); Chris@17: emit commandExecuted(command); Chris@17: Chris@16: updateActions(); Chris@16: } Chris@16: Chris@16: void Chris@17: CommandHistory::addExecutedCommand(Command *command) Chris@17: { Chris@17: addCommand(command, false); Chris@17: } Chris@17: Chris@17: void Chris@17: CommandHistory::addCommandAndExecute(Command *command) Chris@17: { Chris@17: addCommand(command, true); Chris@17: } Chris@17: Chris@17: void Chris@17: CommandHistory::undo() Chris@16: { Chris@16: if (m_undoStack.empty()) return; Chris@16: Chris@16: Command *command = m_undoStack.top(); Chris@16: command->unexecute(); Chris@16: emit commandExecuted(); Chris@17: emit commandUnexecuted(command); Chris@16: Chris@16: m_redoStack.push(command); Chris@16: m_undoStack.pop(); Chris@16: Chris@16: clipCommands(); Chris@16: updateActions(); Chris@16: Chris@16: if ((int)m_undoStack.size() == m_savedAt) emit documentRestored(); Chris@16: } Chris@16: Chris@16: void Chris@17: CommandHistory::redo() Chris@16: { Chris@16: if (m_redoStack.empty()) return; Chris@16: Chris@16: Command *command = m_redoStack.top(); Chris@16: command->execute(); Chris@16: emit commandExecuted(); Chris@16: emit commandExecuted(command); Chris@16: Chris@16: m_undoStack.push(command); Chris@16: m_redoStack.pop(); Chris@16: // no need to clip Chris@16: Chris@16: updateActions(); Chris@16: } Chris@16: Chris@16: void Chris@17: CommandHistory::setUndoLimit(int limit) Chris@16: { Chris@16: if (limit > 0 && limit != m_undoLimit) { Chris@16: m_undoLimit = limit; Chris@16: clipCommands(); Chris@16: } Chris@16: } Chris@16: Chris@16: void Chris@17: CommandHistory::setRedoLimit(int limit) Chris@16: { Chris@16: if (limit > 0 && limit != m_redoLimit) { Chris@16: m_redoLimit = limit; Chris@16: clipCommands(); Chris@16: } Chris@16: } Chris@16: Chris@16: void Chris@17: CommandHistory::documentSaved() Chris@16: { Chris@16: m_savedAt = m_undoStack.size(); Chris@16: } Chris@16: Chris@16: void Chris@17: CommandHistory::clipCommands() Chris@16: { Chris@16: if ((int)m_undoStack.size() > m_undoLimit) { Chris@16: m_savedAt -= (m_undoStack.size() - m_undoLimit); Chris@16: } Chris@16: Chris@16: clipStack(m_undoStack, m_undoLimit); Chris@16: clipStack(m_redoStack, m_redoLimit); Chris@16: } Chris@16: Chris@16: void Chris@17: CommandHistory::clipStack(CommandStack &stack, int limit) Chris@16: { Chris@16: int i; Chris@16: Chris@16: if ((int)stack.size() > limit) { Chris@16: Chris@16: CommandStack tempStack; Chris@16: Chris@16: for (i = 0; i < limit; ++i) { Chris@16: Command *command = stack.top(); Chris@17: std::cerr << "MVCH::clipStack: Saving recent command: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl; Chris@16: tempStack.push(stack.top()); Chris@16: stack.pop(); Chris@16: } Chris@16: Chris@16: clearStack(stack); Chris@16: Chris@16: for (i = 0; i < m_undoLimit; ++i) { Chris@16: stack.push(tempStack.top()); Chris@16: tempStack.pop(); Chris@16: } Chris@16: } Chris@16: } Chris@16: Chris@16: void Chris@17: CommandHistory::clearStack(CommandStack &stack) Chris@16: { Chris@16: while (!stack.empty()) { Chris@16: Command *command = stack.top(); Chris@17: std::cerr << "MVCH::clearStack: About to delete command: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl; Chris@16: delete command; Chris@16: stack.pop(); Chris@16: } Chris@16: } Chris@16: Chris@16: void Chris@17: CommandHistory::undoActivated(QAction *action) Chris@16: { Chris@16: int pos = m_actionCounts[action]; Chris@16: for (int i = 0; i <= pos; ++i) { Chris@16: undo(); Chris@16: } Chris@16: } Chris@16: Chris@16: void Chris@17: CommandHistory::redoActivated(QAction *action) Chris@16: { Chris@16: int pos = m_actionCounts[action]; Chris@16: for (int i = 0; i <= pos; ++i) { Chris@16: redo(); Chris@16: } Chris@16: } Chris@16: Chris@16: void Chris@17: CommandHistory::updateActions() Chris@16: { Chris@16: m_actionCounts.clear(); Chris@16: Chris@16: for (int undo = 0; undo <= 1; ++undo) { Chris@16: Chris@17: QAction *action(undo ? m_undoAction : m_redoAction); Chris@17: QAction *menuAction(undo ? m_undoMenuAction : m_redoMenuAction); Chris@16: QMenu *menu(undo ? m_undoMenu : m_redoMenu); Chris@16: CommandStack &stack(undo ? m_undoStack : m_redoStack); Chris@16: Chris@17: if (stack.empty()) { Chris@17: Chris@17: QString text(undo ? tr("Nothing to undo") : tr("Nothing to redo")); Chris@17: Chris@17: action->setEnabled(false); Chris@17: action->setText(text); Chris@17: Chris@17: menuAction->setEnabled(false); Chris@17: menuAction->setText(text); Chris@17: Chris@17: } else { Chris@17: Chris@17: action->setEnabled(true); Chris@17: menuAction->setEnabled(true); Chris@17: Chris@17: QString commandName = stack.top()->getName(); Chris@17: commandName.replace(QRegExp("&"), ""); Chris@17: Chris@17: QString text = (undo ? tr("&Undo %1") : tr("Re&do %1")) Chris@17: .arg(commandName); Chris@17: Chris@17: action->setText(text); Chris@17: menuAction->setText(text); Chris@17: } Chris@17: Chris@16: menu->clear(); Chris@16: Chris@16: CommandStack tempStack; Chris@16: int j = 0; Chris@16: Chris@16: while (j < 10 && !stack.empty()) { Chris@16: Chris@16: Command *command = stack.top(); Chris@16: tempStack.push(command); Chris@16: stack.pop(); Chris@16: Chris@17: QString commandName = command->getName(); Chris@16: commandName.replace(QRegExp("&"), ""); Chris@16: Chris@16: QString text; Chris@16: if (undo) text = tr("&Undo %1").arg(commandName); Chris@16: else text = tr("Re&do %1").arg(commandName); Chris@16: Chris@16: QAction *action = menu->addAction(text); Chris@16: m_actionCounts[action] = j++; Chris@16: } Chris@16: Chris@16: while (!tempStack.empty()) { Chris@16: stack.push(tempStack.top()); Chris@16: tempStack.pop(); Chris@16: } Chris@16: } Chris@16: } Chris@16: