changeset 16:cc98d496d52b

* Add command history class, and basic undo/redo menus. No actual commands to undo/redo yet. Selecting the placeholders sometimes seems to cause a crash, so this looks a little uncertain so far. * Add Rename Layer * Remove models from playback when their layers are removed (and ref counts hit zero) * Don't hang around waiting so much when there's work to be done in the audio buffer fill thread * Put more sensible names on layers generated from transforms * Add basic editing to time-value layer like existing editing in time-instants layer, and make both of them snap to the appropriate resolution during drag
author Chris Cannam
date Mon, 30 Jan 2006 17:51:56 +0000
parents 47500c27ac26
children 2fb933f88604
files base/Command.h base/MultiViewCommandHistory.cpp base/MultiViewCommandHistory.h base/View.cpp base/View.h base/ViewManager.cpp transform/TransformFactory.cpp transform/TransformFactory.h
diffstat 8 files changed, 513 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/Command.h	Mon Jan 30 17:51:56 2006 +0000
@@ -0,0 +1,22 @@
+/* -*- c-basic-offset: 4 -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    A waveform viewer and audio annotation editor.
+    Chris Cannam, Queen Mary University of London, 2005-2006
+    
+    This is experimental software.  Not for distribution.
+*/
+
+#ifndef _COMMAND_H_
+#define _COMMAND_H_
+
+class Command
+{
+public:
+    virtual void execute() = 0;
+    virtual void unexecute() = 0;
+    virtual QString name() const = 0;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/MultiViewCommandHistory.cpp	Mon Jan 30 17:51:56 2006 +0000
@@ -0,0 +1,299 @@
+/* -*- c-basic-offset: 4 -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    A waveform viewer and audio annotation editor.
+    Chris Cannam, Queen Mary University of London, 2005-2006
+    
+    This is experimental software.  Not for distribution.
+*/
+
+/*
+   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 "MultiViewCommandHistory.h"
+
+#include "Command.h"
+
+#include <QRegExp>
+#include <QMenu>
+#include <QToolBar>
+#include <QString>
+
+#include <iostream>
+
+MultiViewCommandHistory::MultiViewCommandHistory() :
+    m_undoLimit(50),
+    m_redoLimit(50),
+    m_savedAt(0)
+{
+    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_undoMenu = new QMenu(tr("&Undo"));
+    m_undoAction->setMenu(m_undoMenu);
+    connect(m_undoMenu, SIGNAL(triggered(QAction *)),
+	    this, SLOT(undoActivated(QAction*)));
+
+    m_redoAction = new QAction(QIcon(":/icons/redo.png"), tr("&Redo"), this);
+    m_redoAction->setShortcut(tr("Ctrl+Shift+Z"));
+    connect(m_redoAction, SIGNAL(triggered()), this, SLOT(redo()));
+
+    m_redoMenu = new QMenu(tr("Re&do"));
+    m_redoAction->setMenu(m_redoMenu);
+    connect(m_redoMenu, SIGNAL(triggered(QAction *)),
+	    this, SLOT(redoActivated(QAction*)));
+}
+
+MultiViewCommandHistory::~MultiViewCommandHistory()
+{
+    m_savedAt = -1;
+    clearStack(m_undoStack);
+    clearStack(m_redoStack);
+
+    delete m_undoMenu;
+    delete m_redoMenu;
+}
+
+void
+MultiViewCommandHistory::clear()
+{
+    m_savedAt = -1;
+    clearStack(m_undoStack);
+    clearStack(m_redoStack);
+    updateActions();
+}
+
+void
+MultiViewCommandHistory::registerMenu(QMenu *menu)
+{
+    menu->addAction(m_undoAction);
+    menu->addAction(m_redoAction);
+}
+
+void
+MultiViewCommandHistory::registerToolbar(QToolBar *toolbar)
+{
+    toolbar->addAction(m_undoAction);
+    toolbar->addAction(m_redoAction);
+}
+
+void
+MultiViewCommandHistory::addCommand(Command *command, bool execute)
+{
+    if (!command) return;
+
+    std::cerr << "MVCH::addCommand: " << command->name().toLocal8Bit().data() << std::endl;
+
+    // We can't redo after adding a command
+    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 commandExecuted();
+	emit commandExecuted(command);
+    }
+
+//    updateButtons();
+    updateActions();
+}
+
+void
+MultiViewCommandHistory::undo()
+{
+    if (m_undoStack.empty()) return;
+
+    Command *command = m_undoStack.top();
+    command->unexecute();
+    emit commandExecuted();
+    emit commandExecuted(command);
+
+    m_redoStack.push(command);
+    m_undoStack.pop();
+
+    clipCommands();
+    updateActions();
+
+    if ((int)m_undoStack.size() == m_savedAt) emit documentRestored();
+}
+
+void
+MultiViewCommandHistory::redo()
+{
+    if (m_redoStack.empty()) return;
+
+    Command *command = m_redoStack.top();
+    command->execute();
+    emit commandExecuted();
+    emit commandExecuted(command);
+
+    m_undoStack.push(command);
+    m_redoStack.pop();
+    // no need to clip
+
+    updateActions();
+}
+
+void
+MultiViewCommandHistory::setUndoLimit(int limit)
+{
+    if (limit > 0 && limit != m_undoLimit) {
+        m_undoLimit = limit;
+        clipCommands();
+    }
+}
+
+void
+MultiViewCommandHistory::setRedoLimit(int limit)
+{
+    if (limit > 0 && limit != m_redoLimit) {
+        m_redoLimit = limit;
+        clipCommands();
+    }
+}
+
+void
+MultiViewCommandHistory::documentSaved()
+{
+    m_savedAt = m_undoStack.size();
+}
+
+void
+MultiViewCommandHistory::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
+MultiViewCommandHistory::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 << "MVCH::clipStack: Saving recent command: " << command->name().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
+MultiViewCommandHistory::clearStack(CommandStack &stack)
+{
+    while (!stack.empty()) {
+	Command *command = stack.top();
+	std::cerr << "MVCH::clearStack: About to delete command: " << command->name().toLocal8Bit().data() << " at " << command << std::endl;
+	delete command;
+	stack.pop();
+    }
+}
+
+void
+MultiViewCommandHistory::undoActivated(QAction *action)
+{
+    int pos = m_actionCounts[action];
+    for (int i = 0; i <= pos; ++i) {
+	undo();
+    }
+}
+
+void
+MultiViewCommandHistory::redoActivated(QAction *action)
+{
+    int pos = m_actionCounts[action];
+    for (int i = 0; i <= pos; ++i) {
+	redo();
+    }
+}
+
+void
+MultiViewCommandHistory::updateActions()
+{
+    if (m_undoStack.empty()) {
+	m_undoAction->setEnabled(false);
+	m_undoAction->setText(tr("Nothing to undo"));
+    } else {
+	m_undoAction->setEnabled(true);
+	QString commandName = m_undoStack.top()->name();
+	commandName.replace(QRegExp("&"), "");
+	QString text = tr("&Undo %1").arg(commandName);
+	m_undoAction->setText(text);
+    }
+
+    if (m_redoStack.empty()) {
+	m_redoAction->setEnabled(false);
+	m_redoAction->setText(tr("Nothing to redo"));
+    } else {
+	m_redoAction->setEnabled(true);
+	QString commandName = m_redoStack.top()->name();
+	commandName.replace(QRegExp("&"), "");
+	QString text = tr("Re&do %1").arg(commandName);
+	m_redoAction->setText(text);
+    }
+
+    m_actionCounts.clear();
+
+    for (int undo = 0; undo <= 1; ++undo) {
+
+	QMenu *menu(undo ? m_undoMenu : m_redoMenu);
+	CommandStack &stack(undo ? m_undoStack : m_redoStack);
+
+	menu->clear();
+
+	CommandStack tempStack;
+	int j = 0;
+
+	while (j < 10 && !stack.empty()) {
+
+	    Command *command = stack.top();
+	    tempStack.push(command);
+	    stack.pop();
+
+	    QString commandName = command->name();
+	    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();
+	}
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/MultiViewCommandHistory.h	Mon Jan 30 17:51:56 2006 +0000
@@ -0,0 +1,136 @@
+/* -*- c-basic-offset: 4 -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    A waveform viewer and audio annotation editor.
+    Chris Cannam, Queen Mary University of London, 2005-2006
+    
+    This is experimental software.  Not for distribution.
+*/
+
+/*
+   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.
+*/
+
+#ifndef _MULTI_VIEW_COMMAND_HISTORY_H_
+#define _MULTI_VIEW_COMMAND_HISTORY_H_
+
+#include <QObject>
+#include <QString>
+
+#include <stack>
+#include <set>
+#include <map>
+
+class Command;
+class QAction;
+class QMenu;
+class QToolBar;
+
+/**
+ * The MultiViewCommandHistory class stores a list of executed
+ * commands and maintains Undo and Redo actions synchronised with
+ * those commands.
+ *
+ * MultiViewCommandHistory also allows you to associate more than one
+ * Undo and Redo action with the same command history, and it keeps
+ * them all up-to-date at once.  This makes it effective in systems
+ * where multiple views may be editing the same data at once.
+ */
+
+class MultiViewCommandHistory : public QObject
+{
+    Q_OBJECT
+
+public:
+    MultiViewCommandHistory();
+    virtual ~MultiViewCommandHistory();
+
+    void clear();
+    
+    void registerMenu(QMenu *menu);
+    void registerToolbar(QToolBar *toolbar);
+
+    void addCommand(Command *command, bool execute = true);
+    
+    /// Return the maximum number of items in the undo history.
+    int undoLimit() { return m_undoLimit; }
+
+    /// Set the maximum number of items in the undo history.
+    void setUndoLimit(int limit);
+
+    /// Return the maximum number of items in the redo history.
+    int redoLimit() { return m_redoLimit; }
+
+    /// Set the maximum number of items in the redo history.
+    void setRedoLimit(int limit);
+    
+public slots:
+    /**
+     * Checkpoint function that should be called when the document is
+     * saved.  If the undo/redo stack later returns to the point at
+     * which the document was saved, the documentRestored signal will
+     * be emitted.
+     */
+    virtual void documentSaved();
+
+protected slots:
+    void undo();
+    void redo();
+    void undoActivated(QAction *);
+    void redoActivated(QAction *);
+
+signals:
+    /**
+     * Emitted whenever a command has just been executed, whether by
+     * addCommand, undo, or redo.
+     */
+    void commandExecuted(Command *);
+
+    /**
+     * Emitted whenever a command has just been executed, whether by
+     * addCommand, undo, or redo.
+     */
+    void commandExecuted();
+
+    /**
+     * Emitted when the undo/redo stack has reached the same state at
+     * which the documentSaved slot was last called.
+     */
+    void documentRestored();
+
+private:
+    QAction *m_undoAction;
+    QAction *m_redoAction;
+    QMenu *m_undoMenu;
+    QMenu *m_redoMenu;
+
+    std::map<QAction *, int> m_actionCounts;
+
+    typedef std::stack<Command *> CommandStack;
+    CommandStack m_undoStack;
+    CommandStack m_redoStack;
+
+    int m_undoLimit;
+    int m_redoLimit;
+    int m_savedAt;
+
+    void updateActions();
+//    void updateMenus();
+
+//    void updateButtons();
+//    void updateButton(bool undo, const QString &name, CommandStack &stack);
+//    void updateMenu(bool undo, const QString &name, CommandStack &stack);
+    void clipCommands();
+
+    void clipStack(CommandStack &stack, int limit);
+    void clearStack(CommandStack &stack);
+};
+
+
+#endif
--- a/base/View.cpp	Mon Jan 30 13:19:42 2006 +0000
+++ b/base/View.cpp	Mon Jan 30 17:51:56 2006 +0000
@@ -21,8 +21,9 @@
 #include <QApplication>
 
 #include <iostream>
+#include <cassert>
 
-//#define DEBUG_VIEW_WIDGET_PAINT 1
+#define DEBUG_VIEW_WIDGET_PAINT 1
 
 using std::cerr;
 using std::endl;
@@ -195,7 +196,7 @@
 size_t
 View::getEndFrame() const
 {
-    return getStartFrame() + (width() * m_zoomLevel) - 1;
+    return getFrameForX(width()) - 1;
 }
 
 void
@@ -255,6 +256,14 @@
     }
 }
 
+View::LayerProgressBar::LayerProgressBar(QWidget *parent) :
+    QProgressBar(parent)
+{
+    QFont f(font());
+    f.setPointSize(f.pointSize() * 8 / 10);
+    setFont(f);
+}
+
 void
 View::addLayer(Layer *layer)
 {
@@ -268,7 +277,7 @@
     m_progressBars[layer]->setMaximum(100);
     m_progressBars[layer]->setMinimumWidth(80);
     m_progressBars[layer]->hide();
-
+    
     connect(layer, SIGNAL(layerParametersChanged()),
 	    this,    SLOT(layerParametersChanged()));
     connect(layer, SIGNAL(layerNameChanged()),
@@ -503,6 +512,9 @@
 
     if (m_playPointerFrame == f) return;
     bool visible = (getXForFrame(m_playPointerFrame) != getXForFrame(f));
+    std::cerr << "old x = " << getXForFrame(m_playPointerFrame)
+	      << ", new x = " << getXForFrame(f)
+	      << ", visible = " << visible << std::endl;
     size_t oldPlayPointerFrame = m_playPointerFrame;
     m_playPointerFrame = f;
     if (!visible) return;
--- a/base/View.h	Mon Jan 30 13:19:42 2006 +0000
+++ b/base/View.h	Mon Jan 30 17:51:56 2006 +0000
@@ -252,7 +252,7 @@
 
     class LayerProgressBar : public QProgressBar {
     public:
-	LayerProgressBar(QWidget *parent) : QProgressBar(parent) { }
+	LayerProgressBar(QWidget *parent);
 	virtual QString text() const { return m_text; }
 	virtual void setText(QString text) { m_text = text; }
     protected:
--- a/base/ViewManager.cpp	Mon Jan 30 13:19:42 2006 +0000
+++ b/base/ViewManager.cpp	Mon Jan 30 17:51:56 2006 +0000
@@ -14,7 +14,7 @@
 
 #include <iostream>
 
-//#define DEBUG_VIEW_MANAGER 1
+#define DEBUG_VIEW_MANAGER 1
 
 ViewManager::ViewManager() :
     m_playSource(0),
--- a/transform/TransformFactory.cpp	Mon Jan 30 13:19:42 2006 +0000
+++ b/transform/TransformFactory.cpp	Mon Jan 30 17:51:56 2006 +0000
@@ -33,10 +33,20 @@
 TransformFactory::TransformList
 TransformFactory::getAllTransforms()
 {
+    if (m_transforms.empty()) populateTransforms();
+
     TransformList list;
-//!!!    list.push_back(BeatDetectTransform::getName());
-//    list.push_back(BeatDetectionFunctionTransform::getName());
+    for (TransformMap::const_iterator i = m_transforms.begin();
+	 i != m_transforms.end(); ++i) {
+	list.push_back(TransformDesc(i->first, i->second));
+    }
 
+    return list;
+}
+
+void
+TransformFactory::populateTransforms()
+{
     //!!!
     std::vector<QString> fexplugs =
 	FeatureExtractionPluginFactory::getAllPluginIdentifiers();
@@ -62,24 +72,31 @@
 		    plugin->getOutputDescriptors();
 
 		if (outputs.size() == 1) {
-		    list.push_back
-			(TransformDesc
-			 (QString("%1:%2").arg(pluginId).arg(outputs[0].name.c_str()),
-			  pluginDescription));
+		    m_transforms[QString("%1:%2")
+				 .arg(pluginId)
+				 .arg(outputs[0].name.c_str())]
+			= pluginDescription;
 		} else {
 		    for (size_t j = 0; j < outputs.size(); ++j) {
-			list.push_back
-			    (TransformDesc
-			     (QString("%1:%2").arg(pluginId).arg(outputs[j].name.c_str()),
-			      QString("%1: %2").arg(pluginDescription)
-			      .arg(outputs[j].description.c_str())));
+			m_transforms[QString("%1:%2")
+				     .arg(pluginId)
+				     .arg(outputs[j].name.c_str())]
+			    = QString("%1: %2")
+			    .arg(pluginDescription)
+			    .arg(outputs[j].description.c_str());
 		    }
 		}
 	    }
 	}
     }
-    
-    return list;
+}
+
+QString
+TransformFactory::getTransformDescription(TransformName name)
+{
+    if (m_transforms.find(name) != m_transforms.end()) {
+	return m_transforms[name];
+    } else return "";
 }
 
 Transform *
@@ -111,6 +128,7 @@
     }
 
     if (start && transform) transform->start();
+    transform->setObjectName(name);
     return transform;
 }
 
--- a/transform/TransformFactory.h	Mon Jan 30 13:19:42 2006 +0000
+++ b/transform/TransformFactory.h	Mon Jan 30 17:51:56 2006 +0000
@@ -12,6 +12,8 @@
 
 #include "Transform.h"
 
+#include <map>
+
 class TransformFactory : public QObject
 {
     Q_OBJECT
@@ -50,6 +52,8 @@
      */
     Model *transform(TransformName name, Model *inputModel);
 
+    QString getTransformDescription(TransformName name);
+
     //!!! Need some way to indicate that the input model has changed /
     //been deleted so as not to blow up backgrounded transform!  -- Or
     //indeed, if the output model has been deleted -- could equally
@@ -65,6 +69,10 @@
     Transform *createTransform(TransformName name, Model *inputModel,
 			       bool start);
 
+    typedef std::map<TransformName, QString> TransformMap;
+    TransformMap m_transforms;
+    void populateTransforms();
+
     static TransformFactory *m_instance;
 };