Chris@173: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@173: 
Chris@173: /*
Chris@173:     Sonic Visualiser
Chris@173:     An audio file viewer and annotation editor.
Chris@173:     Centre for Digital Music, Queen Mary, University of London.
Chris@182:     This file copyright 2006 Chris Cannam and QMUL.
Chris@173:     
Chris@173:     This program is free software; you can redistribute it and/or
Chris@173:     modify it under the terms of the GNU General Public License as
Chris@173:     published by the Free Software Foundation; either version 2 of the
Chris@173:     License, or (at your option) any later version.  See the file
Chris@173:     COPYING included with this distribution for more information.
Chris@173: */
Chris@173: 
Chris@173: #include "Overview.h"
Chris@173: #include "layer/Layer.h"
Chris@173: #include "data/model/Model.h"
Chris@173: #include "base/ZoomConstraint.h"
Chris@173: 
Chris@173: #include <QPaintEvent>
Chris@173: #include <QPainter>
Chris@173: #include <iostream>
Chris@173: 
Chris@643: //#define DEBUG_OVERVIEW 1
Chris@642: 
Chris@682: 
Chris@173: Overview::Overview(QWidget *w) :
Chris@173:     View(w, false),
Chris@854:     m_clickedInRange(false),
Chris@854:     m_dragCentreFrame(0)
Chris@173: {
Chris@173:     setObjectName(tr("Overview"));
Chris@173:     m_followPan = false;
Chris@173:     m_followZoom = false;
Chris@211:     setPlaybackFollow(PlaybackIgnore);
Chris@274:     m_modelTestTime.start();
Chris@965: 
Chris@965:     bool light = hasLightBackground();
Chris@965:     if (light) m_boxColour = Qt::darkGray;
Chris@965:     else m_boxColour = Qt::lightGray;
Chris@173: }
Chris@173: 
Chris@173: void
Chris@908: Overview::modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame)
Chris@173: {
Chris@253:     bool zoomChanged = false;
Chris@253: 
Chris@908:     sv_frame_t frameCount = getModelsEndFrame() - getModelsStartFrame();
Chris@908:     int zoomLevel = int(frameCount / width());
Chris@253:     if (zoomLevel < 1) zoomLevel = 1;
Chris@253:     zoomLevel = getZoomConstraintBlockSize(zoomLevel,
Chris@253: 					   ZoomConstraint::RoundUp);
Chris@253:     if (zoomLevel != m_zoomLevel) {
Chris@253:         zoomChanged = true;
Chris@253:     }
Chris@253: 
Chris@253:     if (!zoomChanged) {
Chris@274:         if (m_modelTestTime.elapsed() < 1000) {
Chris@835:             for (LayerList::const_iterator i = m_layerStack.begin();
Chris@835:                  i != m_layerStack.end(); ++i) {
Chris@274:                 if ((*i)->getModel() &&
Chris@389:                     (!(*i)->getModel()->isOK() ||
Chris@389:                      !(*i)->getModel()->isReady())) {
Chris@274:                     return;
Chris@274:                 }
Chris@253:             }
Chris@274:         } else {
Chris@274:             m_modelTestTime.restart();
Chris@253:         }
Chris@253:     }
Chris@253: 
Chris@806:     View::modelChangedWithin(startFrame, endFrame);
Chris@173: }
Chris@173: 
Chris@173: void
Chris@173: Overview::modelReplaced()
Chris@173: {
Chris@339:     m_playPointerFrame = getAlignedPlaybackFrame();
Chris@173:     View::modelReplaced();
Chris@173: }
Chris@173: 
Chris@173: void
Chris@211: Overview::registerView(View *view)
Chris@173: {
Chris@211:     m_views.insert(view);
Chris@173:     update(); 
Chris@173: }
Chris@173: 
Chris@173: void
Chris@211: Overview::unregisterView(View *view)
Chris@173: {
Chris@211:     m_views.erase(view);
Chris@173:     update();
Chris@173: }
Chris@173: 
Chris@173: void
Chris@908: Overview::globalCentreFrameChanged(sv_frame_t 
Chris@806: #ifdef DEBUG_OVERVIEW
Chris@806:                                    f
Chris@806: #endif
Chris@806:     )
Chris@173: {
Chris@642: #ifdef DEBUG_OVERVIEW
Chris@682:     cerr << "Overview::globalCentreFrameChanged: " << f << endl;
Chris@642: #endif
Chris@211:     update();
Chris@211: }
Chris@173: 
Chris@211: void
Chris@908: Overview::viewCentreFrameChanged(View *v, sv_frame_t
Chris@806: #ifdef DEBUG_OVERVIEW
Chris@806:                                  f
Chris@806: #endif
Chris@806:     )
Chris@211: {
Chris@642: #ifdef DEBUG_OVERVIEW
Chris@682:     cerr << "Overview[" << this << "]::viewCentreFrameChanged(" << v << "): " << f << endl;
Chris@642: #endif
Chris@211:     if (m_views.find(v) != m_views.end()) {
Chris@173: 	update();
Chris@173:     }
Chris@211: }    
Chris@173: 
Chris@173: void
Chris@806: Overview::viewZoomLevelChanged(View *v, int, bool)
Chris@173: {
Chris@222:     if (v == this) return;
Chris@211:     if (m_views.find(v) != m_views.end()) {
Chris@173: 	update();
Chris@173:     }
Chris@173: }
Chris@173: 
Chris@173: void
Chris@908: Overview::viewManagerPlaybackFrameChanged(sv_frame_t f)
Chris@173: {
Chris@642: #ifdef DEBUG_OVERVIEW
Chris@682:     cerr << "Overview[" << this << "]::viewManagerPlaybackFrameChanged(" << f << "): " << f << endl;
Chris@642: #endif
Chris@642: 
Chris@173:     bool changed = false;
Chris@173: 
Chris@339:     f = getAlignedPlaybackFrame();
Chris@339: 
Chris@173:     if (getXForFrame(m_playPointerFrame) != getXForFrame(f)) changed = true;
Chris@173:     m_playPointerFrame = f;
Chris@173: 
Chris@173:     if (changed) update();
Chris@173: }
Chris@173: 
Chris@871: QColor
Chris@871: Overview::getFillWithin() const
Chris@871: {
Chris@871:     return Qt::transparent;
Chris@871: }
Chris@871: 
Chris@871: QColor
Chris@871: Overview::getFillWithout() const
Chris@871: {
Chris@871:     QColor c = palette().window().color();
Chris@871:     c.setAlpha(100);
Chris@871:     return c;
Chris@871: }
Chris@871: 
Chris@173: void
Chris@965: Overview::setBoxColour(QColor c)
Chris@965: {
Chris@965:     m_boxColour = c;
Chris@965: }
Chris@965: 
Chris@965: void
Chris@173: Overview::paintEvent(QPaintEvent *e)
Chris@173: {
Chris@173:     // Recalculate zoom in case the size of the widget has changed.
Chris@173: 
Chris@642: #ifdef DEBUG_OVERVIEW
Chris@682:     cerr << "Overview::paintEvent: width is " << width() << ", centre frame " << m_centreFrame << endl;
Chris@642: #endif
Chris@214: 
Chris@908:     sv_frame_t startFrame = getModelsStartFrame();
Chris@908:     sv_frame_t frameCount = getModelsEndFrame() - getModelsStartFrame();
Chris@908:     int zoomLevel = int(frameCount / width());
Chris@173:     if (zoomLevel < 1) zoomLevel = 1;
Chris@173:     zoomLevel = getZoomConstraintBlockSize(zoomLevel,
Chris@173: 					   ZoomConstraint::RoundUp);
Chris@173:     if (zoomLevel != m_zoomLevel) {
Chris@173: 	m_zoomLevel = zoomLevel;
Chris@222: 	emit zoomLevelChanged(m_zoomLevel, m_followZoom);
Chris@173:     }
Chris@253: 
Chris@908:     sv_frame_t centreFrame = startFrame + m_zoomLevel * (width() / 2);
Chris@173:     if (centreFrame > (startFrame + getModelsEndFrame())/2) {
Chris@173: 	centreFrame = (startFrame + getModelsEndFrame())/2;
Chris@173:     }
Chris@173:     if (centreFrame != m_centreFrame) {
Chris@642: #ifdef DEBUG_OVERVIEW
Chris@682:         cerr << "Overview::paintEvent: Centre frame changed from "
Chris@642:                   << m_centreFrame << " to " << centreFrame << " and thus start frame from " << getStartFrame();
Chris@642: #endif
Chris@173: 	m_centreFrame = centreFrame;
Chris@642: #ifdef DEBUG_OVERVIEW
Chris@682:         cerr << " to " << getStartFrame() << endl;
Chris@642: #endif
Chris@211: 	emit centreFrameChanged(m_centreFrame, false, PlaybackIgnore);
Chris@173:     }
Chris@173: 
Chris@173:     View::paintEvent(e);
Chris@173: 
Chris@173:     QPainter paint;
Chris@173:     paint.begin(this);
Chris@871:     paint.setClipRegion(e->region());
Chris@871:     paint.setRenderHints(QPainter::Antialiasing);
Chris@871:     
Chris@173:     QRect r(rect());
Chris@173: 
Chris@871:     // We paint a rounded rect for each distinct set of view extents,
Chris@871:     // and we colour in the inside and outside of the rect that
Chris@871:     // corresponds to the current view. (One small caveat -- we don't
Chris@871:     // know which rect that is yet. We'll have to figure it out
Chris@871:     // somehow...)
Chris@173: 
Chris@871:     std::set<std::pair<int, int> > extents;
Chris@871:     std::vector<QRect> rects;
Chris@871:     QRect primary;
Chris@173: 
Chris@173:     int y = 0;
Chris@173: 
Chris@211:     for (ViewSet::iterator i = m_views.begin(); i != m_views.end(); ++i) {
Chris@173: 	if (!*i) continue;
Chris@173: 
Chris@173: 	View *w = (View *)*i;
Chris@173: 
Chris@908: 	sv_frame_t f0 = w->getFrameForX(0);
Chris@908: 	sv_frame_t f1 = w->getFrameForX(w->width());
Chris@173: 
Chris@339:         if (f0 >= 0) {
Chris@908:             sv_frame_t rf0 = w->alignToReference(f0);
Chris@339:             f0 = alignFromReference(rf0);
Chris@339:         }
Chris@339:         if (f1 >= 0) {
Chris@908:             sv_frame_t rf1 = w->alignToReference(f1);
Chris@339:             f1 = alignFromReference(rf1);
Chris@339:         }
Chris@339: 
Chris@173: 	int x0 = getXForFrame(f0);
Chris@173: 	int x1 = getXForFrame(f1);
Chris@173: 
matthiasm@935: 
Chris@871: 	if (x1 <= x0) x1 = x0 + 1;
Chris@871: 
Chris@871:         std::pair<int, int> extent(x0, x1);
Chris@871: 
Chris@871:         if (extents.find(extent) == extents.end()) {
Chris@871: 
matthiasm@935:     	    y += height() / 10 + 1;
Chris@871:             extents.insert(extent);
Chris@173: 
Chris@871:             QRect vr(x0, y, x1 - x0, height() - 2 * y);
Chris@871:             rects.push_back(vr);
Chris@871:             primary = vr; //!!! for now
Chris@871:         }
Chris@871:     }
Chris@871: 
Chris@871:     QPainterPath without;
matthiasm@935:     without.addRoundedRect(primary, 4, 4);
Chris@871:     without.addRect(rect());
Chris@871:     paint.setPen(Qt::NoPen);
Chris@871:     paint.setBrush(getFillWithout());
Chris@871:     paint.drawPath(without);
Chris@871: 
Chris@871:     paint.setBrush(getFillWithin());
matthiasm@935:     paint.drawRoundedRect(primary, 4, 4);
Chris@871:     
Chris@871:     foreach (QRect vr, rects) {
Chris@871:         paint.setBrush(Qt::NoBrush);
Chris@965:         paint.setPen(QPen(m_boxColour, 2));
matthiasm@935:         paint.drawRoundedRect(vr, 4, 4);
Chris@173:     }
Chris@173: 
Chris@173:     paint.end();
Chris@173: }
Chris@173: 
Chris@173: void
Chris@173: Overview::mousePressEvent(QMouseEvent *e)
Chris@173: {
Chris@173:     m_clickPos = e->pos();
Chris@908:     sv_frame_t clickFrame = getFrameForX(m_clickPos.x());
Chris@339:     if (clickFrame > 0) m_dragCentreFrame = clickFrame;
Chris@339:     else m_dragCentreFrame = 0;
Chris@339:     m_clickedInRange = true;
Chris@339: 
Chris@211:     for (ViewSet::iterator i = m_views.begin(); i != m_views.end(); ++i) {
Chris@339: 	if (*i && (*i)->getAligningModel() == getAligningModel()) {
Chris@339:             m_dragCentreFrame = (*i)->getCentreFrame();
Chris@339:             break;
Chris@339:         }
Chris@173:     }
Chris@173: }
Chris@173: 
Chris@173: void
Chris@173: Overview::mouseReleaseEvent(QMouseEvent *e)
Chris@173: {
Chris@173:     if (m_clickedInRange) {
Chris@173: 	mouseMoveEvent(e);
Chris@173:     }
Chris@173:     m_clickedInRange = false;
Chris@173: }
Chris@173: 
Chris@173: void
Chris@173: Overview::mouseMoveEvent(QMouseEvent *e)
Chris@173: {
Chris@173:     if (!m_clickedInRange) return;
Chris@173: 
Chris@806:     int xoff = int(e->x()) - int(m_clickPos.x());
Chris@908:     sv_frame_t frameOff = xoff * m_zoomLevel;
Chris@173:     
Chris@908:     sv_frame_t newCentreFrame = m_dragCentreFrame;
Chris@173:     if (frameOff > 0) {
Chris@173: 	newCentreFrame += frameOff;
Chris@908:     } else if (newCentreFrame >= -frameOff) {
Chris@173: 	newCentreFrame += frameOff;
Chris@173:     } else {
Chris@173: 	newCentreFrame = 0;
Chris@173:     }
Chris@173: 
Chris@173:     if (newCentreFrame >= getModelsEndFrame()) {
Chris@173: 	newCentreFrame = getModelsEndFrame();
Chris@173: 	if (newCentreFrame > 0) --newCentreFrame;
Chris@173:     }
Chris@173:     
Chris@173:     if (std::max(m_centreFrame, newCentreFrame) -
Chris@908: 	std::min(m_centreFrame, newCentreFrame) > m_zoomLevel) {
Chris@908:         sv_frame_t rf = alignToReference(newCentreFrame);
Chris@643: #ifdef DEBUG_OVERVIEW
Chris@682:         cerr << "Overview::mouseMoveEvent: x " << e->x() << " and click x " << m_clickPos.x() << " -> frame " << newCentreFrame << " -> rf " << rf << endl;
Chris@643: #endif
Chris@817:         if (m_followPlay == PlaybackScrollContinuous ||
Chris@817:             m_followPlay == PlaybackScrollPageWithCentre) {
Chris@817:             emit centreFrameChanged(rf, true, PlaybackScrollContinuous);
Chris@817:         } else {
Chris@817:             emit centreFrameChanged(rf, true, PlaybackIgnore);
Chris@817:         }            
Chris@173:     }
Chris@173: }
Chris@173: 
Chris@189: void
Chris@189: Overview::mouseDoubleClickEvent(QMouseEvent *e)
Chris@189: {
Chris@908:     sv_frame_t frame = getFrameForX(e->x());
Chris@908:     sv_frame_t rf = 0;
Chris@339:     if (frame > 0) rf = alignToReference(frame);
Chris@643: #ifdef DEBUG_OVERVIEW
Chris@682:     cerr << "Overview::mouseDoubleClickEvent: frame " << frame << " -> rf " << rf << endl;
Chris@643: #endif
Chris@643:     m_clickedInRange = false; // we're not starting a drag with the second click
Chris@339:     emit centreFrameChanged(rf, true, PlaybackScrollContinuous);
Chris@189: }
Chris@173: 
Chris@189: void
Chris@189: Overview::enterEvent(QEvent *)
Chris@189: {
Chris@189:     emit contextHelpChanged(tr("Click and drag to navigate; double-click to jump"));
Chris@189: }
Chris@189: 
Chris@189: void
Chris@189: Overview::leaveEvent(QEvent *)
Chris@189: {
Chris@189:     emit contextHelpChanged("");
Chris@189: }
Chris@189: 
Chris@189: