annotate layer/TimeRulerLayer.cpp @ 1534:bfd8b22fd67c

Fix #1904 Scrolling colour 3d plot does not always work when in View normalisation mode. We shouldn't imagine we've just invalidated the cache if the truth is that we've only just created the renderer
author Chris Cannam
date Wed, 09 Oct 2019 13:45:17 +0100
parents 36ad3cdabf55
children e6362cf5ff1d
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@1345 22 #include "base/Preferences.h"
Chris@1078 23 #include "view/View.h"
Chris@1078 24
Chris@376 25 #include "ColourDatabase.h"
Chris@1078 26 #include "PaintAssistant.h"
Chris@0 27
Chris@0 28 #include <QPainter>
Chris@0 29
Chris@0 30 #include <iostream>
Chris@271 31 #include <cmath>
Chris@1021 32 #include <stdexcept>
Chris@0 33
Chris@1346 34 //#define DEBUG_TIME_RULER_LAYER 1
Chris@370 35
Chris@682 36
Chris@44 37 TimeRulerLayer::TimeRulerLayer() :
Chris@287 38 SingleColourLayer(),
Chris@0 39 m_labelHeight(LabelTop)
Chris@0 40 {
Chris@44 41
Chris@0 42 }
Chris@0 43
Chris@0 44 void
Chris@1471 45 TimeRulerLayer::setModel(ModelId model)
Chris@0 46 {
Chris@0 47 if (m_model == model) return;
Chris@0 48 m_model = model;
Chris@0 49 emit modelReplaced();
Chris@0 50 }
Chris@0 51
Chris@271 52 bool
Chris@918 53 TimeRulerLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
Chris@805 54 int &resolution, SnapType snap) const
Chris@271 55 {
Chris@1474 56 auto model = ModelById::get(m_model);
Chris@1474 57 if (!model) {
Chris@271 58 resolution = 1;
Chris@271 59 return false;
Chris@271 60 }
Chris@271 61
Chris@271 62 bool q;
Chris@1346 63 int64_t tickUSec = getMajorTickUSec(v, q);
Chris@1346 64 RealTime rtick = RealTime::fromMicroseconds(tickUSec);
Chris@1474 65 sv_samplerate_t rate = model->getSampleRate();
Chris@271 66
Chris@271 67 RealTime rt = RealTime::frame2RealTime(frame, rate);
Chris@271 68 double ratio = rt / rtick;
Chris@271 69
Chris@272 70 int rounded = int(ratio);
Chris@271 71 RealTime rdrt = rtick * rounded;
Chris@271 72
Chris@908 73 sv_frame_t left = RealTime::realTime2Frame(rdrt, rate);
Chris@908 74 resolution = int(RealTime::realTime2Frame(rtick, rate));
Chris@908 75 sv_frame_t right = left + resolution;
Chris@271 76
Chris@587 77 // SVDEBUG << "TimeRulerLayer::snapToFeatureFrame: type "
Chris@272 78 // << int(snap) << ", frame " << frame << " (time "
Chris@272 79 // << rt << ", tick " << rtick << ", rounded " << rdrt << ") ";
Chris@272 80
Chris@271 81 switch (snap) {
Chris@271 82
Chris@271 83 case SnapLeft:
Chris@271 84 frame = left;
Chris@271 85 break;
Chris@271 86
Chris@271 87 case SnapRight:
Chris@271 88 frame = right;
Chris@271 89 break;
Chris@271 90
Chris@271 91 case SnapNeighbouring:
Chris@271 92 {
Chris@271 93 int dl = -1, dr = -1;
Chris@271 94 int x = v->getXForFrame(frame);
Chris@271 95
Chris@271 96 if (left > v->getStartFrame() &&
Chris@271 97 left < v->getEndFrame()) {
Chris@271 98 dl = abs(v->getXForFrame(left) - x);
Chris@271 99 }
Chris@271 100
Chris@271 101 if (right > v->getStartFrame() &&
Chris@271 102 right < v->getEndFrame()) {
Chris@271 103 dr = abs(v->getXForFrame(right) - x);
Chris@271 104 }
Chris@271 105
Chris@1391 106 int fuzz = ViewManager::scalePixelSize(2);
Chris@271 107
Chris@271 108 if (dl >= 0 && dr >= 0) {
Chris@271 109 if (dl < dr) {
Chris@271 110 if (dl <= fuzz) {
Chris@271 111 frame = left;
Chris@271 112 }
Chris@271 113 } else {
Chris@271 114 if (dr < fuzz) {
Chris@271 115 frame = right;
Chris@271 116 }
Chris@271 117 }
Chris@271 118 } else if (dl >= 0) {
Chris@271 119 if (dl <= fuzz) {
Chris@271 120 frame = left;
Chris@271 121 }
Chris@271 122 } else if (dr >= 0) {
Chris@271 123 if (dr <= fuzz) {
Chris@271 124 frame = right;
Chris@271 125 }
Chris@271 126 }
Chris@271 127 }
Chris@271 128 }
Chris@271 129
Chris@587 130 // SVDEBUG << " -> " << frame << " (resolution = " << resolution << ")" << endl;
Chris@272 131
Chris@271 132 return true;
Chris@271 133 }
Chris@271 134
Chris@1346 135 int64_t
Chris@1341 136 TimeRulerLayer::getMajorTickUSec(LayerGeometryProvider *v,
Chris@1341 137 bool &quarterTicks) const
Chris@271 138 {
Chris@1341 139 // return value is in microseconds
Chris@1474 140 auto model = ModelById::get(m_model);
Chris@1474 141 if (!model || !v) return 1000 * 1000;
Chris@271 142
Chris@1474 143 sv_samplerate_t sampleRate = model->getSampleRate();
Chris@1341 144 if (!sampleRate) return 1000 * 1000;
Chris@271 145
Chris@908 146 sv_frame_t startFrame = v->getStartFrame();
Chris@908 147 sv_frame_t endFrame = v->getEndFrame();
Chris@1356 148 if (endFrame == startFrame) {
Chris@1356 149 endFrame = startFrame + 1;
Chris@1356 150 }
Chris@271 151
Chris@1474 152 // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
Chris@1474 153 // replacement (horizontalAdvance) was only added in Qt 5.11
Chris@1474 154 // which is too new for us
Chris@1474 155 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Chris@1474 156
Chris@1356 157 int exampleWidth = QFontMetrics(QFont()).width("10:42.987654");
Chris@1356 158 int minPixelSpacing = v->getXForViewX(exampleWidth);
Chris@271 159
Chris@271 160 RealTime rtStart = RealTime::frame2RealTime(startFrame, sampleRate);
Chris@271 161 RealTime rtEnd = RealTime::frame2RealTime(endFrame, sampleRate);
Chris@271 162
Chris@918 163 int count = v->getPaintWidth() / minPixelSpacing;
Chris@271 164 if (count < 1) count = 1;
Chris@271 165 RealTime rtGap = (rtEnd - rtStart) / count;
Chris@271 166
Chris@1356 167 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1356 168 SVCERR << "zoomLevel = " << v->getZoomLevel()
Chris@1356 169 << ", startFrame = " << startFrame << ", endFrame = " << endFrame
Chris@1356 170 << ", rtStart = " << rtStart << ", rtEnd = " << rtEnd
Chris@1356 171 << ", paint width = " << v->getPaintWidth()
Chris@1356 172 << ", minPixelSpacing = " << minPixelSpacing
Chris@1356 173 << ", count = " << count << ", rtGap = " << rtGap << endl;
Chris@1356 174 #endif
Chris@1356 175
Chris@1346 176 int64_t incus;
Chris@271 177 quarterTicks = false;
Chris@271 178
Chris@271 179 if (rtGap.sec > 0) {
Chris@1341 180 incus = 1000 * 1000;
Chris@1266 181 int s = rtGap.sec;
Chris@1341 182 if (s > 0) { incus *= 5; s /= 5; }
Chris@1341 183 if (s > 0) { incus *= 2; s /= 2; }
Chris@1341 184 if (s > 0) { incus *= 6; s /= 6; quarterTicks = true; }
Chris@1341 185 if (s > 0) { incus *= 5; s /= 5; quarterTicks = false; }
Chris@1341 186 if (s > 0) { incus *= 2; s /= 2; }
Chris@1341 187 if (s > 0) { incus *= 6; s /= 6; quarterTicks = true; }
Chris@1266 188 while (s > 0) {
Chris@1341 189 incus *= 10;
Chris@1266 190 s /= 10;
Chris@1266 191 quarterTicks = false;
Chris@1266 192 }
Chris@1341 193 } else if (rtGap.msec() > 0) {
Chris@1341 194 incus = 1000;
Chris@1341 195 int ms = rtGap.msec();
Chris@1341 196 if (ms > 0) { incus *= 10; ms /= 10; }
Chris@1341 197 if (ms > 0) { incus *= 10; ms /= 10; }
Chris@1341 198 if (ms > 0) { incus *= 5; ms /= 5; }
Chris@1341 199 if (ms > 0) { incus *= 2; ms /= 2; }
Chris@271 200 } else {
Chris@1341 201 incus = 1;
Chris@1341 202 int us = rtGap.usec();
Chris@1341 203 if (us > 0) { incus *= 10; us /= 10; }
Chris@1341 204 if (us > 0) { incus *= 10; us /= 10; }
Chris@1341 205 if (us > 0) { incus *= 5; us /= 5; }
Chris@1341 206 if (us > 0) { incus *= 2; us /= 2; }
Chris@271 207 }
Chris@271 208
Chris@1356 209 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1356 210 SVCERR << "getMajorTickUSec: returning incus = " << incus << endl;
Chris@1356 211 #endif
Chris@1356 212
Chris@1341 213 return incus;
Chris@1341 214 }
Chris@1341 215
Chris@1341 216 int
Chris@1341 217 TimeRulerLayer::getXForUSec(LayerGeometryProvider *v, double us) const
Chris@1341 218 {
Chris@1474 219 auto model = ModelById::get(m_model);
Chris@1474 220 sv_samplerate_t sampleRate = model->getSampleRate();
Chris@1341 221 double dframe = (us * sampleRate) / 1000000.0;
Chris@1341 222 double eps = 1e-7;
Chris@1341 223 sv_frame_t frame = sv_frame_t(floor(dframe + eps));
Chris@1341 224 int x;
Chris@1341 225
Chris@1341 226 ZoomLevel zoom = v->getZoomLevel();
Chris@1341 227
Chris@1341 228 if (zoom.zone == ZoomLevel::FramesPerPixel) {
Chris@1341 229
Chris@1341 230 frame /= zoom.level;
Chris@1341 231 frame *= zoom.level; // so frame corresponds to an exact pixel
Chris@1341 232
Chris@1341 233 x = v->getXForFrame(frame);
Chris@1341 234
Chris@1341 235 } else {
Chris@1341 236
Chris@1341 237 double off = dframe - double(frame);
Chris@1341 238 int x0 = v->getXForFrame(frame);
Chris@1341 239 int x1 = v->getXForFrame(frame + 1);
Chris@1341 240
Chris@1341 241 x = int(x0 + off * (x1 - x0));
Chris@1341 242 }
Chris@1341 243
Chris@1341 244 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1341 245 cerr << "Considering frame = " << frame << ", x = " << x << endl;
Chris@1341 246 #endif
Chris@1341 247
Chris@1341 248 return x;
Chris@271 249 }
Chris@271 250
Chris@0 251 void
Chris@916 252 TimeRulerLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@0 253 {
Chris@370 254 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 255 SVCERR << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y()
Chris@1346 256 << ") [" << rect.width() << "x" << rect.height() << "]" << endl;
Chris@370 257 #endif
Chris@0 258
Chris@1474 259 auto model = ModelById::get(m_model);
Chris@1474 260 if (!model || !model->isOK()) return;
Chris@0 261
Chris@1474 262 sv_samplerate_t sampleRate = model->getSampleRate();
Chris@0 263 if (!sampleRate) return;
Chris@0 264
Chris@908 265 sv_frame_t startFrame = v->getFrameForX(rect.x() - 50);
Chris@0 266
Chris@370 267 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 268 SVCERR << "start frame = " << startFrame << endl;
Chris@370 269 #endif
Chris@0 270
Chris@0 271 bool quarter = false;
Chris@1346 272 int64_t incus = getMajorTickUSec(v, quarter);
Chris@1346 273 int64_t us = int64_t(floor(1000.0 * 1000.0 * (double(startFrame) /
Chris@1346 274 double(sampleRate))));
Chris@1341 275 us = (us / incus) * incus - incus;
Chris@0 276
Chris@370 277 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 278 SVCERR << "start us = " << us << " at step " << incus << endl;
Chris@370 279 #endif
Chris@370 280
Chris@1345 281 Preferences *prefs = Preferences::getInstance();
Chris@1345 282 auto origTimeTextMode = prefs->getTimeToTextMode();
Chris@1345 283 if (incus < 1000) {
Chris@1345 284 // Temporarily switch to usec display mode (if we aren't using
Chris@1345 285 // it already)
Chris@1345 286 prefs->blockSignals(true);
Chris@1345 287 prefs->setTimeToTextMode(Preferences::TimeToTextUs);
Chris@1345 288 }
Chris@1345 289
Chris@370 290 // Calculate the number of ticks per increment -- approximate
Chris@370 291 // values for x and frame counts here will do, no rounding issue.
Chris@1341 292 // We always use the exact incus in our calculations for where to
Chris@370 293 // draw the actual ticks or lines.
Chris@370 294
Chris@1356 295 int minPixelSpacing = v->getXForViewX(50);
Chris@1346 296 sv_frame_t incFrame = lrint((double(incus) * sampleRate) / 1000000);
Chris@1326 297 int incX = int(round(v->getZoomLevel().framesToPixels(double(incFrame))));
Chris@0 298 int ticks = 10;
Chris@0 299 if (incX < minPixelSpacing * 2) {
Chris@1266 300 ticks = quarter ? 4 : 5;
Chris@0 301 }
Chris@0 302
Chris@370 303 QColor greyColour = getPartialShades(v)[1];
Chris@0 304
Chris@370 305 paint.save();
Chris@0 306
Chris@832 307 // Do not label time zero - we now overlay an opaque area over
Chris@832 308 // time < 0 which would cut it in half
Chris@1341 309 int minlabel = 1; // us
Chris@1021 310
Chris@0 311 while (1) {
Chris@0 312
Chris@370 313 // frame is used to determine where to draw the lines, so it
Chris@370 314 // needs to correspond to an exact pixel (so that we don't get
Chris@370 315 // a different pixel when scrolling a small amount and
Chris@370 316 // re-drawing with a different start frame).
Chris@370 317
Chris@1346 318 double dus = double(us);
Chris@370 319
Chris@1341 320 int x = getXForUSec(v, dus);
Chris@0 321
Chris@370 322 if (x >= rect.x() + rect.width() + 50) {
Chris@370 323 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 324 SVCERR << "X well out of range, ending here" << endl;
Chris@370 325 #endif
Chris@370 326 break;
Chris@370 327 }
Chris@0 328
Chris@1341 329 if (x >= rect.x() - 50 && us >= minlabel) {
Chris@0 330
Chris@1342 331 RealTime rt = RealTime::fromMicroseconds(us);
Chris@375 332
Chris@370 333 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 334 SVCERR << "X in range, drawing line here for time " << rt.toText() << " (usec = " << us << ")" << endl;
Chris@370 335 #endif
Chris@0 336
Chris@370 337 QString text(QString::fromStdString(rt.toText()));
Chris@1345 338
Chris@370 339 QFontMetrics metrics = paint.fontMetrics();
Chris@370 340 int tw = metrics.width(text);
Chris@0 341
Chris@370 342 if (tw < 50 &&
Chris@370 343 (x < rect.x() - tw/2 ||
Chris@370 344 x >= rect.x() + rect.width() + tw/2)) {
Chris@370 345 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 346 SVCERR << "hm, maybe X isn't in range after all (x = " << x << ", tw = " << tw << ", rect.x() = " << rect.x() << ", rect.width() = " << rect.width() << ")" << endl;
Chris@370 347 #endif
Chris@370 348 }
Chris@70 349
Chris@370 350 paint.setPen(greyColour);
Chris@918 351 paint.drawLine(x, 0, x, v->getPaintHeight());
Chris@370 352
Chris@370 353 paint.setPen(getBaseQColor());
Chris@370 354 paint.drawLine(x, 0, x, 5);
Chris@918 355 paint.drawLine(x, v->getPaintHeight() - 6, x, v->getPaintHeight() - 1);
Chris@370 356
Chris@370 357 int y;
Chris@370 358 switch (m_labelHeight) {
Chris@370 359 default:
Chris@370 360 case LabelTop:
Chris@370 361 y = 6 + metrics.ascent();
Chris@370 362 break;
Chris@370 363 case LabelMiddle:
Chris@918 364 y = v->getPaintHeight() / 2 - metrics.height() / 2 + metrics.ascent();
Chris@370 365 break;
Chris@370 366 case LabelBottom:
Chris@918 367 y = v->getPaintHeight() - metrics.height() + metrics.ascent() - 6;
Chris@370 368 }
Chris@370 369
Chris@370 370 if (v->getViewManager() && v->getViewManager()->getOverlayMode() !=
Chris@370 371 ViewManager::NoOverlays) {
Chris@370 372
Chris@919 373 if (v->getView()->getLayer(0) == this) {
Chris@370 374 // backmost layer, don't worry about outlining the text
Chris@370 375 paint.drawText(x+2 - tw/2, y, text);
Chris@370 376 } else {
Chris@1078 377 PaintAssistant::drawVisibleText(v, paint, x+2 - tw/2, y, text, PaintAssistant::OutlinedText);
Chris@370 378 }
Chris@70 379 }
Chris@70 380 }
Chris@0 381
Chris@1266 382 paint.setPen(greyColour);
Chris@0 383
Chris@1266 384 for (int i = 1; i < ticks; ++i) {
Chris@370 385
Chris@1346 386 dus = double(us) + (i * double(incus)) / ticks;
Chris@370 387
Chris@1341 388 x = getXForUSec(v, dus);
Chris@370 389
Chris@370 390 if (x < rect.x() || x >= rect.x() + rect.width()) {
Chris@370 391 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 392 // SVCERR << "tick " << i << ": X out of range, going on to next tick" << endl;
Chris@370 393 #endif
Chris@370 394 continue;
Chris@370 395 }
Chris@370 396
Chris@370 397 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 398 SVCERR << "tick " << i << " in range, drawing at " << x << endl;
Chris@370 399 #endif
Chris@370 400
Chris@1266 401 int sz = 5;
Chris@1266 402 if (ticks == 10) {
Chris@1266 403 if ((i % 2) == 1) {
Chris@1266 404 if (i == 5) {
Chris@1266 405 paint.drawLine(x, 0, x, v->getPaintHeight());
Chris@1266 406 } else sz = 3;
Chris@1266 407 } else {
Chris@1266 408 sz = 7;
Chris@1266 409 }
Chris@1266 410 }
Chris@1266 411 paint.drawLine(x, 0, x, sz);
Chris@1266 412 paint.drawLine(x, v->getPaintHeight() - sz - 1, x, v->getPaintHeight() - 1);
Chris@1266 413 }
Chris@375 414
Chris@1341 415 us += incus;
Chris@0 416 }
Chris@1345 417
Chris@1345 418 prefs->setTimeToTextMode(origTimeTextMode);
Chris@1345 419 prefs->blockSignals(false);
Chris@0 420
Chris@0 421 paint.restore();
Chris@0 422 }
Chris@287 423
Chris@287 424 int
Chris@287 425 TimeRulerLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 426 {
Chris@287 427 impose = true;
Chris@287 428 return ColourDatabase::getInstance()->getColourIndex
Chris@287 429 (QString(darkbg ? "White" : "Black"));
Chris@287 430 }
Chris@287 431
Chris@335 432 QString TimeRulerLayer::getLayerPresentationName() const
Chris@335 433 {
Chris@335 434 LayerFactory *factory = LayerFactory::getInstance();
Chris@335 435 QString layerName = factory->getLayerPresentationName
Chris@335 436 (factory->getLayerType(this));
Chris@335 437 return layerName;
Chris@335 438 }
Chris@335 439
Chris@316 440 void
Chris@316 441 TimeRulerLayer::toXml(QTextStream &stream,
Chris@316 442 QString indent, QString extraAttributes) const
Chris@6 443 {
Chris@316 444 SingleColourLayer::toXml(stream, indent, extraAttributes);
Chris@6 445 }
Chris@6 446
Chris@11 447 void
Chris@11 448 TimeRulerLayer::setProperties(const QXmlAttributes &attributes)
Chris@11 449 {
Chris@287 450 SingleColourLayer::setProperties(attributes);
Chris@11 451 }
Chris@11 452