annotate layer/TimeInstantLayer.cpp @ 1551:e79731086b0f

Fixes to NoteLayer, particularly to calculation of vertical scale when model unit is not Hz. To avoid inconsistency we now behave as if the unit is always Hz from the point of view of the external API and display, converting at the point where we obtain values from the events themselves. Also various fixes to editing.
author Chris Cannam
date Thu, 21 Nov 2019 14:02:57 +0000
parents e6362cf5ff1d
children 9a5eede01869
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@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@1537 381 int textY = v->getTextLabelYCoord(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@1533 766 TimeInstantLayer::paste(LayerGeometryProvider *v, const Clipboard &from,
Chris@1533 767 sv_frame_t frameOffset, bool)
Chris@76 768 {
Chris@1474 769 auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
Chris@1474 770 if (!model) return false;
Chris@99 771
Chris@1433 772 EventVector points = from.getPoints();
Chris@76 773
Chris@358 774 bool realign = false;
Chris@358 775
Chris@360 776 if (clipboardHasDifferentAlignment(v, from)) {
Chris@358 777
Chris@360 778 QMessageBox::StandardButton button =
Chris@918 779 QMessageBox::question(v->getView(), tr("Re-align pasted instants?"),
Chris@360 780 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 781 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@360 782 QMessageBox::Yes);
Chris@358 783
Chris@360 784 if (button == QMessageBox::Cancel) {
Chris@360 785 return false;
Chris@360 786 }
Chris@358 787
Chris@360 788 if (button == QMessageBox::Yes) {
Chris@360 789 realign = true;
Chris@360 790 }
Chris@358 791 }
Chris@358 792
Chris@1433 793 ChangeEventsCommand *command =
Chris@1474 794 new ChangeEventsCommand(m_model.untyped, tr("Paste"));
Chris@358 795
Chris@1423 796 for (EventVector::const_iterator i = points.begin();
Chris@76 797 i != points.end(); ++i) {
Chris@76 798
Chris@908 799 sv_frame_t frame = 0;
Chris@359 800
Chris@359 801 if (!realign) {
Chris@359 802
Chris@359 803 frame = i->getFrame();
Chris@359 804
Chris@359 805 } else {
Chris@359 806
Chris@1423 807 if (i->hasReferenceFrame()) {
Chris@359 808 frame = i->getReferenceFrame();
Chris@359 809 frame = alignFromReference(v, frame);
Chris@359 810 } else {
Chris@359 811 frame = i->getFrame();
Chris@359 812 }
Chris@76 813 }
Chris@359 814
Chris@359 815 if (frameOffset > 0) frame += frameOffset;
Chris@359 816 else if (frameOffset < 0) {
Chris@359 817 if (frame > -frameOffset) frame += frameOffset;
Chris@359 818 else frame = 0;
Chris@359 819 }
Chris@359 820
Chris@1533 821 Event newPoint = i->withFrame(frame);
Chris@1533 822
Chris@1433 823 if (!i->hasLabel() && i->hasValue()) {
Chris@1433 824 newPoint = newPoint.withLabel(QString("%1").arg(i->getValue()));
Chris@125 825 }
Chris@76 826
Chris@1433 827 command->add(newPoint);
Chris@76 828 }
Chris@76 829
Chris@376 830 finish(command);
Chris@125 831 return true;
Chris@76 832 }
Chris@43 833
Chris@287 834 int
Chris@287 835 TimeInstantLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287 836 {
Chris@287 837 impose = false;
Chris@287 838 return ColourDatabase::getInstance()->getColourIndex
Chris@287 839 (QString(darkbg ? "Bright Purple" : "Purple"));
Chris@287 840 }
Chris@287 841
Chris@316 842 void
Chris@316 843 TimeInstantLayer::toXml(QTextStream &stream,
Chris@316 844 QString indent, QString extraAttributes) const
Chris@6 845 {
Chris@316 846 SingleColourLayer::toXml(stream, indent,
Chris@316 847 extraAttributes +
Chris@316 848 QString(" plotStyle=\"%1\"")
Chris@316 849 .arg(m_plotStyle));
Chris@6 850 }
Chris@0 851
Chris@11 852 void
Chris@11 853 TimeInstantLayer::setProperties(const QXmlAttributes &attributes)
Chris@11 854 {
Chris@287 855 SingleColourLayer::setProperties(attributes);
Chris@28 856
Chris@28 857 bool ok;
Chris@28 858 PlotStyle style = (PlotStyle)
Chris@1266 859 attributes.value("plotStyle").toInt(&ok);
Chris@28 860 if (ok) setPlotStyle(style);
Chris@11 861 }
Chris@11 862