TimeRulerLayer.cpp
Go to the documentation of this file.
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4  Sonic Visualiser
5  An audio file viewer and annotation editor.
6  Centre for Digital Music, Queen Mary, University of London.
7  This file copyright 2006 Chris Cannam.
8 
9  This program is free software; you can redistribute it and/or
10  modify it under the terms of the GNU General Public License as
11  published by the Free Software Foundation; either version 2 of the
12  License, or (at your option) any later version. See the file
13  COPYING included with this distribution for more information.
14 */
15 
16 #include "TimeRulerLayer.h"
17 
18 #include "LayerFactory.h"
19 
20 #include "data/model/Model.h"
21 #include "base/RealTime.h"
22 #include "base/Preferences.h"
23 #include "view/View.h"
24 
25 #include "ColourDatabase.h"
26 #include "PaintAssistant.h"
27 
28 #include <QPainter>
29 
30 #include <iostream>
31 #include <cmath>
32 #include <stdexcept>
33 
34 //#define DEBUG_TIME_RULER_LAYER 1
35 
36 
39  m_labelHeight(LabelTop)
40 {
41 
42 }
43 
44 void
46 {
47  if (m_model == model) return;
48  m_model = model;
49  emit modelReplaced();
50 }
51 
52 bool
54  int &resolution, SnapType snap, int) const
55 {
56  auto model = ModelById::get(m_model);
57  if (!model) {
58  resolution = 1;
59  return false;
60  }
61 
62  bool q;
63  int64_t tickUSec = getMajorTickUSec(v, q);
64  RealTime rtick = RealTime::fromMicroseconds(tickUSec);
65  sv_samplerate_t rate = model->getSampleRate();
66 
67  RealTime rt = RealTime::frame2RealTime(frame, rate);
68  double ratio = rt / rtick;
69 
70  int rounded = int(ratio);
71  RealTime rdrt = rtick * rounded;
72 
73  sv_frame_t left = RealTime::realTime2Frame(rdrt, rate);
74  resolution = int(RealTime::realTime2Frame(rtick, rate));
75  sv_frame_t right = left + resolution;
76 
77 // SVDEBUG << "TimeRulerLayer::snapToFeatureFrame: type "
78 // << int(snap) << ", frame " << frame << " (time "
79 // << rt << ", tick " << rtick << ", rounded " << rdrt << ") ";
80 
81  switch (snap) {
82 
83  case SnapLeft:
84  frame = left;
85  break;
86 
87  case SnapRight:
88  frame = right;
89  break;
90 
91  case SnapNeighbouring:
92  {
93  int dl = -1, dr = -1;
94  int x = v->getXForFrame(frame);
95 
96  if (left > v->getStartFrame() &&
97  left < v->getEndFrame()) {
98  dl = abs(v->getXForFrame(left) - x);
99  }
100 
101  if (right > v->getStartFrame() &&
102  right < v->getEndFrame()) {
103  dr = abs(v->getXForFrame(right) - x);
104  }
105 
106  int fuzz = ViewManager::scalePixelSize(2);
107 
108  if (dl >= 0 && dr >= 0) {
109  if (dl < dr) {
110  if (dl <= fuzz) {
111  frame = left;
112  }
113  } else {
114  if (dr < fuzz) {
115  frame = right;
116  }
117  }
118  } else if (dl >= 0) {
119  if (dl <= fuzz) {
120  frame = left;
121  }
122  } else if (dr >= 0) {
123  if (dr <= fuzz) {
124  frame = right;
125  }
126  }
127  }
128  }
129 
130 // SVDEBUG << " -> " << frame << " (resolution = " << resolution << ")" << endl;
131 
132  return true;
133 }
134 
135 int64_t
137  bool &quarterTicks) const
138 {
139  // return value is in microseconds
140  auto model = ModelById::get(m_model);
141  if (!model || !v) return 1000 * 1000;
142 
143  sv_samplerate_t sampleRate = model->getSampleRate();
144  if (!sampleRate) return 1000 * 1000;
145 
146  sv_frame_t startFrame = v->getStartFrame();
147  sv_frame_t endFrame = v->getEndFrame();
148  if (endFrame == startFrame) {
149  endFrame = startFrame + 1;
150  }
151 
152  // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
153  // replacement (horizontalAdvance) was only added in Qt 5.11
154  // which is too new for us
155 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
156 
157  int exampleWidth = QFontMetrics(QFont()).width("10:42.987654");
158  int minPixelSpacing = v->getXForViewX(exampleWidth);
159 
160  RealTime rtStart = RealTime::frame2RealTime(startFrame, sampleRate);
161  RealTime rtEnd = RealTime::frame2RealTime(endFrame, sampleRate);
162 
163  int count = v->getPaintWidth() / minPixelSpacing;
164  if (count < 1) count = 1;
165  RealTime rtGap = (rtEnd - rtStart) / count;
166 
167 #ifdef DEBUG_TIME_RULER_LAYER
168  SVCERR << "zoomLevel = " << v->getZoomLevel()
169  << ", startFrame = " << startFrame << ", endFrame = " << endFrame
170  << ", rtStart = " << rtStart << ", rtEnd = " << rtEnd
171  << ", paint width = " << v->getPaintWidth()
172  << ", minPixelSpacing = " << minPixelSpacing
173  << ", count = " << count << ", rtGap = " << rtGap << endl;
174 #endif
175 
176  int64_t incus;
177  quarterTicks = false;
178 
179  if (rtGap.sec > 0) {
180  incus = 1000 * 1000;
181  int s = rtGap.sec;
182  if (s > 0) { incus *= 5; s /= 5; }
183  if (s > 0) { incus *= 2; s /= 2; }
184  if (s > 0) { incus *= 6; s /= 6; quarterTicks = true; }
185  if (s > 0) { incus *= 5; s /= 5; quarterTicks = false; }
186  if (s > 0) { incus *= 2; s /= 2; }
187  if (s > 0) { incus *= 6; s /= 6; quarterTicks = true; }
188  while (s > 0) {
189  incus *= 10;
190  s /= 10;
191  quarterTicks = false;
192  }
193  } else if (rtGap.msec() > 0) {
194  incus = 1000;
195  int ms = rtGap.msec();
196  if (ms > 0) { incus *= 10; ms /= 10; }
197  if (ms > 0) { incus *= 10; ms /= 10; }
198  if (ms > 0) { incus *= 5; ms /= 5; }
199  if (ms > 0) { incus *= 2; ms /= 2; }
200  } else {
201  incus = 1;
202  int us = rtGap.usec();
203  if (us > 0) { incus *= 10; us /= 10; }
204  if (us > 0) { incus *= 10; us /= 10; }
205  if (us > 0) { incus *= 5; us /= 5; }
206  if (us > 0) { incus *= 2; us /= 2; }
207  }
208 
209 #ifdef DEBUG_TIME_RULER_LAYER
210  SVCERR << "getMajorTickUSec: returning incus = " << incus << endl;
211 #endif
212 
213  return incus;
214 }
215 
216 int
218 {
219  auto model = ModelById::get(m_model);
220  sv_samplerate_t sampleRate = model->getSampleRate();
221  double dframe = (us * sampleRate) / 1000000.0;
222  double eps = 1e-7;
223  sv_frame_t frame = sv_frame_t(floor(dframe + eps));
224  int x;
225 
226  ZoomLevel zoom = v->getZoomLevel();
227 
228  if (zoom.zone == ZoomLevel::FramesPerPixel) {
229 
230  frame /= zoom.level;
231  frame *= zoom.level; // so frame corresponds to an exact pixel
232 
233  x = v->getXForFrame(frame);
234 
235  } else {
236 
237  double off = dframe - double(frame);
238  int x0 = v->getXForFrame(frame);
239  int x1 = v->getXForFrame(frame + 1);
240 
241  x = int(x0 + off * (x1 - x0));
242  }
243 
244 #ifdef DEBUG_TIME_RULER_LAYER
245  cerr << "Considering frame = " << frame << ", x = " << x << endl;
246 #endif
247 
248  return x;
249 }
250 
251 void
252 TimeRulerLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
253 {
254 #ifdef DEBUG_TIME_RULER_LAYER
255  SVCERR << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y()
256  << ") [" << rect.width() << "x" << rect.height() << "]" << endl;
257 #endif
258 
259  auto model = ModelById::get(m_model);
260  if (!model || !model->isOK()) return;
261 
262  sv_samplerate_t sampleRate = model->getSampleRate();
263  if (!sampleRate) return;
264 
265  sv_frame_t startFrame = v->getFrameForX(rect.x() - 50);
266 
267 #ifdef DEBUG_TIME_RULER_LAYER
268  SVCERR << "start frame = " << startFrame << endl;
269 #endif
270 
271  bool quarter = false;
272  int64_t incus = getMajorTickUSec(v, quarter);
273  int64_t us = int64_t(floor(1000.0 * 1000.0 * (double(startFrame) /
274  double(sampleRate))));
275  us = (us / incus) * incus - incus;
276 
277 #ifdef DEBUG_TIME_RULER_LAYER
278  SVCERR << "start us = " << us << " at step " << incus << endl;
279 #endif
280 
281  Preferences *prefs = Preferences::getInstance();
282  auto origTimeTextMode = prefs->getTimeToTextMode();
283  if (incus < 1000) {
284  // Temporarily switch to usec display mode (if we aren't using
285  // it already)
286  prefs->blockSignals(true);
287  prefs->setTimeToTextMode(Preferences::TimeToTextUs);
288  }
289 
290  // Calculate the number of ticks per increment -- approximate
291  // values for x and frame counts here will do, no rounding issue.
292  // We always use the exact incus in our calculations for where to
293  // draw the actual ticks or lines.
294 
295  int minPixelSpacing = v->getXForViewX(50);
296  sv_frame_t incFrame = lrint((double(incus) * sampleRate) / 1000000);
297  int incX = int(round(v->getZoomLevel().framesToPixels(double(incFrame))));
298  int ticks = 10;
299  if (incX < minPixelSpacing * 2) {
300  ticks = quarter ? 4 : 5;
301  }
302 
303  QColor greyColour = getPartialShades(v)[1];
304 
305  paint.save();
306 
307  // Do not label time zero - we now overlay an opaque area over
308  // time < 0 which would cut it in half
309  int minlabel = 1; // us
310 
311  while (1) {
312 
313  // frame is used to determine where to draw the lines, so it
314  // needs to correspond to an exact pixel (so that we don't get
315  // a different pixel when scrolling a small amount and
316  // re-drawing with a different start frame).
317 
318  double dus = double(us);
319 
320  int x = getXForUSec(v, dus);
321 
322  if (x >= rect.x() + rect.width() + 50) {
323 #ifdef DEBUG_TIME_RULER_LAYER
324  SVCERR << "X well out of range, ending here" << endl;
325 #endif
326  break;
327  }
328 
329  if (x >= rect.x() - 50 && us >= minlabel) {
330 
331  RealTime rt = RealTime::fromMicroseconds(us);
332 
333 #ifdef DEBUG_TIME_RULER_LAYER
334  SVCERR << "X in range, drawing line here for time " << rt.toText() << " (usec = " << us << ")" << endl;
335 #endif
336 
337  QString text(QString::fromStdString(rt.toText()));
338 
339  QFontMetrics metrics = paint.fontMetrics();
340  int tw = metrics.width(text);
341 
342  if (tw < 50 &&
343  (x < rect.x() - tw/2 ||
344  x >= rect.x() + rect.width() + tw/2)) {
345 #ifdef DEBUG_TIME_RULER_LAYER
346  SVCERR << "hm, maybe X isn't in range after all (x = " << x << ", tw = " << tw << ", rect.x() = " << rect.x() << ", rect.width() = " << rect.width() << ")" << endl;
347 #endif
348  }
349 
350  paint.setPen(greyColour);
351  paint.drawLine(x, 0, x, v->getPaintHeight());
352 
353  paint.setPen(getBaseQColor());
354  paint.drawLine(x, 0, x, 5);
355  paint.drawLine(x, v->getPaintHeight() - 6, x, v->getPaintHeight() - 1);
356 
357  int y;
358  switch (m_labelHeight) {
359  default:
360  case LabelTop:
361  y = 6 + metrics.ascent();
362  break;
363  case LabelMiddle:
364  y = v->getPaintHeight() / 2 - metrics.height() / 2 + metrics.ascent();
365  break;
366  case LabelBottom:
367  y = v->getPaintHeight() - metrics.height() + metrics.ascent() - 6;
368  }
369 
370  if (v->getViewManager() && v->getViewManager()->getOverlayMode() !=
372 
373  if (v->getView()->getLayer(0) == this) {
374  // backmost layer, don't worry about outlining the text
375  paint.drawText(x+2 - tw/2, y, text);
376  } else {
377  PaintAssistant::drawVisibleText(v, paint, x+2 - tw/2, y, text, PaintAssistant::OutlinedText);
378  }
379  }
380  }
381 
382  paint.setPen(greyColour);
383 
384  for (int i = 1; i < ticks; ++i) {
385 
386  dus = double(us) + (i * double(incus)) / ticks;
387 
388  x = getXForUSec(v, dus);
389 
390  if (x < rect.x() || x >= rect.x() + rect.width()) {
391 #ifdef DEBUG_TIME_RULER_LAYER
392 // SVCERR << "tick " << i << ": X out of range, going on to next tick" << endl;
393 #endif
394  continue;
395  }
396 
397 #ifdef DEBUG_TIME_RULER_LAYER
398  SVCERR << "tick " << i << " in range, drawing at " << x << endl;
399 #endif
400 
401  int sz = 5;
402  if (ticks == 10) {
403  if ((i % 2) == 1) {
404  if (i == 5) {
405  paint.drawLine(x, 0, x, v->getPaintHeight());
406  } else sz = 3;
407  } else {
408  sz = 7;
409  }
410  }
411  paint.drawLine(x, 0, x, sz);
412  paint.drawLine(x, v->getPaintHeight() - sz - 1, x, v->getPaintHeight() - 1);
413  }
414 
415  us += incus;
416  }
417 
418  prefs->setTimeToTextMode(origTimeTextMode);
419  prefs->blockSignals(false);
420 
421  paint.restore();
422 }
423 
424 int
425 TimeRulerLayer::getDefaultColourHint(bool darkbg, bool &impose)
426 {
427  impose = true;
429  (QString(darkbg ? "White" : "Black"));
430 }
431 
433 {
435  QString layerName = factory->getLayerPresentationName
436  (factory->getLayerType(this));
437  return layerName;
438 }
439 
440 void
441 TimeRulerLayer::toXml(QTextStream &stream,
442  QString indent, QString extraAttributes) const
443 {
444  SingleColourLayer::toXml(stream, indent, extraAttributes);
445 }
446 
447 void
448 TimeRulerLayer::setProperties(const QXmlAttributes &attributes)
449 {
451 }
452 
static LayerFactory * getInstance()
virtual int getXForViewX(int viewx) const =0
Return the closest pixel x-coordinate corresponding to a given view x-coordinate. ...
int getXForUSec(LayerGeometryProvider *, double usec) const
int getDefaultColourHint(bool dark, bool &impose) override
virtual ZoomLevel getZoomLevel() const =0
Return the zoom level, i.e.
static int scalePixelSize(int pixels)
Take a "design pixel" size and scale it for the actual display.
void modelReplaced()
void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const override
Paint the given rectangle of this layer onto the given view using the given painter, superimposing it on top of any existing material in that view.
virtual sv_frame_t getFrameForX(int x) const =0
Return the closest frame to the given pixel x-coordinate.
void toXml(QTextStream &stream, QString indent="", QString extraAttributes="") const override
std::vector< QColor > getPartialShades(LayerGeometryProvider *v) const
virtual QColor getBaseQColor() const
void toXml(QTextStream &stream, QString indent="", QString extraAttributes="") const override
void setProperties(const QXmlAttributes &attributes) override
Set the particular properties of a layer (those specific to the subclass) from a set of XML attribute...
Interface for classes that provide geometry information (such as size, start frame, and a large number of other properties) about the disposition of a layer.
QString getLayerPresentationName(LayerType type)
int getColourIndex(QString name) const
Return the index of the colour with the given name, if found in the database.
virtual sv_frame_t getStartFrame() const =0
Retrieve the first visible sample frame on the widget.
SnapType
Definition: Layer.h:195
bool snapToFeatureFrame(LayerGeometryProvider *, sv_frame_t &, int &, SnapType, int) const override
Adjust the given frame to snap to the nearest feature, if possible.
QString getLayerPresentationName() const override
virtual int getPaintHeight() const
virtual Layer * getLayer(int n)
Return the nth layer, counted in stacking order.
Definition: View.h:205
void setModel(ModelId)
void setProperties(const QXmlAttributes &attributes) override
Set the particular properties of a layer (those specific to the subclass) from a set of XML attribute...
LayerType getLayerType(const Layer *)
virtual ViewManager * getViewManager() const =0
LabelHeight m_labelHeight
OverlayMode getOverlayMode() const
Definition: ViewManager.h:208
static void drawVisibleText(const LayerGeometryProvider *, QPainter &p, int x, int y, QString text, TextStyle style)
virtual sv_frame_t getEndFrame() const =0
Retrieve the last visible sample frame on the widget.
virtual int getXForFrame(sv_frame_t frame) const =0
Return the pixel x-coordinate corresponding to a given sample frame (which may be negative)...
virtual int getPaintWidth() const
static ColourDatabase * getInstance()
virtual View * getView()=0
int64_t getMajorTickUSec(LayerGeometryProvider *, bool &quarterTicks) const