BoxLayer.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 
8  This program is free software; you can redistribute it and/or
9  modify it under the terms of the GNU General Public License as
10  published by the Free Software Foundation; either version 2 of the
11  License, or (at your option) any later version. See the file
12  COPYING included with this distribution for more information.
13 */
14 
15 #include "BoxLayer.h"
16 
17 #include "data/model/Model.h"
18 #include "base/RealTime.h"
19 #include "base/Profiler.h"
20 #include "base/LogRange.h"
21 
22 #include "ColourDatabase.h"
23 #include "ColourMapper.h"
24 #include "LinearNumericalScale.h"
25 #include "LogNumericalScale.h"
26 #include "PaintAssistant.h"
27 
28 #include "view/View.h"
29 
30 #include "data/model/BoxModel.h"
31 
32 #include "widgets/ItemEditDialog.h"
33 #include "widgets/TextAbbrev.h"
34 
35 #include <QPainter>
36 #include <QPainterPath>
37 #include <QMouseEvent>
38 #include <QTextStream>
39 #include <QMessageBox>
40 
41 #include <iostream>
42 #include <cmath>
43 
46  m_editing(false),
47  m_dragPointX(0),
48  m_dragPointY(0),
49  m_dragStartX(0),
50  m_dragStartY(0),
51  m_originalPoint(0, 0.0, 0, tr("New Box")),
52  m_editingPoint(0, 0.0, 0, tr("New Box")),
53  m_editingCommand(nullptr),
54  m_verticalScale(AutoAlignScale)
55 {
56 
57 }
58 
59 int
61 {
62  auto model = ModelById::get(m_model);
63  if (model) return model->getCompletion();
64  else return 0;
65 }
66 
67 void
68 BoxLayer::setModel(ModelId modelId)
69 {
70  auto oldModel = ModelById::getAs<BoxModel>(m_model);
71  auto newModel = ModelById::getAs<BoxModel>(modelId);
72 
73  if (!modelId.isNone() && !newModel) {
74  throw std::logic_error("Not a BoxModel");
75  }
76 
77  if (m_model == modelId) return;
78  m_model = modelId;
79 
80  if (newModel) {
82  }
83 
84  emit modelReplaced();
85 }
86 
87 Layer::PropertyList
89 {
90  PropertyList list = SingleColourLayer::getProperties();
91  list.push_back("Vertical Scale");
92  list.push_back("Scale Units");
93  return list;
94 }
95 
96 QString
97 BoxLayer::getPropertyLabel(const PropertyName &name) const
98 {
99  if (name == "Vertical Scale") return tr("Vertical Scale");
100  if (name == "Scale Units") return tr("Scale Units");
102 }
103 
104 Layer::PropertyType
105 BoxLayer::getPropertyType(const PropertyName &name) const
106 {
107  if (name == "Vertical Scale") return ValueProperty;
108  if (name == "Scale Units") return UnitsProperty;
110 }
111 
112 QString
113 BoxLayer::getPropertyGroupName(const PropertyName &name) const
114 {
115  if (name == "Vertical Scale" || name == "Scale Units") {
116  return tr("Scale");
117  }
119 }
120 
121 int
122 BoxLayer::getPropertyRangeAndValue(const PropertyName &name,
123  int *min, int *max, int *deflt) const
124 {
125  int val = 0;
126 
127  if (name == "Vertical Scale") {
128 
129  if (min) *min = 0;
130  if (max) *max = 2;
131  if (deflt) *deflt = int(LinearScale);
132 
133  val = int(m_verticalScale);
134 
135  } else if (name == "Scale Units") {
136 
137  if (deflt) *deflt = 0;
138  auto model = ModelById::getAs<BoxModel>(m_model);
139  if (model) {
140  val = UnitDatabase::getInstance()->getUnitId
141  (model->getScaleUnits());
142  }
143 
144  } else {
145  val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
146  }
147 
148  return val;
149 }
150 
151 QString
152 BoxLayer::getPropertyValueLabel(const PropertyName &name,
153  int value) const
154 {
155  if (name == "Vertical Scale") {
156  switch (value) {
157  default:
158  case 0: return tr("Auto-Align");
159  case 1: return tr("Linear");
160  case 2: return tr("Log");
161  }
162  }
163  return SingleColourLayer::getPropertyValueLabel(name, value);
164 }
165 
166 void
167 BoxLayer::setProperty(const PropertyName &name, int value)
168 {
169  if (name == "Vertical Scale") {
171  } else if (name == "Scale Units") {
172  auto model = ModelById::getAs<BoxModel>(m_model);
173  if (model) {
174  model->setScaleUnits
175  (UnitDatabase::getInstance()->getUnitById(value));
176  emit modelChanged(m_model);
177  }
178  } else {
179  return SingleColourLayer::setProperty(name, value);
180  }
181 }
182 
183 void
185 {
186  if (m_verticalScale == scale) return;
187  m_verticalScale = scale;
188  emit layerParametersChanged();
189 }
190 
191 bool
193 {
194  QPoint discard;
195  return !v->shouldIlluminateLocalFeatures(this, discard);
196 }
197 
198 bool
199 BoxLayer::getValueExtents(double &min, double &max,
200  bool &logarithmic, QString &unit) const
201 {
202  auto model = ModelById::getAs<BoxModel>(m_model);
203  if (!model) return false;
204  min = model->getValueMinimum();
205  max = model->getValueMaximum();
206  unit = getScaleUnits();
207 
208  if (m_verticalScale == LogScale) logarithmic = true;
209 
210  return true;
211 }
212 
213 bool
214 BoxLayer::getDisplayExtents(double &min, double &max) const
215 {
216  auto model = ModelById::getAs<BoxModel>(m_model);
217  if (!model || m_verticalScale == AutoAlignScale) return false;
218 
219  min = model->getValueMinimum();
220  max = model->getValueMaximum();
221 
222  return true;
223 }
224 
225 bool
226 BoxLayer::adoptExtents(double min, double max, QString unit)
227 {
228  auto model = ModelById::getAs<BoxModel>(m_model);
229  if (!model) return false;
230 
231  SVDEBUG << "BoxLayer[" << this << "]::adoptExtents: min " << min
232  << ", max " << max << ", unit " << unit << endl;
233 
234  if (model->getScaleUnits() == "") {
235  model->setScaleUnits(unit);
236  return true;
237  } else {
238  return false;
239  }
240 }
241 
242 bool
244  Event &point) const
245 {
246  auto model = ModelById::getAs<BoxModel>(m_model);
247  if (!model || !model->isReady()) return false;
248 
249  sv_frame_t frame = v->getFrameForX(x);
250 
251  EventVector onPoints = model->getEventsCovering(frame);
252  if (onPoints.empty()) return false;
253 
254  Event bestContaining;
255  for (const auto &p: onPoints) {
256  auto r = getRange(p);
257  if (y > getYForValue(v, r.first) || y < getYForValue(v, r.second)) {
258  SVCERR << "inPoints: rejecting " << p.toXmlString() << endl;
259  continue;
260  }
261  SVCERR << "inPoints: looking at " << p.toXmlString() << endl;
262  if (bestContaining == Event()) {
263  bestContaining = p;
264  continue;
265  }
266  auto br = getRange(bestContaining);
267  if (r.first < br.first && r.second > br.second) {
268  continue;
269  }
270  if (r.first > br.first && r.second < br.second) {
271  bestContaining = p;
272  continue;
273  }
274  if (p.getFrame() > bestContaining.getFrame() &&
275  p.getFrame() + p.getDuration() <
276  bestContaining.getFrame() + bestContaining.getDuration()) {
277  bestContaining = p;
278  continue;
279  }
280  }
281 
282  if (bestContaining != Event()) {
283  point = bestContaining;
284  } else {
285  int nearestDistance = -1;
286  for (const auto &p: onPoints) {
287  const auto r = getRange(p);
288  int distance = std::min
289  (getYForValue(v, r.first) - y,
290  getYForValue(v, r.second) - y);
291  if (distance < 0) distance = -distance;
292  if (nearestDistance == -1 || distance < nearestDistance) {
293  nearestDistance = distance;
294  point = p;
295  }
296  }
297  }
298 
299  return true;
300 }
301 
302 QString
303 BoxLayer::getLabelPreceding(sv_frame_t frame) const
304 {
305  auto model = ModelById::getAs<BoxModel>(m_model);
306  if (!model) return "";
307  EventVector points = model->getEventsStartingWithin
308  (model->getStartFrame(), frame - model->getStartFrame());
309  if (!points.empty()) {
310  for (auto i = points.rbegin(); i != points.rend(); ++i) {
311  if (i->getLabel() != QString()) {
312  return i->getLabel();
313  }
314  }
315  }
316  return QString();
317 }
318 
319 QString
321  QPoint &pos) const
322 {
323  auto model = ModelById::getAs<BoxModel>(m_model);
324  if (!model || !model->getSampleRate()) return "";
325 
326  Event box;
327 
328  if (!getLocalPoint(v, pos.x(), pos.y(), box)) {
329  if (!model->isReady()) {
330  return tr("In progress");
331  } else {
332  return tr("No local points");
333  }
334  }
335 
336  RealTime rt = RealTime::frame2RealTime(box.getFrame(),
337  model->getSampleRate());
338  RealTime rd = RealTime::frame2RealTime(box.getDuration(),
339  model->getSampleRate());
340 
341  QString rangeText;
342  auto r = getRange(box);
343 
344  rangeText = tr("%1 %2 - %3 %4")
345  .arg(r.first).arg(getScaleUnits())
346  .arg(r.second).arg(getScaleUnits());
347 
348  QString text;
349 
350  if (box.getLabel() == "") {
351  text = QString(tr("Time:\t%1\nDuration:\t%2\nValue:\t%3\nNo label"))
352  .arg(rt.toText(true).c_str())
353  .arg(rd.toText(true).c_str())
354  .arg(rangeText);
355  } else {
356  text = QString(tr("Time:\t%1\nDuration:\t%2\nValue:\t%3\nLabel:\t%4"))
357  .arg(rt.toText(true).c_str())
358  .arg(rd.toText(true).c_str())
359  .arg(rangeText)
360  .arg(box.getLabel());
361  }
362 
363  pos = QPoint(v->getXForFrame(box.getFrame()),
364  getYForValue(v, box.getValue()));
365  return text;
366 }
367 
368 bool
370  sv_frame_t &frame,
371  int &resolution,
372  SnapType snap,
373  int ycoord) const
374 {
375  auto model = ModelById::getAs<BoxModel>(m_model);
376  if (!model) {
377  return Layer::snapToFeatureFrame(v, frame, resolution, snap, ycoord);
378  }
379 
380  // SnapLeft / SnapRight: return frame of nearest feature in that
381  // direction no matter how far away
382  //
383  // SnapNeighbouring: return frame of feature that would be used in
384  // an editing operation, i.e. closest feature in either direction
385  // but only if it is "close enough"
386 
387  resolution = model->getResolution();
388 
389  Event containing;
390 
391  if (getLocalPoint(v, v->getXForFrame(frame), ycoord, containing)) {
392 
393  switch (snap) {
394 
395  case SnapLeft:
396  case SnapNeighbouring:
397  frame = containing.getFrame();
398  return true;
399 
400  case SnapRight:
401  frame = containing.getFrame() + containing.getDuration();
402  return true;
403  }
404  }
405 
406  if (snap == SnapNeighbouring) {
407  return false;
408  }
409 
410  // We aren't actually contained (in time) by any single event, so
411  // seek the next one in the relevant direction
412 
413  Event e;
414 
415  if (snap == SnapLeft) {
416  if (model->getNearestEventMatching
417  (frame, [](Event) { return true; }, EventSeries::Backward, e)) {
418 
419  if (e.getFrame() + e.getDuration() < frame) {
420  frame = e.getFrame() + e.getDuration();
421  } else {
422  frame = e.getFrame();
423  }
424  return true;
425  }
426  }
427 
428  if (snap == SnapRight) {
429  if (model->getNearestEventMatching
430  (frame, [](Event) { return true; }, EventSeries::Forward, e)) {
431 
432  frame = e.getFrame();
433  return true;
434  }
435  }
436 
437  return false;
438 }
439 
440 QString
442 {
443  auto model = ModelById::getAs<BoxModel>(m_model);
444  if (model) return model->getScaleUnits();
445  else return "";
446 }
447 
448 void
450  double &min, double &max,
451  bool &log) const
452 {
453  min = 0.0;
454  max = 0.0;
455  log = false;
456 
457  auto model = ModelById::getAs<BoxModel>(m_model);
458  if (!model) return;
459 
460  QString queryUnits;
461  queryUnits = getScaleUnits();
462 
464 
465  if (!v->getVisibleExtentsForUnit(queryUnits, min, max, log)) {
466 
467  min = model->getValueMinimum();
468  max = model->getValueMaximum();
469 
470 // cerr << "BoxLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
471 
472  } else if (log) {
473 
474  LogRange::mapRange(min, max);
475 
476 // cerr << "BoxLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
477 
478  }
479 
480  } else {
481 
482  min = model->getValueMinimum();
483  max = model->getValueMaximum();
484 
485  if (m_verticalScale == LogScale) {
486  LogRange::mapRange(min, max);
487  log = true;
488  }
489  }
490 
491  if (max == min) max = min + 1.0;
492 }
493 
494 int
496 {
497  double min = 0.0, max = 0.0;
498  bool logarithmic = false;
499  int h = v->getPaintHeight();
500 
501  getScaleExtents(v, min, max, logarithmic);
502 
503 // cerr << "BoxLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl;
504 // cerr << "h = " << h << ", margin = " << margin << endl;
505 
506  if (logarithmic) {
507  val = LogRange::map(val);
508  }
509 
510  return int(h - ((val - min) * h) / (max - min));
511 }
512 
513 double
515 {
516  double min = 0.0, max = 0.0;
517  bool logarithmic = false;
518  int h = v->getPaintHeight();
519 
520  getScaleExtents(v, min, max, logarithmic);
521 
522  double val = min + (double(h - y) * double(max - min)) / h;
523 
524  if (logarithmic) {
525  val = pow(10.0, val);
526  }
527 
528  return val;
529 }
530 
531 void
533  QRect rect) const
534 {
535  auto model = ModelById::getAs<BoxModel>(m_model);
536  if (!model || !model->isOK()) return;
537 
538  sv_samplerate_t sampleRate = model->getSampleRate();
539  if (!sampleRate) return;
540 
541 // Profiler profiler("BoxLayer::paint", true);
542 
543  int x0 = rect.left() - 40;
544  int x1 = x0 + rect.width() + 80;
545 
546  sv_frame_t wholeFrame0 = v->getFrameForX(0);
547  sv_frame_t wholeFrame1 = v->getFrameForX(v->getPaintWidth());
548 
549  EventVector points(model->getEventsSpanning(wholeFrame0,
550  wholeFrame1 - wholeFrame0));
551  if (points.empty()) return;
552 
553  paint.setPen(getBaseQColor());
554 
555 // SVDEBUG << "BoxLayer::paint: resolution is "
556 // << model->getResolution() << " frames" << endl;
557 
558  double min = model->getValueMinimum();
559  double max = model->getValueMaximum();
560  if (max == min) max = min + 1.0;
561 
562  QPoint localPos;
563  Event illuminatePoint(0);
564  bool shouldIlluminate = false;
565 
566  if (v->shouldIlluminateLocalFeatures(this, localPos)) {
567  shouldIlluminate = getLocalPoint(v, localPos.x(), localPos.y(),
568  illuminatePoint);
569  }
570 
571  paint.save();
572  paint.setRenderHint(QPainter::Antialiasing, false);
573 
574  QFontMetrics fm = paint.fontMetrics();
575 
576  for (EventVector::const_iterator i = points.begin();
577  i != points.end(); ++i) {
578 
579  const Event &p(*i);
580  const auto r = getRange(p);
581 
582  int x = v->getXForFrame(p.getFrame());
583  int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x;
584  int y = getYForValue(v, r.first);
585  int h = getYForValue(v, r.second) - y;
586  int ex = x + w;
587  int gap = v->scalePixelSize(2);
588 
589  EventVector::const_iterator j = i;
590  ++j;
591 
592  if (j != points.end()) {
593  const Event &q(*j);
594  int nx = v->getXForFrame(q.getFrame());
595  if (nx < ex) ex = nx;
596  }
597 
598  if (w < 1) w = 1;
599 
600  paint.setPen(getBaseQColor());
601  paint.setBrush(Qt::NoBrush);
602 
603  if ((shouldIlluminate && illuminatePoint == p) ||
604  (m_editing && m_editingPoint == p)) {
605 
606  paint.setPen(QPen(getBaseQColor(), v->scalePixelSize(2)));
607 
608  // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
609  // replacement (horizontalAdvance) was only added in Qt 5.11
610  // which is too new for us
611 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
612 
613  if (abs(h) > 2 * fm.height()) {
614 
615  QString y0label = QString("%1 %2")
616  .arg(r.first)
617  .arg(getScaleUnits());
618 
619  QString y1label = QString("%1 %2")
620  .arg(r.second)
621  .arg(getScaleUnits());
622 
624  (v, paint,
625  x - fm.width(y0label) - gap,
626  y - fm.descent(),
628 
630  (v, paint,
631  x - fm.width(y1label) - gap,
632  y + h + fm.ascent(),
634 
635  } else {
636 
637  QString ylabel = QString("%1 %2 - %3 %4")
638  .arg(r.first)
639  .arg(getScaleUnits())
640  .arg(r.second)
641  .arg(getScaleUnits());
642 
644  (v, paint,
645  x - fm.width(ylabel) - gap,
646  y - fm.descent(),
648  }
649 
650  QString t0label = RealTime::frame2RealTime
651  (p.getFrame(), model->getSampleRate()).toText(true).c_str();
652 
653  QString t1label = RealTime::frame2RealTime
654  (p.getFrame() + p.getDuration(), model->getSampleRate())
655  .toText(true).c_str();
656 
658  (v, paint, x, y + fm.ascent() + gap,
660 
661  if (w > fm.width(t0label) + fm.width(t1label) + gap * 3) {
662 
664  (v, paint,
665  x + w - fm.width(t1label),
666  y + fm.ascent() + gap,
668 
669  } else {
670 
672  (v, paint,
673  x + w - fm.width(t1label),
674  y + fm.ascent() + fm.height() + gap,
676  }
677  }
678 
679  paint.drawRect(x, y, w, h);
680  }
681 
682  for (EventVector::const_iterator i = points.begin();
683  i != points.end(); ++i) {
684 
685  const Event &p(*i);
686 
687  QString label = p.getLabel();
688  if (label == "") continue;
689 
690  if (shouldIlluminate && illuminatePoint == p) {
691  continue; // already handled this in illumination special case
692  }
693 
694  int x = v->getXForFrame(p.getFrame());
695  int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x;
696  int y = getYForValue(v, p.getValue());
697 
698  int labelWidth = fm.width(label);
699 
700  int gap = v->scalePixelSize(2);
701 
702  if (x + w < x0 || x - labelWidth - gap > x1) {
703  continue;
704  }
705 
706  int labelX, labelY;
707 
708  labelX = x - labelWidth - gap;
709  labelY = y - fm.descent();
710 
711  PaintAssistant::drawVisibleText(v, paint, labelX, labelY, label,
713  }
714 
715  paint.restore();
716 }
717 
718 int
720  bool, QPainter &paint) const
721 {
722  auto model = ModelById::getAs<BoxModel>(m_model);
723  if (!model || m_verticalScale == AutoAlignScale) {
724  return 0;
725  } else {
726  if (m_verticalScale == LogScale) {
727  return LogNumericalScale().getWidth(v, paint);
728  } else {
729  return LinearNumericalScale().getWidth(v, paint);
730  }
731  }
732 }
733 
734 void
736  bool, QPainter &paint, QRect) const
737 {
738  auto model = ModelById::getAs<BoxModel>(m_model);
739  if (!model || model->isEmpty()) return;
740 
741  QString unit;
742  double min, max;
743  bool logarithmic;
744 
745  int w = getVerticalScaleWidth(v, false, paint);
746 
747  getScaleExtents(v, min, max, logarithmic);
748 
749  if (logarithmic) {
750  LogNumericalScale().paintVertical(v, this, paint, 0, min, max);
751  } else {
752  LinearNumericalScale().paintVertical(v, this, paint, 0, min, max);
753  }
754 
755  if (getScaleUnits() != "") {
756  int mw = w - 5;
757  paint.drawText(5,
758  5 + paint.fontMetrics().ascent(),
760  paint.fontMetrics(),
761  mw));
762  }
763 }
764 
765 void
767 {
768  auto model = ModelById::getAs<BoxModel>(m_model);
769  if (!model) return;
770 
771  sv_frame_t frame = v->getFrameForX(e->x());
772  if (frame < 0) frame = 0;
773  frame = frame / model->getResolution() * model->getResolution();
774 
775  double value = getValueForY(v, e->y());
776 
777  m_editingPoint = Event(frame, float(value), 0, "");
779 
781  m_editingCommand = new ChangeEventsCommand(m_model.untyped,
782  tr("Draw Box"));
783  m_editingCommand->add(m_editingPoint);
784 
785  m_editing = true;
786 }
787 
788 void
790 {
791  auto model = ModelById::getAs<BoxModel>(m_model);
792  if (!model || !m_editing) return;
793 
794  sv_frame_t dragFrame = v->getFrameForX(e->x());
795  if (dragFrame < 0) dragFrame = 0;
796  dragFrame = dragFrame / model->getResolution() * model->getResolution();
797 
798  sv_frame_t eventFrame = m_originalPoint.getFrame();
799  sv_frame_t eventDuration = dragFrame - eventFrame;
800  if (eventDuration < 0) {
801  eventFrame = eventFrame + eventDuration;
802  eventDuration = -eventDuration;
803  } else if (eventDuration == 0) {
804  eventDuration = model->getResolution();
805  }
806 
807  double dragValue = getValueForY(v, e->y());
808 
809  double eventValue = m_originalPoint.getValue();
810  double eventFreqDiff = dragValue - eventValue;
811  if (eventFreqDiff < 0) {
812  eventValue = eventValue + eventFreqDiff;
813  eventFreqDiff = -eventFreqDiff;
814  }
815 
818  .withFrame(eventFrame)
819  .withDuration(eventDuration)
820  .withValue(float(eventValue))
821  .withLevel(float(eventFreqDiff));
823 }
824 
825 void
827 {
828  auto model = ModelById::getAs<BoxModel>(m_model);
829  if (!model || !m_editing) return;
831  m_editingCommand = nullptr;
832  m_editing = false;
833 }
834 
835 void
837 {
838  auto model = ModelById::getAs<BoxModel>(m_model);
839  if (!model) return;
840 
841  if (!getLocalPoint(v, e->x(), e->y(), m_editingPoint)) return;
842 
843  if (m_editingCommand) {
845  m_editingCommand = nullptr;
846  }
847 
848  m_editing = true;
849 }
850 
851 void
853 {
854 }
855 
856 void
858 {
859  auto model = ModelById::getAs<BoxModel>(m_model);
860  if (!model || !m_editing) return;
861 
862  m_editing = false;
863 
864  Event p(0);
865  if (!getLocalPoint(v, e->x(), e->y(), p)) return;
866  if (p.getFrame() != m_editingPoint.getFrame() ||
867  p.getValue() != m_editingPoint.getValue()) return;
868 
869  m_editingCommand = new ChangeEventsCommand
870  (m_model.untyped, tr("Erase Box"));
871 
873 
875  m_editingCommand = nullptr;
876  m_editing = false;
877 }
878 
879 void
881 {
882  auto model = ModelById::getAs<BoxModel>(m_model);
883  if (!model) return;
884 
885  if (!getLocalPoint(v, e->x(), e->y(), m_editingPoint)) {
886  return;
887  }
888 
889  m_dragPointX = v->getXForFrame(m_editingPoint.getFrame());
890  m_dragPointY = getYForValue(v, m_editingPoint.getValue());
891 
893 
894  if (m_editingCommand) {
896  m_editingCommand = nullptr;
897  }
898 
899  m_editing = true;
900  m_dragStartX = e->x();
901  m_dragStartY = e->y();
902 }
903 
904 void
906 {
907  auto model = ModelById::getAs<BoxModel>(m_model);
908  if (!model || !m_editing) return;
909 
910  int xdist = e->x() - m_dragStartX;
911  int ydist = e->y() - m_dragStartY;
912  int newx = m_dragPointX + xdist;
913  int newy = m_dragPointY + ydist;
914 
915  sv_frame_t frame = v->getFrameForX(newx);
916  if (frame < 0) frame = 0;
917  frame = frame / model->getResolution() * model->getResolution();
918 
919  double value = getValueForY(v, newy);
920 
921  if (!m_editingCommand) {
922  m_editingCommand = new ChangeEventsCommand
923  (m_model.untyped,
924  tr("Drag Box"));
925  }
926 
929  .withFrame(frame)
930  .withValue(float(value));
932 }
933 
934 void
936 {
937  auto model = ModelById::getAs<BoxModel>(m_model);
938  if (!model || !m_editing) return;
939 
940  if (m_editingCommand) {
941 
942  QString newName = m_editingCommand->getName();
943 
944  if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) {
945  if (m_editingPoint.getValue() != m_originalPoint.getValue()) {
946  newName = tr("Edit Box");
947  } else {
948  newName = tr("Relocate Box");
949  }
950  } else {
951  newName = tr("Change Point Value");
952  }
953 
954  m_editingCommand->setName(newName);
956  }
957 
958  m_editingCommand = nullptr;
959  m_editing = false;
960 }
961 
962 bool
964 {
965  auto model = ModelById::getAs<BoxModel>(m_model);
966  if (!model) return false;
967 
968  Event region(0);
969  if (!getLocalPoint(v, e->x(), e->y(), region)) return false;
970 
971  ItemEditDialog::LabelOptions labelOptions;
972  labelOptions.valueLabel = tr("Minimum Value");
973  labelOptions.levelLabel = tr("Value Extent");
974  labelOptions.valueUnits = getScaleUnits();
975  labelOptions.levelUnits = getScaleUnits();
976 
977  ItemEditDialog *dialog = new ItemEditDialog
978  (model->getSampleRate(),
984  labelOptions);
985 
986  dialog->setFrameTime(region.getFrame());
987  dialog->setValue(region.getValue());
988  dialog->setLevel(region.getLevel());
989  dialog->setFrameDuration(region.getDuration());
990  dialog->setText(region.getLabel());
991 
992  if (dialog->exec() == QDialog::Accepted) {
993 
994  Event newBox = region
995  .withFrame(dialog->getFrameTime())
996  .withValue(dialog->getValue())
997  .withLevel(dialog->getLevel())
998  .withDuration(dialog->getFrameDuration())
999  .withLabel(dialog->getText());
1000 
1001  ChangeEventsCommand *command = new ChangeEventsCommand
1002  (m_model.untyped, tr("Edit Box"));
1003  command->remove(region);
1004  command->add(newBox);
1005  finish(command);
1006  }
1007 
1008  delete dialog;
1009  return true;
1010 }
1011 
1012 void
1013 BoxLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
1014 {
1015  auto model = ModelById::getAs<BoxModel>(m_model);
1016  if (!model) return;
1017 
1018  ChangeEventsCommand *command =
1019  new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
1020 
1021  EventVector points =
1022  model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1023 
1024  for (EventVector::iterator i = points.begin();
1025  i != points.end(); ++i) {
1026 
1027  Event newPoint = (*i)
1028  .withFrame(i->getFrame() + newStartFrame - s.getStartFrame());
1029  command->remove(*i);
1030  command->add(newPoint);
1031  }
1032 
1033  finish(command);
1034 }
1035 
1036 void
1037 BoxLayer::resizeSelection(Selection s, Selection newSize)
1038 {
1039  auto model = ModelById::getAs<BoxModel>(m_model);
1040  if (!model || !s.getDuration()) return;
1041 
1042  ChangeEventsCommand *command =
1043  new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
1044 
1045  EventVector points =
1046  model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1047 
1048  double ratio = double(newSize.getDuration()) / double(s.getDuration());
1049  double oldStart = double(s.getStartFrame());
1050  double newStart = double(newSize.getStartFrame());
1051 
1052  for (Event p: points) {
1053 
1054  double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
1055  double newDuration = double(p.getDuration()) * ratio;
1056 
1057  Event newPoint = p
1058  .withFrame(lrint(newFrame))
1059  .withDuration(lrint(newDuration));
1060  command->remove(p);
1061  command->add(newPoint);
1062  }
1063 
1064  finish(command);
1065 }
1066 
1067 void
1069 {
1070  auto model = ModelById::getAs<BoxModel>(m_model);
1071  if (!model) return;
1072 
1073  ChangeEventsCommand *command =
1074  new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points"));
1075 
1076  EventVector points =
1077  model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1078 
1079  for (EventVector::iterator i = points.begin();
1080  i != points.end(); ++i) {
1081 
1082  if (s.contains(i->getFrame())) {
1083  command->remove(*i);
1084  }
1085  }
1086 
1087  finish(command);
1088 }
1089 
1090 void
1091 BoxLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
1092 {
1093  auto model = ModelById::getAs<BoxModel>(m_model);
1094  if (!model) return;
1095 
1096  EventVector points =
1097  model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1098 
1099  for (Event p: points) {
1100  to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
1101  }
1102 }
1103 
1104 bool
1105 BoxLayer::paste(LayerGeometryProvider *v, const Clipboard &from,
1106  sv_frame_t /* frameOffset */, bool /* interactive */)
1107 {
1108  auto model = ModelById::getAs<BoxModel>(m_model);
1109  if (!model) return false;
1110 
1111  const EventVector &points = from.getPoints();
1112 
1113  bool realign = false;
1114 
1115  if (clipboardHasDifferentAlignment(v, from)) {
1116 
1117  QMessageBox::StandardButton button =
1118  QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
1119  tr("The items you are pasting came from a layer with different source material from this one. Do you want to re-align them in time, to match the source material for this layer?"),
1120  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
1121  QMessageBox::Yes);
1122 
1123  if (button == QMessageBox::Cancel) {
1124  return false;
1125  }
1126 
1127  if (button == QMessageBox::Yes) {
1128  realign = true;
1129  }
1130  }
1131 
1132  ChangeEventsCommand *command =
1133  new ChangeEventsCommand(m_model.untyped, tr("Paste"));
1134 
1135  for (EventVector::const_iterator i = points.begin();
1136  i != points.end(); ++i) {
1137 
1138  sv_frame_t frame = 0;
1139 
1140  if (!realign) {
1141 
1142  frame = i->getFrame();
1143 
1144  } else {
1145 
1146  if (i->hasReferenceFrame()) {
1147  frame = i->getReferenceFrame();
1148  frame = alignFromReference(v, frame);
1149  } else {
1150  frame = i->getFrame();
1151  }
1152  }
1153 
1154  Event p = i->withFrame(frame);
1155 
1156  Event newPoint = p;
1157  if (!p.hasValue()) {
1158  newPoint = newPoint.withValue((model->getValueMinimum() +
1159  model->getValueMaximum()) / 2);
1160  }
1161  if (!p.hasDuration()) {
1162  sv_frame_t nextFrame = frame;
1163  EventVector::const_iterator j = i;
1164  for (; j != points.end(); ++j) {
1165  if (j != i) break;
1166  }
1167  if (j != points.end()) {
1168  nextFrame = j->getFrame();
1169  }
1170  if (nextFrame == frame) {
1171  newPoint = newPoint.withDuration(model->getResolution());
1172  } else {
1173  newPoint = newPoint.withDuration(nextFrame - frame);
1174  }
1175  }
1176 
1177  command->add(newPoint);
1178  }
1179 
1180  finish(command);
1181  return true;
1182 }
1183 
1184 void
1185 BoxLayer::toXml(QTextStream &stream,
1186  QString indent, QString extraAttributes) const
1187 {
1188  QString s;
1189 
1190  s += QString("verticalScale=\"%1\" ").arg(m_verticalScale);
1191 
1192  SingleColourLayer::toXml(stream, indent, extraAttributes + " " + s);
1193 }
1194 
1195 void
1196 BoxLayer::setProperties(const QXmlAttributes &attributes)
1197 {
1199 
1200  bool ok;
1201  VerticalScale scale = (VerticalScale)
1202  attributes.value("verticalScale").toInt(&ok);
1203  if (ok) setVerticalScale(scale);
1204 }
1205 
1206 
virtual int scalePixelSize(int size) const =0
float getValue() const
QString getPropertyGroupName(const PropertyName &) const override
Definition: BoxLayer.cpp:113
virtual bool snapToFeatureFrame(LayerGeometryProvider *, sv_frame_t &, int &resolution, SnapType, int) const
Adjust the given frame to snap to the nearest feature, if possible.
Definition: Layer.h:226
bool getLocalPoint(LayerGeometryProvider *v, int x, int y, Event &) const
Definition: BoxLayer.cpp:243
VerticalScale
Definition: BoxLayer.h:86
void copy(LayerGeometryProvider *v, Selection s, Clipboard &to) override
Definition: BoxLayer.cpp:1091
void setFrameDuration(sv_frame_t frame)
PropertyType getPropertyType(const PropertyName &) const override
Definition: BoxLayer.cpp:105
PropertyList getProperties() const override
Definition: BoxLayer.cpp:88
void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const override
Definition: BoxLayer.cpp:735
PropertyList getProperties() const override
virtual bool shouldIlluminateLocalFeatures(const Layer *, QPoint &) const =0
QString getPropertyLabel(const PropertyName &) const override
Definition: BoxLayer.cpp:97
bool editOpen(LayerGeometryProvider *v, QMouseEvent *) override
Open an editor on the item under the mouse (e.g.
Definition: BoxLayer.cpp:963
double getValueForY(LayerGeometryProvider *v, int y) const override
Definition: BoxLayer.cpp:514
void modelReplaced()
static QString abbreviate(QString text, int maxLength, Policy policy=ElideEnd, bool fuzzy=true, QString ellipsis="")
Abbreviate the given text to the given maximum length (including ellipsis), using the given abbreviat...
Definition: TextAbbrev.cpp:79
void finish(ChangeEventsCommand *command)
Definition: BoxLayer.h:143
void setFrameTime(sv_frame_t frame)
void toXml(QTextStream &stream, QString indent="", QString extraAttributes="") const override
Definition: BoxLayer.cpp:1185
void paintVertical(LayerGeometryProvider *v, const VerticalScaleLayer *layer, QPainter &paint, int x0, double minlog, double maxlog)
sv_frame_t getFrameTime() const
virtual sv_frame_t getFrameForX(int x) const =0
Return the closest frame to the given pixel x-coordinate.
void setProperty(const PropertyName &, int value) override
Definition: BoxLayer.cpp:167
void editDrag(LayerGeometryProvider *v, QMouseEvent *) override
Definition: BoxLayer.cpp:905
bool adoptExtents(double min, double max, QString unit) override
Consider using the given value extents and units for this layer.
Definition: BoxLayer.cpp:226
void drawEnd(LayerGeometryProvider *v, QMouseEvent *) override
Definition: BoxLayer.cpp:826
int getWidth(LayerGeometryProvider *v, QPainter &paint)
virtual QColor getBaseQColor() const
void toXml(QTextStream &stream, QString indent="", QString extraAttributes="") const override
int getWidth(LayerGeometryProvider *v, QPainter &paint)
void modelChanged(ModelId)
void setText(QString text)
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.
Event m_originalPoint
Definition: BoxLayer.h:134
VerticalScale m_verticalScale
Definition: BoxLayer.h:137
int getYForValue(LayerGeometryProvider *v, double value) const override
VerticalScaleLayer methods.
Definition: BoxLayer.cpp:495
QString getPropertyValueLabel(const PropertyName &, int value) const override
Definition: BoxLayer.cpp:152
bool getValueExtents(double &min, double &max, bool &log, QString &unit) const override
Return the minimum and maximum values for the y axis of the model in this layer, as well as whether t...
Definition: BoxLayer.cpp:199
virtual sv_frame_t alignFromReference(LayerGeometryProvider *v, sv_frame_t frame) const
Definition: Layer.cpp:198
QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const override
Definition: BoxLayer.cpp:320
QString getPropertyGroupName(const PropertyName &) const override
void setVerticalScale(VerticalScale scale)
Definition: BoxLayer.cpp:184
std::pair< float, float > getRange(const Event &e) const
Definition: BoxLayer.h:139
void layerParametersChanged()
void eraseDrag(LayerGeometryProvider *v, QMouseEvent *) override
Definition: BoxLayer.cpp:852
bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset, bool interactive) override
Paste from the given clipboard onto the layer at the given frame offset.
Definition: BoxLayer.cpp:1105
bool clipboardHasDifferentAlignment(LayerGeometryProvider *v, const Clipboard &clip) const
Definition: Layer.cpp:209
int getPropertyRangeAndValue(const PropertyName &, int *min, int *max, int *deflt) const override
bool getDisplayExtents(double &min, double &max) const override
Return the minimum and maximum values within the visible area for the y axis of this layer...
Definition: BoxLayer.cpp:214
SnapType
Definition: Layer.h:195
void drawDrag(LayerGeometryProvider *v, QMouseEvent *) override
Definition: BoxLayer.cpp:789
virtual sv_frame_t alignToReference(LayerGeometryProvider *v, sv_frame_t frame) const
Definition: Layer.cpp:187
void paintVertical(LayerGeometryProvider *v, const VerticalScaleLayer *layer, QPainter &paint, int x0, double minf, double maxf)
int m_dragPointY
Definition: BoxLayer.h:131
ModelId m_model
Definition: BoxLayer.h:128
void connectSignals(ModelId)
Definition: Layer.cpp:49
virtual int getPaintHeight() const
void editEnd(LayerGeometryProvider *v, QMouseEvent *) override
Definition: BoxLayer.cpp:935
PropertyType getPropertyType(const PropertyName &) const override
QString getText() const
QString getScaleUnits() const override
Definition: BoxLayer.cpp:441
void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const
Definition: BoxLayer.cpp:449
int getPropertyRangeAndValue(const PropertyName &, int *min, int *max, int *deflt) const override
Definition: BoxLayer.cpp:122
void setProperties(const QXmlAttributes &attributes) override
Set the particular properties of a layer (those specific to the subclass) from a set of XML attribute...
int m_dragPointX
Definition: BoxLayer.h:130
int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const override
Definition: BoxLayer.cpp:719
ChangeEventsCommand * m_editingCommand
Definition: BoxLayer.h:136
void eraseStart(LayerGeometryProvider *v, QMouseEvent *) override
Definition: BoxLayer.cpp:836
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.
Definition: BoxLayer.cpp:532
QString getPropertyValueLabel(const PropertyName &, int value) const override
float getLevel() const
int m_dragStartY
Definition: BoxLayer.h:133
void setProperties(const QXmlAttributes &attributes) override
Set the particular properties of a layer (those specific to the subclass) from a set of XML attribute...
Definition: BoxLayer.cpp:1196
void deleteSelection(Selection s) override
Definition: BoxLayer.cpp:1068
void resizeSelection(Selection s, Selection newSize) override
Definition: BoxLayer.cpp:1037
static void drawVisibleText(const LayerGeometryProvider *, QPainter &p, int x, int y, QString text, TextStyle style)
void setModel(ModelId model)
Definition: BoxLayer.cpp:68
void drawStart(LayerGeometryProvider *v, QMouseEvent *) override
Definition: BoxLayer.cpp:766
bool m_editing
Definition: BoxLayer.h:129
virtual bool getVisibleExtentsForUnit(QString unit, double &min, double &max, bool &log) const =0
Return the visible vertical extents for the given unit, if any.
bool isLayerScrollable(const LayerGeometryProvider *v) const override
This should return true if the layer can safely be scrolled automatically by a given view (simply cop...
Definition: BoxLayer.cpp:192
void editStart(LayerGeometryProvider *v, QMouseEvent *) override
Definition: BoxLayer.cpp:880
void setProperty(const PropertyName &, int value) override
BoxLayer()
Definition: BoxLayer.cpp:44
QString getLabelPreceding(sv_frame_t) const override
Definition: BoxLayer.cpp:303
int m_dragStartX
Definition: BoxLayer.h:132
void setLevel(float level)
virtual int getXForFrame(sv_frame_t frame) const =0
Return the pixel x-coordinate corresponding to a given sample frame (which may be negative)...
Event m_editingPoint
Definition: BoxLayer.h:135
bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap, int ycoord) const override
Adjust the given frame to snap to the nearest feature, if possible.
Definition: BoxLayer.cpp:369
virtual int getPaintWidth() const
sv_frame_t getFrameDuration() const
QString getPropertyLabel(const PropertyName &) const override
virtual View * getView()=0
void eraseEnd(LayerGeometryProvider *v, QMouseEvent *) override
Definition: BoxLayer.cpp:857
void setValue(float value)
int getCompletion(LayerGeometryProvider *) const override
Return the proportion of background work complete in drawing this view, as a percentage – in most ca...
Definition: BoxLayer.cpp:60
void moveSelection(Selection s, sv_frame_t newStartFrame) override
Definition: BoxLayer.cpp:1013