comparison layer/TimeInstantLayer.cpp @ 0:fc9323a41f5a

start base : Sonic Visualiser sv1-1.0rc1
author lbajardsilogic
date Fri, 11 May 2007 09:08:14 +0000
parents
children d8e6709e9075
comparison
equal deleted inserted replaced
-1:000000000000 0:fc9323a41f5a
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 This file copyright 2006 Chris Cannam.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version. See the file
13 COPYING included with this distribution for more information.
14 */
15
16 #include "TimeInstantLayer.h"
17
18 #include "data/model/Model.h"
19 #include "base/RealTime.h"
20 #include "view/View.h"
21 #include "base/Profiler.h"
22 #include "base/Clipboard.h"
23
24 #include "data/model/SparseOneDimensionalModel.h"
25
26 #include "widgets/ItemEditDialog.h"
27
28 #include <QPainter>
29 #include <QMouseEvent>
30
31 #include <iostream>
32 #include <cmath>
33
34 TimeInstantLayer::TimeInstantLayer() :
35 Layer(),
36 m_model(0),
37 m_editing(false),
38 m_editingPoint(0, tr("New Point")),
39 m_editingCommand(0),
40 m_colour(QColor(200, 50, 255)),
41 m_plotStyle(PlotInstants)
42 {
43
44 }
45
46 void
47 TimeInstantLayer::setModel(SparseOneDimensionalModel *model)
48 {
49 if (m_model == model) return;
50 m_model = model;
51
52 connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged()));
53 connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
54 this, SIGNAL(modelChanged(size_t, size_t)));
55
56 connect(m_model, SIGNAL(completionChanged()),
57 this, SIGNAL(modelCompletionChanged()));
58
59 std::cerr << "TimeInstantLayer::setModel(" << model << ")" << std::endl;
60
61 emit modelReplaced();
62 }
63
64 Layer::PropertyList
65 TimeInstantLayer::getProperties() const
66 {
67 PropertyList list;
68 list.push_back("Colour");
69 list.push_back("Plot Type");
70 return list;
71 }
72
73 QString
74 TimeInstantLayer::getPropertyLabel(const PropertyName &name) const
75 {
76 if (name == "Colour") return tr("Colour");
77 if (name == "Plot Type") return tr("Plot Type");
78 return "";
79 }
80
81 Layer::PropertyType
82 TimeInstantLayer::getPropertyType(const PropertyName &) const
83 {
84 return ValueProperty;
85 }
86
87 int
88 TimeInstantLayer::getPropertyRangeAndValue(const PropertyName &name,
89 int *min, int *max, int *deflt) const
90 {
91 int val = 0;
92
93 if (name == "Colour") {
94
95 if (min) *min = 0;
96 if (max) *max = 5;
97 if (deflt) *deflt = 0;
98
99 if (m_colour == Qt::black) val = 0;
100 else if (m_colour == Qt::darkRed) val = 1;
101 else if (m_colour == Qt::darkBlue) val = 2;
102 else if (m_colour == Qt::darkGreen) val = 3;
103 else if (m_colour == QColor(200, 50, 255)) val = 4;
104 else if (m_colour == QColor(255, 150, 50)) val = 5;
105
106 } else if (name == "Plot Type") {
107
108 if (min) *min = 0;
109 if (max) *max = 1;
110 if (deflt) *deflt = 0;
111
112 val = int(m_plotStyle);
113
114 } else {
115
116 val = Layer::getPropertyRangeAndValue(name, min, max, deflt);
117 }
118
119 return val;
120 }
121
122 QString
123 TimeInstantLayer::getPropertyValueLabel(const PropertyName &name,
124 int value) const
125 {
126 if (name == "Colour") {
127 switch (value) {
128 default:
129 case 0: return tr("Black");
130 case 1: return tr("Red");
131 case 2: return tr("Blue");
132 case 3: return tr("Green");
133 case 4: return tr("Purple");
134 case 5: return tr("Orange");
135 }
136 } else if (name == "Plot Type") {
137 switch (value) {
138 default:
139 case 0: return tr("Instants");
140 case 1: return tr("Segmentation");
141 }
142 }
143 return tr("<unknown>");
144 }
145
146 void
147 TimeInstantLayer::setProperty(const PropertyName &name, int value)
148 {
149 if (name == "Colour") {
150 switch (value) {
151 default:
152 case 0: setBaseColour(Qt::black); break;
153 case 1: setBaseColour(Qt::darkRed); break;
154 case 2: setBaseColour(Qt::darkBlue); break;
155 case 3: setBaseColour(Qt::darkGreen); break;
156 case 4: setBaseColour(QColor(200, 50, 255)); break;
157 case 5: setBaseColour(QColor(255, 150, 50)); break;
158 }
159 } else if (name == "Plot Type") {
160 setPlotStyle(PlotStyle(value));
161 }
162 }
163
164 void
165 TimeInstantLayer::setBaseColour(QColor colour)
166 {
167 if (m_colour == colour) return;
168 m_colour = colour;
169 emit layerParametersChanged();
170 }
171
172 void
173 TimeInstantLayer::setPlotStyle(PlotStyle style)
174 {
175 if (m_plotStyle == style) return;
176 m_plotStyle = style;
177 emit layerParametersChanged();
178 }
179
180 bool
181 TimeInstantLayer::isLayerScrollable(const View *v) const
182 {
183 QPoint discard;
184 return !v->shouldIlluminateLocalFeatures(this, discard);
185 }
186
187 SparseOneDimensionalModel::PointList
188 TimeInstantLayer::getLocalPoints(View *v, int x) const
189 {
190 // Return a set of points that all have the same frame number, the
191 // nearest to the given x coordinate, and that are within a
192 // certain fuzz distance of that x coordinate.
193
194 if (!m_model) return SparseOneDimensionalModel::PointList();
195
196 long frame = v->getFrameForX(x);
197
198 SparseOneDimensionalModel::PointList onPoints =
199 m_model->getPoints(frame);
200
201 if (!onPoints.empty()) {
202 return onPoints;
203 }
204
205 SparseOneDimensionalModel::PointList prevPoints =
206 m_model->getPreviousPoints(frame);
207 SparseOneDimensionalModel::PointList nextPoints =
208 m_model->getNextPoints(frame);
209
210 SparseOneDimensionalModel::PointList usePoints = prevPoints;
211
212 if (prevPoints.empty()) {
213 usePoints = nextPoints;
214 } else if (long(prevPoints.begin()->frame) < v->getStartFrame() &&
215 !(nextPoints.begin()->frame > v->getEndFrame())) {
216 usePoints = nextPoints;
217 } else if (nextPoints.begin()->frame - frame <
218 frame - prevPoints.begin()->frame) {
219 usePoints = nextPoints;
220 }
221
222 if (!usePoints.empty()) {
223 int fuzz = 2;
224 int px = v->getXForFrame(usePoints.begin()->frame);
225 if ((px > x && px - x > fuzz) ||
226 (px < x && x - px > fuzz + 1)) {
227 usePoints.clear();
228 }
229 }
230
231 return usePoints;
232 }
233
234 QString
235 TimeInstantLayer::getFeatureDescription(View *v, QPoint &pos) const
236 {
237 int x = pos.x();
238
239 if (!m_model || !m_model->getSampleRate()) return "";
240
241 SparseOneDimensionalModel::PointList points = getLocalPoints(v, x);
242
243 if (points.empty()) {
244 if (!m_model->isReady()) {
245 return tr("In progress");
246 } else {
247 return tr("No local points");
248 }
249 }
250
251 long useFrame = points.begin()->frame;
252
253 RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
254
255 QString text;
256
257 if (points.begin()->label == "") {
258 text = QString(tr("Time:\t%1\nNo label"))
259 .arg(rt.toText(true).c_str());
260 } else {
261 text = QString(tr("Time:\t%1\nLabel:\t%2"))
262 .arg(rt.toText(true).c_str())
263 .arg(points.begin()->label);
264 }
265
266 pos = QPoint(v->getXForFrame(useFrame), pos.y());
267 return text;
268 }
269
270 bool
271 TimeInstantLayer::snapToFeatureFrame(View *v, int &frame,
272 size_t &resolution,
273 SnapType snap) const
274 {
275 if (!m_model) {
276 return Layer::snapToFeatureFrame(v, frame, resolution, snap);
277 }
278
279 resolution = m_model->getResolution();
280 SparseOneDimensionalModel::PointList points;
281
282 if (snap == SnapNeighbouring) {
283
284 points = getLocalPoints(v, v->getXForFrame(frame));
285 if (points.empty()) return false;
286 frame = points.begin()->frame;
287 return true;
288 }
289
290 points = m_model->getPoints(frame, frame);
291 int snapped = frame;
292 bool found = false;
293
294 for (SparseOneDimensionalModel::PointList::const_iterator i = points.begin();
295 i != points.end(); ++i) {
296
297 if (snap == SnapRight) {
298
299 if (i->frame >= frame) {
300 snapped = i->frame;
301 found = true;
302 break;
303 }
304
305 } else if (snap == SnapLeft) {
306
307 if (i->frame <= frame) {
308 snapped = i->frame;
309 found = true; // don't break, as the next may be better
310 } else {
311 break;
312 }
313
314 } else { // nearest
315
316 SparseOneDimensionalModel::PointList::const_iterator j = i;
317 ++j;
318
319 if (j == points.end()) {
320
321 snapped = i->frame;
322 found = true;
323 break;
324
325 } else if (j->frame >= frame) {
326
327 if (j->frame - frame < frame - i->frame) {
328 snapped = j->frame;
329 } else {
330 snapped = i->frame;
331 }
332 found = true;
333 break;
334 }
335 }
336 }
337
338 frame = snapped;
339 return found;
340 }
341
342 void
343 TimeInstantLayer::paint(View *v, QPainter &paint, QRect rect) const
344 {
345 if (!m_model || !m_model->isOK()) return;
346
347 // Profiler profiler("TimeInstantLayer::paint", true);
348
349 int x0 = rect.left(), x1 = rect.right();
350
351 long frame0 = v->getFrameForX(x0);
352 long frame1 = v->getFrameForX(x1);
353
354 SparseOneDimensionalModel::PointList points(m_model->getPoints
355 (frame0, frame1));
356
357 bool odd = false;
358 if (m_plotStyle == PlotSegmentation && !points.empty()) {
359 int index = m_model->getIndexOf(*points.begin());
360 odd = ((index % 2) == 1);
361 }
362
363 paint.setPen(m_colour);
364
365 QColor brushColour(m_colour);
366 brushColour.setAlpha(100);
367 paint.setBrush(brushColour);
368
369 QColor oddBrushColour(brushColour);
370 if (m_plotStyle == PlotSegmentation) {
371 if (m_colour == Qt::black) {
372 oddBrushColour = Qt::gray;
373 } else if (m_colour == Qt::darkRed) {
374 oddBrushColour = Qt::red;
375 } else if (m_colour == Qt::darkBlue) {
376 oddBrushColour = Qt::blue;
377 } else if (m_colour == Qt::darkGreen) {
378 oddBrushColour = Qt::green;
379 } else {
380 oddBrushColour = oddBrushColour.light(150);
381 }
382 oddBrushColour.setAlpha(100);
383 }
384
385 // std::cerr << "TimeInstantLayer::paint: resolution is "
386 // << m_model->getResolution() << " frames" << std::endl;
387
388 QPoint localPos;
389 long illuminateFrame = -1;
390
391 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
392 SparseOneDimensionalModel::PointList localPoints =
393 getLocalPoints(v, localPos.x());
394 if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame;
395 }
396
397 int prevX = -1;
398 int textY = v->getTextLabelHeight(this, paint);
399
400 for (SparseOneDimensionalModel::PointList::const_iterator i = points.begin();
401 i != points.end(); ++i) {
402
403 const SparseOneDimensionalModel::Point &p(*i);
404 SparseOneDimensionalModel::PointList::const_iterator j = i;
405 ++j;
406
407 int x = v->getXForFrame(p.frame);
408 if (x == prevX && p.frame != illuminateFrame) continue;
409
410 int iw = v->getXForFrame(p.frame + m_model->getResolution()) - x;
411 if (iw < 2) {
412 if (iw < 1) {
413 iw = 2;
414 if (j != points.end()) {
415 int nx = v->getXForFrame(j->frame);
416 if (nx < x + 3) iw = 1;
417 }
418 } else {
419 iw = 2;
420 }
421 }
422
423 if (p.frame == illuminateFrame) {
424 paint.setPen(Qt::black); //!!!
425 } else {
426 paint.setPen(brushColour);
427 }
428
429 if (m_plotStyle == PlotInstants) {
430 if (iw > 1) {
431 paint.drawRect(x, 0, iw - 1, v->height() - 1);
432 } else {
433 paint.drawLine(x, 0, x, v->height() - 1);
434 }
435 } else {
436
437 if (odd) paint.setBrush(oddBrushColour);
438 else paint.setBrush(brushColour);
439
440 int nx;
441
442 if (j != points.end()) {
443 const SparseOneDimensionalModel::Point &q(*j);
444 nx = v->getXForFrame(q.frame);
445 } else {
446 nx = v->getXForFrame(m_model->getEndFrame());
447 }
448
449 if (nx >= x) {
450
451 if (illuminateFrame != p.frame &&
452 (nx < x + 5 || x >= v->width() - 1)) {
453 paint.setPen(Qt::NoPen);
454 }
455
456 paint.drawRect(x, -1, nx - x, v->height() + 1);
457 }
458
459 odd = !odd;
460 }
461
462 paint.setPen(m_colour);
463
464 if (p.label != "") {
465
466 // only draw if there's enough room from here to the next point
467
468 int lw = paint.fontMetrics().width(p.label);
469 bool good = true;
470
471 if (j != points.end()) {
472 int nx = v->getXForFrame(j->frame);
473 if (nx >= x && nx - x - iw - 3 <= lw) good = false;
474 }
475
476 if (good) {
477 paint.drawText(x + iw + 2, textY, p.label);
478 }
479 }
480
481 prevX = x;
482 }
483 }
484
485 void
486 TimeInstantLayer::drawStart(View *v, QMouseEvent *e)
487 {
488 std::cerr << "TimeInstantLayer::drawStart(" << e->x() << ")" << std::endl;
489
490 if (!m_model) return;
491
492 long frame = v->getFrameForX(e->x());
493 if (frame < 0) frame = 0;
494 frame = frame / m_model->getResolution() * m_model->getResolution();
495
496 m_editingPoint = SparseOneDimensionalModel::Point(frame, tr("New Point"));
497
498 if (m_editingCommand) m_editingCommand->finish();
499 m_editingCommand = new SparseOneDimensionalModel::EditCommand(m_model,
500 tr("Draw Point"));
501 m_editingCommand->addPoint(m_editingPoint);
502
503 m_editing = true;
504 }
505
506 void
507 TimeInstantLayer::drawDrag(View *v, QMouseEvent *e)
508 {
509 std::cerr << "TimeInstantLayer::drawDrag(" << e->x() << ")" << std::endl;
510
511 if (!m_model || !m_editing) return;
512
513 long frame = v->getFrameForX(e->x());
514 if (frame < 0) frame = 0;
515 frame = frame / m_model->getResolution() * m_model->getResolution();
516 m_editingCommand->deletePoint(m_editingPoint);
517 m_editingPoint.frame = frame;
518 m_editingCommand->addPoint(m_editingPoint);
519 }
520
521 void
522 TimeInstantLayer::drawEnd(View *, QMouseEvent *e)
523 {
524 std::cerr << "TimeInstantLayer::drawEnd(" << e->x() << ")" << std::endl;
525 if (!m_model || !m_editing) return;
526 QString newName = tr("Add Point at %1 s")
527 .arg(RealTime::frame2RealTime(m_editingPoint.frame,
528 m_model->getSampleRate())
529 .toText(false).c_str());
530 m_editingCommand->setName(newName);
531 m_editingCommand->finish();
532 m_editingCommand = 0;
533 m_editing = false;
534 }
535
536 void
537 TimeInstantLayer::editStart(View *v, QMouseEvent *e)
538 {
539 std::cerr << "TimeInstantLayer::editStart(" << e->x() << ")" << std::endl;
540
541 if (!m_model) return;
542
543 SparseOneDimensionalModel::PointList points = getLocalPoints(v, e->x());
544 if (points.empty()) return;
545
546 m_editingPoint = *points.begin();
547
548 if (m_editingCommand) {
549 m_editingCommand->finish();
550 m_editingCommand = 0;
551 }
552
553 m_editing = true;
554 }
555
556 void
557 TimeInstantLayer::editDrag(View *v, QMouseEvent *e)
558 {
559 std::cerr << "TimeInstantLayer::editDrag(" << e->x() << ")" << std::endl;
560
561 if (!m_model || !m_editing) return;
562
563 long frame = v->getFrameForX(e->x());
564 if (frame < 0) frame = 0;
565 frame = frame / m_model->getResolution() * m_model->getResolution();
566
567 if (!m_editingCommand) {
568 m_editingCommand = new SparseOneDimensionalModel::EditCommand(m_model,
569 tr("Drag Point"));
570 }
571
572 m_editingCommand->deletePoint(m_editingPoint);
573 m_editingPoint.frame = frame;
574 m_editingCommand->addPoint(m_editingPoint);
575 }
576
577 void
578 TimeInstantLayer::editEnd(View *, QMouseEvent *e)
579 {
580 std::cerr << "TimeInstantLayer::editEnd(" << e->x() << ")" << std::endl;
581 if (!m_model || !m_editing) return;
582 if (m_editingCommand) {
583 QString newName = tr("Move Point to %1 s")
584 .arg(RealTime::frame2RealTime(m_editingPoint.frame,
585 m_model->getSampleRate())
586 .toText(false).c_str());
587 m_editingCommand->setName(newName);
588 m_editingCommand->finish();
589 }
590 m_editingCommand = 0;
591 m_editing = false;
592 }
593
594 void
595 TimeInstantLayer::editOpen(View *v, QMouseEvent *e)
596 {
597 if (!m_model) return;
598
599 SparseOneDimensionalModel::PointList points = getLocalPoints(v, e->x());
600 if (points.empty()) return;
601
602 SparseOneDimensionalModel::Point point = *points.begin();
603
604 ItemEditDialog *dialog = new ItemEditDialog
605 (m_model->getSampleRate(),
606 ItemEditDialog::ShowTime |
607 ItemEditDialog::ShowText);
608
609 dialog->setFrameTime(point.frame);
610 dialog->setText(point.label);
611
612 if (dialog->exec() == QDialog::Accepted) {
613
614 SparseOneDimensionalModel::Point newPoint = point;
615 newPoint.frame = dialog->getFrameTime();
616 newPoint.label = dialog->getText();
617
618 SparseOneDimensionalModel::EditCommand *command =
619 new SparseOneDimensionalModel::EditCommand(m_model, tr("Edit Point"));
620 command->deletePoint(point);
621 command->addPoint(newPoint);
622 command->finish();
623 }
624
625 delete dialog;
626 }
627
628 void
629 TimeInstantLayer::moveSelection(Selection s, size_t newStartFrame)
630 {
631 if (!m_model) return;
632
633 SparseOneDimensionalModel::EditCommand *command =
634 new SparseOneDimensionalModel::EditCommand(m_model,
635 tr("Drag Selection"));
636
637 SparseOneDimensionalModel::PointList points =
638 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
639
640 for (SparseOneDimensionalModel::PointList::iterator i = points.begin();
641 i != points.end(); ++i) {
642
643 if (s.contains(i->frame)) {
644 SparseOneDimensionalModel::Point newPoint(*i);
645 newPoint.frame = i->frame + newStartFrame - s.getStartFrame();
646 command->deletePoint(*i);
647 command->addPoint(newPoint);
648 }
649 }
650
651 command->finish();
652 }
653
654 void
655 TimeInstantLayer::resizeSelection(Selection s, Selection newSize)
656 {
657 if (!m_model) return;
658
659 SparseOneDimensionalModel::EditCommand *command =
660 new SparseOneDimensionalModel::EditCommand(m_model,
661 tr("Resize Selection"));
662
663 SparseOneDimensionalModel::PointList points =
664 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
665
666 double ratio =
667 double(newSize.getEndFrame() - newSize.getStartFrame()) /
668 double(s.getEndFrame() - s.getStartFrame());
669
670 for (SparseOneDimensionalModel::PointList::iterator i = points.begin();
671 i != points.end(); ++i) {
672
673 if (s.contains(i->frame)) {
674
675 double target = i->frame;
676 target = newSize.getStartFrame() +
677 double(target - s.getStartFrame()) * ratio;
678
679 SparseOneDimensionalModel::Point newPoint(*i);
680 newPoint.frame = lrint(target);
681 command->deletePoint(*i);
682 command->addPoint(newPoint);
683 }
684 }
685
686 command->finish();
687 }
688
689 void
690 TimeInstantLayer::deleteSelection(Selection s)
691 {
692 if (!m_model) return;
693
694 SparseOneDimensionalModel::EditCommand *command =
695 new SparseOneDimensionalModel::EditCommand(m_model,
696 tr("Delete Selection"));
697
698 SparseOneDimensionalModel::PointList points =
699 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
700
701 for (SparseOneDimensionalModel::PointList::iterator i = points.begin();
702 i != points.end(); ++i) {
703 if (s.contains(i->frame)) command->deletePoint(*i);
704 }
705
706 command->finish();
707 }
708
709 void
710 TimeInstantLayer::copy(Selection s, Clipboard &to)
711 {
712 if (!m_model) return;
713
714 SparseOneDimensionalModel::PointList points =
715 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
716
717 for (SparseOneDimensionalModel::PointList::iterator i = points.begin();
718 i != points.end(); ++i) {
719 if (s.contains(i->frame)) {
720 Clipboard::Point point(i->frame, i->label);
721 to.addPoint(point);
722 }
723 }
724 }
725
726 bool
727 TimeInstantLayer::paste(const Clipboard &from, int frameOffset, bool)
728 {
729 if (!m_model) return false;
730
731 const Clipboard::PointList &points = from.getPoints();
732
733 SparseOneDimensionalModel::EditCommand *command =
734 new SparseOneDimensionalModel::EditCommand(m_model, tr("Paste"));
735
736 for (Clipboard::PointList::const_iterator i = points.begin();
737 i != points.end(); ++i) {
738
739 if (!i->haveFrame()) continue;
740 size_t frame = 0;
741 if (frameOffset > 0 || -frameOffset < i->getFrame()) {
742 frame = i->getFrame() + frameOffset;
743 }
744 SparseOneDimensionalModel::Point newPoint(frame);
745 if (i->haveLabel()) {
746 newPoint.label = i->getLabel();
747 } else if (i->haveValue()) {
748 newPoint.label = QString("%1").arg(i->getValue());
749 }
750
751 command->addPoint(newPoint);
752 }
753
754 command->finish();
755 return true;
756 }
757
758 QString
759 TimeInstantLayer::toXmlString(QString indent, QString extraAttributes) const
760 {
761 return Layer::toXmlString(indent, extraAttributes +
762 QString(" colour=\"%1\" plotStyle=\"%2\"")
763 .arg(encodeColour(m_colour)).arg(m_plotStyle));
764 }
765
766 void
767 TimeInstantLayer::setProperties(const QXmlAttributes &attributes)
768 {
769 QString colourSpec = attributes.value("colour");
770 if (colourSpec != "") {
771 QColor colour(colourSpec);
772 if (colour.isValid()) {
773 setBaseColour(QColor(colourSpec));
774 }
775 }
776
777 bool ok;
778 PlotStyle style = (PlotStyle)
779 attributes.value("plotStyle").toInt(&ok);
780 if (ok) setPlotStyle(style);
781 }
782