diff widgets/CommandHistory.cpp @ 376:e1a9e478b7f2

* juggle some files around in order to free audioio, base, and system libraries from dependency on QtGui
author Chris Cannam
date Wed, 12 Mar 2008 17:42:56 +0000
parents
children 0bcb449d15f4
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/CommandHistory.cpp	Wed Mar 12 17:42:56 2008 +0000
@@ -0,0 +1,487 @@
+/* -*- 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 "base/Command.h"
+
+#include <QRegExp>
+#include <QMenu>
+#include <QToolBar>
+#include <QString>
+#include <QTimer>
+#include <QAction>
+
+#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"));
+    m_undoAction->setStatusTip(tr("Undo the last editing operation"));
+    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"));
+    m_redoAction->setStatusTip(tr("Redo the last operation that was undone"));
+    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 (!m_currentCompound) {
+	std::cerr << "CommandHistory::addToCompound: ERROR: no compound operation in progress!" << std::endl;
+        return;
+    }
+
+    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;
+        return;
+    }
+ 
+    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;
+        return;
+    }
+
+    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();
+	}
+    }
+}
+