changeset 8:214054a0d8b8

* Hook up tool selection buttons to switch the cursor mode * Implement simple and multi-selection, snapping to the resolution of the current layer. You can't actually do anything with a selection yet
author Chris Cannam
date Mon, 23 Jan 2006 17:02:57 +0000
parents 49a95b174050
children 73d85d19919f
files base/Layer.h base/Selection.cpp base/Selection.h base/View.cpp base/View.h base/ViewManager.cpp base/ViewManager.h
diffstat 7 files changed, 390 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/base/Layer.h	Thu Jan 19 17:59:11 2006 +0000
+++ b/base/Layer.h	Mon Jan 23 17:02:57 2006 +0000
@@ -60,12 +60,50 @@
     virtual int getVerticalScaleWidth(QPainter &) const { return 0; }
     virtual void paintVerticalScale(QPainter &, QRect) const { }
 
+    //!!! I don't like these.  The layer should return a structured
+    //string-based description list and the pane should render it
+    //itself.
+
     virtual QRect getFeatureDescriptionRect(QPainter &, QPoint) const {
 	return QRect(0, 0, 0, 0);
     }
     virtual void paintLocalFeatureDescription(QPainter &, QRect, QPoint) const {
     }
 
+    //!!! We also need a method (like the vertical scale method) for
+    //drawing additional scales like a colour scale.  That is, unless
+    //all applicable layers can actually do this from
+    //paintVerticalScale as well?
+
+    // Select mode:
+    //
+    //!!! Next, a method that gets the frame of the nearest feature in
+    //a particular snap direction.  This would be used for selection
+    //mode, where we're selecting from the waveform based on feature
+    //location.  Do we need multi-select on features as well?  This is
+    //an issue; if you select a feature are you selecting that feature
+    //(in which case what do you do with it?) or a section of the
+    //underlying waveform?
+
+    virtual int getNearestFeatureFrame(int frame,
+				       size_t &resolution,
+				       bool snapRight = true) const {
+	resolution = 1;
+	return frame;
+    }
+
+    // Paint and edit modes:
+    //
+    // Layer needs to get actual mouse events, I guess.  Paint mode is
+    // probably the easier.
+
+    // Text mode:
+    //
+    // Label nearest feature.  We need to get the feature coordinates
+    // and current label from the layer, and then the pane can pop up
+    // a little text entry dialog at the right location.  Or we edit
+    // in place?  Probably the dialog is easier.
+
     /**
      * This should return true if the view can safely be scrolled
      * automatically by the widget (simply copying the existing data
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/Selection.cpp	Mon Jan 23 17:02:57 2006 +0000
@@ -0,0 +1,79 @@
+/* -*- 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.
+*/
+
+#include "Selection.h"
+
+Selection::Selection() :
+    m_startFrame(0),
+    m_endFrame(0)
+{
+}
+
+Selection::Selection(size_t startFrame, size_t endFrame) :
+    m_startFrame(startFrame),
+    m_endFrame(endFrame)
+{
+    if (m_startFrame > m_endFrame) {
+	size_t tmp = m_endFrame;
+	m_endFrame = m_startFrame;
+	m_startFrame = tmp;
+    }
+}
+
+Selection::Selection(const Selection &s) :
+    m_startFrame(s.m_startFrame),
+    m_endFrame(s.m_endFrame)
+{
+}
+
+Selection &
+Selection::operator=(const Selection &s)
+{
+    if (this != &s) {
+	m_startFrame = s.m_startFrame;
+	m_endFrame = s.m_endFrame;
+    } 
+    return *this;
+}
+
+Selection::~Selection()
+{
+}
+
+bool
+Selection::isEmpty() const
+{
+    return m_startFrame == m_endFrame;
+}
+
+size_t
+Selection::getStartFrame() const
+{
+    return m_startFrame;
+}
+
+size_t
+Selection::getEndFrame() const
+{
+    return m_endFrame;
+}
+
+bool
+Selection::operator<(const Selection &s) const
+{
+    return (m_startFrame < s.m_startFrame);
+}
+
+bool
+Selection::operator==(const Selection &s) const
+{
+    return (m_startFrame == s.m_startFrame &&
+	    m_endFrame == s.m_endFrame);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/Selection.h	Mon Jan 23 17:02:57 2006 +0000
@@ -0,0 +1,36 @@
+/* -*- 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 _SELECTION_H_
+#define _SELECTION_H_
+
+#include <cstddef>
+
+class Selection
+{
+public:
+    Selection();
+    Selection(size_t startFrame, size_t endFrame);
+    Selection(const Selection &);
+    Selection &operator=(const Selection &);
+    virtual ~Selection();
+
+    bool isEmpty() const;
+    size_t getStartFrame() const;
+    size_t getEndFrame() const;
+
+    bool operator<(const Selection &) const;
+    bool operator==(const Selection &) const;
+    
+protected:
+    size_t m_startFrame;
+    size_t m_endFrame;
+};
+
+#endif
--- a/base/View.cpp	Thu Jan 19 17:59:11 2006 +0000
+++ b/base/View.cpp	Mon Jan 23 17:02:57 2006 +0000
@@ -8,7 +8,6 @@
 */
 
 #include "base/View.h"
-#include "base/ViewManager.h"
 #include "base/Layer.h"
 #include "base/Model.h"
 #include "base/ZoomConstraint.h"
@@ -42,6 +41,7 @@
     m_cacheCentreFrame(0),
     m_cacheZoomLevel(1024),
     m_deleting(false),
+    m_haveSelectedLayer(false),
     m_manager(0)
 {
 //    QWidget::setAttribute(Qt::WA_PaintOnScreen);
@@ -140,7 +140,13 @@
 void
 View::propertyContainerSelected(PropertyContainer *pc)
 {
-    if (pc == this) return;
+    if (pc == this) {
+	if (m_haveSelectedLayer) {
+	    m_haveSelectedLayer = false;
+	    update();
+	}
+	return;
+    }
 
     delete m_cache;
     m_cache = 0;
@@ -156,11 +162,20 @@
     }
 
     if (selectedLayer) {
+	m_haveSelectedLayer = true;
 	m_layers.push_back(selectedLayer);
 	update();
+    } else {
+	m_haveSelectedLayer = false;
     }
 }
 
+void
+View::toolModeChanged()
+{
+    std::cerr << "View::toolModeChanged(" << m_manager->getToolMode() << ")" << std::endl;
+}
+
 long
 View::getStartFrame() const
 {
@@ -280,6 +295,16 @@
     emit propertyContainerRemoved(layer);
 }
 
+Layer *
+View::getSelectedLayer()
+{
+    if (m_haveSelectedLayer && !m_layers.empty()) {
+	return getLayer(getLayerCount() - 1);
+    } else {
+	return 0;
+    }
+}
+
 void
 View::setViewManager(ViewManager *manager)
 {
@@ -288,6 +313,8 @@
 	m_manager->disconnect(this, SLOT(viewManagerZoomLevelChanged(void *, unsigned long, bool)));
 	disconnect(m_manager, SIGNAL(centreFrameChanged(void *, unsigned long, bool)));
 	disconnect(m_manager, SIGNAL(zoomLevelChanged(void *, unsigned long, bool)));
+	disconnect(m_manager, SIGNAL(toolModeChanged()));
+	disconnect(m_manager, SIGNAL(selectionChanged()));
     }
 
     m_manager = manager;
@@ -300,11 +327,17 @@
 	    this, SLOT(viewManagerPlaybackFrameChanged(unsigned long)));
     connect(m_manager, SIGNAL(zoomLevelChanged(void *, unsigned long, bool)),
 	    this, SLOT(viewManagerZoomLevelChanged(void *, unsigned long, bool)));
+    connect(m_manager, SIGNAL(toolModeChanged()),
+	    this, SLOT(toolModeChanged()));
+    connect(m_manager, SIGNAL(selectionChanged()),
+	    this, SLOT(update()));
 
     connect(this, SIGNAL(centreFrameChanged(void *, unsigned long, bool)),
 	    m_manager, SIGNAL(centreFrameChanged(void *, unsigned long, bool)));
     connect(this, SIGNAL(zoomLevelChanged(void *, unsigned long, bool)),
 	    m_manager, SIGNAL(zoomLevelChanged(void *, unsigned long, bool)));
+
+    toolModeChanged();
 }
 
 void
@@ -886,7 +919,10 @@
 	paint.setBrush(Qt::NoBrush);
 	
 	for (LayerList::iterator i = scrollables.begin(); i != scrollables.end(); ++i) {
+	    paint.setRenderHint(QPainter::Antialiasing, false);
+	    paint.save();
 	    (*i)->paint(paint, cacheRect);
+	    paint.restore();
 	}
 	
 	paint.end();
@@ -926,6 +962,42 @@
 	(*i)->paint(paint, nonCacheRect);
     }
 	
+    ViewManager::SelectionList selections;
+
+    if (m_manager) {
+	selections = m_manager->getSelections();
+	if (m_manager->haveInProgressSelection()) {
+	    bool exclusive;
+	    Selection inProgressSelection =
+		m_manager->getInProgressSelection(exclusive);
+	    if (exclusive) selections.clear();
+	    selections.insert(inProgressSelection);
+	}
+    }
+
+    paint.setPen(QColor(150, 150, 255));
+    paint.setBrush(QColor(150, 150, 255, 80));
+
+    for (ViewManager::SelectionList::iterator i = selections.begin();
+	 i != selections.end(); ++i) {
+
+	int p0 = -1, p1 = -1;
+
+	if (int(i->getStartFrame()) >= getStartFrame()) {
+	    p0 = (i->getStartFrame() - getStartFrame()) / m_zoomLevel;
+	}
+
+	if (int(i->getEndFrame()) >= getStartFrame()) {
+	    p1 = (i->getEndFrame() - getStartFrame()) / m_zoomLevel;
+	}
+
+	if (p0 == -1 && p1 == -1) continue;
+
+	if (p1 > width()) p1 = width() + 1;
+
+	paint.drawRect(p0, -1, p1 - p0, height() + 1);
+    }
+
     paint.end();
 
     if (m_followPlay != PlaybackScrollContinuous) {
--- a/base/View.h	Thu Jan 19 17:59:11 2006 +0000
+++ b/base/View.h	Mon Jan 23 17:02:57 2006 +0000
@@ -15,10 +15,10 @@
 
 #include "base/ZoomConstraint.h"
 #include "base/PropertyContainer.h"
+#include "base/ViewManager.h"
 #include "base/XmlExportable.h"
 
 class Layer;
-class ViewManager;
 
 #include <map>
 
@@ -111,6 +111,15 @@
      */
     virtual Layer *getLayer(int n) { return m_layers[n]; }
 
+    /**
+     * Return the layer last selected by the user.  This is normally
+     * the top layer, the same as getLayer(getLayerCount()-1).
+     * However, if the user has selected the pane itself more recently
+     * than any of the layers on it, this function will return 0.  It
+     * will also return 0 if there are no layers.
+     */
+    virtual Layer *getSelectedLayer();
+
     virtual void setViewManager(ViewManager *m);
 
     virtual void setFollowGlobalPan(bool f);
@@ -176,6 +185,8 @@
 
     virtual void propertyContainerSelected(PropertyContainer *pc);
 
+    virtual void toolModeChanged();
+
 protected:
     View(QWidget *, bool showProgress);
     virtual void paintEvent(QPaintEvent *e);
@@ -213,6 +224,7 @@
     bool                m_deleting;
 
     LayerList           m_layers; // I don't own these, but see dtor note above
+    bool                m_haveSelectedLayer;
 
     // caches for use in getScrollableBackLayers, getNonScrollableFrontLayers
     mutable LayerList m_lastScrollableBackLayers;
--- a/base/ViewManager.cpp	Thu Jan 19 17:59:11 2006 +0000
+++ b/base/ViewManager.cpp	Mon Jan 23 17:02:57 2006 +0000
@@ -21,7 +21,9 @@
     m_globalCentreFrame(0),
     m_globalZoom(1024),
     m_lastLeft(0), 
-    m_lastRight(0)
+    m_lastRight(0),
+    m_inProgressExclusive(true),
+    m_toolMode(NavigateMode)
 {
     connect(this, 
 	    SIGNAL(centreFrameChanged(void *, unsigned long, bool)),
@@ -50,6 +52,107 @@
     return m_globalZoom;
 }
 
+bool
+ViewManager::haveInProgressSelection() const
+{
+    return !m_inProgressSelection.isEmpty();
+}
+
+const Selection &
+ViewManager::getInProgressSelection(bool &exclusive) const
+{
+    exclusive = m_inProgressExclusive;
+    return m_inProgressSelection;
+}
+
+void
+ViewManager::setInProgressSelection(const Selection &selection, bool exclusive)
+{
+    m_inProgressExclusive = exclusive;
+    m_inProgressSelection = selection;
+    if (exclusive) clearSelections();
+    emit selectionChanged();
+}
+
+void
+ViewManager::clearInProgressSelection()
+{
+    m_inProgressSelection = Selection();
+    emit selectionChanged();
+}
+
+const ViewManager::SelectionList &
+ViewManager::getSelections() const
+{
+    return m_selections;
+}
+
+void
+ViewManager::setSelection(const Selection &selection)
+{
+    clearSelections();
+    addSelection(selection);
+}
+
+void
+ViewManager::addSelection(const Selection &selection)
+{
+    m_selections.insert(selection);
+
+    // Cope with a sitation where the new selection overlaps one or
+    // more existing ones.  This is a terribly inefficient way to do
+    // this, but that probably isn't significant in real life.
+
+     for (SelectionList::iterator i = m_selections.begin();
+	 i != m_selections.end(); ) {
+	
+	SelectionList::iterator j = i;
+	if (++j == m_selections.end()) break;
+
+	if (i->getEndFrame() >= j->getStartFrame()) {
+	    Selection merged(i->getStartFrame(),
+			     std::max(i->getEndFrame(), j->getEndFrame()));
+	    m_selections.erase(i);
+	    m_selections.erase(j);
+	    m_selections.insert(merged);
+	    i = m_selections.begin();
+	} else {
+	    ++i;
+	}
+    }
+
+    emit selectionChanged();
+}
+
+void
+ViewManager::removeSelection(const Selection &selection)
+{
+    //!!! Likewise this needs to cope correctly with the situation
+    //where selection is not one of the original selection set but
+    //simply overlaps one of them (cutting down the original selection
+    //appropriately)
+
+    m_selections.erase(selection);
+
+    emit selectionChanged();
+}
+
+void
+ViewManager::clearSelections()
+{
+    m_selections.clear();
+
+    emit selectionChanged();
+}
+
+void
+ViewManager::setToolMode(ToolMode mode)
+{
+    m_toolMode = mode;
+
+    emit toolModeChanged();
+}
+
 void
 ViewManager::setAudioPlaySource(AudioPlaySource *source)
 {
@@ -119,6 +222,12 @@
     }
 }
 
+bool
+ViewManager::isPlaying() const
+{
+    return m_playSource && m_playSource->isPlaying();
+}
+
 void
 ViewManager::considerSeek(void *p, unsigned long f, bool locked)
 {
--- a/base/ViewManager.h	Thu Jan 19 17:59:11 2006 +0000
+++ b/base/ViewManager.h	Mon Jan 23 17:02:57 2006 +0000
@@ -14,6 +14,9 @@
 #include <QTimer>
 
 #include <map>
+#include <set>
+
+#include "Selection.h"
 
 class AudioPlaySource;
 class PlayParameters;
@@ -42,9 +45,34 @@
     PlayParameters *getPlayParameters(const Model *model);
     void clearPlayParameters();
 
+    bool isPlaying() const;
+
     unsigned long getGlobalCentreFrame() const;
     unsigned long getGlobalZoom() const;
 
+    typedef std::set<Selection> SelectionList;
+
+    bool haveInProgressSelection() const;
+    const Selection &getInProgressSelection(bool &exclusive) const;
+    void setInProgressSelection(const Selection &selection, bool exclusive);
+    void clearInProgressSelection();
+
+    const SelectionList &getSelections() const;
+    void setSelection(const Selection &selection);
+    void addSelection(const Selection &selection);
+    void removeSelection(const Selection &selection);
+    void clearSelections();
+
+    enum ToolMode {
+	NavigateMode,
+	SelectMode,
+        EditMode,
+	DrawMode,
+	TextMode
+    };
+    ToolMode getToolMode() const { return m_toolMode; }
+    void setToolMode(ToolMode mode);
+
 signals:
     /** Emitted when a widget pans.  The originator identifies the widget. */
     void centreFrameChanged(void *originator, unsigned long frame, bool locked);
@@ -58,6 +86,12 @@
     /** Emitted when the output levels change. Values in range 0.0 -> 1.0. */
     void outputLevelsChanged(float left, float right);
 
+    /** Emitted when the selection has changed. */
+    void selectionChanged();
+
+    /** Emitted when the tool mode has been changed. */
+    void toolModeChanged();
+
 protected slots:
     void checkPlayStatus();
     void considerSeek(void *, unsigned long, bool);
@@ -71,6 +105,12 @@
     float m_lastLeft;
     float m_lastRight;
 
+    SelectionList m_selections;
+    Selection m_inProgressSelection;
+    bool m_inProgressExclusive;
+
+    ToolMode m_toolMode;
+
     std::map<const Model *, PlayParameters *> m_playParameters;
 };