Mercurial > hg > svcore
view base/View.cpp @ 3:581f67f370f3
* Beginnings of session save code
* Add spline curve mode to time value layer
author | Chris Cannam |
---|---|
date | Thu, 12 Jan 2006 17:19:08 +0000 |
parents | a23739e2338a |
children | 149bb02a41ba |
line wrap: on
line source
/* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */ /* A waveform viewer and audio annotation editor. Chris Cannam, Queen Mary University of London, 2005-2006 This is experimental software. Not for distribution. */ #include "base/View.h" #include "base/ViewManager.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 <QPainter> #include <QPaintEvent> #include <QRect> #include <QApplication> #include <iostream> //#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_newModel(true), 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_deleting(false), m_manager(0) { // QWidget::setAttribute(Qt::WA_PaintOnScreen); } View::~View() { m_deleting = true; for (LayerList::iterator i = m_layers.begin(); i != m_layers.end(); ++i) { delete *i; } } PropertyContainer::PropertyList View::getProperties() const { PropertyList list; list.push_back(tr("Global Scroll")); list.push_back(tr("Global Zoom")); list.push_back(tr("Follow Playback")); return list; } PropertyContainer::PropertyType View::getPropertyType(const PropertyName &name) const { if (name == tr("Global Scroll")) return ToggleProperty; if (name == tr("Global Zoom")) return ToggleProperty; if (name == tr("Follow Playback")) return ValueProperty; return InvalidProperty; } int View::getPropertyRangeAndValue(const PropertyName &name, int *min, int *max) const { if (name == tr("Global Scroll")) return m_followPan; if (name == tr("Global Zoom")) return m_followZoom; if (name == tr("Follow Playback")) { *min = 0; *max = 2; return int(m_followPlay); } return PropertyContainer::getPropertyRangeAndValue(name, min, max); } QString View::getPropertyValueLabel(const PropertyName &name, int value) const { if (name == tr("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 PropertyName &name, int value) { if (name == tr("Global Scroll")) { setFollowGlobalPan(value != 0); } else if (name == tr("Global Zoom")) { setFollowGlobalZoom(value != 0); } else if (name == tr("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 this; return m_layers[i-1]; } void View::propertyContainerSelected(PropertyContainer *pc) { if (pc == this) 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_layers.push_back(selectedLayer); update(); } } 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 getStartFrame() + (width() * m_zoomLevel) - 1; } void View::setStartFrame(long f) { setCentreFrame(f + m_zoomLevel * (width() / 2)); } void View::setCentreFrame(size_t f, bool e) { 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(); } if (e) emit centreFrameChanged(this, f, m_followPan); } } void View::setZoomLevel(size_t z) { if (m_zoomLevel != int(z)) { m_zoomLevel = z; emit zoomLevelChanged(this, z, m_followZoom); update(); } } 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())); m_newModel = true; 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); } 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))); } 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(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))); } void View::setFollowGlobalPan(bool f) { m_followPan = f; emit propertyContainerPropertyChanged(this); } void View::setFollowGlobalZoom(bool f) { m_followZoom = f; emit propertyContainerPropertyChanged(this); } void View::setPlaybackFollow(PlaybackFollowMode m) { m_followPlay = m; emit propertyContainerPropertyChanged(this); } void View::modelChanged() { QObject *obj = sender(); #ifdef DEBUG_VIEW_WIDGET_PAINT std::cerr << "View(" << this << ")::modelChanged()" << std::endl; #endif 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; } delete m_cache; m_cache = 0; if (long(startFrame) < myStartFrame) startFrame = myStartFrame; if (endFrame > myEndFrame) endFrame = myEndFrame; int x0 = (startFrame - myStartFrame) / m_zoomLevel; int x1 = (endFrame - myStartFrame) / m_zoomLevel; if (x1 < x0) return; checkProgress(obj); 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; m_newModel = true; 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 = (m_playPointerFrame / m_zoomLevel != f / m_zoomLevel); 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: { long w = width() * getZoomLevel(); w -= w/5; long sf = (f / w) * w - w/8; #ifdef DEBUG_VIEW_WIDGET_PAINT std::cerr << "PlaybackScrollPage: f = " << f << ", sf = " << sf << ", start frame " << getStartFrame() << std::endl; #endif setCentreFrame(sf + width() * getZoomLevel() / 2, false); int xold = (long(oldPlayPointerFrame) - getStartFrame()) / m_zoomLevel; int xnew = (long(m_playPointerFrame) - getStartFrame()) / m_zoomLevel; 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(); } } } 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()) return false; } return true; } View::LayerList View::getScrollableBackLayers(bool &changed) const { changed = false; LayerList scrollables; for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) { if ((*i)->isLayerScrollable()) scrollables.push_back(*i); else { if (scrollables != m_lastScrollableBackLayers) { m_lastScrollableBackLayers = scrollables; changed = true; } return scrollables; } } if (scrollables.size() == 1 && dynamic_cast<TimeRulerLayer *>(*scrollables.begin())) { // If only the ruler is scrollable, it's not worth the bother // -- it probably redraws as quickly as it refreshes from // cache scrollables.clear(); } if (scrollables != m_lastScrollableBackLayers) { m_lastScrollableBackLayers = scrollables; changed = true; } return scrollables; } View::LayerList View::getNonScrollableFrontLayers(bool &changed) const { changed = false; LayerList scrollables = getScrollableBackLayers(changed); LayerList nonScrollables; // Everything in front of the first non-scrollable from the back // should also be considered non-scrollable size_t count = 0; for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) { if (count < scrollables.size()) { ++count; continue; } nonScrollables.push_back(*i); } if (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; for (LayerList::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) { if ((*i)->getZoomConstraint()) { size_t thisBlockSize = (*i)->getZoomConstraint()->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::checkProgress(void *object) { // std::cerr << "View::checkProgress(" << object << ")" << std::endl; 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(); 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::identifyLocalFeatures(bool on, int x, int y) { for (LayerList::const_iterator i = m_layers.end(); i != m_layers.begin(); ) { --i; #ifdef DEBUG_VIEW_WIDGET_PAINT std::cerr << "View::identifyLocalFeatures: calling on " << *i << std::endl; #endif if ((*i)->identifyLocalFeatures(on, x, y)) break; } } */ void View::paintEvent(QPaintEvent *e) { // Profiler prof("View::paintEvent", true); // std::cerr << "View::paintEvent" << std::endl; if (m_layers.empty()) { QFrame::paintEvent(e); return; } if (m_newModel) { m_newModel = false; } // 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(layersChanged); LayerList nonScrollables = getNonScrollableFrontLayers(layersChanged); #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; #endif if (layersChanged || scrollables.empty()) { delete m_cache; m_cache = 0; } 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 = long(m_cacheCentreFrame / m_zoomLevel) - long(m_centreFrame / m_zoomLevel); 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) { (*i)->paint(paint, cacheRect); } 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) { (*i)->paint(paint, nonCacheRect); } paint.end(); if (m_followPlay != PlaybackScrollContinuous) { paint.begin(this); if (long(m_playPointerFrame) > getStartFrame() && m_playPointerFrame < getEndFrame()) { int playx = (long(m_playPointerFrame) - getStartFrame()) / m_zoomLevel; 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); } 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>"; return s; } #ifdef INCLUDE_MOCFILES #include "View.moc.cpp" #endif