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