annotate layer/TimeInstantLayer.cpp @ 1447:8afea53332f3 single-point

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