svgui
1.9
|
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