changeset 127:89c625dda204

* Reorganising code base. This revision will not compile.
author Chris Cannam
date Mon, 31 Jul 2006 11:44:37 +0000
parents 0e95c127bb53
children 33929e0c3c6b
files layer/Layer.cpp layer/Layer.h view/Pane.cpp view/Pane.h view/PaneStack.cpp view/PaneStack.h view/Panner.cpp view/Panner.h view/View.cpp view/View.h view/ViewManager.cpp view/ViewManager.h widgets/Pane.cpp widgets/Pane.h widgets/PaneStack.cpp widgets/PaneStack.h widgets/Panner.cpp widgets/Panner.h
diffstat 18 files changed, 4846 insertions(+), 2005 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/Layer.cpp	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,105 @@
+/* -*- 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 file copyright 2006 Chris Cannam.
+    
+    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.
+*/
+
+#include "Layer.h"
+#include "View.h"
+#include "Model.h"
+
+#include <iostream>
+
+#include "layer/LayerFactory.h" //!!! shouldn't be including this here -- does that suggest we need to move this into layer/ ?
+#include "PlayParameterRepository.h"
+
+Layer::Layer()
+{
+}
+
+Layer::~Layer()
+{
+//    std::cerr << "Layer::~Layer(" << this << ")" << std::endl;
+}
+
+QString
+Layer::getPropertyContainerIconName() const
+{
+    return LayerFactory::getInstance()->getLayerIconName
+	(LayerFactory::getInstance()->getLayerType(this));
+}
+
+QString
+Layer::getLayerPresentationName() const
+{
+    QString layerName = objectName();
+    QString modelName;
+    if (getModel()) modelName = getModel()->objectName();
+	    
+    QString text;
+    if (modelName != "") {
+	text = QString("%1: %2").arg(modelName).arg(layerName);
+    } else {
+	text = layerName;
+    }
+	
+    return text;
+}
+
+void
+Layer::setObjectName(const QString &name)
+{
+    QObject::setObjectName(name);
+    emit layerNameChanged();
+}
+
+QString
+Layer::toXmlString(QString indent, QString extraAttributes) const
+{
+    QString s;
+    
+    s += indent;
+
+    s += QString("<layer id=\"%2\" type=\"%1\" name=\"%3\" model=\"%4\" %5/>\n")
+	.arg(encodeEntities(LayerFactory::getInstance()->getLayerTypeName
+                            (LayerFactory::getInstance()->getLayerType(this))))
+	.arg(getObjectExportId(this))
+	.arg(encodeEntities(objectName()))
+	.arg(getObjectExportId(getModel()))
+	.arg(extraAttributes);
+
+    return s;
+}
+
+PlayParameters *
+Layer::getPlayParameters() 
+{
+//    std::cerr << "Layer (" << this << ", " << objectName().toStdString() << ")::getPlayParameters: model is "<< getModel() << std::endl;
+    const Model *model = getModel();
+    if (model) {
+	return PlayParameterRepository::getInstance()->getPlayParameters(model);
+    }
+    return 0;
+}
+
+void
+Layer::showLayer(View *view, bool show)
+{
+    setLayerDormant(view, !show);
+    emit layerParametersChanged();
+}
+
+
+#ifdef INCLUDE_MOCFILES
+#include "Layer.moc.cpp"
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/Layer.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,301 @@
+
+/* -*- 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 file copyright 2006 Chris Cannam.
+    
+    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.
+*/
+
+#ifndef _LAYER_H_
+#define _LAYER_H_
+
+#include "PropertyContainer.h"
+#include "XmlExportable.h"
+#include "Selection.h"
+
+#include <QObject>
+#include <QRect>
+#include <QXmlAttributes>
+
+#include <map>
+
+class ZoomConstraint;
+class Model;
+class QPainter;
+class View;
+class QMouseEvent;
+class Clipboard;
+
+/**
+ * The base class for visual representations of the data found in a
+ * Model.  Layers are expected to be able to draw themselves onto a
+ * View, and may also be editable.
+ */
+
+class Layer : public PropertyContainer,
+	      public XmlExportable
+{
+    Q_OBJECT
+
+public:
+    Layer();
+    virtual ~Layer();
+
+    virtual const Model *getModel() const = 0;
+    virtual Model *getModel() {
+	return const_cast<Model *>(const_cast<const Layer *>(this)->getModel());
+    }
+
+    virtual const ZoomConstraint *getZoomConstraint() const { return 0; }
+    virtual void paint(View *, QPainter &, QRect) const = 0;   
+
+    enum VerticalPosition {
+	PositionTop, PositionMiddle, PositionBottom
+    };
+    virtual VerticalPosition getPreferredTimeRulerPosition() const {
+	return PositionMiddle;
+    }
+    virtual VerticalPosition getPreferredFrameCountPosition() const {
+	return PositionBottom;
+    }
+
+    virtual QString getPropertyContainerIconName() const;
+
+    virtual QString getPropertyContainerName() const {
+	return objectName();
+    }
+
+    virtual QString getLayerPresentationName() const;
+
+    virtual int getVerticalScaleWidth(View *, QPainter &) const { return 0; }
+    virtual void paintVerticalScale(View *, QPainter &, QRect) const { }
+
+    virtual bool getCrosshairExtents(View *, QPainter &, QPoint /* cursorPos */,
+                                     std::vector<QRect> &) const {
+        return false;
+    }
+    virtual void paintCrosshairs(View *, QPainter &, QPoint) const { }
+
+    virtual QString getFeatureDescription(View *, QPoint &) const {
+	return "";
+    }
+
+    enum SnapType {
+	SnapLeft,
+	SnapRight,
+	SnapNearest,
+	SnapNeighbouring
+    };
+
+    /**
+     * Adjust the given frame to snap to the nearest feature, if
+     * possible.
+     *
+     * If snap is SnapLeft or SnapRight, adjust the frame to match
+     * that of the nearest feature in the given direction regardless
+     * of how far away it is.  If snap is SnapNearest, adjust the
+     * frame to that of the nearest feature in either direction.  If
+     * snap is SnapNeighbouring, adjust the frame to that of the
+     * nearest feature if it is close, and leave it alone (returning
+     * false) otherwise.  SnapNeighbouring should always choose the
+     * same feature that would be used in an editing operation through
+     * calls to editStart etc.
+     *
+     * Return true if a suitable feature was found and frame adjusted
+     * accordingly.  Return false if no suitable feature was
+     * available.  Also return the resolution of the model in this
+     * layer in sample frames.
+     */
+    virtual bool snapToFeatureFrame(View *   /* v */,
+				    int &    /* frame */,
+				    size_t &resolution,
+				    SnapType /* snap */) const {
+	resolution = 1;
+	return false;
+    }
+
+    // Draw and edit modes:
+    //
+    // Layer needs to get actual mouse events, I guess.  Draw mode is
+    // probably the easier.
+
+    virtual void drawStart(View *, QMouseEvent *) { }
+    virtual void drawDrag(View *, QMouseEvent *) { }
+    virtual void drawEnd(View *, QMouseEvent *) { }
+
+    virtual void editStart(View *, QMouseEvent *) { }
+    virtual void editDrag(View *, QMouseEvent *) { }
+    virtual void editEnd(View *, QMouseEvent *) { }
+
+    virtual void editOpen(View *, QMouseEvent *) { } // on double-click
+
+    virtual void moveSelection(Selection, size_t /* newStartFrame */) { }
+    virtual void resizeSelection(Selection, Selection /* newSize */) { }
+    virtual void deleteSelection(Selection) { }
+
+    virtual void copy(Selection, Clipboard & /* to */) { }
+
+    /**
+     * Paste from the given clipboard onto the layer at the given
+     * frame offset.  If interactive is true, the layer may ask the
+     * user about paste options through a dialog if desired, and may
+     * return false if the user cancelled the paste operation.  This
+     * function should return true if a paste actually occurred.
+     */
+    virtual bool paste(const Clipboard & /* from */,
+                       int /* frameOffset */,
+                       bool /* interactive */) { return false; }
+
+    // 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 layer can safely be scrolled
+     * automatically by a given view (simply copying the existing data
+     * and then refreshing the exposed area) without altering its
+     * meaning.  For the view widget as a whole this is usually not
+     * possible because of invariant (non-scrolling) material
+     * displayed over the top, but the widget may be able to optimise
+     * scrolling better if it is known that individual views can be
+     * scrolled safely in this way.
+     */
+    virtual bool isLayerScrollable(const View *) const { return true; }
+
+    /**
+     * This should return true if the layer completely obscures any
+     * underlying layers.  It's used to determine whether the view can
+     * safely draw any selection rectangles under the layer instead of
+     * over it, in the case where the layer is not scrollable and
+     * therefore needs to be redrawn each time (so that the selection
+     * rectangle can be cached).
+     */
+    virtual bool isLayerOpaque() const { return false; }
+
+    /**
+     * This should return true if the layer can be edited by the user.
+     * If this is the case, the appropriate edit tools may be made
+     * available by the application and the layer's drawStart/Drag/End
+     * and editStart/Drag/End methods should be implemented.
+     */
+    virtual bool isLayerEditable() const { return false; }
+
+    /**
+     * Return the proportion of background work complete in drawing
+     * this view, as a percentage -- in most cases this will be the
+     * value returned by pointer from a call to the underlying model's
+     * isReady(int *) call.  The widget may choose to show a progress
+     * meter if it finds that this returns < 100 at any given moment.
+     */
+    virtual int getCompletion(View *) const { return 100; }
+
+    virtual void setObjectName(const QString &name);
+
+    /**
+     * Convert the layer's data (though not those of the model it
+     * refers to) into an XML string for file output.  This class
+     * implements the basic name/type/model-id output; subclasses will
+     * typically call this superclass implementation with extra
+     * attributes describing their particular properties.
+     */
+    virtual QString toXmlString(QString indent = "",
+				QString extraAttributes = "") const;
+
+    /**
+     * Set the particular properties of a layer (those specific to the
+     * subclass) from a set of XML attributes.  This is the effective
+     * inverse of the toXmlString method.
+     */
+    virtual void setProperties(const QXmlAttributes &) = 0;
+
+    /**
+     * Indicate that a layer is not currently visible in the given
+     * view and is not expected to become visible in the near future
+     * (for example because the user has explicitly removed or hidden
+     * it).  The layer may respond by (for example) freeing any cache
+     * memory it is using, until next time its paint method is called,
+     * when it should set itself un-dormant again.
+     */
+    virtual void setLayerDormant(const View *v, bool dormant) {
+	m_dormancy[v] = dormant;
+    }
+
+    /**
+     * Return whether the layer is dormant (i.e. hidden) in the given
+     * view.
+     */
+    virtual bool isLayerDormant(const View *v) const {
+	if (m_dormancy.find(v) == m_dormancy.end()) return false;
+	return m_dormancy.find(v)->second;
+    }
+
+    virtual PlayParameters *getPlayParameters();
+
+    virtual bool needsTextLabelHeight() const { return false; }
+
+    /**
+     * Return the minimum and maximum values for the y axis of the
+     * model in this layer, as well as whether the layer is configured
+     * to use a logarithmic y axis display.  Also return the unit for
+     * these values if known.
+     *
+     * This function returns the "normal" extents for the layer, not
+     * necessarily the extents actually in use in the display.
+     */
+    virtual bool getValueExtents(float &min, float &max,
+                                 bool &logarithmic, QString &unit) const = 0;
+
+    /**
+     * Return the minimum and maximum values within the displayed
+     * range for the y axis, if only a subset of the whole range of
+     * the model (returned by getValueExtents) is being displayed.
+     * Return false if the layer is not imposing a particular display
+     * extent (using the normal layer extents or deferring to whatever
+     * is in use for the same units elsewhere in the view).
+     */
+    virtual bool getDisplayExtents(float & /* min */,
+                                   float & /* max */) const {
+        return false;
+    }
+
+    /**
+     * Set the displayed minimum and maximum values for the y axis to
+     * the given range, if supported.  Return false if not supported
+     * on this layer (and set nothing).  In most cases, layers that
+     * return false for getDisplayExtents should also return false for
+     * this function.
+     */
+    virtual bool setDisplayExtents(float /* min */,
+                                   float /* max */) {
+        return false;
+    }
+
+public slots:
+    void showLayer(View *, bool show);
+
+signals:
+    void modelChanged();
+    void modelCompletionChanged();
+    void modelChanged(size_t startFrame, size_t endFrame);
+    void modelReplaced();
+
+    void layerParametersChanged();
+    void layerNameChanged();
+
+protected:
+    mutable std::map<const void *, bool> m_dormancy;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/view/Pane.cpp	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,1095 @@
+/* -*- 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 file copyright 2006 Chris Cannam.
+    
+    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.
+*/
+
+#include "widgets/Pane.h"
+#include "base/Layer.h"
+#include "base/Model.h"
+#include "base/ZoomConstraint.h"
+#include "base/RealTime.h"
+#include "base/Profiler.h"
+#include "base/ViewManager.h"
+#include "base/CommandHistory.h"
+#include "layer/WaveformLayer.h"
+
+#include <QPaintEvent>
+#include <QPainter>
+#include <iostream>
+#include <cmath>
+
+using std::cerr;
+using std::endl;
+
+Pane::Pane(QWidget *w) :
+    View(w, true),
+    m_identifyFeatures(false),
+    m_clickedInRange(false),
+    m_shiftPressed(false),
+    m_ctrlPressed(false),
+    m_navigating(false),
+    m_resizing(false),
+    m_centreLineVisible(true)
+{
+    setObjectName("Pane");
+    setMouseTracking(true);
+}
+
+bool
+Pane::shouldIlluminateLocalFeatures(const Layer *layer, QPoint &pos) const
+{
+    QPoint discard;
+    bool b0, b1;
+
+    if (layer == getSelectedLayer() &&
+	!shouldIlluminateLocalSelection(discard, b0, b1)) {
+
+	pos = m_identifyPoint;
+	return m_identifyFeatures;
+    }
+
+    return false;
+}
+
+bool
+Pane::shouldIlluminateLocalSelection(QPoint &pos,
+				     bool &closeToLeft,
+				     bool &closeToRight) const
+{
+    if (m_identifyFeatures &&
+	m_manager &&
+	m_manager->getToolMode() == ViewManager::EditMode &&
+	!m_manager->getSelections().empty() &&
+	!selectionIsBeingEdited()) {
+
+	Selection s(getSelectionAt(m_identifyPoint.x(),
+				   closeToLeft, closeToRight));
+
+	if (!s.isEmpty()) {
+	    if (getSelectedLayer() && getSelectedLayer()->isLayerEditable()) {
+		
+		pos = m_identifyPoint;
+		return true;
+	    }
+	}
+    }
+
+    return false;
+}
+
+bool
+Pane::selectionIsBeingEdited() const
+{
+    if (!m_editingSelection.isEmpty()) {
+	if (m_mousePos != m_clickPos &&
+	    getFrameForX(m_mousePos.x()) != getFrameForX(m_clickPos.x())) {
+	    return true;
+	}
+    }
+    return false;
+}
+
+void
+Pane::setCentreLineVisible(bool visible)
+{
+    m_centreLineVisible = visible;
+    update();
+}
+
+void
+Pane::paintEvent(QPaintEvent *e)
+{
+//    Profiler profiler("Pane::paintEvent", true);
+
+    QPainter paint;
+
+    QRect r(rect());
+
+    if (e) {
+	r = e->rect();
+    }
+/*
+    paint.begin(this);
+    paint.setClipRect(r);
+
+    if (hasLightBackground()) {
+	paint.setPen(Qt::white);
+	paint.setBrush(Qt::white);
+    } else {
+	paint.setPen(Qt::black);
+	paint.setBrush(Qt::black);
+    }
+    paint.drawRect(r);
+
+    paint.end();
+*/
+    View::paintEvent(e);
+
+    paint.begin(this);
+
+    if (e) {
+	paint.setClipRect(r);
+    }
+
+    const Model *waveformModel = 0; // just for reporting purposes
+    int verticalScaleWidth = 0;
+    
+    int fontHeight = paint.fontMetrics().height();
+    int fontAscent = paint.fontMetrics().ascent();
+
+    if (m_manager &&
+        !m_manager->isPlaying() &&
+        m_manager->getToolMode() == ViewManager::SelectMode) {
+
+        for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) {
+            --vi;
+
+            std::vector<QRect> crosshairExtents;
+
+            if ((*vi)->getCrosshairExtents(this, paint, m_identifyPoint,
+                                           crosshairExtents)) {
+                (*vi)->paintCrosshairs(this, paint, m_identifyPoint);
+                break;
+            } else if ((*vi)->isLayerOpaque()) {
+                break;
+            }
+        }
+    }
+
+    for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) {
+        --vi;
+            
+        if (dynamic_cast<WaveformLayer *>(*vi)) {
+            waveformModel = (*vi)->getModel();
+        }
+
+        if (!m_manager ||
+            m_manager->getOverlayMode() == ViewManager::NoOverlays) {
+            break;
+        }
+
+        verticalScaleWidth = (*vi)->getVerticalScaleWidth(this, paint);
+
+        if (verticalScaleWidth > 0 && r.left() < verticalScaleWidth) {
+
+//	    Profiler profiler("Pane::paintEvent - painting vertical scale", true);
+
+//	    std::cerr << "Pane::paintEvent: calling paint.save() in vertical scale block" << std::endl;
+            paint.save();
+            
+            paint.setPen(Qt::black);
+            paint.setBrush(Qt::white);
+            paint.drawRect(0, -1, verticalScaleWidth, height()+1);
+            
+            paint.setBrush(Qt::NoBrush);
+            (*vi)->paintVerticalScale
+                (this, paint, QRect(0, 0, verticalScaleWidth, height()));
+            
+            paint.restore();
+        }
+	
+        if (m_identifyFeatures) {
+            
+            QPoint pos = m_identifyPoint;
+            QString desc = (*vi)->getFeatureDescription(this, pos);
+	    
+            if (desc != "") {
+                
+                paint.save();
+                
+                int tabStop =
+                    paint.fontMetrics().width(tr("Some lengthy prefix:"));
+                
+                QRect boundingRect = 
+                    paint.fontMetrics().boundingRect
+                    (rect(),
+                     Qt::AlignRight | Qt::AlignTop | Qt::TextExpandTabs,
+                     desc, tabStop);
+
+                if (hasLightBackground()) {
+                    paint.setPen(Qt::NoPen);
+                    paint.setBrush(QColor(250, 250, 250, 200));
+                } else {
+                    paint.setPen(Qt::NoPen);
+                    paint.setBrush(QColor(50, 50, 50, 200));
+                }
+
+                int extra = paint.fontMetrics().descent();
+                paint.drawRect(width() - boundingRect.width() - 10 - extra,
+                               10 - extra,
+                               boundingRect.width() + 2 * extra,
+                               boundingRect.height() + extra);
+
+                if (hasLightBackground()) {
+                    paint.setPen(QColor(150, 20, 0));
+                } else {
+                    paint.setPen(QColor(255, 150, 100));
+                }
+		
+                QTextOption option;
+                option.setWrapMode(QTextOption::NoWrap);
+                option.setAlignment(Qt::AlignRight | Qt::AlignTop);
+                option.setTabStop(tabStop);
+                paint.drawText(QRectF(width() - boundingRect.width() - 10, 10,
+                                      boundingRect.width(),
+                                      boundingRect.height()),
+                               desc,
+                               option);
+
+                paint.restore();
+            }
+        }
+
+        break;
+    }
+    
+    int sampleRate = getModelsSampleRate();
+    paint.setBrush(Qt::NoBrush);
+
+    if (m_centreLineVisible) {
+
+	if (hasLightBackground()) {
+	    paint.setPen(QColor(50, 50, 50));
+	} else {
+	    paint.setPen(QColor(200, 200, 200));
+	}	
+	paint.drawLine(width() / 2, 0, width() / 2, height() - 1);
+
+	paint.setPen(QColor(50, 50, 50));
+
+	int y = height() - fontHeight
+	    + fontAscent - 6;
+	
+	LayerList::iterator vi = m_layers.end();
+	
+	if (vi != m_layers.begin()) {
+	    
+	    switch ((*--vi)->getPreferredFrameCountPosition()) {
+		
+	    case Layer::PositionTop:
+		y = fontAscent + 6;
+		break;
+		
+	    case Layer::PositionMiddle:
+		y = (height() - fontHeight) / 2
+		    + fontAscent;
+		break;
+
+	    case Layer::PositionBottom:
+		// y already set correctly
+		break;
+	    }
+	}
+
+        if (m_manager &&
+            m_manager->getOverlayMode() != ViewManager::NoOverlays) {
+
+            if (sampleRate) {
+
+                QString text(QString::fromStdString
+                             (RealTime::frame2RealTime
+                              (m_centreFrame, sampleRate).toText(true)));
+                
+                int tw = paint.fontMetrics().width(text);
+                int x = width()/2 - 4 - tw;
+                
+                drawVisibleText(paint, x, y, text, OutlinedText);
+            }
+            
+            QString text = QString("%1").arg(m_centreFrame);
+            
+            int tw = paint.fontMetrics().width(text);
+            int x = width()/2 + 4;
+            
+            drawVisibleText(paint, x, y, text, OutlinedText);
+        }
+
+    } else {
+
+	paint.setPen(QColor(50, 50, 50));
+    }
+
+    if (waveformModel &&
+        m_manager &&
+        m_manager->getOverlayMode() != ViewManager::NoOverlays &&
+	r.y() + r.height() >= height() - fontHeight - 6) {
+
+	size_t mainModelRate = m_manager->getMainModelSampleRate();
+	size_t playbackRate = m_manager->getPlaybackSampleRate();
+	    
+	QString srNote = "";
+
+	// Show (R) for waveform models that will be resampled on
+	// playback, and (X) for waveform models that will be played
+	// at the wrong rate because their rate differs from that of
+	// the main model.
+
+	if (sampleRate == mainModelRate) {
+	    if (sampleRate != playbackRate) srNote = " " + tr("(R)");
+	} else {
+	    std::cerr << "Sample rate = " << sampleRate << ", main model rate = " << mainModelRate << std::endl;
+	    srNote = " " + tr("(X)");
+	}
+
+	QString desc = tr("%1 / %2Hz%3")
+	    .arg(RealTime::frame2RealTime(waveformModel->getEndFrame(),
+					  sampleRate)
+		 .toText(false).c_str())
+	    .arg(sampleRate)
+	    .arg(srNote);
+
+	if (r.x() < verticalScaleWidth + 5 + paint.fontMetrics().width(desc)) {
+	    drawVisibleText(paint, verticalScaleWidth + 5,
+			    height() - fontHeight + fontAscent - 6,
+			    desc, OutlinedText);
+	}
+    }
+
+    if (m_manager &&
+        m_manager->getOverlayMode() == ViewManager::AllOverlays &&
+        r.y() + r.height() >= height() - m_layers.size() * fontHeight - 6) {
+
+	std::vector<QString> texts;
+	int maxTextWidth = 0;
+
+	for (LayerList::iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+
+	    QString text = (*i)->getLayerPresentationName();
+	    int tw = paint.fontMetrics().width(text);
+            bool reduced = false;
+            while (tw > width() / 3 && text.length() > 4) {
+                if (!reduced && text.length() > 8) {
+                    text = text.left(text.length() - 4);
+                } else {
+                    text = text.left(text.length() - 2);
+                }
+                reduced = true;
+                tw = paint.fontMetrics().width(text + "...");
+            }
+            if (reduced) {
+                texts.push_back(text + "...");
+            } else {
+                texts.push_back(text);
+            }
+	    if (tw > maxTextWidth) maxTextWidth = tw;
+	}
+    
+	int lly = height() - 6;
+
+	if (r.x() + r.width() >= width() - maxTextWidth - 5) {
+	    
+	    for (int i = 0; i < texts.size(); ++i) {
+
+		if (i == texts.size() - 1) {
+		    paint.setPen(Qt::black);
+		}
+		
+		drawVisibleText(paint, width() - maxTextWidth - 5,
+				lly - fontHeight + fontAscent,
+				texts[i], OutlinedText);
+		
+		lly -= fontHeight;
+	    }
+	}
+    }
+
+    if (m_clickedInRange && m_shiftPressed) {
+	if (m_manager && (m_manager->getToolMode() == ViewManager::NavigateMode)) {
+	    //!!! be nice if this looked a bit more in keeping with the
+	    //selection block
+	    paint.setPen(Qt::blue);
+	    paint.drawRect(m_clickPos.x(), m_clickPos.y(),
+			   m_mousePos.x() - m_clickPos.x(),
+			   m_mousePos.y() - m_clickPos.y());
+	}
+    }
+    
+    if (selectionIsBeingEdited()) {
+
+	int offset = m_mousePos.x() - m_clickPos.x();
+	int p0 = getXForFrame(m_editingSelection.getStartFrame()) + offset;
+	int p1 = getXForFrame(m_editingSelection.getEndFrame()) + offset;
+
+	if (m_editingSelectionEdge < 0) {
+	    p1 = getXForFrame(m_editingSelection.getEndFrame());
+	} else if (m_editingSelectionEdge > 0) {
+	    p0 = getXForFrame(m_editingSelection.getStartFrame());
+	}
+
+	paint.save();
+	if (hasLightBackground()) {
+	    paint.setPen(QPen(Qt::black, 2));
+	} else {
+	    paint.setPen(QPen(Qt::white, 2));
+	}
+
+	//!!! duplicating display policy with View::drawSelections
+
+	if (m_editingSelectionEdge < 0) {
+	    paint.drawLine(p0, 1, p1, 1);
+	    paint.drawLine(p0, 0, p0, height());
+	    paint.drawLine(p0, height() - 1, p1, height() - 1);
+	} else if (m_editingSelectionEdge > 0) {
+	    paint.drawLine(p0, 1, p1, 1);
+	    paint.drawLine(p1, 0, p1, height());
+	    paint.drawLine(p0, height() - 1, p1, height() - 1);
+	} else {
+	    paint.setBrush(Qt::NoBrush);
+	    paint.drawRect(p0, 1, p1 - p0, height() - 2);
+	}
+	paint.restore();
+    }
+
+    paint.end();
+}
+
+Selection
+Pane::getSelectionAt(int x, bool &closeToLeftEdge, bool &closeToRightEdge) const
+{
+    closeToLeftEdge = closeToRightEdge = false;
+
+    if (!m_manager) return Selection();
+
+    long testFrame = getFrameForX(x - 5);
+    if (testFrame < 0) {
+	testFrame = getFrameForX(x);
+	if (testFrame < 0) return Selection();
+    }
+
+    Selection selection = m_manager->getContainingSelection(testFrame, true);
+    if (selection.isEmpty()) return selection;
+
+    int lx = getXForFrame(selection.getStartFrame());
+    int rx = getXForFrame(selection.getEndFrame());
+    
+    int fuzz = 2;
+    if (x < lx - fuzz || x > rx + fuzz) return Selection();
+
+    int width = rx - lx;
+    fuzz = 3;
+    if (width < 12) fuzz = width / 4;
+    if (fuzz < 1) fuzz = 1;
+
+    if (x < lx + fuzz) closeToLeftEdge = true;
+    if (x > rx - fuzz) closeToRightEdge = true;
+
+    return selection;
+}
+
+void
+Pane::mousePressEvent(QMouseEvent *e)
+{
+    if (e->buttons() & Qt::RightButton) {
+        emit rightButtonMenuRequested(mapToGlobal(e->pos()));
+        return;
+    }
+
+    m_clickPos = e->pos();
+    m_clickedInRange = true;
+    m_editingSelection = Selection();
+    m_editingSelectionEdge = 0;
+    m_shiftPressed = (e->modifiers() & Qt::ShiftModifier);
+    m_ctrlPressed = (e->modifiers() & Qt::ControlModifier);
+
+    ViewManager::ToolMode mode = ViewManager::NavigateMode;
+    if (m_manager) mode = m_manager->getToolMode();
+
+    m_navigating = false;
+
+    if (mode == ViewManager::NavigateMode || (e->buttons() & Qt::MidButton)) {
+
+	if (mode != ViewManager::NavigateMode) {
+	    setCursor(Qt::PointingHandCursor);
+	}
+
+	m_navigating = true;
+	m_dragCentreFrame = m_centreFrame;
+
+    } else if (mode == ViewManager::SelectMode) {
+
+	bool closeToLeft = false, closeToRight = false;
+	Selection selection = getSelectionAt(e->x(), closeToLeft, closeToRight);
+
+	if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) {
+
+	    m_manager->removeSelection(selection);
+
+	    if (closeToLeft) {
+		m_selectionStartFrame = selection.getEndFrame();
+	    } else {
+		m_selectionStartFrame = selection.getStartFrame();
+	    }
+
+	    m_manager->setInProgressSelection(selection, false);
+	    m_resizing = true;
+	
+	} else {
+
+	    int mouseFrame = getFrameForX(e->x());
+	    size_t resolution = 1;
+	    int snapFrame = mouseFrame;
+	
+	    Layer *layer = getSelectedLayer();
+	    if (layer && !m_shiftPressed) {
+		layer->snapToFeatureFrame(this, snapFrame,
+					  resolution, Layer::SnapLeft);
+	    }
+	    
+	    if (snapFrame < 0) snapFrame = 0;
+	    m_selectionStartFrame = snapFrame;
+	    if (m_manager) {
+		m_manager->setInProgressSelection(Selection(snapFrame,
+							    snapFrame + resolution),
+						  !m_ctrlPressed);
+	    }
+
+	    m_resizing = false;
+	}
+
+	update();
+
+    } else if (mode == ViewManager::DrawMode) {
+
+	Layer *layer = getSelectedLayer();
+	if (layer && layer->isLayerEditable()) {
+	    layer->drawStart(this, e);
+	}
+
+    } else if (mode == ViewManager::EditMode) {
+
+	if (!editSelectionStart(e)) {
+	    Layer *layer = getSelectedLayer();
+	    if (layer && layer->isLayerEditable()) {
+		layer->editStart(this, e);
+	    }
+	}
+    }
+
+    emit paneInteractedWith();
+}
+
+void
+Pane::mouseReleaseEvent(QMouseEvent *e)
+{
+    if (e->buttons() & Qt::RightButton) {
+        return;
+    }
+
+    ViewManager::ToolMode mode = ViewManager::NavigateMode;
+    if (m_manager) mode = m_manager->getToolMode();
+
+    if (m_clickedInRange) {
+	mouseMoveEvent(e);
+    }
+
+    if (m_navigating || mode == ViewManager::NavigateMode) {
+
+	m_navigating = false;
+
+	if (mode != ViewManager::NavigateMode) {
+	    // restore cursor
+	    toolModeChanged();
+	}
+
+	if (m_shiftPressed) {
+
+	    int x0 = std::min(m_clickPos.x(), m_mousePos.x());
+	    int x1 = std::max(m_clickPos.x(), m_mousePos.x());
+	    int w = x1 - x0;
+
+	    int y0 = std::min(m_clickPos.y(), m_mousePos.y());
+	    int y1 = std::max(m_clickPos.y(), m_mousePos.y());
+//	    int h = y1 - y0;
+	    
+	    long newStartFrame = getFrameForX(x0);
+	    
+	    long visibleFrames = getEndFrame() - getStartFrame();
+	    if (newStartFrame <= -visibleFrames) {
+		newStartFrame  = -visibleFrames + 1;
+	    }
+	    
+	    if (newStartFrame >= long(getModelsEndFrame())) {
+		newStartFrame  = getModelsEndFrame() - 1;
+	    }
+	    
+	    float ratio = float(w) / float(width());
+//	std::cerr << "ratio: " << ratio << std::endl;
+	    size_t newZoomLevel = (size_t)nearbyint(m_zoomLevel * ratio);
+	    if (newZoomLevel < 1) newZoomLevel = 1;
+
+//	std::cerr << "start: " << m_startFrame << ", level " << m_zoomLevel << std::endl;
+	    setZoomLevel(getZoomConstraintBlockSize(newZoomLevel));
+	    setStartFrame(newStartFrame);
+
+            //!!! lots of faff, shouldn't be here
+
+            QString unit;
+            float min, max;
+            bool log;
+            Layer *layer = 0;
+            for (LayerList::const_iterator i = m_layers.begin();
+                 i != m_layers.end(); ++i) { 
+                if ((*i)->getValueExtents(min, max, log, unit) &&
+                    (*i)->getDisplayExtents(min, max)) {
+                    layer = *i;
+                    break;
+                }
+            }
+            
+            if (layer) {
+                if (log) {
+                    min = (min < 0.0) ? -log10f(-min) : (min == 0.0) ? 0.0 : log10f(min);
+                    max = (max < 0.0) ? -log10f(-max) : (max == 0.0) ? 0.0 : log10f(max);
+                }
+                float rmin = min + ((max - min) * (height() - y1)) / height();
+                float rmax = min + ((max - min) * (height() - y0)) / height();
+                std::cerr << "min: " << min << ", max: " << max << ", y0: " << y0 << ", y1: " << y1 << ", h: " << height() << ", rmin: " << rmin << ", rmax: " << rmax << std::endl;
+                if (log) {
+                    rmin = powf(10, rmin);
+                    rmax = powf(10, rmax);
+                }
+                std::cerr << "finally: rmin: " << rmin << ", rmax: " << rmax << " " << unit.toStdString() << std::endl;
+
+                layer->setDisplayExtents(rmin, rmax);
+            }
+                
+	    //cerr << "mouseReleaseEvent: start frame now " << m_startFrame << endl;
+//	update();
+	}
+
+    } else if (mode == ViewManager::SelectMode) {
+
+	if (m_manager && m_manager->haveInProgressSelection()) {
+
+	    bool exclusive;
+	    Selection selection = m_manager->getInProgressSelection(exclusive);
+	    
+	    if (selection.getEndFrame() < selection.getStartFrame() + 2) {
+		selection = Selection();
+	    }
+	    
+	    m_manager->clearInProgressSelection();
+	    
+	    if (exclusive) {
+		m_manager->setSelection(selection);
+	    } else {
+		m_manager->addSelection(selection);
+	    }
+	}
+	
+	update();
+
+    } else if (mode == ViewManager::DrawMode) {
+
+	Layer *layer = getSelectedLayer();
+	if (layer && layer->isLayerEditable()) {
+	    layer->drawEnd(this, e);
+	    update();
+	}
+
+    } else if (mode == ViewManager::EditMode) {
+
+	if (!editSelectionEnd(e)) {
+	    Layer *layer = getSelectedLayer();
+	    if (layer && layer->isLayerEditable()) {
+		layer->editEnd(this, e);
+		update();
+	    }
+	}
+    }
+
+    m_clickedInRange = false;
+
+    emit paneInteractedWith();
+}
+
+void
+Pane::mouseMoveEvent(QMouseEvent *e)
+{
+    if (e->buttons() & Qt::RightButton) {
+        return;
+    }
+
+    ViewManager::ToolMode mode = ViewManager::NavigateMode;
+    if (m_manager) mode = m_manager->getToolMode();
+
+    QPoint prevPoint = m_identifyPoint;
+    m_identifyPoint = e->pos();
+
+    if (!m_clickedInRange) {
+	
+	if (mode == ViewManager::SelectMode) {
+	    bool closeToLeft = false, closeToRight = false;
+	    getSelectionAt(e->x(), closeToLeft, closeToRight);
+	    if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) {
+		setCursor(Qt::SizeHorCursor);
+	    } else {
+		setCursor(Qt::ArrowCursor);
+	    }
+	}
+
+//!!!	if (mode != ViewManager::DrawMode) {
+
+        if (!m_manager->isPlaying()) {
+
+	if (getSelectedLayer()) {
+
+	    bool previouslyIdentifying = m_identifyFeatures;
+	    m_identifyFeatures = true;
+	    
+	    if (m_identifyFeatures != previouslyIdentifying ||
+		m_identifyPoint != prevPoint) {
+		update();
+	    }
+	}
+
+        }
+
+//	}
+
+	return;
+    }
+
+    if (m_navigating || mode == ViewManager::NavigateMode) {
+
+	if (m_shiftPressed) {
+
+	    m_mousePos = e->pos();
+	    update();
+
+	} else {
+
+	    long frameOff = getFrameForX(e->x()) - getFrameForX(m_clickPos.x());
+
+	    size_t newCentreFrame = m_dragCentreFrame;
+	    
+	    if (frameOff < 0) {
+		newCentreFrame -= frameOff;
+	    } else if (newCentreFrame >= size_t(frameOff)) {
+		newCentreFrame -= frameOff;
+	    } else {
+		newCentreFrame = 0;
+	    }
+	    
+	    if (newCentreFrame >= getModelsEndFrame()) {
+		newCentreFrame = getModelsEndFrame();
+		if (newCentreFrame > 0) --newCentreFrame;
+	    }
+
+	    if (getXForFrame(m_centreFrame) != getXForFrame(newCentreFrame)) {
+		setCentreFrame(newCentreFrame);
+	    }
+	}
+
+    } else if (mode == ViewManager::SelectMode) {
+
+	int mouseFrame = getFrameForX(e->x());
+	size_t resolution = 1;
+	int snapFrameLeft = mouseFrame;
+	int snapFrameRight = mouseFrame;
+	
+	Layer *layer = getSelectedLayer();
+	if (layer && !m_shiftPressed) {
+	    layer->snapToFeatureFrame(this, snapFrameLeft,
+				      resolution, Layer::SnapLeft);
+	    layer->snapToFeatureFrame(this, snapFrameRight,
+				      resolution, Layer::SnapRight);
+	}
+	
+//	std::cerr << "snap: frame = " << mouseFrame << ", start frame = " << m_selectionStartFrame << ", left = " << snapFrameLeft << ", right = " << snapFrameRight << std::endl;
+
+	if (snapFrameLeft < 0) snapFrameLeft = 0;
+	if (snapFrameRight < 0) snapFrameRight = 0;
+	
+	size_t min, max;
+	
+	if (m_selectionStartFrame > snapFrameLeft) {
+	    min = snapFrameLeft;
+	    max = m_selectionStartFrame;
+	} else if (snapFrameRight > m_selectionStartFrame) {
+	    min = m_selectionStartFrame;
+	    max = snapFrameRight;
+	} else {
+	    min = snapFrameLeft;
+	    max = snapFrameRight;
+	}
+
+	if (m_manager) {
+	    m_manager->setInProgressSelection(Selection(min, max),
+					      !m_resizing && !m_ctrlPressed);
+	}
+
+	bool doScroll = false;
+	if (!m_manager) doScroll = true;
+	if (!m_manager->isPlaying()) doScroll = true;
+	if (m_followPlay != PlaybackScrollContinuous) doScroll = true;
+
+	if (doScroll) {
+	    int offset = mouseFrame - getStartFrame();
+	    int available = getEndFrame() - getStartFrame();
+	    if (offset >= available * 0.95) {
+		int move = int(offset - available * 0.95) + 1;
+		setCentreFrame(m_centreFrame + move);
+	    } else if (offset <= available * 0.10) {
+		int move = int(available * 0.10 - offset) + 1;
+		if (m_centreFrame > move) {
+		    setCentreFrame(m_centreFrame - move);
+		} else {
+		    setCentreFrame(0);
+		}
+	    }
+	}
+
+	update();
+
+    } else if (mode == ViewManager::DrawMode) {
+
+	Layer *layer = getSelectedLayer();
+	if (layer && layer->isLayerEditable()) {
+	    layer->drawDrag(this, e);
+	}
+
+    } else if (mode == ViewManager::EditMode) {
+
+	if (!editSelectionDrag(e)) {
+	    Layer *layer = getSelectedLayer();
+	    if (layer && layer->isLayerEditable()) {
+		layer->editDrag(this, e);
+	    }
+	}
+    }
+}
+
+void
+Pane::mouseDoubleClickEvent(QMouseEvent *e)
+{
+    if (e->buttons() & Qt::RightButton) {
+        return;
+    }
+
+//    std::cerr << "mouseDoubleClickEvent" << std::endl;
+
+    m_clickPos = e->pos();
+    m_clickedInRange = true;
+    m_shiftPressed = (e->modifiers() & Qt::ShiftModifier);
+    m_ctrlPressed = (e->modifiers() & Qt::ControlModifier);
+
+    ViewManager::ToolMode mode = ViewManager::NavigateMode;
+    if (m_manager) mode = m_manager->getToolMode();
+
+    if (mode == ViewManager::NavigateMode ||
+        mode == ViewManager::EditMode) {
+
+	Layer *layer = getSelectedLayer();
+	if (layer && layer->isLayerEditable()) {
+	    layer->editOpen(this, e);
+	}
+    }
+}
+
+void
+Pane::leaveEvent(QEvent *)
+{
+    bool previouslyIdentifying = m_identifyFeatures;
+    m_identifyFeatures = false;
+    if (previouslyIdentifying) update();
+}
+
+void
+Pane::wheelEvent(QWheelEvent *e)
+{
+    //std::cerr << "wheelEvent, delta " << e->delta() << std::endl;
+
+    int count = e->delta();
+
+    if (count > 0) {
+	if (count >= 120) count /= 120;
+	else count = 1;
+    } 
+
+    if (count < 0) {
+	if (count <= -120) count /= 120;
+	else count = -1;
+    }
+
+    if (e->modifiers() & Qt::ControlModifier) {
+
+	// Scroll left or right, rapidly
+
+	if (getStartFrame() < 0 && 
+	    getEndFrame() >= getModelsEndFrame()) return;
+
+	long delta = ((width() / 2) * count * m_zoomLevel);
+
+	if (int(m_centreFrame) < delta) {
+	    setCentreFrame(0);
+	} else if (int(m_centreFrame) - delta >= int(getModelsEndFrame())) {
+	    setCentreFrame(getModelsEndFrame());
+	} else {
+	    setCentreFrame(m_centreFrame - delta);
+	}
+
+    } else {
+
+	// Zoom in or out
+
+	int newZoomLevel = m_zoomLevel;
+  
+	while (count > 0) {
+	    if (newZoomLevel <= 2) {
+		newZoomLevel = 1;
+		break;
+	    }
+	    newZoomLevel = getZoomConstraintBlockSize(newZoomLevel - 1, 
+						      ZoomConstraint::RoundDown);
+	    --count;
+	}
+	
+	while (count < 0) {
+	    newZoomLevel = getZoomConstraintBlockSize(newZoomLevel + 1,
+						      ZoomConstraint::RoundUp);
+	    ++count;
+	}
+	
+	if (newZoomLevel != m_zoomLevel) {
+	    setZoomLevel(newZoomLevel);
+	}
+    }
+
+    emit paneInteractedWith();
+}
+
+bool
+Pane::editSelectionStart(QMouseEvent *e)
+{
+    if (!m_identifyFeatures ||
+	!m_manager ||
+	m_manager->getToolMode() != ViewManager::EditMode) {
+	return false;
+    }
+
+    bool closeToLeft, closeToRight;
+    Selection s(getSelectionAt(e->x(), closeToLeft, closeToRight));
+    if (s.isEmpty()) return false;
+    m_editingSelection = s;
+    m_editingSelectionEdge = (closeToLeft ? -1 : closeToRight ? 1 : 0);
+    m_mousePos = e->pos();
+    return true;
+}
+
+bool
+Pane::editSelectionDrag(QMouseEvent *e)
+{
+    if (m_editingSelection.isEmpty()) return false;
+    m_mousePos = e->pos();
+    update();
+    return true;
+}
+
+bool
+Pane::editSelectionEnd(QMouseEvent *e)
+{
+    if (m_editingSelection.isEmpty()) return false;
+
+    int offset = m_mousePos.x() - m_clickPos.x();
+    Layer *layer = getSelectedLayer();
+
+    if (offset == 0 || !layer) {
+	m_editingSelection = Selection();
+	return true;
+    }
+
+    int p0 = getXForFrame(m_editingSelection.getStartFrame()) + offset;
+    int p1 = getXForFrame(m_editingSelection.getEndFrame()) + offset;
+
+    long f0 = getFrameForX(p0);
+    long f1 = getFrameForX(p1);
+
+    Selection newSelection(f0, f1);
+    
+    if (m_editingSelectionEdge == 0) {
+	
+        CommandHistory::getInstance()->startCompoundOperation
+            (tr("Drag Selection"), true);
+
+	layer->moveSelection(m_editingSelection, f0);
+	
+    } else {
+	
+        CommandHistory::getInstance()->startCompoundOperation
+            (tr("Resize Selection"), true);
+
+	if (m_editingSelectionEdge < 0) {
+	    f1 = m_editingSelection.getEndFrame();
+	} else {
+	    f0 = m_editingSelection.getStartFrame();
+	}
+
+	newSelection = Selection(f0, f1);
+	layer->resizeSelection(m_editingSelection, newSelection);
+    }
+    
+    m_manager->removeSelection(m_editingSelection);
+    m_manager->addSelection(newSelection);
+
+    CommandHistory::getInstance()->endCompoundOperation();
+
+    m_editingSelection = Selection();
+    return true;
+}
+
+void
+Pane::toolModeChanged()
+{
+    ViewManager::ToolMode mode = m_manager->getToolMode();
+//    std::cerr << "Pane::toolModeChanged(" << mode << ")" << std::endl;
+
+    switch (mode) {
+
+    case ViewManager::NavigateMode:
+	setCursor(Qt::PointingHandCursor);
+	break;
+	
+    case ViewManager::SelectMode:
+	setCursor(Qt::ArrowCursor);
+	break;
+	
+    case ViewManager::EditMode:
+	setCursor(Qt::UpArrowCursor);
+	break;
+	
+    case ViewManager::DrawMode:
+	setCursor(Qt::CrossCursor);
+	break;
+/*	
+    case ViewManager::TextMode:
+	setCursor(Qt::IBeamCursor);
+	break;
+*/
+    }
+}
+
+QString
+Pane::toXmlString(QString indent, QString extraAttributes) const
+{
+    return View::toXmlString
+	(indent,
+	 QString("type=\"pane\" centreLineVisible=\"%1\" height=\"%2\" %3")
+	 .arg(m_centreLineVisible).arg(height()).arg(extraAttributes));
+}
+
+
+#ifdef INCLUDE_MOCFILES
+#include "Pane.moc.cpp"
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/view/Pane.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,91 @@
+
+/* -*- 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 file copyright 2006 Chris Cannam.
+    
+    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.
+*/
+
+#ifndef _PANE_H_
+#define _PANE_H_
+
+#include <QFrame>
+#include <QPoint>
+
+#include "base/ZoomConstraint.h"
+#include "base/View.h"
+#include "base/Selection.h"
+
+class QWidget;
+class QPaintEvent;
+class Layer;
+
+class Pane : public View
+{
+    Q_OBJECT
+
+public:
+    Pane(QWidget *parent = 0);
+    virtual QString getPropertyContainerIconName() const { return "pane"; }
+
+    virtual bool shouldIlluminateLocalFeatures(const Layer *layer,
+					       QPoint &pos) const;
+    virtual bool shouldIlluminateLocalSelection(QPoint &pos,
+						bool &closeToLeft,
+						bool &closeToRight) const;
+
+    void setCentreLineVisible(bool visible);
+    bool getCentreLineVisible() const { return m_centreLineVisible; }
+
+    virtual QString toXmlString(QString indent = "",
+				QString extraAttributes = "") const;
+
+signals:
+    void paneInteractedWith();
+    void rightButtonMenuRequested(QPoint position);
+
+public slots:
+    virtual void toolModeChanged();
+
+protected:
+    virtual void paintEvent(QPaintEvent *e);
+    virtual void mousePressEvent(QMouseEvent *e);
+    virtual void mouseReleaseEvent(QMouseEvent *e);
+    virtual void mouseMoveEvent(QMouseEvent *e);
+    virtual void mouseDoubleClickEvent(QMouseEvent *e);
+    virtual void leaveEvent(QEvent *e);
+    virtual void wheelEvent(QWheelEvent *e);
+
+    Selection getSelectionAt(int x, bool &closeToLeft, bool &closeToRight) const;
+
+    bool editSelectionStart(QMouseEvent *e);
+    bool editSelectionDrag(QMouseEvent *e);
+    bool editSelectionEnd(QMouseEvent *e);
+    bool selectionIsBeingEdited() const;
+
+    bool m_identifyFeatures;
+    QPoint m_identifyPoint;
+    QPoint m_clickPos;
+    QPoint m_mousePos;
+    bool m_clickedInRange;
+    bool m_shiftPressed;
+    bool m_ctrlPressed;
+    bool m_navigating;
+    bool m_resizing;
+    size_t m_dragCentreFrame;
+    bool m_centreLineVisible;
+    size_t m_selectionStartFrame;
+    Selection m_editingSelection;
+    int m_editingSelectionEdge;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/view/PaneStack.cpp	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,434 @@
+
+/* -*- 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 file copyright 2006 Chris Cannam.
+    
+    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.
+*/
+
+#include "PaneStack.h"
+
+#include "widgets/Pane.h"
+#include "widgets/PropertyStack.h"
+#include "base/Layer.h"
+#include "base/ViewManager.h"
+
+#include <QApplication>
+#include <QHBoxLayout>
+#include <QPainter>
+#include <QPalette>
+#include <QLabel>
+#include <QSplitter>
+#include <QStackedWidget>
+
+#include <iostream>
+
+PaneStack::PaneStack(QWidget *parent, ViewManager *viewManager) :
+    QFrame(parent),
+    m_currentPane(0),
+    m_splitter(new QSplitter),
+    m_propertyStackStack(new QStackedWidget),
+    m_viewManager(viewManager),
+    m_layoutStyle(PropertyStackPerPaneLayout)
+{
+    QHBoxLayout *layout = new QHBoxLayout;
+    layout->setMargin(0);
+    layout->setSpacing(0);
+
+    m_splitter->setOrientation(Qt::Vertical);
+    m_splitter->setOpaqueResize(false);
+
+    layout->addWidget(m_splitter);
+    layout->setStretchFactor(m_splitter, 1);
+    layout->addWidget(m_propertyStackStack);
+    m_propertyStackStack->hide();
+
+    setLayout(layout);
+}
+
+Pane *
+PaneStack::addPane(bool suppressPropertyBox)
+{
+    QFrame *frame = new QFrame;
+    QHBoxLayout *layout = new QHBoxLayout;
+    layout->setMargin(0);
+    layout->setSpacing(2);
+
+    QLabel *currentIndicator = new QLabel(frame);
+    currentIndicator->setFixedWidth(QPainter(this).fontMetrics().width("x"));
+    layout->addWidget(currentIndicator);
+    layout->setStretchFactor(currentIndicator, 1);
+    currentIndicator->setScaledContents(true);
+
+    Pane *pane = new Pane(frame);
+    pane->setViewManager(m_viewManager);
+    layout->addWidget(pane);
+    layout->setStretchFactor(pane, 10);
+
+    QWidget *properties = 0;
+    if (suppressPropertyBox) {
+	properties = new QFrame();
+    } else {
+	properties = new PropertyStack(frame, pane);
+	connect(properties, SIGNAL(propertyContainerSelected(View *, PropertyContainer *)),
+		this, SLOT(propertyContainerSelected(View *, PropertyContainer *)));
+    }
+    if (m_layoutStyle == PropertyStackPerPaneLayout) {
+        layout->addWidget(properties);
+    } else {
+        properties->setParent(m_propertyStackStack);
+        m_propertyStackStack->addWidget(properties);
+    }
+    layout->setStretchFactor(properties, 1);
+
+    PaneRec rec;
+    rec.pane = pane;
+    rec.propertyStack = properties;
+    rec.currentIndicator = currentIndicator;
+    rec.frame = frame;
+    rec.layout = layout;
+    m_panes.push_back(rec);
+
+    frame->setLayout(layout);
+    m_splitter->addWidget(frame);
+
+    connect(pane, SIGNAL(propertyContainerAdded(PropertyContainer *)),
+	    this, SLOT(propertyContainerAdded(PropertyContainer *)));
+    connect(pane, SIGNAL(propertyContainerRemoved(PropertyContainer *)),
+	    this, SLOT(propertyContainerRemoved(PropertyContainer *)));
+    connect(pane, SIGNAL(paneInteractedWith()),
+	    this, SLOT(paneInteractedWith()));
+    connect(pane, SIGNAL(rightButtonMenuRequested(QPoint)),
+            this, SLOT(rightButtonMenuRequested(QPoint)));
+
+    if (!m_currentPane) {
+	setCurrentPane(pane);
+    }
+
+    return pane;
+}
+
+void
+PaneStack::setLayoutStyle(LayoutStyle style)
+{
+    if (style == m_layoutStyle) return;
+    m_layoutStyle = style;
+
+    std::vector<PaneRec>::iterator i;
+
+    switch (style) {
+
+    case SinglePropertyStackLayout:
+        
+        for (i = m_panes.begin(); i != m_panes.end(); ++i) {
+            i->layout->removeWidget(i->propertyStack);
+            i->propertyStack->setParent(m_propertyStackStack);
+            m_propertyStackStack->addWidget(i->propertyStack);
+        }
+        m_propertyStackStack->show();
+        break;
+
+    case PropertyStackPerPaneLayout:
+
+        for (i = m_panes.begin(); i != m_panes.end(); ++i) {
+            m_propertyStackStack->removeWidget(i->propertyStack);
+            i->propertyStack->setParent(i->frame);
+            i->layout->addWidget(i->propertyStack);
+            i->propertyStack->show();
+        }
+        m_propertyStackStack->hide();
+        break;
+    }
+}
+
+Pane *
+PaneStack::getPane(int n)
+{
+    return m_panes[n].pane;
+}
+
+Pane *
+PaneStack::getHiddenPane(int n)
+{
+    return m_hiddenPanes[n].pane;
+}
+
+void
+PaneStack::deletePane(Pane *pane)
+{
+    std::vector<PaneRec>::iterator i;
+    bool found = false;
+
+    for (i = m_panes.begin(); i != m_panes.end(); ++i) {
+	if (i->pane == pane) {
+	    m_panes.erase(i);
+	    found = true;
+	    break;
+	}
+    }
+
+    if (!found) {
+
+	for (i = m_hiddenPanes.begin(); i != m_hiddenPanes.end(); ++i) {
+	    if (i->pane == pane) {
+		m_hiddenPanes.erase(i);
+		found = true;
+		break;
+	    }
+	}
+
+	if (!found) {
+	    std::cerr << "WARNING: PaneStack::deletePane(" << pane << "): Pane not found in visible or hidden panes, not deleting" << std::endl;
+	    return;
+	}
+    }
+
+    delete pane->parent();
+
+    if (m_currentPane == pane) {
+	if (m_panes.size() > 0) {
+            setCurrentPane(m_panes[0].pane);
+	} else {
+	    setCurrentPane(0);
+	}
+    }
+}
+
+int
+PaneStack::getPaneCount() const
+{
+    return m_panes.size();
+}
+
+int
+PaneStack::getHiddenPaneCount() const
+{
+    return m_hiddenPanes.size();
+}
+
+void
+PaneStack::hidePane(Pane *pane)
+{
+    std::vector<PaneRec>::iterator i = m_panes.begin();
+
+    while (i != m_panes.end()) {
+	if (i->pane == pane) {
+
+	    m_hiddenPanes.push_back(*i);
+	    m_panes.erase(i);
+
+	    QWidget *pw = dynamic_cast<QWidget *>(pane->parent());
+	    if (pw) pw->hide();
+
+	    if (m_currentPane == pane) {
+		if (m_panes.size() > 0) {
+		    setCurrentPane(m_panes[0].pane);
+		} else {
+		    setCurrentPane(0);
+		}
+	    }
+	    
+	    return;
+	}
+	++i;
+    }
+
+    std::cerr << "WARNING: PaneStack::hidePane(" << pane << "): Pane not found in visible panes" << std::endl;
+}
+
+void
+PaneStack::showPane(Pane *pane)
+{
+    std::vector<PaneRec>::iterator i = m_hiddenPanes.begin();
+
+    while (i != m_hiddenPanes.end()) {
+	if (i->pane == pane) {
+	    m_panes.push_back(*i);
+	    m_hiddenPanes.erase(i);
+	    QWidget *pw = dynamic_cast<QWidget *>(pane->parent());
+	    if (pw) pw->show();
+
+	    //!!! update current pane
+
+	    return;
+	}
+	++i;
+    }
+
+    std::cerr << "WARNING: PaneStack::showPane(" << pane << "): Pane not found in hidden panes" << std::endl;
+}
+
+void
+PaneStack::setCurrentPane(Pane *pane) // may be null
+{
+    if (m_currentPane == pane) return;
+    
+    std::vector<PaneRec>::iterator i = m_panes.begin();
+
+    // We used to do this by setting the foreground and background
+    // role, but it seems the background role is ignored and the
+    // background drawn transparent in Qt 4.1 -- I can't quite see why
+    
+    QPixmap selectedMap(1, 1);
+    selectedMap.fill(QApplication::palette().color(QPalette::Foreground));
+    
+    QPixmap unselectedMap(1, 1);
+    unselectedMap.fill(QApplication::palette().color(QPalette::Background));
+
+    bool found = false;
+
+    while (i != m_panes.end()) {
+	if (i->pane == pane) {
+	    i->currentIndicator->setPixmap(selectedMap);
+            if (m_layoutStyle == SinglePropertyStackLayout) {
+                m_propertyStackStack->setCurrentWidget(i->propertyStack);
+            }
+	    found = true;
+	} else {
+	    i->currentIndicator->setPixmap(unselectedMap);
+	}
+	++i;
+    }
+
+    if (found || pane == 0) {
+	m_currentPane = pane;
+	emit currentPaneChanged(m_currentPane);
+    } else {
+	std::cerr << "WARNING: PaneStack::setCurrentPane(" << pane << "): pane is not a visible pane in this stack" << std::endl;
+    }
+}
+
+void
+PaneStack::setCurrentLayer(Pane *pane, Layer *layer) // may be null
+{
+    setCurrentPane(pane);
+
+    if (m_currentPane) {
+
+	std::vector<PaneRec>::iterator i = m_panes.begin();
+
+	while (i != m_panes.end()) {
+
+	    if (i->pane == pane) {
+		PropertyStack *stack = dynamic_cast<PropertyStack *>
+		    (i->propertyStack);
+		if (stack) {
+		    if (stack->containsContainer(layer)) {
+			stack->setCurrentIndex(stack->getContainerIndex(layer));
+			emit currentLayerChanged(pane, layer);
+		    } else {
+			stack->setCurrentIndex
+			    (stack->getContainerIndex
+			     (pane->getPropertyContainer(0)));
+			emit currentLayerChanged(pane, 0);
+		    }
+		}
+		break;
+	    }
+	    ++i;
+	}
+    }
+}
+
+Pane *
+PaneStack::getCurrentPane() 
+{
+    return m_currentPane;
+}
+
+void
+PaneStack::propertyContainerAdded(PropertyContainer *)
+{
+    sizePropertyStacks();
+}
+
+void
+PaneStack::propertyContainerRemoved(PropertyContainer *)
+{
+    sizePropertyStacks();
+}
+
+void
+PaneStack::propertyContainerSelected(View *client, PropertyContainer *pc)
+{
+    std::vector<PaneRec>::iterator i = m_panes.begin();
+
+    while (i != m_panes.end()) {
+	PropertyStack *stack = dynamic_cast<PropertyStack *>(i->propertyStack);
+	if (stack &&
+	    stack->getClient() == client &&
+	    stack->containsContainer(pc)) {
+	    setCurrentPane(i->pane);
+	    break;
+	}
+	++i;
+    }
+
+    Layer *layer = dynamic_cast<Layer *>(pc);
+    if (layer) emit currentLayerChanged(m_currentPane, layer);
+    else emit currentLayerChanged(m_currentPane, 0);
+}
+
+void
+PaneStack::paneInteractedWith()
+{
+    Pane *pane = dynamic_cast<Pane *>(sender());
+    if (!pane) return;
+    setCurrentPane(pane);
+}
+
+void
+PaneStack::rightButtonMenuRequested(QPoint position)
+{
+    Pane *pane = dynamic_cast<Pane *>(sender());
+    if (!pane) return;
+    emit rightButtonMenuRequested(pane, position);
+}
+
+void
+PaneStack::sizePropertyStacks()
+{
+    int maxMinWidth = 0;
+
+    for (size_t i = 0; i < m_panes.size(); ++i) {
+	if (!m_panes[i].propertyStack) continue;
+//	std::cerr << "PaneStack::sizePropertyStacks: " << i << ": min " 
+//		  << m_panes[i].propertyStack->minimumSizeHint().width() << ", current "
+//		  << m_panes[i].propertyStack->width() << std::endl;
+
+	if (m_panes[i].propertyStack->minimumSizeHint().width() > maxMinWidth) {
+	    maxMinWidth = m_panes[i].propertyStack->minimumSizeHint().width();
+	}
+    }
+
+//    std::cerr << "PaneStack::sizePropertyStacks: max min width " << maxMinWidth << std::endl;
+
+#ifdef Q_WS_MAC
+    // This is necessary to compensate for cb->setMinimumSize(10, 10)
+    // in PropertyBox in the Mac version (to avoid a mysterious crash)
+    int setWidth = maxMinWidth * 3 / 2;
+#else
+    int setWidth = maxMinWidth;
+#endif
+
+    m_propertyStackStack->setMaximumWidth(setWidth + 10);
+
+    for (size_t i = 0; i < m_panes.size(); ++i) {
+	if (!m_panes[i].propertyStack) continue;
+	m_panes[i].propertyStack->setMinimumWidth(setWidth);
+    }
+}
+    
+
+#ifdef INCLUDE_MOCFILES
+#include "PaneStack.moc.cpp"
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/view/PaneStack.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,102 @@
+
+/* -*- 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 file copyright 2006 Chris Cannam.
+    
+    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.
+*/
+
+#ifndef _PANESTACK_H_
+#define _PANESTACK_H_
+
+#include <QFrame>
+
+class QWidget;
+class QLabel;
+class QStackedWidget;
+class QSplitter;
+class QHBoxLayout;
+class View;
+class Pane;
+class Layer;
+class ViewManager;
+class PropertyContainer;
+class PropertyStack;
+
+class PaneStack : public QFrame
+{
+    Q_OBJECT
+
+public:
+    PaneStack(QWidget *parent, ViewManager *viewManager);
+
+    Pane *addPane(bool suppressPropertyBox = false); // I own the returned value
+    void deletePane(Pane *pane); // Deletes the pane, but _not_ its layers
+
+    int getPaneCount() const; // Returns only count of visible panes
+    Pane *getPane(int n); // Of visible panes; I own the returned value
+
+    void hidePane(Pane *pane); // Also removes pane from getPane/getPaneCount
+    void showPane(Pane *pane); // Returns pane to getPane/getPaneCount
+
+    int getHiddenPaneCount() const;
+    Pane *getHiddenPane(int n); // I own the returned value
+
+    void setCurrentPane(Pane *pane);
+    void setCurrentLayer(Pane *pane, Layer *layer);
+    Pane *getCurrentPane();
+
+    enum LayoutStyle {
+        SinglePropertyStackLayout = 1,
+        PropertyStackPerPaneLayout = 2
+    };
+
+    LayoutStyle getLayoutStyle() const { return m_layoutStyle; }
+    void setLayoutStyle(LayoutStyle style);
+
+signals:
+    void currentPaneChanged(Pane *pane);
+    void currentLayerChanged(Pane *pane, Layer *layer);
+    void rightButtonMenuRequested(Pane *pane, QPoint position);
+
+public slots:
+    void propertyContainerAdded(PropertyContainer *);
+    void propertyContainerRemoved(PropertyContainer *);
+    void propertyContainerSelected(View *client, PropertyContainer *);
+    void paneInteractedWith();
+    void rightButtonMenuRequested(QPoint);
+
+protected:
+    Pane *m_currentPane;
+
+    struct PaneRec
+    {
+	Pane        *pane;
+	QWidget     *propertyStack;
+	QLabel      *currentIndicator;
+        QFrame      *frame;
+        QHBoxLayout *layout;
+    };
+
+    std::vector<PaneRec> m_panes;
+    std::vector<PaneRec> m_hiddenPanes;
+
+    QSplitter *m_splitter;
+    QStackedWidget *m_propertyStackStack;
+
+    ViewManager *m_viewManager; // I don't own this
+    void sizePropertyStacks();
+
+    LayoutStyle m_layoutStyle;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/view/Panner.cpp	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,216 @@
+/* -*- 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 file copyright 2006 Chris Cannam.
+    
+    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.
+*/
+
+#include "Panner.h"
+#include "base/Layer.h"
+#include "base/Model.h"
+#include "base/ZoomConstraint.h"
+
+#include <QPaintEvent>
+#include <QPainter>
+#include <iostream>
+
+using std::cerr;
+using std::endl;
+
+Panner::Panner(QWidget *w) :
+    View(w, false),
+    m_clickedInRange(false)
+{
+    setObjectName(tr("Panner"));
+    m_followPan = false;
+    m_followZoom = false;
+}
+
+void
+Panner::modelChanged(size_t startFrame, size_t endFrame)
+{
+    View::modelChanged(startFrame, endFrame);
+}
+
+void
+Panner::modelReplaced()
+{
+    View::modelReplaced();
+}
+
+void
+Panner::registerView(View *widget)
+{
+    m_widgets.insert(widget);
+    update(); 
+}
+
+void
+Panner::unregisterView(View *widget)
+{
+    m_widgets.erase(widget);
+    update();
+}
+
+void
+Panner::viewManagerCentreFrameChanged(void *p, unsigned long f, bool)
+{
+//    std::cerr << "Panner[" << this << "]::viewManagerCentreFrameChanged(" 
+//	      << p << ", " << f << ")" << std::endl;
+
+    if (p == this) return;
+    if (m_widgets.find(p) != m_widgets.end()) {
+	update();
+    }
+}
+
+void
+Panner::viewManagerZoomLevelChanged(void *p, unsigned long z, bool)
+{
+    if (p == this) return;
+    if (m_widgets.find(p) != m_widgets.end()) {
+	update();
+    }
+}
+
+void
+Panner::viewManagerPlaybackFrameChanged(unsigned long f)
+{
+    bool changed = false;
+
+    if (getXForFrame(m_playPointerFrame) != getXForFrame(f)) changed = true;
+    m_playPointerFrame = f;
+
+    if (changed) update();
+}
+
+void
+Panner::paintEvent(QPaintEvent *e)
+{
+    // Recalculate zoom in case the size of the widget has changed.
+
+    size_t startFrame = getModelsStartFrame();
+    size_t frameCount = getModelsEndFrame() - getModelsStartFrame();
+    int zoomLevel = frameCount / width();
+    if (zoomLevel < 1) zoomLevel = 1;
+    zoomLevel = getZoomConstraintBlockSize(zoomLevel,
+					   ZoomConstraint::RoundUp);
+    if (zoomLevel != m_zoomLevel) {
+	m_zoomLevel = zoomLevel;
+	emit zoomLevelChanged(this, m_zoomLevel, m_followZoom);
+    }
+    size_t centreFrame = startFrame + m_zoomLevel * (width() / 2);
+    if (centreFrame > (startFrame + getModelsEndFrame())/2) {
+	centreFrame = (startFrame + getModelsEndFrame())/2;
+    }
+    if (centreFrame != m_centreFrame) {
+	m_centreFrame = centreFrame;
+	emit centreFrameChanged(this, m_centreFrame, false);
+    }
+
+    View::paintEvent(e);
+
+    QPainter paint;
+    paint.begin(this);
+
+    QRect r(rect());
+
+    if (e) {
+	r = e->rect();
+	paint.setClipRect(r);
+    }
+
+    paint.setPen(Qt::black);
+
+    int y = 0;
+
+    int prevx0 = -10;
+    int prevx1 = -10;
+
+    for (WidgetSet::iterator i = m_widgets.begin(); i != m_widgets.end(); ++i) {
+	if (!*i) continue;
+
+	View *w = (View *)*i;
+
+	long f0 = w->getFrameForX(0);
+	long f1 = w->getFrameForX(w->width());
+
+	int x0 = getXForFrame(f0);
+	int x1 = getXForFrame(f1);
+
+	if (x0 != prevx0 || x1 != prevx1) {
+	    y += height() / 10 + 1;
+	    prevx0 = x0;
+	    prevx1 = x1;
+	}
+
+	if (x1 <= x0) x1 = x0 + 1;
+	
+	paint.drawRect(x0, y, x1 - x0, height() - 2 * y);
+    }
+
+    paint.end();
+}
+
+void
+Panner::mousePressEvent(QMouseEvent *e)
+{
+    m_clickPos = e->pos();
+    for (WidgetSet::iterator i = m_widgets.begin(); i != m_widgets.end(); ++i) {
+	if (*i) {
+	    m_clickedInRange = true;
+	    m_dragCentreFrame = ((View *)*i)->getCentreFrame();
+	    break;
+	}
+    }
+}
+
+void
+Panner::mouseReleaseEvent(QMouseEvent *e)
+{
+    if (m_clickedInRange) {
+	mouseMoveEvent(e);
+    }
+    m_clickedInRange = false;
+}
+
+void
+Panner::mouseMoveEvent(QMouseEvent *e)
+{
+    if (!m_clickedInRange) return;
+
+    long xoff = int(e->x()) - int(m_clickPos.x());
+    long frameOff = xoff * m_zoomLevel;
+    
+    size_t newCentreFrame = m_dragCentreFrame;
+    if (frameOff > 0) {
+	newCentreFrame += frameOff;
+    } else if (newCentreFrame >= size_t(-frameOff)) {
+	newCentreFrame += frameOff;
+    } else {
+	newCentreFrame = 0;
+    }
+
+    if (newCentreFrame >= getModelsEndFrame()) {
+	newCentreFrame = getModelsEndFrame();
+	if (newCentreFrame > 0) --newCentreFrame;
+    }
+    
+    if (std::max(m_centreFrame, newCentreFrame) -
+	std::min(m_centreFrame, newCentreFrame) > size_t(m_zoomLevel)) {
+	emit centreFrameChanged(this, newCentreFrame, true);
+    }
+}
+
+#ifdef INCLUDE_MOCFILES
+#include "Panner.moc.cpp"
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/view/Panner.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,67 @@
+/* -*- 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 file copyright 2006 Chris Cannam.
+    
+    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.
+*/
+
+#ifndef _PAN_WIDGET_H_
+#define _PAN_WIDGET_H_
+
+#include "base/View.h"
+
+#include <QPoint>
+
+class QWidget;
+class QPaintEvent;
+class Layer;
+class View;
+
+#include <map>
+
+class Panner : public View
+{
+    Q_OBJECT
+
+public:
+    Panner(QWidget *parent = 0);
+
+    void registerView(View *widget);
+    void unregisterView(View *widget);
+
+    virtual QString getPropertyContainerIconName() const { return "panner"; }
+
+public slots:
+    virtual void modelChanged(size_t startFrame, size_t endFrame);
+    virtual void modelReplaced();
+
+    virtual void viewManagerCentreFrameChanged(void *, unsigned long, bool);
+    virtual void viewManagerZoomLevelChanged(void *, unsigned long, bool);
+    virtual void viewManagerPlaybackFrameChanged(unsigned long);
+
+protected:
+    virtual void paintEvent(QPaintEvent *e);
+    virtual void mousePressEvent(QMouseEvent *e);
+    virtual void mouseReleaseEvent(QMouseEvent *e);
+    virtual void mouseMoveEvent(QMouseEvent *e);
+    virtual bool shouldLabelSelections() const { return false; }
+
+    QPoint m_clickPos;
+    QPoint m_mousePos;
+    bool m_clickedInRange;
+    size_t m_dragCentreFrame;
+    
+    typedef std::set<void *> WidgetSet;
+    WidgetSet m_widgets;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/view/View.cpp	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,1524 @@
+/* -*- 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 file copyright 2006 Chris Cannam.
+    
+    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.
+*/
+
+#include "base/View.h"
+#include "base/Layer.h"
+#include "base/Model.h"
+#include "base/ZoomConstraint.h"
+#include "base/Profiler.h"
+
+#include "layer/TimeRulerLayer.h" //!!! damn, shouldn't be including that here
+#include "model/PowerOfSqrtTwoZoomConstraint.h" //!!! likewise
+
+#include <QPainter>
+#include <QPaintEvent>
+#include <QRect>
+#include <QApplication>
+
+#include <iostream>
+#include <cassert>
+#include <math.h>
+
+//#define DEBUG_VIEW_WIDGET_PAINT 1
+
+using std::cerr;
+using std::endl;
+
+View::View(QWidget *w, bool showProgress) :
+    QFrame(w),
+    m_centreFrame(0),
+    m_zoomLevel(1024),
+    m_followPan(true),
+    m_followZoom(true),
+    m_followPlay(PlaybackScrollPage),
+    m_lightBackground(true),
+    m_showProgress(showProgress),
+    m_cache(0),
+    m_cacheCentreFrame(0),
+    m_cacheZoomLevel(1024),
+    m_selectionCached(false),
+    m_deleting(false),
+    m_haveSelectedLayer(false),
+    m_manager(0),
+    m_propertyContainer(new ViewPropertyContainer(this))
+{
+//    QWidget::setAttribute(Qt::WA_PaintOnScreen);
+}
+
+View::~View()
+{
+//    std::cerr << "View::~View(" << this << ")" << std::endl;
+
+    m_deleting = true;
+    delete m_propertyContainer;
+}
+
+PropertyContainer::PropertyList
+View::getProperties() const
+{
+    PropertyContainer::PropertyList list;
+    list.push_back("Global Scroll");
+    list.push_back("Global Zoom");
+    list.push_back("Follow Playback");
+    return list;
+}
+
+QString
+View::getPropertyLabel(const PropertyName &pn) const
+{
+    if (pn == "Global Scroll") return tr("Global Scroll");
+    if (pn == "Global Zoom") return tr("Global Zoom");
+    if (pn == "Follow Playback") return tr("Follow Playback");
+    return "";
+}
+
+PropertyContainer::PropertyType
+View::getPropertyType(const PropertyContainer::PropertyName &name) const
+{
+    if (name == "Global Scroll") return PropertyContainer::ToggleProperty;
+    if (name == "Global Zoom") return PropertyContainer::ToggleProperty;
+    if (name == "Follow Playback") return PropertyContainer::ValueProperty;
+    return PropertyContainer::InvalidProperty;
+}
+
+int
+View::getPropertyRangeAndValue(const PropertyContainer::PropertyName &name,
+			       int *min, int *max) const
+{
+    if (name == "Global Scroll") return m_followPan;
+    if (name == "Global Zoom") return m_followZoom;
+    if (name == "Follow Playback") {
+	if (min) *min = 0;
+	if (max) *max = 2;
+	return int(m_followPlay);
+    }
+    if (min) *min = 0;
+    if (max) *max = 0;
+    return 0;
+}
+
+QString
+View::getPropertyValueLabel(const PropertyContainer::PropertyName &name,
+			    int value) const
+{
+    if (name == "Follow Playback") {
+	switch (value) {
+	default:
+	case 0: return tr("Scroll");
+	case 1: return tr("Page");
+	case 2: return tr("Off");
+	}
+    }
+    return tr("<unknown>");
+}
+
+void
+View::setProperty(const PropertyContainer::PropertyName &name, int value)
+{
+    if (name == "Global Scroll") {
+	setFollowGlobalPan(value != 0);
+    } else if (name == "Global Zoom") {
+	setFollowGlobalZoom(value != 0);
+    } else if (name == "Follow Playback") {
+	switch (value) {
+	default:
+	case 0: setPlaybackFollow(PlaybackScrollContinuous); break;
+	case 1: setPlaybackFollow(PlaybackScrollPage); break;
+	case 2: setPlaybackFollow(PlaybackIgnore); break;
+	}
+    }
+}
+
+size_t
+View::getPropertyContainerCount() const
+{
+    return m_layers.size() + 1; // the 1 is for me
+}
+
+const PropertyContainer *
+View::getPropertyContainer(size_t i) const
+{
+    return (const PropertyContainer *)(((View *)this)->
+				       getPropertyContainer(i));
+}
+
+PropertyContainer *
+View::getPropertyContainer(size_t i)
+{
+    if (i == 0) return m_propertyContainer;
+    return m_layers[i-1];
+}
+
+bool
+View::getValueExtents(QString unit, float &min, float &max, bool &log) const
+{
+    bool have = false;
+
+    for (LayerList::const_iterator i = m_layers.begin();
+         i != m_layers.end(); ++i) { 
+
+        QString layerUnit;
+        float layerMin = 0.0, layerMax = 0.0;
+        float displayMin = 0.0, displayMax = 0.0;
+        bool layerLog = false;
+
+        if ((*i)->getValueExtents(layerMin, layerMax, layerLog, layerUnit) &&
+            layerUnit.toLower() == unit.toLower()) {
+
+            if ((*i)->getDisplayExtents(displayMin, displayMax)) {
+
+                min = displayMin;
+                max = displayMax;
+                log = layerLog;
+                have = true;
+                break;
+
+            } else {
+
+                if (!have || layerMin < min) min = layerMin;
+                if (!have || layerMax > max) max = layerMax;
+                if (layerLog) log = true;
+                have = true;
+            }
+        }
+    }
+
+    return have;
+}
+
+int
+View::getTextLabelHeight(const Layer *layer, QPainter &paint) const
+{
+    std::map<int, Layer *> sortedLayers;
+
+    for (LayerList::const_iterator i = m_layers.begin();
+         i != m_layers.end(); ++i) { 
+        if ((*i)->needsTextLabelHeight()) {
+            sortedLayers[getObjectExportId(*i)] = *i;
+        }
+    }
+
+    int y = 15 + paint.fontMetrics().ascent();
+
+    for (std::map<int, Layer *>::const_iterator i = sortedLayers.begin();
+         i != sortedLayers.end(); ++i) {
+        if (i->second == layer) return y;
+        y += paint.fontMetrics().height();
+    }
+
+    return y;
+}
+
+void
+View::propertyContainerSelected(View *client, PropertyContainer *pc)
+{
+    if (client != this) return;
+    
+    if (pc == m_propertyContainer) {
+	if (m_haveSelectedLayer) {
+	    m_haveSelectedLayer = false;
+	    update();
+	}
+	return;
+    }
+
+    delete m_cache;
+    m_cache = 0;
+
+    Layer *selectedLayer = 0;
+
+    for (LayerList::iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+	if (*i == pc) {
+	    selectedLayer = *i;
+	    m_layers.erase(i);
+	    break;
+	}
+    }
+
+    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
+{
+    size_t w2 = (width() / 2) * m_zoomLevel;
+    size_t frame = m_centreFrame;
+    if (frame >= w2) {
+	frame -= w2;
+	return (frame / m_zoomLevel * m_zoomLevel);
+    } else {
+	frame = w2 - frame;
+	frame = frame / m_zoomLevel * m_zoomLevel;
+	return -(long)frame - m_zoomLevel;
+    }
+}
+
+size_t
+View::getEndFrame() const
+{
+    return getFrameForX(width()) - 1;
+}
+
+void
+View::setStartFrame(long f)
+{
+    setCentreFrame(f + m_zoomLevel * (width() / 2));
+}
+
+bool
+View::setCentreFrame(size_t f, bool e)
+{
+    bool changeVisible = false;
+
+    if (m_centreFrame != f) {
+
+	int formerPixel = m_centreFrame / m_zoomLevel;
+
+	m_centreFrame = f;
+
+	int newPixel = m_centreFrame / m_zoomLevel;
+	
+	if (newPixel != formerPixel) {
+
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+	    std::cout << "View(" << this << ")::setCentreFrame: newPixel " << newPixel << ", formerPixel " << formerPixel << std::endl;
+#endif
+	    update();
+
+	    changeVisible = true;
+	}
+
+	if (e) emit centreFrameChanged(this, f, m_followPan);
+    }
+
+    return changeVisible;
+}
+
+int
+View::getXForFrame(long frame) const
+{
+    return (frame - getStartFrame()) / m_zoomLevel;
+}
+
+long
+View::getFrameForX(int x) const
+{
+    return (long(x) * long(m_zoomLevel)) + getStartFrame();
+}
+
+float
+View::getYForFrequency(float frequency,
+		       float minf,
+		       float maxf, 
+		       bool logarithmic) const
+{
+    int h = height();
+
+    if (logarithmic) {
+
+	static float lastminf = 0.0, lastmaxf = 0.0;
+	static float logminf = 0.0, logmaxf = 0.0;
+
+	if (lastminf != minf) {
+	    lastminf = (minf == 0.0 ? 1.0 : minf);
+	    logminf = log10f(minf);
+	}
+	if (lastmaxf != maxf) {
+	    lastmaxf = (maxf < lastminf ? lastminf : maxf);
+	    logmaxf = log10f(maxf);
+	}
+
+	if (logminf == logmaxf) return 0;
+	return h - (h * (log10f(frequency) - logminf)) / (logmaxf - logminf);
+
+    } else {
+	
+	if (minf == maxf) return 0;
+	return h - (h * (frequency - minf)) / (maxf - minf);
+    }
+}
+
+float
+View::getFrequencyForY(int y,
+		       float minf,
+		       float maxf,
+		       bool logarithmic) const
+{
+    int h = height();
+
+    if (logarithmic) {
+
+	static float lastminf = 0.0, lastmaxf = 0.0;
+	static float logminf = 0.0, logmaxf = 0.0;
+
+	if (lastminf != minf) {
+	    lastminf = (minf == 0.0 ? 1.0 : minf);
+	    logminf = log10f(minf);
+	}
+	if (lastmaxf != maxf) {
+	    lastmaxf = (maxf < lastminf ? lastminf : maxf);
+	    logmaxf = log10f(maxf);
+	}
+
+	if (logminf == logmaxf) return 0;
+	return pow(10.f, logminf + ((logmaxf - logminf) * (h - y)) / h);
+
+    } else {
+
+	if (minf == maxf) return 0;
+	return minf + ((h - y) * (maxf - minf)) / h;
+    }
+}
+
+int
+View::getZoomLevel() const
+{
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+	std::cout << "zoom level: " << m_zoomLevel << std::endl;
+#endif
+    return m_zoomLevel;
+}
+
+void
+View::setZoomLevel(size_t z)
+{
+    if (m_zoomLevel != int(z)) {
+	m_zoomLevel = z;
+	emit zoomLevelChanged(this, z, m_followZoom);
+	update();
+    }
+}
+
+View::LayerProgressBar::LayerProgressBar(QWidget *parent) :
+    QProgressBar(parent)
+{
+    QFont f(font());
+    f.setPointSize(f.pointSize() * 8 / 10);
+    setFont(f);
+}
+
+void
+View::addLayer(Layer *layer)
+{
+    delete m_cache;
+    m_cache = 0;
+
+    m_layers.push_back(layer);
+
+    m_progressBars[layer] = new LayerProgressBar(this);
+    m_progressBars[layer]->setMinimum(0);
+    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()),
+	    this,    SLOT(layerNameChanged()));
+    connect(layer, SIGNAL(modelChanged()),
+	    this,    SLOT(modelChanged()));
+    connect(layer, SIGNAL(modelCompletionChanged()),
+	    this,    SLOT(modelCompletionChanged()));
+    connect(layer, SIGNAL(modelChanged(size_t, size_t)),
+	    this,    SLOT(modelChanged(size_t, size_t)));
+    connect(layer, SIGNAL(modelReplaced()),
+	    this,    SLOT(modelReplaced()));
+
+    update();
+
+    emit propertyContainerAdded(layer);
+}
+
+void
+View::removeLayer(Layer *layer)
+{
+    if (m_deleting) {
+	return;
+    }
+
+    delete m_cache;
+    m_cache = 0;
+
+    for (LayerList::iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+	if (*i == layer) {
+	    m_layers.erase(i);
+	    if (m_progressBars.find(layer) != m_progressBars.end()) {
+		delete m_progressBars[layer];
+		m_progressBars.erase(layer);
+	    }
+	    break;
+	}
+    }
+    
+    update();
+
+    emit propertyContainerRemoved(layer);
+}
+
+Layer *
+View::getSelectedLayer()
+{
+    if (m_haveSelectedLayer && !m_layers.empty()) {
+	return getLayer(getLayerCount() - 1);
+    } else {
+	return 0;
+    }
+}
+
+const Layer *
+View::getSelectedLayer() const
+{
+    return const_cast<const Layer *>(const_cast<View *>(this)->getSelectedLayer());
+}
+
+void
+View::setViewManager(ViewManager *manager)
+{
+    if (m_manager) {
+	m_manager->disconnect(this, SLOT(viewManagerCentreFrameChanged(void *, unsigned long, bool)));
+	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()));
+	disconnect(m_manager, SIGNAL(inProgressSelectionChanged()));
+    }
+
+    m_manager = manager;
+    if (m_followPan) setCentreFrame(m_manager->getGlobalCentreFrame(), false);
+    if (m_followZoom) setZoomLevel(m_manager->getGlobalZoom());
+
+    connect(m_manager, SIGNAL(centreFrameChanged(void *, unsigned long, bool)),
+	    this, SLOT(viewManagerCentreFrameChanged(void *, unsigned long, bool)));
+    connect(m_manager, SIGNAL(playbackFrameChanged(unsigned long)),
+	    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(selectionChanged()));
+    connect(m_manager, SIGNAL(inProgressSelectionChanged()),
+	    this, SLOT(selectionChanged()));
+    connect(m_manager, SIGNAL(overlayModeChanged()),
+            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
+View::setFollowGlobalPan(bool f)
+{
+    m_followPan = f;
+    emit propertyContainerPropertyChanged(m_propertyContainer);
+}
+
+void
+View::setFollowGlobalZoom(bool f)
+{
+    m_followZoom = f;
+    emit propertyContainerPropertyChanged(m_propertyContainer);
+}
+
+void
+View::drawVisibleText(QPainter &paint, int x, int y, QString text, TextStyle style)
+{
+    if (style == OutlinedText) {
+
+	QColor origPenColour = paint.pen().color();
+	QColor penColour = origPenColour;
+	QColor surroundColour = Qt::white;  //palette().background().color();
+
+	if (!hasLightBackground()) {
+	    int h, s, v;
+	    penColour.getHsv(&h, &s, &v);
+	    penColour = QColor::fromHsv(h, s, 255 - v);
+	    surroundColour = Qt::black;
+	}
+
+	paint.setPen(surroundColour);
+
+	for (int dx = -1; dx <= 1; ++dx) {
+	    for (int dy = -1; dy <= 1; ++dy) {
+		if (!(dx || dy)) continue;
+		paint.drawText(x + dx, y + dy, text);
+	    }
+	}
+
+	paint.setPen(penColour);
+
+	paint.drawText(x, y, text);
+
+	paint.setPen(origPenColour);
+
+    } else {
+
+	std::cerr << "ERROR: View::drawVisibleText: Boxed style not yet implemented!" << std::endl;
+    }
+}
+
+void
+View::setPlaybackFollow(PlaybackFollowMode m)
+{
+    m_followPlay = m;
+    emit propertyContainerPropertyChanged(m_propertyContainer);
+}
+
+void
+View::modelChanged()
+{
+    QObject *obj = sender();
+
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+    std::cerr << "View(" << this << ")::modelChanged()" << std::endl;
+#endif
+    
+    // If the model that has changed is not used by any of the cached
+    // layers, we won't need to recreate the cache
+    
+    bool recreate = false;
+
+    bool discard;
+    LayerList scrollables = getScrollableBackLayers(false, discard);
+    for (LayerList::const_iterator i = scrollables.begin();
+	 i != scrollables.end(); ++i) {
+	if (*i == obj || (*i)->getModel() == obj) {
+	    recreate = true;
+	    break;
+	}
+    }
+
+    if (recreate) {
+	delete m_cache;
+	m_cache = 0;
+    }
+
+    checkProgress(obj);
+
+    update();
+}
+
+void
+View::modelChanged(size_t startFrame, size_t endFrame)
+{
+    QObject *obj = sender();
+
+    long myStartFrame = getStartFrame();
+    size_t myEndFrame = getEndFrame();
+
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+    std::cerr << "View(" << this << ")::modelChanged(" << startFrame << "," << endFrame << ") [me " << myStartFrame << "," << myEndFrame << "]" << std::endl;
+#endif
+
+    if (myStartFrame > 0 && endFrame < size_t(myStartFrame)) {
+	checkProgress(obj);
+	return;
+    }
+    if (startFrame > myEndFrame) {
+	checkProgress(obj);
+	return;
+    }
+
+    // If the model that has changed is not used by any of the cached
+    // layers, we won't need to recreate the cache
+    
+    bool recreate = false;
+
+    bool discard;
+    LayerList scrollables = getScrollableBackLayers(false, discard);
+    for (LayerList::const_iterator i = scrollables.begin();
+	 i != scrollables.end(); ++i) {
+	if (*i == obj || (*i)->getModel() == obj) {
+	    recreate = true;
+	    break;
+	}
+    }
+
+    if (recreate) {
+	delete m_cache;
+	m_cache = 0;
+    }
+
+    if (long(startFrame) < myStartFrame) startFrame = myStartFrame;
+    if (endFrame > myEndFrame) endFrame = myEndFrame;
+
+    int x0 = getXForFrame(startFrame);
+    int x1 = getXForFrame(endFrame + 1);
+    if (x1 < x0) x1 = x0;
+
+    checkProgress(obj);
+
+    update();
+//!!!    update(x0, 0, x1 - x0 + 1, height());
+}    
+
+void
+View::modelCompletionChanged()
+{
+    QObject *obj = sender();
+    checkProgress(obj);
+}
+
+void
+View::modelReplaced()
+{
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+    std::cerr << "View(" << this << ")::modelReplaced()" << std::endl;
+#endif
+    delete m_cache;
+    m_cache = 0;
+
+    update();
+}
+
+void
+View::layerParametersChanged()
+{
+    Layer *layer = dynamic_cast<Layer *>(sender());
+
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+    std::cerr << "View::layerParametersChanged()" << std::endl;
+#endif
+
+    delete m_cache;
+    m_cache = 0;
+    update();
+
+    if (layer) {
+	emit propertyContainerPropertyChanged(layer);
+    }
+}
+
+void
+View::layerNameChanged()
+{
+    Layer *layer = dynamic_cast<Layer *>(sender());
+    if (layer) emit propertyContainerNameChanged(layer);
+}
+
+void
+View::viewManagerCentreFrameChanged(void *p, unsigned long f, bool locked)
+{
+    if (m_followPan && p != this && locked) {
+	if (m_manager && (sender() == m_manager)) {
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+	    std::cerr << this << ": manager frame changed " << f << " from " << p << std::endl;
+#endif
+	    setCentreFrame(f);
+	    if (p == this) repaint();
+	}
+    }
+}
+
+void
+View::viewManagerPlaybackFrameChanged(unsigned long f)
+{
+    if (m_manager) {
+	if (sender() != m_manager) return;
+    }
+
+    if (m_playPointerFrame == f) return;
+    bool visible = (getXForFrame(m_playPointerFrame) != getXForFrame(f));
+    size_t oldPlayPointerFrame = m_playPointerFrame;
+    m_playPointerFrame = f;
+    if (!visible) return;
+
+    switch (m_followPlay) {
+
+    case PlaybackScrollContinuous:
+	if (QApplication::mouseButtons() == Qt::NoButton) {
+	    setCentreFrame(f, false);
+	}
+	break;
+
+    case PlaybackScrollPage:
+    { 
+	int xold = getXForFrame(oldPlayPointerFrame);
+	update(xold - 1, 0, 3, height());
+
+	long w = getEndFrame() - getStartFrame();
+	w -= w/5;
+	long sf = (f / w) * w - w/8;
+
+	if (m_manager &&
+	    m_manager->isPlaying() &&
+	    m_manager->getPlaySelectionMode()) {
+	    MultiSelection::SelectionList selections = m_manager->getSelections();
+	    if (!selections.empty()) {
+		size_t selectionStart = selections.begin()->getStartFrame();
+		if (sf < long(selectionStart) - w / 10) {
+		    sf = long(selectionStart) - w / 10;
+		}
+	    }
+	}
+
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+	std::cerr << "PlaybackScrollPage: f = " << f << ", sf = " << sf << ", start frame "
+		  << getStartFrame() << std::endl;
+#endif
+
+	// We don't consider scrolling unless the pointer is outside
+	// the clearly visible range already
+
+	int xnew = getXForFrame(m_playPointerFrame);
+
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+	std::cerr << "xnew = " << xnew << ", width = " << width() << std::endl;
+#endif
+
+	if (xnew < width()/8 || xnew > (width()*7)/8) {
+	    if (QApplication::mouseButtons() == Qt::NoButton) {
+		long offset = getFrameForX(width()/2) - getStartFrame();
+		long newCentre = sf + offset;
+		bool changed = setCentreFrame(newCentre, false);
+		if (changed) {
+		    xold = getXForFrame(oldPlayPointerFrame);
+		    update(xold - 1, 0, 3, height());
+		}
+	    }
+	}
+
+	update(xnew - 1, 0, 3, height());
+
+	break;
+    }
+
+    case PlaybackIgnore:
+	if (long(f) >= getStartFrame() && f < getEndFrame()) {
+	    update();
+	}
+	break;
+    }
+}
+
+void
+View::viewManagerZoomLevelChanged(void *p, unsigned long z, bool locked)
+{
+    if (m_followZoom && p != this && locked) {
+	if (m_manager && (sender() == m_manager)) {
+	    setZoomLevel(z);
+	    if (p == this) repaint();
+	}
+    }
+}
+
+void
+View::selectionChanged()
+{
+    if (m_selectionCached) {
+	delete m_cache;
+	m_cache = 0;
+	m_selectionCached = false;
+    }
+    update();
+}
+
+size_t
+View::getModelsStartFrame() const
+{
+    bool first = true;
+    size_t startFrame = 0;
+
+    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+
+	if ((*i)->getModel() && (*i)->getModel()->isOK()) {
+
+	    size_t thisStartFrame = (*i)->getModel()->getStartFrame();
+
+	    if (first || thisStartFrame < startFrame) {
+		startFrame = thisStartFrame;
+	    }
+	    first = false;
+	}
+    }
+    return startFrame;
+}
+
+size_t
+View::getModelsEndFrame() const
+{
+    bool first = true;
+    size_t endFrame = 0;
+
+    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+
+	if ((*i)->getModel() && (*i)->getModel()->isOK()) {
+
+	    size_t thisEndFrame = (*i)->getModel()->getEndFrame();
+
+	    if (first || thisEndFrame > endFrame) {
+		endFrame = thisEndFrame;
+	    }
+	    first = false;
+	}
+    }
+
+    if (first) return getModelsStartFrame();
+    return endFrame;
+}
+
+int
+View::getModelsSampleRate() const
+{
+    //!!! Just go for the first, for now.  If we were supporting
+    // multiple samplerates, we'd probably want to do frame/time
+    // conversion in the model
+
+    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+	if ((*i)->getModel() && (*i)->getModel()->isOK()) {
+	    return (*i)->getModel()->getSampleRate();
+	}
+    }
+    return 0;
+}
+
+bool
+View::areLayersScrollable() const
+{
+    // True iff all views are scrollable
+    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+	if (!(*i)->isLayerScrollable(this)) return false;
+    }
+    return true;
+}
+
+View::LayerList
+View::getScrollableBackLayers(bool testChanged, bool &changed) const
+{
+    changed = false;
+
+    // We want a list of all the scrollable layers that are behind the
+    // backmost non-scrollable layer.
+
+    LayerList scrollables;
+    bool metUnscrollable = false;
+
+    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+	if ((*i)->isLayerDormant(this)) continue;
+	if ((*i)->isLayerOpaque()) {
+	    // You can't see anything behind an opaque layer!
+	    scrollables.clear();
+            if (metUnscrollable) break;
+	}
+	if (!metUnscrollable && (*i)->isLayerScrollable(this)) {
+            scrollables.push_back(*i);
+        } else {
+            metUnscrollable = true;
+        }
+    }
+
+    if (testChanged && scrollables != m_lastScrollableBackLayers) {
+	m_lastScrollableBackLayers = scrollables;
+	changed = true;
+    }
+    return scrollables;
+}
+
+View::LayerList
+View::getNonScrollableFrontLayers(bool testChanged, bool &changed) const
+{
+    changed = false;
+    LayerList nonScrollables;
+
+    // Everything in front of the first non-scrollable from the back
+    // should also be considered non-scrollable
+
+    bool started = false;
+
+    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+	if ((*i)->isLayerDormant(this)) continue;
+	if (!started && (*i)->isLayerScrollable(this)) {
+	    continue;
+	}
+	started = true;
+	if ((*i)->isLayerOpaque()) {
+	    // You can't see anything behind an opaque layer!
+	    nonScrollables.clear();
+	}
+	nonScrollables.push_back(*i);
+    }
+
+    if (testChanged && nonScrollables != m_lastNonScrollableBackLayers) {
+	m_lastNonScrollableBackLayers = nonScrollables;
+	changed = true;
+    }
+
+    return nonScrollables;
+}
+
+size_t
+View::getZoomConstraintBlockSize(size_t blockSize,
+				 ZoomConstraint::RoundingDirection dir)
+    const
+{
+    size_t candidate = blockSize;
+    bool haveCandidate = false;
+
+    PowerOfSqrtTwoZoomConstraint defaultZoomConstraint;
+
+    for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
+
+	const ZoomConstraint *zoomConstraint = (*i)->getZoomConstraint();
+	if (!zoomConstraint) zoomConstraint = &defaultZoomConstraint;
+
+	size_t thisBlockSize =
+	    zoomConstraint->getNearestBlockSize(blockSize, dir);
+
+	// Go for the block size that's furthest from the one
+	// passed in.  Most of the time, that's what we want.
+	if (!haveCandidate ||
+	    (thisBlockSize > blockSize && thisBlockSize > candidate) ||
+	    (thisBlockSize < blockSize && thisBlockSize < candidate)) {
+	    candidate = thisBlockSize;
+	    haveCandidate = true;
+	}
+    }
+
+    return candidate;
+}
+
+void
+View::zoom(bool in)
+{
+    int newZoomLevel = m_zoomLevel;
+
+    if (in) {
+	newZoomLevel = getZoomConstraintBlockSize(newZoomLevel - 1, 
+						  ZoomConstraint::RoundDown);
+    } else {
+	newZoomLevel = getZoomConstraintBlockSize(newZoomLevel + 1,
+						  ZoomConstraint::RoundUp);
+    }
+
+    if (newZoomLevel != m_zoomLevel) {
+	setZoomLevel(newZoomLevel);
+    }
+}
+
+void
+View::scroll(bool right, bool lots)
+{
+    long delta;
+    if (lots) {
+	delta = (getEndFrame() - getStartFrame()) / 2;
+    } else {
+	delta = (getEndFrame() - getStartFrame()) / 20;
+    }
+    if (right) delta = -delta;
+
+    if (int(m_centreFrame) < delta) {
+	setCentreFrame(0);
+    } else if (int(m_centreFrame) - delta >= int(getModelsEndFrame())) {
+	setCentreFrame(getModelsEndFrame());
+    } else {
+	setCentreFrame(m_centreFrame - delta);
+    }
+}
+
+void
+View::checkProgress(void *object)
+{
+    if (!m_showProgress) return;
+
+    int ph = height();
+
+    for (ProgressMap::const_iterator i = m_progressBars.begin();
+	 i != m_progressBars.end(); ++i) {
+
+	if (i->first == object) {
+
+	    int completion = i->first->getCompletion(this);
+
+	    if (completion >= 100) {
+
+		i->second->hide();
+
+	    } else {
+
+		i->second->setText(i->first->getPropertyContainerName());
+		i->second->setValue(completion);
+		i->second->move(0, ph - i->second->height());
+
+		i->second->show();
+		i->second->update();
+
+		ph -= i->second->height();
+	    }
+	} else {
+	    if (i->second->isVisible()) {
+		ph -= i->second->height();
+	    }
+	}
+    }
+}
+
+void
+View::paintEvent(QPaintEvent *e)
+{
+//    Profiler prof("View::paintEvent", false);
+//    std::cerr << "View::paintEvent" << std::endl;
+
+    if (m_layers.empty()) {
+	QFrame::paintEvent(e);
+	return;
+    }
+
+    // ensure our constraints are met
+    m_zoomLevel = getZoomConstraintBlockSize(m_zoomLevel,
+					     ZoomConstraint::RoundUp);
+
+    QPainter paint;
+    bool repaintCache = false;
+    bool paintedCacheRect = false;
+
+    QRect cacheRect(rect());
+
+    if (e) {
+	cacheRect &= e->rect();
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+	std::cerr << "paint rect " << cacheRect.width() << "x" << cacheRect.height()
+		  << ", my rect " << width() << "x" << height() << std::endl;
+#endif
+    }
+
+    QRect nonCacheRect(cacheRect);
+
+    // If not all layers are scrollable, but some of the back layers
+    // are, we should store only those in the cache.
+
+    bool layersChanged = false;
+    LayerList scrollables = getScrollableBackLayers(true, layersChanged);
+    LayerList nonScrollables = getNonScrollableFrontLayers(true, layersChanged);
+    bool selectionCacheable = nonScrollables.empty();
+    bool haveSelections = m_manager && !m_manager->getSelections().empty();
+    bool selectionDrawn = false;
+
+    // If all the non-scrollable layers are non-opaque, then we draw
+    // the selection rectangle behind them and cache it.  If any are
+    // opaque, however, we can't cache.
+    //
+    if (!selectionCacheable) {
+	selectionCacheable = true;
+	for (LayerList::const_iterator i = nonScrollables.begin();
+	     i != nonScrollables.end(); ++i) {
+	    if ((*i)->isLayerOpaque()) {
+		selectionCacheable = false;
+		break;
+	    }
+	}
+    }
+
+    if (selectionCacheable) {
+	QPoint localPos;
+	bool closeToLeft, closeToRight;
+	if (shouldIlluminateLocalSelection(localPos, closeToLeft, closeToRight)) {
+	    selectionCacheable = false;
+	}
+    }
+
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+    std::cerr << "View(" << this << ")::paintEvent: have " << scrollables.size()
+	      << " scrollable back layers and " << nonScrollables.size()
+	      << " non-scrollable front layers" << std::endl;
+    std::cerr << "haveSelections " << haveSelections << ", selectionCacheable "
+	      << selectionCacheable << ", m_selectionCached " << m_selectionCached << std::endl;
+#endif
+
+    if (layersChanged || scrollables.empty() ||
+	(haveSelections && (selectionCacheable != m_selectionCached))) {
+	delete m_cache;
+	m_cache = 0;
+	m_selectionCached = false;
+    }
+
+    if (!scrollables.empty()) {
+	if (!m_cache ||
+	    m_cacheZoomLevel != m_zoomLevel ||
+	    width() != m_cache->width() ||
+	    height() != m_cache->height()) {
+
+	    // cache is not valid
+
+	    if (cacheRect.width() < width()/10) {
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+		std::cerr << "View(" << this << ")::paintEvent: small repaint, not bothering to recreate cache" << std::endl;
+#endif
+	    } else {
+		delete m_cache;
+		m_cache = new QPixmap(width(), height());
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+		std::cerr << "View(" << this << ")::paintEvent: recreated cache" << std::endl;
+#endif
+		cacheRect = rect();
+		repaintCache = true;
+	    }
+
+	} else if (m_cacheCentreFrame != m_centreFrame) {
+
+	    long dx =
+		getXForFrame(m_cacheCentreFrame) -
+		getXForFrame(m_centreFrame);
+
+	    if (dx > -width() && dx < width()) {
+#if defined(Q_WS_WIN32) || defined(Q_WS_MAC)
+		// Copying a pixmap to itself doesn't work properly on Windows
+		// or Mac (it only works when moving in one direction)
+		static QPixmap *tmpPixmap = 0;
+		if (!tmpPixmap ||
+		    tmpPixmap->width() != width() ||
+		    tmpPixmap->height() != height()) {
+		    delete tmpPixmap;
+		    tmpPixmap = new QPixmap(width(), height());
+		}
+		paint.begin(tmpPixmap);
+		paint.drawPixmap(0, 0, *m_cache);
+		paint.end();
+		paint.begin(m_cache);
+		paint.drawPixmap(dx, 0, *tmpPixmap);
+		paint.end();
+#else
+		// But it seems to be fine on X11
+		paint.begin(m_cache);
+		paint.drawPixmap(dx, 0, *m_cache);
+		paint.end();
+#endif
+
+		if (dx < 0) {
+		    cacheRect = QRect(width() + dx, 0, -dx, height());
+		} else {
+		    cacheRect = QRect(0, 0, dx, height());
+		}
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+		std::cerr << "View(" << this << ")::paintEvent: scrolled cache by " << dx << std::endl;
+#endif
+	    } else {
+		cacheRect = rect();
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+		std::cerr << "View(" << this << ")::paintEvent: scrolling too far" << std::endl;
+#endif
+	    }
+	    repaintCache = true;
+
+	} else {
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+	    std::cerr << "View(" << this << ")::paintEvent: cache is good" << std::endl;
+#endif
+	    paint.begin(this);
+	    paint.drawPixmap(cacheRect, *m_cache, cacheRect);
+	    paint.end();
+	    QFrame::paintEvent(e);
+	    paintedCacheRect = true;
+	}
+
+	m_cacheCentreFrame = m_centreFrame;
+	m_cacheZoomLevel = m_zoomLevel;
+    }
+
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+//    std::cerr << "View(" << this << ")::paintEvent: cacheRect " << cacheRect << ", nonCacheRect " << (nonCacheRect | cacheRect) << ", repaintCache " << repaintCache << ", paintedCacheRect " << paintedCacheRect << std::endl;
+#endif
+
+    // Scrollable (cacheable) items first
+
+    if (!paintedCacheRect) {
+
+	if (repaintCache) paint.begin(m_cache);
+	else paint.begin(this);
+
+	paint.setClipRect(cacheRect);
+	
+	if (hasLightBackground()) {
+	    paint.setPen(Qt::white);
+	    paint.setBrush(Qt::white);
+	} else {
+	    paint.setPen(Qt::black);
+	    paint.setBrush(Qt::black);
+	}
+	paint.drawRect(cacheRect);
+
+	paint.setPen(Qt::black);
+	paint.setBrush(Qt::NoBrush);
+	
+	for (LayerList::iterator i = scrollables.begin(); i != scrollables.end(); ++i) {
+	    paint.setRenderHint(QPainter::Antialiasing, false);
+	    paint.save();
+	    (*i)->paint(this, paint, cacheRect);
+	    paint.restore();
+	}
+
+	if (haveSelections && selectionCacheable) {
+	    drawSelections(paint);
+	    m_selectionCached = repaintCache;
+	    selectionDrawn = true;
+	}
+	
+	paint.end();
+
+	if (repaintCache) {
+	    cacheRect |= (e ? e->rect() : rect());
+	    paint.begin(this);
+	    paint.drawPixmap(cacheRect, *m_cache, cacheRect);
+	    paint.end();
+	}
+    }
+
+    // Now non-cacheable items.  We always need to redraw the
+    // non-cacheable items across at least the area we drew of the
+    // cacheable items.
+
+    nonCacheRect |= cacheRect;
+
+    paint.begin(this);
+    paint.setClipRect(nonCacheRect);
+
+    if (scrollables.empty()) {
+	if (hasLightBackground()) {
+	    paint.setPen(Qt::white);
+	    paint.setBrush(Qt::white);
+	} else {
+	    paint.setPen(Qt::black);
+	    paint.setBrush(Qt::black);
+	}
+	paint.drawRect(nonCacheRect);
+    }
+	
+    paint.setPen(Qt::black);
+    paint.setBrush(Qt::NoBrush);
+	
+    for (LayerList::iterator i = nonScrollables.begin(); i != nonScrollables.end(); ++i) {
+//        Profiler profiler2("View::paintEvent non-cacheable");
+	(*i)->paint(this, paint, nonCacheRect);
+    }
+	
+    paint.end();
+
+    paint.begin(this);
+    if (e) paint.setClipRect(e->rect());
+    if (!m_selectionCached) {
+	drawSelections(paint);
+    }
+    paint.end();
+
+    if (m_followPlay != PlaybackScrollContinuous) {
+
+	paint.begin(this);
+
+	if (long(m_playPointerFrame) > getStartFrame() &&
+	    m_playPointerFrame < getEndFrame()) {
+
+	    int playx = getXForFrame(m_playPointerFrame);
+
+	    paint.setPen(Qt::black);
+	    paint.drawLine(playx - 1, 0, playx - 1, height() - 1);
+	    paint.drawLine(playx + 1, 0, playx + 1, height() - 1);
+	    paint.drawPoint(playx, 0);
+	    paint.drawPoint(playx, height() - 1);
+	    paint.setPen(Qt::white);
+	    paint.drawLine(playx, 1, playx, height() - 2);
+	}
+
+	paint.end();
+    }
+
+    QFrame::paintEvent(e);
+}
+
+void
+View::drawSelections(QPainter &paint)
+{
+    MultiSelection::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.save();
+    paint.setBrush(QColor(150, 150, 255, 80));
+
+    int sampleRate = getModelsSampleRate();
+
+    QPoint localPos;
+    long illuminateFrame = -1;
+    bool closeToLeft, closeToRight;
+
+    if (shouldIlluminateLocalSelection(localPos, closeToLeft, closeToRight)) {
+	illuminateFrame = getFrameForX(localPos.x());
+    }
+
+    const QFontMetrics &metrics = paint.fontMetrics();
+
+    for (MultiSelection::SelectionList::iterator i = selections.begin();
+	 i != selections.end(); ++i) {
+
+	int p0 = getXForFrame(i->getStartFrame());
+	int p1 = getXForFrame(i->getEndFrame());
+
+	if (p1 < 0 || p0 > width()) continue;
+
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+	std::cerr << "View::drawSelections: " << p0 << ",-1 [" << (p1-p0) << "x" << (height()+1) << "]" << std::endl;
+#endif
+
+	bool illuminateThis =
+	    (illuminateFrame >= 0 && i->contains(illuminateFrame));
+
+	paint.setPen(QColor(150, 150, 255));
+	paint.drawRect(p0, -1, p1 - p0, height() + 1);
+
+	if (illuminateThis) {
+	    paint.save();
+	    if (hasLightBackground()) {
+		paint.setPen(QPen(Qt::black, 2));
+	    } else {
+		paint.setPen(QPen(Qt::white, 2));
+	    }
+	    if (closeToLeft) {
+		paint.drawLine(p0, 1, p1, 1);
+		paint.drawLine(p0, 0, p0, height());
+		paint.drawLine(p0, height() - 1, p1, height() - 1);
+	    } else if (closeToRight) {
+		paint.drawLine(p0, 1, p1, 1);
+		paint.drawLine(p1, 0, p1, height());
+		paint.drawLine(p0, height() - 1, p1, height() - 1);
+	    } else {
+		paint.setBrush(Qt::NoBrush);
+		paint.drawRect(p0, 1, p1 - p0, height() - 2);
+	    }
+	    paint.restore();
+	}
+
+	if (sampleRate && shouldLabelSelections() && m_manager &&
+            m_manager->getOverlayMode() != ViewManager::NoOverlays) {
+	    
+	    QString startText = QString("%1 / %2")
+		.arg(QString::fromStdString
+		     (RealTime::frame2RealTime
+		      (i->getStartFrame(), sampleRate).toText(true)))
+		.arg(i->getStartFrame());
+	    
+	    QString endText = QString(" %1 / %2")
+		.arg(QString::fromStdString
+		     (RealTime::frame2RealTime
+		      (i->getEndFrame(), sampleRate).toText(true)))
+		.arg(i->getEndFrame());
+	    
+	    QString durationText = QString("(%1 / %2) ")
+		.arg(QString::fromStdString
+		     (RealTime::frame2RealTime
+		      (i->getEndFrame() - i->getStartFrame(), sampleRate)
+		      .toText(true)))
+		.arg(i->getEndFrame() - i->getStartFrame());
+
+	    int sw = metrics.width(startText),
+		ew = metrics.width(endText),
+		dw = metrics.width(durationText);
+
+	    int sy = metrics.ascent() + metrics.height() + 4;
+	    int ey = sy;
+	    int dy = sy + metrics.height();
+
+	    int sx = p0 + 2;
+	    int ex = sx;
+	    int dx = sx;
+
+	    if (sw + ew > (p1 - p0)) {
+		ey += metrics.height();
+		dy += metrics.height();
+	    }
+
+	    if (ew < (p1 - p0)) {
+		ex = p1 - 2 - ew;
+	    }
+
+	    if (dw < (p1 - p0)) {
+		dx = p1 - 2 - dw;
+	    }
+
+	    paint.drawText(sx, sy, startText);
+	    paint.drawText(ex, ey, endText);
+	    paint.drawText(dx, dy, durationText);
+	}
+    }
+
+    paint.restore();
+}
+
+QString
+View::toXmlString(QString indent, QString extraAttributes) const
+{
+    QString s;
+
+    s += indent;
+
+    s += QString("<view "
+		 "centre=\"%1\" "
+		 "zoom=\"%2\" "
+		 "followPan=\"%3\" "
+		 "followZoom=\"%4\" "
+		 "tracking=\"%5\" "
+		 "light=\"%6\" %7>\n")
+	.arg(m_centreFrame)
+	.arg(m_zoomLevel)
+	.arg(m_followPan)
+	.arg(m_followZoom)
+	.arg(m_followPlay == PlaybackScrollContinuous ? "scroll" :
+	     m_followPlay == PlaybackScrollPage ? "page" : "ignore")
+	.arg(m_lightBackground)
+	.arg(extraAttributes);
+
+    for (size_t i = 0; i < m_layers.size(); ++i) {
+	s += m_layers[i]->toXmlString(indent + "  ");
+    }
+
+    s += indent + "</view>\n";
+
+    return s;
+}
+
+ViewPropertyContainer::ViewPropertyContainer(View *v) :
+    m_v(v)
+{
+    connect(m_v, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
+	    this, SIGNAL(propertyChanged(PropertyContainer::PropertyName)));
+}
+
+#ifdef INCLUDE_MOCFILES
+#include "View.moc.cpp"
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/view/View.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,362 @@
+/* -*- 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 file copyright 2006 Chris Cannam.
+    
+    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.
+*/
+
+#ifndef _CANVAS_H_
+#define _CANVAS_H_
+
+#include <QFrame>
+#include <QProgressBar>
+
+#include "base/ZoomConstraint.h"
+#include "base/PropertyContainer.h"
+#include "base/ViewManager.h"
+#include "base/XmlExportable.h"
+
+// #define DEBUG_VIEW_WIDGET_PAINT 1
+
+class Layer;
+class ViewPropertyContainer;
+
+#include <map>
+
+/**
+ * View is the base class of widgets that display one or more
+ * overlaid views of data against a horizontal time scale. 
+ *
+ * A View may have any number of attached Layers, each of which
+ * is expected to have one data Model (although multiple views may
+ * share the same model).
+ *
+ * A View may be panned in time and zoomed, although the
+ * mechanisms for doing so (as well as any other operations and
+ * properties available) depend on the subclass.
+ */
+
+class View : public QFrame,
+	     public XmlExportable
+{
+    Q_OBJECT
+
+public:
+    /**
+     * Deleting a View does not delete any of its layers.  They should
+     * be managed elsewhere (e.g. by the Document).
+     */
+    virtual ~View();
+
+    /**
+     * Retrieve the first visible sample frame on the widget.
+     * This is a calculated value based on the centre-frame, widget
+     * width and zoom level.  The result may be negative.
+     */
+    virtual long getStartFrame() const;
+
+    /**
+     * Set the widget pan based on the given first visible frame.  The
+     * frame value may be negative.
+     */
+    virtual void setStartFrame(long);
+
+    /**
+     * Return the centre frame of the visible widget.  This is an
+     * exact value that does not depend on the zoom block size.  Other
+     * frame values (start, end) are calculated from this based on the
+     * zoom and other factors.
+     */
+    virtual size_t getCentreFrame() const { return m_centreFrame; }
+
+    /**
+     * Set the centre frame of the visible widget.
+     */
+    virtual void setCentreFrame(size_t f) { setCentreFrame(f, true); }
+
+    /**
+     * Retrieve the last visible sample frame on the widget.
+     * This is a calculated value based on the centre-frame, widget
+     * width and zoom level.
+     */
+    virtual size_t getEndFrame() const;
+
+    /**
+     * Return the pixel x-coordinate corresponding to a given sample
+     * frame (which may be negative).
+     */
+    int getXForFrame(long frame) const;
+
+    /**
+     * Return the closest frame to the given pixel x-coordinate.
+     */
+    long getFrameForX(int x) const;
+
+    /**
+     * Return the pixel y-coordinate corresponding to a given
+     * frequency, if the frequency range is as specified.  This does
+     * not imply any policy about layer frequency ranges, but it might
+     * be useful for layers to match theirs up if desired.
+     *
+     * Not thread-safe in logarithmic mode.  Call only from GUI thread.
+     */
+    float getYForFrequency(float frequency, float minFreq, float maxFreq, 
+			   bool logarithmic) const;
+
+    /**
+     * Return the closest frequency to the given pixel y-coordinate,
+     * if the frequency range is as specified.
+     *
+     * Not thread-safe in logarithmic mode.  Call only from GUI thread.
+     */
+    float getFrequencyForY(int y, float minFreq, float maxFreq,
+			   bool logarithmic) const;
+
+    /**
+     * Return the zoom level, i.e. the number of frames per pixel
+     */
+    int getZoomLevel() const;
+
+    /**
+     * Set the zoom level, i.e. the number of frames per pixel.  The
+     * centre frame will be unchanged; the start and end frames will
+     * change.
+     */
+    virtual void setZoomLevel(size_t z);
+
+    /**
+     * Zoom in or out.
+     */
+    virtual void zoom(bool in);
+
+    /**
+     * Scroll left or right by a smallish or largish amount.
+     */
+    virtual void scroll(bool right, bool lots);
+
+    virtual void addLayer(Layer *v);
+    virtual void removeLayer(Layer *v); // does not delete the layer
+    virtual int getLayerCount() const { return m_layers.size(); }
+
+    /**
+     * Return a layer, counted in stacking order.  That is, layer 0 is
+     * the bottom layer and layer "getLayerCount()-1" is the top one.
+     */
+    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 const Layer *getSelectedLayer() const;
+
+    virtual void setViewManager(ViewManager *m);
+    virtual ViewManager *getViewManager() const { return m_manager; }
+
+    virtual void setFollowGlobalPan(bool f);
+    virtual bool getFollowGlobalPan() const { return m_followPan; }
+
+    virtual void setFollowGlobalZoom(bool f);
+    virtual bool getFollowGlobalZoom() const { return m_followZoom; }
+
+    virtual void setLightBackground(bool lb) { m_lightBackground = lb; }
+    virtual bool hasLightBackground() const { return m_lightBackground; }
+
+    enum TextStyle {
+	BoxedText,
+	OutlinedText
+    };
+
+    virtual void drawVisibleText(QPainter &p, int x, int y,
+				 QString text, TextStyle style);
+
+    virtual bool shouldIlluminateLocalFeatures(const Layer *, QPoint &) const {
+	return false;
+    }
+    virtual bool shouldIlluminateLocalSelection(QPoint &, bool &, bool &) const {
+	return false;
+    }
+
+    enum PlaybackFollowMode {
+	PlaybackScrollContinuous,
+	PlaybackScrollPage,
+	PlaybackIgnore
+    };
+    virtual void setPlaybackFollow(PlaybackFollowMode m);
+    virtual PlaybackFollowMode getPlaybackFollow() const { return m_followPlay; }
+
+    typedef PropertyContainer::PropertyName PropertyName;
+
+    // We implement the PropertyContainer API, although we don't
+    // actually subclass PropertyContainer.  We have our own
+    // PropertyContainer that we can return on request that just
+    // delegates back to us.
+    virtual PropertyContainer::PropertyList getProperties() const;
+    virtual QString getPropertyLabel(const PropertyName &) const;
+    virtual PropertyContainer::PropertyType getPropertyType(const PropertyName &) const;
+    virtual int getPropertyRangeAndValue(const PropertyName &,
+					 int *min, int *max) const;
+    virtual QString getPropertyValueLabel(const PropertyName &,
+					  int value) const;
+    virtual void setProperty(const PropertyName &, int value);
+    virtual QString getPropertyContainerName() const {
+	return objectName();
+    }
+    virtual QString getPropertyContainerIconName() const = 0;
+
+    virtual size_t getPropertyContainerCount() const;
+
+    virtual const PropertyContainer *getPropertyContainer(size_t i) const;
+    virtual PropertyContainer *getPropertyContainer(size_t i);
+
+    virtual int getTextLabelHeight(const Layer *layer, QPainter &) const;
+
+    virtual bool getValueExtents(QString unit, float &min, float &max,
+                                 bool &log) const;
+
+    virtual QString toXmlString(QString indent = "",
+				QString extraAttributes = "") const;
+
+    size_t getModelsStartFrame() const;
+    size_t getModelsEndFrame() const;
+
+signals:
+    void propertyContainerAdded(PropertyContainer *pc);
+    void propertyContainerRemoved(PropertyContainer *pc);
+    void propertyContainerPropertyChanged(PropertyContainer *pc);
+    void propertyContainerNameChanged(PropertyContainer *pc);
+    void propertyChanged(PropertyContainer::PropertyName);
+
+    void centreFrameChanged(void *, unsigned long, bool);
+    void zoomLevelChanged(void *, unsigned long, bool);
+
+public slots:
+    virtual void modelChanged();
+    virtual void modelChanged(size_t startFrame, size_t endFrame);
+    virtual void modelCompletionChanged();
+    virtual void modelReplaced();
+    virtual void layerParametersChanged();
+    virtual void layerNameChanged();
+
+    virtual void viewManagerCentreFrameChanged(void *, unsigned long, bool);
+    virtual void viewManagerPlaybackFrameChanged(unsigned long);
+    virtual void viewManagerZoomLevelChanged(void *, unsigned long, bool);
+
+    virtual void propertyContainerSelected(View *, PropertyContainer *pc);
+
+    virtual void selectionChanged();
+    virtual void toolModeChanged();
+
+protected:
+    View(QWidget *, bool showProgress);
+    virtual void paintEvent(QPaintEvent *e);
+    virtual void drawSelections(QPainter &);
+    virtual bool shouldLabelSelections() const { return true; }
+
+    typedef std::vector<Layer *> LayerList;
+
+    int getModelsSampleRate() const;
+    bool areLayersScrollable() const;
+    LayerList getScrollableBackLayers(bool testChanged, bool &changed) const;
+    LayerList getNonScrollableFrontLayers(bool testChanged, bool &changed) const;
+    size_t getZoomConstraintBlockSize(size_t blockSize,
+				      ZoomConstraint::RoundingDirection dir =
+				      ZoomConstraint::RoundNearest) const;
+
+    bool setCentreFrame(size_t f, bool doEmit);
+
+    void checkProgress(void *object);
+
+    size_t              m_centreFrame;
+    int                 m_zoomLevel;
+    bool                m_followPan;
+    bool                m_followZoom;
+    PlaybackFollowMode  m_followPlay;
+    size_t              m_playPointerFrame;
+    bool                m_lightBackground;
+    bool                m_showProgress;
+
+    QPixmap            *m_cache;
+    size_t              m_cacheCentreFrame;
+    int                 m_cacheZoomLevel;
+    bool                m_selectionCached;
+
+    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;
+    mutable LayerList m_lastNonScrollableBackLayers;
+
+    class LayerProgressBar : public QProgressBar {
+    public:
+	LayerProgressBar(QWidget *parent);
+	virtual QString text() const { return m_text; }
+	virtual void setText(QString text) { m_text = text; }
+    protected:
+	QString m_text;
+    };
+
+    typedef std::map<Layer *, LayerProgressBar *> ProgressMap;
+    ProgressMap m_progressBars; // I own the ProgressBars
+
+    ViewManager *m_manager; // I don't own this
+    ViewPropertyContainer *m_propertyContainer; // I own this
+};
+
+
+// Use this for delegation, because we can't subclass from
+// PropertyContainer (which is a QObject) ourselves because of
+// ambiguity with QFrame parent
+
+class ViewPropertyContainer : public PropertyContainer
+{
+    Q_OBJECT
+
+public:
+    ViewPropertyContainer(View *v);
+    PropertyList getProperties() const { return m_v->getProperties(); }
+    QString getPropertyLabel(const PropertyName &n) const {
+        return m_v->getPropertyLabel(n);
+    }
+    PropertyType getPropertyType(const PropertyName &n) const {
+	return m_v->getPropertyType(n);
+    }
+    int getPropertyRangeAndValue(const PropertyName &n, int *min, int *max) const {
+	return m_v->getPropertyRangeAndValue(n, min, max);
+    }
+    QString getPropertyValueLabel(const PropertyName &n, int value) const {
+	return m_v->getPropertyValueLabel(n, value);
+    }
+    QString getPropertyContainerName() const {
+	return m_v->getPropertyContainerName();
+    }
+    QString getPropertyContainerIconName() const {
+	return m_v->getPropertyContainerIconName();
+    }
+
+public slots:
+    virtual void setProperty(const PropertyName &n, int value) {
+	m_v->setProperty(n, value);
+    }
+
+protected:
+    View *m_v;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/view/ViewManager.cpp	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,361 @@
+/* -*- 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 file copyright 2006 Chris Cannam.
+    
+    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.
+*/
+
+#include "ViewManager.h"
+#include "AudioPlaySource.h"
+#include "Model.h"
+#include "CommandHistory.h"
+
+#include <iostream>
+
+// #define DEBUG_VIEW_MANAGER 1
+
+ViewManager::ViewManager() :
+    m_playSource(0),
+    m_globalCentreFrame(0),
+    m_globalZoom(1024),
+    m_playbackFrame(0),
+    m_mainModelSampleRate(0),
+    m_lastLeft(0), 
+    m_lastRight(0),
+    m_inProgressExclusive(true),
+    m_toolMode(NavigateMode),
+    m_playLoopMode(false),
+    m_playSelectionMode(false),
+    m_overlayMode(BasicOverlays)
+{
+    connect(this, 
+	    SIGNAL(centreFrameChanged(void *, unsigned long, bool)),
+	    SLOT(considerSeek(void *, unsigned long, bool)));
+
+    connect(this, 
+	    SIGNAL(zoomLevelChanged(void *, unsigned long, bool)),
+	    SLOT(considerZoomChange(void *, unsigned long, bool)));
+}
+
+ViewManager::~ViewManager()
+{
+}
+
+unsigned long
+ViewManager::getGlobalCentreFrame() const
+{
+#ifdef DEBUG_VIEW_MANAGER
+    std::cout << "ViewManager::getGlobalCentreFrame: returning " << m_globalCentreFrame << std::endl;
+#endif
+    return m_globalCentreFrame;
+}
+
+unsigned long
+ViewManager::getGlobalZoom() const
+{
+#ifdef DEBUG_VIEW_MANAGER
+    std::cout << "ViewManager::getGlobalZoom: returning " << m_globalZoom << std::endl;
+#endif
+    return m_globalZoom;
+}
+
+unsigned long
+ViewManager::getPlaybackFrame() const
+{
+    if (m_playSource && m_playSource->isPlaying()) {
+	m_playbackFrame = m_playSource->getCurrentPlayingFrame();
+    }
+    return m_playbackFrame;
+}
+
+void
+ViewManager::setPlaybackFrame(unsigned long f)
+{
+    if (m_playbackFrame != f) {
+	m_playbackFrame = f;
+	emit playbackFrameChanged(f);
+	if (m_playSource && m_playSource->isPlaying()) {
+	    m_playSource->play(f);
+	}
+    }
+}
+
+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 inProgressSelectionChanged();
+}
+
+void
+ViewManager::clearInProgressSelection()
+{
+    m_inProgressSelection = Selection();
+    emit inProgressSelectionChanged();
+}
+
+const MultiSelection &
+ViewManager::getSelection() const
+{
+    return m_selections;
+}
+
+const MultiSelection::SelectionList &
+ViewManager::getSelections() const
+{
+    return m_selections.getSelections();
+}
+
+void
+ViewManager::setSelection(const Selection &selection)
+{
+    MultiSelection ms(m_selections);
+    ms.setSelection(selection);
+    setSelections(ms);
+}
+
+void
+ViewManager::addSelection(const Selection &selection)
+{
+    MultiSelection ms(m_selections);
+    ms.addSelection(selection);
+    setSelections(ms);
+}
+
+void
+ViewManager::removeSelection(const Selection &selection)
+{
+    MultiSelection ms(m_selections);
+    ms.removeSelection(selection);
+    setSelections(ms);
+}
+
+void
+ViewManager::clearSelections()
+{
+    MultiSelection ms(m_selections);
+    ms.clearSelections();
+    setSelections(ms);
+}
+
+void
+ViewManager::setSelections(const MultiSelection &ms)
+{
+    if (m_selections.getSelections() == ms.getSelections()) return;
+    SetSelectionCommand *command = new SetSelectionCommand(this, ms);
+    CommandHistory::getInstance()->addCommand(command);
+}
+
+void
+ViewManager::signalSelectionChange()
+{
+    emit selectionChanged();
+}
+
+ViewManager::SetSelectionCommand::SetSelectionCommand(ViewManager *vm,
+						      const MultiSelection &ms) :
+    m_vm(vm),
+    m_oldSelection(vm->m_selections),
+    m_newSelection(ms)
+{
+}
+
+ViewManager::SetSelectionCommand::~SetSelectionCommand() { }
+
+void
+ViewManager::SetSelectionCommand::execute()
+{
+    m_vm->m_selections = m_newSelection;
+    m_vm->signalSelectionChange();
+}
+
+void
+ViewManager::SetSelectionCommand::unexecute()
+{
+    m_vm->m_selections = m_oldSelection;
+    m_vm->signalSelectionChange();
+}
+
+QString
+ViewManager::SetSelectionCommand::getName() const
+{
+    if (m_newSelection.getSelections().empty()) return tr("Clear Selection");
+    else return tr("Select");
+}
+
+Selection
+ViewManager::getContainingSelection(size_t frame, bool defaultToFollowing) const
+{
+    return m_selections.getContainingSelection(frame, defaultToFollowing);
+}
+
+void
+ViewManager::setToolMode(ToolMode mode)
+{
+    m_toolMode = mode;
+
+    emit toolModeChanged();
+}
+
+void
+ViewManager::setPlayLoopMode(bool mode)
+{
+    m_playLoopMode = mode;
+
+    emit playLoopModeChanged();
+}
+
+void
+ViewManager::setPlaySelectionMode(bool mode)
+{
+    m_playSelectionMode = mode;
+
+    emit playSelectionModeChanged();
+}
+
+size_t
+ViewManager::getPlaybackSampleRate() const
+{
+    if (m_playSource) {
+	return m_playSource->getTargetSampleRate();
+    }
+    return 0;
+}
+
+void
+ViewManager::setAudioPlaySource(AudioPlaySource *source)
+{
+    if (!m_playSource) {
+	QTimer::singleShot(100, this, SLOT(checkPlayStatus()));
+    }
+    m_playSource = source;
+}
+
+void
+ViewManager::playStatusChanged(bool playing)
+{
+    checkPlayStatus();
+}
+
+void
+ViewManager::checkPlayStatus()
+{
+    if (m_playSource && m_playSource->isPlaying()) {
+
+	float left = 0, right = 0;
+	if (m_playSource->getOutputLevels(left, right)) {
+	    if (left != m_lastLeft || right != m_lastRight) {
+		emit outputLevelsChanged(left, right);
+		m_lastLeft = left;
+		m_lastRight = right;
+	    }
+	}
+
+	m_playbackFrame = m_playSource->getCurrentPlayingFrame();
+
+#ifdef DEBUG_VIEW_MANAGER
+	std::cout << "ViewManager::checkPlayStatus: Playing, frame " << m_playbackFrame << ", levels " << m_lastLeft << "," << m_lastRight << std::endl;
+#endif
+
+	emit playbackFrameChanged(m_playbackFrame);
+
+	QTimer::singleShot(20, this, SLOT(checkPlayStatus()));
+
+    } else {
+
+	QTimer::singleShot(100, this, SLOT(checkPlayStatus()));
+	
+	if (m_lastLeft != 0.0 || m_lastRight != 0.0) {
+	    emit outputLevelsChanged(0.0, 0.0);
+	    m_lastLeft = 0.0;
+	    m_lastRight = 0.0;
+	}
+
+#ifdef DEBUG_VIEW_MANAGER
+//	std::cout << "ViewManager::checkPlayStatus: Not playing" << std::endl;
+#endif
+    }
+}
+
+bool
+ViewManager::isPlaying() const
+{
+    return m_playSource && m_playSource->isPlaying();
+}
+
+void
+ViewManager::considerSeek(void *p, unsigned long f, bool locked)
+{
+    if (locked) {
+	m_globalCentreFrame = f;
+    }
+
+#ifdef DEBUG_VIEW_MANAGER 
+    std::cout << "ViewManager::considerSeek(" << p << ", " << f << ", " << locked << ")" << std::endl;
+#endif
+
+    if (p == this || !locked) return;
+
+    if (m_playSource && m_playSource->isPlaying()) {
+	unsigned long playFrame = m_playSource->getCurrentPlayingFrame();
+	unsigned long diff = std::max(f, playFrame) - std::min(f, playFrame);
+	if (diff > 20000) {
+	    m_playbackFrame = f;
+	    m_playSource->play(f);
+#ifdef DEBUG_VIEW_MANAGER 
+	    std::cout << "ViewManager::considerSeek: reseeking from " << playFrame << " to " << f << std::endl;
+#endif
+	}
+    } else {
+	m_playbackFrame = f; //!!! only if that view is in scroll mode?
+    }
+}
+
+void
+ViewManager::considerZoomChange(void *p, unsigned long z, bool locked)
+{
+    if (locked) {
+	m_globalZoom = z;
+    }
+
+#ifdef DEBUG_VIEW_MANAGER 
+    std::cout << "ViewManager::considerZoomChange(" << p << ", " << z << ", " << locked << ")" << std::endl;
+#endif
+}
+
+void
+ViewManager::setOverlayMode(OverlayMode mode)
+{
+    if (m_overlayMode != mode) {
+        m_overlayMode = mode;
+        emit overlayModeChanged();
+    }
+}
+
+#ifdef INCLUDE_MOCFILES
+#include "ViewManager.moc.cpp"
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/view/ViewManager.h	Mon Jul 31 11:44:37 2006 +0000
@@ -0,0 +1,188 @@
+/* -*- 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 file copyright 2006 Chris Cannam.
+    
+    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.
+*/
+
+#ifndef _VIEW_MANAGER_H_
+#define _VIEW_MANAGER_H_
+
+#include <QObject>
+#include <QTimer>
+
+#include <map>
+
+#include "Selection.h"
+#include "Command.h"
+#include "Clipboard.h"
+
+class AudioPlaySource;
+class Model;
+
+/**
+ * The ViewManager manages properties that may need to be synchronised
+ * between separate Views.  For example, it handles signals associated
+ * with changes to the global pan and zoom, and it handles selections.
+ *
+ * Views should be implemented in such a way as to work
+ * correctly whether they are supplied with a ViewManager or not.
+ */
+
+class ViewManager : public QObject
+{
+    Q_OBJECT
+
+public:
+    ViewManager();
+    virtual ~ViewManager();
+
+    void setAudioPlaySource(AudioPlaySource *source);
+
+    bool isPlaying() const;
+
+    unsigned long getGlobalCentreFrame() const;
+    unsigned long getGlobalZoom() const;
+
+    unsigned long getPlaybackFrame() const;
+    void setPlaybackFrame(unsigned long frame);
+
+    bool haveInProgressSelection() const;
+    const Selection &getInProgressSelection(bool &exclusive) const;
+    void setInProgressSelection(const Selection &selection, bool exclusive);
+    void clearInProgressSelection();
+
+    const MultiSelection &getSelection() const;
+
+    const MultiSelection::SelectionList &getSelections() const;
+    void setSelection(const Selection &selection);
+    void addSelection(const Selection &selection);
+    void removeSelection(const Selection &selection);
+    void clearSelections();
+
+    /**
+     * Return the selection that contains a given frame.
+     * If defaultToFollowing is true, and if the frame is not in a
+     * selected area, return the next selection after the given frame.
+     * Return the empty selection if no appropriate selection is found.
+     */
+    Selection getContainingSelection(size_t frame, bool defaultToFollowing) const;
+
+    Clipboard &getClipboard() { return m_clipboard; }
+
+    enum ToolMode {
+	NavigateMode,
+	SelectMode,
+        EditMode,
+	DrawMode
+    };
+    ToolMode getToolMode() const { return m_toolMode; }
+    void setToolMode(ToolMode mode);
+
+    bool getPlayLoopMode() const { return m_playLoopMode; }
+    void setPlayLoopMode(bool on);
+
+    bool getPlaySelectionMode() const { return m_playSelectionMode; }
+    void setPlaySelectionMode(bool on);
+
+    size_t getPlaybackSampleRate() const;
+    size_t getMainModelSampleRate() const { return m_mainModelSampleRate; }
+    void setMainModelSampleRate(size_t sr) { m_mainModelSampleRate = sr; }
+
+    enum OverlayMode {
+        NoOverlays,
+        BasicOverlays,
+        AllOverlays
+    };
+    void setOverlayMode(OverlayMode mode);
+    OverlayMode getOverlayMode() const { return m_overlayMode; }
+
+signals:
+    /** Emitted when a widget pans.  The originator identifies the widget. */
+    void centreFrameChanged(void *originator, unsigned long frame, bool locked);
+
+    /** Emitted when a widget zooms.  The originator identifies the widget. */
+    void zoomLevelChanged(void *originator, unsigned long zoom, bool locked);
+
+    /** Emitted when the playback frame changes. */
+    void playbackFrameChanged(unsigned long frame);
+
+    /** 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 in-progress (rubberbanding) selection has changed. */
+    void inProgressSelectionChanged();
+
+    /** Emitted when the tool mode has been changed. */
+    void toolModeChanged();
+
+    /** Emitted when the play loop mode has been changed. */
+    void playLoopModeChanged();
+
+    /** Emitted when the play selection mode has been changed. */
+    void playSelectionModeChanged();
+
+    /** Emitted when the overlay mode has been changed. */
+    void overlayModeChanged();
+
+protected slots:
+    void checkPlayStatus();
+    void playStatusChanged(bool playing);
+    void considerSeek(void *, unsigned long, bool);
+    void considerZoomChange(void *, unsigned long, bool);
+
+protected:
+    AudioPlaySource *m_playSource;
+    unsigned long m_globalCentreFrame;
+    unsigned long m_globalZoom;
+    mutable unsigned long m_playbackFrame;
+    size_t m_mainModelSampleRate;
+
+    float m_lastLeft;
+    float m_lastRight;
+
+    MultiSelection m_selections;
+    Selection m_inProgressSelection;
+    bool m_inProgressExclusive;
+
+    Clipboard m_clipboard;
+
+    ToolMode m_toolMode;
+
+    bool m_playLoopMode;
+    bool m_playSelectionMode;
+
+    void setSelections(const MultiSelection &ms);
+    void signalSelectionChange();
+
+    class SetSelectionCommand : public Command
+    {
+    public:
+	SetSelectionCommand(ViewManager *vm, const MultiSelection &ms);
+	virtual ~SetSelectionCommand();
+	virtual void execute();
+	virtual void unexecute();
+	virtual QString getName() const;
+
+    protected:
+	ViewManager *m_vm;
+	MultiSelection m_oldSelection;
+	MultiSelection m_newSelection;
+    };
+
+    OverlayMode m_overlayMode;
+};
+
+#endif
+
--- a/widgets/Pane.cpp	Thu Jul 27 16:08:04 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1095 +0,0 @@
-/* -*- 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 file copyright 2006 Chris Cannam.
-    
-    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.
-*/
-
-#include "widgets/Pane.h"
-#include "base/Layer.h"
-#include "base/Model.h"
-#include "base/ZoomConstraint.h"
-#include "base/RealTime.h"
-#include "base/Profiler.h"
-#include "base/ViewManager.h"
-#include "base/CommandHistory.h"
-#include "layer/WaveformLayer.h"
-
-#include <QPaintEvent>
-#include <QPainter>
-#include <iostream>
-#include <cmath>
-
-using std::cerr;
-using std::endl;
-
-Pane::Pane(QWidget *w) :
-    View(w, true),
-    m_identifyFeatures(false),
-    m_clickedInRange(false),
-    m_shiftPressed(false),
-    m_ctrlPressed(false),
-    m_navigating(false),
-    m_resizing(false),
-    m_centreLineVisible(true)
-{
-    setObjectName("Pane");
-    setMouseTracking(true);
-}
-
-bool
-Pane::shouldIlluminateLocalFeatures(const Layer *layer, QPoint &pos) const
-{
-    QPoint discard;
-    bool b0, b1;
-
-    if (layer == getSelectedLayer() &&
-	!shouldIlluminateLocalSelection(discard, b0, b1)) {
-
-	pos = m_identifyPoint;
-	return m_identifyFeatures;
-    }
-
-    return false;
-}
-
-bool
-Pane::shouldIlluminateLocalSelection(QPoint &pos,
-				     bool &closeToLeft,
-				     bool &closeToRight) const
-{
-    if (m_identifyFeatures &&
-	m_manager &&
-	m_manager->getToolMode() == ViewManager::EditMode &&
-	!m_manager->getSelections().empty() &&
-	!selectionIsBeingEdited()) {
-
-	Selection s(getSelectionAt(m_identifyPoint.x(),
-				   closeToLeft, closeToRight));
-
-	if (!s.isEmpty()) {
-	    if (getSelectedLayer() && getSelectedLayer()->isLayerEditable()) {
-		
-		pos = m_identifyPoint;
-		return true;
-	    }
-	}
-    }
-
-    return false;
-}
-
-bool
-Pane::selectionIsBeingEdited() const
-{
-    if (!m_editingSelection.isEmpty()) {
-	if (m_mousePos != m_clickPos &&
-	    getFrameForX(m_mousePos.x()) != getFrameForX(m_clickPos.x())) {
-	    return true;
-	}
-    }
-    return false;
-}
-
-void
-Pane::setCentreLineVisible(bool visible)
-{
-    m_centreLineVisible = visible;
-    update();
-}
-
-void
-Pane::paintEvent(QPaintEvent *e)
-{
-//    Profiler profiler("Pane::paintEvent", true);
-
-    QPainter paint;
-
-    QRect r(rect());
-
-    if (e) {
-	r = e->rect();
-    }
-/*
-    paint.begin(this);
-    paint.setClipRect(r);
-
-    if (hasLightBackground()) {
-	paint.setPen(Qt::white);
-	paint.setBrush(Qt::white);
-    } else {
-	paint.setPen(Qt::black);
-	paint.setBrush(Qt::black);
-    }
-    paint.drawRect(r);
-
-    paint.end();
-*/
-    View::paintEvent(e);
-
-    paint.begin(this);
-
-    if (e) {
-	paint.setClipRect(r);
-    }
-
-    const Model *waveformModel = 0; // just for reporting purposes
-    int verticalScaleWidth = 0;
-    
-    int fontHeight = paint.fontMetrics().height();
-    int fontAscent = paint.fontMetrics().ascent();
-
-    if (m_manager &&
-        !m_manager->isPlaying() &&
-        m_manager->getToolMode() == ViewManager::SelectMode) {
-
-        for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) {
-            --vi;
-
-            std::vector<QRect> crosshairExtents;
-
-            if ((*vi)->getCrosshairExtents(this, paint, m_identifyPoint,
-                                           crosshairExtents)) {
-                (*vi)->paintCrosshairs(this, paint, m_identifyPoint);
-                break;
-            } else if ((*vi)->isLayerOpaque()) {
-                break;
-            }
-        }
-    }
-
-    for (LayerList::iterator vi = m_layers.end(); vi != m_layers.begin(); ) {
-        --vi;
-            
-        if (dynamic_cast<WaveformLayer *>(*vi)) {
-            waveformModel = (*vi)->getModel();
-        }
-
-        if (!m_manager ||
-            m_manager->getOverlayMode() == ViewManager::NoOverlays) {
-            break;
-        }
-
-        verticalScaleWidth = (*vi)->getVerticalScaleWidth(this, paint);
-
-        if (verticalScaleWidth > 0 && r.left() < verticalScaleWidth) {
-
-//	    Profiler profiler("Pane::paintEvent - painting vertical scale", true);
-
-//	    std::cerr << "Pane::paintEvent: calling paint.save() in vertical scale block" << std::endl;
-            paint.save();
-            
-            paint.setPen(Qt::black);
-            paint.setBrush(Qt::white);
-            paint.drawRect(0, -1, verticalScaleWidth, height()+1);
-            
-            paint.setBrush(Qt::NoBrush);
-            (*vi)->paintVerticalScale
-                (this, paint, QRect(0, 0, verticalScaleWidth, height()));
-            
-            paint.restore();
-        }
-	
-        if (m_identifyFeatures) {
-            
-            QPoint pos = m_identifyPoint;
-            QString desc = (*vi)->getFeatureDescription(this, pos);
-	    
-            if (desc != "") {
-                
-                paint.save();
-                
-                int tabStop =
-                    paint.fontMetrics().width(tr("Some lengthy prefix:"));
-                
-                QRect boundingRect = 
-                    paint.fontMetrics().boundingRect
-                    (rect(),
-                     Qt::AlignRight | Qt::AlignTop | Qt::TextExpandTabs,
-                     desc, tabStop);
-
-                if (hasLightBackground()) {
-                    paint.setPen(Qt::NoPen);
-                    paint.setBrush(QColor(250, 250, 250, 200));
-                } else {
-                    paint.setPen(Qt::NoPen);
-                    paint.setBrush(QColor(50, 50, 50, 200));
-                }
-
-                int extra = paint.fontMetrics().descent();
-                paint.drawRect(width() - boundingRect.width() - 10 - extra,
-                               10 - extra,
-                               boundingRect.width() + 2 * extra,
-                               boundingRect.height() + extra);
-
-                if (hasLightBackground()) {
-                    paint.setPen(QColor(150, 20, 0));
-                } else {
-                    paint.setPen(QColor(255, 150, 100));
-                }
-		
-                QTextOption option;
-                option.setWrapMode(QTextOption::NoWrap);
-                option.setAlignment(Qt::AlignRight | Qt::AlignTop);
-                option.setTabStop(tabStop);
-                paint.drawText(QRectF(width() - boundingRect.width() - 10, 10,
-                                      boundingRect.width(),
-                                      boundingRect.height()),
-                               desc,
-                               option);
-
-                paint.restore();
-            }
-        }
-
-        break;
-    }
-    
-    int sampleRate = getModelsSampleRate();
-    paint.setBrush(Qt::NoBrush);
-
-    if (m_centreLineVisible) {
-
-	if (hasLightBackground()) {
-	    paint.setPen(QColor(50, 50, 50));
-	} else {
-	    paint.setPen(QColor(200, 200, 200));
-	}	
-	paint.drawLine(width() / 2, 0, width() / 2, height() - 1);
-
-	paint.setPen(QColor(50, 50, 50));
-
-	int y = height() - fontHeight
-	    + fontAscent - 6;
-	
-	LayerList::iterator vi = m_layers.end();
-	
-	if (vi != m_layers.begin()) {
-	    
-	    switch ((*--vi)->getPreferredFrameCountPosition()) {
-		
-	    case Layer::PositionTop:
-		y = fontAscent + 6;
-		break;
-		
-	    case Layer::PositionMiddle:
-		y = (height() - fontHeight) / 2
-		    + fontAscent;
-		break;
-
-	    case Layer::PositionBottom:
-		// y already set correctly
-		break;
-	    }
-	}
-
-        if (m_manager &&
-            m_manager->getOverlayMode() != ViewManager::NoOverlays) {
-
-            if (sampleRate) {
-
-                QString text(QString::fromStdString
-                             (RealTime::frame2RealTime
-                              (m_centreFrame, sampleRate).toText(true)));
-                
-                int tw = paint.fontMetrics().width(text);
-                int x = width()/2 - 4 - tw;
-                
-                drawVisibleText(paint, x, y, text, OutlinedText);
-            }
-            
-            QString text = QString("%1").arg(m_centreFrame);
-            
-            int tw = paint.fontMetrics().width(text);
-            int x = width()/2 + 4;
-            
-            drawVisibleText(paint, x, y, text, OutlinedText);
-        }
-
-    } else {
-
-	paint.setPen(QColor(50, 50, 50));
-    }
-
-    if (waveformModel &&
-        m_manager &&
-        m_manager->getOverlayMode() != ViewManager::NoOverlays &&
-	r.y() + r.height() >= height() - fontHeight - 6) {
-
-	size_t mainModelRate = m_manager->getMainModelSampleRate();
-	size_t playbackRate = m_manager->getPlaybackSampleRate();
-	    
-	QString srNote = "";
-
-	// Show (R) for waveform models that will be resampled on
-	// playback, and (X) for waveform models that will be played
-	// at the wrong rate because their rate differs from that of
-	// the main model.
-
-	if (sampleRate == mainModelRate) {
-	    if (sampleRate != playbackRate) srNote = " " + tr("(R)");
-	} else {
-	    std::cerr << "Sample rate = " << sampleRate << ", main model rate = " << mainModelRate << std::endl;
-	    srNote = " " + tr("(X)");
-	}
-
-	QString desc = tr("%1 / %2Hz%3")
-	    .arg(RealTime::frame2RealTime(waveformModel->getEndFrame(),
-					  sampleRate)
-		 .toText(false).c_str())
-	    .arg(sampleRate)
-	    .arg(srNote);
-
-	if (r.x() < verticalScaleWidth + 5 + paint.fontMetrics().width(desc)) {
-	    drawVisibleText(paint, verticalScaleWidth + 5,
-			    height() - fontHeight + fontAscent - 6,
-			    desc, OutlinedText);
-	}
-    }
-
-    if (m_manager &&
-        m_manager->getOverlayMode() == ViewManager::AllOverlays &&
-        r.y() + r.height() >= height() - m_layers.size() * fontHeight - 6) {
-
-	std::vector<QString> texts;
-	int maxTextWidth = 0;
-
-	for (LayerList::iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
-
-	    QString text = (*i)->getLayerPresentationName();
-	    int tw = paint.fontMetrics().width(text);
-            bool reduced = false;
-            while (tw > width() / 3 && text.length() > 4) {
-                if (!reduced && text.length() > 8) {
-                    text = text.left(text.length() - 4);
-                } else {
-                    text = text.left(text.length() - 2);
-                }
-                reduced = true;
-                tw = paint.fontMetrics().width(text + "...");
-            }
-            if (reduced) {
-                texts.push_back(text + "...");
-            } else {
-                texts.push_back(text);
-            }
-	    if (tw > maxTextWidth) maxTextWidth = tw;
-	}
-    
-	int lly = height() - 6;
-
-	if (r.x() + r.width() >= width() - maxTextWidth - 5) {
-	    
-	    for (int i = 0; i < texts.size(); ++i) {
-
-		if (i == texts.size() - 1) {
-		    paint.setPen(Qt::black);
-		}
-		
-		drawVisibleText(paint, width() - maxTextWidth - 5,
-				lly - fontHeight + fontAscent,
-				texts[i], OutlinedText);
-		
-		lly -= fontHeight;
-	    }
-	}
-    }
-
-    if (m_clickedInRange && m_shiftPressed) {
-	if (m_manager && (m_manager->getToolMode() == ViewManager::NavigateMode)) {
-	    //!!! be nice if this looked a bit more in keeping with the
-	    //selection block
-	    paint.setPen(Qt::blue);
-	    paint.drawRect(m_clickPos.x(), m_clickPos.y(),
-			   m_mousePos.x() - m_clickPos.x(),
-			   m_mousePos.y() - m_clickPos.y());
-	}
-    }
-    
-    if (selectionIsBeingEdited()) {
-
-	int offset = m_mousePos.x() - m_clickPos.x();
-	int p0 = getXForFrame(m_editingSelection.getStartFrame()) + offset;
-	int p1 = getXForFrame(m_editingSelection.getEndFrame()) + offset;
-
-	if (m_editingSelectionEdge < 0) {
-	    p1 = getXForFrame(m_editingSelection.getEndFrame());
-	} else if (m_editingSelectionEdge > 0) {
-	    p0 = getXForFrame(m_editingSelection.getStartFrame());
-	}
-
-	paint.save();
-	if (hasLightBackground()) {
-	    paint.setPen(QPen(Qt::black, 2));
-	} else {
-	    paint.setPen(QPen(Qt::white, 2));
-	}
-
-	//!!! duplicating display policy with View::drawSelections
-
-	if (m_editingSelectionEdge < 0) {
-	    paint.drawLine(p0, 1, p1, 1);
-	    paint.drawLine(p0, 0, p0, height());
-	    paint.drawLine(p0, height() - 1, p1, height() - 1);
-	} else if (m_editingSelectionEdge > 0) {
-	    paint.drawLine(p0, 1, p1, 1);
-	    paint.drawLine(p1, 0, p1, height());
-	    paint.drawLine(p0, height() - 1, p1, height() - 1);
-	} else {
-	    paint.setBrush(Qt::NoBrush);
-	    paint.drawRect(p0, 1, p1 - p0, height() - 2);
-	}
-	paint.restore();
-    }
-
-    paint.end();
-}
-
-Selection
-Pane::getSelectionAt(int x, bool &closeToLeftEdge, bool &closeToRightEdge) const
-{
-    closeToLeftEdge = closeToRightEdge = false;
-
-    if (!m_manager) return Selection();
-
-    long testFrame = getFrameForX(x - 5);
-    if (testFrame < 0) {
-	testFrame = getFrameForX(x);
-	if (testFrame < 0) return Selection();
-    }
-
-    Selection selection = m_manager->getContainingSelection(testFrame, true);
-    if (selection.isEmpty()) return selection;
-
-    int lx = getXForFrame(selection.getStartFrame());
-    int rx = getXForFrame(selection.getEndFrame());
-    
-    int fuzz = 2;
-    if (x < lx - fuzz || x > rx + fuzz) return Selection();
-
-    int width = rx - lx;
-    fuzz = 3;
-    if (width < 12) fuzz = width / 4;
-    if (fuzz < 1) fuzz = 1;
-
-    if (x < lx + fuzz) closeToLeftEdge = true;
-    if (x > rx - fuzz) closeToRightEdge = true;
-
-    return selection;
-}
-
-void
-Pane::mousePressEvent(QMouseEvent *e)
-{
-    if (e->buttons() & Qt::RightButton) {
-        emit rightButtonMenuRequested(mapToGlobal(e->pos()));
-        return;
-    }
-
-    m_clickPos = e->pos();
-    m_clickedInRange = true;
-    m_editingSelection = Selection();
-    m_editingSelectionEdge = 0;
-    m_shiftPressed = (e->modifiers() & Qt::ShiftModifier);
-    m_ctrlPressed = (e->modifiers() & Qt::ControlModifier);
-
-    ViewManager::ToolMode mode = ViewManager::NavigateMode;
-    if (m_manager) mode = m_manager->getToolMode();
-
-    m_navigating = false;
-
-    if (mode == ViewManager::NavigateMode || (e->buttons() & Qt::MidButton)) {
-
-	if (mode != ViewManager::NavigateMode) {
-	    setCursor(Qt::PointingHandCursor);
-	}
-
-	m_navigating = true;
-	m_dragCentreFrame = m_centreFrame;
-
-    } else if (mode == ViewManager::SelectMode) {
-
-	bool closeToLeft = false, closeToRight = false;
-	Selection selection = getSelectionAt(e->x(), closeToLeft, closeToRight);
-
-	if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) {
-
-	    m_manager->removeSelection(selection);
-
-	    if (closeToLeft) {
-		m_selectionStartFrame = selection.getEndFrame();
-	    } else {
-		m_selectionStartFrame = selection.getStartFrame();
-	    }
-
-	    m_manager->setInProgressSelection(selection, false);
-	    m_resizing = true;
-	
-	} else {
-
-	    int mouseFrame = getFrameForX(e->x());
-	    size_t resolution = 1;
-	    int snapFrame = mouseFrame;
-	
-	    Layer *layer = getSelectedLayer();
-	    if (layer && !m_shiftPressed) {
-		layer->snapToFeatureFrame(this, snapFrame,
-					  resolution, Layer::SnapLeft);
-	    }
-	    
-	    if (snapFrame < 0) snapFrame = 0;
-	    m_selectionStartFrame = snapFrame;
-	    if (m_manager) {
-		m_manager->setInProgressSelection(Selection(snapFrame,
-							    snapFrame + resolution),
-						  !m_ctrlPressed);
-	    }
-
-	    m_resizing = false;
-	}
-
-	update();
-
-    } else if (mode == ViewManager::DrawMode) {
-
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-	    layer->drawStart(this, e);
-	}
-
-    } else if (mode == ViewManager::EditMode) {
-
-	if (!editSelectionStart(e)) {
-	    Layer *layer = getSelectedLayer();
-	    if (layer && layer->isLayerEditable()) {
-		layer->editStart(this, e);
-	    }
-	}
-    }
-
-    emit paneInteractedWith();
-}
-
-void
-Pane::mouseReleaseEvent(QMouseEvent *e)
-{
-    if (e->buttons() & Qt::RightButton) {
-        return;
-    }
-
-    ViewManager::ToolMode mode = ViewManager::NavigateMode;
-    if (m_manager) mode = m_manager->getToolMode();
-
-    if (m_clickedInRange) {
-	mouseMoveEvent(e);
-    }
-
-    if (m_navigating || mode == ViewManager::NavigateMode) {
-
-	m_navigating = false;
-
-	if (mode != ViewManager::NavigateMode) {
-	    // restore cursor
-	    toolModeChanged();
-	}
-
-	if (m_shiftPressed) {
-
-	    int x0 = std::min(m_clickPos.x(), m_mousePos.x());
-	    int x1 = std::max(m_clickPos.x(), m_mousePos.x());
-	    int w = x1 - x0;
-
-	    int y0 = std::min(m_clickPos.y(), m_mousePos.y());
-	    int y1 = std::max(m_clickPos.y(), m_mousePos.y());
-//	    int h = y1 - y0;
-	    
-	    long newStartFrame = getFrameForX(x0);
-	    
-	    long visibleFrames = getEndFrame() - getStartFrame();
-	    if (newStartFrame <= -visibleFrames) {
-		newStartFrame  = -visibleFrames + 1;
-	    }
-	    
-	    if (newStartFrame >= long(getModelsEndFrame())) {
-		newStartFrame  = getModelsEndFrame() - 1;
-	    }
-	    
-	    float ratio = float(w) / float(width());
-//	std::cerr << "ratio: " << ratio << std::endl;
-	    size_t newZoomLevel = (size_t)nearbyint(m_zoomLevel * ratio);
-	    if (newZoomLevel < 1) newZoomLevel = 1;
-
-//	std::cerr << "start: " << m_startFrame << ", level " << m_zoomLevel << std::endl;
-	    setZoomLevel(getZoomConstraintBlockSize(newZoomLevel));
-	    setStartFrame(newStartFrame);
-
-            //!!! lots of faff, shouldn't be here
-
-            QString unit;
-            float min, max;
-            bool log;
-            Layer *layer = 0;
-            for (LayerList::const_iterator i = m_layers.begin();
-                 i != m_layers.end(); ++i) { 
-                if ((*i)->getValueExtents(min, max, log, unit) &&
-                    (*i)->getDisplayExtents(min, max)) {
-                    layer = *i;
-                    break;
-                }
-            }
-            
-            if (layer) {
-                if (log) {
-                    min = (min < 0.0) ? -log10f(-min) : (min == 0.0) ? 0.0 : log10f(min);
-                    max = (max < 0.0) ? -log10f(-max) : (max == 0.0) ? 0.0 : log10f(max);
-                }
-                float rmin = min + ((max - min) * (height() - y1)) / height();
-                float rmax = min + ((max - min) * (height() - y0)) / height();
-                std::cerr << "min: " << min << ", max: " << max << ", y0: " << y0 << ", y1: " << y1 << ", h: " << height() << ", rmin: " << rmin << ", rmax: " << rmax << std::endl;
-                if (log) {
-                    rmin = powf(10, rmin);
-                    rmax = powf(10, rmax);
-                }
-                std::cerr << "finally: rmin: " << rmin << ", rmax: " << rmax << " " << unit.toStdString() << std::endl;
-
-                layer->setDisplayExtents(rmin, rmax);
-            }
-                
-	    //cerr << "mouseReleaseEvent: start frame now " << m_startFrame << endl;
-//	update();
-	}
-
-    } else if (mode == ViewManager::SelectMode) {
-
-	if (m_manager && m_manager->haveInProgressSelection()) {
-
-	    bool exclusive;
-	    Selection selection = m_manager->getInProgressSelection(exclusive);
-	    
-	    if (selection.getEndFrame() < selection.getStartFrame() + 2) {
-		selection = Selection();
-	    }
-	    
-	    m_manager->clearInProgressSelection();
-	    
-	    if (exclusive) {
-		m_manager->setSelection(selection);
-	    } else {
-		m_manager->addSelection(selection);
-	    }
-	}
-	
-	update();
-
-    } else if (mode == ViewManager::DrawMode) {
-
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-	    layer->drawEnd(this, e);
-	    update();
-	}
-
-    } else if (mode == ViewManager::EditMode) {
-
-	if (!editSelectionEnd(e)) {
-	    Layer *layer = getSelectedLayer();
-	    if (layer && layer->isLayerEditable()) {
-		layer->editEnd(this, e);
-		update();
-	    }
-	}
-    }
-
-    m_clickedInRange = false;
-
-    emit paneInteractedWith();
-}
-
-void
-Pane::mouseMoveEvent(QMouseEvent *e)
-{
-    if (e->buttons() & Qt::RightButton) {
-        return;
-    }
-
-    ViewManager::ToolMode mode = ViewManager::NavigateMode;
-    if (m_manager) mode = m_manager->getToolMode();
-
-    QPoint prevPoint = m_identifyPoint;
-    m_identifyPoint = e->pos();
-
-    if (!m_clickedInRange) {
-	
-	if (mode == ViewManager::SelectMode) {
-	    bool closeToLeft = false, closeToRight = false;
-	    getSelectionAt(e->x(), closeToLeft, closeToRight);
-	    if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) {
-		setCursor(Qt::SizeHorCursor);
-	    } else {
-		setCursor(Qt::ArrowCursor);
-	    }
-	}
-
-//!!!	if (mode != ViewManager::DrawMode) {
-
-        if (!m_manager->isPlaying()) {
-
-	if (getSelectedLayer()) {
-
-	    bool previouslyIdentifying = m_identifyFeatures;
-	    m_identifyFeatures = true;
-	    
-	    if (m_identifyFeatures != previouslyIdentifying ||
-		m_identifyPoint != prevPoint) {
-		update();
-	    }
-	}
-
-        }
-
-//	}
-
-	return;
-    }
-
-    if (m_navigating || mode == ViewManager::NavigateMode) {
-
-	if (m_shiftPressed) {
-
-	    m_mousePos = e->pos();
-	    update();
-
-	} else {
-
-	    long frameOff = getFrameForX(e->x()) - getFrameForX(m_clickPos.x());
-
-	    size_t newCentreFrame = m_dragCentreFrame;
-	    
-	    if (frameOff < 0) {
-		newCentreFrame -= frameOff;
-	    } else if (newCentreFrame >= size_t(frameOff)) {
-		newCentreFrame -= frameOff;
-	    } else {
-		newCentreFrame = 0;
-	    }
-	    
-	    if (newCentreFrame >= getModelsEndFrame()) {
-		newCentreFrame = getModelsEndFrame();
-		if (newCentreFrame > 0) --newCentreFrame;
-	    }
-
-	    if (getXForFrame(m_centreFrame) != getXForFrame(newCentreFrame)) {
-		setCentreFrame(newCentreFrame);
-	    }
-	}
-
-    } else if (mode == ViewManager::SelectMode) {
-
-	int mouseFrame = getFrameForX(e->x());
-	size_t resolution = 1;
-	int snapFrameLeft = mouseFrame;
-	int snapFrameRight = mouseFrame;
-	
-	Layer *layer = getSelectedLayer();
-	if (layer && !m_shiftPressed) {
-	    layer->snapToFeatureFrame(this, snapFrameLeft,
-				      resolution, Layer::SnapLeft);
-	    layer->snapToFeatureFrame(this, snapFrameRight,
-				      resolution, Layer::SnapRight);
-	}
-	
-//	std::cerr << "snap: frame = " << mouseFrame << ", start frame = " << m_selectionStartFrame << ", left = " << snapFrameLeft << ", right = " << snapFrameRight << std::endl;
-
-	if (snapFrameLeft < 0) snapFrameLeft = 0;
-	if (snapFrameRight < 0) snapFrameRight = 0;
-	
-	size_t min, max;
-	
-	if (m_selectionStartFrame > snapFrameLeft) {
-	    min = snapFrameLeft;
-	    max = m_selectionStartFrame;
-	} else if (snapFrameRight > m_selectionStartFrame) {
-	    min = m_selectionStartFrame;
-	    max = snapFrameRight;
-	} else {
-	    min = snapFrameLeft;
-	    max = snapFrameRight;
-	}
-
-	if (m_manager) {
-	    m_manager->setInProgressSelection(Selection(min, max),
-					      !m_resizing && !m_ctrlPressed);
-	}
-
-	bool doScroll = false;
-	if (!m_manager) doScroll = true;
-	if (!m_manager->isPlaying()) doScroll = true;
-	if (m_followPlay != PlaybackScrollContinuous) doScroll = true;
-
-	if (doScroll) {
-	    int offset = mouseFrame - getStartFrame();
-	    int available = getEndFrame() - getStartFrame();
-	    if (offset >= available * 0.95) {
-		int move = int(offset - available * 0.95) + 1;
-		setCentreFrame(m_centreFrame + move);
-	    } else if (offset <= available * 0.10) {
-		int move = int(available * 0.10 - offset) + 1;
-		if (m_centreFrame > move) {
-		    setCentreFrame(m_centreFrame - move);
-		} else {
-		    setCentreFrame(0);
-		}
-	    }
-	}
-
-	update();
-
-    } else if (mode == ViewManager::DrawMode) {
-
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-	    layer->drawDrag(this, e);
-	}
-
-    } else if (mode == ViewManager::EditMode) {
-
-	if (!editSelectionDrag(e)) {
-	    Layer *layer = getSelectedLayer();
-	    if (layer && layer->isLayerEditable()) {
-		layer->editDrag(this, e);
-	    }
-	}
-    }
-}
-
-void
-Pane::mouseDoubleClickEvent(QMouseEvent *e)
-{
-    if (e->buttons() & Qt::RightButton) {
-        return;
-    }
-
-//    std::cerr << "mouseDoubleClickEvent" << std::endl;
-
-    m_clickPos = e->pos();
-    m_clickedInRange = true;
-    m_shiftPressed = (e->modifiers() & Qt::ShiftModifier);
-    m_ctrlPressed = (e->modifiers() & Qt::ControlModifier);
-
-    ViewManager::ToolMode mode = ViewManager::NavigateMode;
-    if (m_manager) mode = m_manager->getToolMode();
-
-    if (mode == ViewManager::NavigateMode ||
-        mode == ViewManager::EditMode) {
-
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-	    layer->editOpen(this, e);
-	}
-    }
-}
-
-void
-Pane::leaveEvent(QEvent *)
-{
-    bool previouslyIdentifying = m_identifyFeatures;
-    m_identifyFeatures = false;
-    if (previouslyIdentifying) update();
-}
-
-void
-Pane::wheelEvent(QWheelEvent *e)
-{
-    //std::cerr << "wheelEvent, delta " << e->delta() << std::endl;
-
-    int count = e->delta();
-
-    if (count > 0) {
-	if (count >= 120) count /= 120;
-	else count = 1;
-    } 
-
-    if (count < 0) {
-	if (count <= -120) count /= 120;
-	else count = -1;
-    }
-
-    if (e->modifiers() & Qt::ControlModifier) {
-
-	// Scroll left or right, rapidly
-
-	if (getStartFrame() < 0 && 
-	    getEndFrame() >= getModelsEndFrame()) return;
-
-	long delta = ((width() / 2) * count * m_zoomLevel);
-
-	if (int(m_centreFrame) < delta) {
-	    setCentreFrame(0);
-	} else if (int(m_centreFrame) - delta >= int(getModelsEndFrame())) {
-	    setCentreFrame(getModelsEndFrame());
-	} else {
-	    setCentreFrame(m_centreFrame - delta);
-	}
-
-    } else {
-
-	// Zoom in or out
-
-	int newZoomLevel = m_zoomLevel;
-  
-	while (count > 0) {
-	    if (newZoomLevel <= 2) {
-		newZoomLevel = 1;
-		break;
-	    }
-	    newZoomLevel = getZoomConstraintBlockSize(newZoomLevel - 1, 
-						      ZoomConstraint::RoundDown);
-	    --count;
-	}
-	
-	while (count < 0) {
-	    newZoomLevel = getZoomConstraintBlockSize(newZoomLevel + 1,
-						      ZoomConstraint::RoundUp);
-	    ++count;
-	}
-	
-	if (newZoomLevel != m_zoomLevel) {
-	    setZoomLevel(newZoomLevel);
-	}
-    }
-
-    emit paneInteractedWith();
-}
-
-bool
-Pane::editSelectionStart(QMouseEvent *e)
-{
-    if (!m_identifyFeatures ||
-	!m_manager ||
-	m_manager->getToolMode() != ViewManager::EditMode) {
-	return false;
-    }
-
-    bool closeToLeft, closeToRight;
-    Selection s(getSelectionAt(e->x(), closeToLeft, closeToRight));
-    if (s.isEmpty()) return false;
-    m_editingSelection = s;
-    m_editingSelectionEdge = (closeToLeft ? -1 : closeToRight ? 1 : 0);
-    m_mousePos = e->pos();
-    return true;
-}
-
-bool
-Pane::editSelectionDrag(QMouseEvent *e)
-{
-    if (m_editingSelection.isEmpty()) return false;
-    m_mousePos = e->pos();
-    update();
-    return true;
-}
-
-bool
-Pane::editSelectionEnd(QMouseEvent *e)
-{
-    if (m_editingSelection.isEmpty()) return false;
-
-    int offset = m_mousePos.x() - m_clickPos.x();
-    Layer *layer = getSelectedLayer();
-
-    if (offset == 0 || !layer) {
-	m_editingSelection = Selection();
-	return true;
-    }
-
-    int p0 = getXForFrame(m_editingSelection.getStartFrame()) + offset;
-    int p1 = getXForFrame(m_editingSelection.getEndFrame()) + offset;
-
-    long f0 = getFrameForX(p0);
-    long f1 = getFrameForX(p1);
-
-    Selection newSelection(f0, f1);
-    
-    if (m_editingSelectionEdge == 0) {
-	
-        CommandHistory::getInstance()->startCompoundOperation
-            (tr("Drag Selection"), true);
-
-	layer->moveSelection(m_editingSelection, f0);
-	
-    } else {
-	
-        CommandHistory::getInstance()->startCompoundOperation
-            (tr("Resize Selection"), true);
-
-	if (m_editingSelectionEdge < 0) {
-	    f1 = m_editingSelection.getEndFrame();
-	} else {
-	    f0 = m_editingSelection.getStartFrame();
-	}
-
-	newSelection = Selection(f0, f1);
-	layer->resizeSelection(m_editingSelection, newSelection);
-    }
-    
-    m_manager->removeSelection(m_editingSelection);
-    m_manager->addSelection(newSelection);
-
-    CommandHistory::getInstance()->endCompoundOperation();
-
-    m_editingSelection = Selection();
-    return true;
-}
-
-void
-Pane::toolModeChanged()
-{
-    ViewManager::ToolMode mode = m_manager->getToolMode();
-//    std::cerr << "Pane::toolModeChanged(" << mode << ")" << std::endl;
-
-    switch (mode) {
-
-    case ViewManager::NavigateMode:
-	setCursor(Qt::PointingHandCursor);
-	break;
-	
-    case ViewManager::SelectMode:
-	setCursor(Qt::ArrowCursor);
-	break;
-	
-    case ViewManager::EditMode:
-	setCursor(Qt::UpArrowCursor);
-	break;
-	
-    case ViewManager::DrawMode:
-	setCursor(Qt::CrossCursor);
-	break;
-/*	
-    case ViewManager::TextMode:
-	setCursor(Qt::IBeamCursor);
-	break;
-*/
-    }
-}
-
-QString
-Pane::toXmlString(QString indent, QString extraAttributes) const
-{
-    return View::toXmlString
-	(indent,
-	 QString("type=\"pane\" centreLineVisible=\"%1\" height=\"%2\" %3")
-	 .arg(m_centreLineVisible).arg(height()).arg(extraAttributes));
-}
-
-
-#ifdef INCLUDE_MOCFILES
-#include "Pane.moc.cpp"
-#endif
-
--- a/widgets/Pane.h	Thu Jul 27 16:08:04 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +0,0 @@
-
-/* -*- 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 file copyright 2006 Chris Cannam.
-    
-    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.
-*/
-
-#ifndef _PANE_H_
-#define _PANE_H_
-
-#include <QFrame>
-#include <QPoint>
-
-#include "base/ZoomConstraint.h"
-#include "base/View.h"
-#include "base/Selection.h"
-
-class QWidget;
-class QPaintEvent;
-class Layer;
-
-class Pane : public View
-{
-    Q_OBJECT
-
-public:
-    Pane(QWidget *parent = 0);
-    virtual QString getPropertyContainerIconName() const { return "pane"; }
-
-    virtual bool shouldIlluminateLocalFeatures(const Layer *layer,
-					       QPoint &pos) const;
-    virtual bool shouldIlluminateLocalSelection(QPoint &pos,
-						bool &closeToLeft,
-						bool &closeToRight) const;
-
-    void setCentreLineVisible(bool visible);
-    bool getCentreLineVisible() const { return m_centreLineVisible; }
-
-    virtual QString toXmlString(QString indent = "",
-				QString extraAttributes = "") const;
-
-signals:
-    void paneInteractedWith();
-    void rightButtonMenuRequested(QPoint position);
-
-public slots:
-    virtual void toolModeChanged();
-
-protected:
-    virtual void paintEvent(QPaintEvent *e);
-    virtual void mousePressEvent(QMouseEvent *e);
-    virtual void mouseReleaseEvent(QMouseEvent *e);
-    virtual void mouseMoveEvent(QMouseEvent *e);
-    virtual void mouseDoubleClickEvent(QMouseEvent *e);
-    virtual void leaveEvent(QEvent *e);
-    virtual void wheelEvent(QWheelEvent *e);
-
-    Selection getSelectionAt(int x, bool &closeToLeft, bool &closeToRight) const;
-
-    bool editSelectionStart(QMouseEvent *e);
-    bool editSelectionDrag(QMouseEvent *e);
-    bool editSelectionEnd(QMouseEvent *e);
-    bool selectionIsBeingEdited() const;
-
-    bool m_identifyFeatures;
-    QPoint m_identifyPoint;
-    QPoint m_clickPos;
-    QPoint m_mousePos;
-    bool m_clickedInRange;
-    bool m_shiftPressed;
-    bool m_ctrlPressed;
-    bool m_navigating;
-    bool m_resizing;
-    size_t m_dragCentreFrame;
-    bool m_centreLineVisible;
-    size_t m_selectionStartFrame;
-    Selection m_editingSelection;
-    int m_editingSelectionEdge;
-};
-
-#endif
-
--- a/widgets/PaneStack.cpp	Thu Jul 27 16:08:04 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,434 +0,0 @@
-
-/* -*- 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 file copyright 2006 Chris Cannam.
-    
-    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.
-*/
-
-#include "PaneStack.h"
-
-#include "widgets/Pane.h"
-#include "widgets/PropertyStack.h"
-#include "base/Layer.h"
-#include "base/ViewManager.h"
-
-#include <QApplication>
-#include <QHBoxLayout>
-#include <QPainter>
-#include <QPalette>
-#include <QLabel>
-#include <QSplitter>
-#include <QStackedWidget>
-
-#include <iostream>
-
-PaneStack::PaneStack(QWidget *parent, ViewManager *viewManager) :
-    QFrame(parent),
-    m_currentPane(0),
-    m_splitter(new QSplitter),
-    m_propertyStackStack(new QStackedWidget),
-    m_viewManager(viewManager),
-    m_layoutStyle(PropertyStackPerPaneLayout)
-{
-    QHBoxLayout *layout = new QHBoxLayout;
-    layout->setMargin(0);
-    layout->setSpacing(0);
-
-    m_splitter->setOrientation(Qt::Vertical);
-    m_splitter->setOpaqueResize(false);
-
-    layout->addWidget(m_splitter);
-    layout->setStretchFactor(m_splitter, 1);
-    layout->addWidget(m_propertyStackStack);
-    m_propertyStackStack->hide();
-
-    setLayout(layout);
-}
-
-Pane *
-PaneStack::addPane(bool suppressPropertyBox)
-{
-    QFrame *frame = new QFrame;
-    QHBoxLayout *layout = new QHBoxLayout;
-    layout->setMargin(0);
-    layout->setSpacing(2);
-
-    QLabel *currentIndicator = new QLabel(frame);
-    currentIndicator->setFixedWidth(QPainter(this).fontMetrics().width("x"));
-    layout->addWidget(currentIndicator);
-    layout->setStretchFactor(currentIndicator, 1);
-    currentIndicator->setScaledContents(true);
-
-    Pane *pane = new Pane(frame);
-    pane->setViewManager(m_viewManager);
-    layout->addWidget(pane);
-    layout->setStretchFactor(pane, 10);
-
-    QWidget *properties = 0;
-    if (suppressPropertyBox) {
-	properties = new QFrame();
-    } else {
-	properties = new PropertyStack(frame, pane);
-	connect(properties, SIGNAL(propertyContainerSelected(View *, PropertyContainer *)),
-		this, SLOT(propertyContainerSelected(View *, PropertyContainer *)));
-    }
-    if (m_layoutStyle == PropertyStackPerPaneLayout) {
-        layout->addWidget(properties);
-    } else {
-        properties->setParent(m_propertyStackStack);
-        m_propertyStackStack->addWidget(properties);
-    }
-    layout->setStretchFactor(properties, 1);
-
-    PaneRec rec;
-    rec.pane = pane;
-    rec.propertyStack = properties;
-    rec.currentIndicator = currentIndicator;
-    rec.frame = frame;
-    rec.layout = layout;
-    m_panes.push_back(rec);
-
-    frame->setLayout(layout);
-    m_splitter->addWidget(frame);
-
-    connect(pane, SIGNAL(propertyContainerAdded(PropertyContainer *)),
-	    this, SLOT(propertyContainerAdded(PropertyContainer *)));
-    connect(pane, SIGNAL(propertyContainerRemoved(PropertyContainer *)),
-	    this, SLOT(propertyContainerRemoved(PropertyContainer *)));
-    connect(pane, SIGNAL(paneInteractedWith()),
-	    this, SLOT(paneInteractedWith()));
-    connect(pane, SIGNAL(rightButtonMenuRequested(QPoint)),
-            this, SLOT(rightButtonMenuRequested(QPoint)));
-
-    if (!m_currentPane) {
-	setCurrentPane(pane);
-    }
-
-    return pane;
-}
-
-void
-PaneStack::setLayoutStyle(LayoutStyle style)
-{
-    if (style == m_layoutStyle) return;
-    m_layoutStyle = style;
-
-    std::vector<PaneRec>::iterator i;
-
-    switch (style) {
-
-    case SinglePropertyStackLayout:
-        
-        for (i = m_panes.begin(); i != m_panes.end(); ++i) {
-            i->layout->removeWidget(i->propertyStack);
-            i->propertyStack->setParent(m_propertyStackStack);
-            m_propertyStackStack->addWidget(i->propertyStack);
-        }
-        m_propertyStackStack->show();
-        break;
-
-    case PropertyStackPerPaneLayout:
-
-        for (i = m_panes.begin(); i != m_panes.end(); ++i) {
-            m_propertyStackStack->removeWidget(i->propertyStack);
-            i->propertyStack->setParent(i->frame);
-            i->layout->addWidget(i->propertyStack);
-            i->propertyStack->show();
-        }
-        m_propertyStackStack->hide();
-        break;
-    }
-}
-
-Pane *
-PaneStack::getPane(int n)
-{
-    return m_panes[n].pane;
-}
-
-Pane *
-PaneStack::getHiddenPane(int n)
-{
-    return m_hiddenPanes[n].pane;
-}
-
-void
-PaneStack::deletePane(Pane *pane)
-{
-    std::vector<PaneRec>::iterator i;
-    bool found = false;
-
-    for (i = m_panes.begin(); i != m_panes.end(); ++i) {
-	if (i->pane == pane) {
-	    m_panes.erase(i);
-	    found = true;
-	    break;
-	}
-    }
-
-    if (!found) {
-
-	for (i = m_hiddenPanes.begin(); i != m_hiddenPanes.end(); ++i) {
-	    if (i->pane == pane) {
-		m_hiddenPanes.erase(i);
-		found = true;
-		break;
-	    }
-	}
-
-	if (!found) {
-	    std::cerr << "WARNING: PaneStack::deletePane(" << pane << "): Pane not found in visible or hidden panes, not deleting" << std::endl;
-	    return;
-	}
-    }
-
-    delete pane->parent();
-
-    if (m_currentPane == pane) {
-	if (m_panes.size() > 0) {
-            setCurrentPane(m_panes[0].pane);
-	} else {
-	    setCurrentPane(0);
-	}
-    }
-}
-
-int
-PaneStack::getPaneCount() const
-{
-    return m_panes.size();
-}
-
-int
-PaneStack::getHiddenPaneCount() const
-{
-    return m_hiddenPanes.size();
-}
-
-void
-PaneStack::hidePane(Pane *pane)
-{
-    std::vector<PaneRec>::iterator i = m_panes.begin();
-
-    while (i != m_panes.end()) {
-	if (i->pane == pane) {
-
-	    m_hiddenPanes.push_back(*i);
-	    m_panes.erase(i);
-
-	    QWidget *pw = dynamic_cast<QWidget *>(pane->parent());
-	    if (pw) pw->hide();
-
-	    if (m_currentPane == pane) {
-		if (m_panes.size() > 0) {
-		    setCurrentPane(m_panes[0].pane);
-		} else {
-		    setCurrentPane(0);
-		}
-	    }
-	    
-	    return;
-	}
-	++i;
-    }
-
-    std::cerr << "WARNING: PaneStack::hidePane(" << pane << "): Pane not found in visible panes" << std::endl;
-}
-
-void
-PaneStack::showPane(Pane *pane)
-{
-    std::vector<PaneRec>::iterator i = m_hiddenPanes.begin();
-
-    while (i != m_hiddenPanes.end()) {
-	if (i->pane == pane) {
-	    m_panes.push_back(*i);
-	    m_hiddenPanes.erase(i);
-	    QWidget *pw = dynamic_cast<QWidget *>(pane->parent());
-	    if (pw) pw->show();
-
-	    //!!! update current pane
-
-	    return;
-	}
-	++i;
-    }
-
-    std::cerr << "WARNING: PaneStack::showPane(" << pane << "): Pane not found in hidden panes" << std::endl;
-}
-
-void
-PaneStack::setCurrentPane(Pane *pane) // may be null
-{
-    if (m_currentPane == pane) return;
-    
-    std::vector<PaneRec>::iterator i = m_panes.begin();
-
-    // We used to do this by setting the foreground and background
-    // role, but it seems the background role is ignored and the
-    // background drawn transparent in Qt 4.1 -- I can't quite see why
-    
-    QPixmap selectedMap(1, 1);
-    selectedMap.fill(QApplication::palette().color(QPalette::Foreground));
-    
-    QPixmap unselectedMap(1, 1);
-    unselectedMap.fill(QApplication::palette().color(QPalette::Background));
-
-    bool found = false;
-
-    while (i != m_panes.end()) {
-	if (i->pane == pane) {
-	    i->currentIndicator->setPixmap(selectedMap);
-            if (m_layoutStyle == SinglePropertyStackLayout) {
-                m_propertyStackStack->setCurrentWidget(i->propertyStack);
-            }
-	    found = true;
-	} else {
-	    i->currentIndicator->setPixmap(unselectedMap);
-	}
-	++i;
-    }
-
-    if (found || pane == 0) {
-	m_currentPane = pane;
-	emit currentPaneChanged(m_currentPane);
-    } else {
-	std::cerr << "WARNING: PaneStack::setCurrentPane(" << pane << "): pane is not a visible pane in this stack" << std::endl;
-    }
-}
-
-void
-PaneStack::setCurrentLayer(Pane *pane, Layer *layer) // may be null
-{
-    setCurrentPane(pane);
-
-    if (m_currentPane) {
-
-	std::vector<PaneRec>::iterator i = m_panes.begin();
-
-	while (i != m_panes.end()) {
-
-	    if (i->pane == pane) {
-		PropertyStack *stack = dynamic_cast<PropertyStack *>
-		    (i->propertyStack);
-		if (stack) {
-		    if (stack->containsContainer(layer)) {
-			stack->setCurrentIndex(stack->getContainerIndex(layer));
-			emit currentLayerChanged(pane, layer);
-		    } else {
-			stack->setCurrentIndex
-			    (stack->getContainerIndex
-			     (pane->getPropertyContainer(0)));
-			emit currentLayerChanged(pane, 0);
-		    }
-		}
-		break;
-	    }
-	    ++i;
-	}
-    }
-}
-
-Pane *
-PaneStack::getCurrentPane() 
-{
-    return m_currentPane;
-}
-
-void
-PaneStack::propertyContainerAdded(PropertyContainer *)
-{
-    sizePropertyStacks();
-}
-
-void
-PaneStack::propertyContainerRemoved(PropertyContainer *)
-{
-    sizePropertyStacks();
-}
-
-void
-PaneStack::propertyContainerSelected(View *client, PropertyContainer *pc)
-{
-    std::vector<PaneRec>::iterator i = m_panes.begin();
-
-    while (i != m_panes.end()) {
-	PropertyStack *stack = dynamic_cast<PropertyStack *>(i->propertyStack);
-	if (stack &&
-	    stack->getClient() == client &&
-	    stack->containsContainer(pc)) {
-	    setCurrentPane(i->pane);
-	    break;
-	}
-	++i;
-    }
-
-    Layer *layer = dynamic_cast<Layer *>(pc);
-    if (layer) emit currentLayerChanged(m_currentPane, layer);
-    else emit currentLayerChanged(m_currentPane, 0);
-}
-
-void
-PaneStack::paneInteractedWith()
-{
-    Pane *pane = dynamic_cast<Pane *>(sender());
-    if (!pane) return;
-    setCurrentPane(pane);
-}
-
-void
-PaneStack::rightButtonMenuRequested(QPoint position)
-{
-    Pane *pane = dynamic_cast<Pane *>(sender());
-    if (!pane) return;
-    emit rightButtonMenuRequested(pane, position);
-}
-
-void
-PaneStack::sizePropertyStacks()
-{
-    int maxMinWidth = 0;
-
-    for (size_t i = 0; i < m_panes.size(); ++i) {
-	if (!m_panes[i].propertyStack) continue;
-//	std::cerr << "PaneStack::sizePropertyStacks: " << i << ": min " 
-//		  << m_panes[i].propertyStack->minimumSizeHint().width() << ", current "
-//		  << m_panes[i].propertyStack->width() << std::endl;
-
-	if (m_panes[i].propertyStack->minimumSizeHint().width() > maxMinWidth) {
-	    maxMinWidth = m_panes[i].propertyStack->minimumSizeHint().width();
-	}
-    }
-
-//    std::cerr << "PaneStack::sizePropertyStacks: max min width " << maxMinWidth << std::endl;
-
-#ifdef Q_WS_MAC
-    // This is necessary to compensate for cb->setMinimumSize(10, 10)
-    // in PropertyBox in the Mac version (to avoid a mysterious crash)
-    int setWidth = maxMinWidth * 3 / 2;
-#else
-    int setWidth = maxMinWidth;
-#endif
-
-    m_propertyStackStack->setMaximumWidth(setWidth + 10);
-
-    for (size_t i = 0; i < m_panes.size(); ++i) {
-	if (!m_panes[i].propertyStack) continue;
-	m_panes[i].propertyStack->setMinimumWidth(setWidth);
-    }
-}
-    
-
-#ifdef INCLUDE_MOCFILES
-#include "PaneStack.moc.cpp"
-#endif
-
--- a/widgets/PaneStack.h	Thu Jul 27 16:08:04 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-
-/* -*- 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 file copyright 2006 Chris Cannam.
-    
-    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.
-*/
-
-#ifndef _PANESTACK_H_
-#define _PANESTACK_H_
-
-#include <QFrame>
-
-class QWidget;
-class QLabel;
-class QStackedWidget;
-class QSplitter;
-class QHBoxLayout;
-class View;
-class Pane;
-class Layer;
-class ViewManager;
-class PropertyContainer;
-class PropertyStack;
-
-class PaneStack : public QFrame
-{
-    Q_OBJECT
-
-public:
-    PaneStack(QWidget *parent, ViewManager *viewManager);
-
-    Pane *addPane(bool suppressPropertyBox = false); // I own the returned value
-    void deletePane(Pane *pane); // Deletes the pane, but _not_ its layers
-
-    int getPaneCount() const; // Returns only count of visible panes
-    Pane *getPane(int n); // Of visible panes; I own the returned value
-
-    void hidePane(Pane *pane); // Also removes pane from getPane/getPaneCount
-    void showPane(Pane *pane); // Returns pane to getPane/getPaneCount
-
-    int getHiddenPaneCount() const;
-    Pane *getHiddenPane(int n); // I own the returned value
-
-    void setCurrentPane(Pane *pane);
-    void setCurrentLayer(Pane *pane, Layer *layer);
-    Pane *getCurrentPane();
-
-    enum LayoutStyle {
-        SinglePropertyStackLayout = 1,
-        PropertyStackPerPaneLayout = 2
-    };
-
-    LayoutStyle getLayoutStyle() const { return m_layoutStyle; }
-    void setLayoutStyle(LayoutStyle style);
-
-signals:
-    void currentPaneChanged(Pane *pane);
-    void currentLayerChanged(Pane *pane, Layer *layer);
-    void rightButtonMenuRequested(Pane *pane, QPoint position);
-
-public slots:
-    void propertyContainerAdded(PropertyContainer *);
-    void propertyContainerRemoved(PropertyContainer *);
-    void propertyContainerSelected(View *client, PropertyContainer *);
-    void paneInteractedWith();
-    void rightButtonMenuRequested(QPoint);
-
-protected:
-    Pane *m_currentPane;
-
-    struct PaneRec
-    {
-	Pane        *pane;
-	QWidget     *propertyStack;
-	QLabel      *currentIndicator;
-        QFrame      *frame;
-        QHBoxLayout *layout;
-    };
-
-    std::vector<PaneRec> m_panes;
-    std::vector<PaneRec> m_hiddenPanes;
-
-    QSplitter *m_splitter;
-    QStackedWidget *m_propertyStackStack;
-
-    ViewManager *m_viewManager; // I don't own this
-    void sizePropertyStacks();
-
-    LayoutStyle m_layoutStyle;
-};
-
-#endif
-
--- a/widgets/Panner.cpp	Thu Jul 27 16:08:04 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,216 +0,0 @@
-/* -*- 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 file copyright 2006 Chris Cannam.
-    
-    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.
-*/
-
-#include "Panner.h"
-#include "base/Layer.h"
-#include "base/Model.h"
-#include "base/ZoomConstraint.h"
-
-#include <QPaintEvent>
-#include <QPainter>
-#include <iostream>
-
-using std::cerr;
-using std::endl;
-
-Panner::Panner(QWidget *w) :
-    View(w, false),
-    m_clickedInRange(false)
-{
-    setObjectName(tr("Panner"));
-    m_followPan = false;
-    m_followZoom = false;
-}
-
-void
-Panner::modelChanged(size_t startFrame, size_t endFrame)
-{
-    View::modelChanged(startFrame, endFrame);
-}
-
-void
-Panner::modelReplaced()
-{
-    View::modelReplaced();
-}
-
-void
-Panner::registerView(View *widget)
-{
-    m_widgets.insert(widget);
-    update(); 
-}
-
-void
-Panner::unregisterView(View *widget)
-{
-    m_widgets.erase(widget);
-    update();
-}
-
-void
-Panner::viewManagerCentreFrameChanged(void *p, unsigned long f, bool)
-{
-//    std::cerr << "Panner[" << this << "]::viewManagerCentreFrameChanged(" 
-//	      << p << ", " << f << ")" << std::endl;
-
-    if (p == this) return;
-    if (m_widgets.find(p) != m_widgets.end()) {
-	update();
-    }
-}
-
-void
-Panner::viewManagerZoomLevelChanged(void *p, unsigned long z, bool)
-{
-    if (p == this) return;
-    if (m_widgets.find(p) != m_widgets.end()) {
-	update();
-    }
-}
-
-void
-Panner::viewManagerPlaybackFrameChanged(unsigned long f)
-{
-    bool changed = false;
-
-    if (getXForFrame(m_playPointerFrame) != getXForFrame(f)) changed = true;
-    m_playPointerFrame = f;
-
-    if (changed) update();
-}
-
-void
-Panner::paintEvent(QPaintEvent *e)
-{
-    // Recalculate zoom in case the size of the widget has changed.
-
-    size_t startFrame = getModelsStartFrame();
-    size_t frameCount = getModelsEndFrame() - getModelsStartFrame();
-    int zoomLevel = frameCount / width();
-    if (zoomLevel < 1) zoomLevel = 1;
-    zoomLevel = getZoomConstraintBlockSize(zoomLevel,
-					   ZoomConstraint::RoundUp);
-    if (zoomLevel != m_zoomLevel) {
-	m_zoomLevel = zoomLevel;
-	emit zoomLevelChanged(this, m_zoomLevel, m_followZoom);
-    }
-    size_t centreFrame = startFrame + m_zoomLevel * (width() / 2);
-    if (centreFrame > (startFrame + getModelsEndFrame())/2) {
-	centreFrame = (startFrame + getModelsEndFrame())/2;
-    }
-    if (centreFrame != m_centreFrame) {
-	m_centreFrame = centreFrame;
-	emit centreFrameChanged(this, m_centreFrame, false);
-    }
-
-    View::paintEvent(e);
-
-    QPainter paint;
-    paint.begin(this);
-
-    QRect r(rect());
-
-    if (e) {
-	r = e->rect();
-	paint.setClipRect(r);
-    }
-
-    paint.setPen(Qt::black);
-
-    int y = 0;
-
-    int prevx0 = -10;
-    int prevx1 = -10;
-
-    for (WidgetSet::iterator i = m_widgets.begin(); i != m_widgets.end(); ++i) {
-	if (!*i) continue;
-
-	View *w = (View *)*i;
-
-	long f0 = w->getFrameForX(0);
-	long f1 = w->getFrameForX(w->width());
-
-	int x0 = getXForFrame(f0);
-	int x1 = getXForFrame(f1);
-
-	if (x0 != prevx0 || x1 != prevx1) {
-	    y += height() / 10 + 1;
-	    prevx0 = x0;
-	    prevx1 = x1;
-	}
-
-	if (x1 <= x0) x1 = x0 + 1;
-	
-	paint.drawRect(x0, y, x1 - x0, height() - 2 * y);
-    }
-
-    paint.end();
-}
-
-void
-Panner::mousePressEvent(QMouseEvent *e)
-{
-    m_clickPos = e->pos();
-    for (WidgetSet::iterator i = m_widgets.begin(); i != m_widgets.end(); ++i) {
-	if (*i) {
-	    m_clickedInRange = true;
-	    m_dragCentreFrame = ((View *)*i)->getCentreFrame();
-	    break;
-	}
-    }
-}
-
-void
-Panner::mouseReleaseEvent(QMouseEvent *e)
-{
-    if (m_clickedInRange) {
-	mouseMoveEvent(e);
-    }
-    m_clickedInRange = false;
-}
-
-void
-Panner::mouseMoveEvent(QMouseEvent *e)
-{
-    if (!m_clickedInRange) return;
-
-    long xoff = int(e->x()) - int(m_clickPos.x());
-    long frameOff = xoff * m_zoomLevel;
-    
-    size_t newCentreFrame = m_dragCentreFrame;
-    if (frameOff > 0) {
-	newCentreFrame += frameOff;
-    } else if (newCentreFrame >= size_t(-frameOff)) {
-	newCentreFrame += frameOff;
-    } else {
-	newCentreFrame = 0;
-    }
-
-    if (newCentreFrame >= getModelsEndFrame()) {
-	newCentreFrame = getModelsEndFrame();
-	if (newCentreFrame > 0) --newCentreFrame;
-    }
-    
-    if (std::max(m_centreFrame, newCentreFrame) -
-	std::min(m_centreFrame, newCentreFrame) > size_t(m_zoomLevel)) {
-	emit centreFrameChanged(this, newCentreFrame, true);
-    }
-}
-
-#ifdef INCLUDE_MOCFILES
-#include "Panner.moc.cpp"
-#endif
-
--- a/widgets/Panner.h	Thu Jul 27 16:08:04 2006 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/* -*- 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 file copyright 2006 Chris Cannam.
-    
-    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.
-*/
-
-#ifndef _PAN_WIDGET_H_
-#define _PAN_WIDGET_H_
-
-#include "base/View.h"
-
-#include <QPoint>
-
-class QWidget;
-class QPaintEvent;
-class Layer;
-class View;
-
-#include <map>
-
-class Panner : public View
-{
-    Q_OBJECT
-
-public:
-    Panner(QWidget *parent = 0);
-
-    void registerView(View *widget);
-    void unregisterView(View *widget);
-
-    virtual QString getPropertyContainerIconName() const { return "panner"; }
-
-public slots:
-    virtual void modelChanged(size_t startFrame, size_t endFrame);
-    virtual void modelReplaced();
-
-    virtual void viewManagerCentreFrameChanged(void *, unsigned long, bool);
-    virtual void viewManagerZoomLevelChanged(void *, unsigned long, bool);
-    virtual void viewManagerPlaybackFrameChanged(unsigned long);
-
-protected:
-    virtual void paintEvent(QPaintEvent *e);
-    virtual void mousePressEvent(QMouseEvent *e);
-    virtual void mouseReleaseEvent(QMouseEvent *e);
-    virtual void mouseMoveEvent(QMouseEvent *e);
-    virtual bool shouldLabelSelections() const { return false; }
-
-    QPoint m_clickPos;
-    QPoint m_mousePos;
-    bool m_clickedInRange;
-    size_t m_dragCentreFrame;
-    
-    typedef std::set<void *> WidgetSet;
-    WidgetSet m_widgets;
-};
-
-#endif
-