annotate layer/TimeRulerLayer.cpp @ 1269:f2894944c6b8

Make the overlays at either end translucent, so they don't completely crop out any underlying text or necessary info (e.g. selection extents)
author Chris Cannam
date Thu, 19 Apr 2018 14:35:59 +0100
parents a34a2a25907c
children bc2cb82050a0
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@908 233 int incX = int(incFrame / v->getZoomLevel());
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@370 259 frame /= v->getZoomLevel();
Chris@370 260 frame *= v->getZoomLevel(); // so frame corresponds to an exact pixel
Chris@370 261
Chris@1021 262 if (frame == prevframe && prevframe != 0) {
Chris@1021 263 cerr << "ERROR: frame == prevframe (== " << frame
Chris@1021 264 << ") in TimeRulerLayer::paint" << endl;
Chris@1021 265 throw std::logic_error("frame == prevframe in TimeRulerLayer::paint");
Chris@1021 266 }
Chris@1021 267 prevframe = frame;
Chris@1021 268
Chris@370 269 int x = v->getXForFrame(frame);
Chris@0 270
Chris@370 271 #ifdef DEBUG_TIME_RULER_LAYER
Chris@1021 272 cerr << "Considering frame = " << frame << ", x = " << x << endl;
Chris@370 273 #endif
Chris@0 274
Chris@370 275 if (x >= rect.x() + rect.width() + 50) {
Chris@370 276 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 277 cerr << "X well out of range, ending here" << endl;
Chris@370 278 #endif
Chris@370 279 break;
Chris@370 280 }
Chris@0 281
Chris@1266 282 if (x >= rect.x() - 50 && ms >= minlabel) {
Chris@0 283
Chris@375 284 RealTime rt = RealTime::fromMilliseconds(ms);
Chris@375 285
Chris@370 286 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 287 cerr << "X in range, drawing line here for time " << rt.toText() << endl;
Chris@370 288 #endif
Chris@0 289
Chris@370 290 QString text(QString::fromStdString(rt.toText()));
Chris@370 291 QFontMetrics metrics = paint.fontMetrics();
Chris@370 292 int tw = metrics.width(text);
Chris@0 293
Chris@370 294 if (tw < 50 &&
Chris@370 295 (x < rect.x() - tw/2 ||
Chris@370 296 x >= rect.x() + rect.width() + tw/2)) {
Chris@370 297 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 298 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 299 #endif
Chris@370 300 }
Chris@70 301
Chris@370 302 paint.setPen(greyColour);
Chris@918 303 paint.drawLine(x, 0, x, v->getPaintHeight());
Chris@370 304
Chris@370 305 paint.setPen(getBaseQColor());
Chris@370 306 paint.drawLine(x, 0, x, 5);
Chris@918 307 paint.drawLine(x, v->getPaintHeight() - 6, x, v->getPaintHeight() - 1);
Chris@370 308
Chris@370 309 int y;
Chris@370 310 switch (m_labelHeight) {
Chris@370 311 default:
Chris@370 312 case LabelTop:
Chris@370 313 y = 6 + metrics.ascent();
Chris@370 314 break;
Chris@370 315 case LabelMiddle:
Chris@918 316 y = v->getPaintHeight() / 2 - metrics.height() / 2 + metrics.ascent();
Chris@370 317 break;
Chris@370 318 case LabelBottom:
Chris@918 319 y = v->getPaintHeight() - metrics.height() + metrics.ascent() - 6;
Chris@370 320 }
Chris@370 321
Chris@370 322 if (v->getViewManager() && v->getViewManager()->getOverlayMode() !=
Chris@370 323 ViewManager::NoOverlays) {
Chris@370 324
Chris@919 325 if (v->getView()->getLayer(0) == this) {
Chris@370 326 // backmost layer, don't worry about outlining the text
Chris@370 327 paint.drawText(x+2 - tw/2, y, text);
Chris@370 328 } else {
Chris@1078 329 PaintAssistant::drawVisibleText(v, paint, x+2 - tw/2, y, text, PaintAssistant::OutlinedText);
Chris@370 330 }
Chris@70 331 }
Chris@70 332 }
Chris@0 333
Chris@1266 334 paint.setPen(greyColour);
Chris@0 335
Chris@1266 336 for (int i = 1; i < ticks; ++i) {
Chris@370 337
Chris@380 338 dms = ms + (i * double(incms)) / ticks;
Chris@370 339 frame = lrint((dms * sampleRate) / 1000.0);
Chris@370 340 frame /= v->getZoomLevel();
Chris@370 341 frame *= v->getZoomLevel(); // exact pixel as above
Chris@370 342
Chris@370 343 x = v->getXForFrame(frame);
Chris@370 344
Chris@370 345 if (x < rect.x() || x >= rect.x() + rect.width()) {
Chris@370 346 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 347 // cerr << "tick " << i << ": X out of range, going on to next tick" << endl;
Chris@370 348 #endif
Chris@370 349 continue;
Chris@370 350 }
Chris@370 351
Chris@370 352 #ifdef DEBUG_TIME_RULER_LAYER
Chris@682 353 cerr << "tick " << i << " in range, drawing at " << x << endl;
Chris@370 354 #endif
Chris@370 355
Chris@1266 356 int sz = 5;
Chris@1266 357 if (ticks == 10) {
Chris@1266 358 if ((i % 2) == 1) {
Chris@1266 359 if (i == 5) {
Chris@1266 360 paint.drawLine(x, 0, x, v->getPaintHeight());
Chris@1266 361 } else sz = 3;
Chris@1266 362 } else {
Chris@1266 363 sz = 7;
Chris@1266 364 }
Chris@1266 365 }
Chris@1266 366 paint.drawLine(x, 0, x, sz);
Chris@1266 367 paint.drawLine(x, v->getPaintHeight() - sz - 1, x, v->getPaintHeight() - 1);
Chris@1266 368 }
Chris@375 369
Chris@1266 370 ms += incms;
Chris@0 371 }
Chris@0 372
Chris@0 373 paint.restore();
Chris@0 374 }
Chris@287 375
Chris@287 376 int
Chris@287 377 TimeRulerLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 378 {
Chris@287 379 impose = true;
Chris@287 380 return ColourDatabase::getInstance()->getColourIndex
Chris@287 381 (QString(darkbg ? "White" : "Black"));
Chris@287 382 }
Chris@287 383
Chris@335 384 QString TimeRulerLayer::getLayerPresentationName() const
Chris@335 385 {
Chris@335 386 LayerFactory *factory = LayerFactory::getInstance();
Chris@335 387 QString layerName = factory->getLayerPresentationName
Chris@335 388 (factory->getLayerType(this));
Chris@335 389 return layerName;
Chris@335 390 }
Chris@335 391
Chris@316 392 void
Chris@316 393 TimeRulerLayer::toXml(QTextStream &stream,
Chris@316 394 QString indent, QString extraAttributes) const
Chris@6 395 {
Chris@316 396 SingleColourLayer::toXml(stream, indent, extraAttributes);
Chris@6 397 }
Chris@6 398
Chris@11 399 void
Chris@11 400 TimeRulerLayer::setProperties(const QXmlAttributes &attributes)
Chris@11 401 {
Chris@287 402 SingleColourLayer::setProperties(attributes);
Chris@11 403 }
Chris@11 404