comparison layer/BoxLayer.cpp @ 1518:2e94c268f7a0 time-frequency-boxes

Rename TimeFrequencyBoxLayer to just BoxLayer, supporting vertical scales other than Hz
author Chris Cannam
date Wed, 25 Sep 2019 09:45:42 +0100
parents layer/TimeFrequencyBoxLayer.cpp@0fa49a6ce64f
children 14c07e445365
comparison
equal deleted inserted replaced
1517:c5d2de8f7647 1518:2e94c268f7a0
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
44 BoxLayer::BoxLayer() :
45 SingleColourLayer(),
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
60 BoxLayer::getCompletion(LayerGeometryProvider *) const
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) {
81 connectSignals(m_model);
82 }
83
84 emit modelReplaced();
85 }
86
87 Layer::PropertyList
88 BoxLayer::getProperties() const
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");
101 return SingleColourLayer::getPropertyLabel(name);
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;
109 return SingleColourLayer::getPropertyType(name);
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 }
118 return SingleColourLayer::getPropertyGroupName(name);
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") {
170 setVerticalScale(VerticalScale(value));
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
184 BoxLayer::setVerticalScale(VerticalScale scale)
185 {
186 if (m_verticalScale == scale) return;
187 m_verticalScale = scale;
188 emit layerParametersChanged();
189 }
190
191 bool
192 BoxLayer::isLayerScrollable(const LayerGeometryProvider *v) const
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 if (model->getScaleUnits() == "") {
231 model->setScaleUnits(unit);
232 return true;
233 } else {
234 return false;
235 }
236 }
237
238 EventVector
239 BoxLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
240 {
241 auto model = ModelById::getAs<BoxModel>(m_model);
242 if (!model) return EventVector();
243
244 sv_frame_t frame = v->getFrameForX(x);
245
246 EventVector local = model->getEventsCovering(frame);
247 if (!local.empty()) return local;
248
249 int fuzz = ViewManager::scalePixelSize(2);
250 sv_frame_t start = v->getFrameForX(x - fuzz);
251 sv_frame_t end = v->getFrameForX(x + fuzz);
252
253 local = model->getEventsStartingWithin(frame, end - frame);
254 if (!local.empty()) return local;
255
256 local = model->getEventsSpanning(start, frame - start);
257 if (!local.empty()) return local;
258
259 return {};
260 }
261
262 bool
263 BoxLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y,
264 Event &point) const
265 {
266 auto model = ModelById::getAs<BoxModel>(m_model);
267 if (!model) return false;
268
269 sv_frame_t frame = v->getFrameForX(x);
270
271 EventVector onPoints = model->getEventsCovering(frame);
272 if (onPoints.empty()) return false;
273
274 int nearestDistance = -1;
275 for (const auto &p: onPoints) {
276 int distance = getYForValue(v, p.getValue()) - y;
277 if (distance < 0) distance = -distance;
278 if (nearestDistance == -1 || distance < nearestDistance) {
279 nearestDistance = distance;
280 point = p;
281 }
282 }
283
284 return true;
285 }
286
287 QString
288 BoxLayer::getLabelPreceding(sv_frame_t frame) const
289 {
290 auto model = ModelById::getAs<BoxModel>(m_model);
291 if (!model) return "";
292 EventVector points = model->getEventsStartingWithin
293 (model->getStartFrame(), frame - model->getStartFrame());
294 if (!points.empty()) {
295 for (auto i = points.rbegin(); i != points.rend(); ++i) {
296 if (i->getLabel() != QString()) {
297 return i->getLabel();
298 }
299 }
300 }
301 return QString();
302 }
303
304 QString
305 BoxLayer::getFeatureDescription(LayerGeometryProvider *v,
306 QPoint &pos) const
307 {
308 int x = pos.x();
309
310 auto model = ModelById::getAs<BoxModel>(m_model);
311 if (!model || !model->getSampleRate()) return "";
312
313 EventVector points = getLocalPoints(v, x);
314
315 if (points.empty()) {
316 if (!model->isReady()) {
317 return tr("In progress");
318 } else {
319 return tr("No local points");
320 }
321 }
322
323 Event box;
324 EventVector::iterator i;
325
326 for (i = points.begin(); i != points.end(); ++i) {
327
328 int y0 = getYForValue(v, i->getValue());
329 int y1 = getYForValue(v, i->getValue() + fabsf(i->getLevel()));
330
331 if (pos.y() >= y0 && pos.y() <= y1) {
332 box = *i;
333 break;
334 }
335 }
336
337 if (i == points.end()) return tr("No local points");
338
339 RealTime rt = RealTime::frame2RealTime(box.getFrame(),
340 model->getSampleRate());
341 RealTime rd = RealTime::frame2RealTime(box.getDuration(),
342 model->getSampleRate());
343
344 QString rangeText;
345
346 rangeText = tr("%1 %2 - %3 %4")
347 .arg(box.getValue()).arg(getScaleUnits())
348 .arg(box.getValue() + fabsf(box.getLevel())).arg(getScaleUnits());
349
350 QString text;
351
352 if (box.getLabel() == "") {
353 text = QString(tr("Time:\t%1\nDuration:\t%2\nValue:\t%3\nNo label"))
354 .arg(rt.toText(true).c_str())
355 .arg(rd.toText(true).c_str())
356 .arg(rangeText);
357 } else {
358 text = QString(tr("Time:\t%1\nDuration:\t%2\nValue:\t%3\nLabel:\t%4"))
359 .arg(rt.toText(true).c_str())
360 .arg(rd.toText(true).c_str())
361 .arg(rangeText)
362 .arg(box.getLabel());
363 }
364
365 pos = QPoint(v->getXForFrame(box.getFrame()),
366 getYForValue(v, box.getValue()));
367 return text;
368 }
369
370 bool
371 BoxLayer::snapToFeatureFrame(LayerGeometryProvider *v,
372 sv_frame_t &frame,
373 int &resolution,
374 SnapType snap) const
375 {
376 auto model = ModelById::getAs<BoxModel>(m_model);
377 if (!model) {
378 return Layer::snapToFeatureFrame(v, frame, resolution, snap);
379 }
380
381 // SnapLeft / SnapRight: return frame of nearest feature in that
382 // direction no matter how far away
383 //
384 // SnapNeighbouring: return frame of feature that would be used in
385 // an editing operation, i.e. closest feature in either direction
386 // but only if it is "close enough"
387
388 resolution = model->getResolution();
389
390 if (snap == SnapNeighbouring) {
391 EventVector points = getLocalPoints(v, v->getXForFrame(frame));
392 if (points.empty()) return false;
393 frame = points.begin()->getFrame();
394 return true;
395 }
396
397 // Normally we snap to the start frame of whichever event we
398 // find. However here, for SnapRight only, if the end frame of
399 // whichever event we would have snapped to had we been snapping
400 // left is closer than the start frame of the next event to the
401 // right, then we snap to that frame instead. Clear?
402
403 Event left;
404 bool haveLeft = false;
405 if (model->getNearestEventMatching
406 (frame, [](Event) { return true; }, EventSeries::Backward, left)) {
407 haveLeft = true;
408 }
409
410 if (snap == SnapLeft) {
411 frame = left.getFrame();
412 return haveLeft;
413 }
414
415 Event right;
416 bool haveRight = false;
417 if (model->getNearestEventMatching
418 (frame, [](Event) { return true; }, EventSeries::Forward, right)) {
419 haveRight = true;
420 }
421
422 if (haveLeft) {
423 sv_frame_t leftEnd = left.getFrame() + left.getDuration();
424 if (leftEnd > frame) {
425 if (haveRight) {
426 if (leftEnd - frame < right.getFrame() - frame) {
427 frame = leftEnd;
428 } else {
429 frame = right.getFrame();
430 }
431 } else {
432 frame = leftEnd;
433 }
434 return true;
435 }
436 }
437
438 if (haveRight) {
439 frame = right.getFrame();
440 return true;
441 }
442
443 return false;
444 }
445
446 QString
447 BoxLayer::getScaleUnits() const
448 {
449 auto model = ModelById::getAs<BoxModel>(m_model);
450 if (model) return model->getScaleUnits();
451 else return "";
452 }
453
454 void
455 BoxLayer::getScaleExtents(LayerGeometryProvider *v,
456 double &min, double &max,
457 bool &log) const
458 {
459 min = 0.0;
460 max = 0.0;
461 log = false;
462
463 auto model = ModelById::getAs<BoxModel>(m_model);
464 if (!model) return;
465
466 QString queryUnits;
467 queryUnits = getScaleUnits();
468
469 if (m_verticalScale == AutoAlignScale) {
470
471 if (!v->getValueExtents(queryUnits, min, max, log)) {
472
473 min = model->getValueMinimum();
474 max = model->getValueMaximum();
475
476 // cerr << "BoxLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
477
478 } else if (log) {
479
480 LogRange::mapRange(min, max);
481
482 // cerr << "BoxLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
483
484 }
485
486 } else {
487
488 min = model->getValueMinimum();
489 max = model->getValueMaximum();
490
491 if (m_verticalScale == LogScale) {
492 LogRange::mapRange(min, max);
493 log = true;
494 }
495 }
496
497 if (max == min) max = min + 1.0;
498 }
499
500 int
501 BoxLayer::getYForValue(LayerGeometryProvider *v, double val) const
502 {
503 double min = 0.0, max = 0.0;
504 bool logarithmic = false;
505 int h = v->getPaintHeight();
506
507 getScaleExtents(v, min, max, logarithmic);
508
509 // cerr << "BoxLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl;
510 // cerr << "h = " << h << ", margin = " << margin << endl;
511
512 if (logarithmic) {
513 val = LogRange::map(val);
514 }
515
516 return int(h - ((val - min) * h) / (max - min));
517 }
518
519 double
520 BoxLayer::getValueForY(LayerGeometryProvider *v, int y) const
521 {
522 double min = 0.0, max = 0.0;
523 bool logarithmic = false;
524 int h = v->getPaintHeight();
525
526 getScaleExtents(v, min, max, logarithmic);
527
528 double val = min + (double(h - y) * double(max - min)) / h;
529
530 if (logarithmic) {
531 val = pow(10.0, val);
532 }
533
534 return val;
535 }
536
537 void
538 BoxLayer::paint(LayerGeometryProvider *v, QPainter &paint,
539 QRect rect) const
540 {
541 auto model = ModelById::getAs<BoxModel>(m_model);
542 if (!model || !model->isOK()) return;
543
544 sv_samplerate_t sampleRate = model->getSampleRate();
545 if (!sampleRate) return;
546
547 // Profiler profiler("BoxLayer::paint", true);
548
549 int x0 = rect.left() - 40, x1 = rect.right();
550
551 sv_frame_t wholeFrame0 = v->getFrameForX(0);
552 sv_frame_t wholeFrame1 = v->getFrameForX(v->getPaintWidth());
553
554 EventVector points(model->getEventsSpanning(wholeFrame0,
555 wholeFrame1 - wholeFrame0));
556 if (points.empty()) return;
557
558 paint.setPen(getBaseQColor());
559
560 // SVDEBUG << "BoxLayer::paint: resolution is "
561 // << model->getResolution() << " frames" << endl;
562
563 double min = model->getValueMinimum();
564 double max = model->getValueMaximum();
565 if (max == min) max = min + 1.0;
566
567 QPoint localPos;
568 Event illuminatePoint(0);
569 bool shouldIlluminate = false;
570
571 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
572 shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(),
573 illuminatePoint);
574 }
575
576 paint.save();
577 paint.setRenderHint(QPainter::Antialiasing, false);
578
579 QFontMetrics fm = paint.fontMetrics();
580
581 for (EventVector::const_iterator i = points.begin();
582 i != points.end(); ++i) {
583
584 const Event &p(*i);
585
586 int x = v->getXForFrame(p.getFrame());
587 int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x;
588 int y = getYForValue(v, p.getValue());
589 int h = getYForValue(v, p.getValue() + fabsf(p.getLevel())) - y;
590 int ex = x + w;
591 int gap = v->scalePixelSize(2);
592
593 EventVector::const_iterator j = i;
594 ++j;
595
596 if (j != points.end()) {
597 const Event &q(*j);
598 int nx = v->getXForFrame(q.getFrame());
599 if (nx < ex) ex = nx;
600 }
601
602 if (w < 1) w = 1;
603
604 paint.setPen(getBaseQColor());
605 paint.setBrush(Qt::NoBrush);
606
607 if ((shouldIlluminate && illuminatePoint == p) ||
608 (m_editing && m_editingPoint == p)) {
609
610 paint.setPen(QPen(getBaseQColor(), v->scalePixelSize(2)));
611
612 // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
613 // replacement (horizontalAdvance) was only added in Qt 5.11
614 // which is too new for us
615 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
616
617 if (abs(h) > 2 * fm.height()) {
618
619 QString y0label = QString("%1 %2")
620 .arg(p.getValue())
621 .arg(getScaleUnits());
622
623 QString y1label = QString("%1 %2")
624 .arg(p.getValue() + fabsf(p.getLevel()))
625 .arg(getScaleUnits());
626
627 PaintAssistant::drawVisibleText
628 (v, paint,
629 x - fm.width(y0label) - gap,
630 y - fm.descent(),
631 y0label, PaintAssistant::OutlinedText);
632
633 PaintAssistant::drawVisibleText
634 (v, paint,
635 x - fm.width(y1label) - gap,
636 y + h + fm.ascent(),
637 y1label, PaintAssistant::OutlinedText);
638
639 } else {
640
641 QString ylabel = QString("%1 %2 - %3 %4")
642 .arg(p.getValue())
643 .arg(getScaleUnits())
644 .arg(p.getValue() + fabsf(p.getLevel()))
645 .arg(getScaleUnits());
646
647 PaintAssistant::drawVisibleText
648 (v, paint,
649 x - fm.width(ylabel) - gap,
650 y - fm.descent(),
651 ylabel, PaintAssistant::OutlinedText);
652 }
653
654 QString t0label = RealTime::frame2RealTime
655 (p.getFrame(), model->getSampleRate()).toText(true).c_str();
656
657 QString t1label = RealTime::frame2RealTime
658 (p.getFrame() + p.getDuration(), model->getSampleRate())
659 .toText(true).c_str();
660
661 PaintAssistant::drawVisibleText
662 (v, paint, x, y + fm.ascent() + gap,
663 t0label, PaintAssistant::OutlinedText);
664
665 if (w > fm.width(t0label) + fm.width(t1label) + gap * 3) {
666
667 PaintAssistant::drawVisibleText
668 (v, paint,
669 x + w - fm.width(t1label),
670 y + fm.ascent() + gap,
671 t1label, PaintAssistant::OutlinedText);
672
673 } else {
674
675 PaintAssistant::drawVisibleText
676 (v, paint,
677 x + w - fm.width(t1label),
678 y + fm.ascent() + fm.height() + gap,
679 t1label, PaintAssistant::OutlinedText);
680 }
681 }
682
683 paint.drawRect(x, y, w, h);
684 }
685
686 for (EventVector::const_iterator i = points.begin();
687 i != points.end(); ++i) {
688
689 const Event &p(*i);
690
691 QString label = p.getLabel();
692 if (label == "") continue;
693
694 if (shouldIlluminate && illuminatePoint == p) {
695 continue; // already handled this in illumination special case
696 }
697
698 int x = v->getXForFrame(p.getFrame());
699 int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x;
700 int y = getYForValue(v, p.getValue());
701
702 int labelWidth = fm.width(label);
703
704 int gap = v->scalePixelSize(2);
705
706 if (x + w < x0 || x - labelWidth - gap > x1) {
707 continue;
708 }
709
710 int labelX, labelY;
711
712 labelX = x - labelWidth - gap;
713 labelY = y - fm.descent();
714
715 PaintAssistant::drawVisibleText(v, paint, labelX, labelY, label,
716 PaintAssistant::OutlinedText);
717 }
718
719 paint.restore();
720 }
721
722 int
723 BoxLayer::getVerticalScaleWidth(LayerGeometryProvider *v,
724 bool, QPainter &paint) const
725 {
726 auto model = ModelById::getAs<BoxModel>(m_model);
727 if (!model || m_verticalScale == AutoAlignScale) {
728 return 0;
729 } else {
730 if (m_verticalScale == LogScale) {
731 return LogNumericalScale().getWidth(v, paint);
732 } else {
733 return LinearNumericalScale().getWidth(v, paint);
734 }
735 }
736 }
737
738 void
739 BoxLayer::paintVerticalScale(LayerGeometryProvider *v,
740 bool, QPainter &paint, QRect) const
741 {
742 auto model = ModelById::getAs<BoxModel>(m_model);
743 if (!model || model->isEmpty()) return;
744
745 QString unit;
746 double min, max;
747 bool logarithmic;
748
749 int w = getVerticalScaleWidth(v, false, paint);
750
751 getScaleExtents(v, min, max, logarithmic);
752
753 if (logarithmic) {
754 LogNumericalScale().paintVertical(v, this, paint, 0, min, max);
755 } else {
756 LinearNumericalScale().paintVertical(v, this, paint, 0, min, max);
757 }
758
759 if (getScaleUnits() != "") {
760 int mw = w - 5;
761 paint.drawText(5,
762 5 + paint.fontMetrics().ascent(),
763 TextAbbrev::abbreviate(getScaleUnits(),
764 paint.fontMetrics(),
765 mw));
766 }
767 }
768
769 void
770 BoxLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
771 {
772 auto model = ModelById::getAs<BoxModel>(m_model);
773 if (!model) return;
774
775 sv_frame_t frame = v->getFrameForX(e->x());
776 if (frame < 0) frame = 0;
777 frame = frame / model->getResolution() * model->getResolution();
778
779 double value = getValueForY(v, e->y());
780
781 m_editingPoint = Event(frame, float(value), 0, "");
782 m_originalPoint = m_editingPoint;
783
784 if (m_editingCommand) finish(m_editingCommand);
785 m_editingCommand = new ChangeEventsCommand(m_model.untyped,
786 tr("Draw Box"));
787 m_editingCommand->add(m_editingPoint);
788
789 m_editing = true;
790 }
791
792 void
793 BoxLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
794 {
795 auto model = ModelById::getAs<BoxModel>(m_model);
796 if (!model || !m_editing) return;
797
798 sv_frame_t dragFrame = v->getFrameForX(e->x());
799 if (dragFrame < 0) dragFrame = 0;
800 dragFrame = dragFrame / model->getResolution() * model->getResolution();
801
802 sv_frame_t eventFrame = m_originalPoint.getFrame();
803 sv_frame_t eventDuration = dragFrame - eventFrame;
804 if (eventDuration < 0) {
805 eventFrame = eventFrame + eventDuration;
806 eventDuration = -eventDuration;
807 } else if (eventDuration == 0) {
808 eventDuration = model->getResolution();
809 }
810
811 double dragValue = getValueForY(v, e->y());
812
813 double eventValue = m_originalPoint.getValue();
814 double eventFreqDiff = dragValue - eventValue;
815 if (eventFreqDiff < 0) {
816 eventValue = eventValue + eventFreqDiff;
817 eventFreqDiff = -eventFreqDiff;
818 }
819
820 m_editingCommand->remove(m_editingPoint);
821 m_editingPoint = m_editingPoint
822 .withFrame(eventFrame)
823 .withDuration(eventDuration)
824 .withValue(float(eventValue))
825 .withLevel(float(eventFreqDiff));
826 m_editingCommand->add(m_editingPoint);
827 }
828
829 void
830 BoxLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
831 {
832 auto model = ModelById::getAs<BoxModel>(m_model);
833 if (!model || !m_editing) return;
834 finish(m_editingCommand);
835 m_editingCommand = nullptr;
836 m_editing = false;
837 }
838
839 void
840 BoxLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
841 {
842 auto model = ModelById::getAs<BoxModel>(m_model);
843 if (!model) return;
844
845 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
846
847 if (m_editingCommand) {
848 finish(m_editingCommand);
849 m_editingCommand = nullptr;
850 }
851
852 m_editing = true;
853 }
854
855 void
856 BoxLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
857 {
858 }
859
860 void
861 BoxLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
862 {
863 auto model = ModelById::getAs<BoxModel>(m_model);
864 if (!model || !m_editing) return;
865
866 m_editing = false;
867
868 Event p(0);
869 if (!getPointToDrag(v, e->x(), e->y(), p)) return;
870 if (p.getFrame() != m_editingPoint.getFrame() ||
871 p.getValue() != m_editingPoint.getValue()) return;
872
873 m_editingCommand = new ChangeEventsCommand
874 (m_model.untyped, tr("Erase Box"));
875
876 m_editingCommand->remove(m_editingPoint);
877
878 finish(m_editingCommand);
879 m_editingCommand = nullptr;
880 m_editing = false;
881 }
882
883 void
884 BoxLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
885 {
886 auto model = ModelById::getAs<BoxModel>(m_model);
887 if (!model) return;
888
889 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) {
890 return;
891 }
892
893 m_dragPointX = v->getXForFrame(m_editingPoint.getFrame());
894 m_dragPointY = getYForValue(v, m_editingPoint.getValue());
895
896 m_originalPoint = m_editingPoint;
897
898 if (m_editingCommand) {
899 finish(m_editingCommand);
900 m_editingCommand = nullptr;
901 }
902
903 m_editing = true;
904 m_dragStartX = e->x();
905 m_dragStartY = e->y();
906 }
907
908 void
909 BoxLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
910 {
911 auto model = ModelById::getAs<BoxModel>(m_model);
912 if (!model || !m_editing) return;
913
914 int xdist = e->x() - m_dragStartX;
915 int ydist = e->y() - m_dragStartY;
916 int newx = m_dragPointX + xdist;
917 int newy = m_dragPointY + ydist;
918
919 sv_frame_t frame = v->getFrameForX(newx);
920 if (frame < 0) frame = 0;
921 frame = frame / model->getResolution() * model->getResolution();
922
923 double value = getValueForY(v, newy);
924
925 if (!m_editingCommand) {
926 m_editingCommand = new ChangeEventsCommand
927 (m_model.untyped,
928 tr("Drag Box"));
929 }
930
931 m_editingCommand->remove(m_editingPoint);
932 m_editingPoint = m_editingPoint
933 .withFrame(frame)
934 .withValue(float(value));
935 m_editingCommand->add(m_editingPoint);
936 }
937
938 void
939 BoxLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
940 {
941 auto model = ModelById::getAs<BoxModel>(m_model);
942 if (!model || !m_editing) return;
943
944 if (m_editingCommand) {
945
946 QString newName = m_editingCommand->getName();
947
948 if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) {
949 if (m_editingPoint.getValue() != m_originalPoint.getValue()) {
950 newName = tr("Edit Box");
951 } else {
952 newName = tr("Relocate Box");
953 }
954 } else {
955 newName = tr("Change Point Value");
956 }
957
958 m_editingCommand->setName(newName);
959 finish(m_editingCommand);
960 }
961
962 m_editingCommand = nullptr;
963 m_editing = false;
964 }
965
966 bool
967 BoxLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
968 {
969 auto model = ModelById::getAs<BoxModel>(m_model);
970 if (!model) return false;
971
972 Event region(0);
973 if (!getPointToDrag(v, e->x(), e->y(), region)) return false;
974
975 ItemEditDialog::LabelOptions labelOptions;
976 labelOptions.valueLabel = tr("Minimum Value");
977 labelOptions.levelLabel = tr("Value Extent");
978 labelOptions.valueUnits = getScaleUnits();
979 labelOptions.levelUnits = getScaleUnits();
980
981 ItemEditDialog *dialog = new ItemEditDialog
982 (model->getSampleRate(),
983 ItemEditDialog::ShowTime |
984 ItemEditDialog::ShowDuration |
985 ItemEditDialog::ShowValue |
986 ItemEditDialog::ShowLevel |
987 ItemEditDialog::ShowText,
988 labelOptions);
989
990 dialog->setFrameTime(region.getFrame());
991 dialog->setValue(region.getValue());
992 dialog->setLevel(region.getLevel());
993 dialog->setFrameDuration(region.getDuration());
994 dialog->setText(region.getLabel());
995
996 if (dialog->exec() == QDialog::Accepted) {
997
998 Event newBox = region
999 .withFrame(dialog->getFrameTime())
1000 .withValue(dialog->getValue())
1001 .withLevel(dialog->getLevel())
1002 .withDuration(dialog->getFrameDuration())
1003 .withLabel(dialog->getText());
1004
1005 ChangeEventsCommand *command = new ChangeEventsCommand
1006 (m_model.untyped, tr("Edit Box"));
1007 command->remove(region);
1008 command->add(newBox);
1009 finish(command);
1010 }
1011
1012 delete dialog;
1013 return true;
1014 }
1015
1016 void
1017 BoxLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
1018 {
1019 auto model = ModelById::getAs<BoxModel>(m_model);
1020 if (!model) return;
1021
1022 ChangeEventsCommand *command =
1023 new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
1024
1025 EventVector points =
1026 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1027
1028 for (EventVector::iterator i = points.begin();
1029 i != points.end(); ++i) {
1030
1031 Event newPoint = (*i)
1032 .withFrame(i->getFrame() + newStartFrame - s.getStartFrame());
1033 command->remove(*i);
1034 command->add(newPoint);
1035 }
1036
1037 finish(command);
1038 }
1039
1040 void
1041 BoxLayer::resizeSelection(Selection s, Selection newSize)
1042 {
1043 auto model = ModelById::getAs<BoxModel>(m_model);
1044 if (!model || !s.getDuration()) return;
1045
1046 ChangeEventsCommand *command =
1047 new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
1048
1049 EventVector points =
1050 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1051
1052 double ratio = double(newSize.getDuration()) / double(s.getDuration());
1053 double oldStart = double(s.getStartFrame());
1054 double newStart = double(newSize.getStartFrame());
1055
1056 for (Event p: points) {
1057
1058 double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
1059 double newDuration = double(p.getDuration()) * ratio;
1060
1061 Event newPoint = p
1062 .withFrame(lrint(newFrame))
1063 .withDuration(lrint(newDuration));
1064 command->remove(p);
1065 command->add(newPoint);
1066 }
1067
1068 finish(command);
1069 }
1070
1071 void
1072 BoxLayer::deleteSelection(Selection s)
1073 {
1074 auto model = ModelById::getAs<BoxModel>(m_model);
1075 if (!model) return;
1076
1077 ChangeEventsCommand *command =
1078 new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points"));
1079
1080 EventVector points =
1081 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1082
1083 for (EventVector::iterator i = points.begin();
1084 i != points.end(); ++i) {
1085
1086 if (s.contains(i->getFrame())) {
1087 command->remove(*i);
1088 }
1089 }
1090
1091 finish(command);
1092 }
1093
1094 void
1095 BoxLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
1096 {
1097 auto model = ModelById::getAs<BoxModel>(m_model);
1098 if (!model) return;
1099
1100 EventVector points =
1101 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1102
1103 for (Event p: points) {
1104 to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
1105 }
1106 }
1107
1108 bool
1109 BoxLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
1110 {
1111 auto model = ModelById::getAs<BoxModel>(m_model);
1112 if (!model) return false;
1113
1114 const EventVector &points = from.getPoints();
1115
1116 bool realign = false;
1117
1118 if (clipboardHasDifferentAlignment(v, from)) {
1119
1120 QMessageBox::StandardButton button =
1121 QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
1122 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?"),
1123 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
1124 QMessageBox::Yes);
1125
1126 if (button == QMessageBox::Cancel) {
1127 return false;
1128 }
1129
1130 if (button == QMessageBox::Yes) {
1131 realign = true;
1132 }
1133 }
1134
1135 ChangeEventsCommand *command =
1136 new ChangeEventsCommand(m_model.untyped, tr("Paste"));
1137
1138 for (EventVector::const_iterator i = points.begin();
1139 i != points.end(); ++i) {
1140
1141 sv_frame_t frame = 0;
1142
1143 if (!realign) {
1144
1145 frame = i->getFrame();
1146
1147 } else {
1148
1149 if (i->hasReferenceFrame()) {
1150 frame = i->getReferenceFrame();
1151 frame = alignFromReference(v, frame);
1152 } else {
1153 frame = i->getFrame();
1154 }
1155 }
1156
1157 Event p = *i;
1158 Event newPoint = p;
1159 if (!p.hasValue()) {
1160 newPoint = newPoint.withValue((model->getValueMinimum() +
1161 model->getValueMaximum()) / 2);
1162 }
1163 if (!p.hasDuration()) {
1164 sv_frame_t nextFrame = frame;
1165 EventVector::const_iterator j = i;
1166 for (; j != points.end(); ++j) {
1167 if (j != i) break;
1168 }
1169 if (j != points.end()) {
1170 nextFrame = j->getFrame();
1171 }
1172 if (nextFrame == frame) {
1173 newPoint = newPoint.withDuration(model->getResolution());
1174 } else {
1175 newPoint = newPoint.withDuration(nextFrame - frame);
1176 }
1177 }
1178
1179 command->add(newPoint);
1180 }
1181
1182 finish(command);
1183 return true;
1184 }
1185
1186 void
1187 BoxLayer::toXml(QTextStream &stream,
1188 QString indent, QString extraAttributes) const
1189 {
1190 QString s;
1191
1192 s += QString("verticalScale=\"%1\" ").arg(m_verticalScale);
1193
1194 SingleColourLayer::toXml(stream, indent, extraAttributes + " " + s);
1195 }
1196
1197 void
1198 BoxLayer::setProperties(const QXmlAttributes &attributes)
1199 {
1200 SingleColourLayer::setProperties(attributes);
1201
1202 bool ok;
1203 VerticalScale scale = (VerticalScale)
1204 attributes.value("verticalScale").toInt(&ok);
1205 if (ok) setVerticalScale(scale);
1206 }
1207
1208