annotate layer/TimeInstantLayer.cpp @ 1614:c6f5c822b10d

Fix potential divide-by-zero (depending on a race elsewhere)
author Chris Cannam
date Tue, 30 Jun 2020 10:56:56 +0100
parents 9a5eede01869
children
rev   line source
Chris@58 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@0 2
Chris@0 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@0 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@0 14 */
Chris@0 15
Chris@0 16 #include "TimeInstantLayer.h"
Chris@0 17
Chris@128 18 #include "data/model/Model.h"
Chris@0 19 #include "base/RealTime.h"
Chris@128 20 #include "view/View.h"
Chris@0 21 #include "base/Profiler.h"
Chris@76 22 #include "base/Clipboard.h"
Chris@1078 23
Chris@376 24 #include "ColourDatabase.h"
Chris@1078 25 #include "PaintAssistant.h"
Chris@0 26
Chris@128 27 #include "data/model/SparseOneDimensionalModel.h"
Chris@0 28
Chris@70 29 #include "widgets/ItemEditDialog.h"
Chris@358 30 #include "widgets/ListInputDialog.h"
Chris@70 31
Chris@0 32 #include <QPainter>
Chris@17 33 #include <QMouseEvent>
Chris@316 34 #include <QTextStream>
Chris@360 35 #include <QMessageBox>
Chris@0 36
Chris@0 37 #include <iostream>
Martin@46 38 #include <cmath>
Chris@0 39
Chris@429 40 //#define DEBUG_TIME_INSTANT_LAYER 1
Chris@387 41
Chris@44 42 TimeInstantLayer::TimeInstantLayer() :
Chris@287 43 SingleColourLayer(),
Chris@18 44 m_editing(false),
Chris@17 45 m_editingPoint(0, tr("New Point")),
Chris@1408 46 m_editingCommand(nullptr),
Chris@28 47 m_plotStyle(PlotInstants)
Chris@0 48 {
Chris@308 49 }
Chris@308 50
Chris@308 51 TimeInstantLayer::~TimeInstantLayer()
Chris@308 52 {
Chris@0 53 }
Chris@0 54
Chris@1470 55 int
Chris@1470 56 TimeInstantLayer::getCompletion(LayerGeometryProvider *) const
Chris@1470 57 {
Chris@1470 58 auto model = ModelById::get(m_model);
Chris@1470 59 if (model) return model->getCompletion();
Chris@1470 60 else return 0;
Chris@1470 61 }
Chris@1470 62
Chris@0 63 void
Chris@1470 64 TimeInstantLayer::setModel(ModelId modelId)
Chris@0 65 {
Chris@1471 66 auto newModel = ModelById::getAs<SparseOneDimensionalModel>(modelId);
Chris@1471 67 if (!modelId.isNone() && !newModel) {
Chris@1471 68 throw std::logic_error("Not a SparseOneDimensionalModel");
Chris@1471 69 }
Chris@1471 70
Chris@1470 71 if (m_model == modelId) return;
Chris@1470 72 m_model = modelId;
Chris@0 73
Chris@1471 74 if (newModel) {
Chris@1471 75 connectSignals(m_model);
Chris@1471 76 if (newModel->getRDFTypeURI().endsWith("Segment")) {
Chris@1471 77 setPlotStyle(PlotSegmentation);
Chris@1471 78 }
Chris@494 79 }
Chris@494 80
Chris@0 81 emit modelReplaced();
Chris@0 82 }
Chris@0 83
Chris@0 84 Layer::PropertyList
Chris@0 85 TimeInstantLayer::getProperties() const
Chris@0 86 {
Chris@287 87 PropertyList list = SingleColourLayer::getProperties();
Chris@87 88 list.push_back("Plot Type");
Chris@0 89 return list;
Chris@0 90 }
Chris@0 91
Chris@87 92 QString
Chris@87 93 TimeInstantLayer::getPropertyLabel(const PropertyName &name) const
Chris@87 94 {
Chris@87 95 if (name == "Plot Type") return tr("Plot Type");
Chris@287 96 return SingleColourLayer::getPropertyLabel(name);
Chris@87 97 }
Chris@87 98
Chris@0 99 Layer::PropertyType
Chris@287 100 TimeInstantLayer::getPropertyType(const PropertyName &name) const
Chris@0 101 {
Chris@287 102 if (name == "Plot Type") return ValueProperty;
Chris@287 103 return SingleColourLayer::getPropertyType(name);
Chris@0 104 }
Chris@0 105
Chris@0 106 int
Chris@0 107 TimeInstantLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@216 108 int *min, int *max, int *deflt) const
Chris@0 109 {
Chris@216 110 int val = 0;
Chris@0 111
Chris@287 112 if (name == "Plot Type") {
Chris@1266 113
Chris@1266 114 if (min) *min = 0;
Chris@1266 115 if (max) *max = 1;
Chris@216 116 if (deflt) *deflt = 0;
Chris@1266 117
Chris@1266 118 val = int(m_plotStyle);
Chris@28 119
Chris@0 120 } else {
Chris@1266 121
Chris@1266 122 val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@0 123 }
Chris@0 124
Chris@216 125 return val;
Chris@0 126 }
Chris@0 127
Chris@0 128 QString
Chris@0 129 TimeInstantLayer::getPropertyValueLabel(const PropertyName &name,
Chris@287 130 int value) const
Chris@0 131 {
Chris@287 132 if (name == "Plot Type") {
Chris@1266 133 switch (value) {
Chris@1266 134 default:
Chris@1266 135 case 0: return tr("Instants");
Chris@1266 136 case 1: return tr("Segmentation");
Chris@1266 137 }
Chris@0 138 }
Chris@287 139 return SingleColourLayer::getPropertyValueLabel(name, value);
Chris@0 140 }
Chris@0 141
Chris@0 142 void
Chris@0 143 TimeInstantLayer::setProperty(const PropertyName &name, int value)
Chris@0 144 {
Chris@287 145 if (name == "Plot Type") {
Chris@1266 146 setPlotStyle(PlotStyle(value));
Chris@287 147 } else {
Chris@287 148 SingleColourLayer::setProperty(name, value);
Chris@0 149 }
Chris@0 150 }
Chris@0 151
Chris@0 152 void
Chris@28 153 TimeInstantLayer::setPlotStyle(PlotStyle style)
Chris@28 154 {
Chris@28 155 if (m_plotStyle == style) return;
Chris@28 156 m_plotStyle = style;
Chris@28 157 emit layerParametersChanged();
Chris@28 158 }
Chris@28 159
Chris@0 160 bool
Chris@1470 161 TimeInstantLayer::needsTextLabelHeight() const
Chris@1470 162 {
Chris@1470 163 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 164 if (model) return model->hasTextLabels();
Chris@1470 165 else return false;
Chris@1470 166 }
Chris@1470 167
Chris@1470 168 bool
Chris@918 169 TimeInstantLayer::isLayerScrollable(const LayerGeometryProvider *v) const
Chris@0 170 {
Chris@0 171 QPoint discard;
Chris@44 172 return !v->shouldIlluminateLocalFeatures(this, discard);
Chris@0 173 }
Chris@0 174
Chris@1433 175 EventVector
Chris@918 176 TimeInstantLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
Chris@0 177 {
Chris@1474 178 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 179 if (!model) return {};
Chris@1433 180
Chris@28 181 // Return a set of points that all have the same frame number, the
Chris@28 182 // nearest to the given x coordinate, and that are within a
Chris@28 183 // certain fuzz distance of that x coordinate.
Chris@28 184
Chris@989 185 sv_frame_t frame = v->getFrameForX(x);
Chris@0 186
Chris@1474 187 EventVector exact = model->getEventsStartingAt(frame);
Chris@1433 188 if (!exact.empty()) return exact;
Chris@0 189
Chris@1433 190 // overspill == 1, so one event either side of the given span
Chris@1474 191 EventVector neighbouring = model->getEventsWithin
Chris@1474 192 (frame, model->getResolution(), 1);
Chris@0 193
Chris@1433 194 double fuzz = v->scaleSize(2);
Chris@1433 195 sv_frame_t suitable = 0;
Chris@1433 196 bool have = false;
Chris@1433 197
Chris@1433 198 for (Event e: neighbouring) {
Chris@1433 199 sv_frame_t f = e.getFrame();
Chris@1433 200 if (f < v->getStartFrame() || f > v->getEndFrame()) {
Chris@1433 201 continue;
Chris@1433 202 }
Chris@1433 203 int px = v->getXForFrame(f);
Chris@1433 204 if ((px > x && px - x > fuzz) || (px < x && x - px > fuzz + 3)) {
Chris@1433 205 continue;
Chris@1433 206 }
Chris@1433 207 if (!have) {
Chris@1433 208 suitable = f;
Chris@1433 209 have = true;
Chris@1433 210 } else if (llabs(frame - f) < llabs(suitable - f)) {
Chris@1433 211 suitable = f;
Chris@1266 212 }
Chris@28 213 }
Chris@28 214
Chris@1433 215 if (have) {
Chris@1474 216 return model->getEventsStartingAt(suitable);
Chris@1433 217 } else {
Chris@1433 218 return {};
Chris@1433 219 }
Chris@0 220 }
Chris@0 221
Chris@25 222 QString
Chris@909 223 TimeInstantLayer::getLabelPreceding(sv_frame_t frame) const
Chris@552 224 {
Chris@1474 225 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 226 if (!model || !model->hasTextLabels()) return "";
Chris@1433 227
Chris@1433 228 Event e;
Chris@1474 229 if (model->getNearestEventMatching
Chris@1433 230 (frame,
Chris@1433 231 [](Event e) { return e.hasLabel() && e.getLabel() != ""; },
Chris@1433 232 EventSeries::Backward,
Chris@1433 233 e)) {
Chris@1433 234 return e.getLabel();
Chris@552 235 }
Chris@1433 236
Chris@552 237 return "";
Chris@552 238 }
Chris@552 239
Chris@552 240 QString
Chris@918 241 TimeInstantLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
Chris@0 242 {
Chris@25 243 int x = pos.x();
Chris@0 244
Chris@1474 245 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 246 if (!model || !model->getSampleRate()) return "";
Chris@0 247
Chris@1433 248 EventVector points = getLocalPoints(v, x);
Chris@0 249
Chris@0 250 if (points.empty()) {
Chris@1474 251 if (!model->isReady()) {
Chris@1266 252 return tr("In progress");
Chris@1266 253 } else {
Chris@1266 254 return tr("No local points");
Chris@1266 255 }
Chris@0 256 }
Chris@0 257
Chris@1433 258 sv_frame_t useFrame = points.begin()->getFrame();
Chris@0 259
Chris@1474 260 RealTime rt = RealTime::frame2RealTime(useFrame, model->getSampleRate());
Chris@25 261
Chris@25 262 QString text;
Chris@0 263
Chris@1433 264 if (points.begin()->getLabel() == "") {
Chris@1266 265 text = QString(tr("Time:\t%1\nNo label"))
Chris@1266 266 .arg(rt.toText(true).c_str());
Chris@25 267 } else {
Chris@1266 268 text = QString(tr("Time:\t%1\nLabel:\t%2"))
Chris@1266 269 .arg(rt.toText(true).c_str())
Chris@1433 270 .arg(points.begin()->getLabel());
Chris@25 271 }
Chris@0 272
Chris@44 273 pos = QPoint(v->getXForFrame(useFrame), pos.y());
Chris@25 274 return text;
Chris@0 275 }
Chris@0 276
Chris@28 277 bool
Chris@918 278 TimeInstantLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
Chris@1266 279 int &resolution,
Chris@1547 280 SnapType snap, int ycoord) const
Chris@13 281 {
Chris@1474 282 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 283 if (!model) {
Chris@1547 284 return Layer::snapToFeatureFrame(v, frame, resolution, snap, ycoord);
Chris@13 285 }
Chris@13 286
Chris@1433 287 // SnapLeft / SnapRight: return frame of nearest feature in that
Chris@1433 288 // direction no matter how far away
Chris@1433 289 //
Chris@1433 290 // SnapNeighbouring: return frame of feature that would be used in
Chris@1433 291 // an editing operation, i.e. closest feature in either direction
Chris@1433 292 // but only if it is "close enough"
Chris@1433 293
Chris@1474 294 resolution = model->getResolution();
Chris@13 295
Chris@28 296 if (snap == SnapNeighbouring) {
Chris@1433 297 EventVector points = getLocalPoints(v, v->getXForFrame(frame));
Chris@1266 298 if (points.empty()) return false;
Chris@1433 299 frame = points.begin()->getFrame();
Chris@1266 300 return true;
Chris@13 301 }
Chris@13 302
Chris@1433 303 Event e;
Chris@1474 304 if (model->getNearestEventMatching
Chris@1433 305 (frame,
Chris@1433 306 [](Event) { return true; },
Chris@1433 307 snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
Chris@1433 308 e)) {
Chris@1433 309 frame = e.getFrame();
Chris@1433 310 return true;
Chris@1433 311 }
Chris@1433 312
Chris@1433 313 return false;
Chris@13 314 }
Chris@13 315
Chris@0 316 void
Chris@916 317 TimeInstantLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@0 318 {
Chris@1474 319 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 320 if (!model || !model->isOK()) return;
Chris@0 321
Chris@0 322 // Profiler profiler("TimeInstantLayer::paint", true);
Chris@0 323
Chris@1549 324 int x0 = rect.left();
Chris@1549 325 int x1 = x0 + rect.width();
Chris@0 326
Chris@1549 327 sv_frame_t resolution = model->getResolution();
Chris@1549 328
Chris@1549 329 sv_frame_t frame0 = v->getFrameForX(x0) - resolution;
Chris@989 330 sv_frame_t frame1 = v->getFrameForX(x1);
Chris@0 331
Chris@1549 332 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@1549 333 SVCERR << "TimeInstantLayer[" << this << "]::paint: x0 = "
Chris@1549 334 << x0 << ", x1 = " << x1 << ", frame0 = " << frame0
Chris@1549 335 << ", frame1 = " << frame1 << endl;
Chris@1549 336 #endif
Chris@1549 337
Chris@1461 338 int overspill = 0;
Chris@1461 339 if (m_plotStyle == PlotSegmentation) {
Chris@1461 340 // We need to start painting at the prior point, so we can
Chris@1461 341 // fill in the visible part of its segmentation area
Chris@1461 342 overspill = 1;
Chris@1461 343 }
Chris@1461 344
Chris@1474 345 EventVector points(model->getEventsWithin(frame0, frame1 - frame0,
Chris@1549 346 overspill));
Chris@1549 347
Chris@1549 348 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@1549 349 SVCERR << "TimeInstantLayer[" << this << "]::paint: have " << points.size()
Chris@1549 350 << " point(s) with overspill = " << overspill << " from model "
Chris@1549 351 << getModel() << endl;
Chris@1549 352 #endif
Chris@0 353
Chris@28 354 bool odd = false;
Chris@28 355 if (m_plotStyle == PlotSegmentation && !points.empty()) {
Chris@1474 356 int index = model->getRowForFrame(points.begin()->getFrame());
Chris@1266 357 odd = ((index % 2) == 1);
Chris@28 358 }
Chris@28 359
Chris@287 360 paint.setPen(getBaseQColor());
Chris@0 361
Chris@287 362 QColor brushColour(getBaseQColor());
Chris@0 363 brushColour.setAlpha(100);
Chris@0 364 paint.setBrush(brushColour);
Chris@0 365
Chris@28 366 QColor oddBrushColour(brushColour);
Chris@28 367 if (m_plotStyle == PlotSegmentation) {
Chris@1266 368 if (getBaseQColor() == Qt::black) {
Chris@1266 369 oddBrushColour = Qt::gray;
Chris@1266 370 } else if (getBaseQColor() == Qt::darkRed) {
Chris@1266 371 oddBrushColour = Qt::red;
Chris@1266 372 } else if (getBaseQColor() == Qt::darkBlue) {
Chris@1266 373 oddBrushColour = Qt::blue;
Chris@1266 374 } else if (getBaseQColor() == Qt::darkGreen) {
Chris@1266 375 oddBrushColour = Qt::green;
Chris@1266 376 } else {
Chris@1474 377 oddBrushColour = oddBrushColour.lighter(150);
Chris@1266 378 }
Chris@1266 379 oddBrushColour.setAlpha(100);
Chris@28 380 }
Chris@28 381
Chris@0 382 QPoint localPos;
Chris@989 383 sv_frame_t illuminateFrame = -1;
Chris@0 384
Chris@44 385 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
Chris@1433 386 EventVector localPoints = getLocalPoints(v, localPos.x());
Chris@1433 387 if (!localPoints.empty()) {
Chris@1433 388 illuminateFrame = localPoints.begin()->getFrame();
Chris@1433 389 }
Chris@0 390 }
Chris@1266 391
Chris@23 392 int prevX = -1;
Chris@1537 393 int textY = v->getTextLabelYCoord(this, paint);
Chris@79 394
Chris@1433 395 for (EventVector::const_iterator i = points.begin();
Chris@1266 396 i != points.end(); ++i) {
Chris@0 397
Chris@1433 398 Event p(*i);
Chris@1433 399 EventVector::const_iterator j = i;
Chris@1266 400 ++j;
Chris@0 401
Chris@1433 402 int x = v->getXForFrame(p.getFrame());
Chris@1549 403
Chris@1549 404 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@1549 405 SVCERR << "point frame = " << p.getFrame() << " -> x = " << x << endl;
Chris@1549 406 #endif
Chris@1549 407
Chris@576 408 if (x == prevX && m_plotStyle == PlotInstants &&
Chris@1549 409 p.getFrame() != illuminateFrame) {
Chris@1549 410 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@1549 411 SVCERR << "(skipping)" << endl;
Chris@1549 412 #endif
Chris@1549 413 continue;
Chris@1549 414 }
Chris@23 415
Chris@1474 416 int iw = v->getXForFrame(p.getFrame() + model->getResolution()) - x;
Chris@1266 417 if (iw < 2) {
Chris@1266 418 if (iw < 1) {
Chris@1266 419 iw = 2;
Chris@1266 420 if (j != points.end()) {
Chris@1433 421 int nx = v->getXForFrame(j->getFrame());
Chris@1266 422 if (nx < x + 3) iw = 1;
Chris@1266 423 }
Chris@1266 424 } else {
Chris@1266 425 iw = 2;
Chris@1266 426 }
Chris@1266 427 }
Chris@1266 428
Chris@1433 429 if (p.getFrame() == illuminateFrame) {
Chris@1266 430 paint.setPen(getForegroundQColor(v->getView()));
Chris@1266 431 } else {
Chris@1266 432 paint.setPen(brushColour);
Chris@1266 433 }
Chris@23 434
Chris@1549 435 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@1549 436 SVCERR << "m_plotStyle = " << m_plotStyle << ", iw = " << iw << endl;
Chris@1549 437 #endif
Chris@1549 438
Chris@1266 439 if (m_plotStyle == PlotInstants) {
Chris@1266 440 if (iw > 1) {
Chris@1266 441 paint.drawRect(x, 0, iw - 1, v->getPaintHeight() - 1);
Chris@1266 442 } else {
Chris@1266 443 paint.drawLine(x, 0, x, v->getPaintHeight() - 1);
Chris@1266 444 }
Chris@1266 445 } else {
Chris@28 446
Chris@1266 447 if (odd) paint.setBrush(oddBrushColour);
Chris@1266 448 else paint.setBrush(brushColour);
Chris@1266 449
Chris@1266 450 int nx;
Chris@1266 451
Chris@1266 452 if (j != points.end()) {
Chris@1433 453 Event q(*j);
Chris@1433 454 nx = v->getXForFrame(q.getFrame());
Chris@1266 455 } else {
Chris@1474 456 nx = v->getXForFrame(model->getEndFrame());
Chris@1266 457 }
Chris@28 458
Chris@1266 459 if (nx >= x) {
Chris@1266 460
Chris@1433 461 if (illuminateFrame != p.getFrame() &&
Chris@1266 462 (nx < x + 5 || x >= v->getPaintWidth() - 1)) {
Chris@1266 463 paint.setPen(Qt::NoPen);
Chris@1266 464 }
Chris@28 465
Chris@918 466 paint.drawRect(x, -1, nx - x, v->getPaintHeight() + 1);
Chris@1266 467 }
Chris@28 468
Chris@1266 469 odd = !odd;
Chris@1266 470 }
Chris@28 471
Chris@1266 472 paint.setPen(getBaseQColor());
Chris@1266 473
Chris@1433 474 if (p.getLabel() != "") {
Chris@1474 475
Chris@1474 476 // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
Chris@1474 477 // replacement (horizontalAdvance) was only added in Qt 5.11
Chris@1474 478 // which is too new for us
Chris@1474 479 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Chris@0 480
Chris@1266 481 // only draw if there's enough room from here to the next point
Chris@0 482
Chris@1433 483 int lw = paint.fontMetrics().width(p.getLabel());
Chris@1266 484 bool good = true;
Chris@0 485
Chris@1266 486 if (j != points.end()) {
Chris@1433 487 int nx = v->getXForFrame(j->getFrame());
Chris@1266 488 if (nx >= x && nx - x - iw - 3 <= lw) good = false;
Chris@1266 489 }
Chris@0 490
Chris@1266 491 if (good) {
Chris@1273 492 PaintAssistant::drawVisibleText(v, paint,
Chris@1273 493 x + iw + 2, textY,
Chris@1433 494 p.getLabel(),
Chris@1273 495 PaintAssistant::OutlinedText);
Chris@1266 496 }
Chris@1266 497 }
Chris@23 498
Chris@1266 499 prevX = x;
Chris@0 500 }
Chris@0 501 }
Chris@0 502
Chris@17 503 void
Chris@918 504 TimeInstantLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@17 505 {
Chris@387 506 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682 507 cerr << "TimeInstantLayer::drawStart(" << e->x() << ")" << endl;
Chris@387 508 #endif
Chris@17 509
Chris@1474 510 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 511 if (!model) return;
Chris@17 512
Chris@989 513 sv_frame_t frame = v->getFrameForX(e->x());
Chris@17 514 if (frame < 0) frame = 0;
Chris@1474 515 frame = frame / model->getResolution() * model->getResolution();
Chris@22 516
Chris@1433 517 m_editingPoint = Event(frame, tr("New Point"));
Chris@22 518
Chris@376 519 if (m_editingCommand) finish(m_editingCommand);
Chris@1474 520 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Draw Point"));
Chris@1433 521 m_editingCommand->add(m_editingPoint);
Chris@22 522
Chris@18 523 m_editing = true;
Chris@17 524 }
Chris@17 525
Chris@17 526 void
Chris@918 527 TimeInstantLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@17 528 {
Chris@387 529 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682 530 cerr << "TimeInstantLayer::drawDrag(" << e->x() << ")" << endl;
Chris@387 531 #endif
Chris@17 532
Chris@1474 533 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 534 if (!model || !m_editing) return;
Chris@17 535
Chris@989 536 sv_frame_t frame = v->getFrameForX(e->x());
Chris@17 537 if (frame < 0) frame = 0;
Chris@1474 538 frame = frame / model->getResolution() * model->getResolution();
Chris@1433 539 m_editingCommand->remove(m_editingPoint);
Chris@1433 540 m_editingPoint = m_editingPoint.withFrame(frame);
Chris@1433 541 m_editingCommand->add(m_editingPoint);
Chris@17 542 }
Chris@17 543
Chris@17 544 void
Chris@1549 545 TimeInstantLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *
Chris@1549 546 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@1549 547 e
Chris@1549 548 #endif
Chris@1549 549 )
Chris@17 550 {
Chris@387 551 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682 552 cerr << "TimeInstantLayer::drawEnd(" << e->x() << ")" << endl;
Chris@387 553 #endif
Chris@1474 554 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 555 if (!model || !m_editing) return;
Chris@23 556 QString newName = tr("Add Point at %1 s")
Chris@1433 557 .arg(RealTime::frame2RealTime(m_editingPoint.getFrame(),
Chris@1474 558 model->getSampleRate())
Chris@1266 559 .toText(false).c_str());
Chris@23 560 m_editingCommand->setName(newName);
Chris@376 561 finish(m_editingCommand);
Chris@1408 562 m_editingCommand = nullptr;
Chris@18 563 m_editing = false;
Chris@18 564 }
Chris@18 565
Chris@18 566 void
Chris@918 567 TimeInstantLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 568 {
Chris@1474 569 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 570 if (!model) return;
Chris@335 571
Chris@1433 572 EventVector points = getLocalPoints(v, e->x());
Chris@335 573 if (points.empty()) return;
Chris@335 574
Chris@335 575 m_editingPoint = *points.begin();
Chris@335 576
Chris@335 577 if (m_editingCommand) {
Chris@1266 578 finish(m_editingCommand);
Chris@1408 579 m_editingCommand = nullptr;
Chris@335 580 }
Chris@335 581
Chris@335 582 m_editing = true;
Chris@335 583 }
Chris@335 584
Chris@335 585 void
Chris@918 586 TimeInstantLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
Chris@335 587 {
Chris@335 588 }
Chris@335 589
Chris@335 590 void
Chris@918 591 TimeInstantLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 592 {
Chris@1474 593 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 594 if (!model || !m_editing) return;
Chris@335 595
Chris@335 596 m_editing = false;
Chris@335 597
Chris@1433 598 EventVector points = getLocalPoints(v, e->x());
Chris@335 599 if (points.empty()) return;
Chris@1433 600 if (points.begin()->getFrame() != m_editingPoint.getFrame()) return;
Chris@335 601
Chris@1474 602 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Erase Point"));
Chris@1433 603 m_editingCommand->remove(m_editingPoint);
Chris@376 604 finish(m_editingCommand);
Chris@1408 605 m_editingCommand = nullptr;
Chris@335 606 m_editing = false;
Chris@335 607 }
Chris@335 608
Chris@335 609 void
Chris@918 610 TimeInstantLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@18 611 {
Chris@387 612 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682 613 cerr << "TimeInstantLayer::editStart(" << e->x() << ")" << endl;
Chris@387 614 #endif
Chris@18 615
Chris@1474 616 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 617 if (!model) return;
Chris@18 618
Chris@1433 619 EventVector points = getLocalPoints(v, e->x());
Chris@18 620 if (points.empty()) return;
Chris@18 621
Chris@18 622 m_editingPoint = *points.begin();
Chris@22 623
Chris@22 624 if (m_editingCommand) {
Chris@1266 625 finish(m_editingCommand);
Chris@1408 626 m_editingCommand = nullptr;
Chris@22 627 }
Chris@22 628
Chris@18 629 m_editing = true;
Chris@18 630 }
Chris@18 631
Chris@18 632 void
Chris@918 633 TimeInstantLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@18 634 {
Chris@387 635 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682 636 cerr << "TimeInstantLayer::editDrag(" << e->x() << ")" << endl;
Chris@387 637 #endif
Chris@18 638
Chris@1474 639 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 640 if (!model || !m_editing) return;
Chris@18 641
Chris@989 642 sv_frame_t frame = v->getFrameForX(e->x());
Chris@18 643 if (frame < 0) frame = 0;
Chris@1474 644 frame = frame / model->getResolution() * model->getResolution();
Chris@22 645
Chris@22 646 if (!m_editingCommand) {
Chris@1474 647 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Drag Point"));
Chris@22 648 }
Chris@22 649
Chris@1433 650 m_editingCommand->remove(m_editingPoint);
Chris@1433 651 m_editingPoint = m_editingPoint.withFrame(frame);
Chris@1433 652 m_editingCommand->add(m_editingPoint);
Chris@18 653 }
Chris@18 654
Chris@18 655 void
Chris@1549 656 TimeInstantLayer::editEnd(LayerGeometryProvider *, QMouseEvent *
Chris@1549 657 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@1549 658 e
Chris@1549 659 #endif
Chris@1549 660 )
Chris@18 661 {
Chris@387 662 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682 663 cerr << "TimeInstantLayer::editEnd(" << e->x() << ")" << endl;
Chris@387 664 #endif
Chris@1474 665 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 666 if (!model || !m_editing) return;
Chris@23 667 if (m_editingCommand) {
Chris@1266 668 QString newName = tr("Move Point to %1 s")
Chris@1433 669 .arg(RealTime::frame2RealTime(m_editingPoint.getFrame(),
Chris@1474 670 model->getSampleRate())
Chris@1266 671 .toText(false).c_str());
Chris@1266 672 m_editingCommand->setName(newName);
Chris@1266 673 finish(m_editingCommand);
Chris@23 674 }
Chris@1408 675 m_editingCommand = nullptr;
Chris@18 676 m_editing = false;
Chris@17 677 }
Chris@17 678
Chris@255 679 bool
Chris@918 680 TimeInstantLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
Chris@70 681 {
Chris@1474 682 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 683 if (!model) return false;
Chris@70 684
Chris@1433 685 EventVector points = getLocalPoints(v, e->x());
Chris@255 686 if (points.empty()) return false;
Chris@70 687
Chris@1433 688 Event point = *points.begin();
Chris@70 689
Chris@70 690 ItemEditDialog *dialog = new ItemEditDialog
Chris@1474 691 (model->getSampleRate(),
Chris@70 692 ItemEditDialog::ShowTime |
Chris@70 693 ItemEditDialog::ShowText);
Chris@70 694
Chris@1433 695 dialog->setFrameTime(point.getFrame());
Chris@1433 696 dialog->setText(point.getLabel());
Chris@70 697
Chris@70 698 if (dialog->exec() == QDialog::Accepted) {
Chris@70 699
Chris@1433 700 Event newPoint = point
Chris@1433 701 .withFrame(dialog->getFrameTime())
Chris@1433 702 .withLabel(dialog->getText());
Chris@70 703
Chris@1433 704 ChangeEventsCommand *command =
Chris@1474 705 new ChangeEventsCommand(m_model.untyped, tr("Edit Point"));
Chris@1433 706 command->remove(point);
Chris@1433 707 command->add(newPoint);
Chris@376 708 finish(command);
Chris@70 709 }
Chris@70 710
Chris@70 711 delete dialog;
Chris@255 712 return true;
Chris@70 713 }
Chris@70 714
Chris@70 715 void
Chris@908 716 TimeInstantLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
Chris@43 717 {
Chris@1474 718 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 719 if (!model) return;
Chris@99 720
Chris@1433 721 ChangeEventsCommand *command =
Chris@1474 722 new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
Chris@43 723
Chris@1433 724 EventVector points =
Chris@1474 725 model->getEventsWithin(s.getStartFrame(), s.getDuration());
Chris@43 726
Chris@1433 727 for (auto p: points) {
Chris@1433 728 Event newPoint = p
Chris@1433 729 .withFrame(p.getFrame() + newStartFrame - s.getStartFrame());
Chris@1433 730 command->remove(p);
Chris@1433 731 command->add(newPoint);
Chris@43 732 }
Chris@43 733
Chris@376 734 finish(command);
Chris@43 735 }
Chris@43 736
Chris@43 737 void
Chris@43 738 TimeInstantLayer::resizeSelection(Selection s, Selection newSize)
Chris@43 739 {
Chris@1474 740 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 741 if (!model) return;
Chris@99 742
Chris@1433 743 ChangeEventsCommand *command =
Chris@1474 744 new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
Chris@43 745
Chris@1433 746 EventVector points =
Chris@1474 747 model->getEventsWithin(s.getStartFrame(), s.getDuration());
Chris@43 748
Chris@1433 749 double ratio = double(newSize.getDuration()) / double(s.getDuration());
Chris@1433 750 double oldStart = double(s.getStartFrame());
Chris@1433 751 double newStart = double(newSize.getStartFrame());
Chris@43 752
Chris@1433 753 for (auto p: points) {
Chris@43 754
Chris@1433 755 double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
Chris@43 756
Chris@1433 757 Event newPoint = p
Chris@1433 758 .withFrame(lrint(newFrame));
Chris@1433 759 command->remove(p);
Chris@1433 760 command->add(newPoint);
Chris@43 761 }
Chris@43 762
Chris@376 763 finish(command);
Chris@43 764 }
Chris@43 765
Chris@43 766 void
Chris@43 767 TimeInstantLayer::deleteSelection(Selection s)
Chris@43 768 {
Chris@1474 769 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 770 if (!model) return;
Chris@99 771
Chris@1433 772 ChangeEventsCommand *command =
Chris@1474 773 new ChangeEventsCommand(m_model.untyped, tr("Delete Selection"));
Chris@43 774
Chris@1433 775 EventVector points =
Chris@1474 776 model->getEventsWithin(s.getStartFrame(), s.getDuration());
Chris@43 777
Chris@1433 778 for (auto p: points) {
Chris@1433 779 command->remove(p);
Chris@43 780 }
Chris@43 781
Chris@376 782 finish(command);
Chris@43 783 }
Chris@76 784
Chris@76 785 void
Chris@918 786 TimeInstantLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
Chris@76 787 {
Chris@1474 788 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 789 if (!model) return;
Chris@99 790
Chris@1433 791 EventVector points =
Chris@1474 792 model->getEventsWithin(s.getStartFrame(), s.getDuration());
Chris@76 793
Chris@1433 794 for (auto p: points) {
Chris@1433 795 to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
Chris@76 796 }
Chris@76 797 }
Chris@76 798
Chris@125 799 bool
Chris@1533 800 TimeInstantLayer::paste(LayerGeometryProvider *v, const Clipboard &from,
Chris@1533 801 sv_frame_t frameOffset, bool)
Chris@76 802 {
Chris@1474 803 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 804 if (!model) return false;
Chris@99 805
Chris@1433 806 EventVector points = from.getPoints();
Chris@76 807
Chris@358 808 bool realign = false;
Chris@358 809
Chris@360 810 if (clipboardHasDifferentAlignment(v, from)) {
Chris@358 811
Chris@360 812 QMessageBox::StandardButton button =
Chris@918 813 QMessageBox::question(v->getView(), tr("Re-align pasted instants?"),
Chris@360 814 tr("The instants 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 815 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@360 816 QMessageBox::Yes);
Chris@358 817
Chris@360 818 if (button == QMessageBox::Cancel) {
Chris@360 819 return false;
Chris@360 820 }
Chris@358 821
Chris@360 822 if (button == QMessageBox::Yes) {
Chris@360 823 realign = true;
Chris@360 824 }
Chris@358 825 }
Chris@358 826
Chris@1433 827 ChangeEventsCommand *command =
Chris@1474 828 new ChangeEventsCommand(m_model.untyped, tr("Paste"));
Chris@358 829
Chris@1423 830 for (EventVector::const_iterator i = points.begin();
Chris@76 831 i != points.end(); ++i) {
Chris@76 832
Chris@908 833 sv_frame_t frame = 0;
Chris@359 834
Chris@359 835 if (!realign) {
Chris@359 836
Chris@359 837 frame = i->getFrame();
Chris@359 838
Chris@359 839 } else {
Chris@359 840
Chris@1423 841 if (i->hasReferenceFrame()) {
Chris@359 842 frame = i->getReferenceFrame();
Chris@359 843 frame = alignFromReference(v, frame);
Chris@359 844 } else {
Chris@359 845 frame = i->getFrame();
Chris@359 846 }
Chris@76 847 }
Chris@359 848
Chris@359 849 if (frameOffset > 0) frame += frameOffset;
Chris@359 850 else if (frameOffset < 0) {
Chris@359 851 if (frame > -frameOffset) frame += frameOffset;
Chris@359 852 else frame = 0;
Chris@359 853 }
Chris@359 854
Chris@1533 855 Event newPoint = i->withFrame(frame);
Chris@1533 856
Chris@1433 857 if (!i->hasLabel() && i->hasValue()) {
Chris@1433 858 newPoint = newPoint.withLabel(QString("%1").arg(i->getValue()));
Chris@125 859 }
Chris@76 860
Chris@1433 861 command->add(newPoint);
Chris@76 862 }
Chris@76 863
Chris@376 864 finish(command);
Chris@125 865 return true;
Chris@76 866 }
Chris@43 867
Chris@287 868 int
Chris@287 869 TimeInstantLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 870 {
Chris@287 871 impose = false;
Chris@287 872 return ColourDatabase::getInstance()->getColourIndex
Chris@287 873 (QString(darkbg ? "Bright Purple" : "Purple"));
Chris@287 874 }
Chris@287 875
Chris@316 876 void
Chris@316 877 TimeInstantLayer::toXml(QTextStream &stream,
Chris@316 878 QString indent, QString extraAttributes) const
Chris@6 879 {
Chris@316 880 SingleColourLayer::toXml(stream, indent,
Chris@316 881 extraAttributes +
Chris@316 882 QString(" plotStyle=\"%1\"")
Chris@316 883 .arg(m_plotStyle));
Chris@6 884 }
Chris@0 885
Chris@11 886 void
Chris@11 887 TimeInstantLayer::setProperties(const QXmlAttributes &attributes)
Chris@11 888 {
Chris@287 889 SingleColourLayer::setProperties(attributes);
Chris@28 890
Chris@28 891 bool ok;
Chris@28 892 PlotStyle style = (PlotStyle)
Chris@1266 893 attributes.value("plotStyle").toInt(&ok);
Chris@28 894 if (ok) setPlotStyle(style);
Chris@11 895 }
Chris@11 896