Mercurial > hg > svgui
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 |