Mercurial > hg > svgui
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 -