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@127: This file copyright 2006 Chris Cannam. 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@1225: #ifndef SV_VIEW_H Chris@1225: #define SV_VIEW_H Chris@127: Chris@127: #include Chris@127: #include Chris@127: Chris@1077: #include "layer/LayerGeometryProvider.h" Chris@916: Chris@127: #include "base/ZoomConstraint.h" Chris@127: #include "base/PropertyContainer.h" Chris@128: #include "ViewManager.h" Chris@127: #include "base/XmlExportable.h" Chris@902: #include "base/BaseTypes.h" Chris@127: Chris@127: // #define DEBUG_VIEW_WIDGET_PAINT 1 Chris@127: Chris@127: class Layer; Chris@127: class ViewPropertyContainer; Chris@127: Chris@797: class QPushButton; Chris@797: Chris@127: #include Chris@315: #include Chris@127: Chris@127: /** Chris@127: * View is the base class of widgets that display one or more Chris@127: * overlaid views of data against a horizontal time scale. Chris@127: * Chris@127: * A View may have any number of attached Layers, each of which Chris@127: * is expected to have one data Model (although multiple views may Chris@127: * share the same model). Chris@127: * Chris@127: * A View may be panned in time and zoomed, although the Chris@127: * mechanisms for doing so (as well as any other operations and Chris@127: * properties available) depend on the subclass. Chris@127: */ Chris@127: Chris@127: class View : public QFrame, Chris@916: public XmlExportable, Chris@916: public LayerGeometryProvider Chris@127: { Chris@127: Q_OBJECT Chris@127: Chris@127: public: Chris@127: /** Chris@127: * Deleting a View does not delete any of its layers. They should Chris@127: * be managed elsewhere (e.g. by the Document). Chris@127: */ Chris@127: virtual ~View(); Chris@1044: Chris@1044: /** Chris@1044: * Retrieve the id of this object. Views have their own unique Chris@1044: * ids, but ViewProxy objects share the id of their View. Chris@1044: */ Chris@1044: int getId() const { return m_id; } Chris@1030: Chris@127: /** Chris@127: * Retrieve the first visible sample frame on the widget. Chris@127: * This is a calculated value based on the centre-frame, widget Chris@127: * width and zoom level. The result may be negative. Chris@127: */ Chris@902: sv_frame_t getStartFrame() const; Chris@127: Chris@127: /** Chris@127: * Set the widget pan based on the given first visible frame. The Chris@127: * frame value may be negative. Chris@127: */ Chris@902: void setStartFrame(sv_frame_t); Chris@127: Chris@127: /** Chris@127: * Return the centre frame of the visible widget. This is an Chris@127: * exact value that does not depend on the zoom block size. Other Chris@127: * frame values (start, end) are calculated from this based on the Chris@127: * zoom and other factors. Chris@127: */ Chris@902: sv_frame_t getCentreFrame() const { return m_centreFrame; } Chris@127: Chris@127: /** Chris@127: * Set the centre frame of the visible widget. Chris@127: */ Chris@902: void setCentreFrame(sv_frame_t f) { setCentreFrame(f, true); } Chris@127: Chris@127: /** Chris@127: * Retrieve the last visible sample frame on the widget. Chris@127: * This is a calculated value based on the centre-frame, widget Chris@127: * width and zoom level. Chris@127: */ Chris@902: sv_frame_t getEndFrame() const; Chris@127: Chris@127: /** Chris@127: * Return the pixel x-coordinate corresponding to a given sample Chris@127: * frame (which may be negative). Chris@127: */ Chris@902: int getXForFrame(sv_frame_t frame) const; Chris@127: Chris@127: /** Chris@127: * Return the closest frame to the given pixel x-coordinate. Chris@127: */ Chris@902: sv_frame_t getFrameForX(int x) const; Chris@127: Chris@127: /** Chris@1030: * Return the closest pixel x-coordinate corresponding to a given Chris@1030: * view x-coordinate. Default is no scaling, ViewProxy handles Chris@1030: * scaling case. Chris@1030: */ Chris@1030: int getXForViewX(int viewx) const { return viewx; } Chris@1030: Chris@1030: /** Chris@1030: * Return the closest view x-coordinate corresponding to a given Chris@1030: * pixel x-coordinate. Default is no scaling, ViewProxy handles Chris@1030: * scaling case. Chris@1030: */ Chris@1030: int getViewXForX(int x) const { return x; } Chris@1030: Chris@1030: /** Chris@127: * Return the pixel y-coordinate corresponding to a given Chris@127: * frequency, if the frequency range is as specified. This does Chris@127: * not imply any policy about layer frequency ranges, but it might Chris@127: * be useful for layers to match theirs up if desired. Chris@127: * Chris@127: * Not thread-safe in logarithmic mode. Call only from GUI thread. Chris@127: */ Chris@904: double getYForFrequency(double frequency, double minFreq, double maxFreq, Chris@127: bool logarithmic) const; Chris@127: Chris@127: /** Chris@127: * Return the closest frequency to the given pixel y-coordinate, Chris@127: * if the frequency range is as specified. Chris@127: * Chris@127: * Not thread-safe in logarithmic mode. Call only from GUI thread. Chris@127: */ Chris@1085: double getFrequencyForY(double y, double minFreq, double maxFreq, Chris@1085: bool logarithmic) const; Chris@127: Chris@127: /** Chris@127: * Return the zoom level, i.e. the number of frames per pixel Chris@127: */ Chris@127: int getZoomLevel() const; Chris@127: Chris@127: /** Chris@127: * Set the zoom level, i.e. the number of frames per pixel. The Chris@127: * centre frame will be unchanged; the start and end frames will Chris@127: * change. Chris@127: */ Chris@806: virtual void setZoomLevel(int z); Chris@127: Chris@127: /** Chris@127: * Zoom in or out. Chris@127: */ Chris@127: virtual void zoom(bool in); Chris@127: Chris@127: /** Chris@127: * Scroll left or right by a smallish or largish amount. Chris@127: */ Chris@510: virtual void scroll(bool right, bool lots, bool doEmit = true); Chris@127: Chris@834: /** Chris@834: * Add a layer to the view. (Normally this should be handled Chris@834: * through some command abstraction instead of using this function Chris@834: * directly.) Chris@834: */ Chris@127: virtual void addLayer(Layer *v); Chris@834: Chris@834: /** Chris@834: * Remove a layer from the view. Does not delete the Chris@834: * layer. (Normally this should be handled through some command Chris@834: * abstraction instead of using this function directly.) Chris@834: */ Chris@834: virtual void removeLayer(Layer *v); Chris@834: Chris@834: /** Chris@834: * Return the number of layers, regardless of whether visible or Chris@834: * dormant, i.e. invisible, in this view. Chris@834: */ Chris@902: virtual int getLayerCount() const { return int(m_layerStack.size()); } Chris@127: Chris@127: /** Chris@834: * Return the nth layer, counted in stacking order. That is, Chris@834: * layer 0 is the bottom layer and layer "getLayerCount()-1" is Chris@834: * the top one. The returned layer may be visible or it may be Chris@834: * dormant, i.e. invisible. Chris@127: */ Chris@277: virtual Layer *getLayer(int n) { Chris@902: if (in_range_for(m_layerStack, n)) return m_layerStack[n]; Chris@835: else return 0; Chris@277: } Chris@127: Chris@127: /** Chris@835: * Return the nth layer, counted in the order they were Chris@835: * added. Unlike the stacking order used in getLayer(), which Chris@835: * changes each time a layer is selected, this ordering remains Chris@835: * fixed. The returned layer may be visible or it may be dormant, Chris@835: * i.e. invisible. Chris@268: */ Chris@835: virtual Layer *getFixedOrderLayer(int n) { Chris@835: if (n < int(m_fixedOrderLayers.size())) return m_fixedOrderLayers[n]; Chris@835: else return 0; Chris@268: } Chris@268: Chris@268: /** Chris@834: * Return the layer currently active for tool interaction. This is Chris@834: * the topmost non-dormant (i.e. visible) layer in the view. If Chris@834: * there are no visible layers in the view, return 0. Chris@834: */ Chris@834: virtual Layer *getInteractionLayer(); Chris@834: Chris@841: virtual const Layer *getInteractionLayer() const; Chris@841: Chris@834: /** Chris@835: * Return the layer most recently selected by the user. This is Chris@835: * the layer that any non-tool-driven commands should operate on, Chris@835: * in the case where this view is the "current" one. Chris@835: * Chris@835: * If the user has selected the view itself more recently than any Chris@835: * of the layers on it, this function will return 0, and any Chris@835: * non-tool-driven layer commands should be deactivated while this Chris@835: * view is current. It will also return 0 if there are no layers Chris@835: * in the view. Chris@834: * Chris@834: * Note that, unlike getInteractionLayer(), this could return an Chris@834: * invisible (dormant) layer. Chris@127: */ Chris@127: virtual Layer *getSelectedLayer(); Chris@834: Chris@127: virtual const Layer *getSelectedLayer() const; Chris@127: Chris@835: /** Chris@835: * Return the "top" layer in the view, whether visible or dormant. Chris@835: * This is the same as getLayer(getLayerCount()-1) if there is at Chris@835: * least one layer, and 0 otherwise. Chris@835: * Chris@835: * For most purposes involving interaction or commands, you Chris@835: * probably want either getInteractionLayer() or Chris@835: * getSelectedLayer() instead. Chris@835: */ Chris@835: virtual Layer *getTopLayer() { Chris@835: return m_layerStack.empty() ? 0 : m_layerStack[m_layerStack.size()-1]; Chris@835: } Chris@835: Chris@127: virtual void setViewManager(ViewManager *m); Chris@908: virtual void setViewManager(ViewManager *m, sv_frame_t initialFrame); Chris@127: virtual ViewManager *getViewManager() const { return m_manager; } Chris@127: Chris@127: virtual void setFollowGlobalPan(bool f); Chris@127: virtual bool getFollowGlobalPan() const { return m_followPan; } Chris@127: Chris@127: virtual void setFollowGlobalZoom(bool f); Chris@127: virtual bool getFollowGlobalZoom() const { return m_followZoom; } Chris@127: Chris@224: virtual bool hasLightBackground() const; Chris@287: virtual QColor getForeground() const; Chris@287: virtual QColor getBackground() const; Chris@127: Chris@270: virtual void drawMeasurementRect(QPainter &p, const Layer *, Chris@270: QRect rect, bool focus) const; Chris@127: Chris@741: virtual bool shouldShowFeatureLabels() const { Chris@741: return m_manager && m_manager->shouldShowFeatureLabels(); Chris@741: } Chris@127: virtual bool shouldIlluminateLocalFeatures(const Layer *, QPoint &) const { Chris@127: return false; Chris@127: } Chris@127: virtual bool shouldIlluminateLocalSelection(QPoint &, bool &, bool &) const { Chris@127: return false; Chris@127: } Chris@127: Chris@127: virtual void setPlaybackFollow(PlaybackFollowMode m); Chris@127: virtual PlaybackFollowMode getPlaybackFollow() const { return m_followPlay; } Chris@127: Chris@127: typedef PropertyContainer::PropertyName PropertyName; Chris@127: Chris@127: // We implement the PropertyContainer API, although we don't Chris@127: // actually subclass PropertyContainer. We have our own Chris@127: // PropertyContainer that we can return on request that just Chris@127: // delegates back to us. Chris@127: virtual PropertyContainer::PropertyList getProperties() const; Chris@127: virtual QString getPropertyLabel(const PropertyName &) const; Chris@127: virtual PropertyContainer::PropertyType getPropertyType(const PropertyName &) const; Chris@127: virtual int getPropertyRangeAndValue(const PropertyName &, Chris@216: int *min, int *max, int *deflt) const; Chris@127: virtual QString getPropertyValueLabel(const PropertyName &, Chris@127: int value) const; Chris@127: virtual void setProperty(const PropertyName &, int value); Chris@127: virtual QString getPropertyContainerName() const { Chris@127: return objectName(); Chris@127: } Chris@127: virtual QString getPropertyContainerIconName() const = 0; Chris@127: Chris@806: virtual int getPropertyContainerCount() const; Chris@127: Chris@837: // The 0th property container is the view's own; the rest are the Chris@837: // layers in fixed-order series Chris@806: virtual const PropertyContainer *getPropertyContainer(int i) const; Chris@806: virtual PropertyContainer *getPropertyContainer(int i); Chris@127: Chris@1202: /** Chris@1202: * Render the view contents to a new QImage (which may be wider Chris@1202: * than the visible View). Chris@1202: */ Chris@1202: virtual QImage *renderToNewImage(); Chris@226: Chris@1202: /** Chris@1202: * Render the view contents between the given frame extents to a Chris@1202: * new QImage (which may be wider than the visible View). Chris@1202: */ Chris@1202: virtual QImage *renderPartToNewImage(sv_frame_t f0, sv_frame_t f1); Chris@1202: Chris@1202: /** Chris@1202: * Calculate and return the size of image that will be generated Chris@1202: * by renderToNewImage(). Chris@1202: */ Chris@1202: virtual QSize getRenderedImageSize(); Chris@1202: Chris@1202: /** Chris@1202: * Calculate and return the size of image that will be generated Chris@1202: * by renderPartToNewImage(f0, f1). Chris@1202: */ Chris@1202: virtual QSize getRenderedPartImageSize(sv_frame_t f0, sv_frame_t f1); Chris@1202: Chris@1202: /** Chris@1202: * Render the view contents to a new SVG file. Chris@1202: */ Chris@1202: virtual bool renderToSvgFile(QString filename); Chris@1202: Chris@1202: /** Chris@1202: * Render the view contents between the given frame extents to a Chris@1202: * new SVG file. Chris@1202: */ Chris@1202: virtual bool renderPartToSvgFile(QString filename, Chris@1202: sv_frame_t f0, sv_frame_t f1); Chris@1202: Chris@127: virtual int getTextLabelHeight(const Layer *layer, QPainter &) const; Chris@127: Chris@904: virtual bool getValueExtents(QString unit, double &min, double &max, Chris@127: bool &log) const; Chris@127: Chris@316: virtual void toXml(QTextStream &stream, QString indent = "", Chris@316: QString extraAttributes = "") const; Chris@127: Chris@222: // First frame actually in model, to right of scale, if present Chris@902: virtual sv_frame_t getFirstVisibleFrame() const; Chris@902: virtual sv_frame_t getLastVisibleFrame() const; Chris@222: Chris@902: sv_frame_t getModelsStartFrame() const; Chris@902: sv_frame_t getModelsEndFrame() const; Chris@127: Chris@915: /** Chris@915: * To be called from a layer, to obtain the extent of the surface Chris@915: * that the layer is currently painting to. This may be the extent Chris@915: * of the view (if 1x display scaling is in effect) or of a larger Chris@915: * cached pixmap (if greater display scaling is in effect). Chris@915: */ Chris@915: QRect getPaintRect() const; Chris@915: Chris@915: QSize getPaintSize() const { return getPaintRect().size(); } Chris@915: int getPaintWidth() const { return getPaintRect().width(); } Chris@915: int getPaintHeight() const { return getPaintRect().height(); } Chris@915: Chris@315: typedef std::set ModelSet; Chris@315: ModelSet getModels(); Chris@315: Chris@301: //!!! Chris@320: Model *getAligningModel() const; Chris@902: sv_frame_t alignFromReference(sv_frame_t) const; Chris@902: sv_frame_t alignToReference(sv_frame_t) const; Chris@902: sv_frame_t getAlignedPlaybackFrame() const; Chris@301: Chris@1030: void updatePaintRect(QRect r) { update(r); } Chris@1030: Chris@918: View *getView() { return this; } Chris@918: const View *getView() const { return this; } Chris@918: Chris@127: signals: Chris@127: void propertyContainerAdded(PropertyContainer *pc); Chris@127: void propertyContainerRemoved(PropertyContainer *pc); Chris@127: void propertyContainerPropertyChanged(PropertyContainer *pc); Chris@197: void propertyContainerPropertyRangeChanged(PropertyContainer *pc); Chris@127: void propertyContainerNameChanged(PropertyContainer *pc); Chris@298: void propertyContainerSelected(PropertyContainer *pc); Chris@127: void propertyChanged(PropertyContainer::PropertyName); Chris@127: Chris@336: void layerModelChanged(); Chris@336: Chris@902: void centreFrameChanged(sv_frame_t frame, Chris@211: bool globalScroll, Chris@211: PlaybackFollowMode followMode); Chris@211: Chris@867: void zoomLevelChanged(int level, bool locked); Chris@127: Chris@189: void contextHelpChanged(const QString &); Chris@189: Chris@127: public slots: Chris@127: virtual void modelChanged(); Chris@902: virtual void modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame); Chris@127: virtual void modelCompletionChanged(); Chris@320: virtual void modelAlignmentCompletionChanged(); Chris@127: virtual void modelReplaced(); Chris@127: virtual void layerParametersChanged(); Chris@197: virtual void layerParameterRangesChanged(); Chris@268: virtual void layerMeasurementRectsChanged(); Chris@127: virtual void layerNameChanged(); Chris@127: Chris@902: virtual void globalCentreFrameChanged(sv_frame_t); Chris@902: virtual void viewCentreFrameChanged(View *, sv_frame_t); Chris@902: virtual void viewManagerPlaybackFrameChanged(sv_frame_t); Chris@806: virtual void viewZoomLevelChanged(View *, int, bool); Chris@127: Chris@127: virtual void propertyContainerSelected(View *, PropertyContainer *pc); Chris@127: Chris@127: virtual void selectionChanged(); Chris@127: virtual void toolModeChanged(); Chris@133: virtual void overlayModeChanged(); Chris@133: virtual void zoomWheelsEnabledChanged(); Chris@127: Chris@797: virtual void cancelClicked(); Chris@797: Chris@555: virtual void progressCheckStalledTimerElapsed(); Chris@555: Chris@127: protected: Chris@127: View(QWidget *, bool showProgress); Chris@1030: Chris@1030: int m_id; Chris@1030: Chris@127: virtual void paintEvent(QPaintEvent *e); Chris@127: virtual void drawSelections(QPainter &); Chris@127: virtual bool shouldLabelSelections() const { return true; } Chris@908: virtual bool render(QPainter &paint, int x0, sv_frame_t f0, sv_frame_t f1); Chris@339: virtual void setPaintFont(QPainter &paint); Chris@952: Chris@952: QSize scaledSize(const QSize &s, int factor) { Chris@952: return QSize(s.width() * factor, s.height() * factor); Chris@952: } Chris@952: QRect scaledRect(const QRect &r, int factor) { Chris@952: return QRect(r.x() * factor, r.y() * factor, Chris@952: r.width() * factor, r.height() * factor); Chris@952: } Chris@339: Chris@127: typedef std::vector LayerList; Chris@127: Chris@908: sv_samplerate_t getModelsSampleRate() const; Chris@127: bool areLayersScrollable() const; Chris@127: LayerList getScrollableBackLayers(bool testChanged, bool &changed) const; Chris@127: LayerList getNonScrollableFrontLayers(bool testChanged, bool &changed) const; Chris@806: int getZoomConstraintBlockSize(int blockSize, Chris@127: ZoomConstraint::RoundingDirection dir = Chris@127: ZoomConstraint::RoundNearest) const; Chris@127: Chris@183: // True if the top layer(s) use colours for meaningful things. If Chris@183: // this is the case, selections will be shown using unfilled boxes Chris@183: // rather than with a translucent fill. Chris@183: bool areLayerColoursSignificant() const; Chris@183: Chris@217: // True if the top layer has a time axis on the x coordinate (this Chris@217: // is generally the case except for spectrum/slice layers). It Chris@217: // will not be possible to make or display selections if this is Chris@217: // false. Chris@217: bool hasTopLayerTimeXAxis() const; Chris@217: Chris@902: bool setCentreFrame(sv_frame_t f, bool doEmit); Chris@127: Chris@902: void movePlayPointer(sv_frame_t f); Chris@511: Chris@127: void checkProgress(void *object); Chris@384: int getProgressBarWidth() const; // if visible Chris@127: Chris@956: int effectiveDevicePixelRatio() const; Chris@956: Chris@902: sv_frame_t m_centreFrame; Chris@127: int m_zoomLevel; Chris@127: bool m_followPan; Chris@127: bool m_followZoom; Chris@127: PlaybackFollowMode m_followPlay; Chris@789: bool m_followPlayIsDetached; Chris@902: sv_frame_t m_playPointerFrame; Chris@127: bool m_lightBackground; Chris@127: bool m_showProgress; Chris@127: Chris@1215: QPixmap *m_cache; // I own this Chris@1215: QPixmap *m_buffer; // I own this Chris@902: sv_frame_t m_cacheCentreFrame; Chris@127: int m_cacheZoomLevel; Chris@127: bool m_selectionCached; Chris@127: Chris@127: bool m_deleting; Chris@127: Chris@835: LayerList m_layerStack; // I don't own these, but see dtor note above Chris@835: LayerList m_fixedOrderLayers; Chris@127: bool m_haveSelectedLayer; Chris@127: Chris@583: QString m_lastError; Chris@583: Chris@127: // caches for use in getScrollableBackLayers, getNonScrollableFrontLayers Chris@127: mutable LayerList m_lastScrollableBackLayers; Chris@127: mutable LayerList m_lastNonScrollableBackLayers; Chris@127: Chris@555: struct ProgressBarRec { Chris@797: QPushButton *cancel; Chris@555: QProgressBar *bar; Chris@555: int lastCheck; Chris@555: QTimer *checkTimer; Chris@555: }; Chris@555: typedef std::map ProgressMap; Chris@127: ProgressMap m_progressBars; // I own the ProgressBars Chris@127: Chris@127: ViewManager *m_manager; // I don't own this Chris@127: ViewPropertyContainer *m_propertyContainer; // I own this Chris@127: }; Chris@127: Chris@127: Chris@127: // Use this for delegation, because we can't subclass from Chris@127: // PropertyContainer (which is a QObject) ourselves because of Chris@127: // ambiguity with QFrame parent Chris@127: Chris@127: class ViewPropertyContainer : public PropertyContainer Chris@127: { Chris@127: Q_OBJECT Chris@127: Chris@127: public: Chris@127: ViewPropertyContainer(View *v); Chris@728: virtual ~ViewPropertyContainer(); Chris@728: Chris@127: PropertyList getProperties() const { return m_v->getProperties(); } Chris@127: QString getPropertyLabel(const PropertyName &n) const { Chris@127: return m_v->getPropertyLabel(n); Chris@127: } Chris@127: PropertyType getPropertyType(const PropertyName &n) const { Chris@127: return m_v->getPropertyType(n); Chris@127: } Chris@216: int getPropertyRangeAndValue(const PropertyName &n, int *min, int *max, Chris@216: int *deflt) const { Chris@216: return m_v->getPropertyRangeAndValue(n, min, max, deflt); Chris@127: } Chris@127: QString getPropertyValueLabel(const PropertyName &n, int value) const { Chris@127: return m_v->getPropertyValueLabel(n, value); Chris@127: } Chris@127: QString getPropertyContainerName() const { Chris@127: return m_v->getPropertyContainerName(); Chris@127: } Chris@127: QString getPropertyContainerIconName() const { Chris@127: return m_v->getPropertyContainerIconName(); Chris@127: } Chris@127: Chris@127: public slots: Chris@127: virtual void setProperty(const PropertyName &n, int value) { Chris@127: m_v->setProperty(n, value); Chris@127: } Chris@127: Chris@127: protected: Chris@127: View *m_v; Chris@127: }; Chris@127: Chris@127: #endif Chris@127: