annotate layer/TimeInstantLayer.cpp @ 1512:0fa155b84bac time-frequency-boxes

Add TimeFrequencyBoxes to layer factory
author Chris Cannam
date Thu, 19 Sep 2019 17:15:36 +0100
parents 36ad3cdabf55
children 37df1530519d
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@1266 280 SnapType snap) const
Chris@13 281 {
Chris@1474 282 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 283 if (!model) {
Chris@1266 284 return Layer::snapToFeatureFrame(v, frame, resolution, snap);
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@20 324 int x0 = rect.left(), x1 = rect.right();
Chris@0 325
Chris@989 326 sv_frame_t frame0 = v->getFrameForX(x0);
Chris@989 327 sv_frame_t frame1 = v->getFrameForX(x1);
Chris@0 328
Chris@1461 329 int overspill = 0;
Chris@1461 330 if (m_plotStyle == PlotSegmentation) {
Chris@1461 331 // We need to start painting at the prior point, so we can
Chris@1461 332 // fill in the visible part of its segmentation area
Chris@1461 333 overspill = 1;
Chris@1461 334 }
Chris@1461 335
Chris@1474 336 EventVector points(model->getEventsWithin(frame0, frame1 - frame0,
Chris@1461 337 overspill));
Chris@0 338
Chris@28 339 bool odd = false;
Chris@28 340 if (m_plotStyle == PlotSegmentation && !points.empty()) {
Chris@1474 341 int index = model->getRowForFrame(points.begin()->getFrame());
Chris@1266 342 odd = ((index % 2) == 1);
Chris@28 343 }
Chris@28 344
Chris@287 345 paint.setPen(getBaseQColor());
Chris@0 346
Chris@287 347 QColor brushColour(getBaseQColor());
Chris@0 348 brushColour.setAlpha(100);
Chris@0 349 paint.setBrush(brushColour);
Chris@0 350
Chris@28 351 QColor oddBrushColour(brushColour);
Chris@28 352 if (m_plotStyle == PlotSegmentation) {
Chris@1266 353 if (getBaseQColor() == Qt::black) {
Chris@1266 354 oddBrushColour = Qt::gray;
Chris@1266 355 } else if (getBaseQColor() == Qt::darkRed) {
Chris@1266 356 oddBrushColour = Qt::red;
Chris@1266 357 } else if (getBaseQColor() == Qt::darkBlue) {
Chris@1266 358 oddBrushColour = Qt::blue;
Chris@1266 359 } else if (getBaseQColor() == Qt::darkGreen) {
Chris@1266 360 oddBrushColour = Qt::green;
Chris@1266 361 } else {
Chris@1474 362 oddBrushColour = oddBrushColour.lighter(150);
Chris@1266 363 }
Chris@1266 364 oddBrushColour.setAlpha(100);
Chris@28 365 }
Chris@28 366
Chris@587 367 // SVDEBUG << "TimeInstantLayer::paint: resolution is "
Chris@1474 368 // << model->getResolution() << " frames" << endl;
Chris@0 369
Chris@0 370 QPoint localPos;
Chris@989 371 sv_frame_t illuminateFrame = -1;
Chris@0 372
Chris@44 373 if (v->shouldIlluminateLocalFeatures(this, localPos)) {
Chris@1433 374 EventVector localPoints = getLocalPoints(v, localPos.x());
Chris@1433 375 if (!localPoints.empty()) {
Chris@1433 376 illuminateFrame = localPoints.begin()->getFrame();
Chris@1433 377 }
Chris@0 378 }
Chris@1266 379
Chris@23 380 int prevX = -1;
Chris@79 381 int textY = v->getTextLabelHeight(this, paint);
Chris@79 382
Chris@1433 383 for (EventVector::const_iterator i = points.begin();
Chris@1266 384 i != points.end(); ++i) {
Chris@0 385
Chris@1433 386 Event p(*i);
Chris@1433 387 EventVector::const_iterator j = i;
Chris@1266 388 ++j;
Chris@0 389
Chris@1433 390 int x = v->getXForFrame(p.getFrame());
Chris@576 391 if (x == prevX && m_plotStyle == PlotInstants &&
Chris@1433 392 p.getFrame() != illuminateFrame) continue;
Chris@23 393
Chris@1474 394 int iw = v->getXForFrame(p.getFrame() + model->getResolution()) - x;
Chris@1266 395 if (iw < 2) {
Chris@1266 396 if (iw < 1) {
Chris@1266 397 iw = 2;
Chris@1266 398 if (j != points.end()) {
Chris@1433 399 int nx = v->getXForFrame(j->getFrame());
Chris@1266 400 if (nx < x + 3) iw = 1;
Chris@1266 401 }
Chris@1266 402 } else {
Chris@1266 403 iw = 2;
Chris@1266 404 }
Chris@1266 405 }
Chris@1266 406
Chris@1433 407 if (p.getFrame() == illuminateFrame) {
Chris@1266 408 paint.setPen(getForegroundQColor(v->getView()));
Chris@1266 409 } else {
Chris@1266 410 paint.setPen(brushColour);
Chris@1266 411 }
Chris@23 412
Chris@1266 413 if (m_plotStyle == PlotInstants) {
Chris@1266 414 if (iw > 1) {
Chris@1266 415 paint.drawRect(x, 0, iw - 1, v->getPaintHeight() - 1);
Chris@1266 416 } else {
Chris@1266 417 paint.drawLine(x, 0, x, v->getPaintHeight() - 1);
Chris@1266 418 }
Chris@1266 419 } else {
Chris@28 420
Chris@1266 421 if (odd) paint.setBrush(oddBrushColour);
Chris@1266 422 else paint.setBrush(brushColour);
Chris@1266 423
Chris@1266 424 int nx;
Chris@1266 425
Chris@1266 426 if (j != points.end()) {
Chris@1433 427 Event q(*j);
Chris@1433 428 nx = v->getXForFrame(q.getFrame());
Chris@1266 429 } else {
Chris@1474 430 nx = v->getXForFrame(model->getEndFrame());
Chris@1266 431 }
Chris@28 432
Chris@1266 433 if (nx >= x) {
Chris@1266 434
Chris@1433 435 if (illuminateFrame != p.getFrame() &&
Chris@1266 436 (nx < x + 5 || x >= v->getPaintWidth() - 1)) {
Chris@1266 437 paint.setPen(Qt::NoPen);
Chris@1266 438 }
Chris@28 439
Chris@918 440 paint.drawRect(x, -1, nx - x, v->getPaintHeight() + 1);
Chris@1266 441 }
Chris@28 442
Chris@1266 443 odd = !odd;
Chris@1266 444 }
Chris@28 445
Chris@1266 446 paint.setPen(getBaseQColor());
Chris@1266 447
Chris@1433 448 if (p.getLabel() != "") {
Chris@1474 449
Chris@1474 450 // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
Chris@1474 451 // replacement (horizontalAdvance) was only added in Qt 5.11
Chris@1474 452 // which is too new for us
Chris@1474 453 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Chris@0 454
Chris@1266 455 // only draw if there's enough room from here to the next point
Chris@0 456
Chris@1433 457 int lw = paint.fontMetrics().width(p.getLabel());
Chris@1266 458 bool good = true;
Chris@0 459
Chris@1266 460 if (j != points.end()) {
Chris@1433 461 int nx = v->getXForFrame(j->getFrame());
Chris@1266 462 if (nx >= x && nx - x - iw - 3 <= lw) good = false;
Chris@1266 463 }
Chris@0 464
Chris@1266 465 if (good) {
Chris@1273 466 PaintAssistant::drawVisibleText(v, paint,
Chris@1273 467 x + iw + 2, textY,
Chris@1433 468 p.getLabel(),
Chris@1273 469 PaintAssistant::OutlinedText);
Chris@1266 470 }
Chris@1266 471 }
Chris@23 472
Chris@1266 473 prevX = x;
Chris@0 474 }
Chris@0 475 }
Chris@0 476
Chris@17 477 void
Chris@918 478 TimeInstantLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@17 479 {
Chris@387 480 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682 481 cerr << "TimeInstantLayer::drawStart(" << e->x() << ")" << endl;
Chris@387 482 #endif
Chris@17 483
Chris@1474 484 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 485 if (!model) return;
Chris@17 486
Chris@989 487 sv_frame_t frame = v->getFrameForX(e->x());
Chris@17 488 if (frame < 0) frame = 0;
Chris@1474 489 frame = frame / model->getResolution() * model->getResolution();
Chris@22 490
Chris@1433 491 m_editingPoint = Event(frame, tr("New Point"));
Chris@22 492
Chris@376 493 if (m_editingCommand) finish(m_editingCommand);
Chris@1474 494 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Draw Point"));
Chris@1433 495 m_editingCommand->add(m_editingPoint);
Chris@22 496
Chris@18 497 m_editing = true;
Chris@17 498 }
Chris@17 499
Chris@17 500 void
Chris@918 501 TimeInstantLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@17 502 {
Chris@387 503 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682 504 cerr << "TimeInstantLayer::drawDrag(" << e->x() << ")" << endl;
Chris@387 505 #endif
Chris@17 506
Chris@1474 507 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 508 if (!model || !m_editing) return;
Chris@17 509
Chris@989 510 sv_frame_t frame = v->getFrameForX(e->x());
Chris@17 511 if (frame < 0) frame = 0;
Chris@1474 512 frame = frame / model->getResolution() * model->getResolution();
Chris@1433 513 m_editingCommand->remove(m_editingPoint);
Chris@1433 514 m_editingPoint = m_editingPoint.withFrame(frame);
Chris@1433 515 m_editingCommand->add(m_editingPoint);
Chris@17 516 }
Chris@17 517
Chris@17 518 void
Chris@918 519 TimeInstantLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
Chris@17 520 {
Chris@387 521 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682 522 cerr << "TimeInstantLayer::drawEnd(" << e->x() << ")" << endl;
Chris@387 523 #endif
Chris@1474 524 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 525 if (!model || !m_editing) return;
Chris@23 526 QString newName = tr("Add Point at %1 s")
Chris@1433 527 .arg(RealTime::frame2RealTime(m_editingPoint.getFrame(),
Chris@1474 528 model->getSampleRate())
Chris@1266 529 .toText(false).c_str());
Chris@23 530 m_editingCommand->setName(newName);
Chris@376 531 finish(m_editingCommand);
Chris@1408 532 m_editingCommand = nullptr;
Chris@18 533 m_editing = false;
Chris@18 534 }
Chris@18 535
Chris@18 536 void
Chris@918 537 TimeInstantLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 538 {
Chris@1474 539 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 540 if (!model) return;
Chris@335 541
Chris@1433 542 EventVector points = getLocalPoints(v, e->x());
Chris@335 543 if (points.empty()) return;
Chris@335 544
Chris@335 545 m_editingPoint = *points.begin();
Chris@335 546
Chris@335 547 if (m_editingCommand) {
Chris@1266 548 finish(m_editingCommand);
Chris@1408 549 m_editingCommand = nullptr;
Chris@335 550 }
Chris@335 551
Chris@335 552 m_editing = true;
Chris@335 553 }
Chris@335 554
Chris@335 555 void
Chris@918 556 TimeInstantLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
Chris@335 557 {
Chris@335 558 }
Chris@335 559
Chris@335 560 void
Chris@918 561 TimeInstantLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
Chris@335 562 {
Chris@1474 563 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 564 if (!model || !m_editing) return;
Chris@335 565
Chris@335 566 m_editing = false;
Chris@335 567
Chris@1433 568 EventVector points = getLocalPoints(v, e->x());
Chris@335 569 if (points.empty()) return;
Chris@1433 570 if (points.begin()->getFrame() != m_editingPoint.getFrame()) return;
Chris@335 571
Chris@1474 572 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Erase Point"));
Chris@1433 573 m_editingCommand->remove(m_editingPoint);
Chris@376 574 finish(m_editingCommand);
Chris@1408 575 m_editingCommand = nullptr;
Chris@335 576 m_editing = false;
Chris@335 577 }
Chris@335 578
Chris@335 579 void
Chris@918 580 TimeInstantLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
Chris@18 581 {
Chris@387 582 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682 583 cerr << "TimeInstantLayer::editStart(" << e->x() << ")" << endl;
Chris@387 584 #endif
Chris@18 585
Chris@1474 586 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 587 if (!model) return;
Chris@18 588
Chris@1433 589 EventVector points = getLocalPoints(v, e->x());
Chris@18 590 if (points.empty()) return;
Chris@18 591
Chris@18 592 m_editingPoint = *points.begin();
Chris@22 593
Chris@22 594 if (m_editingCommand) {
Chris@1266 595 finish(m_editingCommand);
Chris@1408 596 m_editingCommand = nullptr;
Chris@22 597 }
Chris@22 598
Chris@18 599 m_editing = true;
Chris@18 600 }
Chris@18 601
Chris@18 602 void
Chris@918 603 TimeInstantLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
Chris@18 604 {
Chris@387 605 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682 606 cerr << "TimeInstantLayer::editDrag(" << e->x() << ")" << endl;
Chris@387 607 #endif
Chris@18 608
Chris@1474 609 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 610 if (!model || !m_editing) return;
Chris@18 611
Chris@989 612 sv_frame_t frame = v->getFrameForX(e->x());
Chris@18 613 if (frame < 0) frame = 0;
Chris@1474 614 frame = frame / model->getResolution() * model->getResolution();
Chris@22 615
Chris@22 616 if (!m_editingCommand) {
Chris@1474 617 m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Drag Point"));
Chris@22 618 }
Chris@22 619
Chris@1433 620 m_editingCommand->remove(m_editingPoint);
Chris@1433 621 m_editingPoint = m_editingPoint.withFrame(frame);
Chris@1433 622 m_editingCommand->add(m_editingPoint);
Chris@18 623 }
Chris@18 624
Chris@18 625 void
Chris@918 626 TimeInstantLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
Chris@18 627 {
Chris@387 628 #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682 629 cerr << "TimeInstantLayer::editEnd(" << e->x() << ")" << endl;
Chris@387 630 #endif
Chris@1474 631 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 632 if (!model || !m_editing) return;
Chris@23 633 if (m_editingCommand) {
Chris@1266 634 QString newName = tr("Move Point to %1 s")
Chris@1433 635 .arg(RealTime::frame2RealTime(m_editingPoint.getFrame(),
Chris@1474 636 model->getSampleRate())
Chris@1266 637 .toText(false).c_str());
Chris@1266 638 m_editingCommand->setName(newName);
Chris@1266 639 finish(m_editingCommand);
Chris@23 640 }
Chris@1408 641 m_editingCommand = nullptr;
Chris@18 642 m_editing = false;
Chris@17 643 }
Chris@17 644
Chris@255 645 bool
Chris@918 646 TimeInstantLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
Chris@70 647 {
Chris@1474 648 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 649 if (!model) return false;
Chris@70 650
Chris@1433 651 EventVector points = getLocalPoints(v, e->x());
Chris@255 652 if (points.empty()) return false;
Chris@70 653
Chris@1433 654 Event point = *points.begin();
Chris@70 655
Chris@70 656 ItemEditDialog *dialog = new ItemEditDialog
Chris@1474 657 (model->getSampleRate(),
Chris@70 658 ItemEditDialog::ShowTime |
Chris@70 659 ItemEditDialog::ShowText);
Chris@70 660
Chris@1433 661 dialog->setFrameTime(point.getFrame());
Chris@1433 662 dialog->setText(point.getLabel());
Chris@70 663
Chris@70 664 if (dialog->exec() == QDialog::Accepted) {
Chris@70 665
Chris@1433 666 Event newPoint = point
Chris@1433 667 .withFrame(dialog->getFrameTime())
Chris@1433 668 .withLabel(dialog->getText());
Chris@70 669
Chris@1433 670 ChangeEventsCommand *command =
Chris@1474 671 new ChangeEventsCommand(m_model.untyped, tr("Edit Point"));
Chris@1433 672 command->remove(point);
Chris@1433 673 command->add(newPoint);
Chris@376 674 finish(command);
Chris@70 675 }
Chris@70 676
Chris@70 677 delete dialog;
Chris@255 678 return true;
Chris@70 679 }
Chris@70 680
Chris@70 681 void
Chris@908 682 TimeInstantLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
Chris@43 683 {
Chris@1474 684 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 685 if (!model) return;
Chris@99 686
Chris@1433 687 ChangeEventsCommand *command =
Chris@1474 688 new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
Chris@43 689
Chris@1433 690 EventVector points =
Chris@1474 691 model->getEventsWithin(s.getStartFrame(), s.getDuration());
Chris@43 692
Chris@1433 693 for (auto p: points) {
Chris@1433 694 Event newPoint = p
Chris@1433 695 .withFrame(p.getFrame() + newStartFrame - s.getStartFrame());
Chris@1433 696 command->remove(p);
Chris@1433 697 command->add(newPoint);
Chris@43 698 }
Chris@43 699
Chris@376 700 finish(command);
Chris@43 701 }
Chris@43 702
Chris@43 703 void
Chris@43 704 TimeInstantLayer::resizeSelection(Selection s, Selection newSize)
Chris@43 705 {
Chris@1474 706 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 707 if (!model) return;
Chris@99 708
Chris@1433 709 ChangeEventsCommand *command =
Chris@1474 710 new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
Chris@43 711
Chris@1433 712 EventVector points =
Chris@1474 713 model->getEventsWithin(s.getStartFrame(), s.getDuration());
Chris@43 714
Chris@1433 715 double ratio = double(newSize.getDuration()) / double(s.getDuration());
Chris@1433 716 double oldStart = double(s.getStartFrame());
Chris@1433 717 double newStart = double(newSize.getStartFrame());
Chris@43 718
Chris@1433 719 for (auto p: points) {
Chris@43 720
Chris@1433 721 double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
Chris@43 722
Chris@1433 723 Event newPoint = p
Chris@1433 724 .withFrame(lrint(newFrame));
Chris@1433 725 command->remove(p);
Chris@1433 726 command->add(newPoint);
Chris@43 727 }
Chris@43 728
Chris@376 729 finish(command);
Chris@43 730 }
Chris@43 731
Chris@43 732 void
Chris@43 733 TimeInstantLayer::deleteSelection(Selection s)
Chris@43 734 {
Chris@1474 735 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 736 if (!model) return;
Chris@99 737
Chris@1433 738 ChangeEventsCommand *command =
Chris@1474 739 new ChangeEventsCommand(m_model.untyped, tr("Delete Selection"));
Chris@43 740
Chris@1433 741 EventVector points =
Chris@1474 742 model->getEventsWithin(s.getStartFrame(), s.getDuration());
Chris@43 743
Chris@1433 744 for (auto p: points) {
Chris@1433 745 command->remove(p);
Chris@43 746 }
Chris@43 747
Chris@376 748 finish(command);
Chris@43 749 }
Chris@76 750
Chris@76 751 void
Chris@918 752 TimeInstantLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
Chris@76 753 {
Chris@1474 754 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 755 if (!model) return;
Chris@99 756
Chris@1433 757 EventVector points =
Chris@1474 758 model->getEventsWithin(s.getStartFrame(), s.getDuration());
Chris@76 759
Chris@1433 760 for (auto p: points) {
Chris@1433 761 to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
Chris@76 762 }
Chris@76 763 }
Chris@76 764
Chris@125 765 bool
Chris@918 766 TimeInstantLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset, bool)
Chris@76 767 {
Chris@1474 768 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 769 if (!model) return false;
Chris@99 770
Chris@1433 771 EventVector points = from.getPoints();
Chris@76 772
Chris@358 773 bool realign = false;
Chris@358 774
Chris@360 775 if (clipboardHasDifferentAlignment(v, from)) {
Chris@358 776
Chris@360 777 QMessageBox::StandardButton button =
Chris@918 778 QMessageBox::question(v->getView(), tr("Re-align pasted instants?"),
Chris@360 779 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 780 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@360 781 QMessageBox::Yes);
Chris@358 782
Chris@360 783 if (button == QMessageBox::Cancel) {
Chris@360 784 return false;
Chris@360 785 }
Chris@358 786
Chris@360 787 if (button == QMessageBox::Yes) {
Chris@360 788 realign = true;
Chris@360 789 }
Chris@358 790 }
Chris@358 791
Chris@1433 792 ChangeEventsCommand *command =
Chris@1474 793 new ChangeEventsCommand(m_model.untyped, tr("Paste"));
Chris@358 794
Chris@1423 795 for (EventVector::const_iterator i = points.begin();
Chris@76 796 i != points.end(); ++i) {
Chris@76 797
Chris@908 798 sv_frame_t frame = 0;
Chris@359 799
Chris@359 800 if (!realign) {
Chris@359 801
Chris@359 802 frame = i->getFrame();
Chris@359 803
Chris@359 804 } else {
Chris@359 805
Chris@1423 806 if (i->hasReferenceFrame()) {
Chris@359 807 frame = i->getReferenceFrame();
Chris@359 808 frame = alignFromReference(v, frame);
Chris@359 809 } else {
Chris@359 810 frame = i->getFrame();
Chris@359 811 }
Chris@76 812 }
Chris@359 813
Chris@359 814 if (frameOffset > 0) frame += frameOffset;
Chris@359 815 else if (frameOffset < 0) {
Chris@359 816 if (frame > -frameOffset) frame += frameOffset;
Chris@359 817 else frame = 0;
Chris@359 818 }
Chris@359 819
Chris@1433 820 Event newPoint = *i;
Chris@1433 821 if (!i->hasLabel() && i->hasValue()) {
Chris@1433 822 newPoint = newPoint.withLabel(QString("%1").arg(i->getValue()));
Chris@125 823 }
Chris@76 824
Chris@1433 825 command->add(newPoint);
Chris@76 826 }
Chris@76 827
Chris@376 828 finish(command);
Chris@125 829 return true;
Chris@76 830 }
Chris@43 831
Chris@287 832 int
Chris@287 833 TimeInstantLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 834 {
Chris@287 835 impose = false;
Chris@287 836 return ColourDatabase::getInstance()->getColourIndex
Chris@287 837 (QString(darkbg ? "Bright Purple" : "Purple"));
Chris@287 838 }
Chris@287 839
Chris@316 840 void
Chris@316 841 TimeInstantLayer::toXml(QTextStream &stream,
Chris@316 842 QString indent, QString extraAttributes) const
Chris@6 843 {
Chris@316 844 SingleColourLayer::toXml(stream, indent,
Chris@316 845 extraAttributes +
Chris@316 846 QString(" plotStyle=\"%1\"")
Chris@316 847 .arg(m_plotStyle));
Chris@6 848 }
Chris@0 849
Chris@11 850 void
Chris@11 851 TimeInstantLayer::setProperties(const QXmlAttributes &attributes)
Chris@11 852 {
Chris@287 853 SingleColourLayer::setProperties(attributes);
Chris@28 854
Chris@28 855 bool ok;
Chris@28 856 PlotStyle style = (PlotStyle)
Chris@1266 857 attributes.value("plotStyle").toInt(&ok);
Chris@28 858 if (ok) setPlotStyle(style);
Chris@11 859 }
Chris@11 860