annotate layer/TimeRulerLayer.cpp @ 1024:3bce4c45b681 spectrogram-minor-refactor

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