00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016 #include "TextLayer.h"
00017
00018 #include "data/model/Model.h"
00019 #include "base/RealTime.h"
00020 #include "base/Profiler.h"
00021 #include "base/ColourDatabase.h"
00022 #include "view/View.h"
00023
00024 #include "data/model/TextModel.h"
00025
00026 #include <QPainter>
00027 #include <QMouseEvent>
00028 #include <QInputDialog>
00029 #include <QTextStream>
00030 #include <QMessageBox>
00031
00032 #include <iostream>
00033 #include <cmath>
00034
00035 TextLayer::TextLayer() :
00036 SingleColourLayer(),
00037 m_model(0),
00038 m_editing(false),
00039 m_originalPoint(0, 0.0, tr("Empty Label")),
00040 m_editingPoint(0, 0.0, tr("Empty Label")),
00041 m_editingCommand(0)
00042 {
00043
00044 }
00045
00046 void
00047 TextLayer::setModel(TextModel *model)
00048 {
00049 if (m_model == model) return;
00050 m_model = model;
00051
00052 connectSignals(m_model);
00053
00054
00055
00056 emit modelReplaced();
00057 }
00058
00059 Layer::PropertyList
00060 TextLayer::getProperties() const
00061 {
00062 PropertyList list = SingleColourLayer::getProperties();
00063 return list;
00064 }
00065
00066 QString
00067 TextLayer::getPropertyLabel(const PropertyName &name) const
00068 {
00069 return SingleColourLayer::getPropertyLabel(name);
00070 }
00071
00072 Layer::PropertyType
00073 TextLayer::getPropertyType(const PropertyName &name) const
00074 {
00075 return SingleColourLayer::getPropertyType(name);
00076 }
00077
00078 int
00079 TextLayer::getPropertyRangeAndValue(const PropertyName &name,
00080 int *min, int *max, int *deflt) const
00081 {
00082 return SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
00083 }
00084
00085 QString
00086 TextLayer::getPropertyValueLabel(const PropertyName &name,
00087 int value) const
00088 {
00089 return SingleColourLayer::getPropertyValueLabel(name, value);
00090 }
00091
00092 void
00093 TextLayer::setProperty(const PropertyName &name, int value)
00094 {
00095 SingleColourLayer::setProperty(name, value);
00096 }
00097
00098 bool
00099 TextLayer::getValueExtents(float &, float &, bool &, QString &) const
00100 {
00101 return false;
00102 }
00103
00104 bool
00105 TextLayer::isLayerScrollable(const View *v) const
00106 {
00107 QPoint discard;
00108 return !v->shouldIlluminateLocalFeatures(this, discard);
00109 }
00110
00111
00112 TextModel::PointList
00113 TextLayer::getLocalPoints(View *v, int x, int y) const
00114 {
00115 if (!m_model) return TextModel::PointList();
00116
00117 long frame0 = v->getFrameForX(-150);
00118 long frame1 = v->getFrameForX(v->width() + 150);
00119
00120 TextModel::PointList points(m_model->getPoints(frame0, frame1));
00121
00122 TextModel::PointList rv;
00123 QFontMetrics metrics = QPainter().fontMetrics();
00124
00125 for (TextModel::PointList::iterator i = points.begin();
00126 i != points.end(); ++i) {
00127
00128 const TextModel::Point &p(*i);
00129
00130 int px = v->getXForFrame(p.frame);
00131 int py = getYForHeight(v, p.height);
00132
00133 QString label = p.label;
00134 if (label == "") {
00135 label = tr("<no text>");
00136 }
00137
00138 QRect rect = metrics.boundingRect
00139 (QRect(0, 0, 150, 200),
00140 Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, label);
00141
00142 if (py + rect.height() > v->height()) {
00143 if (rect.height() > v->height()) py = 0;
00144 else py = v->height() - rect.height() - 1;
00145 }
00146
00147 if (x >= px && x < px + rect.width() &&
00148 y >= py && y < py + rect.height()) {
00149 rv.insert(p);
00150 }
00151 }
00152
00153 return rv;
00154 }
00155
00156 QString
00157 TextLayer::getFeatureDescription(View *v, QPoint &pos) const
00158 {
00159 int x = pos.x();
00160
00161 if (!m_model || !m_model->getSampleRate()) return "";
00162
00163 TextModel::PointList points = getLocalPoints(v, x, pos.y());
00164
00165 if (points.empty()) {
00166 if (!m_model->isReady()) {
00167 return tr("In progress");
00168 } else {
00169 return "";
00170 }
00171 }
00172
00173 long useFrame = points.begin()->frame;
00174
00175 RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
00176
00177 QString text;
00178
00179 if (points.begin()->label == "") {
00180 text = QString(tr("Time:\t%1\nHeight:\t%2\nLabel:\t%3"))
00181 .arg(rt.toText(true).c_str())
00182 .arg(points.begin()->height)
00183 .arg(points.begin()->label);
00184 }
00185
00186 pos = QPoint(v->getXForFrame(useFrame),
00187 getYForHeight(v, points.begin()->height));
00188 return text;
00189 }
00190
00191
00193
00194 bool
00195 TextLayer::snapToFeatureFrame(View *v, int &frame,
00196 size_t &resolution,
00197 SnapType snap) const
00198 {
00199 if (!m_model) {
00200 return Layer::snapToFeatureFrame(v, frame, resolution, snap);
00201 }
00202
00203 resolution = m_model->getResolution();
00204 TextModel::PointList points;
00205
00206 if (snap == SnapNeighbouring) {
00207
00208 points = getLocalPoints(v, v->getXForFrame(frame), -1);
00209 if (points.empty()) return false;
00210 frame = points.begin()->frame;
00211 return true;
00212 }
00213
00214 points = m_model->getPoints(frame, frame);
00215 int snapped = frame;
00216 bool found = false;
00217
00218 for (TextModel::PointList::const_iterator i = points.begin();
00219 i != points.end(); ++i) {
00220
00221 if (snap == SnapRight) {
00222
00223 if (i->frame > frame) {
00224 snapped = i->frame;
00225 found = true;
00226 break;
00227 }
00228
00229 } else if (snap == SnapLeft) {
00230
00231 if (i->frame <= frame) {
00232 snapped = i->frame;
00233 found = true;
00234 } else {
00235 break;
00236 }
00237
00238 } else {
00239
00240 TextModel::PointList::const_iterator j = i;
00241 ++j;
00242
00243 if (j == points.end()) {
00244
00245 snapped = i->frame;
00246 found = true;
00247 break;
00248
00249 } else if (j->frame >= frame) {
00250
00251 if (j->frame - frame < frame - i->frame) {
00252 snapped = j->frame;
00253 } else {
00254 snapped = i->frame;
00255 }
00256 found = true;
00257 break;
00258 }
00259 }
00260 }
00261
00262 frame = snapped;
00263 return found;
00264 }
00265
00266 int
00267 TextLayer::getYForHeight(View *v, float height) const
00268 {
00269 int h = v->height();
00270 return h - int(height * h);
00271 }
00272
00273 float
00274 TextLayer::getHeightForY(View *v, int y) const
00275 {
00276 int h = v->height();
00277 return float(h - y) / h;
00278 }
00279
00280 void
00281 TextLayer::paint(View *v, QPainter &paint, QRect rect) const
00282 {
00283 if (!m_model || !m_model->isOK()) return;
00284
00285 int sampleRate = m_model->getSampleRate();
00286 if (!sampleRate) return;
00287
00288
00289
00290 int x0 = rect.left(), x1 = rect.right();
00291 long frame0 = v->getFrameForX(x0);
00292 long frame1 = v->getFrameForX(x1);
00293
00294 TextModel::PointList points(m_model->getPoints(frame0, frame1));
00295 if (points.empty()) return;
00296
00297 QColor brushColour(getBaseQColor());
00298
00299 int h, s, val;
00300 brushColour.getHsv(&h, &s, &val);
00301 brushColour.setHsv(h, s, 255, 100);
00302
00303 QColor penColour;
00304 penColour = v->getForeground();
00305
00306
00307
00308
00309 QPoint localPos;
00310 long illuminateFrame = -1;
00311
00312 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
00313 TextModel::PointList localPoints = getLocalPoints(v, localPos.x(),
00314 localPos.y());
00315 if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame;
00316 }
00317
00318 int boxMaxWidth = 150;
00319 int boxMaxHeight = 200;
00320
00321 paint.save();
00322 paint.setClipRect(rect.x(), 0, rect.width() + boxMaxWidth, v->height());
00323
00324 for (TextModel::PointList::const_iterator i = points.begin();
00325 i != points.end(); ++i) {
00326
00327 const TextModel::Point &p(*i);
00328
00329 int x = v->getXForFrame(p.frame);
00330 int y = getYForHeight(v, p.height);
00331
00332 if (illuminateFrame == p.frame) {
00333 paint.setBrush(penColour);
00334 paint.setPen(v->getBackground());
00335 } else {
00336 paint.setPen(penColour);
00337 paint.setBrush(brushColour);
00338 }
00339
00340 QString label = p.label;
00341 if (label == "") {
00342 label = tr("<no text>");
00343 }
00344
00345 QRect boxRect = paint.fontMetrics().boundingRect
00346 (QRect(0, 0, boxMaxWidth, boxMaxHeight),
00347 Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, label);
00348
00349 QRect textRect = QRect(3, 2, boxRect.width(), boxRect.height());
00350 boxRect = QRect(0, 0, boxRect.width() + 6, boxRect.height() + 2);
00351
00352 if (y + boxRect.height() > v->height()) {
00353 if (boxRect.height() > v->height()) y = 0;
00354 else y = v->height() - boxRect.height() - 1;
00355 }
00356
00357 boxRect = QRect(x, y, boxRect.width(), boxRect.height());
00358 textRect = QRect(x + 3, y + 2, textRect.width(), textRect.height());
00359
00360
00361
00362
00363 paint.setRenderHint(QPainter::Antialiasing, false);
00364 paint.drawRect(boxRect);
00365
00366 paint.setRenderHint(QPainter::Antialiasing, true);
00367 paint.drawText(textRect,
00368 Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap,
00369 label);
00370
00374 }
00375
00376 paint.restore();
00377
00378
00379 paint.setRenderHint(QPainter::Antialiasing, false);
00380 }
00381
00382 void
00383 TextLayer::drawStart(View *v, QMouseEvent *e)
00384 {
00385
00386
00387 if (!m_model) {
00388 std::cerr << "TextLayer::drawStart: no model" << std::endl;
00389 return;
00390 }
00391
00392 long frame = v->getFrameForX(e->x());
00393 if (frame < 0) frame = 0;
00394 frame = frame / m_model->getResolution() * m_model->getResolution();
00395
00396 float height = getHeightForY(v, e->y());
00397
00398 m_editingPoint = TextModel::Point(frame, height, "");
00399 m_originalPoint = m_editingPoint;
00400
00401 if (m_editingCommand) m_editingCommand->finish();
00402 m_editingCommand = new TextModel::EditCommand(m_model, "Add Label");
00403 m_editingCommand->addPoint(m_editingPoint);
00404
00405 m_editing = true;
00406 }
00407
00408 void
00409 TextLayer::drawDrag(View *v, QMouseEvent *e)
00410 {
00411
00412
00413 if (!m_model || !m_editing) return;
00414
00415 long frame = v->getFrameForX(e->x());
00416 if (frame < 0) frame = 0;
00417 frame = frame / m_model->getResolution() * m_model->getResolution();
00418
00419 float height = getHeightForY(v, e->y());
00420
00421 m_editingCommand->deletePoint(m_editingPoint);
00422 m_editingPoint.frame = frame;
00423 m_editingPoint.height = height;
00424 m_editingCommand->addPoint(m_editingPoint);
00425 }
00426
00427 void
00428 TextLayer::drawEnd(View *v, QMouseEvent *)
00429 {
00430
00431 if (!m_model || !m_editing) return;
00432
00433 bool ok = false;
00434 QString label = QInputDialog::getText(v, tr("Enter label"),
00435 tr("Please enter a new label:"),
00436 QLineEdit::Normal, "", &ok);
00437
00438 if (ok) {
00439 TextModel::RelabelCommand *command =
00440 new TextModel::RelabelCommand(m_model, m_editingPoint, label);
00441 m_editingCommand->addCommand(command);
00442 } else {
00443 m_editingCommand->deletePoint(m_editingPoint);
00444 }
00445
00446 m_editingCommand->finish();
00447 m_editingCommand = 0;
00448 m_editing = false;
00449 }
00450
00451 void
00452 TextLayer::eraseStart(View *v, QMouseEvent *e)
00453 {
00454 if (!m_model) return;
00455
00456 TextModel::PointList points = getLocalPoints(v, e->x(), e->y());
00457 if (points.empty()) return;
00458
00459 m_editingPoint = *points.begin();
00460
00461 if (m_editingCommand) {
00462 m_editingCommand->finish();
00463 m_editingCommand = 0;
00464 }
00465
00466 m_editing = true;
00467 }
00468
00469 void
00470 TextLayer::eraseDrag(View *v, QMouseEvent *e)
00471 {
00472 }
00473
00474 void
00475 TextLayer::eraseEnd(View *v, QMouseEvent *e)
00476 {
00477 if (!m_model || !m_editing) return;
00478
00479 m_editing = false;
00480
00481 TextModel::PointList points = getLocalPoints(v, e->x(), e->y());
00482 if (points.empty()) return;
00483 if (points.begin()->frame != m_editingPoint.frame ||
00484 points.begin()->height != m_editingPoint.height) return;
00485
00486 m_editingCommand = new TextModel::EditCommand
00487 (m_model, tr("Erase Point"));
00488
00489 m_editingCommand->deletePoint(m_editingPoint);
00490
00491 m_editingCommand->finish();
00492 m_editingCommand = 0;
00493 m_editing = false;
00494 }
00495
00496 void
00497 TextLayer::editStart(View *v, QMouseEvent *e)
00498 {
00499
00500
00501 if (!m_model) return;
00502
00503 TextModel::PointList points = getLocalPoints(v, e->x(), e->y());
00504 if (points.empty()) return;
00505
00506 m_editOrigin = e->pos();
00507 m_editingPoint = *points.begin();
00508 m_originalPoint = m_editingPoint;
00509
00510 if (m_editingCommand) {
00511 m_editingCommand->finish();
00512 m_editingCommand = 0;
00513 }
00514
00515 m_editing = true;
00516 }
00517
00518 void
00519 TextLayer::editDrag(View *v, QMouseEvent *e)
00520 {
00521 if (!m_model || !m_editing) return;
00522
00523 long frameDiff = v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x());
00524 float heightDiff = getHeightForY(v, e->y()) - getHeightForY(v, m_editOrigin.y());
00525
00526 long frame = m_originalPoint.frame + frameDiff;
00527 float height = m_originalPoint.height + heightDiff;
00528
00529
00530 if (frame < 0) frame = 0;
00531 frame = (frame / m_model->getResolution()) * m_model->getResolution();
00532
00533
00534
00535 if (!m_editingCommand) {
00536 m_editingCommand = new TextModel::EditCommand(m_model, tr("Drag Label"));
00537 }
00538
00539 m_editingCommand->deletePoint(m_editingPoint);
00540 m_editingPoint.frame = frame;
00541 m_editingPoint.height = height;
00542 m_editingCommand->addPoint(m_editingPoint);
00543 }
00544
00545 void
00546 TextLayer::editEnd(View *, QMouseEvent *)
00547 {
00548
00549 if (!m_model || !m_editing) return;
00550
00551 if (m_editingCommand) {
00552
00553 QString newName = m_editingCommand->getName();
00554
00555 if (m_editingPoint.frame != m_originalPoint.frame) {
00556 if (m_editingPoint.height != m_originalPoint.height) {
00557 newName = tr("Move Label");
00558 } else {
00559 newName = tr("Move Label Horizontally");
00560 }
00561 } else {
00562 newName = tr("Move Label Vertically");
00563 }
00564
00565 m_editingCommand->setName(newName);
00566 m_editingCommand->finish();
00567 }
00568
00569 m_editingCommand = 0;
00570 m_editing = false;
00571 }
00572
00573 bool
00574 TextLayer::editOpen(View *v, QMouseEvent *e)
00575 {
00576 if (!m_model) return false;
00577
00578 TextModel::PointList points = getLocalPoints(v, e->x(), e->y());
00579 if (points.empty()) return false;
00580
00581 QString label = points.begin()->label;
00582
00583 bool ok = false;
00584 label = QInputDialog::getText(v, tr("Enter label"),
00585 tr("Please enter a new label:"),
00586 QLineEdit::Normal, label, &ok);
00587 if (ok && label != points.begin()->label) {
00588 TextModel::RelabelCommand *command =
00589 new TextModel::RelabelCommand(m_model, *points.begin(), label);
00590 CommandHistory::getInstance()->addCommand(command);
00591 }
00592
00593 return true;
00594 }
00595
00596 void
00597 TextLayer::moveSelection(Selection s, size_t newStartFrame)
00598 {
00599 if (!m_model) return;
00600
00601 TextModel::EditCommand *command =
00602 new TextModel::EditCommand(m_model, tr("Drag Selection"));
00603
00604 TextModel::PointList points =
00605 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
00606
00607 for (TextModel::PointList::iterator i = points.begin();
00608 i != points.end(); ++i) {
00609
00610 if (s.contains(i->frame)) {
00611 TextModel::Point newPoint(*i);
00612 newPoint.frame = i->frame + newStartFrame - s.getStartFrame();
00613 command->deletePoint(*i);
00614 command->addPoint(newPoint);
00615 }
00616 }
00617
00618 command->finish();
00619 }
00620
00621 void
00622 TextLayer::resizeSelection(Selection s, Selection newSize)
00623 {
00624 if (!m_model) return;
00625
00626 TextModel::EditCommand *command =
00627 new TextModel::EditCommand(m_model, tr("Resize Selection"));
00628
00629 TextModel::PointList points =
00630 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
00631
00632 double ratio =
00633 double(newSize.getEndFrame() - newSize.getStartFrame()) /
00634 double(s.getEndFrame() - s.getStartFrame());
00635
00636 for (TextModel::PointList::iterator i = points.begin();
00637 i != points.end(); ++i) {
00638
00639 if (s.contains(i->frame)) {
00640
00641 double target = i->frame;
00642 target = newSize.getStartFrame() +
00643 double(target - s.getStartFrame()) * ratio;
00644
00645 TextModel::Point newPoint(*i);
00646 newPoint.frame = lrint(target);
00647 command->deletePoint(*i);
00648 command->addPoint(newPoint);
00649 }
00650 }
00651
00652 command->finish();
00653 }
00654
00655 void
00656 TextLayer::deleteSelection(Selection s)
00657 {
00658 if (!m_model) return;
00659
00660 TextModel::EditCommand *command =
00661 new TextModel::EditCommand(m_model, tr("Delete Selection"));
00662
00663 TextModel::PointList points =
00664 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
00665
00666 for (TextModel::PointList::iterator i = points.begin();
00667 i != points.end(); ++i) {
00668 if (s.contains(i->frame)) command->deletePoint(*i);
00669 }
00670
00671 command->finish();
00672 }
00673
00674 void
00675 TextLayer::copy(View *v, Selection s, Clipboard &to)
00676 {
00677 if (!m_model) return;
00678
00679 TextModel::PointList points =
00680 m_model->getPoints(s.getStartFrame(), s.getEndFrame());
00681
00682 for (TextModel::PointList::iterator i = points.begin();
00683 i != points.end(); ++i) {
00684 if (s.contains(i->frame)) {
00685 Clipboard::Point point(i->frame, i->height, i->label);
00686 point.setReferenceFrame(alignToReference(v, i->frame));
00687 to.addPoint(point);
00688 }
00689 }
00690 }
00691
00692 bool
00693 TextLayer::paste(View *v, const Clipboard &from, int frameOffset, bool )
00694 {
00695 if (!m_model) return false;
00696
00697 const Clipboard::PointList &points = from.getPoints();
00698
00699 bool realign = false;
00700
00701 if (clipboardHasDifferentAlignment(v, from)) {
00702
00703 QMessageBox::StandardButton button =
00704 QMessageBox::question(v, tr("Re-align pasted items?"),
00705 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?"),
00706 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
00707 QMessageBox::Yes);
00708
00709 if (button == QMessageBox::Cancel) {
00710 return false;
00711 }
00712
00713 if (button == QMessageBox::Yes) {
00714 realign = true;
00715 }
00716 }
00717
00718 TextModel::EditCommand *command =
00719 new TextModel::EditCommand(m_model, tr("Paste"));
00720
00721 float valueMin = 0.0, valueMax = 1.0;
00722 for (Clipboard::PointList::const_iterator i = points.begin();
00723 i != points.end(); ++i) {
00724 if (i->haveValue()) {
00725 if (i->getValue() < valueMin) valueMin = i->getValue();
00726 if (i->getValue() > valueMax) valueMax = i->getValue();
00727 }
00728 }
00729 if (valueMax < valueMin + 1.0) valueMax = valueMin + 1.0;
00730
00731 for (Clipboard::PointList::const_iterator i = points.begin();
00732 i != points.end(); ++i) {
00733
00734 if (!i->haveFrame()) continue;
00735 size_t frame = 0;
00736
00737 if (!realign) {
00738
00739 frame = i->getFrame();
00740
00741 } else {
00742
00743 if (i->haveReferenceFrame()) {
00744 frame = i->getReferenceFrame();
00745 frame = alignFromReference(v, frame);
00746 } else {
00747 frame = i->getFrame();
00748 }
00749 }
00750
00751 TextModel::Point newPoint(frame);
00752
00753 if (i->haveValue()) {
00754 newPoint.height = (i->getValue() - valueMin) / (valueMax - valueMin);
00755 } else {
00756 newPoint.height = 0.5;
00757 }
00758
00759 if (i->haveLabel()) {
00760 newPoint.label = i->getLabel();
00761 } else if (i->haveValue()) {
00762 newPoint.label = QString("%1").arg(i->getValue());
00763 } else {
00764 newPoint.label = tr("New Point");
00765 }
00766
00767 command->addPoint(newPoint);
00768 }
00769
00770 command->finish();
00771 return true;
00772 }
00773
00774 int
00775 TextLayer::getDefaultColourHint(bool darkbg, bool &impose)
00776 {
00777 impose = false;
00778 return ColourDatabase::getInstance()->getColourIndex
00779 (QString(darkbg ? "Bright Orange" : "Orange"));
00780 }
00781
00782 void
00783 TextLayer::toXml(QTextStream &stream,
00784 QString indent, QString extraAttributes) const
00785 {
00786 SingleColourLayer::toXml(stream, indent, extraAttributes);
00787 }
00788
00789 void
00790 TextLayer::setProperties(const QXmlAttributes &attributes)
00791 {
00792 SingleColourLayer::setProperties(attributes);
00793 }
00794