svgui  1.9
TimeRulerLayer.cpp
Go to the documentation of this file.
00001 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
00002 
00003 /*
00004     Sonic Visualiser
00005     An audio file viewer and annotation editor.
00006     Centre for Digital Music, Queen Mary, University of London.
00007     This file copyright 2006 Chris Cannam.
00008    
00009     This program is free software; you can redistribute it and/or
00010     modify it under the terms of the GNU General Public License as
00011     published by the Free Software Foundation; either version 2 of the
00012     License, or (at your option) any later version.  See the file
00013     COPYING included with this distribution for more information.
00014 */
00015 
00016 #include "TimeRulerLayer.h"
00017 
00018 #include "LayerFactory.h"
00019 
00020 #include "data/model/Model.h"
00021 #include "base/RealTime.h"
00022 #include "ColourDatabase.h"
00023 #include "view/View.h"
00024 
00025 #include <QPainter>
00026 
00027 #include <iostream>
00028 #include <cmath>
00029 
00030 //#define DEBUG_TIME_RULER_LAYER 1
00031 
00032 
00033 
00034 
00035 TimeRulerLayer::TimeRulerLayer() :
00036     SingleColourLayer(),
00037     m_model(0),
00038     m_labelHeight(LabelTop)
00039 {
00040     
00041 }
00042 
00043 void
00044 TimeRulerLayer::setModel(Model *model)
00045 {
00046     if (m_model == model) return;
00047     m_model = model;
00048     emit modelReplaced();
00049 }
00050 
00051 bool
00052 TimeRulerLayer::snapToFeatureFrame(View *v, int &frame,
00053                                    int &resolution, SnapType snap) const
00054 {
00055     if (!m_model) {
00056         resolution = 1;
00057         return false;
00058     }
00059 
00060     bool q;
00061     int tick = getMajorTickSpacing(v, q);
00062     RealTime rtick = RealTime::fromMilliseconds(tick);
00063     int rate = m_model->getSampleRate();
00064     
00065     RealTime rt = RealTime::frame2RealTime(frame, rate);
00066     double ratio = rt / rtick;
00067 
00068     int rounded = int(ratio);
00069     RealTime rdrt = rtick * rounded;
00070 
00071     int left = RealTime::realTime2Frame(rdrt, rate);
00072     resolution = RealTime::realTime2Frame(rtick, rate);
00073     int right = left + resolution;
00074 
00075 //    SVDEBUG << "TimeRulerLayer::snapToFeatureFrame: type "
00076 //              << int(snap) << ", frame " << frame << " (time "
00077 //              << rt << ", tick " << rtick << ", rounded " << rdrt << ") ";
00078 
00079     switch (snap) {
00080 
00081     case SnapLeft:
00082         frame = left;
00083         break;
00084 
00085     case SnapRight:
00086         frame = right;
00087         break;
00088         
00089     case SnapNearest:
00090     {
00091         if (abs(frame - left) > abs(right - frame)) {
00092             frame = right;
00093         } else {
00094             frame = left;
00095         }
00096         break;
00097     }
00098 
00099     case SnapNeighbouring:
00100     {
00101         int dl = -1, dr = -1;
00102         int x = v->getXForFrame(frame);
00103 
00104         if (left > v->getStartFrame() &&
00105             left < v->getEndFrame()) {
00106             dl = abs(v->getXForFrame(left) - x);
00107         }
00108 
00109         if (right > v->getStartFrame() &&
00110             right < v->getEndFrame()) {
00111             dr = abs(v->getXForFrame(right) - x);
00112         }
00113 
00114         int fuzz = 2;
00115 
00116         if (dl >= 0 && dr >= 0) {
00117             if (dl < dr) {
00118                 if (dl <= fuzz) {
00119                     frame = left;
00120                 }
00121             } else {
00122                 if (dr < fuzz) {
00123                     frame = right;
00124                 }
00125             }
00126         } else if (dl >= 0) {
00127             if (dl <= fuzz) {
00128                 frame = left;
00129             }
00130         } else if (dr >= 0) {
00131             if (dr <= fuzz) {
00132                 frame = right;
00133             }
00134         }
00135     }
00136     }
00137 
00138 //    SVDEBUG << " -> " << frame << " (resolution = " << resolution << ")" << endl;
00139 
00140     return true;
00141 }
00142 
00143 int
00144 TimeRulerLayer::getMajorTickSpacing(View *v, bool &quarterTicks) const
00145 {
00146     // return value is in milliseconds
00147 
00148     if (!m_model || !v) return 1000;
00149 
00150     int sampleRate = m_model->getSampleRate();
00151     if (!sampleRate) return 1000;
00152 
00153     long startFrame = v->getStartFrame();
00154     long endFrame = v->getEndFrame();
00155 
00156     int minPixelSpacing = 50;
00157 
00158     RealTime rtStart = RealTime::frame2RealTime(startFrame, sampleRate);
00159     RealTime rtEnd = RealTime::frame2RealTime(endFrame, sampleRate);
00160 
00161     int count = v->width() / minPixelSpacing;
00162     if (count < 1) count = 1;
00163     RealTime rtGap = (rtEnd - rtStart) / count;
00164 
00165     int incms;
00166     quarterTicks = false;
00167 
00168     if (rtGap.sec > 0) {
00169         incms = 1000;
00170         int s = rtGap.sec;
00171         if (s > 0) { incms *= 5; s /= 5; }
00172         if (s > 0) { incms *= 2; s /= 2; }
00173         if (s > 0) { incms *= 6; s /= 6; quarterTicks = true; }
00174         if (s > 0) { incms *= 5; s /= 5; quarterTicks = false; }
00175         if (s > 0) { incms *= 2; s /= 2; }
00176         if (s > 0) { incms *= 6; s /= 6; quarterTicks = true; }
00177         while (s > 0) {
00178             incms *= 10;
00179             s /= 10;
00180             quarterTicks = false;
00181         }
00182     } else {
00183         incms = 1;
00184         int ms = rtGap.msec();
00185         if (ms > 0) { incms *= 10; ms /= 10; }
00186         if (ms > 0) { incms *= 10; ms /= 10; }
00187         if (ms > 0) { incms *= 5; ms /= 5; }
00188         if (ms > 0) { incms *= 2; ms /= 2; }
00189     }
00190 
00191     return incms;
00192 }
00193 
00194 void
00195 TimeRulerLayer::paint(View *v, QPainter &paint, QRect rect) const
00196 {
00197 #ifdef DEBUG_TIME_RULER_LAYER
00198     SVDEBUG << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y()
00199               << ") [" << rect.width() << "x" << rect.height() << "]" << endl;
00200 #endif
00201     
00202     if (!m_model || !m_model->isOK()) return;
00203 
00204     int sampleRate = m_model->getSampleRate();
00205     if (!sampleRate) return;
00206 
00207     long startFrame = v->getFrameForX(rect.x() - 50);
00208 
00209 #ifdef DEBUG_TIME_RULER_LAYER
00210     cerr << "start frame = " << startFrame << endl;
00211 #endif
00212 
00213     bool quarter = false;
00214     int incms = getMajorTickSpacing(v, quarter);
00215 
00216     int ms = lrint(1000.0 * (double(startFrame) / double(sampleRate)));
00217     ms = (ms / incms) * incms - incms;
00218 
00219 #ifdef DEBUG_TIME_RULER_LAYER
00220     cerr << "start ms = " << ms << " at step " << incms << endl;
00221 #endif
00222 
00223     // Calculate the number of ticks per increment -- approximate
00224     // values for x and frame counts here will do, no rounding issue.
00225     // We always use the exact incms in our calculations for where to
00226     // draw the actual ticks or lines.
00227 
00228     int minPixelSpacing = 50;
00229     long incFrame = (incms * sampleRate) / 1000;
00230     int incX = incFrame / v->getZoomLevel();
00231     int ticks = 10;
00232     if (incX < minPixelSpacing * 2) {
00233         ticks = quarter ? 4 : 5;
00234     }
00235 
00236     QColor greyColour = getPartialShades(v)[1];
00237 
00238     paint.save();
00239 
00240     // Do not label time zero - we now overlay an opaque area over
00241     // time < 0 which would cut it in half
00242     int minlabel = 1; // ms
00243 
00244     while (1) {
00245 
00246         // frame is used to determine where to draw the lines, so it
00247         // needs to correspond to an exact pixel (so that we don't get
00248         // a different pixel when scrolling a small amount and
00249         // re-drawing with a different start frame).
00250 
00251         double dms = ms;
00252         long frame = lrint((dms * sampleRate) / 1000.0);
00253         frame /= v->getZoomLevel();
00254         frame *= v->getZoomLevel(); // so frame corresponds to an exact pixel
00255 
00256         int x = v->getXForFrame(frame);
00257 
00258 #ifdef DEBUG_TIME_RULER_LAYER
00259         SVDEBUG << "Considering frame = " << frame << ", x = " << x << endl;
00260 #endif
00261 
00262         if (x >= rect.x() + rect.width() + 50) {
00263 #ifdef DEBUG_TIME_RULER_LAYER
00264             cerr << "X well out of range, ending here" << endl;
00265 #endif
00266             break;
00267         }
00268 
00269         if (x >= rect.x() - 50 && ms >= minlabel) {
00270 
00271             RealTime rt = RealTime::fromMilliseconds(ms);
00272 
00273 #ifdef DEBUG_TIME_RULER_LAYER
00274             cerr << "X in range, drawing line here for time " << rt.toText() << endl;
00275 #endif
00276 
00277             QString text(QString::fromStdString(rt.toText()));
00278             QFontMetrics metrics = paint.fontMetrics();
00279             int tw = metrics.width(text);
00280 
00281             if (tw < 50 &&
00282                 (x < rect.x() - tw/2 ||
00283                  x >= rect.x() + rect.width() + tw/2)) {
00284 #ifdef DEBUG_TIME_RULER_LAYER
00285                 cerr << "hm, maybe X isn't in range after all (x = " << x << ", tw = " << tw << ", rect.x() = " << rect.x() << ", rect.width() = " << rect.width() << ")" << endl;
00286 #endif
00287             }
00288 
00289             paint.setPen(greyColour);
00290             paint.drawLine(x, 0, x, v->height());
00291 
00292             paint.setPen(getBaseQColor());
00293             paint.drawLine(x, 0, x, 5);
00294             paint.drawLine(x, v->height() - 6, x, v->height() - 1);
00295 
00296             int y;
00297             switch (m_labelHeight) {
00298             default:
00299             case LabelTop:
00300                 y = 6 + metrics.ascent();
00301                 break;
00302             case LabelMiddle:
00303                 y = v->height() / 2 - metrics.height() / 2 + metrics.ascent();
00304                 break;
00305             case LabelBottom:
00306                 y = v->height() - metrics.height() + metrics.ascent() - 6;
00307             }
00308 
00309             if (v->getViewManager() && v->getViewManager()->getOverlayMode() !=
00310                 ViewManager::NoOverlays) {
00311 
00312                 if (v->getLayer(0) == this) {
00313                     // backmost layer, don't worry about outlining the text
00314                     paint.drawText(x+2 - tw/2, y, text);
00315                 } else {
00316                     v->drawVisibleText(paint, x+2 - tw/2, y, text, View::OutlinedText);
00317                 }
00318             }
00319         }
00320 
00321         paint.setPen(greyColour);
00322 
00323         for (int i = 1; i < ticks; ++i) {
00324 
00325             dms = ms + (i * double(incms)) / ticks;
00326             frame = lrint((dms * sampleRate) / 1000.0);
00327             frame /= v->getZoomLevel();
00328             frame *= v->getZoomLevel(); // exact pixel as above
00329 
00330             x = v->getXForFrame(frame);
00331 
00332             if (x < rect.x() || x >= rect.x() + rect.width()) {
00333 #ifdef DEBUG_TIME_RULER_LAYER
00334 //                cerr << "tick " << i << ": X out of range, going on to next tick" << endl;
00335 #endif
00336                 continue;
00337             }
00338 
00339 #ifdef DEBUG_TIME_RULER_LAYER
00340             cerr << "tick " << i << " in range, drawing at " << x << endl;
00341 #endif
00342 
00343             int sz = 5;
00344             if (ticks == 10) {
00345                 if ((i % 2) == 1) {
00346                     if (i == 5) {
00347                         paint.drawLine(x, 0, x, v->height());
00348                     } else sz = 3;
00349                 } else {
00350                     sz = 7;
00351                 }
00352             }
00353             paint.drawLine(x, 0, x, sz);
00354             paint.drawLine(x, v->height() - sz - 1, x, v->height() - 1);
00355         }
00356 
00357         ms += incms;
00358     }
00359 
00360     paint.restore();
00361 }
00362 
00363 int
00364 TimeRulerLayer::getDefaultColourHint(bool darkbg, bool &impose)
00365 {
00366     impose = true;
00367     return ColourDatabase::getInstance()->getColourIndex
00368         (QString(darkbg ? "White" : "Black"));
00369 }
00370 
00371 QString TimeRulerLayer::getLayerPresentationName() const
00372 {
00373     LayerFactory *factory = LayerFactory::getInstance();
00374     QString layerName = factory->getLayerPresentationName
00375         (factory->getLayerType(this));
00376     return layerName;
00377 }
00378 
00379 void
00380 TimeRulerLayer::toXml(QTextStream &stream,
00381                       QString indent, QString extraAttributes) const
00382 {
00383     SingleColourLayer::toXml(stream, indent, extraAttributes);
00384 }
00385 
00386 void
00387 TimeRulerLayer::setProperties(const QXmlAttributes &attributes)
00388 {
00389     SingleColourLayer::setProperties(attributes);
00390 }
00391