Mercurial > hg > svgui
view layer/TimeRulerLayer.cpp @ 561:aced8ec09bc8
* Complete the overhaul of CSV file import; now you can pick the purpose for
each column in the file, and SV should do the rest. The most significant
practical improvement here is that we can now handle files in which time
and duration do not necessarily appear in known columns.
author | Chris Cannam |
---|---|
date | Mon, 19 Jul 2010 17:08:56 +0000 |
parents | a6408c382616 |
children | f4960f8ce798 |
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. This file copyright 2006 Chris Cannam. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See the file COPYING included with this distribution for more information. */ #include "TimeRulerLayer.h" #include "LayerFactory.h" #include "data/model/Model.h" #include "base/RealTime.h" #include "ColourDatabase.h" #include "view/View.h" #include <QPainter> #include <iostream> #include <cmath> //#define DEBUG_TIME_RULER_LAYER 1 using std::cerr; using std::endl; TimeRulerLayer::TimeRulerLayer() : SingleColourLayer(), m_model(0), m_labelHeight(LabelTop) { } void TimeRulerLayer::setModel(Model *model) { if (m_model == model) return; m_model = model; emit modelReplaced(); } bool TimeRulerLayer::snapToFeatureFrame(View *v, int &frame, size_t &resolution, SnapType snap) const { if (!m_model) { resolution = 1; return false; } bool q; int tick = getMajorTickSpacing(v, q); RealTime rtick = RealTime::fromMilliseconds(tick); int rate = m_model->getSampleRate(); RealTime rt = RealTime::frame2RealTime(frame, rate); double ratio = rt / rtick; int rounded = int(ratio); RealTime rdrt = rtick * rounded; int left = RealTime::realTime2Frame(rdrt, rate); resolution = RealTime::realTime2Frame(rtick, rate); int right = left + resolution; // std::cerr << "TimeRulerLayer::snapToFeatureFrame: type " // << int(snap) << ", frame " << frame << " (time " // << rt << ", tick " << rtick << ", rounded " << rdrt << ") "; switch (snap) { case SnapLeft: frame = left; break; case SnapRight: frame = right; break; case SnapNearest: { if (abs(frame - left) > abs(right - frame)) { frame = right; } else { frame = left; } break; } case SnapNeighbouring: { int dl = -1, dr = -1; int x = v->getXForFrame(frame); if (left > v->getStartFrame() && left < v->getEndFrame()) { dl = abs(v->getXForFrame(left) - x); } if (right > v->getStartFrame() && right < v->getEndFrame()) { dr = abs(v->getXForFrame(right) - x); } int fuzz = 2; if (dl >= 0 && dr >= 0) { if (dl < dr) { if (dl <= fuzz) { frame = left; } } else { if (dr < fuzz) { frame = right; } } } else if (dl >= 0) { if (dl <= fuzz) { frame = left; } } else if (dr >= 0) { if (dr <= fuzz) { frame = right; } } } } // std::cerr << " -> " << frame << " (resolution = " << resolution << ")" << std::endl; return true; } int TimeRulerLayer::getMajorTickSpacing(View *v, bool &quarterTicks) const { // return value is in milliseconds if (!m_model || !v) return 1000; int sampleRate = m_model->getSampleRate(); if (!sampleRate) return 1000; long startFrame = v->getStartFrame(); long endFrame = v->getEndFrame(); int minPixelSpacing = 50; RealTime rtStart = RealTime::frame2RealTime(startFrame, sampleRate); RealTime rtEnd = RealTime::frame2RealTime(endFrame, sampleRate); int count = v->width() / minPixelSpacing; if (count < 1) count = 1; RealTime rtGap = (rtEnd - rtStart) / count; int incms; quarterTicks = false; if (rtGap.sec > 0) { incms = 1000; int s = rtGap.sec; if (s > 0) { incms *= 5; s /= 5; } if (s > 0) { incms *= 2; s /= 2; } if (s > 0) { incms *= 6; s /= 6; quarterTicks = true; } if (s > 0) { incms *= 5; s /= 5; quarterTicks = false; } if (s > 0) { incms *= 2; s /= 2; } if (s > 0) { incms *= 6; s /= 6; quarterTicks = true; } while (s > 0) { incms *= 10; s /= 10; quarterTicks = false; } } else { incms = 1; int ms = rtGap.msec(); if (ms > 0) { incms *= 10; ms /= 10; } if (ms > 0) { incms *= 10; ms /= 10; } if (ms > 0) { incms *= 5; ms /= 5; } if (ms > 0) { incms *= 2; ms /= 2; } } return incms; } void TimeRulerLayer::paint(View *v, QPainter &paint, QRect rect) const { #ifdef DEBUG_TIME_RULER_LAYER std::cerr << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y() << ") [" << rect.width() << "x" << rect.height() << "]" << std::endl; #endif if (!m_model || !m_model->isOK()) return; int sampleRate = m_model->getSampleRate(); if (!sampleRate) return; long startFrame = v->getFrameForX(rect.x() - 50); #ifdef DEBUG_TIME_RULER_LAYER std::cerr << "start frame = " << startFrame << std::endl; #endif bool quarter = false; int incms = getMajorTickSpacing(v, quarter); int ms = lrint(1000.0 * (double(startFrame) / double(sampleRate))); ms = (ms / incms) * incms - incms; #ifdef DEBUG_TIME_RULER_LAYER std::cerr << "start ms = " << ms << " at step " << incms << std::endl; #endif // Calculate the number of ticks per increment -- approximate // values for x and frame counts here will do, no rounding issue. // We always use the exact incms in our calculations for where to // draw the actual ticks or lines. int minPixelSpacing = 50; long incFrame = (incms * sampleRate) / 1000; int incX = incFrame / v->getZoomLevel(); int ticks = 10; if (incX < minPixelSpacing * 2) { ticks = quarter ? 4 : 5; } QColor greyColour = getPartialShades(v)[1]; paint.save(); while (1) { // frame is used to determine where to draw the lines, so it // needs to correspond to an exact pixel (so that we don't get // a different pixel when scrolling a small amount and // re-drawing with a different start frame). double dms = ms; long frame = lrint((dms * sampleRate) / 1000.0); frame /= v->getZoomLevel(); frame *= v->getZoomLevel(); // so frame corresponds to an exact pixel int x = v->getXForFrame(frame); #ifdef DEBUG_TIME_RULER_LAYER std::cerr << "Considering frame = " << frame << ", x = " << x << std::endl; #endif if (x >= rect.x() + rect.width() + 50) { #ifdef DEBUG_TIME_RULER_LAYER std::cerr << "X well out of range, ending here" << std::endl; #endif break; } if (x >= rect.x() - 50) { RealTime rt = RealTime::fromMilliseconds(ms); #ifdef DEBUG_TIME_RULER_LAYER std::cerr << "X in range, drawing line here for time " << rt.toText() << std::endl; #endif QString text(QString::fromStdString(rt.toText())); QFontMetrics metrics = paint.fontMetrics(); int tw = metrics.width(text); if (tw < 50 && (x < rect.x() - tw/2 || x >= rect.x() + rect.width() + tw/2)) { #ifdef DEBUG_TIME_RULER_LAYER std::cerr << "hm, maybe X isn't in range after all (x = " << x << ", tw = " << tw << ", rect.x() = " << rect.x() << ", rect.width() = " << rect.width() << ")" << std::endl; #endif } paint.setPen(greyColour); paint.drawLine(x, 0, x, v->height()); paint.setPen(getBaseQColor()); paint.drawLine(x, 0, x, 5); paint.drawLine(x, v->height() - 6, x, v->height() - 1); int y; switch (m_labelHeight) { default: case LabelTop: y = 6 + metrics.ascent(); break; case LabelMiddle: y = v->height() / 2 - metrics.height() / 2 + metrics.ascent(); break; case LabelBottom: y = v->height() - metrics.height() + metrics.ascent() - 6; } if (v->getViewManager() && v->getViewManager()->getOverlayMode() != ViewManager::NoOverlays) { if (v->getLayer(0) == this) { // backmost layer, don't worry about outlining the text paint.drawText(x+2 - tw/2, y, text); } else { v->drawVisibleText(paint, x+2 - tw/2, y, text, View::OutlinedText); } } } paint.setPen(greyColour); for (int i = 1; i < ticks; ++i) { dms = ms + (i * double(incms)) / ticks; frame = lrint((dms * sampleRate) / 1000.0); frame /= v->getZoomLevel(); frame *= v->getZoomLevel(); // exact pixel as above x = v->getXForFrame(frame); if (x < rect.x() || x >= rect.x() + rect.width()) { #ifdef DEBUG_TIME_RULER_LAYER // std::cerr << "tick " << i << ": X out of range, going on to next tick" << std::endl; #endif continue; } #ifdef DEBUG_TIME_RULER_LAYER std::cerr << "tick " << i << " in range, drawing at " << x << std::endl; #endif int sz = 5; if (ticks == 10) { if ((i % 2) == 1) { if (i == 5) { paint.drawLine(x, 0, x, v->height()); } else sz = 3; } else { sz = 7; } } paint.drawLine(x, 0, x, sz); paint.drawLine(x, v->height() - sz - 1, x, v->height() - 1); } ms += incms; } paint.restore(); } int TimeRulerLayer::getDefaultColourHint(bool darkbg, bool &impose) { impose = true; return ColourDatabase::getInstance()->getColourIndex (QString(darkbg ? "White" : "Black")); } QString TimeRulerLayer::getLayerPresentationName() const { LayerFactory *factory = LayerFactory::getInstance(); QString layerName = factory->getLayerPresentationName (factory->getLayerType(this)); return layerName; } void TimeRulerLayer::toXml(QTextStream &stream, QString indent, QString extraAttributes) const { SingleColourLayer::toXml(stream, indent, extraAttributes); } void TimeRulerLayer::setProperties(const QXmlAttributes &attributes) { SingleColourLayer::setProperties(attributes); }