annotate layer/TimeRulerLayer.cpp @ 1330:c1f719094c25 zoom

Ensure getFrameForX returns value on zoom blocksize boundary; take advantage of that (this is essentially reverting to the same behaviour as in the default branch, which we should probably have done all along)
author Chris Cannam
date Fri, 21 Sep 2018 11:50:05 +0100
parents 97c68bffbda6
children ab2cafd3a7cb
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@1078 22 #include "view/View.h"
Chris@1078 23
Chris@376 24 #include "ColourDatabase.h"
Chris@1078 25 #include "PaintAssistant.h"
Chris@0 26
Chris@0 27 #include <QPainter>
Chris@0 28
Chris@0 29 #include <iostream>
Chris@271 30 #include <cmath>
Chris@1021 31 #include <stdexcept>
Chris@0 32
Chris@370 33 //#define DEBUG_TIME_RULER_LAYER 1
Chris@370 34
Chris@682 35
Chris@44 36 TimeRulerLayer::TimeRulerLayer() :
Chris@287 37 SingleColourLayer(),
Chris@0 38 m_model(0),
Chris@0 39 m_labelHeight(LabelTop)
Chris@0 40 {
Chris@44 41
Chris@0 42 }
Chris@0 43
Chris@0 44 void
Chris@0 45 TimeRulerLayer::setModel(Model *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@271 56 if (!m_model) {
Chris@271 57 resolution = 1;
Chris@271 58 return false;
Chris@271 59 }
Chris@271 60
Chris@271 61 bool q;
Chris@271 62 int tick = getMajorTickSpacing(v, q);
Chris@271 63 RealTime rtick = RealTime::fromMilliseconds(tick);
Chris@908 64 sv_samplerate_t rate = m_model->getSampleRate();
Chris@271 65
Chris@271 66 RealTime rt = RealTime::frame2RealTime(frame, rate);
Chris@271 67 double ratio = rt / rtick;
Chris@271 68
Chris@272 69 int rounded = int(ratio);
Chris@271 70 RealTime rdrt = rtick * rounded;
Chris@271 71
Chris@908 72 sv_frame_t left = RealTime::realTime2Frame(rdrt, rate);
Chris@908 73 resolution = int(RealTime::realTime2Frame(rtick, rate));
Chris@908 74 sv_frame_t right = left + resolution;
Chris@271 75
Chris@587 76 // SVDEBUG << "TimeRulerLayer::snapToFeatureFrame: type "
Chris@272 77 // << int(snap) << ", frame " << frame << " (time "
Chris@272 78 // << rt << ", tick " << rtick << ", rounded " << rdrt << ") ";
Chris@272 79
Chris@271 80 switch (snap) {
Chris@271 81
Chris@271 82 case SnapLeft:
Chris@271 83 frame = left;
Chris@271 84 break;
Chris@271 85
Chris@271 86 case SnapRight:
Chris@271 87 frame = right;
Chris@271 88 break;
Chris@271 89
Chris@271 90 case SnapNearest:
Chris@271 91 {
Chris@989 92 if (llabs(frame - left) > llabs(right - frame)) {
Chris@271 93 frame = right;
Chris@271 94 } else {
Chris@271 95 frame = left;
Chris@271 96 }
Chris@271 97 break;
Chris@271 98 }
Chris@271 99
Chris@271 100 case SnapNeighbouring:
Chris@271 101 {
Chris@271 102 int dl = -1, dr = -1;
Chris@271 103 int x = v->getXForFrame(frame);
Chris@271 104
Chris@271 105 if (left > v->getStartFrame() &&
Chris@271 106 left < v->getEndFrame()) {
Chris@271 107 dl = abs(v->getXForFrame(left) - x);
Chris@271 108 }
Chris@271 109
Chris@271 110 if (right > v->getStartFrame() &&
Chris@271 111 right < v->getEndFrame()) {
Chris@271 112 dr = abs(v->getXForFrame(right) - x);
Chris@271 113 }
Chris@271 114
Chris@271 115 int fuzz = 2;
Chris@271 116
Chris@271 117 if (dl >= 0 && dr >= 0) {
Chris@271 118 if (dl < dr) {
Chris@271 119 if (dl <= fuzz) {
Chris@271 120 frame = left;
Chris@271 121 }
Chris@271 122 } else {
Chris@271 123 if (dr < fuzz) {
Chris@271 124 frame = right;
Chris@271 125 }
Chris@271 126 }
Chris@271 127 } else if (dl >= 0) {
Chris@271 128 if (dl <= fuzz) {
Chris@271 129 frame = left;
Chris@271 130 }
Chris@271 131 } else if (dr >= 0) {
Chris@271 132 if (dr <= fuzz) {
Chris@271 133 frame = right;
Chris@271 134 }
Chris@271 135 }
Chris@271 136 }
Chris@271 137 }
Chris@271 138
Chris@587 139 // SVDEBUG << " -> " << frame << " (resolution = " << resolution << ")" << endl;
Chris@272 140
Chris@271 141 return true;
Chris@271 142 }
Chris@271 143
Chris@271 144 int
Chris@918 145 TimeRulerLayer::getMajorTickSpacing(LayerGeometryProvider *v, bool &quarterTicks) const
Chris@271 146 {
Chris@271 147 // return value is in milliseconds
Chris@271 148
Chris@271 149 if (!m_model || !v) return 1000;
Chris@271 150
Chris@908 151 sv_samplerate_t sampleRate = m_model->getSampleRate();
Chris@271 152 if (!sampleRate) return 1000;
Chris@271 153
Chris@908 154 sv_frame_t startFrame = v->getStartFrame();
Chris@908 155 sv_frame_t endFrame = v->getEndFrame();
Chris@271 156
Chris@271 157 int minPixelSpacing = 50;
Chris@271 158
Chris@271 159 RealTime rtStart = RealTime::frame2RealTime(startFrame, sampleRate);
Chris@271 160 RealTime rtEnd = RealTime::frame2RealTime(endFrame, sampleRate);
Chris@271 161
Chris@918 162 int count = v->getPaintWidth() / minPixelSpacing;
Chris@271 163 if (count < 1) count = 1;
Chris@271 164 RealTime rtGap = (rtEnd - rtStart) / count;
Chris@271 165
Chris@271 166 int incms;
Chris@271 167 quarterTicks = false;
Chris@271 168
Chris@271 169 if (rtGap.sec > 0) {
Chris@1266 170 incms = 1000;
Chris@1266 171 int s = rtGap.sec;
Chris@1266 172 if (s > 0) { incms *= 5; s /= 5; }
Chris@1266 173 if (s > 0) { incms *= 2; s /= 2; }
Chris@1266 174 if (s > 0) { incms *= 6; s /= 6; quarterTicks = true; }
Chris@1266 175 if (s > 0) { incms *= 5; s /= 5; quarterTicks = false; }
Chris@1266 176 if (s > 0) { incms *= 2; s /= 2; }
Chris@1266 177 if (s > 0) { incms *= 6; s /= 6; quarterTicks = true; }
Chris@1266 178 while (s > 0) {
Chris@1266 179 incms *= 10;
Chris@1266 180 s /= 10;
Chris@1266 181 quarterTicks = false;
Chris@1266 182 }
Chris@271 183 } else {
Chris@1266 184 incms = 1;
Chris@1266 185 int ms = rtGap.msec();
Chris@1021 186 // cerr << "rtGap.msec = " << ms << ", rtGap = " << rtGap << ", count = " << count << endl;
Chris@1021 187 // cerr << "startFrame = " << startFrame << ", endFrame = " << endFrame << " rtStart = " << rtStart << ", rtEnd = " << rtEnd << endl;
Chris@1266 188 if (ms > 0) { incms *= 10; ms /= 10; }
Chris@1266 189 if (ms > 0) { incms *= 10; ms /= 10; }
Chris@1266 190 if (ms > 0) { incms *= 5; ms /= 5; }
Chris@1266 191 if (ms > 0) { incms *= 2; ms /= 2; }
Chris@271 192 }
Chris@271 193
Chris@271 194 return incms;
Chris@271 195 }
Chris@271 196
Chris@0 197 void
Chris@916 198 TimeRulerLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@0 199 {
Chris@370 200 #ifdef DEBUG_TIME_RULER_LAYER
Chris@587 201 SVDEBUG << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y()
Chris@1266 202 << ") [" << rect.width() << "x" << rect.height() << "]" << endl;
Chris@370 203 #endif
Chris@0 204
Chris@0 205 if (!m_model || !m_model->isOK()) return;
Chris@0 206
Chris@908 207 sv_samplerate_t sampleRate = m_model->getSampleRate();
Chris@0 208 if (!sampleRate) return;
Chris@0 209
Chris@908 210 sv_frame_t startFrame = v->getFrameForX(rect.x() - 50);
Chris@0 211
Chris@370 212 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 213 cerr << "start frame = " << startFrame << endl;
Chris@370 214 #endif
Chris@0 215
Chris@0 216 bool quarter = false;
Chris@271 217 int incms = getMajorTickSpacing(v, quarter);
Chris@0 218
Chris@908 219 int ms = int(lrint(1000.0 * (double(startFrame) / double(sampleRate))));
Chris@0 220 ms = (ms / incms) * incms - incms;
Chris@0 221
Chris@370 222 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 223 cerr << "start ms = " << ms << " at step " << incms << endl;
Chris@370 224 #endif
Chris@370 225
Chris@370 226 // Calculate the number of ticks per increment -- approximate
Chris@370 227 // values for x and frame counts here will do, no rounding issue.
Chris@370 228 // We always use the exact incms in our calculations for where to
Chris@370 229 // draw the actual ticks or lines.
Chris@370 230
Chris@370 231 int minPixelSpacing = 50;
Chris@908 232 sv_frame_t incFrame = lrint((incms * sampleRate) / 1000);
Chris@1326 233 int incX = int(round(v->getZoomLevel().framesToPixels(double(incFrame))));
Chris@0 234 int ticks = 10;
Chris@0 235 if (incX < minPixelSpacing * 2) {
Chris@1266 236 ticks = quarter ? 4 : 5;
Chris@0 237 }
Chris@0 238
Chris@370 239 QColor greyColour = getPartialShades(v)[1];
Chris@0 240
Chris@370 241 paint.save();
Chris@0 242
Chris@832 243 // Do not label time zero - we now overlay an opaque area over
Chris@832 244 // time < 0 which would cut it in half
Chris@832 245 int minlabel = 1; // ms
Chris@832 246
Chris@1021 247 // used for a sanity check
Chris@1021 248 sv_frame_t prevframe = 0;
Chris@1021 249
Chris@0 250 while (1) {
Chris@0 251
Chris@370 252 // frame is used to determine where to draw the lines, so it
Chris@370 253 // needs to correspond to an exact pixel (so that we don't get
Chris@370 254 // a different pixel when scrolling a small amount and
Chris@370 255 // re-drawing with a different start frame).
Chris@370 256
Chris@370 257 double dms = ms;
Chris@908 258 sv_frame_t frame = lrint((dms * sampleRate) / 1000.0);
Chris@1325 259 ZoomLevel zoom = v->getZoomLevel();
Chris@1325 260 if (zoom.zone == ZoomLevel::FramesPerPixel) {
Chris@1325 261 frame /= zoom.level;
Chris@1325 262 frame *= zoom.level; // so frame corresponds to an exact pixel
Chris@1325 263 }
Chris@370 264
Chris@1021 265 if (frame == prevframe && prevframe != 0) {
Chris@1021 266 cerr << "ERROR: frame == prevframe (== " << frame
Chris@1021 267 << ") in TimeRulerLayer::paint" << endl;
Chris@1021 268 throw std::logic_error("frame == prevframe in TimeRulerLayer::paint");
Chris@1021 269 }
Chris@1021 270 prevframe = frame;
Chris@1021 271
Chris@370 272 int x = v->getXForFrame(frame);
Chris@0 273
Chris@370 274 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1021 275 cerr << "Considering frame = " << frame << ", x = " << x << endl;
Chris@370 276 #endif
Chris@0 277
Chris@370 278 if (x >= rect.x() + rect.width() + 50) {
Chris@370 279 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 280 cerr << "X well out of range, ending here" << endl;
Chris@370 281 #endif
Chris@370 282 break;
Chris@370 283 }
Chris@0 284
Chris@1266 285 if (x >= rect.x() - 50 && ms >= minlabel) {
Chris@0 286
Chris@375 287 RealTime rt = RealTime::fromMilliseconds(ms);
Chris@375 288
Chris@370 289 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 290 cerr << "X in range, drawing line here for time " << rt.toText() << endl;
Chris@370 291 #endif
Chris@0 292
Chris@370 293 QString text(QString::fromStdString(rt.toText()));
Chris@370 294 QFontMetrics metrics = paint.fontMetrics();
Chris@370 295 int tw = metrics.width(text);
Chris@0 296
Chris@370 297 if (tw < 50 &&
Chris@370 298 (x < rect.x() - tw/2 ||
Chris@370 299 x >= rect.x() + rect.width() + tw/2)) {
Chris@370 300 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 301 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 302 #endif
Chris@370 303 }
Chris@70 304
Chris@370 305 paint.setPen(greyColour);
Chris@918 306 paint.drawLine(x, 0, x, v->getPaintHeight());
Chris@370 307
Chris@370 308 paint.setPen(getBaseQColor());
Chris@370 309 paint.drawLine(x, 0, x, 5);
Chris@918 310 paint.drawLine(x, v->getPaintHeight() - 6, x, v->getPaintHeight() - 1);
Chris@370 311
Chris@370 312 int y;
Chris@370 313 switch (m_labelHeight) {
Chris@370 314 default:
Chris@370 315 case LabelTop:
Chris@370 316 y = 6 + metrics.ascent();
Chris@370 317 break;
Chris@370 318 case LabelMiddle:
Chris@918 319 y = v->getPaintHeight() / 2 - metrics.height() / 2 + metrics.ascent();
Chris@370 320 break;
Chris@370 321 case LabelBottom:
Chris@918 322 y = v->getPaintHeight() - metrics.height() + metrics.ascent() - 6;
Chris@370 323 }
Chris@370 324
Chris@370 325 if (v->getViewManager() && v->getViewManager()->getOverlayMode() !=
Chris@370 326 ViewManager::NoOverlays) {
Chris@370 327
Chris@919 328 if (v->getView()->getLayer(0) == this) {
Chris@370 329 // backmost layer, don't worry about outlining the text
Chris@370 330 paint.drawText(x+2 - tw/2, y, text);
Chris@370 331 } else {
Chris@1078 332 PaintAssistant::drawVisibleText(v, paint, x+2 - tw/2, y, text, PaintAssistant::OutlinedText);
Chris@370 333 }
Chris@70 334 }
Chris@70 335 }
Chris@0 336
Chris@1266 337 paint.setPen(greyColour);
Chris@0 338
Chris@1266 339 for (int i = 1; i < ticks; ++i) {
Chris@370 340
Chris@380 341 dms = ms + (i * double(incms)) / ticks;
Chris@370 342 frame = lrint((dms * sampleRate) / 1000.0);
Chris@1325 343 if (zoom.zone == ZoomLevel::FramesPerPixel) {
Chris@1325 344 frame /= zoom.level;
Chris@1325 345 frame *= zoom.level; // exact pixel as above
Chris@1325 346 }
Chris@370 347
Chris@370 348 x = v->getXForFrame(frame);
Chris@370 349
Chris@370 350 if (x < rect.x() || x >= rect.x() + rect.width()) {
Chris@370 351 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 352 // cerr << "tick " << i << ": X out of range, going on to next tick" << endl;
Chris@370 353 #endif
Chris@370 354 continue;
Chris@370 355 }
Chris@370 356
Chris@370 357 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 358 cerr << "tick " << i << " in range, drawing at " << x << endl;
Chris@370 359 #endif
Chris@370 360
Chris@1266 361 int sz = 5;
Chris@1266 362 if (ticks == 10) {
Chris@1266 363 if ((i % 2) == 1) {
Chris@1266 364 if (i == 5) {
Chris@1266 365 paint.drawLine(x, 0, x, v->getPaintHeight());
Chris@1266 366 } else sz = 3;
Chris@1266 367 } else {
Chris@1266 368 sz = 7;
Chris@1266 369 }
Chris@1266 370 }
Chris@1266 371 paint.drawLine(x, 0, x, sz);
Chris@1266 372 paint.drawLine(x, v->getPaintHeight() - sz - 1, x, v->getPaintHeight() - 1);
Chris@1266 373 }
Chris@375 374
Chris@1266 375 ms += incms;
Chris@0 376 }
Chris@0 377
Chris@0 378 paint.restore();
Chris@0 379 }
Chris@287 380
Chris@287 381 int
Chris@287 382 TimeRulerLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 383 {
Chris@287 384 impose = true;
Chris@287 385 return ColourDatabase::getInstance()->getColourIndex
Chris@287 386 (QString(darkbg ? "White" : "Black"));
Chris@287 387 }
Chris@287 388
Chris@335 389 QString TimeRulerLayer::getLayerPresentationName() const
Chris@335 390 {
Chris@335 391 LayerFactory *factory = LayerFactory::getInstance();
Chris@335 392 QString layerName = factory->getLayerPresentationName
Chris@335 393 (factory->getLayerType(this));
Chris@335 394 return layerName;
Chris@335 395 }
Chris@335 396
Chris@316 397 void
Chris@316 398 TimeRulerLayer::toXml(QTextStream &stream,
Chris@316 399 QString indent, QString extraAttributes) const
Chris@6 400 {
Chris@316 401 SingleColourLayer::toXml(stream, indent, extraAttributes);
Chris@6 402 }
Chris@6 403
Chris@11 404 void
Chris@11 405 TimeRulerLayer::setProperties(const QXmlAttributes &attributes)
Chris@11 406 {
Chris@287 407 SingleColourLayer::setProperties(attributes);
Chris@11 408 }
Chris@11 409