annotate layer/TimeRulerLayer.cpp @ 1431:af824022bffd single-point

Begin fixing the various snap operations. Also remove SnapNearest, which is never used and seems to consume more lines of code than the rest!
author Chris Cannam
date Wed, 20 Mar 2019 14:59:34 +0000
parents c8a6fd3f9dff
children f2525e6cbdf1
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@1408 39 m_model(nullptr),
Chris@0 40 m_labelHeight(LabelTop)
Chris@0 41 {
Chris@44 42
Chris@0 43 }
Chris@0 44
Chris@0 45 void
Chris@0 46 TimeRulerLayer::setModel(Model *model)
Chris@0 47 {
Chris@0 48 if (m_model == model) return;
Chris@0 49 m_model = model;
Chris@0 50 emit modelReplaced();
Chris@0 51 }
Chris@0 52
Chris@271 53 bool
Chris@918 54 TimeRulerLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
Chris@805 55 int &resolution, SnapType snap) const
Chris@271 56 {
Chris@271 57 if (!m_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@908 65 sv_samplerate_t rate = m_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@1341 140 if (!m_model || !v) return 1000 * 1000;
Chris@271 141
Chris@908 142 sv_samplerate_t sampleRate = m_model->getSampleRate();
Chris@1341 143 if (!sampleRate) return 1000 * 1000;
Chris@271 144
Chris@908 145 sv_frame_t startFrame = v->getStartFrame();
Chris@908 146 sv_frame_t endFrame = v->getEndFrame();
Chris@1356 147 if (endFrame == startFrame) {
Chris@1356 148 endFrame = startFrame + 1;
Chris@1356 149 }
Chris@271 150
Chris@1356 151 int exampleWidth = QFontMetrics(QFont()).width("10:42.987654");
Chris@1356 152 int minPixelSpacing = v->getXForViewX(exampleWidth);
Chris@271 153
Chris@271 154 RealTime rtStart = RealTime::frame2RealTime(startFrame, sampleRate);
Chris@271 155 RealTime rtEnd = RealTime::frame2RealTime(endFrame, sampleRate);
Chris@271 156
Chris@918 157 int count = v->getPaintWidth() / minPixelSpacing;
Chris@271 158 if (count < 1) count = 1;
Chris@271 159 RealTime rtGap = (rtEnd - rtStart) / count;
Chris@271 160
Chris@1356 161 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1356 162 SVCERR << "zoomLevel = " << v->getZoomLevel()
Chris@1356 163 << ", startFrame = " << startFrame << ", endFrame = " << endFrame
Chris@1356 164 << ", rtStart = " << rtStart << ", rtEnd = " << rtEnd
Chris@1356 165 << ", paint width = " << v->getPaintWidth()
Chris@1356 166 << ", minPixelSpacing = " << minPixelSpacing
Chris@1356 167 << ", count = " << count << ", rtGap = " << rtGap << endl;
Chris@1356 168 #endif
Chris@1356 169
Chris@1346 170 int64_t incus;
Chris@271 171 quarterTicks = false;
Chris@271 172
Chris@271 173 if (rtGap.sec > 0) {
Chris@1341 174 incus = 1000 * 1000;
Chris@1266 175 int s = rtGap.sec;
Chris@1341 176 if (s > 0) { incus *= 5; s /= 5; }
Chris@1341 177 if (s > 0) { incus *= 2; s /= 2; }
Chris@1341 178 if (s > 0) { incus *= 6; s /= 6; quarterTicks = true; }
Chris@1341 179 if (s > 0) { incus *= 5; s /= 5; quarterTicks = false; }
Chris@1341 180 if (s > 0) { incus *= 2; s /= 2; }
Chris@1341 181 if (s > 0) { incus *= 6; s /= 6; quarterTicks = true; }
Chris@1266 182 while (s > 0) {
Chris@1341 183 incus *= 10;
Chris@1266 184 s /= 10;
Chris@1266 185 quarterTicks = false;
Chris@1266 186 }
Chris@1341 187 } else if (rtGap.msec() > 0) {
Chris@1341 188 incus = 1000;
Chris@1341 189 int ms = rtGap.msec();
Chris@1341 190 if (ms > 0) { incus *= 10; ms /= 10; }
Chris@1341 191 if (ms > 0) { incus *= 10; ms /= 10; }
Chris@1341 192 if (ms > 0) { incus *= 5; ms /= 5; }
Chris@1341 193 if (ms > 0) { incus *= 2; ms /= 2; }
Chris@271 194 } else {
Chris@1341 195 incus = 1;
Chris@1341 196 int us = rtGap.usec();
Chris@1341 197 if (us > 0) { incus *= 10; us /= 10; }
Chris@1341 198 if (us > 0) { incus *= 10; us /= 10; }
Chris@1341 199 if (us > 0) { incus *= 5; us /= 5; }
Chris@1341 200 if (us > 0) { incus *= 2; us /= 2; }
Chris@271 201 }
Chris@271 202
Chris@1356 203 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1356 204 SVCERR << "getMajorTickUSec: returning incus = " << incus << endl;
Chris@1356 205 #endif
Chris@1356 206
Chris@1341 207 return incus;
Chris@1341 208 }
Chris@1341 209
Chris@1341 210 int
Chris@1341 211 TimeRulerLayer::getXForUSec(LayerGeometryProvider *v, double us) const
Chris@1341 212 {
Chris@1341 213 sv_samplerate_t sampleRate = m_model->getSampleRate();
Chris@1341 214 double dframe = (us * sampleRate) / 1000000.0;
Chris@1341 215 double eps = 1e-7;
Chris@1341 216 sv_frame_t frame = sv_frame_t(floor(dframe + eps));
Chris@1341 217 int x;
Chris@1341 218
Chris@1341 219 ZoomLevel zoom = v->getZoomLevel();
Chris@1341 220
Chris@1341 221 if (zoom.zone == ZoomLevel::FramesPerPixel) {
Chris@1341 222
Chris@1341 223 frame /= zoom.level;
Chris@1341 224 frame *= zoom.level; // so frame corresponds to an exact pixel
Chris@1341 225
Chris@1341 226 x = v->getXForFrame(frame);
Chris@1341 227
Chris@1341 228 } else {
Chris@1341 229
Chris@1341 230 double off = dframe - double(frame);
Chris@1341 231 int x0 = v->getXForFrame(frame);
Chris@1341 232 int x1 = v->getXForFrame(frame + 1);
Chris@1341 233
Chris@1341 234 x = int(x0 + off * (x1 - x0));
Chris@1341 235 }
Chris@1341 236
Chris@1341 237 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1341 238 cerr << "Considering frame = " << frame << ", x = " << x << endl;
Chris@1341 239 #endif
Chris@1341 240
Chris@1341 241 return x;
Chris@271 242 }
Chris@271 243
Chris@0 244 void
Chris@916 245 TimeRulerLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@0 246 {
Chris@370 247 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 248 SVCERR << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y()
Chris@1346 249 << ") [" << rect.width() << "x" << rect.height() << "]" << endl;
Chris@370 250 #endif
Chris@0 251
Chris@0 252 if (!m_model || !m_model->isOK()) return;
Chris@0 253
Chris@908 254 sv_samplerate_t sampleRate = m_model->getSampleRate();
Chris@0 255 if (!sampleRate) return;
Chris@0 256
Chris@908 257 sv_frame_t startFrame = v->getFrameForX(rect.x() - 50);
Chris@0 258
Chris@370 259 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 260 SVCERR << "start frame = " << startFrame << endl;
Chris@370 261 #endif
Chris@0 262
Chris@0 263 bool quarter = false;
Chris@1346 264 int64_t incus = getMajorTickUSec(v, quarter);
Chris@1346 265 int64_t us = int64_t(floor(1000.0 * 1000.0 * (double(startFrame) /
Chris@1346 266 double(sampleRate))));
Chris@1341 267 us = (us / incus) * incus - incus;
Chris@0 268
Chris@370 269 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 270 SVCERR << "start us = " << us << " at step " << incus << endl;
Chris@370 271 #endif
Chris@370 272
Chris@1345 273 Preferences *prefs = Preferences::getInstance();
Chris@1345 274 auto origTimeTextMode = prefs->getTimeToTextMode();
Chris@1345 275 if (incus < 1000) {
Chris@1345 276 // Temporarily switch to usec display mode (if we aren't using
Chris@1345 277 // it already)
Chris@1345 278 prefs->blockSignals(true);
Chris@1345 279 prefs->setTimeToTextMode(Preferences::TimeToTextUs);
Chris@1345 280 }
Chris@1345 281
Chris@370 282 // Calculate the number of ticks per increment -- approximate
Chris@370 283 // values for x and frame counts here will do, no rounding issue.
Chris@1341 284 // We always use the exact incus in our calculations for where to
Chris@370 285 // draw the actual ticks or lines.
Chris@370 286
Chris@1356 287 int minPixelSpacing = v->getXForViewX(50);
Chris@1346 288 sv_frame_t incFrame = lrint((double(incus) * sampleRate) / 1000000);
Chris@1326 289 int incX = int(round(v->getZoomLevel().framesToPixels(double(incFrame))));
Chris@0 290 int ticks = 10;
Chris@0 291 if (incX < minPixelSpacing * 2) {
Chris@1266 292 ticks = quarter ? 4 : 5;
Chris@0 293 }
Chris@0 294
Chris@370 295 QColor greyColour = getPartialShades(v)[1];
Chris@0 296
Chris@370 297 paint.save();
Chris@0 298
Chris@832 299 // Do not label time zero - we now overlay an opaque area over
Chris@832 300 // time < 0 which would cut it in half
Chris@1341 301 int minlabel = 1; // us
Chris@1021 302
Chris@0 303 while (1) {
Chris@0 304
Chris@370 305 // frame is used to determine where to draw the lines, so it
Chris@370 306 // needs to correspond to an exact pixel (so that we don't get
Chris@370 307 // a different pixel when scrolling a small amount and
Chris@370 308 // re-drawing with a different start frame).
Chris@370 309
Chris@1346 310 double dus = double(us);
Chris@370 311
Chris@1341 312 int x = getXForUSec(v, dus);
Chris@0 313
Chris@370 314 if (x >= rect.x() + rect.width() + 50) {
Chris@370 315 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 316 SVCERR << "X well out of range, ending here" << endl;
Chris@370 317 #endif
Chris@370 318 break;
Chris@370 319 }
Chris@0 320
Chris@1341 321 if (x >= rect.x() - 50 && us >= minlabel) {
Chris@0 322
Chris@1342 323 RealTime rt = RealTime::fromMicroseconds(us);
Chris@375 324
Chris@370 325 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 326 SVCERR << "X in range, drawing line here for time " << rt.toText() << " (usec = " << us << ")" << endl;
Chris@370 327 #endif
Chris@0 328
Chris@370 329 QString text(QString::fromStdString(rt.toText()));
Chris@1345 330
Chris@370 331 QFontMetrics metrics = paint.fontMetrics();
Chris@370 332 int tw = metrics.width(text);
Chris@0 333
Chris@370 334 if (tw < 50 &&
Chris@370 335 (x < rect.x() - tw/2 ||
Chris@370 336 x >= rect.x() + rect.width() + tw/2)) {
Chris@370 337 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 338 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 339 #endif
Chris@370 340 }
Chris@70 341
Chris@370 342 paint.setPen(greyColour);
Chris@918 343 paint.drawLine(x, 0, x, v->getPaintHeight());
Chris@370 344
Chris@370 345 paint.setPen(getBaseQColor());
Chris@370 346 paint.drawLine(x, 0, x, 5);
Chris@918 347 paint.drawLine(x, v->getPaintHeight() - 6, x, v->getPaintHeight() - 1);
Chris@370 348
Chris@370 349 int y;
Chris@370 350 switch (m_labelHeight) {
Chris@370 351 default:
Chris@370 352 case LabelTop:
Chris@370 353 y = 6 + metrics.ascent();
Chris@370 354 break;
Chris@370 355 case LabelMiddle:
Chris@918 356 y = v->getPaintHeight() / 2 - metrics.height() / 2 + metrics.ascent();
Chris@370 357 break;
Chris@370 358 case LabelBottom:
Chris@918 359 y = v->getPaintHeight() - metrics.height() + metrics.ascent() - 6;
Chris@370 360 }
Chris@370 361
Chris@370 362 if (v->getViewManager() && v->getViewManager()->getOverlayMode() !=
Chris@370 363 ViewManager::NoOverlays) {
Chris@370 364
Chris@919 365 if (v->getView()->getLayer(0) == this) {
Chris@370 366 // backmost layer, don't worry about outlining the text
Chris@370 367 paint.drawText(x+2 - tw/2, y, text);
Chris@370 368 } else {
Chris@1078 369 PaintAssistant::drawVisibleText(v, paint, x+2 - tw/2, y, text, PaintAssistant::OutlinedText);
Chris@370 370 }
Chris@70 371 }
Chris@70 372 }
Chris@0 373
Chris@1266 374 paint.setPen(greyColour);
Chris@0 375
Chris@1266 376 for (int i = 1; i < ticks; ++i) {
Chris@370 377
Chris@1346 378 dus = double(us) + (i * double(incus)) / ticks;
Chris@370 379
Chris@1341 380 x = getXForUSec(v, dus);
Chris@370 381
Chris@370 382 if (x < rect.x() || x >= rect.x() + rect.width()) {
Chris@370 383 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 384 // SVCERR << "tick " << i << ": X out of range, going on to next tick" << endl;
Chris@370 385 #endif
Chris@370 386 continue;
Chris@370 387 }
Chris@370 388
Chris@370 389 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1346 390 SVCERR << "tick " << i << " in range, drawing at " << x << endl;
Chris@370 391 #endif
Chris@370 392
Chris@1266 393 int sz = 5;
Chris@1266 394 if (ticks == 10) {
Chris@1266 395 if ((i % 2) == 1) {
Chris@1266 396 if (i == 5) {
Chris@1266 397 paint.drawLine(x, 0, x, v->getPaintHeight());
Chris@1266 398 } else sz = 3;
Chris@1266 399 } else {
Chris@1266 400 sz = 7;
Chris@1266 401 }
Chris@1266 402 }
Chris@1266 403 paint.drawLine(x, 0, x, sz);
Chris@1266 404 paint.drawLine(x, v->getPaintHeight() - sz - 1, x, v->getPaintHeight() - 1);
Chris@1266 405 }
Chris@375 406
Chris@1341 407 us += incus;
Chris@0 408 }
Chris@1345 409
Chris@1345 410 prefs->setTimeToTextMode(origTimeTextMode);
Chris@1345 411 prefs->blockSignals(false);
Chris@0 412
Chris@0 413 paint.restore();
Chris@0 414 }
Chris@287 415
Chris@287 416 int
Chris@287 417 TimeRulerLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 418 {
Chris@287 419 impose = true;
Chris@287 420 return ColourDatabase::getInstance()->getColourIndex
Chris@287 421 (QString(darkbg ? "White" : "Black"));
Chris@287 422 }
Chris@287 423
Chris@335 424 QString TimeRulerLayer::getLayerPresentationName() const
Chris@335 425 {
Chris@335 426 LayerFactory *factory = LayerFactory::getInstance();
Chris@335 427 QString layerName = factory->getLayerPresentationName
Chris@335 428 (factory->getLayerType(this));
Chris@335 429 return layerName;
Chris@335 430 }
Chris@335 431
Chris@316 432 void
Chris@316 433 TimeRulerLayer::toXml(QTextStream &stream,
Chris@316 434 QString indent, QString extraAttributes) const
Chris@6 435 {
Chris@316 436 SingleColourLayer::toXml(stream, indent, extraAttributes);
Chris@6 437 }
Chris@6 438
Chris@11 439 void
Chris@11 440 TimeRulerLayer::setProperties(const QXmlAttributes &attributes)
Chris@11 441 {
Chris@287 442 SingleColourLayer::setProperties(attributes);
Chris@11 443 }
Chris@11 444