annotate layer/TimeInstantLayer.cpp @ 1461:e561f0a8d75b

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