annotate layer/TimeInstantLayer.cpp @ 1619:36634b427d61

Fix wrongly-written test which made the mapping alignments line up wrongly in some cases where adjacent panes were related (but, because of this test, the alignment view thought they were not)
author Chris Cannam
date Tue, 18 Aug 2020 14:49:36 +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