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