Chris@127: 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@127: Chris@127: #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@127: Chris@127: virtual QString getPropertyContainerIconName() const; Chris@127: Chris@127: virtual QString getPropertyContainerName() const { Chris@127: return objectName(); Chris@127: } Chris@127: Chris@127: virtual QString getLayerPresentationName() const; 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@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@127: // Draw 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@127: virtual void editStart(View *, QMouseEvent *) { } Chris@127: virtual void editDrag(View *, QMouseEvent *) { } Chris@127: virtual void editEnd(View *, QMouseEvent *) { } Chris@127: Chris@127: virtual void editOpen(View *, QMouseEvent *) { } // on double-click 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@127: virtual void copy(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@127: virtual bool paste(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@127: /** Chris@183: * This should return true if the layer uses colours to indicate Chris@183: * meaningful information (as opposed to just using a single Chris@183: * colour of the user's choice). If this is the case, the view Chris@183: * will show selections using unfilled rectangles instead of Chris@183: * translucent filled rectangles, so as not to disturb the colours Chris@183: * underneath. Chris@183: */ Chris@183: virtual bool isLayerColourSignificant() const { return false; } 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@127: * isReady(int *) call. The widget 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@127: * refers to) into an XML string for file output. This class Chris@127: * implements the basic name/type/model-id output; subclasses will Chris@127: * typically call this superclass implementation with extra Chris@127: * attributes describing their particular properties. Chris@127: */ Chris@127: virtual QString toXmlString(QString indent = "", Chris@127: 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@127: * inverse of the toXmlString method. Chris@127: */ Chris@127: virtual void setProperties(const QXmlAttributes &) = 0; Chris@127: Chris@127: /** 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@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@133: 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@127: void modelChanged(size_t startFrame, size_t endFrame); Chris@127: void modelReplaced(); Chris@127: Chris@127: void layerParametersChanged(); Chris@197: void layerParameterRangesChanged(); Chris@127: void layerNameChanged(); Chris@127: Chris@133: void verticalZoomChanged(); Chris@133: 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: