comparison layer/TextLayer.cpp @ 35:10ba9276a315

* Add TextModel and TextLayer types * Make View refresh work better when editing a model (previously edits might not be refreshed if their visible changed area extended beyond the strict frame range that was being modified in the model) * Add phase-adjusted instantaneous frequency display to spectrogram layer (still a work in progress) * Pull maths aliases out into a separate header in dsp/maths so MathUtilities can be included without introducing them
author Chris Cannam
date Mon, 20 Feb 2006 13:33:36 +0000
parents
children c28ebb4ba4de
comparison
equal deleted inserted replaced
34:c43f2c4f66f2 35:10ba9276a315
1 /* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 A waveform viewer and audio annotation editor.
5 Chris Cannam, Queen Mary University of London, 2005-2006
6
7 This is experimental software. Not for distribution.
8 */
9
10 #include "TextLayer.h"
11
12 #include "base/Model.h"
13 #include "base/RealTime.h"
14 #include "base/Profiler.h"
15 #include "base/View.h"
16
17 #include "model/TextModel.h"
18
19 #include <QPainter>
20 #include <QMouseEvent>
21
22 #include <iostream>
23 #include <cmath>
24
25 TextLayer::TextLayer(View *w) :
26 Layer(w),
27 m_model(0),
28 m_editing(false),
29 m_originalPoint(0, 0.0, tr("Empty Label")),
30 m_editingPoint(0, 0.0, tr("Empty Label")),
31 m_editingCommand(0),
32 m_colour(255, 150, 50) // orange
33 {
34 m_view->addLayer(this);
35 }
36
37 void
38 TextLayer::setModel(TextModel *model)
39 {
40 if (m_model == model) return;
41 m_model = model;
42
43 connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged()));
44 connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
45 this, SIGNAL(modelChanged(size_t, size_t)));
46
47 connect(m_model, SIGNAL(completionChanged()),
48 this, SIGNAL(modelCompletionChanged()));
49
50 std::cerr << "TextLayer::setModel(" << model << ")" << std::endl;
51
52 emit modelReplaced();
53 }
54
55 Layer::PropertyList
56 TextLayer::getProperties() const
57 {
58 PropertyList list;
59 list.push_back(tr("Colour"));
60 return list;
61 }
62
63 Layer::PropertyType
64 TextLayer::getPropertyType(const PropertyName &name) const
65 {
66 return ValueProperty;
67 }
68
69 int
70 TextLayer::getPropertyRangeAndValue(const PropertyName &name,
71 int *min, int *max) const
72 {
73 //!!! factor this colour handling stuff out into a colour manager class
74
75 int deft = 0;
76
77 if (name == tr("Colour")) {
78
79 if (min) *min = 0;
80 if (max) *max = 5;
81
82 if (m_colour == Qt::black) deft = 0;
83 else if (m_colour == Qt::darkRed) deft = 1;
84 else if (m_colour == Qt::darkBlue) deft = 2;
85 else if (m_colour == Qt::darkGreen) deft = 3;
86 else if (m_colour == QColor(200, 50, 255)) deft = 4;
87 else if (m_colour == QColor(255, 150, 50)) deft = 5;
88
89 } else {
90
91 deft = Layer::getPropertyRangeAndValue(name, min, max);
92 }
93
94 return deft;
95 }
96
97 QString
98 TextLayer::getPropertyValueLabel(const PropertyName &name,
99 int value) const
100 {
101 if (name == tr("Colour")) {
102 switch (value) {
103 default:
104 case 0: return tr("Black");
105 case 1: return tr("Red");
106 case 2: return tr("Blue");
107 case 3: return tr("Green");
108 case 4: return tr("Purple");
109 case 5: return tr("Orange");
110 }
111 }
112 return tr("<unknown>");
113 }
114
115 void
116 TextLayer::setProperty(const PropertyName &name, int value)
117 {
118 if (name == tr("Colour")) {
119 switch (value) {
120 default:
121 case 0: setBaseColour(Qt::black); break;
122 case 1: setBaseColour(Qt::darkRed); break;
123 case 2: setBaseColour(Qt::darkBlue); break;
124 case 3: setBaseColour(Qt::darkGreen); break;
125 case 4: setBaseColour(QColor(200, 50, 255)); break;
126 case 5: setBaseColour(QColor(255, 150, 50)); break;
127 }
128 }
129 }
130
131 void
132 TextLayer::setBaseColour(QColor colour)
133 {
134 if (m_colour == colour) return;
135 m_colour = colour;
136 emit layerParametersChanged();
137 }
138
139 bool
140 TextLayer::isLayerScrollable() const
141 {
142 QPoint discard;
143 return !m_view->shouldIlluminateLocalFeatures(this, discard);
144 }
145
146
147 TextModel::PointList
148 TextLayer::getLocalPoints(int x, int y) const
149 {
150 if (!m_model) return TextModel::PointList();
151
152 long frame0 = getFrameForX(-150);
153 long frame1 = getFrameForX(m_view->width() + 150);
154
155 TextModel::PointList points(m_model->getPoints(frame0, frame1));
156
157 TextModel::PointList rv;
158 QFontMetrics metrics = QPainter().fontMetrics();
159
160 for (TextModel::PointList::iterator i = points.begin();
161 i != points.end(); ++i) {
162
163 const TextModel::Point &p(*i);
164
165 int px = getXForFrame(p.frame);
166 int py = getYForHeight(p.height);
167
168 QString label = p.label;
169 if (label == "") {
170 label = tr("<no text>");
171 }
172
173 QRect rect = metrics.boundingRect
174 (QRect(0, 0, 150, 200),
175 Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, label);
176
177 if (py + rect.height() > m_view->height()) {
178 if (rect.height() > m_view->height()) py = 0;
179 else py = m_view->height() - rect.height() - 1;
180 }
181
182 if (x >= px && x < px + rect.width() &&
183 y >= py && y < py + rect.height()) {
184 rv.insert(p);
185 }
186 }
187
188 return rv;
189 }
190
191 QString
192 TextLayer::getFeatureDescription(QPoint &pos) const
193 {
194 int x = pos.x();
195
196 if (!m_model || !m_model->getSampleRate()) return "";
197
198 TextModel::PointList points = getLocalPoints(x, pos.y());
199
200 if (points.empty()) {
201 if (!m_model->isReady()) {
202 return tr("In progress");
203 } else {
204 return "";
205 }
206 }
207
208 long useFrame = points.begin()->frame;
209
210 RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
211
212 QString text;
213
214 if (points.begin()->label == "") {
215 text = QString(tr("Time:\t%1\nHeight:\t%2\nLabel:\t%3"))
216 .arg(rt.toText(true).c_str())
217 .arg(points.begin()->height)
218 .arg(points.begin()->label);
219 }
220
221 pos = QPoint(getXForFrame(useFrame), getYForHeight(points.begin()->height));
222 return text;
223 }
224
225
226 //!!! too much overlap with TimeValueLayer/TimeInstantLayer
227
228 bool
229 TextLayer::snapToFeatureFrame(int &frame,
230 size_t &resolution,
231 SnapType snap) const
232 {
233 if (!m_model) {
234 return Layer::snapToFeatureFrame(frame, resolution, snap);
235 }
236
237 resolution = m_model->getResolution();
238 TextModel::PointList points;
239
240 if (snap == SnapNeighbouring) {
241
242 points = getLocalPoints(getXForFrame(frame), -1);
243 if (points.empty()) return false;
244 frame = points.begin()->frame;
245 return true;
246 }
247
248 points = m_model->getPoints(frame, frame);
249 int snapped = frame;
250 bool found = false;
251
252 for (TextModel::PointList::const_iterator i = points.begin();
253 i != points.end(); ++i) {
254
255 if (snap == SnapRight) {
256
257 if (i->frame > frame) {
258 snapped = i->frame;
259 found = true;
260 break;
261 }
262
263 } else if (snap == SnapLeft) {
264
265 if (i->frame <= frame) {
266 snapped = i->frame;
267 found = true; // don't break, as the next may be better
268 } else {
269 break;
270 }
271
272 } else { // nearest
273
274 TextModel::PointList::const_iterator j = i;
275 ++j;
276
277 if (j == points.end()) {
278
279 snapped = i->frame;
280 found = true;
281 break;
282
283 } else if (j->frame >= frame) {
284
285 if (j->frame - frame < frame - i->frame) {
286 snapped = j->frame;
287 } else {
288 snapped = i->frame;
289 }
290 found = true;
291 break;
292 }
293 }
294 }
295
296 frame = snapped;
297 return found;
298 }
299
300 int
301 TextLayer::getYForHeight(float height) const
302 {
303 int h = m_view->height();
304 return h - int(height * h);
305 }
306
307 float
308 TextLayer::getHeightForY(int y) const
309 {
310 int h = m_view->height();
311 return float(h - y) / h;
312 }
313
314 void
315 TextLayer::paint(QPainter &paint, QRect rect) const
316 {
317 if (!m_model || !m_model->isOK()) return;
318
319 int sampleRate = m_model->getSampleRate();
320 if (!sampleRate) return;
321
322 // Profiler profiler("TextLayer::paint", true);
323
324 int x0 = rect.left(), x1 = rect.right();
325 long frame0 = getFrameForX(x0);
326 long frame1 = getFrameForX(x1);
327
328 TextModel::PointList points(m_model->getPoints(frame0, frame1));
329 if (points.empty()) return;
330
331 QColor brushColour(m_colour);
332 brushColour.setAlpha(80);
333 paint.setBrush(brushColour);
334
335 if (m_view->hasLightBackground()) {
336 paint.setPen(Qt::black);
337 } else {
338 paint.setPen(Qt::white);
339 }
340
341 // std::cerr << "TextLayer::paint: resolution is "
342 // << m_model->getResolution() << " frames" << std::endl;
343
344 QPoint localPos;
345 long illuminateFrame = -1;
346
347 if (m_view->shouldIlluminateLocalFeatures(this, localPos)) {
348 TextModel::PointList localPoints = getLocalPoints(localPos.x(),
349 localPos.y());
350 if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame;
351 }
352
353 int boxMaxWidth = 150;
354 int boxMaxHeight = 200;
355
356 paint.save();
357 paint.setClipRect(rect.x(), 0, rect.width() + boxMaxWidth, m_view->height());
358
359 for (TextModel::PointList::const_iterator i = points.begin();
360 i != points.end(); ++i) {
361
362 const TextModel::Point &p(*i);
363
364 int x = getXForFrame(p.frame);
365 int y = getYForHeight(p.height);
366
367 if (illuminateFrame == p.frame) {
368 /*
369 //!!! aside from the problem of choosing a colour, it'd be
370 //better to save the highlighted rects and draw them at
371 //the end perhaps
372
373 //!!! not equipped to illuminate the right section in line
374 //or curve mode
375
376 if (m_plotStyle != PlotCurve &&
377 m_plotStyle != PlotLines) {
378 paint.setPen(Qt::black);//!!!
379 if (m_plotStyle != PlotSegmentation) {
380 paint.setBrush(Qt::black);//!!!
381 }
382 }
383 */
384 }
385
386 QString label = p.label;
387 if (label == "") {
388 label = tr("<no text>");
389 }
390
391 QRect boxRect = paint.fontMetrics().boundingRect
392 (QRect(0, 0, boxMaxWidth, boxMaxHeight),
393 Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, label);
394
395 QRect textRect = QRect(3, 2, boxRect.width(), boxRect.height());
396 boxRect = QRect(0, 0, boxRect.width() + 6, boxRect.height() + 2);
397
398 if (y + boxRect.height() > m_view->height()) {
399 if (boxRect.height() > m_view->height()) y = 0;
400 else y = m_view->height() - boxRect.height() - 1;
401 }
402
403 boxRect = QRect(x, y, boxRect.width(), boxRect.height());
404 textRect = QRect(x + 3, y + 2, textRect.width(), textRect.height());
405
406 // boxRect = QRect(x, y, boxRect.width(), boxRect.height());
407 // textRect = QRect(x + 3, y + 2, textRect.width(), textRect.height());
408
409 paint.setRenderHint(QPainter::Antialiasing, false);
410 paint.drawRect(boxRect);
411
412 paint.setRenderHint(QPainter::Antialiasing, true);
413 paint.drawText(textRect,
414 Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap,
415 label);
416
417 /// if (p.label != "") {
418 /// paint.drawText(x + 5, y - paint.fontMetrics().height() + paint.fontMetrics().ascent(), p.label);
419 /// }
420 }
421
422 paint.restore();
423
424 // looks like save/restore doesn't deal with this:
425 paint.setRenderHint(QPainter::Antialiasing, false);
426 }
427
428 void
429 TextLayer::drawStart(QMouseEvent *e)
430 {
431 std::cerr << "TextLayer::drawStart(" << e->x() << "," << e->y() << ")" << std::endl;
432
433 if (!m_model) {
434 std::cerr << "TextLayer::drawStart: no model" << std::endl;
435 return;
436 }
437
438 long frame = getFrameForX(e->x());
439 if (frame < 0) frame = 0;
440 frame = frame / m_model->getResolution() * m_model->getResolution();
441
442 float height = getHeightForY(e->y());
443
444 m_editingPoint = TextModel::Point(frame, height, "");
445 m_originalPoint = m_editingPoint;
446
447 if (m_editingCommand) m_editingCommand->finish();
448 m_editingCommand = new TextModel::EditCommand(m_model, "Add Label");
449 m_editingCommand->addPoint(m_editingPoint);
450
451 m_editing = true;
452 }
453
454 void
455 TextLayer::drawDrag(QMouseEvent *e)
456 {
457 std::cerr << "TextLayer::drawDrag(" << e->x() << "," << e->y() << ")" << std::endl;
458
459 if (!m_model || !m_editing) return;
460
461 long frame = getFrameForX(e->x());
462 if (frame < 0) frame = 0;
463 frame = frame / m_model->getResolution() * m_model->getResolution();
464
465 float height = getHeightForY(e->y());
466
467 m_editingCommand->deletePoint(m_editingPoint);
468 m_editingPoint.frame = frame;
469 m_editingPoint.height = height;
470 m_editingCommand->addPoint(m_editingPoint);
471 }
472
473 void
474 TextLayer::drawEnd(QMouseEvent *e)
475 {
476 std::cerr << "TextLayer::drawEnd(" << e->x() << "," << e->y() << ")" << std::endl;
477 if (!m_model || !m_editing) return;
478 m_editingCommand->finish();
479 m_editingCommand = 0;
480 m_editing = false;
481 }
482
483 void
484 TextLayer::editStart(QMouseEvent *e)
485 {
486 std::cerr << "TextLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
487
488 if (!m_model) return;
489
490 TextModel::PointList points = getLocalPoints(e->x(), e->y());
491 if (points.empty()) return;
492
493 m_editingPoint = *points.begin();
494 m_originalPoint = m_editingPoint;
495
496 if (m_editingCommand) {
497 m_editingCommand->finish();
498 m_editingCommand = 0;
499 }
500
501 m_editing = true;
502 }
503
504 void
505 TextLayer::editDrag(QMouseEvent *e)
506 {
507 if (!m_model || !m_editing) return;
508
509 long frame = getFrameForX(e->x());
510 if (frame < 0) frame = 0;
511 frame = frame / m_model->getResolution() * m_model->getResolution();
512
513 float height = getHeightForY(e->y());
514
515 if (!m_editingCommand) {
516 m_editingCommand = new TextModel::EditCommand(m_model, tr("Drag Label"));
517 }
518
519 m_editingCommand->deletePoint(m_editingPoint);
520 m_editingPoint.frame = frame;
521 m_editingPoint.height = height;
522 m_editingCommand->addPoint(m_editingPoint);
523 }
524
525 void
526 TextLayer::editEnd(QMouseEvent *e)
527 {
528 std::cerr << "TextLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl;
529 if (!m_model || !m_editing) return;
530
531 if (m_editingCommand) {
532
533 QString newName = m_editingCommand->getName();
534
535 if (m_editingPoint.frame != m_originalPoint.frame) {
536 if (m_editingPoint.height != m_originalPoint.height) {
537 newName = tr("Move Label");
538 } else {
539 newName = tr("Relocate Label");
540 }
541 } else {
542 newName = tr("Change Point Height");
543 }
544
545 m_editingCommand->setName(newName);
546 m_editingCommand->finish();
547 }
548
549 m_editingCommand = 0;
550 m_editing = false;
551 }
552
553 QString
554 TextLayer::toXmlString(QString indent, QString extraAttributes) const
555 {
556 return Layer::toXmlString(indent, extraAttributes +
557 QString(" colour=\"%1\"")
558 .arg(encodeColour(m_colour)));
559 }
560
561 void
562 TextLayer::setProperties(const QXmlAttributes &attributes)
563 {
564 QString colourSpec = attributes.value("colour");
565 if (colourSpec != "") {
566 QColor colour(colourSpec);
567 if (colour.isValid()) {
568 setBaseColour(QColor(colourSpec));
569 }
570 }
571 }
572
573
574 #ifdef INCLUDE_MOCFILES
575 #include "TextLayer.moc.cpp"
576 #endif
577