annotate layer/TimeRulerLayer.cpp @ 947:e53a87a5efb2

Allow layers to be loaded without models if their layer class explicitly says it's OK (otherwise default template won't load, as it has an empty waveform layer)
author Chris Cannam
date Mon, 20 Apr 2015 10:10:26 +0100
parents 4a578a360011
children 94e4952a6774 3871dffc31bd
rev   line source
Chris@58 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@0 2
Chris@0 3 /*
Chris@59 4 Sonic Visualiser
Chris@59 5 An audio file viewer and annotation editor.
Chris@59 6 Centre for Digital Music, Queen Mary, University of London.
Chris@59 7 This file copyright 2006 Chris Cannam.
Chris@0 8
Chris@59 9 This program is free software; you can redistribute it and/or
Chris@59 10 modify it under the terms of the GNU General Public License as
Chris@59 11 published by the Free Software Foundation; either version 2 of the
Chris@59 12 License, or (at your option) any later version. See the file
Chris@59 13 COPYING included with this distribution for more information.
Chris@0 14 */
Chris@0 15
Chris@0 16 #include "TimeRulerLayer.h"
Chris@0 17
Chris@335 18 #include "LayerFactory.h"
Chris@335 19
Chris@128 20 #include "data/model/Model.h"
Chris@0 21 #include "base/RealTime.h"
Chris@376 22 #include "ColourDatabase.h"
Chris@128 23 #include "view/View.h"
Chris@0 24
Chris@0 25 #include <QPainter>
Chris@0 26
Chris@0 27 #include <iostream>
Chris@271 28 #include <cmath>
Chris@0 29
Chris@370 30 //#define DEBUG_TIME_RULER_LAYER 1
Chris@370 31
Chris@682 32
Chris@682 33
Chris@0 34
Chris@44 35 TimeRulerLayer::TimeRulerLayer() :
Chris@287 36 SingleColourLayer(),
Chris@0 37 m_model(0),
Chris@0 38 m_labelHeight(LabelTop)
Chris@0 39 {
Chris@44 40
Chris@0 41 }
Chris@0 42
Chris@0 43 void
Chris@0 44 TimeRulerLayer::setModel(Model *model)
Chris@0 45 {
Chris@0 46 if (m_model == model) return;
Chris@0 47 m_model = model;
Chris@0 48 emit modelReplaced();
Chris@0 49 }
Chris@0 50
Chris@271 51 bool
Chris@908 52 TimeRulerLayer::snapToFeatureFrame(View *v, sv_frame_t &frame,
Chris@805 53 int &resolution, SnapType snap) const
Chris@271 54 {
Chris@271 55 if (!m_model) {
Chris@271 56 resolution = 1;
Chris@271 57 return false;
Chris@271 58 }
Chris@271 59
Chris@271 60 bool q;
Chris@271 61 int tick = getMajorTickSpacing(v, q);
Chris@271 62 RealTime rtick = RealTime::fromMilliseconds(tick);
Chris@908 63 sv_samplerate_t rate = m_model->getSampleRate();
Chris@271 64
Chris@271 65 RealTime rt = RealTime::frame2RealTime(frame, rate);
Chris@271 66 double ratio = rt / rtick;
Chris@271 67
Chris@272 68 int rounded = int(ratio);
Chris@271 69 RealTime rdrt = rtick * rounded;
Chris@271 70
Chris@908 71 sv_frame_t left = RealTime::realTime2Frame(rdrt, rate);
Chris@908 72 resolution = int(RealTime::realTime2Frame(rtick, rate));
Chris@908 73 sv_frame_t right = left + resolution;
Chris@271 74
Chris@587 75 // SVDEBUG << "TimeRulerLayer::snapToFeatureFrame: type "
Chris@272 76 // << int(snap) << ", frame " << frame << " (time "
Chris@272 77 // << rt << ", tick " << rtick << ", rounded " << rdrt << ") ";
Chris@272 78
Chris@271 79 switch (snap) {
Chris@271 80
Chris@271 81 case SnapLeft:
Chris@271 82 frame = left;
Chris@271 83 break;
Chris@271 84
Chris@271 85 case SnapRight:
Chris@271 86 frame = right;
Chris@271 87 break;
Chris@271 88
Chris@271 89 case SnapNearest:
Chris@271 90 {
Chris@908 91 if (labs(frame - left) > labs(right - frame)) {
Chris@271 92 frame = right;
Chris@271 93 } else {
Chris@271 94 frame = left;
Chris@271 95 }
Chris@271 96 break;
Chris@271 97 }
Chris@271 98
Chris@271 99 case SnapNeighbouring:
Chris@271 100 {
Chris@271 101 int dl = -1, dr = -1;
Chris@271 102 int x = v->getXForFrame(frame);
Chris@271 103
Chris@271 104 if (left > v->getStartFrame() &&
Chris@271 105 left < v->getEndFrame()) {
Chris@271 106 dl = abs(v->getXForFrame(left) - x);
Chris@271 107 }
Chris@271 108
Chris@271 109 if (right > v->getStartFrame() &&
Chris@271 110 right < v->getEndFrame()) {
Chris@271 111 dr = abs(v->getXForFrame(right) - x);
Chris@271 112 }
Chris@271 113
Chris@271 114 int fuzz = 2;
Chris@271 115
Chris@271 116 if (dl >= 0 && dr >= 0) {
Chris@271 117 if (dl < dr) {
Chris@271 118 if (dl <= fuzz) {
Chris@271 119 frame = left;
Chris@271 120 }
Chris@271 121 } else {
Chris@271 122 if (dr < fuzz) {
Chris@271 123 frame = right;
Chris@271 124 }
Chris@271 125 }
Chris@271 126 } else if (dl >= 0) {
Chris@271 127 if (dl <= fuzz) {
Chris@271 128 frame = left;
Chris@271 129 }
Chris@271 130 } else if (dr >= 0) {
Chris@271 131 if (dr <= fuzz) {
Chris@271 132 frame = right;
Chris@271 133 }
Chris@271 134 }
Chris@271 135 }
Chris@271 136 }
Chris@271 137
Chris@587 138 // SVDEBUG << " -> " << frame << " (resolution = " << resolution << ")" << endl;
Chris@272 139
Chris@271 140 return true;
Chris@271 141 }
Chris@271 142
Chris@271 143 int
Chris@271 144 TimeRulerLayer::getMajorTickSpacing(View *v, bool &quarterTicks) const
Chris@271 145 {
Chris@271 146 // return value is in milliseconds
Chris@271 147
Chris@271 148 if (!m_model || !v) return 1000;
Chris@271 149
Chris@908 150 sv_samplerate_t sampleRate = m_model->getSampleRate();
Chris@271 151 if (!sampleRate) return 1000;
Chris@271 152
Chris@908 153 sv_frame_t startFrame = v->getStartFrame();
Chris@908 154 sv_frame_t endFrame = v->getEndFrame();
Chris@271 155
Chris@271 156 int minPixelSpacing = 50;
Chris@271 157
Chris@271 158 RealTime rtStart = RealTime::frame2RealTime(startFrame, sampleRate);
Chris@271 159 RealTime rtEnd = RealTime::frame2RealTime(endFrame, sampleRate);
Chris@271 160
Chris@271 161 int count = v->width() / minPixelSpacing;
Chris@271 162 if (count < 1) count = 1;
Chris@271 163 RealTime rtGap = (rtEnd - rtStart) / count;
Chris@271 164
Chris@271 165 int incms;
Chris@271 166 quarterTicks = false;
Chris@271 167
Chris@271 168 if (rtGap.sec > 0) {
Chris@271 169 incms = 1000;
Chris@271 170 int s = rtGap.sec;
Chris@271 171 if (s > 0) { incms *= 5; s /= 5; }
Chris@271 172 if (s > 0) { incms *= 2; s /= 2; }
Chris@271 173 if (s > 0) { incms *= 6; s /= 6; quarterTicks = true; }
Chris@271 174 if (s > 0) { incms *= 5; s /= 5; quarterTicks = false; }
Chris@271 175 if (s > 0) { incms *= 2; s /= 2; }
Chris@271 176 if (s > 0) { incms *= 6; s /= 6; quarterTicks = true; }
Chris@271 177 while (s > 0) {
Chris@271 178 incms *= 10;
Chris@271 179 s /= 10;
Chris@271 180 quarterTicks = false;
Chris@271 181 }
Chris@271 182 } else {
Chris@271 183 incms = 1;
Chris@271 184 int ms = rtGap.msec();
Chris@271 185 if (ms > 0) { incms *= 10; ms /= 10; }
Chris@271 186 if (ms > 0) { incms *= 10; ms /= 10; }
Chris@271 187 if (ms > 0) { incms *= 5; ms /= 5; }
Chris@271 188 if (ms > 0) { incms *= 2; ms /= 2; }
Chris@271 189 }
Chris@271 190
Chris@271 191 return incms;
Chris@271 192 }
Chris@271 193
Chris@0 194 void
Chris@44 195 TimeRulerLayer::paint(View *v, QPainter &paint, QRect rect) const
Chris@0 196 {
Chris@370 197 #ifdef DEBUG_TIME_RULER_LAYER
Chris@587 198 SVDEBUG << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y()
Chris@585 199 << ") [" << rect.width() << "x" << rect.height() << "]" << endl;
Chris@370 200 #endif
Chris@0 201
Chris@0 202 if (!m_model || !m_model->isOK()) return;
Chris@0 203
Chris@908 204 sv_samplerate_t sampleRate = m_model->getSampleRate();
Chris@0 205 if (!sampleRate) return;
Chris@0 206
Chris@908 207 sv_frame_t startFrame = v->getFrameForX(rect.x() - 50);
Chris@0 208
Chris@370 209 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 210 cerr << "start frame = " << startFrame << endl;
Chris@370 211 #endif
Chris@0 212
Chris@0 213 bool quarter = false;
Chris@271 214 int incms = getMajorTickSpacing(v, quarter);
Chris@0 215
Chris@908 216 int ms = int(lrint(1000.0 * (double(startFrame) / double(sampleRate))));
Chris@0 217 ms = (ms / incms) * incms - incms;
Chris@0 218
Chris@370 219 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 220 cerr << "start ms = " << ms << " at step " << incms << endl;
Chris@370 221 #endif
Chris@370 222
Chris@370 223 // Calculate the number of ticks per increment -- approximate
Chris@370 224 // values for x and frame counts here will do, no rounding issue.
Chris@370 225 // We always use the exact incms in our calculations for where to
Chris@370 226 // draw the actual ticks or lines.
Chris@370 227
Chris@370 228 int minPixelSpacing = 50;
Chris@908 229 sv_frame_t incFrame = lrint((incms * sampleRate) / 1000);
Chris@908 230 int incX = int(incFrame / v->getZoomLevel());
Chris@0 231 int ticks = 10;
Chris@0 232 if (incX < minPixelSpacing * 2) {
Chris@0 233 ticks = quarter ? 4 : 5;
Chris@0 234 }
Chris@0 235
Chris@370 236 QColor greyColour = getPartialShades(v)[1];
Chris@0 237
Chris@370 238 paint.save();
Chris@0 239
Chris@832 240 // Do not label time zero - we now overlay an opaque area over
Chris@832 241 // time < 0 which would cut it in half
Chris@832 242 int minlabel = 1; // ms
Chris@832 243
Chris@0 244 while (1) {
Chris@0 245
Chris@370 246 // frame is used to determine where to draw the lines, so it
Chris@370 247 // needs to correspond to an exact pixel (so that we don't get
Chris@370 248 // a different pixel when scrolling a small amount and
Chris@370 249 // re-drawing with a different start frame).
Chris@370 250
Chris@370 251 double dms = ms;
Chris@908 252 sv_frame_t frame = lrint((dms * sampleRate) / 1000.0);
Chris@370 253 frame /= v->getZoomLevel();
Chris@370 254 frame *= v->getZoomLevel(); // so frame corresponds to an exact pixel
Chris@370 255
Chris@370 256 int x = v->getXForFrame(frame);
Chris@0 257
Chris@370 258 #ifdef DEBUG_TIME_RULER_LAYER
Chris@587 259 SVDEBUG << "Considering frame = " << frame << ", x = " << x << endl;
Chris@370 260 #endif
Chris@0 261
Chris@370 262 if (x >= rect.x() + rect.width() + 50) {
Chris@370 263 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 264 cerr << "X well out of range, ending here" << endl;
Chris@370 265 #endif
Chris@370 266 break;
Chris@370 267 }
Chris@0 268
Chris@832 269 if (x >= rect.x() - 50 && ms >= minlabel) {
Chris@0 270
Chris@375 271 RealTime rt = RealTime::fromMilliseconds(ms);
Chris@375 272
Chris@370 273 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 274 cerr << "X in range, drawing line here for time " << rt.toText() << endl;
Chris@370 275 #endif
Chris@0 276
Chris@370 277 QString text(QString::fromStdString(rt.toText()));
Chris@370 278 QFontMetrics metrics = paint.fontMetrics();
Chris@370 279 int tw = metrics.width(text);
Chris@0 280
Chris@370 281 if (tw < 50 &&
Chris@370 282 (x < rect.x() - tw/2 ||
Chris@370 283 x >= rect.x() + rect.width() + tw/2)) {
Chris@370 284 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 285 cerr << "hm, maybe X isn't in range after all (x = " << x << ", tw = " << tw << ", rect.x() = " << rect.x() << ", rect.width() = " << rect.width() << ")" << endl;
Chris@370 286 #endif
Chris@370 287 }
Chris@70 288
Chris@370 289 paint.setPen(greyColour);
Chris@370 290 paint.drawLine(x, 0, x, v->height());
Chris@370 291
Chris@370 292 paint.setPen(getBaseQColor());
Chris@370 293 paint.drawLine(x, 0, x, 5);
Chris@370 294 paint.drawLine(x, v->height() - 6, x, v->height() - 1);
Chris@370 295
Chris@370 296 int y;
Chris@370 297 switch (m_labelHeight) {
Chris@370 298 default:
Chris@370 299 case LabelTop:
Chris@370 300 y = 6 + metrics.ascent();
Chris@370 301 break;
Chris@370 302 case LabelMiddle:
Chris@370 303 y = v->height() / 2 - metrics.height() / 2 + metrics.ascent();
Chris@370 304 break;
Chris@370 305 case LabelBottom:
Chris@370 306 y = v->height() - metrics.height() + metrics.ascent() - 6;
Chris@370 307 }
Chris@370 308
Chris@370 309 if (v->getViewManager() && v->getViewManager()->getOverlayMode() !=
Chris@370 310 ViewManager::NoOverlays) {
Chris@370 311
Chris@370 312 if (v->getLayer(0) == this) {
Chris@370 313 // backmost layer, don't worry about outlining the text
Chris@370 314 paint.drawText(x+2 - tw/2, y, text);
Chris@370 315 } else {
Chris@370 316 v->drawVisibleText(paint, x+2 - tw/2, y, text, View::OutlinedText);
Chris@370 317 }
Chris@70 318 }
Chris@70 319 }
Chris@0 320
Chris@0 321 paint.setPen(greyColour);
Chris@0 322
Chris@0 323 for (int i = 1; i < ticks; ++i) {
Chris@370 324
Chris@380 325 dms = ms + (i * double(incms)) / ticks;
Chris@370 326 frame = lrint((dms * sampleRate) / 1000.0);
Chris@370 327 frame /= v->getZoomLevel();
Chris@370 328 frame *= v->getZoomLevel(); // exact pixel as above
Chris@370 329
Chris@370 330 x = v->getXForFrame(frame);
Chris@370 331
Chris@370 332 if (x < rect.x() || x >= rect.x() + rect.width()) {
Chris@370 333 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 334 // cerr << "tick " << i << ": X out of range, going on to next tick" << endl;
Chris@370 335 #endif
Chris@370 336 continue;
Chris@370 337 }
Chris@370 338
Chris@370 339 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 340 cerr << "tick " << i << " in range, drawing at " << x << endl;
Chris@370 341 #endif
Chris@370 342
Chris@0 343 int sz = 5;
Chris@0 344 if (ticks == 10) {
Chris@0 345 if ((i % 2) == 1) {
Chris@0 346 if (i == 5) {
Chris@44 347 paint.drawLine(x, 0, x, v->height());
Chris@0 348 } else sz = 3;
Chris@0 349 } else {
Chris@0 350 sz = 7;
Chris@0 351 }
Chris@0 352 }
Chris@0 353 paint.drawLine(x, 0, x, sz);
Chris@44 354 paint.drawLine(x, v->height() - sz - 1, x, v->height() - 1);
Chris@0 355 }
Chris@375 356
Chris@375 357 ms += incms;
Chris@0 358 }
Chris@0 359
Chris@0 360 paint.restore();
Chris@0 361 }
Chris@287 362
Chris@287 363 int
Chris@287 364 TimeRulerLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 365 {
Chris@287 366 impose = true;
Chris@287 367 return ColourDatabase::getInstance()->getColourIndex
Chris@287 368 (QString(darkbg ? "White" : "Black"));
Chris@287 369 }
Chris@287 370
Chris@335 371 QString TimeRulerLayer::getLayerPresentationName() const
Chris@335 372 {
Chris@335 373 LayerFactory *factory = LayerFactory::getInstance();
Chris@335 374 QString layerName = factory->getLayerPresentationName
Chris@335 375 (factory->getLayerType(this));
Chris@335 376 return layerName;
Chris@335 377 }
Chris@335 378
Chris@316 379 void
Chris@316 380 TimeRulerLayer::toXml(QTextStream &stream,
Chris@316 381 QString indent, QString extraAttributes) const
Chris@6 382 {
Chris@316 383 SingleColourLayer::toXml(stream, indent, extraAttributes);
Chris@6 384 }
Chris@6 385
Chris@11 386 void
Chris@11 387 TimeRulerLayer::setProperties(const QXmlAttributes &attributes)
Chris@11 388 {
Chris@287 389 SingleColourLayer::setProperties(attributes);
Chris@11 390 }
Chris@11 391