annotate layer/TimeInstantLayer.cpp @ 1471:f2525e6cbdf1 by-id

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