Chris@127: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@127: Chris@127: /* Chris@127: Sonic Visualiser Chris@127: An audio file viewer and annotation editor. Chris@127: Centre for Digital Music, Queen Mary, University of London. Chris@182: This file copyright 2006 Chris Cannam and QMUL. Chris@127: Chris@127: This program is free software; you can redistribute it and/or Chris@127: modify it under the terms of the GNU General Public License as Chris@127: published by the Free Software Foundation; either version 2 of the Chris@127: License, or (at your option) any later version. See the file Chris@127: COPYING included with this distribution for more information. Chris@127: */ Chris@127: Chris@127: #ifndef _LAYER_H_ Chris@127: #define _LAYER_H_ Chris@127: Chris@128: #include "base/PropertyContainer.h" Chris@128: #include "base/XmlExportable.h" Chris@128: #include "base/Selection.h" Chris@127: Chris@127: #include Chris@127: #include Chris@127: #include Chris@131: #include Chris@299: #include Chris@127: Chris@127: #include Chris@268: #include Chris@127: Chris@127: class ZoomConstraint; Chris@127: class Model; Chris@127: class QPainter; Chris@127: class View; Chris@127: class QMouseEvent; Chris@127: class Clipboard; Chris@187: class RangeMapper; Chris@127: Chris@127: /** Chris@127: * The base class for visual representations of the data found in a Chris@127: * Model. Layers are expected to be able to draw themselves onto a Chris@127: * View, and may also be editable. Chris@127: */ Chris@127: Chris@127: class Layer : public PropertyContainer, Chris@127: public XmlExportable Chris@127: { Chris@127: Q_OBJECT Chris@127: Chris@127: public: Chris@127: Layer(); Chris@127: virtual ~Layer(); Chris@127: Chris@127: virtual const Model *getModel() const = 0; Chris@127: virtual Model *getModel() { Chris@127: return const_cast(const_cast(this)->getModel()); Chris@127: } Chris@127: Chris@137: /** Chris@137: * Return a zoom constraint object defining the supported zoom Chris@137: * levels for this layer. If this returns zero, the layer will Chris@137: * support any integer zoom level. Chris@137: */ Chris@127: virtual const ZoomConstraint *getZoomConstraint() const { return 0; } Chris@137: Chris@137: /** Chris@137: * Return true if this layer can handle zoom levels other than Chris@137: * those supported by its zoom constraint (presumably less Chris@137: * efficiently or accurately than the officially supported zoom Chris@137: * levels). If true, the layer will unenthusistically accept any Chris@137: * integer zoom level from 1 to the maximum returned by its zoom Chris@137: * constraint. Chris@137: */ Chris@137: virtual bool supportsOtherZoomLevels() const { return true; } Chris@137: Chris@127: virtual void paint(View *, QPainter &, QRect) const = 0; Chris@127: Chris@127: enum VerticalPosition { Chris@127: PositionTop, PositionMiddle, PositionBottom Chris@127: }; Chris@127: virtual VerticalPosition getPreferredTimeRulerPosition() const { Chris@127: return PositionMiddle; Chris@127: } Chris@127: virtual VerticalPosition getPreferredFrameCountPosition() const { Chris@127: return PositionBottom; Chris@127: } Chris@224: virtual bool hasLightBackground() const { Chris@224: return true; Chris@224: } Chris@127: Chris@127: virtual QString getPropertyContainerIconName() const; Chris@127: Chris@127: virtual QString getPropertyContainerName() const { Chris@363: if (m_presentationName != "") return m_presentationName; Chris@363: else return objectName(); Chris@127: } Chris@127: Chris@363: virtual void setPresentationName(QString name); Chris@363: Chris@127: virtual QString getLayerPresentationName() const; Chris@299: virtual QPixmap getLayerPresentationPixmap(QSize) const { return QPixmap(); } Chris@127: Chris@127: virtual int getVerticalScaleWidth(View *, QPainter &) const { return 0; } Chris@127: virtual void paintVerticalScale(View *, QPainter &, QRect) const { } Chris@127: Chris@127: virtual bool getCrosshairExtents(View *, QPainter &, QPoint /* cursorPos */, Chris@127: std::vector &) const { Chris@127: return false; Chris@127: } Chris@127: virtual void paintCrosshairs(View *, QPainter &, QPoint) const { } Chris@127: Chris@272: virtual void paintMeasurementRects(View *, QPainter &, Chris@272: bool showFocus, QPoint focusPoint) const; Chris@272: Chris@272: virtual bool nearestMeasurementRectChanged(View *, QPoint prev, Chris@272: QPoint now) const; Chris@267: Chris@127: virtual QString getFeatureDescription(View *, QPoint &) const { Chris@127: return ""; Chris@127: } Chris@127: Chris@127: enum SnapType { Chris@127: SnapLeft, Chris@127: SnapRight, Chris@127: SnapNearest, Chris@127: SnapNeighbouring Chris@127: }; Chris@127: Chris@127: /** Chris@127: * Adjust the given frame to snap to the nearest feature, if Chris@127: * possible. Chris@127: * Chris@127: * If snap is SnapLeft or SnapRight, adjust the frame to match Chris@127: * that of the nearest feature in the given direction regardless Chris@127: * of how far away it is. If snap is SnapNearest, adjust the Chris@127: * frame to that of the nearest feature in either direction. If Chris@127: * snap is SnapNeighbouring, adjust the frame to that of the Chris@127: * nearest feature if it is close, and leave it alone (returning Chris@127: * false) otherwise. SnapNeighbouring should always choose the Chris@127: * same feature that would be used in an editing operation through Chris@127: * calls to editStart etc. Chris@127: * Chris@127: * Return true if a suitable feature was found and frame adjusted Chris@180: * accordingly. Return false if no suitable feature was available Chris@180: * (and leave frame unmodified). Also return the resolution of Chris@180: * the model in this layer in sample frames. Chris@127: */ Chris@127: virtual bool snapToFeatureFrame(View * /* v */, Chris@127: int & /* frame */, Chris@127: size_t &resolution, Chris@127: SnapType /* snap */) const { Chris@127: resolution = 1; Chris@127: return false; Chris@127: } Chris@127: Chris@335: // Draw, erase, and edit modes: Chris@127: // Chris@127: // Layer needs to get actual mouse events, I guess. Draw mode is Chris@127: // probably the easier. Chris@127: Chris@127: virtual void drawStart(View *, QMouseEvent *) { } Chris@127: virtual void drawDrag(View *, QMouseEvent *) { } Chris@127: virtual void drawEnd(View *, QMouseEvent *) { } Chris@127: Chris@335: virtual void eraseStart(View *, QMouseEvent *) { } Chris@335: virtual void eraseDrag(View *, QMouseEvent *) { } Chris@335: virtual void eraseEnd(View *, QMouseEvent *) { } Chris@335: Chris@127: virtual void editStart(View *, QMouseEvent *) { } Chris@127: virtual void editDrag(View *, QMouseEvent *) { } Chris@127: virtual void editEnd(View *, QMouseEvent *) { } Chris@127: Chris@267: // Measurement rectangle (or equivalent). Unlike draw and edit, Chris@267: // the base Layer class can provide working implementations of Chris@267: // these for most situations. Chris@267: // Chris@267: virtual void measureStart(View *, QMouseEvent *); Chris@267: virtual void measureDrag(View *, QMouseEvent *); Chris@267: virtual void measureEnd(View *, QMouseEvent *); Chris@280: virtual void measureDoubleClick(View *, QMouseEvent *); Chris@267: Chris@283: virtual bool haveCurrentMeasureRect() const { Chris@283: return m_haveCurrentMeasureRect; Chris@283: } Chris@283: virtual void deleteCurrentMeasureRect(); // using a command Chris@283: Chris@255: /** Chris@255: * Open an editor on the item under the mouse (e.g. on Chris@255: * double-click). If there is no item or editing is not Chris@255: * supported, return false. Chris@255: */ Chris@255: virtual bool editOpen(View *, QMouseEvent *) { return false; } Chris@127: Chris@127: virtual void moveSelection(Selection, size_t /* newStartFrame */) { } Chris@127: virtual void resizeSelection(Selection, Selection /* newSize */) { } Chris@127: virtual void deleteSelection(Selection) { } Chris@127: Chris@359: virtual void copy(View *, Selection, Clipboard & /* to */) { } Chris@127: Chris@127: /** Chris@127: * Paste from the given clipboard onto the layer at the given Chris@127: * frame offset. If interactive is true, the layer may ask the Chris@127: * user about paste options through a dialog if desired, and may Chris@127: * return false if the user cancelled the paste operation. This Chris@127: * function should return true if a paste actually occurred. Chris@127: */ Chris@359: virtual bool paste(View *, Chris@359: const Clipboard & /* from */, Chris@127: int /* frameOffset */, Chris@127: bool /* interactive */) { return false; } Chris@127: Chris@127: // Text mode: Chris@127: // Chris@127: // Label nearest feature. We need to get the feature coordinates Chris@127: // and current label from the layer, and then the pane can pop up Chris@127: // a little text entry dialog at the right location. Or we edit Chris@127: // in place? Probably the dialog is easier. Chris@127: Chris@127: /** Chris@127: * This should return true if the layer can safely be scrolled Chris@127: * automatically by a given view (simply copying the existing data Chris@127: * and then refreshing the exposed area) without altering its Chris@127: * meaning. For the view widget as a whole this is usually not Chris@127: * possible because of invariant (non-scrolling) material Chris@127: * displayed over the top, but the widget may be able to optimise Chris@127: * scrolling better if it is known that individual views can be Chris@127: * scrolled safely in this way. Chris@127: */ Chris@127: virtual bool isLayerScrollable(const View *) const { return true; } Chris@127: Chris@127: /** Chris@127: * This should return true if the layer completely obscures any Chris@127: * underlying layers. It's used to determine whether the view can Chris@127: * safely draw any selection rectangles under the layer instead of Chris@127: * over it, in the case where the layer is not scrollable and Chris@127: * therefore needs to be redrawn each time (so that the selection Chris@127: * rectangle can be cached). Chris@127: */ Chris@127: virtual bool isLayerOpaque() const { return false; } Chris@127: Chris@287: enum ColourSignificance { Chris@287: ColourAbsent, Chris@287: ColourIrrelevant, Chris@287: ColourDistinguishes, Chris@287: ColourAndBackgroundSignificant, Chris@287: ColourHasMeaningfulValue Chris@287: }; Chris@287: Chris@127: /** Chris@287: * This should return the degree of meaning associated with colour Chris@287: * in this layer. Chris@287: * Chris@287: * If ColourAbsent, the layer does not use colour. If Chris@287: * ColourIrrelevant, the layer is coloured and the colour may be Chris@287: * set by the user, but it doesn't really matter what the colour Chris@287: * is (for example, in a time ruler layer). If Chris@287: * ColourDistinguishes, then the colour is used to distinguish Chris@287: * this layer from other similar layers (e.g. for data layers). Chris@287: * If ColourAndBackgroundSignificant, then the layer should be Chris@287: * given greater weight than ColourDistinguishes layers when Chris@287: * choosing a background colour (e.g. for waveforms). If Chris@287: * ColourHasMeaningfulValue, colours are actually meaningful -- Chris@287: * the view will then show selections using unfilled rectangles Chris@287: * instead of translucent filled rectangles, so as not to disturb Chris@287: * the colours underneath. Chris@183: */ Chris@287: virtual ColourSignificance getLayerColourSignificance() const = 0; Chris@183: Chris@183: /** Chris@127: * This should return true if the layer can be edited by the user. Chris@127: * If this is the case, the appropriate edit tools may be made Chris@127: * available by the application and the layer's drawStart/Drag/End Chris@127: * and editStart/Drag/End methods should be implemented. Chris@127: */ Chris@127: virtual bool isLayerEditable() const { return false; } Chris@127: Chris@127: /** Chris@127: * Return the proportion of background work complete in drawing Chris@127: * this view, as a percentage -- in most cases this will be the Chris@127: * value returned by pointer from a call to the underlying model's Chris@226: * isReady(int *) call. The view may choose to show a progress Chris@127: * meter if it finds that this returns < 100 at any given moment. Chris@127: */ Chris@127: virtual int getCompletion(View *) const { return 100; } Chris@127: Chris@127: virtual void setObjectName(const QString &name); Chris@127: Chris@127: /** Chris@127: * Convert the layer's data (though not those of the model it Chris@316: * refers to) into XML for file output. This class implements the Chris@316: * basic name/type/model-id output; subclasses will typically call Chris@316: * this superclass implementation with extra attributes describing Chris@316: * their particular properties. Chris@127: */ Chris@316: virtual void toXml(QTextStream &stream, QString indent = "", Chris@316: QString extraAttributes = "") const; Chris@127: Chris@127: /** Chris@127: * Set the particular properties of a layer (those specific to the Chris@127: * subclass) from a set of XML attributes. This is the effective Chris@316: * inverse of the toXml method. Chris@127: */ Chris@127: virtual void setProperties(const QXmlAttributes &) = 0; Chris@127: Chris@127: /** Chris@316: * Produce XML containing the layer's ID and type. This is used Chris@316: * to refer to the layer in the display section of the SV session Chris@316: * file, for a layer that has already been described in the data Chris@316: * section. Chris@269: */ Chris@316: virtual void toBriefXml(QTextStream &stream, Chris@316: QString indent = "", Chris@316: QString extraAttributes = "") const; Chris@269: Chris@269: /** Chris@269: * Add a measurement rectangle from the given XML attributes Chris@269: * (presumably taken from a measurement element). Chris@269: * Does not use a command. Chris@269: */ Chris@269: virtual void addMeasurementRect(const QXmlAttributes &); Chris@269: Chris@269: /** Chris@127: * Indicate that a layer is not currently visible in the given Chris@127: * view and is not expected to become visible in the near future Chris@127: * (for example because the user has explicitly removed or hidden Chris@127: * it). The layer may respond by (for example) freeing any cache Chris@127: * memory it is using, until next time its paint method is called, Chris@127: * when it should set itself un-dormant again. Chris@131: * Chris@131: * A layer class that overrides this function must also call this Chris@131: * class's implementation. Chris@127: */ Chris@131: virtual void setLayerDormant(const View *v, bool dormant); Chris@127: Chris@127: /** Chris@127: * Return whether the layer is dormant (i.e. hidden) in the given Chris@127: * view. Chris@127: */ Chris@131: virtual bool isLayerDormant(const View *v) const; Chris@127: Chris@127: virtual PlayParameters *getPlayParameters(); Chris@127: Chris@127: virtual bool needsTextLabelHeight() const { return false; } Chris@127: Chris@217: virtual bool hasTimeXAxis() const { return true; } Chris@217: Chris@127: /** Chris@127: * Return the minimum and maximum values for the y axis of the Chris@127: * model in this layer, as well as whether the layer is configured Chris@127: * to use a logarithmic y axis display. Also return the unit for Chris@127: * these values if known. Chris@127: * Chris@127: * This function returns the "normal" extents for the layer, not Chris@127: * necessarily the extents actually in use in the display. Chris@127: */ Chris@127: virtual bool getValueExtents(float &min, float &max, Chris@127: bool &logarithmic, QString &unit) const = 0; Chris@127: Chris@127: /** Chris@127: * Return the minimum and maximum values within the displayed Chris@127: * range for the y axis, if only a subset of the whole range of Chris@127: * the model (returned by getValueExtents) is being displayed. Chris@127: * Return false if the layer is not imposing a particular display Chris@127: * extent (using the normal layer extents or deferring to whatever Chris@127: * is in use for the same units elsewhere in the view). Chris@127: */ Chris@127: virtual bool getDisplayExtents(float & /* min */, Chris@127: float & /* max */) const { Chris@127: return false; Chris@127: } Chris@127: Chris@127: /** Chris@127: * Set the displayed minimum and maximum values for the y axis to Chris@127: * the given range, if supported. Return false if not supported Chris@127: * on this layer (and set nothing). In most cases, layers that Chris@127: * return false for getDisplayExtents should also return false for Chris@127: * this function. Chris@127: */ Chris@127: virtual bool setDisplayExtents(float /* min */, Chris@127: float /* max */) { Chris@127: return false; Chris@127: } Chris@127: Chris@133: /** Chris@260: * Return the value and unit at the given x coordinate in the Chris@260: * given view. This is for descriptive purposes using the Chris@260: * measurement tool. The default implementation works correctly Chris@260: * if the layer hasTimeXAxis(). Chris@260: */ Chris@267: virtual bool getXScaleValue(const View *v, int x, Chris@260: float &value, QString &unit) const; Chris@260: Chris@260: /** Chris@260: * Return the value and unit at the given y coordinate in the Chris@260: * given view. Chris@260: */ Chris@267: virtual bool getYScaleValue(const View *, int /* y */, Chris@260: float &/* value */, QString &/* unit */) const { Chris@260: return false; Chris@260: } Chris@260: Chris@260: /** Chris@274: * Return the difference between the values at the given y Chris@274: * coordinates in the given view, and the unit of the difference. Chris@274: * The default implementation just calls getYScaleValue twice and Chris@274: * returns the difference, with the same unit. Chris@274: */ Chris@274: virtual bool getYScaleDifference(const View *v, int y0, int y1, Chris@274: float &diff, QString &unit) const; Chris@274: Chris@274: /** Chris@133: * Get the number of vertical zoom steps available for this layer. Chris@133: * If vertical zooming is not available, return 0. The meaning of Chris@133: * "zooming" is entirely up to the layer -- changing the zoom Chris@133: * level may cause the layer to reset its display extents or Chris@180: * change another property such as display gain. However, layers Chris@180: * are advised for consistency to treat smaller zoom steps as Chris@180: * "more distant" or "zoomed out" and larger ones as "closer" or Chris@180: * "zoomed in". Chris@180: * Chris@133: * Layers that provide this facility should also emit the Chris@133: * verticalZoomChanged signal if their vertical zoom changes Chris@133: * due to factors other than setVerticalZoomStep being called. Chris@133: */ Chris@248: virtual int getVerticalZoomSteps(int & /* defaultStep */) const { return 0; } Chris@133: Chris@133: /** Chris@133: * Get the current vertical zoom step. A layer may support finer Chris@133: * control over ranges etc than is available through the integer Chris@133: * zoom step mechanism; if this one does, it should just return Chris@133: * the nearest of the available zoom steps to the current settings. Chris@133: */ Chris@133: virtual int getCurrentVerticalZoomStep() const { return 0; } Chris@133: Chris@133: /** Chris@133: * Set the vertical zoom step. The meaning of "zooming" is Chris@133: * entirely up to the layer -- changing the zoom level may cause Chris@133: * the layer to reset its display extents or change another Chris@133: * property such as display gain. Chris@133: */ Chris@133: virtual void setVerticalZoomStep(int) { } Chris@133: Chris@187: /** Chris@187: * Create and return a range mapper for vertical zoom step values. Chris@187: * See the RangeMapper documentation for more details. The Chris@187: * returned value is allocated on the heap and will be deleted by Chris@187: * the caller. Chris@187: */ Chris@187: virtual RangeMapper *getNewVerticalZoomRangeMapper() const { return 0; } Chris@187: Chris@127: public slots: Chris@127: void showLayer(View *, bool show); Chris@127: Chris@127: signals: Chris@127: void modelChanged(); Chris@127: void modelCompletionChanged(); Chris@320: void modelAlignmentCompletionChanged(); Chris@127: void modelChanged(size_t startFrame, size_t endFrame); Chris@127: void modelReplaced(); Chris@127: Chris@127: void layerParametersChanged(); Chris@197: void layerParameterRangesChanged(); Chris@268: void layerMeasurementRectsChanged(); Chris@127: void layerNameChanged(); Chris@127: Chris@133: void verticalZoomChanged(); Chris@133: Chris@267: protected: Chris@320: void connectSignals(const Model *); Chris@320: Chris@359: virtual size_t alignToReference(View *v, size_t frame) const; Chris@359: virtual size_t alignFromReference(View *v, size_t frame) const; Chris@360: bool clipboardHasDifferentAlignment(View *v, const Clipboard &clip) const; Chris@359: Chris@267: struct MeasureRect { Chris@268: Chris@267: mutable QRect pixrect; Chris@268: bool haveFrames; Chris@268: long startFrame; // only valid if haveFrames Chris@267: long endFrame; // ditto Chris@273: double startY; Chris@273: double endY; Chris@268: Chris@268: bool operator<(const MeasureRect &mr) const; Chris@316: void toXml(QTextStream &stream, QString indent) const; Chris@267: }; Chris@267: Chris@268: class AddMeasurementRectCommand : public Command Chris@268: { Chris@268: public: Chris@268: AddMeasurementRectCommand(Layer *layer, MeasureRect rect) : Chris@268: m_layer(layer), m_rect(rect) { } Chris@268: Chris@268: virtual QString getName() const; Chris@268: virtual void execute(); Chris@268: virtual void unexecute(); Chris@268: Chris@268: private: Chris@268: Layer *m_layer; Chris@268: MeasureRect m_rect; Chris@268: }; Chris@268: Chris@283: class DeleteMeasurementRectCommand : public Command Chris@283: { Chris@283: public: Chris@283: DeleteMeasurementRectCommand(Layer *layer, MeasureRect rect) : Chris@283: m_layer(layer), m_rect(rect) { } Chris@283: Chris@283: virtual QString getName() const; Chris@283: virtual void execute(); Chris@283: virtual void unexecute(); Chris@283: Chris@283: private: Chris@283: Layer *m_layer; Chris@283: MeasureRect m_rect; Chris@283: }; Chris@283: Chris@269: void addMeasureRectToSet(const MeasureRect &r) { Chris@268: m_measureRects.insert(r); Chris@268: emit layerMeasurementRectsChanged(); Chris@268: } Chris@268: Chris@269: void deleteMeasureRectFromSet(const MeasureRect &r) { Chris@268: m_measureRects.erase(r); Chris@268: emit layerMeasurementRectsChanged(); Chris@268: } Chris@268: Chris@268: typedef std::set MeasureRectSet; Chris@268: MeasureRectSet m_measureRects; Chris@267: MeasureRect m_draggingRect; Chris@267: bool m_haveDraggingRect; Chris@283: mutable bool m_haveCurrentMeasureRect; Chris@283: mutable QPoint m_currentMeasureRectPoint; Chris@272: Chris@272: // Note that pixrects are only correct for a single view. Chris@272: // So we should update them at the start of the paint procedure Chris@272: // (painting is single threaded) and only use them after that. Chris@273: void updateMeasurePixrects(View *v) const; Chris@273: Chris@273: virtual void updateMeasureRectYCoords(View *v, const MeasureRect &r) const; Chris@273: virtual void setMeasureRectYCoord(View *v, MeasureRect &r, bool start, int y) const; Chris@283: virtual void setMeasureRectFromPixrect(View *v, MeasureRect &r, QRect pixrect) const; Chris@272: Chris@272: // This assumes updateMeasurementPixrects has been called Chris@272: MeasureRectSet::const_iterator findFocusedMeasureRect(QPoint) const; Chris@267: Chris@269: void paintMeasurementRect(View *v, QPainter &paint, Chris@270: const MeasureRect &r, bool focus) const; Chris@268: Chris@363: QString m_presentationName; Chris@363: Chris@131: private: Chris@131: mutable QMutex m_dormancyMutex; Chris@127: mutable std::map m_dormancy; Chris@127: }; Chris@127: Chris@127: #endif Chris@127: