annotate layer/TimeRulerLayer.cpp @ 789:9fd1bdf214dd tonioni

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