Chris@867: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@867: 
Chris@867: /*
Chris@867:     Sonic Visualiser
Chris@867:     An audio file viewer and annotation editor.
Chris@867:     Centre for Digital Music, Queen Mary, University of London.
Chris@867:     This file copyright 2006-2014 Chris Cannam and QMUL.
Chris@867:     
Chris@867:     This program is free software; you can redistribute it and/or
Chris@867:     modify it under the terms of the GNU General Public License as
Chris@867:     published by the Free Software Foundation; either version 2 of the
Chris@867:     License, or (at your option) any later version.  See the file
Chris@867:     COPYING included with this distribution for more information.
Chris@867: */
Chris@867: 
Chris@867: #include "AlignmentView.h"
Chris@867: 
Chris@867: #include <QPainter>
Chris@867: 
Chris@868: #include "data/model/SparseOneDimensionalModel.h"
Chris@868: 
Chris@868: #include "layer/TimeInstantLayer.h"
Chris@868: 
Chris@1493: //#define DEBUG_ALIGNMENT_VIEW 1
Chris@1493: 
Chris@867: using std::vector;
Chris@1493: using std::set;
Chris@867: 
Chris@867: AlignmentView::AlignmentView(QWidget *w) :
Chris@867:     View(w, false),
Chris@1408:     m_above(nullptr),
Chris@1408:     m_below(nullptr)
Chris@867: {
Chris@867:     setObjectName(tr("AlignmentView"));
Chris@867: }
Chris@867: 
Chris@867: void
Chris@1493: AlignmentView::keyFramesChanged()
Chris@1493: {
Chris@1493: #ifdef DEBUG_ALIGNMENT_VIEW
Chris@1493:     SVCERR << "AlignmentView " << getId() << "::keyFramesChanged" << endl;
Chris@1493: #endif
Chris@1493:     
Chris@1493:     // This is just a notification that we need to rebuild it - so all
Chris@1493:     // we do here is clear it, and it'll be rebuilt on demand later
Chris@1493:     QMutexLocker locker(&m_keyFrameMutex);
Chris@1493:     m_keyFrameMap.clear();
Chris@1493: }
Chris@1493: 
Chris@1493: void
Chris@976: AlignmentView::globalCentreFrameChanged(sv_frame_t f)
Chris@867: {
Chris@867:     View::globalCentreFrameChanged(f);
Chris@867:     update();
Chris@867: }
Chris@867: 
Chris@867: void
Chris@976: AlignmentView::viewCentreFrameChanged(View *v, sv_frame_t f)
Chris@867: {
Chris@867:     View::viewCentreFrameChanged(v, f);
Chris@867:     if (v == m_above) {
Chris@1266:         m_centreFrame = f;
Chris@1266:         update();
Chris@867:     } else if (v == m_below) {
Chris@1266:         update();
Chris@867:     }
Chris@867: }
Chris@867: 
Chris@867: void
Chris@976: AlignmentView::viewManagerPlaybackFrameChanged(sv_frame_t)
Chris@867: {
Chris@867:     update();
Chris@867: }
Chris@867: 
Chris@867: void
Chris@1183: AlignmentView::viewAboveZoomLevelChanged(ZoomLevel level, bool)
Chris@867: {
Chris@867:     m_zoomLevel = level;
Chris@867:     update();
Chris@867: }
Chris@867: 
Chris@867: void
Chris@1183: AlignmentView::viewBelowZoomLevelChanged(ZoomLevel, bool)
Chris@867: {
Chris@867:     update();
Chris@867: }
Chris@867: 
Chris@867: void
Chris@867: AlignmentView::setViewAbove(View *v)
Chris@867: {
Chris@867:     if (m_above) {
Chris@1408:         disconnect(m_above, nullptr, this, nullptr);
Chris@867:     }
Chris@867: 
Chris@867:     m_above = v;
Chris@867: 
Chris@867:     if (m_above) {
Chris@1266:         connect(m_above,
Chris@1183: 		SIGNAL(zoomLevelChanged(ZoomLevel, bool)),
Chris@1266:                 this, 
Chris@1183: 		SLOT(viewAboveZoomLevelChanged(ZoomLevel, bool)));
Chris@1493:         connect(m_above,
Chris@1493:                 SIGNAL(propertyContainerAdded(PropertyContainer *)),
Chris@1493:                 this,
Chris@1493:                 SLOT(keyFramesChanged()));
Chris@1493:         connect(m_above,
Chris@1493:                 SIGNAL(layerModelChanged()),
Chris@1493:                 this,
Chris@1493:                 SLOT(keyFramesChanged()));
Chris@867:     }
Chris@1493: 
Chris@1493:     keyFramesChanged();
Chris@867: }
Chris@867: 
Chris@867: void
Chris@867: AlignmentView::setViewBelow(View *v)
Chris@867: {
Chris@867:     if (m_below) {
Chris@1408:         disconnect(m_below, nullptr, this, nullptr);
Chris@867:     }
Chris@867: 
Chris@867:     m_below = v;
Chris@867: 
Chris@867:     if (m_below) {
Chris@1266:         connect(m_below,
Chris@1183: 		SIGNAL(zoomLevelChanged(ZoomLevel, bool)),
Chris@1266:                 this, 
Chris@1183: 		SLOT(viewBelowZoomLevelChanged(ZoomLevel, bool)));
Chris@1493:         connect(m_below,
Chris@1493:                 SIGNAL(propertyContainerAdded(PropertyContainer *)),
Chris@1493:                 this,
Chris@1493:                 SLOT(keyFramesChanged()));
Chris@1493:         connect(m_below,
Chris@1493:                 SIGNAL(layerModelChanged()),
Chris@1493:                 this,
Chris@1493:                 SLOT(keyFramesChanged()));
Chris@867:     }
Chris@1493: 
Chris@1493:     keyFramesChanged();
Chris@867: }
Chris@867: 
Chris@867: void
Chris@867: AlignmentView::paintEvent(QPaintEvent *)
Chris@867: {
Chris@1408:     if (m_above == nullptr || m_below == nullptr || !m_manager) return;
Chris@1493:     
Chris@1493: #ifdef DEBUG_ALIGNMENT_VIEW
Chris@1493:     SVCERR << "AlignmentView " << getId() << "::paintEvent" << endl;
Chris@1493: #endif
Chris@1493:     
Chris@867:     bool darkPalette = false;
Chris@867:     if (m_manager) darkPalette = m_manager->getGlobalDarkBackground();
Chris@867: 
Chris@881:     QColor fg, bg;
Chris@881:     if (darkPalette) {
Chris@881:         fg = Qt::gray;
Chris@881:         bg = Qt::black;
Chris@881:     } else {
Chris@881:         fg = Qt::black;
Chris@881:         bg = Qt::gray;
Chris@881:     }
Chris@867: 
Chris@867:     QPainter paint(this);
Chris@867:     paint.setPen(QPen(fg, 2));
Chris@867:     paint.setBrush(Qt::NoBrush);
Chris@867:     paint.setRenderHint(QPainter::Antialiasing, true);
Chris@867: 
Chris@867:     paint.fillRect(rect(), bg);
Chris@867: 
Chris@1493:     QMutexLocker locker(&m_keyFrameMutex);
Chris@867: 
Chris@1493:     if (m_keyFrameMap.empty()) {
Chris@1493:         reconnectModels();
Chris@1493:         buildKeyFrameMap();
Chris@1493:     }
Chris@1493: 
Chris@1493: #ifdef DEBUG_ALIGNMENT_VIEW
Chris@1493:     SVCERR << "AlignmentView " << getId() << "::paintEvent: painting "
Chris@1493:            << m_keyFrameMap.size() << " mappings" << endl;
Chris@1493: #endif
Chris@1493: 
Chris@1493:     for (const auto &km: m_keyFrameMap) {
Chris@1493: 
Chris@1493:         sv_frame_t af = km.first;
Chris@1493:         sv_frame_t bf = km.second;
Chris@1493: 
Chris@1493:         int ax = m_above->getXForFrame(af);
Chris@1266:         int bx = m_below->getXForFrame(bf);
Chris@1493: 
Chris@1493:         if (ax >= 0 || ax < width() || bx >= 0 || bx < width()) {
Chris@1493:             paint.drawLine(ax, 0, bx, height());
Chris@1493:         }
Chris@867:     }
Chris@867: 
Chris@867:     paint.end();
Chris@1493: }        
Chris@867: 
Chris@1493: void
Chris@1493: AlignmentView::reconnectModels()
Chris@868: {
Chris@1493:     vector<ModelId> toConnect { 
Chris@1493:         getSalientModel(m_above),
Chris@1493:         getSalientModel(m_below)
Chris@1493:     };
Chris@868: 
Chris@1493:     for (auto modelId: toConnect) {
Chris@1493:         if (auto model = ModelById::get(modelId)) {
Chris@1493:             auto referenceId = model->getAlignmentReference();
Chris@1493:             if (!referenceId.isNone()) {
Chris@1493:                 toConnect.push_back(referenceId);
Chris@1475:             }
Chris@1266:         }
Chris@868:     }
Chris@868: 
Chris@1493:     for (auto modelId: toConnect) {
Chris@1493:         if (auto model = ModelById::get(modelId)) {
Chris@1493:             auto ptr = model.get();
Chris@1493:             disconnect(ptr, 0, this, 0);
Chris@1493:             connect(ptr, SIGNAL(modelChanged(ModelId)),
Chris@1493:                     this, SLOT(keyFramesChanged()));
Chris@1493:             connect(ptr, SIGNAL(completionChanged(ModelId)),
Chris@1493:                     this, SLOT(keyFramesChanged()));
Chris@1493:             connect(ptr, SIGNAL(alignmentCompletionChanged(ModelId)),
Chris@1493:                     this, SLOT(keyFramesChanged()));
Chris@1493:         }
Chris@1493:     }
Chris@1493: }
Chris@1493: 
Chris@1493: void
Chris@1493: AlignmentView::buildKeyFrameMap()
Chris@1493: {
Chris@1493: #ifdef DEBUG_ALIGNMENT_VIEW
Chris@1493:     SVCERR << "AlignmentView " << getId() << "::buildKeyFrameMap" << endl;
Chris@1493: #endif
Chris@1493:     
Chris@1493:     sv_frame_t resolution = 1;
Chris@1493: 
Chris@1493:     set<sv_frame_t> keyFramesBelow;
Chris@1493:     for (auto f: getKeyFrames(m_below, resolution)) {
Chris@1493:         keyFramesBelow.insert(f);
Chris@1493:     }
Chris@1493: 
Chris@1493:     vector<sv_frame_t> keyFrames = getKeyFrames(m_above, resolution);
Chris@1493: 
Chris@1493:     foreach (sv_frame_t f, keyFrames) {
Chris@1493: 
Chris@1493:         sv_frame_t rf = m_above->alignToReference(f);
Chris@1493:         sv_frame_t bf = m_below->alignFromReference(rf);
Chris@1493: 
Chris@1493:         bool mappedSomething = false;
Chris@1493:         
Chris@1493:         if (resolution > 1) {
Chris@1493:             if (keyFramesBelow.find(bf) == keyFramesBelow.end()) {
Chris@1493: 
Chris@1493:                 sv_frame_t f1 = f + resolution;
Chris@1493:                 sv_frame_t rf1 = m_above->alignToReference(f1);
Chris@1493:                 sv_frame_t bf1 = m_below->alignFromReference(rf1);
Chris@1493: 
Chris@1493:                 for (sv_frame_t probe = bf + 1; probe <= bf1; ++probe) {
Chris@1493:                     if (keyFramesBelow.find(probe) != keyFramesBelow.end()) {
Chris@1493:                         m_keyFrameMap.insert({ f, probe });
Chris@1493:                         mappedSomething = true;
Chris@1493:                     }
Chris@1493:                 }
Chris@1493:             }
Chris@1493:         }
Chris@1493: 
Chris@1493:         if (!mappedSomething) {
Chris@1493:             m_keyFrameMap.insert({ f, bf });
Chris@1493:         }
Chris@1493:     }
Chris@1493: 
Chris@1493: #ifdef DEBUG_ALIGNMENT_VIEW
Chris@1493:     SVCERR << "AlignmentView " << getId() << "::buildKeyFrameMap: have "
Chris@1493:            << m_keyFrameMap.size() << " mappings" << endl;
Chris@1493: #endif
Chris@1493: }
Chris@1493: 
Chris@1493: vector<sv_frame_t>
Chris@1493: AlignmentView::getKeyFrames(View *view, sv_frame_t &resolution)
Chris@1493: {
Chris@1493:     resolution = 1;
Chris@1493:     
Chris@1493:     if (!view) {
Chris@1493:         return getDefaultKeyFrames();
Chris@1493:     }
Chris@1493: 
Chris@1493:     ModelId m = getSalientModel(view);
Chris@1475:     auto model = ModelById::getAs<SparseOneDimensionalModel>(m);
Chris@1475:     if (!model) {
Chris@1266:         return getDefaultKeyFrames();
Chris@868:     }
Chris@868: 
Chris@1493:     resolution = model->getResolution();
Chris@1493:     
Chris@976:     vector<sv_frame_t> keyFrames;
Chris@868: 
Chris@1475:     EventVector pp = model->getAllEvents();
Chris@1433:     for (EventVector::const_iterator pi = pp.begin(); pi != pp.end(); ++pi) {
Chris@1433:         keyFrames.push_back(pi->getFrame());
Chris@868:     }
Chris@868: 
Chris@868:     return keyFrames;
Chris@868: }
Chris@868: 
Chris@976: vector<sv_frame_t>
Chris@868: AlignmentView::getDefaultKeyFrames()
Chris@868: {
Chris@976:     vector<sv_frame_t> keyFrames;
Chris@1509:     return keyFrames;
Chris@868: 
Chris@1509: #ifdef NOT_REALLY
Chris@868:     if (!m_above || !m_manager) return keyFrames;
Chris@868: 
Chris@976:     sv_samplerate_t rate = m_manager->getMainModelSampleRate();
Chris@868:     if (rate == 0) return keyFrames;
Chris@868: 
Chris@976:     for (sv_frame_t f = m_above->getModelsStartFrame(); 
Chris@1266:          f <= m_above->getModelsEndFrame(); 
Chris@1266:          f += sv_frame_t(rate * 5 + 0.5)) {
Chris@1266:         keyFrames.push_back(f);
Chris@868:     }
Chris@868:     
Chris@868:     return keyFrames;
Chris@1509: #endif
Chris@868: }
Chris@868: 
Chris@1493: ModelId
Chris@1493: AlignmentView::getSalientModel(View *view)
Chris@1493: {
Chris@1493:     ModelId m;
Chris@1493:     
Chris@1493:     // get the topmost such
Chris@1493:     for (int i = 0; i < view->getLayerCount(); ++i) {
Chris@1493:         if (qobject_cast<TimeInstantLayer *>(view->getLayer(i))) {
Chris@1493:             ModelId mm = view->getLayer(i)->getModel();
Chris@1493:             if (ModelById::isa<SparseOneDimensionalModel>(mm)) {
Chris@1493:                 m = mm;
Chris@1493:             }
Chris@1493:         }
Chris@1493:     }
Chris@1493: 
Chris@1493:     return m;
Chris@1493: }
Chris@1493: 
Chris@1493: