annotate layer/TextLayer.cpp @ 1548:bd6af89982d7

Permit getScaleProvidingLayerForUnit to return a dormant layer if there is no visible alternative. This is necessary to avoid the scale disappearing in Tony when the spectrogram is toggled off.
author Chris Cannam
date Thu, 17 Oct 2019 14:44:22 +0100
parents e6362cf5ff1d
children e95cefd4aa81
rev   line source
Chris@58 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@35 2
Chris@35 3 /*
Chris@59 4 Sonic Visualiser
Chris@59 5 An audio file viewer and annotation editor.
Chris@59 6 Centre for Digital Music, Queen Mary, University of London.
Chris@59 7 This file copyright 2006 Chris Cannam.
Chris@35 8
Chris@59 9 This program is free software; you can redistribute it and/or
Chris@59 10 modify it under the terms of the GNU General Public License as
Chris@59 11 published by the Free Software Foundation; either version 2 of the
Chris@59 12 License, or (at your option) any later version. See the file
Chris@59 13 COPYING included with this distribution for more information.
Chris@35 14 */
Chris@35 15
Chris@35 16 #include "TextLayer.h"
Chris@35 17
Chris@128 18 #include "data/model/Model.h"
Chris@35 19 #include "base/RealTime.h"
Chris@35 20 #include "base/Profiler.h"
Chris@376 21 #include "ColourDatabase.h"
Chris@128 22 #include "view/View.h"
Chris@35 23
Chris@128 24 #include "data/model/TextModel.h"
Chris@35 25
Chris@35 26 #include <QPainter>
Chris@35 27 #include <QMouseEvent>
Chris@36 28 #include <QInputDialog>
Chris@316 29 #include <QTextStream>
Chris@360 30 #include <QMessageBox>
Chris@35 31
Chris@35 32 #include <iostream>
Chris@35 33 #include <cmath>
Chris@35 34
Chris@44 35 TextLayer::TextLayer() :
Chris@287 36 SingleColourLayer(),
Chris@35 37 m_editing(false),
Chris@35 38 m_originalPoint(0, 0.0, tr("Empty Label")),
Chris@35 39 m_editingPoint(0, 0.0, tr("Empty Label")),
Chris@1408 40 m_editingCommand(nullptr)
Chris@35 41 {
Chris@44 42
Chris@35 43 }
Chris@35 44
Chris@1470 45 int
Chris@1470 46 TextLayer::getCompletion(LayerGeometryProvider *) const
Chris@1470 47 {
Chris@1470 48 auto model = ModelById::get(m_model);
Chris@1470 49 if (model) return model->getCompletion();
Chris@1470 50 else return 0;
Chris@1470 51 }
Chris@1470 52
Chris@35 53 void
Chris@1471 54 TextLayer::setModel(ModelId modelId)
Chris@35 55 {
Chris@1471 56 auto newModel = ModelById::getAs<TextModel>(modelId);
Chris@1471 57
Chris@1471 58 if (!modelId.isNone() && !newModel) {
Chris@1471 59 throw std::logic_error("Not a TextModel");
Chris@1471 60 }
Chris@1471 61
Chris@1471 62 if (m_model == modelId) return;
Chris@1471 63 m_model = modelId;
Chris@35 64
Chris@1471 65 if (newModel) {
Chris@1471 66 connectSignals(m_model);
Chris@1471 67 }
Chris@35 68
Chris@35 69 emit modelReplaced();
Chris@35 70 }
Chris@35 71
Chris@35 72 Layer::PropertyList
Chris@35 73 TextLayer::getProperties() const
Chris@35 74 {
Chris@287 75 PropertyList list = SingleColourLayer::getProperties();
Chris@35 76 return list;
Chris@35 77 }
Chris@35 78
Chris@87 79 QString
Chris@87 80 TextLayer::getPropertyLabel(const PropertyName &name) const
Chris@87 81 {
Chris@287 82 return SingleColourLayer::getPropertyLabel(name);
Chris@87 83 }
Chris@87 84
Chris@35 85 Layer::PropertyType
Chris@287 86 TextLayer::getPropertyType(const PropertyName &name) const
Chris@35 87 {
Chris@287 88 return SingleColourLayer::getPropertyType(name);
Chris@35 89 }
Chris@35 90
Chris@35 91 int
Chris@35 92 TextLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@1266 93 int *min, int *max, int *deflt) const
Chris@35 94 {
Chris@287 95 return SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@35 96 }
Chris@35 97
Chris@35 98 QString
Chris@35 99 TextLayer::getPropertyValueLabel(const PropertyName &name,
Chris@1266 100 int value) const
Chris@35 101 {
Chris@287 102 return SingleColourLayer::getPropertyValueLabel(name, value);
Chris@35 103 }
Chris@35 104
Chris@35 105 void
Chris@35 106 TextLayer::setProperty(const PropertyName &name, int value)
Chris@35 107 {
Chris@287 108 SingleColourLayer::setProperty(name, value);
Chris@35 109 }
Chris@35 110
Chris@79 111 bool
Chris@908 112 TextLayer::getValueExtents(double &, double &, bool &, QString &) const
Chris@79 113 {
Chris@79 114 return false;
Chris@79 115 }
Chris@79 116
Chris@35 117 bool
Chris@918 118 TextLayer::isLayerScrollable(const LayerGeometryProvider *v) const
Chris@35 119 {
Chris@35 120 QPoint discard;
Chris@44 121 return !v->shouldIlluminateLocalFeatures(this, discard);
Chris@35 122 }
Chris@35 123
Chris@1437 124 EventVector
Chris@918 125 TextLayer::getLocalPoints(LayerGeometryProvider *v, int x, int y) const
Chris@35 126 {
Chris@1474 127 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 128 if (!model) return {};
Chris@35 129
Chris@1437 130 int overlap = ViewManager::scalePixelSize(150);
Chris@35 131
Chris@1437 132 sv_frame_t frame0 = v->getFrameForX(-overlap);
Chris@1437 133 sv_frame_t frame1 = v->getFrameForX(v->getPaintWidth() + overlap);
Chris@1437 134
Chris@1474 135 EventVector points(model->getEventsSpanning(frame0, frame1 - frame0));
Chris@35 136
Chris@1437 137 EventVector rv;
Chris@552 138 QFontMetrics metrics = QFontMetrics(QFont());
Chris@35 139
Chris@1437 140 for (EventVector::iterator i = points.begin(); i != points.end(); ++i) {
Chris@35 141
Chris@1437 142 Event p(*i);
Chris@35 143
Chris@1437 144 int px = v->getXForFrame(p.getFrame());
Chris@1437 145 int py = getYForHeight(v, p.getValue());
Chris@35 146
Chris@1437 147 QString label = p.getLabel();
Chris@1266 148 if (label == "") {
Chris@1266 149 label = tr("<no text>");
Chris@1266 150 }
Chris@35 151
Chris@1266 152 QRect rect = metrics.boundingRect
Chris@1266 153 (QRect(0, 0, 150, 200),
Chris@1266 154 Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, label);
Chris@35 155
Chris@1266 156 if (py + rect.height() > v->getPaintHeight()) {
Chris@1266 157 if (rect.height() > v->getPaintHeight()) py = 0;
Chris@1266 158 else py = v->getPaintHeight() - rect.height() - 1;
Chris@1266 159 }
Chris@35 160
Chris@1266 161 if (x >= px && x < px + rect.width() &&
Chris@1266 162 y >= py && y < py + rect.height()) {
Chris@1437 163 rv.push_back(p);
Chris@1266 164 }
Chris@35 165 }
Chris@35 166
Chris@35 167 return rv;
Chris@35 168 }
Chris@35 169
Chris@552 170 bool
Chris@1437 171 TextLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &p) const
Chris@552 172 {
Chris@1474 173 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 174 if (!model) return false;
Chris@552 175
Chris@1437 176 sv_frame_t a = v->getFrameForX(x - ViewManager::scalePixelSize(120));
Chris@1437 177 sv_frame_t b = v->getFrameForX(x + ViewManager::scalePixelSize(10));
Chris@1474 178 EventVector onPoints = model->getEventsWithin(a, b);
Chris@552 179 if (onPoints.empty()) return false;
Chris@552 180
Chris@908 181 double nearestDistance = -1;
Chris@552 182
Chris@1437 183 for (EventVector::const_iterator i = onPoints.begin();
Chris@552 184 i != onPoints.end(); ++i) {
Chris@552 185
Chris@1437 186 double yd = getYForHeight(v, i->getValue()) - y;
Chris@1437 187 double xd = v->getXForFrame(i->getFrame()) - x;
Chris@908 188 double distance = sqrt(yd*yd + xd*xd);
Chris@552 189
Chris@552 190 if (nearestDistance == -1 || distance < nearestDistance) {
Chris@552 191 nearestDistance = distance;
Chris@552 192 p = *i;
Chris@552 193 }
Chris@552 194 }
Chris@552 195
Chris@552 196 return true;
Chris@552 197 }
Chris@552 198
Chris@35 199 QString
Chris@918 200 TextLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
Chris@35 201 {
Chris@35 202 int x = pos.x();
Chris@35 203
Chris@1474 204 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 205 if (!model || !model->getSampleRate()) return "";
Chris@35 206
Chris@1437 207 EventVector points = getLocalPoints(v, x, pos.y());
Chris@35 208
Chris@35 209 if (points.empty()) {
Chris@1474 210 if (!model->isReady()) {
Chris@1266 211 return tr("In progress");
Chris@1266 212 } else {
Chris@1266 213 return "";
Chris@1266 214 }
Chris@35 215 }
Chris@35 216
Chris@1437 217 sv_frame_t useFrame = points.begin()->getFrame();
Chris@35 218
Chris@1474 219 RealTime rt = RealTime::frame2RealTime(useFrame, model->getSampleRate());
Chris@35 220
Chris@35 221 QString text;
Chris@35 222
Chris@1437 223 if (points.begin()->getLabel() == "") {
Chris@1266 224 text = QString(tr("Time:\t%1\nHeight:\t%2\nLabel:\t%3"))
Chris@1266 225 .arg(rt.toText(true).c_str())
Chris@1437 226 .arg(points.begin()->getValue())
Chris@1437 227 .arg(points.begin()->getLabel());
Chris@35 228 }
Chris@35 229
Chris@44 230 pos = QPoint(v->getXForFrame(useFrame),
Chris@1437 231 getYForHeight(v, points.begin()->getValue()));
Chris@35 232 return text;
Chris@35 233 }
Chris@35 234
Chris@35 235
Chris@35 236 //!!! too much overlap with TimeValueLayer/TimeInstantLayer
Chris@35 237
Chris@35 238 bool
Chris@918 239 TextLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
Chris@1266 240 int &resolution,
Chris@1547 241 SnapType snap, int ycoord) const
Chris@35 242 {
Chris@1474 243 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 244 if (!model) {
Chris@1547 245 return Layer::snapToFeatureFrame(v, frame, resolution, snap, ycoord);
Chris@35 246 }
Chris@35 247
Chris@1437 248 // SnapLeft / SnapRight: return frame of nearest feature in that
Chris@1437 249 // direction no matter how far away
Chris@1437 250 //
Chris@1437 251 // SnapNeighbouring: return frame of feature that would be used in
Chris@1437 252 // an editing operation, i.e. closest feature in either direction
Chris@1437 253 // but only if it is "close enough"
Chris@1437 254
Chris@1474 255 resolution = model->getResolution();
Chris@35 256
Chris@35 257 if (snap == SnapNeighbouring) {
Chris@1437 258 EventVector points = getLocalPoints(v, v->getXForFrame(frame), -1);
Chris@1266 259 if (points.empty()) return false;
Chris@1437 260 frame = points.begin()->getFrame();
Chris@1266 261 return true;
Chris@35 262 }
Chris@35 263
Chris@1437 264 Event e;
Chris@1474 265 if (model->getNearestEventMatching
Chris@1437 266 (frame,
Chris@1437 267 [](Event) { return true; },
Chris@1437 268 snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
Chris@1437 269 e)) {
Chris@1437 270 frame = e.getFrame();
Chris@1437 271 return true;
Chris@35 272 }
Chris@35 273
Chris@1437 274 return false;
Chris@35 275 }
Chris@35 276
Chris@35 277 int
Chris@918 278 TextLayer::getYForHeight(LayerGeometryProvider *v, double height) const
Chris@35 279 {
Chris@918 280 int h = v->getPaintHeight();
Chris@35 281 return h - int(height * h);
Chris@35 282 }
Chris@35 283
Chris@908 284 double
Chris@918 285 TextLayer::getHeightForY(LayerGeometryProvider *v, int y) const
Chris@35 286 {
Chris@918 287 int h = v->getPaintHeight();
Chris@908 288 return double(h - y) / h;
Chris@35 289 }
Chris@35 290
Chris@35 291 void
Chris@916 292 TextLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@35 293 {
Chris@1474 294 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 295 if (!model || !model->isOK()) return;
Chris@35 296
Chris@1474 297 sv_samplerate_t sampleRate = model->getSampleRate();
Chris@35 298 if (!sampleRate) return;
Chris@35 299
Chris@35 300 // Profiler profiler("TextLayer::paint", true);
Chris@35 301
Chris@35 302 int x0 = rect.left(), x1 = rect.right();
Chris@1437 303 int overlap = ViewManager::scalePixelSize(150);
Chris@1437 304 sv_frame_t frame0 = v->getFrameForX(x0 - overlap);
Chris@1437 305 sv_frame_t frame1 = v->getFrameForX(x1 + overlap);
Chris@35 306
Chris@1474 307 EventVector points(model->getEventsWithin(frame0, frame1 - frame0, 2));
Chris@35 308 if (points.empty()) return;
Chris@35 309
Chris@287 310 QColor brushColour(getBaseQColor());
Chris@35 311
Chris@44 312 int h, s, val;
Chris@44 313 brushColour.getHsv(&h, &s, &val);
Chris@36 314 brushColour.setHsv(h, s, 255, 100);
Chris@36 315
Chris@36 316 QColor penColour;
Chris@287 317 penColour = v->getForeground();
Chris@35 318
Chris@587 319 // SVDEBUG << "TextLayer::paint: resolution is "
Chris@1474 320 // << model->getResolution() << " frames" << endl;
Chris@35 321
Chris@35 322 QPoint localPos;
Chris@1437 323 Event illuminatePoint(0);
Chris@850 324 bool shouldIlluminate = false;
Chris@35 325
Chris@44 326 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
Chris@552 327 shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(),
Chris@552 328 illuminatePoint);
Chris@35 329 }
Chris@35 330
Chris@35 331 int boxMaxWidth = 150;
Chris@35 332 int boxMaxHeight = 200;
Chris@35 333
Chris@35 334 paint.save();
Chris@918 335 paint.setClipRect(rect.x(), 0, rect.width() + boxMaxWidth, v->getPaintHeight());
Chris@35 336
Chris@1437 337 for (EventVector::const_iterator i = points.begin();
Chris@1266 338 i != points.end(); ++i) {
Chris@35 339
Chris@1437 340 Event p(*i);
Chris@35 341
Chris@1437 342 int x = v->getXForFrame(p.getFrame());
Chris@1437 343 int y = getYForHeight(v, p.getValue());
Chris@35 344
Chris@1437 345 if (!shouldIlluminate || illuminatePoint != p) {
Chris@1266 346 paint.setPen(penColour);
Chris@1266 347 paint.setBrush(brushColour);
Chris@552 348 } else {
Chris@1266 349 paint.setBrush(penColour);
Chris@287 350 paint.setPen(v->getBackground());
Chris@1266 351 }
Chris@35 352
Chris@1437 353 QString label = p.getLabel();
Chris@1266 354 if (label == "") {
Chris@1266 355 label = tr("<no text>");
Chris@1266 356 }
Chris@35 357
Chris@1266 358 QRect boxRect = paint.fontMetrics().boundingRect
Chris@1266 359 (QRect(0, 0, boxMaxWidth, boxMaxHeight),
Chris@1266 360 Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, label);
Chris@35 361
Chris@1266 362 QRect textRect = QRect(3, 2, boxRect.width(), boxRect.height());
Chris@1266 363 boxRect = QRect(0, 0, boxRect.width() + 6, boxRect.height() + 2);
Chris@35 364
Chris@1266 365 if (y + boxRect.height() > v->getPaintHeight()) {
Chris@1266 366 if (boxRect.height() > v->getPaintHeight()) y = 0;
Chris@1266 367 else y = v->getPaintHeight() - boxRect.height() - 1;
Chris@1266 368 }
Chris@35 369
Chris@1266 370 boxRect = QRect(x, y, boxRect.width(), boxRect.height());
Chris@1266 371 textRect = QRect(x + 3, y + 2, textRect.width(), textRect.height());
Chris@35 372
Chris@1266 373 // boxRect = QRect(x, y, boxRect.width(), boxRect.height());
Chris@1266 374 // textRect = QRect(x + 3, y + 2, textRect.width(), textRect.height());
Chris@35 375
Chris@1266 376 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@1266 377 paint.drawRect(boxRect);
Chris@35 378
Chris@1266 379 paint.setRenderHint(QPainter::Antialiasing, true);
Chris@1266 380 paint.drawText(textRect,
Chris@1266 381 Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap,
Chris@1266 382 label);
Chris@35 383
Chris@1437 384 /// if (p.getLabel() != "") {
Chris@1437 385 /// paint.drawText(x + 5, y - paint.fontMetrics().height() + paint.fontMetrics().ascent(), p.getLabel());
Chris@1266 386 /// }
Chris@35 387 }
Chris@35 388
Chris@35 389 paint.restore();
Chris@35 390
Chris@35 391 // looks like save/restore doesn't deal with this:
Chris@35 392 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@35 393 }
Chris@35 394
Chris@35 395 void
Chris@918 396 TextLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@35 397 {
Chris@587 398 // SVDEBUG << "TextLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@35 399
Chris@1474 400 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 401 if (!model) {
Chris@1266 402 SVDEBUG << "TextLayer::drawStart: no model" << endl;
Chris@1266 403 return;
Chris@35 404 }
Chris@35 405
Chris@908 406 sv_frame_t frame = v->getFrameForX(e->x());
Chris@35 407 if (frame < 0) frame = 0;
Chris@1474 408 frame = frame / model->getResolution() * model->getResolution();
Chris@35 409
Chris@908 410 double height = getHeightForY(v, e->y());
Chris@35 411
Chris@1437 412 m_editingPoint = Event(frame, float(height), "");
Chris@35 413 m_originalPoint = m_editingPoint;
Chris@35 414
Chris@376 415 if (m_editingCommand) finish(m_editingCommand);
Chris@1474 416 m_editingCommand = new ChangeEventsCommand(m_model.untyped, "Add Label");
Chris@1437 417 m_editingCommand->add(m_editingPoint);
Chris@35 418
Chris@35 419 m_editing = true;
Chris@35 420 }
Chris@35 421
Chris@35 422 void
Chris@918 423 TextLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@35 424 {
Chris@587 425 // SVDEBUG << "TextLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
Chris@35 426
Chris@1474 427 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 428 if (!model || !m_editing) return;
Chris@35 429
Chris@908 430 sv_frame_t frame = v->getFrameForX(e->x());
Chris@35 431 if (frame < 0) frame = 0;
Chris@1474 432 frame = frame / model->getResolution() * model->getResolution();
Chris@35 433
Chris@908 434 double height = getHeightForY(v, e->y());
Chris@35 435
Chris@1437 436 m_editingCommand->remove(m_editingPoint);
Chris@1437 437 m_editingPoint = m_editingPoint
Chris@1437 438 .withFrame(frame)
Chris@1437 439 .withValue(float(height));
Chris@1437 440 m_editingCommand->add(m_editingPoint);
Chris@35 441 }
Chris@35 442
Chris@35 443 void
Chris@918 444 TextLayer::drawEnd(LayerGeometryProvider *v, QMouseEvent *)
Chris@35 445 {
Chris@587 446 // SVDEBUG << "TextLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
Chris@1474 447 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 448 if (!model || !m_editing) return;
Chris@36 449
Chris@36 450 bool ok = false;
Chris@918 451 QString label = QInputDialog::getText(v->getView(), tr("Enter label"),
Chris@1266 452 tr("Please enter a new label:"),
Chris@1266 453 QLineEdit::Normal, "", &ok);
Chris@36 454
Chris@1437 455 m_editingCommand->remove(m_editingPoint);
Chris@1437 456
Chris@36 457 if (ok) {
Chris@1437 458 m_editingPoint = m_editingPoint
Chris@1437 459 .withLabel(label);
Chris@1437 460 m_editingCommand->add(m_editingPoint);
Chris@36 461 }
Chris@36 462
Chris@376 463 finish(m_editingCommand);
Chris@1408 464 m_editingCommand = nullptr;
Chris@35 465 m_editing = false;
Chris@35 466 }
Chris@35 467
Chris@35 468 void
Chris@918 469 TextLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 470 {
Chris@1474 471 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 472 if (!model) return;
Chris@335 473
Chris@552 474 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
Chris@335 475
Chris@335 476 if (m_editingCommand) {
Chris@1266 477 finish(m_editingCommand);
Chris@1408 478 m_editingCommand = nullptr;
Chris@335 479 }
Chris@335 480
Chris@335 481 m_editing = true;
Chris@335 482 }
Chris@335 483
Chris@335 484 void
Chris@918 485 TextLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
Chris@335 486 {
Chris@335 487 }
Chris@335 488
Chris@335 489 void
Chris@918 490 TextLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 491 {
Chris@1474 492 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 493 if (!model || !m_editing) return;
Chris@335 494
Chris@335 495 m_editing = false;
Chris@335 496
Chris@1437 497 Event p;
Chris@552 498 if (!getPointToDrag(v, e->x(), e->y(), p)) return;
Chris@1437 499 if (p.getFrame() != m_editingPoint.getFrame() ||
Chris@1437 500 p.getValue() != m_editingPoint.getValue()) return;
Chris@335 501
Chris@1474 502 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Erase Point"));
Chris@1437 503 m_editingCommand->remove(m_editingPoint);
Chris@376 504 finish(m_editingCommand);
Chris@1408 505 m_editingCommand = nullptr;
Chris@335 506 m_editing = false;
Chris@335 507 }
Chris@335 508
Chris@335 509 void
Chris@918 510 TextLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@35 511 {
Chris@587 512 // SVDEBUG << "TextLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@35 513
Chris@1474 514 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 515 if (!model) return;
Chris@35 516
Chris@552 517 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) {
Chris@552 518 return;
Chris@552 519 }
Chris@35 520
Chris@36 521 m_editOrigin = e->pos();
Chris@35 522 m_originalPoint = m_editingPoint;
Chris@35 523
Chris@35 524 if (m_editingCommand) {
Chris@1266 525 finish(m_editingCommand);
Chris@1408 526 m_editingCommand = nullptr;
Chris@35 527 }
Chris@35 528
Chris@35 529 m_editing = true;
Chris@35 530 }
Chris@35 531
Chris@35 532 void
Chris@918 533 TextLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@35 534 {
Chris@1474 535 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 536 if (!model || !m_editing) return;
Chris@35 537
Chris@1437 538 sv_frame_t frameDiff =
Chris@1437 539 v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x());
Chris@1437 540 double heightDiff =
Chris@1437 541 getHeightForY(v, e->y()) - getHeightForY(v, m_editOrigin.y());
Chris@36 542
Chris@1437 543 sv_frame_t frame = m_originalPoint.getFrame() + frameDiff;
Chris@1437 544 double height = m_originalPoint.getValue() + heightDiff;
Chris@36 545
Chris@35 546 if (frame < 0) frame = 0;
Chris@1474 547 frame = (frame / model->getResolution()) * model->getResolution();
Chris@35 548
Chris@35 549 if (!m_editingCommand) {
Chris@1474 550 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Drag Label"));
Chris@35 551 }
Chris@35 552
Chris@1437 553 m_editingCommand->remove(m_editingPoint);
Chris@1437 554 m_editingPoint = m_editingPoint
Chris@1437 555 .withFrame(frame)
Chris@1437 556 .withValue(float(height));
Chris@1437 557 m_editingCommand->add(m_editingPoint);
Chris@35 558 }
Chris@35 559
Chris@35 560 void
Chris@918 561 TextLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
Chris@35 562 {
Chris@587 563 // SVDEBUG << "TextLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
Chris@1474 564 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 565 if (!model || !m_editing) return;
Chris@35 566
Chris@35 567 if (m_editingCommand) {
Chris@35 568
Chris@1266 569 QString newName = m_editingCommand->getName();
Chris@35 570
Chris@1437 571 if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) {
Chris@1437 572 if (m_editingPoint.getValue() != m_originalPoint.getValue()) {
Chris@1266 573 newName = tr("Move Label");
Chris@1266 574 } else {
Chris@1266 575 newName = tr("Move Label Horizontally");
Chris@1266 576 }
Chris@1266 577 } else {
Chris@1266 578 newName = tr("Move Label Vertically");
Chris@1266 579 }
Chris@35 580
Chris@1266 581 m_editingCommand->setName(newName);
Chris@1266 582 finish(m_editingCommand);
Chris@35 583 }
Chris@35 584
Chris@1408 585 m_editingCommand = nullptr;
Chris@35 586 m_editing = false;
Chris@35 587 }
Chris@35 588
Chris@255 589 bool
Chris@918 590 TextLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
Chris@36 591 {
Chris@1474 592 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 593 if (!model) return false;
Chris@36 594
Chris@1437 595 Event text;
Chris@552 596 if (!getPointToDrag(v, e->x(), e->y(), text)) return false;
Chris@36 597
Chris@1437 598 QString label = text.getLabel();
Chris@36 599
Chris@36 600 bool ok = false;
Chris@918 601 label = QInputDialog::getText(v->getView(), tr("Enter label"),
Chris@1266 602 tr("Please enter a new label:"),
Chris@1266 603 QLineEdit::Normal, label, &ok);
Chris@1437 604 if (ok && label != text.getLabel()) {
Chris@1437 605 ChangeEventsCommand *command =
Chris@1474 606 new ChangeEventsCommand(m_model.untyped, tr("Re-Label Point"));
Chris@1437 607 command->remove(text);
Chris@1437 608 command->add(text.withLabel(label));
Chris@1437 609 finish(command);
Chris@36 610 }
Chris@255 611
Chris@255 612 return true;
Chris@36 613 }
Chris@36 614
Chris@43 615 void
Chris@908 616 TextLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
Chris@43 617 {
Chris@1474 618 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 619 if (!model) return;
Chris@99 620
Chris@1437 621 ChangeEventsCommand *command =
Chris@1474 622 new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
Chris@43 623
Chris@1437 624 EventVector points =
Chris@1474 625 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@43 626
Chris@1437 627 for (Event p: points) {
Chris@1437 628 command->remove(p);
Chris@1437 629 Event moved = p.withFrame(p.getFrame() +
Chris@1437 630 newStartFrame - s.getStartFrame());
Chris@1437 631 command->add(moved);
Chris@43 632 }
Chris@43 633
Chris@376 634 finish(command);
Chris@43 635 }
Chris@43 636
Chris@43 637 void
Chris@43 638 TextLayer::resizeSelection(Selection s, Selection newSize)
Chris@43 639 {
Chris@1474 640 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 641 if (!model) return;
Chris@99 642
Chris@1437 643 ChangeEventsCommand *command =
Chris@1474 644 new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
Chris@43 645
Chris@1437 646 EventVector points =
Chris@1474 647 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@43 648
Chris@1437 649 double ratio = double(newSize.getDuration()) / double(s.getDuration());
Chris@1437 650 double oldStart = double(s.getStartFrame());
Chris@1437 651 double newStart = double(newSize.getStartFrame());
Chris@1437 652
Chris@1437 653 for (Event p: points) {
Chris@43 654
Chris@1437 655 double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
Chris@43 656
Chris@1437 657 Event newPoint = p
Chris@1437 658 .withFrame(lrint(newFrame));
Chris@1437 659 command->remove(p);
Chris@1437 660 command->add(newPoint);
Chris@43 661 }
Chris@43 662
Chris@376 663 finish(command);
Chris@43 664 }
Chris@43 665
Chris@76 666 void
Chris@76 667 TextLayer::deleteSelection(Selection s)
Chris@76 668 {
Chris@1474 669 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 670 if (!model) return;
Chris@99 671
Chris@1437 672 ChangeEventsCommand *command =
Chris@1474 673 new ChangeEventsCommand(m_model.untyped, tr("Delete Selection"));
Chris@76 674
Chris@1437 675 EventVector points =
Chris@1474 676 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@76 677
Chris@1437 678 for (Event p: points) {
Chris@1437 679 command->remove(p);
Chris@76 680 }
Chris@76 681
Chris@376 682 finish(command);
Chris@76 683 }
Chris@76 684
Chris@76 685 void
Chris@918 686 TextLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
Chris@76 687 {
Chris@1474 688 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 689 if (!model) return;
Chris@99 690
Chris@1437 691 EventVector points =
Chris@1474 692 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@76 693
Chris@1437 694 for (Event p: points) {
Chris@1437 695 to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
Chris@76 696 }
Chris@76 697 }
Chris@76 698
Chris@125 699 bool
Chris@1533 700 TextLayer::paste(LayerGeometryProvider *v, const Clipboard &from,
Chris@1533 701 sv_frame_t /* frameOffset */, bool /* interactive */)
Chris@76 702 {
Chris@1474 703 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 704 if (!model) return false;
Chris@99 705
Chris@1423 706 const EventVector &points = from.getPoints();
Chris@76 707
Chris@360 708 bool realign = false;
Chris@360 709
Chris@360 710 if (clipboardHasDifferentAlignment(v, from)) {
Chris@360 711
Chris@360 712 QMessageBox::StandardButton button =
Chris@918 713 QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
Chris@360 714 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?"),
Chris@360 715 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@360 716 QMessageBox::Yes);
Chris@360 717
Chris@360 718 if (button == QMessageBox::Cancel) {
Chris@360 719 return false;
Chris@360 720 }
Chris@360 721
Chris@360 722 if (button == QMessageBox::Yes) {
Chris@360 723 realign = true;
Chris@360 724 }
Chris@360 725 }
Chris@360 726
Chris@1437 727 ChangeEventsCommand *command =
Chris@1474 728 new ChangeEventsCommand(m_model.untyped, tr("Paste"));
Chris@76 729
Chris@908 730 double valueMin = 0.0, valueMax = 1.0;
Chris@1423 731 for (EventVector::const_iterator i = points.begin();
Chris@125 732 i != points.end(); ++i) {
Chris@1423 733 if (i->hasValue()) {
Chris@125 734 if (i->getValue() < valueMin) valueMin = i->getValue();
Chris@125 735 if (i->getValue() > valueMax) valueMax = i->getValue();
Chris@125 736 }
Chris@125 737 }
Chris@125 738 if (valueMax < valueMin + 1.0) valueMax = valueMin + 1.0;
Chris@125 739
Chris@1423 740 for (EventVector::const_iterator i = points.begin();
Chris@76 741 i != points.end(); ++i) {
Chris@76 742
Chris@908 743 sv_frame_t frame = 0;
Chris@360 744
Chris@360 745 if (!realign) {
Chris@360 746
Chris@360 747 frame = i->getFrame();
Chris@360 748
Chris@360 749 } else {
Chris@360 750
Chris@1423 751 if (i->hasReferenceFrame()) {
Chris@360 752 frame = i->getReferenceFrame();
Chris@360 753 frame = alignFromReference(v, frame);
Chris@360 754 } else {
Chris@360 755 frame = i->getFrame();
Chris@360 756 }
Chris@76 757 }
Chris@360 758
Chris@1533 759 Event p = i->withFrame(frame);
Chris@1533 760
Chris@1437 761 Event newPoint = p;
Chris@1437 762 if (p.hasValue()) {
Chris@1437 763 newPoint = newPoint.withValue(float((i->getValue() - valueMin) /
Chris@1437 764 (valueMax - valueMin)));
Chris@125 765 } else {
Chris@1437 766 newPoint = newPoint.withValue(0.5f);
Chris@125 767 }
Chris@125 768
Chris@1437 769 if (!p.hasLabel()) {
Chris@1437 770 if (p.hasValue()) {
Chris@1437 771 newPoint = newPoint.withLabel(QString("%1").arg(p.getValue()));
Chris@1437 772 } else {
Chris@1437 773 newPoint = newPoint.withLabel(tr("New Point"));
Chris@1437 774 }
Chris@125 775 }
Chris@76 776
Chris@1437 777 command->add(newPoint);
Chris@76 778 }
Chris@76 779
Chris@376 780 finish(command);
Chris@125 781 return true;
Chris@76 782 }
Chris@76 783
Chris@287 784 int
Chris@287 785 TextLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 786 {
Chris@287 787 impose = false;
Chris@287 788 return ColourDatabase::getInstance()->getColourIndex
Chris@287 789 (QString(darkbg ? "Bright Orange" : "Orange"));
Chris@287 790 }
Chris@287 791
Chris@316 792 void
Chris@316 793 TextLayer::toXml(QTextStream &stream,
Chris@316 794 QString indent, QString extraAttributes) const
Chris@35 795 {
Chris@316 796 SingleColourLayer::toXml(stream, indent, extraAttributes);
Chris@35 797 }
Chris@35 798
Chris@35 799 void
Chris@35 800 TextLayer::setProperties(const QXmlAttributes &attributes)
Chris@35 801 {
Chris@287 802 SingleColourLayer::setProperties(attributes);
Chris@35 803 }
Chris@35 804