comparison layer/TimeFrequencyBoxLayer.cpp @ 1511:a473b73fb045 time-frequency-boxes

Introduce time-frequency box layer
author Chris Cannam
date Thu, 19 Sep 2019 15:18:28 +0100
parents
children e453053a44dc
comparison
equal deleted inserted replaced
1510:872873aa6463 1511:a473b73fb045
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 "TimeFrequencyBoxLayer.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/TimeFrequencyBoxModel.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 TimeFrequencyBoxLayer::TimeFrequencyBoxLayer() :
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 {
55
56 }
57
58 int
59 TimeFrequencyBoxLayer::getCompletion(LayerGeometryProvider *) const
60 {
61 auto model = ModelById::get(m_model);
62 if (model) return model->getCompletion();
63 else return 0;
64 }
65
66 void
67 TimeFrequencyBoxLayer::setModel(ModelId modelId)
68 {
69 auto oldModel = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
70 auto newModel = ModelById::getAs<TimeFrequencyBoxModel>(modelId);
71
72 if (!modelId.isNone() && !newModel) {
73 throw std::logic_error("Not a TimeFrequencyBoxModel");
74 }
75
76 if (m_model == modelId) return;
77 m_model = modelId;
78
79 if (newModel) {
80 connectSignals(m_model);
81 }
82
83 emit modelReplaced();
84 }
85
86 Layer::PropertyList
87 TimeFrequencyBoxLayer::getProperties() const
88 {
89 PropertyList list = SingleColourLayer::getProperties();
90 list.push_back("Vertical Scale");
91 return list;
92 }
93
94 QString
95 TimeFrequencyBoxLayer::getPropertyLabel(const PropertyName &name) const
96 {
97 if (name == "Vertical Scale") return tr("Vertical Scale");
98 return SingleColourLayer::getPropertyLabel(name);
99 }
100
101 Layer::PropertyType
102 TimeFrequencyBoxLayer::getPropertyType(const PropertyName &name) const
103 {
104 if (name == "Vertical Scale") return ValueProperty;
105 return SingleColourLayer::getPropertyType(name);
106 }
107
108 QString
109 TimeFrequencyBoxLayer::getPropertyGroupName(const PropertyName &name) const
110 {
111 if (name == "Vertical Scale") {
112 return tr("Scale");
113 }
114 return SingleColourLayer::getPropertyGroupName(name);
115 }
116
117 int
118 TimeFrequencyBoxLayer::getPropertyRangeAndValue(const PropertyName &name,
119 int *min, int *max, int *deflt) const
120 {
121 int val = 0;
122
123 if (name == "Vertical Scale") {
124
125 if (min) *min = 0;
126 if (max) *max = 2;
127 if (deflt) *deflt = int(LinearScale);
128
129 val = int(m_verticalScale);
130
131 } else {
132 val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
133 }
134
135 return val;
136 }
137
138 QString
139 TimeFrequencyBoxLayer::getPropertyValueLabel(const PropertyName &name,
140 int value) const
141 {
142 if (name == "Vertical Scale") {
143 switch (value) {
144 default:
145 case 0: return tr("Auto-Align");
146 case 1: return tr("Linear");
147 case 2: return tr("Log");
148 }
149 }
150 return SingleColourLayer::getPropertyValueLabel(name, value);
151 }
152
153 void
154 TimeFrequencyBoxLayer::setProperty(const PropertyName &name, int value)
155 {
156 if (name == "Vertical Scale") {
157 setVerticalScale(VerticalScale(value));
158 } else {
159 return SingleColourLayer::setProperty(name, value);
160 }
161 }
162
163 void
164 TimeFrequencyBoxLayer::setVerticalScale(VerticalScale scale)
165 {
166 if (m_verticalScale == scale) return;
167 m_verticalScale = scale;
168 emit layerParametersChanged();
169 }
170
171 bool
172 TimeFrequencyBoxLayer::isLayerScrollable(const LayerGeometryProvider *v) const
173 {
174 QPoint discard;
175 return !v->shouldIlluminateLocalFeatures(this, discard);
176 }
177
178 bool
179 TimeFrequencyBoxLayer::getValueExtents(double &min, double &max,
180 bool &logarithmic, QString &unit) const
181 {
182 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
183 if (!model) return false;
184 min = model->getFrequencyMinimum();
185 max = model->getFrequencyMaximum();
186 unit = getScaleUnits();
187
188 if (m_verticalScale == LogScale) logarithmic = true;
189
190 return true;
191 }
192
193 bool
194 TimeFrequencyBoxLayer::getDisplayExtents(double &min, double &max) const
195 {
196 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
197 if (!model || m_verticalScale == AutoAlignScale) return false;
198
199 min = model->getFrequencyMinimum();
200 max = model->getFrequencyMaximum();
201
202 return true;
203 }
204
205 EventVector
206 TimeFrequencyBoxLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
207 {
208 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
209 if (!model) return EventVector();
210
211 sv_frame_t frame = v->getFrameForX(x);
212
213 EventVector local = model->getEventsCovering(frame);
214 if (!local.empty()) return local;
215
216 int fuzz = ViewManager::scalePixelSize(2);
217 sv_frame_t start = v->getFrameForX(x - fuzz);
218 sv_frame_t end = v->getFrameForX(x + fuzz);
219
220 local = model->getEventsStartingWithin(frame, end - frame);
221 if (!local.empty()) return local;
222
223 local = model->getEventsSpanning(start, frame - start);
224 if (!local.empty()) return local;
225
226 return {};
227 }
228
229 bool
230 TimeFrequencyBoxLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y,
231 Event &point) const
232 {
233 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
234 if (!model) return false;
235
236 sv_frame_t frame = v->getFrameForX(x);
237
238 EventVector onPoints = model->getEventsCovering(frame);
239 if (onPoints.empty()) return false;
240
241 int nearestDistance = -1;
242 for (const auto &p: onPoints) {
243 int distance = getYForValue(v, p.getValue()) - y;
244 if (distance < 0) distance = -distance;
245 if (nearestDistance == -1 || distance < nearestDistance) {
246 nearestDistance = distance;
247 point = p;
248 }
249 }
250
251 return true;
252 }
253
254 QString
255 TimeFrequencyBoxLayer::getLabelPreceding(sv_frame_t frame) const
256 {
257 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
258 if (!model) return "";
259 EventVector points = model->getEventsStartingWithin
260 (model->getStartFrame(), frame - model->getStartFrame());
261 if (!points.empty()) {
262 for (auto i = points.rbegin(); i != points.rend(); ++i) {
263 if (i->getLabel() != QString()) {
264 return i->getLabel();
265 }
266 }
267 }
268 return QString();
269 }
270
271 QString
272 TimeFrequencyBoxLayer::getFeatureDescription(LayerGeometryProvider *v,
273 QPoint &pos) const
274 {
275 int x = pos.x();
276
277 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
278 if (!model || !model->getSampleRate()) return "";
279
280 EventVector points = getLocalPoints(v, x);
281
282 if (points.empty()) {
283 if (!model->isReady()) {
284 return tr("In progress");
285 } else {
286 return tr("No local points");
287 }
288 }
289
290 Event box;
291 EventVector::iterator i;
292
293 for (i = points.begin(); i != points.end(); ++i) {
294
295 int y0 = getYForValue(v, i->getValue());
296 int y1 = getYForValue(v, i->getValue() + fabsf(i->getLevel()));
297
298 if (pos.y() >= y0 && pos.y() <= y1) {
299 box = *i;
300 break;
301 }
302 }
303
304 if (i == points.end()) return tr("No local points");
305
306 RealTime rt = RealTime::frame2RealTime(box.getFrame(),
307 model->getSampleRate());
308 RealTime rd = RealTime::frame2RealTime(box.getDuration(),
309 model->getSampleRate());
310
311 QString rangeText;
312
313 rangeText = tr("%1 %2 - %3 %4")
314 .arg(box.getValue()).arg(getScaleUnits())
315 .arg(box.getValue() + fabsf(box.getLevel())).arg(getScaleUnits());
316
317 QString text;
318
319 if (box.getLabel() == "") {
320 text = QString(tr("Time:\t%1\nDuration:\t%2\nFrequency:\t%3\nNo label"))
321 .arg(rt.toText(true).c_str())
322 .arg(rd.toText(true).c_str())
323 .arg(rangeText);
324 } else {
325 text = QString(tr("Time:\t%1\nDuration:\t%2\nFrequency:\t%3\nLabel:\t%4"))
326 .arg(rt.toText(true).c_str())
327 .arg(rd.toText(true).c_str())
328 .arg(rangeText)
329 .arg(box.getLabel());
330 }
331
332 pos = QPoint(v->getXForFrame(box.getFrame()),
333 getYForValue(v, box.getValue()));
334 return text;
335 }
336
337 bool
338 TimeFrequencyBoxLayer::snapToFeatureFrame(LayerGeometryProvider *v,
339 sv_frame_t &frame,
340 int &resolution,
341 SnapType snap) const
342 {
343 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
344 if (!model) {
345 return Layer::snapToFeatureFrame(v, frame, resolution, snap);
346 }
347
348 // SnapLeft / SnapRight: return frame of nearest feature in that
349 // direction no matter how far away
350 //
351 // SnapNeighbouring: return frame of feature that would be used in
352 // an editing operation, i.e. closest feature in either direction
353 // but only if it is "close enough"
354
355 resolution = model->getResolution();
356
357 if (snap == SnapNeighbouring) {
358 EventVector points = getLocalPoints(v, v->getXForFrame(frame));
359 if (points.empty()) return false;
360 frame = points.begin()->getFrame();
361 return true;
362 }
363
364 // Normally we snap to the start frame of whichever event we
365 // find. However here, for SnapRight only, if the end frame of
366 // whichever event we would have snapped to had we been snapping
367 // left is closer than the start frame of the next event to the
368 // right, then we snap to that frame instead. Clear?
369
370 Event left;
371 bool haveLeft = false;
372 if (model->getNearestEventMatching
373 (frame, [](Event) { return true; }, EventSeries::Backward, left)) {
374 haveLeft = true;
375 }
376
377 if (snap == SnapLeft) {
378 frame = left.getFrame();
379 return haveLeft;
380 }
381
382 Event right;
383 bool haveRight = false;
384 if (model->getNearestEventMatching
385 (frame, [](Event) { return true; }, EventSeries::Forward, right)) {
386 haveRight = true;
387 }
388
389 if (haveLeft) {
390 sv_frame_t leftEnd = left.getFrame() + left.getDuration();
391 if (leftEnd > frame) {
392 if (haveRight) {
393 if (leftEnd - frame < right.getFrame() - frame) {
394 frame = leftEnd;
395 } else {
396 frame = right.getFrame();
397 }
398 } else {
399 frame = leftEnd;
400 }
401 return true;
402 }
403 }
404
405 if (haveRight) {
406 frame = right.getFrame();
407 return true;
408 }
409
410 return false;
411 }
412
413 QString
414 TimeFrequencyBoxLayer::getScaleUnits() const
415 {
416 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
417 if (model) return model->getScaleUnits();
418 else return "";
419 }
420
421 void
422 TimeFrequencyBoxLayer::getScaleExtents(LayerGeometryProvider *v,
423 double &min, double &max,
424 bool &log) const
425 {
426 min = 0.0;
427 max = 0.0;
428 log = false;
429
430 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
431 if (!model) return;
432
433 QString queryUnits;
434 queryUnits = getScaleUnits();
435
436 if (m_verticalScale == AutoAlignScale) {
437
438 if (!v->getValueExtents(queryUnits, min, max, log)) {
439
440 min = model->getFrequencyMinimum();
441 max = model->getFrequencyMaximum();
442
443 // cerr << "TimeFrequencyBoxLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
444
445 } else if (log) {
446
447 LogRange::mapRange(min, max);
448
449 // cerr << "TimeFrequencyBoxLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
450
451 }
452
453 } else {
454
455 min = model->getFrequencyMinimum();
456 max = model->getFrequencyMaximum();
457
458 if (m_verticalScale == LogScale) {
459 LogRange::mapRange(min, max);
460 log = true;
461 }
462 }
463
464 if (max == min) max = min + 1.0;
465 }
466
467 int
468 TimeFrequencyBoxLayer::getYForValue(LayerGeometryProvider *v, double val) const
469 {
470 double min = 0.0, max = 0.0;
471 bool logarithmic = false;
472 int h = v->getPaintHeight();
473
474 getScaleExtents(v, min, max, logarithmic);
475
476 // cerr << "TimeFrequencyBoxLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl;
477 // cerr << "h = " << h << ", margin = " << margin << endl;
478
479 if (logarithmic) {
480 val = LogRange::map(val);
481 }
482
483 return int(h - ((val - min) * h) / (max - min));
484 }
485
486 double
487 TimeFrequencyBoxLayer::getValueForY(LayerGeometryProvider *v, int y) const
488 {
489 double min = 0.0, max = 0.0;
490 bool logarithmic = false;
491 int h = v->getPaintHeight();
492
493 getScaleExtents(v, min, max, logarithmic);
494
495 double val = min + (double(h - y) * double(max - min)) / h;
496
497 if (logarithmic) {
498 val = pow(10.0, val);
499 }
500
501 return val;
502 }
503
504 void
505 TimeFrequencyBoxLayer::paint(LayerGeometryProvider *v, QPainter &paint,
506 QRect rect) const
507 {
508 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
509 if (!model || !model->isOK()) return;
510
511 sv_samplerate_t sampleRate = model->getSampleRate();
512 if (!sampleRate) return;
513
514 // Profiler profiler("TimeFrequencyBoxLayer::paint", true);
515
516 int x0 = rect.left() - 40, x1 = rect.right();
517
518 sv_frame_t wholeFrame0 = v->getFrameForX(0);
519 sv_frame_t wholeFrame1 = v->getFrameForX(v->getPaintWidth());
520
521 EventVector points(model->getEventsSpanning(wholeFrame0,
522 wholeFrame1 - wholeFrame0));
523 if (points.empty()) return;
524
525 paint.setPen(getBaseQColor());
526
527 QColor brushColour(getBaseQColor());
528 brushColour.setAlpha(80);
529
530 // SVDEBUG << "TimeFrequencyBoxLayer::paint: resolution is "
531 // << model->getResolution() << " frames" << endl;
532
533 double min = model->getFrequencyMinimum();
534 double max = model->getFrequencyMaximum();
535 if (max == min) max = min + 1.0;
536
537 QPoint localPos;
538 Event illuminatePoint(0);
539 bool shouldIlluminate = false;
540
541 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
542 shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(),
543 illuminatePoint);
544 }
545
546 paint.save();
547 paint.setRenderHint(QPainter::Antialiasing, false);
548
549 for (EventVector::const_iterator i = points.begin();
550 i != points.end(); ++i) {
551
552 const Event &p(*i);
553
554 int x = v->getXForFrame(p.getFrame());
555 int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x;
556 int y = getYForValue(v, p.getValue());
557 int h = getYForValue(v, p.getValue() + fabsf(p.getLevel()));
558 int ex = x + w;
559
560 int gap = v->scalePixelSize(2);
561
562 EventVector::const_iterator j = i;
563 ++j;
564
565 if (j != points.end()) {
566 const Event &q(*j);
567 int nx = v->getXForFrame(q.getFrame());
568 if (nx < ex) ex = nx;
569 }
570
571 if (w < 1) w = 1;
572 if (h < 1) h = 1;
573
574 paint.setPen(getBaseQColor());
575 paint.setBrush(brushColour);
576
577 if (shouldIlluminate && illuminatePoint == p) {
578
579 paint.setPen(v->getForeground());
580 paint.setBrush(v->getForeground());
581
582 // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
583 // replacement (horizontalAdvance) was only added in Qt 5.11
584 // which is too new for us
585 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
586
587 QString vlabel =
588 QString("%1%2 - %3%4")
589 .arg(p.getValue()).arg(getScaleUnits())
590 .arg(p.getValue() + fabsf(p.getLevel())).arg(getScaleUnits());
591 PaintAssistant::drawVisibleText
592 (v, paint,
593 x - paint.fontMetrics().width(vlabel) - gap,
594 y + paint.fontMetrics().height()/2
595 - paint.fontMetrics().descent(),
596 vlabel, PaintAssistant::OutlinedText);
597
598 QString hlabel = RealTime::frame2RealTime
599 (p.getFrame(), model->getSampleRate()).toText(true).c_str();
600 PaintAssistant::drawVisibleText
601 (v, paint,
602 x,
603 y - h/2 - paint.fontMetrics().descent() - gap,
604 hlabel, PaintAssistant::OutlinedText);
605 }
606
607 paint.drawRect(x, y, w, h);
608 }
609
610 for (EventVector::const_iterator i = points.begin();
611 i != points.end(); ++i) {
612
613 const Event &p(*i);
614
615 int x = v->getXForFrame(p.getFrame());
616 int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x;
617 int y = getYForValue(v, p.getValue());
618
619 QString label = p.getLabel();
620 if (label == "") {
621 label =
622 QString("%1%2 - %3%4")
623 .arg(p.getValue()).arg(getScaleUnits())
624 .arg(p.getValue() + fabsf(p.getLevel())).arg(getScaleUnits());
625 }
626 int labelWidth = paint.fontMetrics().width(label);
627
628 int gap = v->scalePixelSize(2);
629
630 if (x + w < x0 || x - labelWidth - gap > x1) {
631 continue;
632 }
633
634 bool illuminated = false;
635
636 if (shouldIlluminate && illuminatePoint == p) {
637 illuminated = true;
638 }
639
640 if (!illuminated) {
641
642 int labelX, labelY;
643
644 labelX = x - labelWidth - gap;
645 labelY = y + paint.fontMetrics().height()/2
646 - paint.fontMetrics().descent();
647
648 PaintAssistant::drawVisibleText(v, paint, labelX, labelY, label,
649 PaintAssistant::OutlinedText);
650 }
651 }
652
653 paint.restore();
654 }
655
656 int
657 TimeFrequencyBoxLayer::getVerticalScaleWidth(LayerGeometryProvider *v,
658 bool, QPainter &paint) const
659 {
660 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
661 if (!model || m_verticalScale == AutoAlignScale) {
662 return 0;
663 } else {
664 if (m_verticalScale == LogScale) {
665 return LogNumericalScale().getWidth(v, paint);
666 } else {
667 return LinearNumericalScale().getWidth(v, paint);
668 }
669 }
670 }
671
672 void
673 TimeFrequencyBoxLayer::paintVerticalScale(LayerGeometryProvider *v,
674 bool, QPainter &paint, QRect) const
675 {
676 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
677 if (!model || model->isEmpty()) return;
678
679 QString unit;
680 double min, max;
681 bool logarithmic;
682
683 int w = getVerticalScaleWidth(v, false, paint);
684
685 getScaleExtents(v, min, max, logarithmic);
686
687 if (logarithmic) {
688 LogNumericalScale().paintVertical(v, this, paint, 0, min, max);
689 } else {
690 LinearNumericalScale().paintVertical(v, this, paint, 0, min, max);
691 }
692
693 if (getScaleUnits() != "") {
694 int mw = w - 5;
695 paint.drawText(5,
696 5 + paint.fontMetrics().ascent(),
697 TextAbbrev::abbreviate(getScaleUnits(),
698 paint.fontMetrics(),
699 mw));
700 }
701 }
702
703 //!!! All of the editing operations still need to be updated for
704 //!!! vertical frequency range instead of just value
705
706 void
707 TimeFrequencyBoxLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
708 {
709 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
710 if (!model) return;
711
712 sv_frame_t frame = v->getFrameForX(e->x());
713 if (frame < 0) frame = 0;
714 frame = frame / model->getResolution() * model->getResolution();
715
716 double value = getValueForY(v, e->y());
717
718 m_editingPoint = Event(frame, float(value), 0, "");
719 m_originalPoint = m_editingPoint;
720
721 if (m_editingCommand) finish(m_editingCommand);
722 m_editingCommand = new ChangeEventsCommand(m_model.untyped,
723 tr("Draw Time-Frequency Box"));
724 m_editingCommand->add(m_editingPoint);
725
726 m_editing = true;
727 }
728
729 void
730 TimeFrequencyBoxLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
731 {
732 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
733 if (!model || !m_editing) return;
734
735 sv_frame_t frame = v->getFrameForX(e->x());
736 if (frame < 0) frame = 0;
737 frame = frame / model->getResolution() * model->getResolution();
738
739 double newValue = getValueForY(v, e->y());
740
741 sv_frame_t newFrame = m_editingPoint.getFrame();
742 sv_frame_t newDuration = frame - newFrame;
743 if (newDuration < 0) {
744 newFrame = frame;
745 newDuration = -newDuration;
746 } else if (newDuration == 0) {
747 newDuration = 1;
748 }
749
750 m_editingCommand->remove(m_editingPoint);
751 m_editingPoint = m_editingPoint
752 .withFrame(newFrame)
753 .withValue(float(newValue))
754 .withDuration(newDuration);
755 m_editingCommand->add(m_editingPoint);
756 }
757
758 void
759 TimeFrequencyBoxLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
760 {
761 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
762 if (!model || !m_editing) return;
763 finish(m_editingCommand);
764 m_editingCommand = nullptr;
765 m_editing = false;
766 }
767
768 void
769 TimeFrequencyBoxLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
770 {
771 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
772 if (!model) return;
773
774 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
775
776 if (m_editingCommand) {
777 finish(m_editingCommand);
778 m_editingCommand = nullptr;
779 }
780
781 m_editing = true;
782 }
783
784 void
785 TimeFrequencyBoxLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
786 {
787 }
788
789 void
790 TimeFrequencyBoxLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
791 {
792 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
793 if (!model || !m_editing) return;
794
795 m_editing = false;
796
797 Event p(0);
798 if (!getPointToDrag(v, e->x(), e->y(), p)) return;
799 if (p.getFrame() != m_editingPoint.getFrame() ||
800 p.getValue() != m_editingPoint.getValue()) return;
801
802 m_editingCommand = new ChangeEventsCommand
803 (m_model.untyped, tr("Erase Time-Frequency Box"));
804
805 m_editingCommand->remove(m_editingPoint);
806
807 finish(m_editingCommand);
808 m_editingCommand = nullptr;
809 m_editing = false;
810 }
811
812 void
813 TimeFrequencyBoxLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
814 {
815 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
816 if (!model) return;
817
818 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) {
819 return;
820 }
821
822 m_dragPointX = v->getXForFrame(m_editingPoint.getFrame());
823 m_dragPointY = getYForValue(v, m_editingPoint.getValue());
824
825 m_originalPoint = m_editingPoint;
826
827 if (m_editingCommand) {
828 finish(m_editingCommand);
829 m_editingCommand = nullptr;
830 }
831
832 m_editing = true;
833 m_dragStartX = e->x();
834 m_dragStartY = e->y();
835 }
836
837 void
838 TimeFrequencyBoxLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
839 {
840 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
841 if (!model || !m_editing) return;
842
843 int xdist = e->x() - m_dragStartX;
844 int ydist = e->y() - m_dragStartY;
845 int newx = m_dragPointX + xdist;
846 int newy = m_dragPointY + ydist;
847
848 sv_frame_t frame = v->getFrameForX(newx);
849 if (frame < 0) frame = 0;
850 frame = frame / model->getResolution() * model->getResolution();
851
852 double value = getValueForY(v, newy);
853
854 if (!m_editingCommand) {
855 m_editingCommand = new ChangeEventsCommand
856 (m_model.untyped,
857 tr("Drag Time-Frequency Box"));
858 }
859
860 m_editingCommand->remove(m_editingPoint);
861 m_editingPoint = m_editingPoint
862 .withFrame(frame)
863 .withValue(float(value));
864 m_editingCommand->add(m_editingPoint);
865 }
866
867 void
868 TimeFrequencyBoxLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
869 {
870 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
871 if (!model || !m_editing) return;
872
873 if (m_editingCommand) {
874
875 QString newName = m_editingCommand->getName();
876
877 if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) {
878 if (m_editingPoint.getValue() != m_originalPoint.getValue()) {
879 newName = tr("Edit Time-Frequency Box");
880 } else {
881 newName = tr("Relocate Time-Frequency Box");
882 }
883 } else {
884 newName = tr("Change Point Value");
885 }
886
887 m_editingCommand->setName(newName);
888 finish(m_editingCommand);
889 }
890
891 m_editingCommand = nullptr;
892 m_editing = false;
893 }
894
895 bool
896 TimeFrequencyBoxLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
897 {
898 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
899 if (!model) return false;
900
901 Event region(0);
902 if (!getPointToDrag(v, e->x(), e->y(), region)) return false;
903
904 ItemEditDialog *dialog = new ItemEditDialog
905 (model->getSampleRate(),
906 ItemEditDialog::ShowTime |
907 ItemEditDialog::ShowDuration |
908 ItemEditDialog::ShowValue |
909 ItemEditDialog::ShowText,
910 getScaleUnits());
911
912 dialog->setFrameTime(region.getFrame());
913 dialog->setValue(region.getValue());
914 dialog->setFrameDuration(region.getDuration());
915 dialog->setText(region.getLabel());
916
917 if (dialog->exec() == QDialog::Accepted) {
918
919 Event newTimeFrequencyBox = region
920 .withFrame(dialog->getFrameTime())
921 .withValue(dialog->getValue())
922 .withDuration(dialog->getFrameDuration())
923 .withLabel(dialog->getText());
924
925 ChangeEventsCommand *command = new ChangeEventsCommand
926 (m_model.untyped, tr("Edit Time-Frequency Box"));
927 command->remove(region);
928 command->add(newTimeFrequencyBox);
929 finish(command);
930 }
931
932 delete dialog;
933 return true;
934 }
935
936 void
937 TimeFrequencyBoxLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
938 {
939 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
940 if (!model) return;
941
942 ChangeEventsCommand *command =
943 new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
944
945 EventVector points =
946 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
947
948 for (EventVector::iterator i = points.begin();
949 i != points.end(); ++i) {
950
951 Event newPoint = (*i)
952 .withFrame(i->getFrame() + newStartFrame - s.getStartFrame());
953 command->remove(*i);
954 command->add(newPoint);
955 }
956
957 finish(command);
958 }
959
960 void
961 TimeFrequencyBoxLayer::resizeSelection(Selection s, Selection newSize)
962 {
963 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
964 if (!model || !s.getDuration()) return;
965
966 ChangeEventsCommand *command =
967 new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
968
969 EventVector points =
970 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
971
972 double ratio = double(newSize.getDuration()) / double(s.getDuration());
973 double oldStart = double(s.getStartFrame());
974 double newStart = double(newSize.getStartFrame());
975
976 for (Event p: points) {
977
978 double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
979 double newDuration = double(p.getDuration()) * ratio;
980
981 Event newPoint = p
982 .withFrame(lrint(newFrame))
983 .withDuration(lrint(newDuration));
984 command->remove(p);
985 command->add(newPoint);
986 }
987
988 finish(command);
989 }
990
991 void
992 TimeFrequencyBoxLayer::deleteSelection(Selection s)
993 {
994 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
995 if (!model) return;
996
997 ChangeEventsCommand *command =
998 new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points"));
999
1000 EventVector points =
1001 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1002
1003 for (EventVector::iterator i = points.begin();
1004 i != points.end(); ++i) {
1005
1006 if (s.contains(i->getFrame())) {
1007 command->remove(*i);
1008 }
1009 }
1010
1011 finish(command);
1012 }
1013
1014 void
1015 TimeFrequencyBoxLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
1016 {
1017 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
1018 if (!model) return;
1019
1020 EventVector points =
1021 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
1022
1023 for (Event p: points) {
1024 to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
1025 }
1026 }
1027
1028 bool
1029 TimeFrequencyBoxLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
1030 {
1031 auto model = ModelById::getAs<TimeFrequencyBoxModel>(m_model);
1032 if (!model) return false;
1033
1034 const EventVector &points = from.getPoints();
1035
1036 bool realign = false;
1037
1038 if (clipboardHasDifferentAlignment(v, from)) {
1039
1040 QMessageBox::StandardButton button =
1041 QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
1042 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?"),
1043 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
1044 QMessageBox::Yes);
1045
1046 if (button == QMessageBox::Cancel) {
1047 return false;
1048 }
1049
1050 if (button == QMessageBox::Yes) {
1051 realign = true;
1052 }
1053 }
1054
1055 ChangeEventsCommand *command =
1056 new ChangeEventsCommand(m_model.untyped, tr("Paste"));
1057
1058 for (EventVector::const_iterator i = points.begin();
1059 i != points.end(); ++i) {
1060
1061 sv_frame_t frame = 0;
1062
1063 if (!realign) {
1064
1065 frame = i->getFrame();
1066
1067 } else {
1068
1069 if (i->hasReferenceFrame()) {
1070 frame = i->getReferenceFrame();
1071 frame = alignFromReference(v, frame);
1072 } else {
1073 frame = i->getFrame();
1074 }
1075 }
1076
1077 Event p = *i;
1078 Event newPoint = p;
1079 if (!p.hasValue()) {
1080 newPoint = newPoint.withValue((model->getFrequencyMinimum() +
1081 model->getFrequencyMaximum()) / 2);
1082 }
1083 if (!p.hasDuration()) {
1084 sv_frame_t nextFrame = frame;
1085 EventVector::const_iterator j = i;
1086 for (; j != points.end(); ++j) {
1087 if (j != i) break;
1088 }
1089 if (j != points.end()) {
1090 nextFrame = j->getFrame();
1091 }
1092 if (nextFrame == frame) {
1093 newPoint = newPoint.withDuration(model->getResolution());
1094 } else {
1095 newPoint = newPoint.withDuration(nextFrame - frame);
1096 }
1097 }
1098
1099 command->add(newPoint);
1100 }
1101
1102 finish(command);
1103 return true;
1104 }
1105
1106 void
1107 TimeFrequencyBoxLayer::toXml(QTextStream &stream,
1108 QString indent, QString extraAttributes) const
1109 {
1110 QString s;
1111
1112 s += QString("verticalScale=\"%1\" ")
1113 .arg(m_verticalScale);
1114
1115 SingleColourLayer::toXml(stream, indent, extraAttributes + " " + s);
1116 }
1117
1118 void
1119 TimeFrequencyBoxLayer::setProperties(const QXmlAttributes &attributes)
1120 {
1121 SingleColourLayer::setProperties(attributes);
1122
1123 bool ok;
1124 VerticalScale scale = (VerticalScale)
1125 attributes.value("verticalScale").toInt(&ok);
1126 if (ok) setVerticalScale(scale);
1127 }
1128
1129