annotate layer/TextLayer.cpp @ 1605:ae2d5f8ff005

When asked to render the whole view width, we need to wait for the layers to be ready before we can determine what the width is
author Chris Cannam
date Thu, 30 Apr 2020 14:47:13 +0100
parents e95cefd4aa81
children
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@1550 302 int x0 = rect.left();
Chris@1550 303 int x1 = x0 + rect.width();
Chris@1550 304
Chris@1437 305 int overlap = ViewManager::scalePixelSize(150);
Chris@1437 306 sv_frame_t frame0 = v->getFrameForX(x0 - overlap);
Chris@1437 307 sv_frame_t frame1 = v->getFrameForX(x1 + overlap);
Chris@35 308
Chris@1474 309 EventVector points(model->getEventsWithin(frame0, frame1 - frame0, 2));
Chris@35 310 if (points.empty()) return;
Chris@35 311
Chris@287 312 QColor brushColour(getBaseQColor());
Chris@35 313
Chris@44 314 int h, s, val;
Chris@44 315 brushColour.getHsv(&h, &s, &val);
Chris@36 316 brushColour.setHsv(h, s, 255, 100);
Chris@36 317
Chris@36 318 QColor penColour;
Chris@287 319 penColour = v->getForeground();
Chris@35 320
Chris@587 321 // SVDEBUG << "TextLayer::paint: resolution is "
Chris@1474 322 // << model->getResolution() << " frames" << endl;
Chris@35 323
Chris@35 324 QPoint localPos;
Chris@1437 325 Event illuminatePoint(0);
Chris@850 326 bool shouldIlluminate = false;
Chris@35 327
Chris@44 328 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
Chris@552 329 shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(),
Chris@552 330 illuminatePoint);
Chris@35 331 }
Chris@35 332
Chris@35 333 int boxMaxWidth = 150;
Chris@35 334 int boxMaxHeight = 200;
Chris@35 335
Chris@35 336 paint.save();
Chris@918 337 paint.setClipRect(rect.x(), 0, rect.width() + boxMaxWidth, v->getPaintHeight());
Chris@35 338
Chris@1437 339 for (EventVector::const_iterator i = points.begin();
Chris@1266 340 i != points.end(); ++i) {
Chris@35 341
Chris@1437 342 Event p(*i);
Chris@35 343
Chris@1437 344 int x = v->getXForFrame(p.getFrame());
Chris@1437 345 int y = getYForHeight(v, p.getValue());
Chris@35 346
Chris@1437 347 if (!shouldIlluminate || illuminatePoint != p) {
Chris@1266 348 paint.setPen(penColour);
Chris@1266 349 paint.setBrush(brushColour);
Chris@552 350 } else {
Chris@1266 351 paint.setBrush(penColour);
Chris@287 352 paint.setPen(v->getBackground());
Chris@1266 353 }
Chris@35 354
Chris@1437 355 QString label = p.getLabel();
Chris@1266 356 if (label == "") {
Chris@1266 357 label = tr("<no text>");
Chris@1266 358 }
Chris@35 359
Chris@1266 360 QRect boxRect = paint.fontMetrics().boundingRect
Chris@1266 361 (QRect(0, 0, boxMaxWidth, boxMaxHeight),
Chris@1266 362 Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, label);
Chris@35 363
Chris@1266 364 QRect textRect = QRect(3, 2, boxRect.width(), boxRect.height());
Chris@1266 365 boxRect = QRect(0, 0, boxRect.width() + 6, boxRect.height() + 2);
Chris@35 366
Chris@1266 367 if (y + boxRect.height() > v->getPaintHeight()) {
Chris@1266 368 if (boxRect.height() > v->getPaintHeight()) y = 0;
Chris@1266 369 else y = v->getPaintHeight() - boxRect.height() - 1;
Chris@1266 370 }
Chris@35 371
Chris@1266 372 boxRect = QRect(x, y, boxRect.width(), boxRect.height());
Chris@1266 373 textRect = QRect(x + 3, y + 2, textRect.width(), textRect.height());
Chris@35 374
Chris@1266 375 // boxRect = QRect(x, y, boxRect.width(), boxRect.height());
Chris@1266 376 // textRect = QRect(x + 3, y + 2, textRect.width(), textRect.height());
Chris@35 377
Chris@1266 378 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@1266 379 paint.drawRect(boxRect);
Chris@35 380
Chris@1266 381 paint.setRenderHint(QPainter::Antialiasing, true);
Chris@1266 382 paint.drawText(textRect,
Chris@1266 383 Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap,
Chris@1266 384 label);
Chris@35 385
Chris@1437 386 /// if (p.getLabel() != "") {
Chris@1437 387 /// paint.drawText(x + 5, y - paint.fontMetrics().height() + paint.fontMetrics().ascent(), p.getLabel());
Chris@1266 388 /// }
Chris@35 389 }
Chris@35 390
Chris@35 391 paint.restore();
Chris@35 392
Chris@35 393 // looks like save/restore doesn't deal with this:
Chris@35 394 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@35 395 }
Chris@35 396
Chris@35 397 void
Chris@918 398 TextLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@35 399 {
Chris@587 400 // SVDEBUG << "TextLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@35 401
Chris@1474 402 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 403 if (!model) {
Chris@1266 404 SVDEBUG << "TextLayer::drawStart: no model" << endl;
Chris@1266 405 return;
Chris@35 406 }
Chris@35 407
Chris@908 408 sv_frame_t frame = v->getFrameForX(e->x());
Chris@35 409 if (frame < 0) frame = 0;
Chris@1474 410 frame = frame / model->getResolution() * model->getResolution();
Chris@35 411
Chris@908 412 double height = getHeightForY(v, e->y());
Chris@35 413
Chris@1437 414 m_editingPoint = Event(frame, float(height), "");
Chris@35 415 m_originalPoint = m_editingPoint;
Chris@35 416
Chris@376 417 if (m_editingCommand) finish(m_editingCommand);
Chris@1474 418 m_editingCommand = new ChangeEventsCommand(m_model.untyped, "Add Label");
Chris@1437 419 m_editingCommand->add(m_editingPoint);
Chris@35 420
Chris@35 421 m_editing = true;
Chris@35 422 }
Chris@35 423
Chris@35 424 void
Chris@918 425 TextLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@35 426 {
Chris@587 427 // SVDEBUG << "TextLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
Chris@35 428
Chris@1474 429 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 430 if (!model || !m_editing) return;
Chris@35 431
Chris@908 432 sv_frame_t frame = v->getFrameForX(e->x());
Chris@35 433 if (frame < 0) frame = 0;
Chris@1474 434 frame = frame / model->getResolution() * model->getResolution();
Chris@35 435
Chris@908 436 double height = getHeightForY(v, e->y());
Chris@35 437
Chris@1437 438 m_editingCommand->remove(m_editingPoint);
Chris@1437 439 m_editingPoint = m_editingPoint
Chris@1437 440 .withFrame(frame)
Chris@1437 441 .withValue(float(height));
Chris@1437 442 m_editingCommand->add(m_editingPoint);
Chris@35 443 }
Chris@35 444
Chris@35 445 void
Chris@918 446 TextLayer::drawEnd(LayerGeometryProvider *v, QMouseEvent *)
Chris@35 447 {
Chris@587 448 // SVDEBUG << "TextLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
Chris@1474 449 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 450 if (!model || !m_editing) return;
Chris@36 451
Chris@36 452 bool ok = false;
Chris@918 453 QString label = QInputDialog::getText(v->getView(), tr("Enter label"),
Chris@1266 454 tr("Please enter a new label:"),
Chris@1266 455 QLineEdit::Normal, "", &ok);
Chris@36 456
Chris@1437 457 m_editingCommand->remove(m_editingPoint);
Chris@1437 458
Chris@36 459 if (ok) {
Chris@1437 460 m_editingPoint = m_editingPoint
Chris@1437 461 .withLabel(label);
Chris@1437 462 m_editingCommand->add(m_editingPoint);
Chris@36 463 }
Chris@36 464
Chris@376 465 finish(m_editingCommand);
Chris@1408 466 m_editingCommand = nullptr;
Chris@35 467 m_editing = false;
Chris@35 468 }
Chris@35 469
Chris@35 470 void
Chris@918 471 TextLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 472 {
Chris@1474 473 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 474 if (!model) return;
Chris@335 475
Chris@552 476 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
Chris@335 477
Chris@335 478 if (m_editingCommand) {
Chris@1266 479 finish(m_editingCommand);
Chris@1408 480 m_editingCommand = nullptr;
Chris@335 481 }
Chris@335 482
Chris@335 483 m_editing = true;
Chris@335 484 }
Chris@335 485
Chris@335 486 void
Chris@918 487 TextLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
Chris@335 488 {
Chris@335 489 }
Chris@335 490
Chris@335 491 void
Chris@918 492 TextLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 493 {
Chris@1474 494 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 495 if (!model || !m_editing) return;
Chris@335 496
Chris@335 497 m_editing = false;
Chris@335 498
Chris@1437 499 Event p;
Chris@552 500 if (!getPointToDrag(v, e->x(), e->y(), p)) return;
Chris@1437 501 if (p.getFrame() != m_editingPoint.getFrame() ||
Chris@1437 502 p.getValue() != m_editingPoint.getValue()) return;
Chris@335 503
Chris@1474 504 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Erase Point"));
Chris@1437 505 m_editingCommand->remove(m_editingPoint);
Chris@376 506 finish(m_editingCommand);
Chris@1408 507 m_editingCommand = nullptr;
Chris@335 508 m_editing = false;
Chris@335 509 }
Chris@335 510
Chris@335 511 void
Chris@918 512 TextLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@35 513 {
Chris@587 514 // SVDEBUG << "TextLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@35 515
Chris@1474 516 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 517 if (!model) return;
Chris@35 518
Chris@552 519 if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) {
Chris@552 520 return;
Chris@552 521 }
Chris@35 522
Chris@36 523 m_editOrigin = e->pos();
Chris@35 524 m_originalPoint = m_editingPoint;
Chris@35 525
Chris@35 526 if (m_editingCommand) {
Chris@1266 527 finish(m_editingCommand);
Chris@1408 528 m_editingCommand = nullptr;
Chris@35 529 }
Chris@35 530
Chris@35 531 m_editing = true;
Chris@35 532 }
Chris@35 533
Chris@35 534 void
Chris@918 535 TextLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@35 536 {
Chris@1474 537 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 538 if (!model || !m_editing) return;
Chris@35 539
Chris@1437 540 sv_frame_t frameDiff =
Chris@1437 541 v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x());
Chris@1437 542 double heightDiff =
Chris@1437 543 getHeightForY(v, e->y()) - getHeightForY(v, m_editOrigin.y());
Chris@36 544
Chris@1437 545 sv_frame_t frame = m_originalPoint.getFrame() + frameDiff;
Chris@1437 546 double height = m_originalPoint.getValue() + heightDiff;
Chris@36 547
Chris@35 548 if (frame < 0) frame = 0;
Chris@1474 549 frame = (frame / model->getResolution()) * model->getResolution();
Chris@35 550
Chris@35 551 if (!m_editingCommand) {
Chris@1474 552 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Drag Label"));
Chris@35 553 }
Chris@35 554
Chris@1437 555 m_editingCommand->remove(m_editingPoint);
Chris@1437 556 m_editingPoint = m_editingPoint
Chris@1437 557 .withFrame(frame)
Chris@1437 558 .withValue(float(height));
Chris@1437 559 m_editingCommand->add(m_editingPoint);
Chris@35 560 }
Chris@35 561
Chris@35 562 void
Chris@918 563 TextLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
Chris@35 564 {
Chris@587 565 // SVDEBUG << "TextLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
Chris@1474 566 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 567 if (!model || !m_editing) return;
Chris@35 568
Chris@35 569 if (m_editingCommand) {
Chris@35 570
Chris@1266 571 QString newName = m_editingCommand->getName();
Chris@35 572
Chris@1437 573 if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) {
Chris@1437 574 if (m_editingPoint.getValue() != m_originalPoint.getValue()) {
Chris@1266 575 newName = tr("Move Label");
Chris@1266 576 } else {
Chris@1266 577 newName = tr("Move Label Horizontally");
Chris@1266 578 }
Chris@1266 579 } else {
Chris@1266 580 newName = tr("Move Label Vertically");
Chris@1266 581 }
Chris@35 582
Chris@1266 583 m_editingCommand->setName(newName);
Chris@1266 584 finish(m_editingCommand);
Chris@35 585 }
Chris@35 586
Chris@1408 587 m_editingCommand = nullptr;
Chris@35 588 m_editing = false;
Chris@35 589 }
Chris@35 590
Chris@255 591 bool
Chris@918 592 TextLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
Chris@36 593 {
Chris@1474 594 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 595 if (!model) return false;
Chris@36 596
Chris@1437 597 Event text;
Chris@552 598 if (!getPointToDrag(v, e->x(), e->y(), text)) return false;
Chris@36 599
Chris@1437 600 QString label = text.getLabel();
Chris@36 601
Chris@36 602 bool ok = false;
Chris@918 603 label = QInputDialog::getText(v->getView(), tr("Enter label"),
Chris@1266 604 tr("Please enter a new label:"),
Chris@1266 605 QLineEdit::Normal, label, &ok);
Chris@1437 606 if (ok && label != text.getLabel()) {
Chris@1437 607 ChangeEventsCommand *command =
Chris@1474 608 new ChangeEventsCommand(m_model.untyped, tr("Re-Label Point"));
Chris@1437 609 command->remove(text);
Chris@1437 610 command->add(text.withLabel(label));
Chris@1437 611 finish(command);
Chris@36 612 }
Chris@255 613
Chris@255 614 return true;
Chris@36 615 }
Chris@36 616
Chris@43 617 void
Chris@908 618 TextLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
Chris@43 619 {
Chris@1474 620 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 621 if (!model) return;
Chris@99 622
Chris@1437 623 ChangeEventsCommand *command =
Chris@1474 624 new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
Chris@43 625
Chris@1437 626 EventVector points =
Chris@1474 627 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@43 628
Chris@1437 629 for (Event p: points) {
Chris@1437 630 command->remove(p);
Chris@1437 631 Event moved = p.withFrame(p.getFrame() +
Chris@1437 632 newStartFrame - s.getStartFrame());
Chris@1437 633 command->add(moved);
Chris@43 634 }
Chris@43 635
Chris@376 636 finish(command);
Chris@43 637 }
Chris@43 638
Chris@43 639 void
Chris@43 640 TextLayer::resizeSelection(Selection s, Selection newSize)
Chris@43 641 {
Chris@1474 642 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 643 if (!model) return;
Chris@99 644
Chris@1437 645 ChangeEventsCommand *command =
Chris@1474 646 new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
Chris@43 647
Chris@1437 648 EventVector points =
Chris@1474 649 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@43 650
Chris@1437 651 double ratio = double(newSize.getDuration()) / double(s.getDuration());
Chris@1437 652 double oldStart = double(s.getStartFrame());
Chris@1437 653 double newStart = double(newSize.getStartFrame());
Chris@1437 654
Chris@1437 655 for (Event p: points) {
Chris@43 656
Chris@1437 657 double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
Chris@43 658
Chris@1437 659 Event newPoint = p
Chris@1437 660 .withFrame(lrint(newFrame));
Chris@1437 661 command->remove(p);
Chris@1437 662 command->add(newPoint);
Chris@43 663 }
Chris@43 664
Chris@376 665 finish(command);
Chris@43 666 }
Chris@43 667
Chris@76 668 void
Chris@76 669 TextLayer::deleteSelection(Selection s)
Chris@76 670 {
Chris@1474 671 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 672 if (!model) return;
Chris@99 673
Chris@1437 674 ChangeEventsCommand *command =
Chris@1474 675 new ChangeEventsCommand(m_model.untyped, tr("Delete Selection"));
Chris@76 676
Chris@1437 677 EventVector points =
Chris@1474 678 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@76 679
Chris@1437 680 for (Event p: points) {
Chris@1437 681 command->remove(p);
Chris@76 682 }
Chris@76 683
Chris@376 684 finish(command);
Chris@76 685 }
Chris@76 686
Chris@76 687 void
Chris@918 688 TextLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
Chris@76 689 {
Chris@1474 690 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 691 if (!model) return;
Chris@99 692
Chris@1437 693 EventVector points =
Chris@1474 694 model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
Chris@76 695
Chris@1437 696 for (Event p: points) {
Chris@1437 697 to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
Chris@76 698 }
Chris@76 699 }
Chris@76 700
Chris@125 701 bool
Chris@1533 702 TextLayer::paste(LayerGeometryProvider *v, const Clipboard &from,
Chris@1533 703 sv_frame_t /* frameOffset */, bool /* interactive */)
Chris@76 704 {
Chris@1474 705 auto model = ModelById::getAs<TextModel>(m_model);
Chris@1474 706 if (!model) return false;
Chris@99 707
Chris@1423 708 const EventVector &points = from.getPoints();
Chris@76 709
Chris@360 710 bool realign = false;
Chris@360 711
Chris@360 712 if (clipboardHasDifferentAlignment(v, from)) {
Chris@360 713
Chris@360 714 QMessageBox::StandardButton button =
Chris@918 715 QMessageBox::question(v->getView(), tr("Re-align pasted items?"),
Chris@360 716 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 717 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@360 718 QMessageBox::Yes);
Chris@360 719
Chris@360 720 if (button == QMessageBox::Cancel) {
Chris@360 721 return false;
Chris@360 722 }
Chris@360 723
Chris@360 724 if (button == QMessageBox::Yes) {
Chris@360 725 realign = true;
Chris@360 726 }
Chris@360 727 }
Chris@360 728
Chris@1437 729 ChangeEventsCommand *command =
Chris@1474 730 new ChangeEventsCommand(m_model.untyped, tr("Paste"));
Chris@76 731
Chris@908 732 double valueMin = 0.0, valueMax = 1.0;
Chris@1423 733 for (EventVector::const_iterator i = points.begin();
Chris@125 734 i != points.end(); ++i) {
Chris@1423 735 if (i->hasValue()) {
Chris@125 736 if (i->getValue() < valueMin) valueMin = i->getValue();
Chris@125 737 if (i->getValue() > valueMax) valueMax = i->getValue();
Chris@125 738 }
Chris@125 739 }
Chris@125 740 if (valueMax < valueMin + 1.0) valueMax = valueMin + 1.0;
Chris@125 741
Chris@1423 742 for (EventVector::const_iterator i = points.begin();
Chris@76 743 i != points.end(); ++i) {
Chris@76 744
Chris@908 745 sv_frame_t frame = 0;
Chris@360 746
Chris@360 747 if (!realign) {
Chris@360 748
Chris@360 749 frame = i->getFrame();
Chris@360 750
Chris@360 751 } else {
Chris@360 752
Chris@1423 753 if (i->hasReferenceFrame()) {
Chris@360 754 frame = i->getReferenceFrame();
Chris@360 755 frame = alignFromReference(v, frame);
Chris@360 756 } else {
Chris@360 757 frame = i->getFrame();
Chris@360 758 }
Chris@76 759 }
Chris@360 760
Chris@1533 761 Event p = i->withFrame(frame);
Chris@1533 762
Chris@1437 763 Event newPoint = p;
Chris@1437 764 if (p.hasValue()) {
Chris@1437 765 newPoint = newPoint.withValue(float((i->getValue() - valueMin) /
Chris@1437 766 (valueMax - valueMin)));
Chris@125 767 } else {
Chris@1437 768 newPoint = newPoint.withValue(0.5f);
Chris@125 769 }
Chris@125 770
Chris@1437 771 if (!p.hasLabel()) {
Chris@1437 772 if (p.hasValue()) {
Chris@1437 773 newPoint = newPoint.withLabel(QString("%1").arg(p.getValue()));
Chris@1437 774 } else {
Chris@1437 775 newPoint = newPoint.withLabel(tr("New Point"));
Chris@1437 776 }
Chris@125 777 }
Chris@76 778
Chris@1437 779 command->add(newPoint);
Chris@76 780 }
Chris@76 781
Chris@376 782 finish(command);
Chris@125 783 return true;
Chris@76 784 }
Chris@76 785
Chris@287 786 int
Chris@287 787 TextLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 788 {
Chris@287 789 impose = false;
Chris@287 790 return ColourDatabase::getInstance()->getColourIndex
Chris@287 791 (QString(darkbg ? "Bright Orange" : "Orange"));
Chris@287 792 }
Chris@287 793
Chris@316 794 void
Chris@316 795 TextLayer::toXml(QTextStream &stream,
Chris@316 796 QString indent, QString extraAttributes) const
Chris@35 797 {
Chris@316 798 SingleColourLayer::toXml(stream, indent, extraAttributes);
Chris@35 799 }
Chris@35 800
Chris@35 801 void
Chris@35 802 TextLayer::setProperties(const QXmlAttributes &attributes)
Chris@35 803 {
Chris@287 804 SingleColourLayer::setProperties(attributes);
Chris@35 805 }
Chris@35 806