annotate layer/TimeRulerLayer.cpp @ 561:aced8ec09bc8

* Complete the overhaul of CSV file import; now you can pick the purpose for each column in the file, and SV should do the rest. The most significant practical improvement here is that we can now handle files in which time and duration do not necessarily appear in known columns.
author Chris Cannam
date Mon, 19 Jul 2010 17:08:56 +0000
parents a6408c382616
children f4960f8ce798
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@0 32 using std::cerr;
Chris@0 33 using std::endl;
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@272 75 // std::cerr << "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@272 138 // std::cerr << " -> " << frame << " (resolution = " << resolution << ")" << std::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@370 198 std::cerr << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y()
Chris@370 199 << ") [" << rect.width() << "x" << rect.height() << "]" << std::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@370 210 std::cerr << "start frame = " << startFrame << std::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@370 220 std::cerr << "start ms = " << ms << " at step " << incms << std::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@370 255 std::cerr << "Considering frame = " << frame << ", x = " << x << std::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@370 260 std::cerr << "X well out of range, ending here" << std::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@375 270 std::cerr << "X in range, drawing line here for time " << rt.toText() << std::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@370 281 std::cerr << "hm, maybe X isn't in range after all (x = " << x << ", tw = " << tw << ", rect.x() = " << rect.x() << ", rect.width() = " << rect.width() << ")" << std::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@370 330 // std::cerr << "tick " << i << ": X out of range, going on to next tick" << std::endl;
Chris@370 331 #endif
Chris@370 332 continue;
Chris@370 333 }
Chris@370 334
Chris@370 335 #ifdef DEBUG_TIME_RULER_LAYER
Chris@370 336 std::cerr << "tick " << i << " in range, drawing at " << x << std::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